Added esp-idf-lib for a lot of sensors. Added the basic design for the BMP280 task.

Mon, 27 Mar 2023 22:13:21 +0200

author
Michiel Broek <mbroek@mbse.eu>
date
Mon, 27 Mar 2023 22:13:21 +0200
changeset 1
1c9894662795
parent 0
f8b0268c8d0a
child 2
3462a53e548f

Added esp-idf-lib for a lot of sensors. Added the basic design for the BMP280 task.

CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/CHANGELOG.md file | annotate | diff | comparison | revisions
esp-idf-lib/CONTRIBUTING.md file | annotate | diff | comparison | revisions
esp-idf-lib/FAQ.md file | annotate | diff | comparison | revisions
esp-idf-lib/Gemfile file | annotate | diff | comparison | revisions
esp-idf-lib/Gemfile.lock file | annotate | diff | comparison | revisions
esp-idf-lib/Metadata.md file | annotate | diff | comparison | revisions
esp-idf-lib/README.md file | annotate | diff | comparison | revisions
esp-idf-lib/components/ads111x/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/ads111x/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/ads111x/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/ads111x/ads111x.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/ads111x/ads111x.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/ads111x/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/ads130e08/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/ads130e08/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/ads130e08/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/ads130e08/ads130e08.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/ads130e08/ads130e08.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/ads130e08/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/aht/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/aht/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/aht/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/aht/aht.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/aht/aht.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/aht/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/bh1750/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/bh1750/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/bh1750/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/bh1750/bh1750.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/bh1750/bh1750.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/bh1750/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/bh1900nux/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/bh1900nux/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/bh1900nux/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/bh1900nux/bh1900nux.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/bh1900nux/bh1900nux.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/bh1900nux/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/bme680/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/bme680/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/bme680/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/bme680/README.md file | annotate | diff | comparison | revisions
esp-idf-lib/components/bme680/bme680.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/bme680/bme680.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/bme680/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/bmp180/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/bmp180/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/bmp180/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/bmp180/bmp180.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/bmp180/bmp180.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/bmp180/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/bmp280/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/bmp280/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/bmp280/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/bmp280/bmp280.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/bmp280/bmp280.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/bmp280/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/button/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/button/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/button/Kconfig file | annotate | diff | comparison | revisions
esp-idf-lib/components/button/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/button/button.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/button/button.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/button/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/ccs811/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/ccs811/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/ccs811/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/ccs811/README.md file | annotate | diff | comparison | revisions
esp-idf-lib/components/ccs811/ccs811.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/ccs811/ccs811.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/ccs811/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/color/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/color/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/color/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/color/color.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/color/color.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/color/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/color/hsv.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/color/rgb.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/dht/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/dht/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/dht/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/dht/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/dht/dht.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/dht/dht.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/dps310/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/dps310/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/dps310/Kconfig file | annotate | diff | comparison | revisions
esp-idf-lib/components/dps310/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/dps310/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/dps310/include/dps310.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/dps310/priv_include/helper_i2c.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/dps310/priv_include/helper_macro.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/dps310/src/dps310.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/dps310/src/helper_i2c.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/ds1302/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/ds1302/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/ds1302/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/ds1302/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/ds1302/ds1302.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/ds1302/ds1302.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/ds1307/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/ds1307/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/ds1307/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/ds1307/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/ds1307/ds1307.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/ds1307/ds1307.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/ds18x20/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/ds18x20/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/ds18x20/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/ds18x20/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/ds18x20/ds18x20.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/ds18x20/ds18x20.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/ds3231/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/ds3231/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/ds3231/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/ds3231/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/ds3231/ds3231.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/ds3231/ds3231.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/ds3502/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/ds3502/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/ds3502/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/ds3502/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/ds3502/ds3502.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/ds3502/ds3502.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/encoder/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/encoder/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/encoder/Kconfig file | annotate | diff | comparison | revisions
esp-idf-lib/components/encoder/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/encoder/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/encoder/encoder.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/encoder/encoder.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/esp_idf_lib_helpers/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/esp_idf_lib_helpers/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/esp_idf_lib_helpers/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/esp_idf_lib_helpers/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/esp_idf_lib_helpers/esp_idf_lib_helpers.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/esp_idf_lib_helpers/ets_sys.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/example/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/example/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/example/Kconfig file | annotate | diff | comparison | revisions
esp-idf-lib/components/example/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/example/README.md file | annotate | diff | comparison | revisions
esp-idf-lib/components/example/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/example/example.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/example/example.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/framebuffer/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/framebuffer/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/framebuffer/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/framebuffer/fbanimation.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/framebuffer/fbanimation.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/framebuffer/framebuffer.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/framebuffer/framebuffer.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/hd44780/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/hd44780/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/hd44780/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/hd44780/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/hd44780/hd44780.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/hd44780/hd44780.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/hdc1000/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/hdc1000/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/hdc1000/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/hdc1000/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/hdc1000/hdc1000.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/hdc1000/hdc1000.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/hmc5883l/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/hmc5883l/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/hmc5883l/Kconfig file | annotate | diff | comparison | revisions
esp-idf-lib/components/hmc5883l/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/hmc5883l/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/hmc5883l/hmc5883l.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/hmc5883l/hmc5883l.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/ht16k33/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/ht16k33/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/ht16k33/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/ht16k33/README.md file | annotate | diff | comparison | revisions
esp-idf-lib/components/ht16k33/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/ht16k33/ht16k33.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/ht16k33/ht16k33.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/hts221/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/hts221/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/hts221/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/hts221/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/hts221/hts221.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/hts221/hts221.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/hx711/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/hx711/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/hx711/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/hx711/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/hx711/hx711.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/hx711/hx711.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/i2cdev/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/i2cdev/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/i2cdev/Kconfig file | annotate | diff | comparison | revisions
esp-idf-lib/components/i2cdev/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/i2cdev/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/i2cdev/i2cdev.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/i2cdev/i2cdev.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/icm42670/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/icm42670/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/icm42670/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/icm42670/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/icm42670/icm42670.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/icm42670/icm42670.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/ina219/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/ina219/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/ina219/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/ina219/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/ina219/ina219.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/ina219/ina219.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/ina260/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/ina260/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/ina260/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/ina260/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/ina260/ina260.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/ina260/ina260.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/ina3221/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/ina3221/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/ina3221/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/ina3221/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/ina3221/ina3221.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/ina3221/ina3221.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/lc709203f/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/lc709203f/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/lc709203f/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/lc709203f/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/lc709203f/lc709203f.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/lc709203f/lc709203f.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/led_strip/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/led_strip/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/led_strip/Kconfig file | annotate | diff | comparison | revisions
esp-idf-lib/components/led_strip/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/led_strip/README.md file | annotate | diff | comparison | revisions
esp-idf-lib/components/led_strip/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/led_strip/led_strip.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/led_strip/led_strip.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/led_strip_spi/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/led_strip_spi/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/led_strip_spi/Kconfig file | annotate | diff | comparison | revisions
esp-idf-lib/components/led_strip_spi/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/led_strip_spi/README.md file | annotate | diff | comparison | revisions
esp-idf-lib/components/led_strip_spi/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/led_strip_spi/led_strip_spi.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/led_strip_spi/led_strip_spi.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/led_strip_spi/led_strip_spi_esp32.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/led_strip_spi/led_strip_spi_esp8266.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/led_strip_spi/led_strip_spi_sk9822.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/led_strip_spi/led_strip_spi_sk9822.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/lib8tion/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/lib8tion/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/lib8tion/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/lib8tion/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/lib8tion/lib8tion.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/lib8tion/lib8tion.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/lib8tion/lib8tion/math8.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/lib8tion/lib8tion/random8.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/lib8tion/lib8tion/scale8.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/lib8tion/lib8tion/trig8.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/lm75/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/lm75/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/lm75/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/lm75/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/lm75/lm75.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/lm75/lm75.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/ls7366r/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/ls7366r/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/ls7366r/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/ls7366r/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/ls7366r/ls7366r.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/ls7366r/ls7366r.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/max31725/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/max31725/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/max31725/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/max31725/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/max31725/max31725.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/max31725/max31725.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/max31855/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/max31855/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/max31855/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/max31855/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/max31855/max31855.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/max31855/max31855.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/max31865/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/max31865/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/max31865/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/max31865/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/max31865/max31865.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/max31865/max31865.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/max7219/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/max7219/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/max7219/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/max7219/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/max7219/max7219.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/max7219/max7219.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/max7219/max7219_priv.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp23008/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp23008/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp23008/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp23008/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp23008/mcp23008.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp23008/mcp23008.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp23x17/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp23x17/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp23x17/Kconfig file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp23x17/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp23x17/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp23x17/mcp23x17.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp23x17/mcp23x17.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp342x/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp342x/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp342x/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp342x/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp342x/mcp342x.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp342x/mcp342x.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp4725/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp4725/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp4725/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp4725/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp4725/mcp4725.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp4725/mcp4725.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp960x/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp960x/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp960x/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp960x/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp960x/mcp960x.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp960x/mcp960x.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp9808/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp9808/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp9808/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp9808/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp9808/mcp9808.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/mcp9808/mcp9808.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/mhz19b/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/mhz19b/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/mhz19b/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/mhz19b/README.md file | annotate | diff | comparison | revisions
esp-idf-lib/components/mhz19b/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/mhz19b/mhz19b.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/mhz19b/mhz19b.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/ms5611/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/ms5611/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/ms5611/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/ms5611/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/ms5611/ms5611.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/ms5611/ms5611.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/noise/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/noise/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/noise/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/noise/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/noise/noise.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/noise/noise.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/onewire/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/onewire/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/onewire/Kconfig file | annotate | diff | comparison | revisions
esp-idf-lib/components/onewire/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/onewire/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/onewire/onewire.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/onewire/onewire.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/pca9557/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/pca9557/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/pca9557/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/pca9557/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/pca9557/pca9557.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/pca9557/pca9557.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/pca9685/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/pca9685/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/pca9685/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/pca9685/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/pca9685/pca9685.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/pca9685/pca9685.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/pcf8563/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/pcf8563/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/pcf8563/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/pcf8563/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/pcf8563/pcf8563.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/pcf8563/pcf8563.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/pcf8574/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/pcf8574/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/pcf8574/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/pcf8574/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/pcf8574/pcf8574.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/pcf8574/pcf8574.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/pcf8575/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/pcf8575/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/pcf8575/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/pcf8575/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/pcf8575/pcf8575.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/pcf8575/pcf8575.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/pcf8591/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/pcf8591/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/pcf8591/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/pcf8591/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/pcf8591/pcf8591.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/pcf8591/pcf8591.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/qmc5883l/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/qmc5883l/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/qmc5883l/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/qmc5883l/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/qmc5883l/qmc5883l.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/qmc5883l/qmc5883l.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/rda5807m/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/rda5807m/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/rda5807m/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/rda5807m/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/rda5807m/rda5807m.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/rda5807m/rda5807m.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/scd30/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/scd30/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/scd30/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/scd30/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/scd30/scd30.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/scd30/scd30.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/scd4x/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/scd4x/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/scd4x/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/scd4x/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/scd4x/scd4x.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/scd4x/scd4x.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/sgp40/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/sgp40/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/sgp40/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/sgp40/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/sgp40/sensirion_voc_algorithm.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/sgp40/sensirion_voc_algorithm.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/sgp40/sgp40.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/sgp40/sgp40.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/sht3x/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/sht3x/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/sht3x/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/sht3x/README.md file | annotate | diff | comparison | revisions
esp-idf-lib/components/sht3x/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/sht3x/sht3x.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/sht3x/sht3x.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/sht4x/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/sht4x/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/sht4x/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/sht4x/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/sht4x/sht4x.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/sht4x/sht4x.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/si7021/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/si7021/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/si7021/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/si7021/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/si7021/si7021.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/si7021/si7021.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/sts21/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/sts21/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/sts21/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/sts21/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/sts21/sts21.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/sts21/sts21.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/tca9548/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/tca9548/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/tca9548/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/tca9548/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/tca9548/tca9548.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/tca9548/tca9548.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/tca95x5/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/tca95x5/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/tca95x5/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/tca95x5/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/tca95x5/tca95x5.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/tca95x5/tca95x5.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/tda74xx/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/tda74xx/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/tda74xx/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/tda74xx/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/tda74xx/tda74xx.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/tda74xx/tda74xx.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/tsl2561/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/tsl2561/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/tsl2561/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/tsl2561/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/tsl2561/tsl2561.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/tsl2561/tsl2561.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/tsl2591/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/tsl2591/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/tsl2591/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/tsl2591/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/tsl2591/tsl2591.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/tsl2591/tsl2591.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/tsl4531/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/tsl4531/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/tsl4531/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/tsl4531/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/tsl4531/tsl4531.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/tsl4531/tsl4531.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/tsys01/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/tsys01/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/tsys01/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/tsys01/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/tsys01/tsys01.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/tsys01/tsys01.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/ultrasonic/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/ultrasonic/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/ultrasonic/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/ultrasonic/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/ultrasonic/ultrasonic.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/ultrasonic/ultrasonic.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/veml7700/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/veml7700/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/veml7700/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/veml7700/README.md file | annotate | diff | comparison | revisions
esp-idf-lib/components/veml7700/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/veml7700/veml7700.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/veml7700/veml7700.h file | annotate | diff | comparison | revisions
esp-idf-lib/components/wiegand/.eil.yml file | annotate | diff | comparison | revisions
esp-idf-lib/components/wiegand/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/components/wiegand/LICENSE file | annotate | diff | comparison | revisions
esp-idf-lib/components/wiegand/component.mk file | annotate | diff | comparison | revisions
esp-idf-lib/components/wiegand/wiegand.c file | annotate | diff | comparison | revisions
esp-idf-lib/components/wiegand/wiegand.h file | annotate | diff | comparison | revisions
esp-idf-lib/devtools/.rubocop.yml file | annotate | diff | comparison | revisions
esp-idf-lib/devtools/README.md.erb file | annotate | diff | comparison | revisions
esp-idf-lib/devtools/Rakefile file | annotate | diff | comparison | revisions
esp-idf-lib/devtools/cmake-get-requires/.gitignore file | annotate | diff | comparison | revisions
esp-idf-lib/devtools/cmake-get-requires/CMakeLists.txt file | annotate | diff | comparison | revisions
esp-idf-lib/devtools/groups.yml file | annotate | diff | comparison | revisions
esp-idf-lib/devtools/persons.yml file | annotate | diff | comparison | revisions
esp-idf-lib/devtools/spec/component.rb file | annotate | diff | comparison | revisions
esp-idf-lib/devtools/spec/component_spec.rb file | annotate | diff | comparison | revisions
esp-idf-lib/devtools/spec/copyright.rb file | annotate | diff | comparison | revisions
esp-idf-lib/devtools/spec/group.rb file | annotate | diff | comparison | revisions
esp-idf-lib/devtools/spec/group_list.rb file | annotate | diff | comparison | revisions
esp-idf-lib/devtools/spec/groups_spec.rb file | annotate | diff | comparison | revisions
esp-idf-lib/devtools/spec/license.rb file | annotate | diff | comparison | revisions
esp-idf-lib/devtools/spec/metadata.rb file | annotate | diff | comparison | revisions
esp-idf-lib/devtools/spec/person.rb file | annotate | diff | comparison | revisions
esp-idf-lib/devtools/spec/person_list.rb file | annotate | diff | comparison | revisions
esp-idf-lib/devtools/spec/persons_spec.rb file | annotate | diff | comparison | revisions
esp-idf-lib/devtools/spec/spec_helper.rb file | annotate | diff | comparison | revisions
esp-idf-lib/devtools/spec/target.rb file | annotate | diff | comparison | revisions
esp-idf-lib/devtools/spec/target_list.rb file | annotate | diff | comparison | revisions
esp-idf-lib/devtools/spec/targets_spec.rb file | annotate | diff | comparison | revisions
esp-idf-lib/devtools/targets.yml file | annotate | diff | comparison | revisions
main/CMakeLists.txt file | annotate | diff | comparison | revisions
main/Kconfig.projbuild file | annotate | diff | comparison | revisions
main/config.c file | annotate | diff | comparison | revisions
main/config.h file | annotate | diff | comparison | revisions
main/iotbalkon.c file | annotate | diff | comparison | revisions
main/task_bmp280.c file | annotate | diff | comparison | revisions
main/task_bmp280.h file | annotate | diff | comparison | revisions
sdkconfig file | annotate | diff | comparison | revisions
--- a/CMakeLists.txt	Sun Mar 26 22:22:45 2023 +0200
+++ b/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -4,5 +4,9 @@
 # CMakeLists in this exact order for cmake to work correctly
 cmake_minimum_required(VERSION 3.16)
 
+set(PROJECT_VER "0.0.1")
+set(PROJECT_NAME "iotbalkon")
+
+set(EXTRA_COMPONENT_DIRS esp-idf-lib/components)
 include($ENV{IDF_PATH}/tools/cmake/project.cmake)
 project(iotbalkon)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/CHANGELOG.md	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,248 @@
+# Changelog
+
+## v.0.9.2
+
+### Features
+- (ds3231): Added ds3231_get_squarewave_freq by @dizcza in https://github.com/UncleRus/esp-idf-lib/pull/447
+- (ads130e08): Driver for ADS130E08 ADC by @weslleymfd in https://github.com/UncleRus/esp-idf-lib/pull/462
+- (dps310): DPS310 driver by @trombik in https://github.com/UncleRus/esp-idf-lib/pull/463
+
+### Bugfixes
+- (pca9557): Fixed incorrect I2C address and register bug by @AxelLin in https://github.com/UncleRus/esp-idf-lib/pull/453
+- (max7219) Fix string bounds check by @UncleRus in https://github.com/UncleRus/esp-idf-lib/pull/471
+- (ci) Updated esp-idf versions by @trombik
+- (ci) Updated actions/checkout to v3 by @trombik in https://github.com/UncleRus/esp-idf-lib/pull/476
+- (esp_idf_lib_helpers): Fixed ets_sys.h error by @UncleRus in https://github.com/UncleRus/esp-idf-lib/pull/479
+- (i2cdev): Fixed i2c param config and driver install order for esp32 target by @AxelLin in https://github.com/UncleRus/esp-idf-lib/pull/475
+
+## New Contributors
+* @AxelLin made their first contribution in https://github.com/UncleRus/esp-idf-lib/pull/453
+* @weslleymfd made their first contribution in https://github.com/UncleRus/esp-idf-lib/pull/462
+
+### Documentation: https://esp-idf-lib.readthedocs.io/en/0.9.2/
+
+## v.0.9.1
+
+### Features
+- (lc709203f): Driver for LC709203F battery fuel gauge by @jmpmscorp in https://github.com/UncleRus/esp-idf-lib/pull/433
+- (hmc5883l): Added support for HMC5983 by @dizcza in https://github.com/UncleRus/esp-idf-lib/pull/431
+
+### Bugfixes
+- (i2cdev): Fixed I2C driver reinstallation bug by @UncleRus in https://github.com/UncleRus/esp-idf-lib/pull/430
+- (chore): Updated io-console by @trombik in https://github.com/UncleRus/esp-idf-lib/pull/435
+- (doc): Reordered Usages of metadata in the project section by @trombik in https://github.com/UncleRus/esp-idf-lib/pull/439
+- (all): Fixed xprintf() format specifications in components and examples to support ESP-IDF v5 by @UncleRus
+- (ci): Fixed generation of components list by @UncleRus in https://github.com/UncleRus/esp-idf-lib/pull/446
+
+## New Contributors
+- @jmpmscorp made their first contribution in https://github.com/UncleRus/esp-idf-lib/pull/433
+
+### Documentation: https://esp-idf-lib.readthedocs.io/en/0.9.1/
+
+
+## v.0.9.0
+
+### Features
+- (docs) How to porting i2c libs by @dizcza in https://github.com/UncleRus/esp-idf-lib/pull/428
+- (sts21) Driver for STS21 temperature sensor by @UncleRus in https://github.com/UncleRus/esp-idf-lib/pull/326
+- (max31855) Driver for MAX31855 cold-junction compensated thermocouple-to-digital converter by @UncleRus in https://github.com/UncleRus/esp-idf-lib/pull/324
+- (ht16k33) Driver for HT16K33 LED driver by @chudsaviet in https://github.com/UncleRus/esp-idf-lib/pull/341
+- (ci) All-new awesome label-based workflow with auto-labeler by @trombik
+- (ci) Introduced release-drafter GitHub Actions workflow by @trombik in https://github.com/UncleRus/esp-idf-lib/pull/338
+- (chore) Added clang-format options file by @UncleRus in https://github.com/UncleRus/esp-idf-lib/pull/421
+- (tca9548): Added example by @UncleRus in https://github.com/UncleRus/esp-idf-lib/pull/424
+
+### Bugfixes
+- (all) Fixed requirements in components using `esp_timer` by @EldritchJS in https://github.com/UncleRus/esp-idf-lib/pull/328
+- (lm75, pca9557) Fixed type casting warnings by @UncleRus in https://github.com/UncleRus/esp-idf-lib/pull/400
+- (ads111x) Fixed bug `in ads111x_is_busy()` (#418) by @UncleRus in https://github.com/UncleRus/esp-idf-lib/pull/419
+
+## New Contributors
+- @EldritchJS made their first contribution in https://github.com/UncleRus/esp-idf-lib/pull/328
+- @chudsaviet made their first contribution in https://github.com/UncleRus/esp-idf-lib/pull/341
+
+### Documentation: https://esp-idf-lib.readthedocs.io/en/0.9.0/
+
+
+## v.0.8.3
+
+### Changes that break compatibility 
+- ⚠ (bh1900nux, max31725, mcp23008, mcp23x17, mcp342x, mcp4725, pca9557, pcf8574, pcf8575, qmc5883l, sht3x, tca9548, tca95x5): The order of the arguments in `xxx_init_desc()` functions has been changed to bring all drivers to a common standard: instead of `xxx_init_desc(..., i2c_port_t port, uint8_t addr, ...)`, now `xxx_init_desc(..., uint8_t addr, i2c_port_t port, ...)`. Attention: both arguments are ints, so compiler will not throw an error when building your firmware without changing it!
+- (common) Dropped support for ESP-IDF v3.x
+
+### Features
+- (common) Added support for ESP32-C3
+- (hts221) Driver for HTS221 temperature and humidity sensor
+- (hdc1000) Driver for HDC1000 temperature and humidity sensor
+- (examples) Constant parameters in examples have been moved to Kconfig.projbuild files, so you can now setup examples by `idf.py menuconfig` or `make menuconfig` instead of modifying source code.
+- (examples) Added README to all examples
+- (hx711) Added function to read average data
+- (bh1900nux) Added software reset function
+- (ds3231) Added functions to set and get aging offset register
+- (esp_idf_lib_helper): Moved ets_sys.h includes to the separate file
+
+### Bugfixes
+- (i2cdev) Showing error name in error logging
+- (color) Fixed narrowing conversion error
+- (examples) Use SPI2_HOST in examples with ESP-IDF v4.x
+- (rda5807) Replaced `ESP_LOGI()` to `ESP_LOGD()`
+- (led_strip_spi) Fixed pointer arithmetic for esp8266
+- (led_strip_spi) Fixed "initialized field overwritten" warning
+- (mcp960x) Fixed incorrect I2C addressing
+
+### Documentation: https://esp-idf-lib.readthedocs.io/en/0.8.3/
+
+## v.0.8.2
+
+### Features
+- (max31865) Driver for MAX31865 resistance converter for platinum RTDs
+- (pca9557) Driver for PCA9537/PCA9557 remote 4/8-bit I/O expanders for I2C-bus
+- (ls7366r) Driver for LS7366R Quadrature Encoder Counter
+- (ci) Added metadata support
+- (bh1900nux) Driver for BH1900NUX temperature sensor
+
+### Bugfixes
+- (scd30) Fixed type casting warning
+- (ccs811) Fixed claculation bug in ccs811_set_environmental_data()
+- (max7219) Fixed bug with chip number in 8x8 example
+- (button) Added user context do descriptor
+- (si7021) Fixed delay for SHT20
+- (led_strip_spi) Added brightness support
+- (pcf8563) Fixed mdays/wdays bug
+- (sht4x) Fixed bug in example
+- (led_strip) Fixed memory leak when handling error in led_strip_init()
+
+
+## v.0.8.1
+
+### Features
+- (button) Driver for GPIO button with debouncing
+- (scd30) Driver for SCD30 CO₂ sensor
+- (scd4x) Driver for SCD40/SCD41 miniature CO₂ sensor
+- (aht) Driver for AHT10/AHT15/AHT20 temperature and humidity sensor
+
+### Bugfixes
+- (mzh19b) Fixed bug with serial buffer length used by the driver
+- (framebuffer) Used a mutex instead of flag, fixed "maybe-uninitialized"
+- (sgp40) Multiple bugs fixed
+- (sht4x) Added 10ms timout for reading serial number
+- (rda5807m) Default I2C clock lowered, example added
+- (color) Fix build with c++
+- (examples) Fixed build of examples for single core ESP32s (S2/C3)
+- (ds18x20) Added split 18B20/18S20 reads to allow for use of DS18X20_ANY
+  without flubbing the conversion on an 18B20
+- (ds18x20) Added scratchpad write and copy commands
+
+
+## v.0.8.0
+
+### Features
+- (wiegand) Wiegand protocol receiver
+- (lib8tion) Math functions specifically designed for LED programming (port
+  from FastLED)
+- (color) Common library for RGB and HSV colors (port from FastLED)
+- (noise) Noise generation functions (port from FastLED)
+- (framebuffer) RGB framebuffer and animation component
+- (mhz19b) Added support for the MH-Z19B CO2 sensor
+- (ci) Dropped support for ESP-IDF < v3.5
+- (ci) Preliminary and untested support for ESP32-C3
+- (ci) Support ESP8266 RTOS SDK v3.3
+- (doc) Added initial version of CONTRIBUTING.md
+
+### Bugfixes
+- (sht3x) Fixed documentation
+- (ds18x20) Fixed ds18x20_scan_devices(), new example
+- (ultrasonic) Added more precise measurement functions
+- (led_strip_spi) Fixed SPI buffer limitation for ESP8266
+- (led_strip) Fixed buffer overflow error for RGBW strips
+- (led_strip) White component for RGBW strips now calculated automatically
+- (led_strip) Fixed bug in led_strip_set_pixels()
+- (led_strip) Added support for global brightness for ESP-IDF >= v4.4
+- (led_strip) Improved stability for WS2812B
+- (refactoring) Updated component makefiles and CMakeLists
+- (i2cdev) Added option to disable all mutexes
+
+
+## v.0.7.3
+
+### Features
+- (led_strip_spi) #156 SPI-based driver for SK9822/APA102 LED strips
+- (ds3502) #160 Driver for nonvolatile digital potentiometer DS3502
+- (sht4x) #165 Driver for SHT4x temperature and humidity sensor
+
+### Bugfixes
+- (pca9685) b633f86 Speed-ups
+- (max7219) #159 Add "minus" sign and fix maximum brightness
+
+
+## v.0.7.2
+
+### Features
+- (tsl2591) #149 Driver for light-to-digital converter TSL2591
+- (sgp40) #137 Driver for SGP40 Indoor Air Quality Sensor
+- (ccs811) #67 Driver for AMS CCS811 digital gas sensor
+
+### Bugfixes
+- (ci) #147 Cache Espressif tools
+- (ci) #155 Update v4.2 branch
+- (led_strip) #153 Tweak led_strip example, add README
+- (led_strip) #154 Fix range bug in led_strip_fill
+- (sht3x, ...) #151 Typo corrections + SHT3x corrections and improvement
+- (sht3x) #152 Fix periodic measurement
+- (hx711) a2b9fc5 Fix incorrect spinlock usage
+- (doc) Multiple fixes
+- (pca9685) ce8f3fa Fix possible race condition
+
+
+## v.0.7.1
+
+### Features
+- (mcp960x) #141 Driver + example for MCP960X/L0X/RL0X
+- (tsys01) #142 Driver + example for TSYS01
+
+### Bugfixes
+- (qmc5883l) dd17522 Fix possible race condition
+- (tca95x5) #144 Copy-paste error, add example
+- (esp_idf_lib_helpers) #143 Invalid error message
+- (tsl4531) c2e835d Fix possible race condition
+- (sht3x) 8289262 Fix possible race confition in SS measurement, refactoring
+- (bh1750, bmp180) d57488b Fix possible race condition
+
+
+## v.0.7
+
+### Features
+- (ina260) #126 Driver for INA260 precision digital current and power monitor
+- (rda5807m) #25 Driver for single-chip broadcast FM radio tuner RDA5807M
+- (i2cdev) #138 I2C clock stertching support
+
+
+## v.0.6.1
+
+### Bugfixes
+- (ina219) #100 Potential error in ina219_get_gain
+- (bme680) #121 Pressure calculation for bme680 gives wrong results
+
+
+## v.0.6
+
+### Features
+- (ci) #116 Port CI process from Travis CI to GitHub Actions
+- (ci) Update CI build tools
+- (ads111x) #117 Support of ADS101x on top ADS111x
+- (led_strip) #120 Smart LED strips support
+
+### Bugfixes
+- (ds1307) #110 wrong squarewave frequency returned
+- (sht3x, hmc5883l, hx711) #118 SHT3x measurements fail after 72min
+- (pca9685) d4f5e35 Fix full on/off
+- (ina219) Typo fix
+
+
+## v.0.5-beta
+
+### Features
+- (mcp342x) #92 Driver for ADC MCP3426/MCP3427/MCP3428
+
+### Bugfixes
+- (ds1302) #97 Fix critical section exit
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/CONTRIBUTING.md	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,617 @@
+# How to contribute to `esp-idf-lib`
+
+## Table of Contents
+
+<!-- vim-markdown-toc GFM -->
+
+* [Possible contributions](#possible-contributions)
+    * [Submitting a bug report](#submitting-a-bug-report)
+    * [Submitting a fix](#submitting-a-fix)
+    * [Writing documentation](#writing-documentation)
+    * [Suggesting enhancements](#suggesting-enhancements)
+    * [Promoting the project](#promoting-the-project)
+    * [Writing code](#writing-code)
+* [Development Life Cycle](#development-life-cycle)
+    * [Creating an Issue](#creating-an-issue)
+    * [Creating a feature branch in your fork and develop](#creating-a-feature-branch-in-your-fork-and-develop)
+    * [C Code style](#c-code-style)
+    * [`markdown` Code style](#markdown-code-style)
+    * [`git` branch name convention](#git-branch-name-convention)
+    * [Typical issues you will face in developments](#typical-issues-you-will-face-in-developments)
+    * [Writing a commit message](#writing-a-commit-message)
+    * [Updating README.md](#updating-readmemd)
+    * [Creating a Pull Request](#creating-a-pull-request)
+* [Licenses](#licenses)
+    * [Acceptable licenses](#acceptable-licenses)
+    * [Acceptable license for new code](#acceptable-license-for-new-code)
+    * [Unacceptable licenses](#unacceptable-licenses)
+
+<!-- vim-markdown-toc -->
+
+## Possible contributions
+
+If you would like to contribute to `esp-idf-lib`, we would like to say _thank
+you_. We appreciate your efforts and contributions. Here is possible
+contributions you can make.
+
+* [Submitting a bug report](#submitting-a-bug-report)
+* [Submitting a fix](#submitting-a-fix)
+* [Writing documentation](#writing-documentation)
+* [Suggesting enhancements](#suggesting-enhancements)
+* [Promoting the project](#promoting-the-project)
+* [Writing code](#writing-code)
+
+### Submitting a bug report
+
+In embedded device development, finding bugs is more difficult than in other
+software development. There are too many uncontrollable factors: physical
+environment, counterfeit IC chips, deviations in revisions and variations,
+difficulties in automations. Even if the bug turned out to be not a bug, such
+report is still appreciated as it is another proof that the code works as
+expected in a different environment.
+
+Please include how to reproduce the bug in the Issue. The more context, the
+better. For example:
+
+* The _full_ error message in text format and the entire code (comment with
+  ` ``` ` for short code, use [Gist](https://gist.github.com) for long code)
+* The circuit diagram
+* Captured signals by an oscilloscope or a signal analyser ([sigrok](https://sigrok.org/))
+
+A question as a bug report is okay but we expect bug reporters to do their
+homework. The homework include:
+
+* Reading the data sheets
+* Reading [the official documentation of `esp-idf`](https://docs.espressif.com/projects/esp-idf)
+  (it's good, really)
+* Understanding C language in general
+
+For introductory C tutorials, see:
+
+* [C Tutorial](https://www.tutorialspoint.com/cprogramming/) by
+  `Tutorialspoint`
+* [C Programming](https://en.wikibooks.org/wiki/C_Programming) by
+  `Wikibooks`
+
+### Submitting a fix
+
+If you found a bug and a fix for it, please create a bug report before
+creating a Pull Request unless the bug is subtle, typos, or easy to reproduce
+and fix. Make sure to read [Development Life Cycle](#development-life-cycle)
+as a Pull Request must meet the same standards documented in the section.
+
+A GitHub Actions workflow,
+[pr-labeler-action](https://github.com/TimonVS/pr-labeler-action), is used to
+label PRs by branch name. Your fix branch should have prefixes defined in
+[.github/pr-labeler.yml](.github/pr-labeler.yml). Create a branch with one of
+the prefixes. If you are fixing a bug, your branch name should be `bugfix-`.
+The rest of branch name should be short, and descriptive. If the fix has
+related issues, the branch name should include them.
+
+See also [`git` branch name convention](#git-branch-name-convention).
+
+```console
+git checkout -b bugfix-issue-1
+```
+
+Change the branch name before creating a PR if your branch name does not
+follow the convention.
+
+```console
+git branch --move bugfix-issue-1
+```
+
+### Writing documentation
+
+Even if you are not an author of the code in the repository, you can write
+documentation as a contribution.
+
+Creating and maintaining FAQ entries is one of great examples. Have you
+encountered seemingly common issues while using a component? That might help
+others.
+
+We encourage code authors to write documentation in the code so that the code
+and the documentation is always synced. However, sometimes they are not.
+Spotting such mistakes is always appreciated.
+
+Not all contributors are native English speakers. If you are, please let us
+know ambiguity in the documentation, wrong usages of terms, and mistakes in
+English grammar. For this case, please create a Pull Request (creating an
+issue is optional).
+
+Create a branch that documents features, or fixes existing documentations.
+
+```console
+git checkout -b doc-foo
+```
+
+See also [`git` branch name convention](#git-branch-name-convention).
+
+### Suggesting enhancements
+
+While we are not always able to write a driver for a chip, we still appreciate
+a request for new driver. It is more likely to happen when:
+
+* the chip is _cool_
+* the chip is easily available
+* the chip is affordable
+
+### Promoting the project
+
+If you find the project useful, we are interested in what you did with
+`esp-idf-lib`, and _how_ you did it.
+
+* Writing a blog post about your porject with `esp-idf-lib`
+* Mentioning the project in SNS
+
+### Writing code
+
+If you can write a driver for new chip, that would be great. Please read
+[Development Life Cycle](#development-life-cycle).
+
+## Development Life Cycle
+
+In this section, a typical development life cycle is explained.
+
+### Creating an Issue
+
+If you are working on a new driver, or an existing one, please create an
+Issue, and assign the Issue to yourself.
+
+`esp-idf-lib` aims at providing stable drivers for IC chips and general
+components. IC chips are stable, in that a chip is manufactured for a long
+time, retaining backward compatibilities. A driver for a chip usually requires
+minimal maintenance once the driver becomes stable. However, network protocols,
+graphics drivers, libraries for external services, are a moving-target.
+Standards will change, external services will evolve, user expectations will
+change, too. We think that such moving-targets should be maintained in a
+dedicated repository. Do you think your code is a moving target? It might be
+better to create a your own repository for the driver. If you are not sure,
+ask in the Issue.
+
+### Creating a feature branch in your fork and develop
+
+_Feature branch workflow_ is adopted in our development.
+[Git Feature Branch Workflow](https://www.atlassian.com/git/tutorials/comparing-workflows/feature-branch-workflow)
+by `atlassian` explains the workflow in details.
+
+Fork the repository and clone it on your machine.
+See [Fork a repo](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo).
+
+Create a feature branch in your fork from the `master` branch.
+
+```console
+git checkout master
+```
+
+Check out the feature branch. The feature branch name should start with
+`feat-` or `feature-`.
+
+```console
+git checkout -b feat-implement-foo
+````
+
+See also [`git` branch name convention](#git-branch-name-convention).
+
+Write your code. Test the code in your physical test environment.  Commit your
+changes and push them to your remote fork on GitHub.
+
+[`components/example`](components/example) has an example component, and
+[`examples/example`](examples/example) has an example application for the
+`example` component.
+
+```console
+git add path/to/files
+git commit -v
+git push --set-upstream origin feat-implement-foo
+````
+
+See also [Writing a commit message](#writing-a-commit-message).
+
+At this point, our CI workflows will run to test the changes. The test
+workflows include:
+
+* building the documentation,
+* building all examples for all supported targets with all supported
+  `esp-idf` versions, and
+* linting code and documentation
+
+You can see the test results in `Actions` page on your GitHub fork. To
+merge your changes to `master` branch, all the tests must pass.
+
+Make sure you are working on the latest `master` of `esp-idf-lib`. To sync the
+`master` in your fork and the latest `master` of `esp-idf-lib`, run:
+
+```console
+git checkout master
+git fetch upstream
+git reset --hard upstream/master
+```
+
+If your branch has many commits, consider `git rebase` to reduce the number of
+commits. This is especially useful when you are actively developing and the
+commit history has many trial-and-error commits.
+
+```console
+git checkout feat-implement-foo
+git rebase -i master
+git push -f
+```
+
+Note that `git rebase` rewrites the commit history. You should avoid `git
+rebase` after you asked someone to review your code because the reviewer needs
+additional steps to ensure the review result is included.
+
+### C Code style
+
+We use a style for source files based on [LLVM Coding Standards](https://llvm.org/docs/CodingStandards.html)
+except some cases, notably brace wrapping. Here is a brief list of the styles.
+
+* Use `snake_case`, not `CamelCase`
+* Use `SNAKE_CASE` in uppercase for macro name, e.g. `MACRO_NAME`.
+* Use spaces. The indent width is four
+* Use `\n`, or `LF` for line breaks
+* Use `//` for inline comments. Use `/* */` for multi line comments after an
+  empty line
+* Break before braces in *most cases* (functions, conditionals, control
+  statements, etc)
+* Always check given arguments
+* Always check return code, return value, or `errno`
+* Return `esp_err_t` from functions where possible
+* Document public functions, data types, and macros in header files
+* Use suffix `_t` for `typedef`, e.g. `foo_t`
+* Use suffix `_cb_t` for function `typedef`, e.g.`my_function_cb_t`
+* Use suffix `_s` for `struct`, e.g. `my_struct_s`
+* Wrap numbers in macro definition with parenthesis, e.g. `#define N_FOO (1)`
+* Use `#include <foo.h> for headers that are not part of the component, such
+  as `string.h`, `esp_log,h`, and `i2cdev.h`. Use `#include "foo.h" when the
+  header is private, i.e. the header is the part of the component.
+
+The style should be followed for all new code. In general, code can be
+considered "new code" when it makes up about 50% or more of the file(s)
+involved. This is enough to break precedents in the existing code and use the
+current style guidelines.
+
+See an example source files under [`components/exmaple`](components/example)
+and, for complete rules, see [`.clang-format`](.clang-format) and the output
+of `clang-format --dump-config`.
+
+New code will be tested in the CI, using `clang-format` (currently `LLVM`
+version 10).
+
+To format your code without modifying the code, run:
+
+```console
+clang-format10 components/example/example.c
+```
+
+To format your code in-place, run:
+
+```console
+clang-format10 -i components/example/example.c
+```
+
+### `markdown` Code style
+
+We use [the default `markdownlint` rules](https://github.com/markdownlint/markdownlint/blob/master/docs/RULES.md)
+with some non-defaults. Our style can be found in [`.mdlstyle.rb`](.mdlstyle.rb).
+
+| Rule                                 | non-default options                            |
+| ------------------------------------ | ---------------------------------------------- |
+| `MD003` - Header style               | use `#` for headers                            |
+| `MD007` - Unordered list indentation | indent with 4 spaces                           |
+| `MD013` - Line length                | ignore line length in code blocks and tables   |
+| `MD024` - Multiple headers with the same content | `allow_different_nesting` is true  |
+| `MD029` - Ordered list item prefix   | `style` is `ordered`, i.e. incremental numbers |
+
+In the CI, we use ruby version of `markdownlint`, or [`mdl`](https://rubygems.org/gems/mdl/)
+gem, but [markdownlint for node.js](https://github.com/DavidAnson/markdownlint)
+should also work.
+
+To test `markdown` style of a file, you need:
+
+* `ruby` 2.6
+* `bundler` 2.x
+
+```console
+bundle install
+bundle exec mdl path/to/file
+```
+
+The output shows path to the file, line number, and the rule.  An example
+output is shown below.
+
+```console
+examples/led_strip_spi/README.md:30: MD040 Fenced code blocks should have a language specified
+```
+
+### `git` branch name convention
+
+We use the following convention for git branch name. Use one of branch name
+prefixes when creating a branch.
+
+| Branch name prefix | Description |
+|--------------------|-------------|
+| `feat-`, and `feature-` | A feature branch that implements feature(s), or add enhancement(s) to existing code |
+| `fix-`, and `bugfix-` | A bug fix branch that fixes bug(s). The rest of the branch name should include issue number, such as `fix-issue-1` |
+| `ci-` | A branch that implements enhancement(s), or fixes issue(s) in CI |
+| `chore-` | A branch that does not affect code or its behavior, such as updating `.gitignore` |
+| `doc-`, and `documentation-` | Adding or updating documentation only, such as documenting undocumented features, or fixing existing documentation(s) |
+
+A GitHub Actions workflow automatically labels PRs depending on the branch
+name prefixes so that the PR is automatically included in release notes.
+
+The rest of the branch name should be short, and descriptive. If your branch
+fixes, implements, or relates to, an Issue, include the Issue number. Say, if
+your branch fixes a bug reported Issue ${N}, the branch name should be
+`fix-issue-${N}` so that reviewer immediately understand there is a related
+Issue with your branch. Replace `${N}` with the Issue number, such as
+`fix-issue-123` when the Issue number is 123.
+
+### Typical issues you will face in developments
+
+**Your code assumes a single target, such as `esp32`**. `esp-idf-lib` supports
+other targets, notably `esp8266`. Make sure the driver supports various other
+targets. If it cannot, such as the peripheral is not available on the target
+chip, your code should bailout during the build by using `#error` C
+preprocessor macro, and your driver must be excluded from the CI (TODO
+document how).
+
+**Your code assumes a single SDK**. `esp-idf-lib` supports `master` and stable
+versions of `esp-idf` and `ESP8266 RTOS SDK`. Generally, the SDKs retain
+backward compatibilities, but sometimes not. Make sure to use `if` C
+preprocessor macro to support different versions. [`esp_idf_lib_helpers`](components/esp_idf_lib_helpers)
+component can help you. `ESP8266 RTOS SDK` shares many functions and
+libraries, backported from `esp-idf`, but they are not identical. `I2C`
+drivers written with [`i2cdev`](components/i2cdev) should work fine on ESP32
+and ESP8266, while SPI drivers need serious workarounds to support ESP8266.
+[`led_strip_spi`](components/led_strip_spi) attempted to support both, but you
+might want to write a different driver for each.
+
+**Your code assumes a single build method, such as `idf.py`**. Although `GNU make`
+build method is considered as legacy, it is still a supported build method.
+The CI builds your code twice; with `idf.py` and with `GNU make`. Both must be
+successful. In ESP8266 RTOS SDK, `idf.py` is lagged behind from the one in
+`esp-idf`. For ESP8266 target, the CI builds examples with `GNU make` only.
+
+Check return codes (most of functions in `esp-idf`), return values (e.g.
+`malloc(3)`), or `errno` (e.g. some standard C functions).  Propagate the
+error by returning it from your function. An example:
+
+```c
+#include <esp_err.h>
+#include <esp_log.h>
+
+esp_err_t do_something()
+{
+    esp_err_t err;
+
+    err = foo();
+    if (err != ESP_OK)
+    {
+        ESP_LOGE("bar", "foo(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+fail:
+    return err;
+}
+```
+
+Note that newer `esp-idf` supports useful macros for error handling, such as
+`ESP_GOTO_ON_ERROR` (see
+[Error Handling](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/error-handling.html)),
+but older versions do not have them yet.
+
+Check given arguments in functions, and return an appropriate error from one
+of predefined errors (see
+[Error Codes Reference](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/error-codes.html)).
+
+### Writing a commit message
+
+When you commit, prefix the first line of your commit message with `foo:`,
+where `foo` is the name of component you are working on. Sometimes, it is not
+possible because you are working on multiple components, i.e. fixing common
+bugs in multiple components. In such cases, use `bugfix:`. Other commonly used
+prefix words are:
+
+* `feature:` for features, or improvements, in multiple components
+* `ci:` for fixes or improvements in the CI process
+* `doc:` for fixes and improvements in the documentation
+
+These prefix words are for conventional purposes. Use common sense and make
+the commit message clear so that others can understand what the change is.
+
+The rest of the first line should start with a verb. Examples:
+
+```text
+foo: fix typos
+```
+
+```text
+foo: resolve race condition in bar()
+```
+
+The first line should make sense when reading _"When you merge this, it will
+`$THE_FIRST_LINE`"_.
+
+The second line of the commit message must be an empty line.
+
+In the rest of the commit message, write details of the change if necessary.
+Explain what it does _and_ *why*.  The lines in the commit message should be
+limited to 72 characters or less where possible.
+
+Include a reference to an Issue when the commit fixes an Issue.
+
+```text
+fixes #$ISSUE_NUMBER
+```
+
+When an Issue number or a Pull Request number is prefixed with certain
+keywords, the referenced Issue or Pull Request will be closed. See [Linking a
+pull request to an issue using a keyword](https://docs.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword)
+for the supported keywords.
+
+### Updating README.md
+
+Each component has a `.eil.yml` file in its component directory. The file is a
+metadata file of the component. If you change the file, you need to update the
+`README.md` in the project root directory. The `README.md` is generated from
+the metadata and a template, `README.md.erb`. Generate `README.md` by:
+
+```console
+bundle exec rake -C devtools readme > README.md
+```
+
+See also [`Metadata.md`](Metadata.md).
+
+### Creating a Pull Request
+
+To test your code, you need to create a Pull Request. It is not practical to
+test code manually because you have to perform many tests. For instance, the
+number of tests is all targets (`esp32`, `esp8266`, `esp32s2`, etc) * build
+methods (`make` and `idf.py`) * supported `esp-idf` versions. Let the CI do it
+for you.
+
+Before creating a Pull Request, make sure:
+
+1. You compiled the code and the build succeeded
+1. Functions, macros, data types are documented in the code
+1. An example application is provided under [`examples`](examples). In the
+   directory, create a directory `${COMPONENT_NAME}/default`. For instance, a
+   component `foo` must have `examples/foo/default`. Create an example
+   application in that directory.
+1. Update [.github/labeler.yml](.github/labeler.yml). The component should
+   have a label for it.
+1. You compiled the example code and the example application ran on a
+   physical device as expected and documented
+1. All files are licensed under one of [Acceptable Licenses](#acceptable-licenses)
+   by including the license at the top of file
+1. One of your commits in the feature branch, or the PR itself, mentions Issue
+   number so that the Issue will be automatically closed when the PR is merged
+
+When a PR is created, GitHub Actions workflows will:
+
+* label the PR with various labels, such as type of the PR (bug fix, or
+  feature)
+* perform necessary tests depending on the changes (build the examples in a
+  matrix if the source code is modified, build the documentation if files
+  under `docs` are modified)
+
+After the CI processes complete, you will see "All checks have passed" or some
+failures in the PR. To merge the PR, all checks must pass. Log is available
+from the link, `Details`, in the failed test.
+
+If the PR does not pass the CI, update the branch with a fix. At this point,
+`git rebase` may be used. For instance, if a commit has a typo and one of the
+test fails because of syntax error, commit a fix of the syntax error and do
+`git rebase` to merge the fix into the original commit that has introduced the
+syntax error.
+
+```console
+git add path/to/file
+git commmit -v
+git rebase -i master
+```
+
+`-i`, or `--interactive`, flag will launch a text editor where the history of
+the branch can be edited with commands. The buffer of the editor will look
+like:
+
+```text
+pick f7f3f6d bugfix: fix issue foo
+pick 310154e bugfix: fix a syntax error in f7f3f6d
+```
+
+The second commit, `310154e`, should be part of the previous, `f7f3f6d`. To
+rewrite the commit history, replace `pick` with `fixup`.
+
+```text
+pick f7f3f6d bugfix: fix issue foo
+fixup 310154e bugfix: fix a syntax error in f7f3f6d
+```
+
+When you save and exit the editor, `git` rewrites the commit history as if
+`310154e` was never committed. The commit `310154e` is now part of `f7f3f6d`.
+If you don't save or modify the buffer, `git` will not rewrite the commit
+history.
+
+`git rebase` can be used to tidy up the commits. To `rebase` or not to
+`rebase` depends on the nature of commits. A single commit per PR is preferred,
+but is not mandatory.
+
+See also
+[7.6 Git Tools - Rewriting History](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History).
+
+When all the tests pass, ask the code owner to review the PR. The code owner
+can be found in `.eli.yml` file in the component directory.  From this point,
+you should avoid to `git rebase` your feature branch. Otherwise, the reviewer
+would have to review the PR from scratch.
+
+Developers who has write access to the repository will leave comments, ask
+rewrites, and merge the PR.
+
+## Licenses
+
+We provide code that can be freely used, copied, modified, and distributed by
+anyone and for any purpose.
+
+### Acceptable licenses
+
+We accept permissive licenses such as:
+
+* [ISC](https://spdx.org/licenses/ISC.html) License
+* [MIT](https://spdx.org/licenses/MIT.html) License
+* [BSD-2-Clause](https://spdx.org/licenses/BSD-2-Clause.html) License
+
+A list of licenses are available at
+[SPDX License List](https://spdx.org/licenses/).
+
+### Acceptable license for new code
+
+New code is the one you own (you wrote it from scratch). The preferred
+license to be applied to new code is a simplified ISC License. The license
+must be included at the top in every files as long as practically possible.
+The following is a preferred wording of the license.
+
+```c
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) YYYY YOUR NAME HERE <user@your.dom.ain>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+ ```
+
+Add `SPDX-License-Identifier: $YOUR_LICENSE` to your license header.
+`$YOUR_LICENSE` is a SPDX License Identifier.
+
+* [ISC](https://spdx.org/licenses/ISC.html)
+* [BSD-2-Clause](https://spdx.org/licenses/BSD-2-Clause.html)
+
+### Unacceptable licenses
+
+We do NOT accept `copyleft` licenses such as:
+
+* GPL License
+* LGPL License
+* GNU Affero General Public License (AGPL)
+
+We do NOT accept _long_ licenses. A license is considered as _long_ when
+it has more than four clauses.
+
+We do NOT accept protective licenses that have additional restrictions, such
+as:
+
+* Apache license version 2 or later
+* various so-called _Shareware_ or _Freeware_
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/FAQ.md	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,145 @@
+# FAQ
+
+<!-- vim-markdown-toc GFM -->
+
+* [How to debug I2C-based drivers?](#how-to-debug-i2c-based-drivers)
+* [Why are semaphores (mutexes) used in i2cdev routines?](#why-are-semaphores-mutexes-used-in-i2cdev-routines)
+* [How can I connect multiple I2C devices?](#how-can-i-connect-multiple-i2c-devices)
+* [How can I change frequency of I2C clock? At default frequency my device is unstable or not working at all.](#how-can-i-change-frequency-of-i2c-clock-at-default-frequency-my-device-is-unstable-or-not-working-at-all)
+* [How to use internal pull-up resistors](#how-to-use-internal-pull-up-resistors)
+* [Can I use I2C device drivers from interrupts?](#can-i-use-i2c-device-drivers-from-interrupts)
+* [Porting I2C libs to I2Cdev](#porting-i2c-libs-to-i2cdev)
+
+<!-- vim-markdown-toc -->
+
+## How to debug I2C-based drivers?
+
+Common causes of I2C issues are:
+
+* wrong wiring
+* wrong pull-up resistors
+* wrong I2C address
+* broken I2C module
+* the driver has a bug
+
+When any of I2C-based drivers does not work, follow the steps below.
+
+Build an [_I2C scanner_ device](examples/i2c_scanner). The device is not
+necessarily an ESP device. There are many examples for various platforms.
+Search by keyword `i2c scanner`.
+
+Connect the I2C module to the I2C scanner device. Make sure appropriate
+pull-up resistors are connected to `SCL` and `SDA` lines.
+
+Scan devices on the I2C bus. If the scanner does not find the I2C device, then
+your wiring might have issues. If the scanner finds the I2C device, make sure
+the address found is what the driver expects. If you have more than one same
+I2C modules, try them all.
+
+If the scanner finds the I2C device and you are sure that the wiring is
+correct, see the signals on the wire using an oscilloscope. Most oscilloscopes
+can decode I2C signals and display I2C transactions in human-readable way.
+
+If the driver does not work after these steps, please [let us
+know](https://github.com/UncleRus/esp-idf-lib/issues).
+
+## Why are semaphores (mutexes) used in i2cdev routines?
+
+i2cdev uses two types of mutexes: port and transactional.
+
+Port mutexes (there are only two of them, by the number of I2C ports) are
+necessary to avoid problems in the situation mentioned in the documentation:
+
+> The I2C APIs are not thread-safe, if you want to use one I2C port in
+different tasks, you need to take care of the multi-thread issue.
+
+They are taken before executing single I2C transactions and are released
+immediately after them.
+
+Mutexes of the second type are created one per device and are necessary if
+the same device is used in several tasks:
+
+```C
+static i2c_dev_t some_dev;
+
+void task1(void *arg)
+{
+     // some_dev used here
+}
+
+void task2(void *arg)
+{
+     // and here some_dev used too
+}
+
+...
+xTaskCreate(task1, "task1", ....);
+xTaskCreate(task2, "task2", ....);
+
+```
+
+These mutexes are used when single device operation requires several I2C
+transactions in a row.
+
+## How can I connect multiple I2C devices?
+
+With i2cdev, you can use almost any way to connect I2C devices.
+
+For example, in the case of using SSD1306 and two MCP3428s, I would recommend
+connecting them like this:
+
+* 2 GPIO outputs on ESP32 for dedicated screen connection via I2C bus 0
+* 2 GPIO outputs to the second I2C bus, to which 2 MCP3428 are connected with
+  different addresses.
+
+If you need to connect more than one sensor with the same addresses and there
+are free GPIOs, then you can directly connect these sensors to individual GPIO
+outputs instead of using the I2C multiplexer. i2cdev will take care of
+reconfiguring I2C driver to the according outputs when you exchange data
+with these devices.
+
+## How can I change frequency of I2C clock? At default frequency my device is unstable or not working at all.
+
+You can change the frequency after initializing the device handler, like this:
+
+```C
+#include <esp_idf_lib_helpers.h>
+
+...
+
+ESP_ERROR_CHECK(ads111x_init_desc(&dev, addr, I2C_PORT, SDA_GPIO, SCL_GPIO));
+
+#if HELPER_TARGET_IS_ESP32
+    dev.cfg.master.clk_speed = 100000; // Hz
+#endif
+
+ESP_ERROR_CHECK(ads111x_set_mode(&dev, ADS111X_MODE_CONTINUOUS));    // Continuous conversion mode
+ESP_ERROR_CHECK(ads111x_set_data_rate(&dev, ADS111X_DATA_RATE_32)); // 32 samples per second
+...
+```
+
+## How to use internal pull-up resistors
+
+Just enable them in `i2c_dev_t` config. For example:
+
+```C
+...
+
+dev.cfg.scl_pullup_en = true;
+dev.cfg.sda_pullup_en = true;
+ESP_ERROR_CHECK(ads111x_init_desc(&dev, addr, I2C_PORT, SDA_GPIO, SCL_GPIO));
+
+...
+```
+
+## Can I use I2C device drivers from interrupts?
+
+With default configuration you can't. Since the drivers use mutexes, this will
+crash the system.  But you can disable use of any I2C mutexes (both port and
+device) in configuration: just enable CONFIG_I2CDEV_NOLOCK. Keep in mind that
+after enabling this option all i2c device drivers will become non-thread safe.
+
+
+## Porting I2C libs to I2Cdev
+
+See [porting.md](docs/porting.md).
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/Gemfile	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+source "https://rubygems.org"
+
+gem "irb"
+gem "rake"
+gem "rspec"
+gem "rubocop"
+gem "io-console", ">= 0.5.11"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/Gemfile.lock	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,58 @@
+GEM
+  remote: https://rubygems.org/
+  specs:
+    ast (2.4.2)
+    diff-lcs (1.4.4)
+    io-console (0.5.11)
+    irb (1.3.7)
+      reline (>= 0.2.7)
+    parallel (1.21.0)
+    parser (3.0.2.0)
+      ast (~> 2.4.1)
+    rainbow (3.0.0)
+    rake (13.0.6)
+    regexp_parser (2.1.1)
+    reline (0.2.7)
+      io-console (~> 0.5)
+    rexml (3.2.5)
+    rspec (3.10.0)
+      rspec-core (~> 3.10.0)
+      rspec-expectations (~> 3.10.0)
+      rspec-mocks (~> 3.10.0)
+    rspec-core (3.10.1)
+      rspec-support (~> 3.10.0)
+    rspec-expectations (3.10.1)
+      diff-lcs (>= 1.2.0, < 2.0)
+      rspec-support (~> 3.10.0)
+    rspec-mocks (3.10.2)
+      diff-lcs (>= 1.2.0, < 2.0)
+      rspec-support (~> 3.10.0)
+    rspec-support (3.10.3)
+    rubocop (1.22.3)
+      parallel (~> 1.10)
+      parser (>= 3.0.0.0)
+      rainbow (>= 2.2.2, < 4.0)
+      regexp_parser (>= 1.8, < 3.0)
+      rexml
+      rubocop-ast (>= 1.12.0, < 2.0)
+      ruby-progressbar (~> 1.7)
+      unicode-display_width (>= 1.4.0, < 3.0)
+    rubocop-ast (1.13.0)
+      parser (>= 3.0.1.1)
+    ruby-progressbar (1.11.0)
+    unicode-display_width (2.1.0)
+
+PLATFORMS
+  amd64-freebsd-14
+  ruby
+  x86_64-linux
+
+DEPENDENCIES
+  io-console (>= 0.5.11)
+  irb
+  rake
+  rspec
+  rubocop
+
+BUNDLED WITH
+   2.2.19
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/Metadata.md	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,309 @@
+# Metadata
+
+## Table of Contents
+
+<!-- vim-markdown-toc GFM -->
+
+* [Purpose](#purpose)
+* [Files](#files)
+    * [`.eil.yml`](#eilyml)
+    * [`persons.yml`](#personsyml)
+    * [`groups.yml`](#groupsyml)
+    * [`targets.yml`](#targetsyml)
+* [Resources](#resources)
+    * [Person](#person)
+    * [Target](#target)
+    * [License](#license)
+    * [Copyright](#copyright)
+    * [Group](#group)
+    * [Metadata](#metadata)
+    * [Component](#component)
+* [Usages of metadata in the project](#usages-of-metadata-in-the-project)
+    * [Validating metadata of components](#validating-metadata-of-components)
+    * [Generating `README.md`](#generating-readmemd)
+* [Known issues](#known-issues)
+    * [conditional `depends`](#conditional-depends)
+
+<!-- vim-markdown-toc -->
+
+This document describes metadata used in the project. The status of the
+document is beta.
+
+## Purpose
+
+The purpose of metadata in the project is to automate works in development and
+project management, to ensure the project policies, and to extend the project
+to support third-party projects.
+
+## Files
+
+### `.eil.yml`
+
+The metadata file of a component.  Each component must have `.eil.yml` in the
+root directory of the component. The file format is YAML.
+
+An example path: `components/ads111x/.eil.yml`.
+
+### `persons.yml`
+
+`persons.yml` is a YAML file that contains a list of `Person`s.
+
+### `groups.yml`
+
+`groups.yml` is a YAML file that contains a list of `Group`s.
+
+### `targets.yml`
+
+`targets.yml` is a YAML file that contains a list of `Target`s.
+
+## Resources
+
+Resources defined here represents various objects used in the metadata.
+
+A resource has unique `name` as a primary key.
+
+When referring to a resource in another resource, use `name` as key and its
+value to identify the resource. As a shorthand, you may use the name of a
+resource as `String`. In this case, the value is assumed to be `name: $VALUE`.
+
+When a resource expects a `Person` as a value,
+
+```yaml
+foo:
+  name: trombik
+```
+
+This is a shorthand version of the above example:
+
+```yaml
+foo: trombik
+```
+
+### Person
+
+A `Person` represents a person. `Person` is used to describe a copyrights
+holder and a code owner. A `Person` must be defined in `persons.yml` file.
+
+| Name | Type | Description | Required |
+|------|------|-------------|----------|
+| `name` | `String` | A unique ID string of the person. Use GitHub account or GitHub project if the person has one | Yes |
+| `full_name` | `String` | Full name of the person or the project | No |
+| `gh_id` | `String` | GitHub account name or project name | No |
+| `email` | `String` | Email address of the person | No |
+| `website` | `String` | Web site URL | No |
+
+When any of `gh_id`, `email`, or `website` is not available, `person` must
+have a full name because it is used to identify the source of code.
+
+If the person does not have `gh_id`, use the full name for `name`. For example,
+when the full name is "Foo Bar", use `name: FooB`.
+
+`Person` should have one or more of optional keys so that one can contact the
+person.
+
+Examples:
+
+```yaml
+name: trombik
+gh_id: trombik
+full_name: Tomoyuki Sakurai
+email: y@trombik.org
+website: https://github.com/trombik
+```
+
+```yaml
+name: foo
+full_name: Foo `bar` buz
+# XXX other keys are optional, but strongly recommended.
+```
+
+### Target
+
+| Name | Type | Description | Required |
+|------|------|-------------|----------|
+| `name` | `String` | Name of the build target in `esp-idf`, or `esp8266`. | Yes |
+
+An example:
+
+```yaml
+name: esp32
+```
+
+### License
+
+| Name | Type | Description | Required |
+|------|------|-------------|----------|
+| `name` | `String` | SPDX License Identifier (see [the list of licenses](https://spdx.org/licenses/)) | Yes |
+
+An example:
+
+```yaml
+name: BSD-3
+```
+
+### Copyright
+
+| Name | Type | Description | Required |
+|------|------|-------------|----------|
+| `author` | `Person` | Copyrights holder. See also `Person`. | No |
+| `name` | `String` | The value of `name` of `Person`. A shorthand for `author` | No |
+| `year` | `Integer` | Registration year of the copyrights | Yes |
+
+`Copyright` must have only one of `author` and `name`, not both.
+
+Examples:
+
+```yaml
+name: trombik
+year: 2021
+```
+
+The above example is a shorthand form of:
+
+```yaml
+author:
+  name: trombik
+year: 2021
+```
+
+### Group
+
+A `Group` represents a group of `Component`s. A `Group` must be in
+`groups.yml`.
+
+| Name | Type | Description | Required |
+|------|------|-------------|----------|
+| `name` | `String` | A unique ID of the group | Yes |
+| `description` | `String` | Description of the group | Yes |
+
+`name` should be short, and memorable. Use `-` as a word separator. It must
+not include spaces (`[0-9a-zA-Z-]+` in regular expression).
+
+An example:
+
+```yaml
+name: adc-dac
+description: ADC/DAC libraries
+```
+
+### Metadata
+
+`Metadata` is the content of `.eil.yml`. `Metadata` includes non-empty list of
+`Component` under `components` top level key.
+
+An example:
+
+```yaml
+---
+components:
+  - name: foo
+  # ... other keys go here ...
+```
+
+### Component
+
+| Name | Type | Description | Required |
+|------|------|-------------|----------|
+| `name` | `String` | The name of the component. Must be unique. | Yes |
+| `description` | `String` | A short description of the component. | Yes |
+| `group` | `Group` | The primary group name of the component. | Yes |
+| `groups` | A list of `Group` | A list of zero or more of `Group` | No |
+| `code_owners` | A list of `Person` | A list of one or more of `Person` | Yes |
+| `depends` | A list of `Component` | Zero or more of `component` that the component depends on | No |
+| `thread_safe` | `Strnig` | One of `yes`, `no`, and `N/A` | Yes |
+| `targets` | A list of `Target` | One or more of supported `target` | Yes |
+| `licenses` | A list of `License` | One or more of licenses used in the component | Yes |
+| `copyrights` | A list of `Copyright` | One or more of copyright holder | Yes |
+
+FIXME `depends` must be a list because some drivers have conditional `REQUIRES`
+in `CMakeLists.txt`.
+
+## Usages of metadata in the project
+
+The current implementation uses `ruby` and `rspec` ruby gem to validate
+metadata in all components, and generate `README.md`.
+
+Requirements are:
+
+* `ruby` 2.7 (other version should also work)
+* [`bundler`](https://bundler.io/)
+
+After installing requirements, run:
+
+```console
+bundle install
+```
+
+### Validating metadata of components
+
+To validate metadata, run:
+
+```console
+bundle exec rake -C devtools rspec
+```
+
+The implementation uses `rspec` to validate metadata because:
+
+1. the output is readable
+2. requires less `ruby` knowledge to maintain the spec than validating
+   everything in ruby code
+3. porting tests to other languages is easier than porting ruby code
+
+Under `spec` directory, there are:
+
+* `spec_helper.rb`, which is a helper for the test
+* `*_spec.rb`, which is a test script
+* other ruby files, such as `person.rb`, which are class definitions used in
+  the test
+
+The ruby classes for the test validate minimum requirements only, such as the
+`.eil.yml` file exists, or a resource has a required primary key. Actual
+test should be performed in `*_spec.rb` files.
+
+### Generating `README.md`
+
+`README.md` is generated from the metadata and `README.md.erb`. To update
+`README.md`, run the following command at the repository root directory:
+
+```console
+bundle exec rake -C devtools readme > README.md
+```
+
+## Known issues
+
+### conditional `depends`
+
+Some `CMakeLists.txt` conditionally sets `REQUIRES`. `depends` does not handle
+the following case.
+
+```yaml
+# for esp32
+depends:
+  - name: driver
+  - name: freertos
+  - name: log
+```
+
+```yaml
+# for esp8266
+depends:
+  - name: esp8266
+  - name: freertos
+  - name: log
+```
+
+A possible solution:
+
+```yaml
+depends:
+  - name: driver
+    target:
+      - name: esp32
+      - name: esp32s2
+      - name: esp32c3
+  - name: esp8266
+    target:
+      - name: esp8266
+  - name: freertos
+  - name: log
+```
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/README.md	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,312 @@
+# ESP-IDF Components library
+
+[![Build Status](https://github.com/UncleRus/esp-idf-lib/workflows/Build%20examples/badge.svg)](https://github.com/UncleRus/esp-idf-lib/actions?query=workflow%3A%22Build+examples%22)
+[![Build the documentation](https://github.com/UncleRus/esp-idf-lib/workflows/Build%20the%20documentation/badge.svg)](https://github.com/UncleRus/esp-idf-lib/actions?query=workflow%3A%22Build+the+documentation%22)
+[![Docs Status](https://readthedocs.org/projects/esp-idf-lib/badge/?version=latest&style=flat)](https://esp-idf-lib.readthedocs.io/en/latest/)
+
+Components for Espressif ESP32 [ESP-IDF framework](https://github.com/espressif/esp-idf)
+and [ESP8266 RTOS SDK](https://github.com/espressif/ESP8266_RTOS_SDK).
+
+Part of them ported from [esp-open-rtos](https://github.com/SuperHouse/esp-open-rtos).
+
+## Supported versions of frameworks and devices
+
+| Chip           | Framework          | Versions
+|----------------|--------------------|-----------------------
+| ESP32          | ESP-IDF            | All officially supported versions (see [Support Period Policy](https://github.com/espressif/esp-idf/blob/master/SUPPORT_POLICY.md)) and `master`
+| ESP32-S2 *[1]* | ESP-IDF            | All officially supported versions and `master`
+| ESP32-C3 *[1]* | ESP-IDF            | All officially supported versions and `master`
+| ESP8266  *[2]* | ESP8266 RTOS SDK   | `master`, v3.4
+
+[1] *Use "`idf.py set-target esp32s2`" or "`idf.py set-target esp32c3`" before "`idf.py menuconfig`" to change
+the chip type.*
+
+[2] *Due to the incompatibility of ESP8266 drivers and hardware, some
+libraries are not* *supported on ESP8266 (see "ESP8266" column in the tables).*
+
+## How to use
+
+### ESP32
+
+Clone this repository somewhere, e.g.:
+
+```Shell
+cd ~/myprojects/esp
+git clone https://github.com/UncleRus/esp-idf-lib.git
+```
+
+Add path to components in your [project makefile](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/build-system-legacy.html),
+e.g:
+
+```Makefile
+PROJECT_NAME := my-esp-project
+EXTRA_COMPONENT_DIRS := /home/user/myprojects/esp/esp-idf-lib/components
+include $(IDF_PATH)/make/project.mk
+```
+
+or in [CMakeLists.txt](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/build-system.html):
+
+```CMake
+cmake_minimum_required(VERSION 3.5)
+set(EXTRA_COMPONENT_DIRS /home/user/myprojects/esp/esp-idf-lib/components)
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(my-esp-project)
+```
+
+or with CMake [FetchContent](https://cmake.org/cmake/help/latest/module/FetchContent.html)
+
+```CMake
+cmake_minimum_required(VERSION 3.11)
+include(FetchContent)
+FetchContent_Declare(
+  espidflib
+  GIT_REPOSITORY https://github.com/UncleRus/esp-idf-lib.git
+)
+FetchContent_MakeAvailable(espidflib)
+set(EXTRA_COMPONENT_DIRS ${espidflib_SOURCE_DIR}/components)
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(my-esp-project)
+```
+
+### ESP8266 RTOS SDK
+
+Clone this repository somewhere, e.g.:
+
+```Shell
+cd ~/myprojects/esp
+git clone https://github.com/UncleRus/esp-idf-lib.git
+```
+
+Add path to components in your [project makefile](https://docs.espressif.com/projects/esp8266-rtos-sdk/en/latest/api-guides/build-system.html),
+e.g:
+
+```Makefile
+PROJECT_NAME := my-esp-project
+EXTRA_COMPONENT_DIRS := /home/user/myprojects/esp/esp-idf-lib/components
+EXCLUDE_COMPONENTS := max7219 mcp23x17 led_strip max31865 ls7366r max31855
+include $(IDF_PATH)/make/project.mk
+```
+
+See [GitHub examples](https://github.com/UncleRus/esp-idf-lib/tree/master/examples)
+or [GitLab examples](https://gitlab.com/UncleRus/esp-idf-lib/tree/master/examples).
+
+## Documentation
+
+- [Documentation](https://esp-idf-lib.readthedocs.io/en/latest/)
+- [Frequently asked questions](FAQ.md)
+
+## Components
+
+### ADC/DAC libraries
+
+| Component                | Description                                                                      | License | Supported on       | Thread safety
+|--------------------------|----------------------------------------------------------------------------------|---------|--------------------|--------------
+| **ads111x**              | Driver for ADS1113/ADS1114/ADS1115 and ADS1013/ADS1014/ADS1015 I2C ADC           | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **ads130e08**            | Driver for ADS130E08 ADC                                                         | MIT     | `esp32`, `esp32s3` | Yes
+| **hx711**                | Driver for HX711 24-bit ADC for weigh scales                                     | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | No
+| **mcp342x**              | Driver for 18-Bit, delta-sigma ADC MCP3426/MCP3427/MCP3428                       | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **mcp4725**              | Driver for 12-bit DAC MCP4725                                                    | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **pcf8591**              | Driver for 8-bit ADC and an 8-bit DAC PCF8591                                    | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+
+### Air quality sensors
+
+| Component                | Description                                                                      | License | Supported on       | Thread safety
+|--------------------------|----------------------------------------------------------------------------------|---------|--------------------|--------------
+| **ccs811**               | Driver for AMS CCS811 digital gas sensor                                         | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **mhz19b**               | Driver for MH-Z19B NDIR CO₂ sensor                                               | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | No
+| **scd30**                | Driver for SCD30 CO₂ sensor                                                      | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **scd4x**                | Driver for SCD40/SCD41 miniature CO₂ sensor                                      | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **sgp40**                | Driver for SGP40 Indoor Air Quality Sensor for VOC Measurements                  | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+
+### Common libraries
+
+| Component                | Description                                                                      | License | Supported on       | Thread safety
+|--------------------------|----------------------------------------------------------------------------------|---------|--------------------|--------------
+| **color**                | Common library for RGB and HSV colors                                            | MIT     | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **esp_idf_lib_helpers**  | Common support library for esp-idf-lib                                           | ISC     | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **framebuffer**          | RGB framebuffer component                                                        | MIT     | `esp32`, `esp32s2`, `esp32c3` | Yes
+| **i2cdev**               | ESP-IDF I2C master thread-safe utilities                                         | MIT     | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **lib8tion**             | Math functions specifically designed for LED programming                         | MIT     | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **noise**                | Noise generation functions                                                       | MIT     | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **onewire**              | Bit-banging 1-Wire driver                                                        | MIT     | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | No
+
+### Current and power sensors
+
+| Component                | Description                                                                      | License | Supported on       | Thread safety
+|--------------------------|----------------------------------------------------------------------------------|---------|--------------------|--------------
+| **ina219**               | Driver for INA219/INA220 bidirectional current/power monitor                     | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **ina260**               | Driver for INA260 precision digital current and power monitor                    | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **ina3221**              | Driver for INA3221 shunt and bus voltage monitor                                 | ISC     | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+
+### GPIO expanders
+
+| Component                | Description                                                                      | License | Supported on       | Thread safety
+|--------------------------|----------------------------------------------------------------------------------|---------|--------------------|--------------
+| **mcp23008**             | Driver for 8-bit I2C GPIO expander MCP23008                                      | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **mcp23x17**             | Driver for I2C/SPI 16 bit GPIO expanders MCP23017/MCP23S17                       | BSD-3   | `esp32`, `esp32s2`, `esp32c3` | Yes
+| **pca9557**              | Driver for PCA9537/PCA9557/TCA9534 remote 4/8-bit I/O expanders for I2C-bus      | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **pcf8574**              | Driver for PCF8574 remote 8-bit I/O expander for I2C-bus                         | MIT     | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **pcf8575**              | Driver for PCF8575 remote 16-bit I/O expander for I2C-bus                        | MIT     | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **tca95x5**              | Driver for TCA9535/TCA9555 remote 16-bit I/O expanders for I2C-bus               | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+
+### Gas sensors
+
+| Component                | Description                                                                      | License | Supported on       | Thread safety
+|--------------------------|----------------------------------------------------------------------------------|---------|--------------------|--------------
+| **ccs811**               | Driver for AMS CCS811 digital gas sensor                                         | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **mhz19b**               | Driver for MH-Z19B NDIR CO₂ sensor                                               | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | No
+| **scd30**                | Driver for SCD30 CO₂ sensor                                                      | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **scd4x**                | Driver for SCD40/SCD41 miniature CO₂ sensor                                      | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+
+### Humidity sensors
+
+| Component                | Description                                                                      | License | Supported on       | Thread safety
+|--------------------------|----------------------------------------------------------------------------------|---------|--------------------|--------------
+| **aht**                  | Driver for AHT10/AHT15/AHT20 temperature and humidity sensor                     | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **bme680**               | Driver for BME680 digital environmental sensor                                   | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **dht**                  | Driver for DHT11, AM2301 (DHT21, DHT22, AM2302, AM2321), Itead Si7021            | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | No
+| **hdc1000**              | Driver for HDC1000 temperature and humidity sensor                               | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **hts221**               | Driver for HTS221 temperature and humidity sensor.                               | ISC     | `esp32`, `esp32s2`, `esp32c3` | Yes
+| **sht3x**                | Driver for Sensirion SHT30/SHT31/SHT35 digital temperature and humidity sensor   | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **sht4x**                | Driver for Sensirion SHT40/SHT41/SHT45 digital temperature and humidity sensor   | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **si7021**               | Driver for Si7013/Si7020/Si7021/HTU2xD/SHT2x and compatible temperature and humidity sensors | BSD-3   | `esp32`, `esp32c3`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+
+### Inertial measurement units
+
+| Component                | Description                                                                      | License | Supported on       | Thread safety
+|--------------------------|----------------------------------------------------------------------------------|---------|--------------------|--------------
+| **icm42670**             | Driver for TDK ICM-42670-P 6-Axis IMU (found on ESP-RS board, https://github.com/esp-rs/esp-rust-board) | ICS     | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+
+### Input device drivers
+
+| Component                | Description                                                                      | License | Supported on       | Thread safety
+|--------------------------|----------------------------------------------------------------------------------|---------|--------------------|--------------
+| **button**               | HW timer-based driver for GPIO buttons                                           | MIT     | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **encoder**              | HW timer-based driver for incremental rotary encoders                            | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **ls7366r**              | Driver for LS7366R Quadrature Encoder Counter                                    | MIT     | `esp32`, `esp32s2`, `esp32c3` | Yes
+
+### LED drivers
+
+| Component                | Description                                                                      | License | Supported on       | Thread safety
+|--------------------------|----------------------------------------------------------------------------------|---------|--------------------|--------------
+| **ht16k33**              | HT16K33 LED controller driver                                                    | MIT     | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **led_strip**            | RMT-based driver for WS2812B/SK6812/APA106/SM16703 LED strips                    | MIT     | `esp32`, `esp32s2`, `esp32c3` | Yes
+| **led_strip_spi**        | SPI-based driver for SK9822/APA102 LED strips                                    | MIT     | `esp32`, `esp32c3`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **max7219**              | Driver for 8-Digit LED display drivers, MAX7219/MAX7221                          | BSD-3   | `esp32`, `esp32s2`, `esp32c3` | Yes
+
+### Light sensors
+
+| Component                | Description                                                                      | License | Supported on       | Thread safety
+|--------------------------|----------------------------------------------------------------------------------|---------|--------------------|--------------
+| **bh1750**               | Driver for BH1750 light sensor                                                   | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **tsl2561**              | Driver for light-to-digital converter TSL2561                                    | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **tsl2591**              | Driver for light-to-digital converter TSL2591                                    | MIT     | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **tsl4531**              | Driver for digital ambient light sensor TSL4531                                  | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **veml7700**             | Driver for VEML7700 ambient light sensor                                         | ISC     | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+
+### Magnetic sensors
+
+| Component                | Description                                                                      | License | Supported on       | Thread safety
+|--------------------------|----------------------------------------------------------------------------------|---------|--------------------|--------------
+| **hmc5883l**             | Driver for 3-axis digital compass HMC5883L and HMC5983L                          | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **qmc5883l**             | Driver for QMC5883L 3-axis magnetic sensor                                       | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+
+### Other misc libraries
+
+| Component                | Description                                                                      | License | Supported on       | Thread safety
+|--------------------------|----------------------------------------------------------------------------------|---------|--------------------|--------------
+| **ds3502**               | Driver for nonvolatile digital potentiometer DS3502                              | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **example**              | An example component                                                             | ISC     | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **hd44780**              | Driver for HD44780 compatible LCD text displays                                  | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | No
+| **lc709203f**            | Driver for LC709203F battery fuel gauge                                          | ISC     | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **pca9685**              | Driver for 16-channel, 12-bit PWM PCA9685                                        | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **rda5807m**             | Driver for single-chip broadcast FM radio tuner RDA5807M                         | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **tca9548**              | Driver for TCA9548A/PCA9548A low-voltage 8-channel I2C switch                    | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **tda74xx**              | Driver for TDA7439/TDA7439DS/TDA7440D audioprocessors                            | MIT     | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **ultrasonic**           | Driver for ultrasonic range meters, e.g. HC-SR04, HY-SRF05                       | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | No
+| **wiegand**              | Wiegand protocol receiver                                                        | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | No
+
+### Pressure sensors
+
+| Component                | Description                                                                      | License | Supported on       | Thread safety
+|--------------------------|----------------------------------------------------------------------------------|---------|--------------------|--------------
+| **bme680**               | Driver for BME680 digital environmental sensor                                   | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **bmp180**               | Driver for BMP180 digital pressure sensor                                        | MIT     | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **bmp280**               | Driver for BMP280/BME280 digital pressure sensor                                 | MIT     | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **dps310**               | Driver for DPS310 barometric pressure sensor                                     | ISC     | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **ms5611**               | Driver for barometic pressure sensor MS5611-01BA03                               | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+
+### Real-time clocks
+
+| Component                | Description                                                                      | License | Supported on       | Thread safety
+|--------------------------|----------------------------------------------------------------------------------|---------|--------------------|--------------
+| **ds1302**               | Driver for DS1302 RTC module                                                     | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | No
+| **ds1307**               | Driver for DS1307 RTC module                                                     | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **ds3231**               | Driver for DS1337 RTC and DS3231 high precision RTC module                       | MIT     | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **pcf8563**              | Driver for PCF8563 real-time clock/calendar                                      | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+
+### Temperature sensors
+
+| Component                | Description                                                                      | License | Supported on       | Thread safety
+|--------------------------|----------------------------------------------------------------------------------|---------|--------------------|--------------
+| **aht**                  | Driver for AHT10/AHT15/AHT20 temperature and humidity sensor                     | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **bh1900nux**            | Driver for BH1900NUX temperature sensor                                          | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **bme680**               | Driver for BME680 digital environmental sensor                                   | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **bmp180**               | Driver for BMP180 digital pressure sensor                                        | MIT     | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **bmp280**               | Driver for BMP280/BME280 digital pressure sensor                                 | MIT     | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **dht**                  | Driver for DHT11, AM2301 (DHT21, DHT22, AM2302, AM2321), Itead Si7021            | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | No
+| **dps310**               | Driver for DPS310 barometric pressure sensor                                     | ISC     | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **ds18x20**              | Driver for DS18B20/DS18S20 families of 1-Wire temperature sensor ICs             | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | No
+| **hdc1000**              | Driver for HDC1000 temperature and humidity sensor                               | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **hts221**               | Driver for HTS221 temperature and humidity sensor.                               | ISC     | `esp32`, `esp32s2`, `esp32c3` | Yes
+| **lm75**                 | Driver for LM75, a digital temperature sensor and thermal watchdog               | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **max31725**             | Driver for MAX31725/MAX31726 temperature sensors                                 | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **max31855**             | Driver for MAX31855 cold-junction compensated thermocouple-to-digital converter  | BSD-3   | `esp32`, `esp32s2`, `esp32c3` | Yes
+| **max31865**             | Driver for MAX31865 resistance converter for platinum RTDs                       | BSD-3   | `esp32`, `esp32s2`, `esp32c3` | Yes
+| **mcp960x**              | Driver for MCP9600/MCP9601, thermocouple EMF to temperature converter            | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **mcp9808**              | Driver for MCP9808 Digital Temperature Sensor                                    | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **ms5611**               | Driver for barometic pressure sensor MS5611-01BA03                               | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **sht3x**                | Driver for Sensirion SHT30/SHT31/SHT35 digital temperature and humidity sensor   | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **sht4x**                | Driver for Sensirion SHT40/SHT41/SHT45 digital temperature and humidity sensor   | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **si7021**               | Driver for Si7013/Si7020/Si7021/HTU2xD/SHT2x and compatible temperature and humidity sensors | BSD-3   | `esp32`, `esp32c3`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **sts21**                | Driver for STS21 temperature sensor                                              | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+| **tsys01**               | Driver for precision digital temperature sensor TSYS01                           | BSD-3   | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes
+
+## Library maintainers
+
+- [Ruslan V. Uss](https://github.com/UncleRus)
+- [Tomoyuki Sakurai](https://github.com/trombik)
+
+## Credits
+
+- [Tomoyuki Sakurai](https://github.com/trombik), developer of the LM75 and
+  SK9822/APA102 drivers, author of the RTOS SDK ESP82666 support, master CI
+- [Gunar Schorcht](https://github.com/gschorcht), developer of SHT3x, BME680
+  and CCS811 drivers
+- [Brian Schwind](https://github.com/bschwind), developer of TS2561 and
+  TSL4531 drivers
+- [Andrej Krutak](https://github.com/andree182), developer of BH1750 driver
+- Frank Bargstedt, developer of BMP180 driver
+- [sheinz](https://github.com/sheinz), developer of BMP280 driver
+- [Jonathan Hartsuiker](https://github.com/jsuiker), developer of DHT driver
+- [Grzegorz Hetman](https://github.com/hetii), developer of DS18B20 driver
+- [Alex Stewart](https://github.com/astewart-consensus), developer of DS18B20 driver
+- [Richard A Burton](mailto:richardaburton@gmail.com), developer of DS3231 driver
+- [Bhuvanchandra DV](https://github.com/bhuvanchandra), developer of DS3231 driver
+- [Zaltora](https://github.com/Zaltora), developer of INA3231 driver
+- [Bernhard Guillon](https://gitlab.com/mrnice), developer of MS5611-01BA03 driver
+- [Pham Ngoc Thanh](https://github.com/panoti), developer of PCF8591 driver
+- [Lucio Tarantino](https://github.com/dianlight), developer of ADS111x driver
+- [Julian Dörner](https://github.com/juliandoerner), developer of TSL2591 driver
+- [FastLED community](https://github.com/FastLED), developers of `lib8tion`,
+  `color` and `noise` libraries
+- [Erriez](https://github.com/Erriez), developer of MH-Z19B driver
+- [David Douard](https://github.com/douardda), developer of MH-Z19B driver
+- [Nate Usher](https://github.com/nated0g), developer of SCD30 driver
+- [Josh Kallus](https://github.com/Jkallus), developer of LS7366R driver
+- [saasaa](https://github.com/saasaa), developer of HTS221 driver
+- [Timofei Korostelev](https://github.com/chudsaviet), developer of HT16K33 driver
+- [Jose Manuel Perez](https://github.com/jmpmscorp), developer of LC709203F driver
+- [Weslley Duarte](https://github.com/weslleymfd), developer of ADS130E08 driver
+- [Jan Veeh](https://github.com/janveeh), developer of ICM42670 driver
+- [Marc Luehr](https://github.com/Th3Link), developer of VEML7700 driver
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ads111x/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+---
+components:
+  - name: ads111x
+    description: |
+      Driver for ADS1113/ADS1114/ADS1115 and ADS1013/ADS1014/ADS1015 I2C ADC
+    group: adc-dac
+    groups: []
+    code_owners: UncleRus
+    depends:
+      - name: i2cdev
+      - name: log
+      - name: esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2016
+      - name: dianlight
+        year: 2020
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ads111x/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS ads111x.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ads111x/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+Copyright (c) 2016, 2018 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ads111x/ads111x.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,354 @@
+/*
+ * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com>
+ * Copyright (c) 2020 Lucio Tarantino <https://github.com/dianlight>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file ads111x.c
+ *
+ * ESP-IDF driver for ADS1113/ADS1114/ADS1115, ADS1013/ADS1014/ADS1015 I2C ADC
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2016, 2018 Ruslan V. Uss <unclerus@gmail.com>
+ * Copyright (c) 2020 Lucio Tarantino <https://github.com/dianlight>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+
+#include <esp_log.h>
+#include <esp_idf_lib_helpers.h>
+#include "ads111x.h"
+
+#define I2C_FREQ_HZ 1000000 // Max 1MHz for esp32
+
+#define REG_CONVERSION 0
+#define REG_CONFIG     1
+#define REG_THRESH_L   2
+#define REG_THRESH_H   3
+
+#define COMP_QUE_OFFSET  1
+#define COMP_QUE_MASK    0x03
+#define COMP_LAT_OFFSET  2
+#define COMP_LAT_MASK    0x01
+#define COMP_POL_OFFSET  3
+#define COMP_POL_MASK    0x01
+#define COMP_MODE_OFFSET 4
+#define COMP_MODE_MASK   0x01
+#define DR_OFFSET        5
+#define DR_MASK          0x07
+#define MODE_OFFSET      8
+#define MODE_MASK        0x01
+#define PGA_OFFSET       9
+#define PGA_MASK         0x07
+#define MUX_OFFSET       12
+#define MUX_MASK         0x07
+#define OS_OFFSET        15
+#define OS_MASK          0x01
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+static const char *TAG = "ads111x";
+
+const float ads111x_gain_values[] = {
+    [ADS111X_GAIN_6V144]   = 6.144,
+    [ADS111X_GAIN_4V096]   = 4.096,
+    [ADS111X_GAIN_2V048]   = 2.048,
+    [ADS111X_GAIN_1V024]   = 1.024,
+    [ADS111X_GAIN_0V512]   = 0.512,
+    [ADS111X_GAIN_0V256]   = 0.256,
+    [ADS111X_GAIN_0V256_2] = 0.256,
+    [ADS111X_GAIN_0V256_3] = 0.256
+};
+
+static esp_err_t read_reg(i2c_dev_t *dev, uint8_t reg, uint16_t *val)
+{
+    uint8_t buf[2];
+    esp_err_t res;
+    if ((res = i2c_dev_read_reg(dev, reg, buf, 2)) != ESP_OK)
+    {
+        ESP_LOGE(TAG, "Could not read from register 0x%02x", reg);
+        return res;
+    }
+    *val = (buf[0] << 8) | buf[1];
+
+    return ESP_OK;
+}
+
+static esp_err_t write_reg(i2c_dev_t *dev, uint8_t reg, uint16_t val)
+{
+    uint8_t buf[2] = { val >> 8, val };
+    esp_err_t res;
+    if ((res = i2c_dev_write_reg(dev, reg, buf, 2)) != ESP_OK)
+    {
+        ESP_LOGE(TAG, "Could not write 0x%04x to register 0x%02x", val, reg);
+        return res;
+    }
+
+    return ESP_OK;
+}
+
+static esp_err_t read_conf_bits(i2c_dev_t *dev, uint8_t offs, uint16_t mask,
+        uint16_t *bits)
+{
+    CHECK_ARG(dev);
+
+    uint16_t val;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, read_reg(dev, REG_CONFIG, &val));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    ESP_LOGD(TAG, "Got config value: 0x%04x", val);
+
+    *bits = (val >> offs) & mask;
+
+    return ESP_OK;
+}
+
+static esp_err_t write_conf_bits(i2c_dev_t *dev, uint16_t val, uint8_t offs,
+        uint16_t mask)
+{
+    CHECK_ARG(dev);
+
+    uint16_t old;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, read_reg(dev, REG_CONFIG, &old));
+    I2C_DEV_CHECK(dev, write_reg(dev, REG_CONFIG, (old & ~(mask << offs)) | (val << offs)));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+#define READ_CONFIG(OFFS, MASK, VAR) do { \
+        CHECK_ARG(VAR); \
+        uint16_t bits; \
+        CHECK(read_conf_bits(dev, OFFS, MASK, &bits)); \
+        *VAR = bits; \
+        return ESP_OK; \
+    } while(0)
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t ads111x_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port,
+        gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+
+    if (addr != ADS111X_ADDR_GND && addr != ADS111X_ADDR_VCC
+            && addr != ADS111X_ADDR_SDA && addr != ADS111X_ADDR_SCL)
+    {
+        ESP_LOGE(TAG, "Invalid I2C address");
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    dev->port = port;
+    dev->addr = addr;
+    dev->cfg.sda_io_num = sda_gpio;
+    dev->cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+    return i2c_dev_create_mutex(dev);
+}
+
+esp_err_t ads111x_free_desc(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(dev);
+}
+
+esp_err_t ads111x_is_busy(i2c_dev_t *dev, bool *busy)
+{
+    CHECK_ARG(dev && busy);
+
+    uint16_t r;
+    CHECK(read_conf_bits(dev, OS_OFFSET, OS_MASK, &r));
+    *busy = !r;
+
+    return ESP_OK;
+}
+
+esp_err_t ads111x_start_conversion(i2c_dev_t *dev)
+{
+    return write_conf_bits(dev, 1, OS_OFFSET, OS_MASK);
+}
+
+esp_err_t ads111x_get_value(i2c_dev_t *dev, int16_t *value)
+{
+    CHECK_ARG(dev && value);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, read_reg(dev, REG_CONVERSION, (uint16_t *)value));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t ads101x_get_value(i2c_dev_t *dev, int16_t *value)
+{
+    CHECK_ARG(dev && value);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, read_reg(dev, REG_CONVERSION, (uint16_t *)value));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    *value = *value >> 4;
+    if (*value > 0x07FF)
+    {
+        // negative number - extend the sign to 16th bit
+        *value |= 0xF000;
+    }
+    return ESP_OK;
+}
+
+esp_err_t ads111x_get_gain(i2c_dev_t *dev, ads111x_gain_t *gain)
+{
+    READ_CONFIG(PGA_OFFSET, PGA_MASK, gain);
+}
+
+esp_err_t ads111x_set_gain(i2c_dev_t *dev, ads111x_gain_t gain)
+{
+    return write_conf_bits(dev, gain, PGA_OFFSET, PGA_MASK);
+}
+
+esp_err_t ads111x_get_input_mux(i2c_dev_t *dev, ads111x_mux_t *mux)
+{
+    READ_CONFIG(MUX_OFFSET, MUX_MASK, mux);
+}
+
+esp_err_t ads111x_set_input_mux(i2c_dev_t *dev, ads111x_mux_t mux)
+{
+    return write_conf_bits(dev, mux, MUX_OFFSET, MUX_MASK);
+}
+
+esp_err_t ads111x_get_mode(i2c_dev_t *dev, ads111x_mode_t *mode)
+{
+    READ_CONFIG(MODE_OFFSET, MODE_MASK, mode);
+}
+
+esp_err_t ads111x_set_mode(i2c_dev_t *dev, ads111x_mode_t mode)
+{
+    return write_conf_bits(dev, mode, MODE_OFFSET, MODE_MASK);
+}
+
+esp_err_t ads111x_get_data_rate(i2c_dev_t *dev, ads111x_data_rate_t *rate)
+{
+    READ_CONFIG(DR_OFFSET, DR_MASK, rate);
+}
+
+esp_err_t ads111x_set_data_rate(i2c_dev_t *dev, ads111x_data_rate_t rate)
+{
+    return write_conf_bits(dev, rate, DR_OFFSET, DR_MASK);
+}
+
+esp_err_t ads111x_get_comp_mode(i2c_dev_t *dev, ads111x_comp_mode_t *mode)
+{
+    READ_CONFIG(COMP_MODE_OFFSET, COMP_MODE_MASK, mode);
+}
+
+esp_err_t ads111x_set_comp_mode(i2c_dev_t *dev, ads111x_comp_mode_t mode)
+{
+    return write_conf_bits(dev, mode, COMP_MODE_OFFSET, COMP_MODE_MASK);
+}
+
+esp_err_t ads111x_get_comp_polarity(i2c_dev_t *dev, ads111x_comp_polarity_t *polarity)
+{
+    READ_CONFIG(COMP_POL_OFFSET, COMP_POL_MASK, polarity);
+}
+
+esp_err_t ads111x_set_comp_polarity(i2c_dev_t *dev, ads111x_comp_polarity_t polarity)
+{
+    return write_conf_bits(dev, polarity, COMP_POL_OFFSET, COMP_POL_MASK);
+}
+
+esp_err_t ads111x_get_comp_latch(i2c_dev_t *dev, ads111x_comp_latch_t *latch)
+{
+    READ_CONFIG(COMP_LAT_OFFSET, COMP_LAT_MASK, latch);
+}
+
+esp_err_t ads111x_set_comp_latch(i2c_dev_t *dev, ads111x_comp_latch_t latch)
+{
+    return write_conf_bits(dev, latch, COMP_LAT_OFFSET, COMP_LAT_MASK);
+}
+
+esp_err_t ads111x_get_comp_queue(i2c_dev_t *dev, ads111x_comp_queue_t *queue)
+{
+    READ_CONFIG(COMP_QUE_OFFSET, COMP_QUE_MASK, queue);
+}
+
+esp_err_t ads111x_set_comp_queue(i2c_dev_t *dev, ads111x_comp_queue_t queue)
+{
+    return write_conf_bits(dev, queue, COMP_QUE_OFFSET, COMP_QUE_MASK);
+}
+
+esp_err_t ads111x_get_comp_low_thresh(i2c_dev_t *dev, int16_t *th)
+{
+    CHECK_ARG(dev && th);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, read_reg(dev, REG_THRESH_L, (uint16_t *)th));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t ads111x_set_comp_low_thresh(i2c_dev_t *dev, int16_t th)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, write_reg(dev, REG_THRESH_L, th));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t ads111x_get_comp_high_thresh(i2c_dev_t *dev, int16_t *th)
+{
+    CHECK_ARG(dev && th);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, read_reg(dev, REG_THRESH_H, (uint16_t *)th));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t ads111x_set_comp_high_thresh(i2c_dev_t *dev, int16_t th)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, write_reg(dev, REG_THRESH_H, th));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ads111x/ads111x.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,439 @@
+/*
+ * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com>
+ * Copyright (c) 2020 Lucio Tarantino <https://github.com/dianlight>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file ads111x.h
+ * @defgroup ads111x ads111x
+ * @{
+ *
+ * ESP-IDF driver for ADS1113/ADS1114/ADS1115, ADS1013/ADS1014/ADS1015 I2C ADC
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2016, 2018 Ruslan V. Uss <unclerus@gmail.com>
+ * Copyright (c) 2020 Lucio Tarantino <https://github.com/dianlight>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __ADS111X_H__
+#define __ADS111X_H__
+
+#include <stdbool.h>
+#include <esp_err.h>
+#include <i2cdev.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define ADS111X_ADDR_GND 0x48 //!< I2C device address with ADDR pin connected to ground
+#define ADS111X_ADDR_VCC 0x49 //!< I2C device address with ADDR pin connected to VCC
+#define ADS111X_ADDR_SDA 0x4a //!< I2C device address with ADDR pin connected to SDA
+#define ADS111X_ADDR_SCL 0x4b //!< I2C device address with ADDR pin connected to SCL
+
+#define ADS111X_MAX_VALUE 0x7fff //!< Maximum ADC value
+#define ADS101X_MAX_VALUE 0x7ff
+
+// ADS101X overrides
+#define ADS101X_DATA_RATE_128  	ADS111X_DATA_RATE_8
+#define ADS101X_DATA_RATE_250  	ADS111X_DATA_RATE_16
+#define ADS101X_DATA_RATE_490  	ADS111X_DATA_RATE_32
+#define ADS101X_DATA_RATE_920  	ADS111X_DATA_RATE_64
+#define ADS101X_DATA_RATE_1600	ADS111X_DATA_RATE_128
+#define ADS101X_DATA_RATE_2400	ADS111X_DATA_RATE_250
+#define ADS101X_DATA_RATE_3300	ADS111X_DATA_RATE_475
+
+/**
+ * Gain amplifier
+ */
+typedef enum
+{
+    ADS111X_GAIN_6V144 = 0, //!< +-6.144V
+    ADS111X_GAIN_4V096,     //!< +-4.096V
+    ADS111X_GAIN_2V048,     //!< +-2.048V (default)
+    ADS111X_GAIN_1V024,     //!< +-1.024V
+    ADS111X_GAIN_0V512,     //!< +-0.512V
+    ADS111X_GAIN_0V256,     //!< +-0.256V
+    ADS111X_GAIN_0V256_2,   //!< +-0.256V (same as ADS111X_GAIN_0V256)
+    ADS111X_GAIN_0V256_3,   //!< +-0.256V (same as ADS111X_GAIN_0V256)
+} ads111x_gain_t;
+
+/**
+ * Gain amplifier values
+ */
+extern const float ads111x_gain_values[];
+
+/**
+ * Input multiplexer configuration (ADS1115 only)
+ */
+typedef enum
+{
+    ADS111X_MUX_0_1 = 0, //!< positive = AIN0, negative = AIN1 (default)
+    ADS111X_MUX_0_3,     //!< positive = AIN0, negative = AIN3
+    ADS111X_MUX_1_3,     //!< positive = AIN1, negative = AIN3
+    ADS111X_MUX_2_3,     //!< positive = AIN2, negative = AIN3
+    ADS111X_MUX_0_GND,   //!< positive = AIN0, negative = GND
+    ADS111X_MUX_1_GND,   //!< positive = AIN1, negative = GND
+    ADS111X_MUX_2_GND,   //!< positive = AIN2, negative = GND
+    ADS111X_MUX_3_GND,   //!< positive = AIN3, negative = GND
+} ads111x_mux_t;
+
+/**
+ * Data rate
+ */
+typedef enum
+{
+    ADS111X_DATA_RATE_8 = 0, //!< 8 samples per second
+    ADS111X_DATA_RATE_16,    //!< 16 samples per second
+    ADS111X_DATA_RATE_32,    //!< 32 samples per second
+    ADS111X_DATA_RATE_64,    //!< 64 samples per second
+    ADS111X_DATA_RATE_128,   //!< 128 samples per second (default)
+    ADS111X_DATA_RATE_250,   //!< 250 samples per second
+    ADS111X_DATA_RATE_475,   //!< 475 samples per second
+    ADS111X_DATA_RATE_860    //!< 860 samples per second
+} ads111x_data_rate_t;
+
+/**
+ * Operational mode
+ */
+typedef enum
+{
+    ADS111X_MODE_CONTINUOUS = 0, //!< Continuous conversion mode
+    ADS111X_MODE_SINGLE_SHOT    //!< Power-down single-shot mode (default)
+} ads111x_mode_t;
+
+/**
+ * Comparator mode (ADS1114 and ADS1115 only)
+ */
+typedef enum
+{
+    ADS111X_COMP_MODE_NORMAL = 0, //!< Traditional comparator with hysteresis (default)
+    ADS111X_COMP_MODE_WINDOW      //!< Window comparator
+} ads111x_comp_mode_t;
+
+/**
+ * Comparator polarity (ADS1114 and ADS1115 only)
+ */
+typedef enum
+{
+    ADS111X_COMP_POLARITY_LOW = 0, //!< Active low (default)
+    ADS111X_COMP_POLARITY_HIGH     //!< Active high
+} ads111x_comp_polarity_t;
+
+/**
+ * Comparator latch (ADS1114 and ADS1115 only)
+ */
+typedef enum
+{
+    ADS111X_COMP_LATCH_DISABLED = 0, //!< Non-latching comparator (default)
+    ADS111X_COMP_LATCH_ENABLED       //!< Latching comparator
+} ads111x_comp_latch_t;
+
+/**
+ * Comparator queue
+ */
+typedef enum
+{
+    ADS111X_COMP_QUEUE_1 = 0,   //!< Assert ALERT/RDY pin after one conversion
+    ADS111X_COMP_QUEUE_2,       //!< Assert ALERT/RDY pin after two conversions
+    ADS111X_COMP_QUEUE_4,       //!< Assert ALERT/RDY pin after four conversions
+    ADS111X_COMP_QUEUE_DISABLED //!< Disable comparator (default)
+} ads111x_comp_queue_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev Device descriptor
+ * @param addr Device address
+ * @param port I2C port number
+ * @param sda_gpio GPIO pin for SDA
+ * @param scl_gpio GPIO pin for SCL
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads111x_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port,
+        gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads111x_free_desc(i2c_dev_t *dev);
+
+/**
+ * @brief Get device operational status
+ *
+ * @param dev Device descriptor
+ * @param[out] busy True when device performing conversion
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads111x_is_busy(i2c_dev_t *dev, bool *busy);
+
+/**
+ * @brief Begin a single conversion
+ *
+ * Only in single-shot mode.
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads111x_start_conversion(i2c_dev_t *dev);
+
+/**
+ * @brief Read last conversion result
+ *
+ * @param dev Device descriptor
+ * @param[out] value Last conversion result
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads111x_get_value(i2c_dev_t *dev, int16_t *value);
+
+/**
+ * @brief Read last conversion result for ADS101x
+ *
+ * @param dev Device descriptor
+ * @param[out] value Last conversion result
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads101x_get_value(i2c_dev_t *dev, int16_t *value);
+
+/**
+ * @brief Read the programmable gain amplifier configuration
+ *
+ * ADS1114 and ADS1115 only.
+ * Use ::ads111x_gain_values[] for real voltage.
+ *
+ * @param dev Device descriptor
+ * @param[out] gain Gain value
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads111x_get_gain(i2c_dev_t *dev, ads111x_gain_t *gain);
+
+/**
+ * @brief Configure the programmable gain amplifier
+ *
+ * ADS1114 and ADS1115 only.
+ *
+ * @param dev Device descriptor
+ * @param gain Gain value
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads111x_set_gain(i2c_dev_t *dev, ads111x_gain_t gain);
+
+/**
+ * @brief Read the input multiplexer configuration
+ *
+ * ADS1115 only.
+ *
+ * @param dev Device descriptor
+ * @param[out] mux Input multiplexer configuration
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads111x_get_input_mux(i2c_dev_t *dev, ads111x_mux_t *mux);
+
+/**
+ * @brief Configure the input multiplexer configuration
+ *
+ * ADS1115 only.
+ *
+ * @param dev Device descriptor
+ * @param mux Input multiplexer configuration
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads111x_set_input_mux(i2c_dev_t *dev, ads111x_mux_t mux);
+
+/**
+ * @brief Read the device operating mode
+ *
+ * @param dev Device descriptor
+ * @param[out] mode Device operating mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads111x_get_mode(i2c_dev_t *dev, ads111x_mode_t *mode);
+
+/**
+ * @brief Set the device operating mode
+ *
+ * @param dev Device descriptor
+ * @param mode Device operating mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads111x_set_mode(i2c_dev_t *dev, ads111x_mode_t mode);
+
+/**
+ * @brief Read the data rate
+ *
+ * @param dev Device descriptor
+ * @param[out] rate Data rate
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads111x_get_data_rate(i2c_dev_t *dev, ads111x_data_rate_t *rate);
+
+/**
+ * @brief Configure the data rate
+ *
+ * @param dev Device descriptor
+ * @param rate Data rate
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads111x_set_data_rate(i2c_dev_t *dev, ads111x_data_rate_t rate);
+
+/**
+ * @brief Get comparator mode
+ *
+ * ADS1114 and ADS1115 only.
+ *
+ * @param dev Device descriptor
+ * @param[out] mode Comparator mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads111x_get_comp_mode(i2c_dev_t *dev, ads111x_comp_mode_t *mode);
+
+/**
+ * @brief Set comparator mode
+ *
+ * ADS1114 and ADS1115 only.
+ *
+ * @param dev Device descriptor
+ * @param mode Comparator mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads111x_set_comp_mode(i2c_dev_t *dev, ads111x_comp_mode_t mode);
+
+/**
+ * @brief Get polarity of the comparator output pin ALERT/RDY
+ *
+ * ADS1114 and ADS1115 only.
+ *
+ * @param dev Device descriptor
+ * @param[out] polarity Comparator output pin polarity
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads111x_get_comp_polarity(i2c_dev_t *dev, ads111x_comp_polarity_t *polarity);
+
+/**
+ * @brief Set polarity of the comparator output pin ALERT/RDY
+ *
+ * ADS1114 and ADS1115 only.
+ *
+ * @param dev Device descriptor
+ * @param polarity Comparator output pin polarity
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads111x_set_comp_polarity(i2c_dev_t *dev, ads111x_comp_polarity_t polarity);
+
+/**
+ * @brief Get comparator output latch mode
+ *
+ * ADS1114 and ADS1115 only.
+ *
+ * @param dev Device descriptor
+ * @param[out] latch Comparator output latch mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads111x_get_comp_latch(i2c_dev_t *dev, ads111x_comp_latch_t *latch);
+
+/**
+ * @brief Set comparator output latch mode
+ *
+ * ADS1114 and ADS1115 only.
+ *
+ * @param dev Device descriptor
+ * @param latch Comparator output latch mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads111x_set_comp_latch(i2c_dev_t *dev, ads111x_comp_latch_t latch);
+
+/**
+ * @brief Get comparator queue size
+ *
+ * Get number of the comparator conversions before pin ALERT/RDY
+ * assertion. ADS1114 and ADS1115 only.
+ *
+ * @param dev Device descriptor
+ * @param[out] queue Number of the comparator conversions
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads111x_get_comp_queue(i2c_dev_t *dev, ads111x_comp_queue_t *queue);
+
+/**
+ * @brief Set comparator queue size
+ *
+ * Set number of the comparator conversions before pin ALERT/RDY
+ * assertion or disable comparator. ADS1114 and ADS1115 only.
+ *
+ * @param dev Device descriptor
+ * @param queue Number of the comparator conversions
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads111x_set_comp_queue(i2c_dev_t *dev, ads111x_comp_queue_t queue);
+
+/**
+ * @brief Get the lower threshold value used by comparator
+ *
+ * @param dev Device descriptor
+ * @param[out] th Lower threshold value
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads111x_get_comp_low_thresh(i2c_dev_t *dev, int16_t *th);
+
+/**
+ * @brief Set the lower threshold value used by comparator
+ *
+ * @param dev Device descriptor
+ * @param th Lower threshold value
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads111x_set_comp_low_thresh(i2c_dev_t *dev, int16_t th);
+
+/**
+ * @brief Get the upper threshold value used by comparator
+ *
+ * @param dev Device descriptor
+ * @param[out] th Upper threshold value
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads111x_get_comp_high_thresh(i2c_dev_t *dev, int16_t *th);
+
+/**
+ * @brief Set the upper threshold value used by comparator
+ *
+ * @param dev Device descriptor
+ * @param th Upper threshold value
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads111x_set_comp_high_thresh(i2c_dev_t *dev, int16_t th);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __ADS111X_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ads111x/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ads130e08/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,20 @@
+---
+components:
+  - name: ads130e08
+    description: Driver for ADS130E08 ADC
+    group: adc-dac
+    groups: []
+    code_owners: weslleymfd
+    depends:
+      - driver
+      - log
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp32s3
+    licenses:
+      - name: MIT
+    copyrights:
+      - author:
+          name: weslleymfd
+        year: 2021
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ads130e08/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS "ads130e08.c"
+    INCLUDE_DIRS "."
+    REQUIRES driver log
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ads130e08/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2021 Weslley M. F. Duarte <weslleymfd@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ads130e08/ads130e08.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,627 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2021 Weslley M. F. Duarte <weslleymfd@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file ads130e08.c
+ *
+ * ESP-IDF driver for ADS130E08 ADC
+ *
+ * Copyright (c) 2021 Weslley M. F. Duarte <weslleymfd@gmail.com>
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+
+#include "ads130e08.h"
+#include <esp_log.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <string.h>
+
+#define ADS130E08_CMD_WAKEUP  (0x02)
+#define ADS130E08_CMD_STANDBY (0x04)
+#define ADS130E08_CMD_RESET   (0x06)
+#define ADS130E08_CMD_START   (0x08)
+#define ADS130E08_CMD_STOP    (0x0A)
+#define ADS130E08_CMD_RDATAC  (0x10)
+#define ADS130E08_CMD_SDATAC  (0x11)
+#define ADS130E08_CMD_RDATA   (0x12)
+#define ADS130E08_CMD_RREG    (0x20)
+#define ADS130E08_CMD_WREG    (0x40)
+
+#define ADS130E08_REG_ID          (0x00)
+#define ADS130E08_REG_CONFIG1     (0x01)
+#define ADS130E08_REG_CONFIG2     (0x02)
+#define ADS130E08_REG_CONFIG3     (0x03)
+#define ADS130E08_REG_FAULT       (0x04)
+#define ADS130E08_REG_CH1SET      (0x05)
+#define ADS130E08_REG_CH2SET      (0x06)
+#define ADS130E08_REG_CH3SET      (0x07)
+#define ADS130E08_REG_CH4SET      (0x08)
+#define ADS130E08_REG_CH5SET      (0x09)
+#define ADS130E08_REG_CH6SET      (0x0A)
+#define ADS130E08_REG_CH7SET      (0x0B)
+#define ADS130E08_REG_CH8SET      (0x0C)
+#define ADS130E08_REG_FAULT_STATP (0x12)
+#define ADS130E08_REG_FAULT_STATN (0x13)
+#define ADS130E08_REG_GPIO        (0x14)
+
+#define ID_MASK_LOW_BITS  (0x08)
+#define ID_MASK_HIGH_BITS (0x10)
+
+#define CONFIG1_MASK_LOW_BITS    (0xDE)
+#define CONFIG1_MASK_HIGH_BITS   (0x01)
+#define CONFIG1_MASK_BITS_CLK_EN BIT(5)
+
+#define CONFIG2_MASK_LOW_BITS       (0x88)
+#define CONFIG2_MASK_HIGH_BITS      (0x60)
+#define CONFIG2_MASK_BITS_INT_TEST  BIT(4)
+#define CONFIG2_MASK_BITS_TEST_AMP  BIT(2)
+#define CONFIG2_MASK_BITS_TEST_FREQ (BIT(1) | BIT(0))
+
+#define CONFIG3_MASK_LOW_BITS       (0x13)
+#define CONFIG3_MASK_HIGH_BITS      (0x40)
+#define CONFIG3_MASK_BITS_PD_REFBUF BIT(7)
+#define CONFIG3_MASK_BITS_VREF_4V   BIT(5)
+#define CONFIG3_MASK_BITS_OPAMP_REF BIT(3)
+#define CONFIG3_MASK_BITS_PD_OPAMP  BIT(2)
+
+#define FAULT_MASK_LOW_BITS     (0x1F)
+#define FAULT_MASK_BITS_COMP_TH (BIT(7) | BIT(6) | BIT(5))
+
+#define CHnSET_MASK_LOW_BITS (0x08)
+#define CHnSET_MASK_BITS_PD  BIT(7)
+#define CHnSET_MASK_BITS_PGA (BIT(6) | BIT(5) | BIT(4))
+#define CHnSET_MASK_BITS_MUX (BIT(2) | BIT(1) | BIT(0))
+
+#define MASK_BITS_IN1P_FAULT BIT(0)
+#define MASK_BITS_IN2P_FAULT BIT(1)
+#define MASK_BITS_IN3P_FAULT BIT(2)
+#define MASK_BITS_IN4P_FAULT BIT(3)
+#define MASK_BITS_IN5P_FAULT BIT(4)
+#define MASK_BITS_IN6P_FAULT BIT(5)
+#define MASK_BITS_IN7P_FAULT BIT(6)
+#define MASK_BITS_IN8P_FAULT BIT(7)
+
+#define MASK_BITS_IN1N_FAULT BIT(0)
+#define MASK_BITS_IN2N_FAULT BIT(1)
+#define MASK_BITS_IN3N_FAULT BIT(2)
+#define MASK_BITS_IN4N_FAULT BIT(3)
+#define MASK_BITS_IN5N_FAULT BIT(4)
+#define MASK_BITS_IN6N_FAULT BIT(5)
+#define MASK_BITS_IN7N_FAULT BIT(6)
+#define MASK_BITS_IN8N_FAULT BIT(7)
+
+#define MASK_BITS_GPIOC1 BIT(0)
+#define MASK_BITS_GPIOC2 BIT(1)
+#define MASK_BITS_GPIOC3 BIT(2)
+#define MASK_BITS_GPIOC4 BIT(3)
+#define MASK_BITS_GPIOD1 BIT(4)
+#define MASK_BITS_GPIOD2 BIT(5)
+#define MASK_BITS_GPIOD3 BIT(6)
+#define MASK_BITS_GPIOD4 BIT(7)
+
+#define CLOCK_SPEED_HZ (4096000) /**< 4MHz */
+
+#define ADS130E08_CONVERSION_CONST 0.0000732421875f /* 2.4 / 32768 */
+
+#define CHECK(x)                                                                                                       \
+    do                                                                                                                 \
+    {                                                                                                                  \
+        esp_err_t __;                                                                                                  \
+        if ((__ = x) != ESP_OK)                                                                                        \
+            return __;                                                                                                 \
+    }                                                                                                                  \
+    while (0)
+#define CHECK_ARG(VAL)                                                                                                 \
+    do                                                                                                                 \
+    {                                                                                                                  \
+        if (!(VAL))                                                                                                    \
+            return ESP_ERR_INVALID_ARG;                                                                                \
+    }                                                                                                                  \
+    while (0)
+#define BV(x) (1 << (x))
+
+static const char *TAG_ADS130E08 = "ads130e08";
+
+static esp_err_t write_reg_8(ads130e08_t *dev, uint8_t reg, uint8_t val)
+{
+    spi_transaction_t t;
+    memset(&t, 0, sizeof(spi_transaction_t));
+
+    uint8_t tx[] = { (reg | ADS130E08_CMD_WREG), 0, val, 0 };
+
+    t.tx_buffer = tx;
+    t.length = sizeof(tx) * 8;
+
+    return spi_device_transmit(dev->spi_dev, &t);
+}
+
+static esp_err_t read_reg_8(ads130e08_t *dev, uint8_t reg, uint8_t *val)
+{
+    spi_transaction_t t;
+    memset(&t, 0, sizeof(spi_transaction_t));
+
+    uint8_t tx[] = { (reg | ADS130E08_CMD_RREG), 0, 0, 0 };
+    uint8_t rx[sizeof(tx)];
+
+    t.tx_buffer = tx;
+    t.rx_buffer = rx;
+    t.length = sizeof(tx) * 8;
+    CHECK(spi_device_transmit(dev->spi_dev, &t));
+
+    *val = rx[2];
+
+    return ESP_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t ads130e08_init_desc(ads130e08_t *dev, spi_host_device_t host, gpio_num_t cs_pin)
+{
+    CHECK_ARG(dev);
+
+    memset(&dev->spi_cfg, 0, sizeof(dev->spi_cfg));
+    dev->spi_cfg.spics_io_num = cs_pin;
+    dev->spi_cfg.clock_speed_hz = CLOCK_SPEED_HZ;
+    dev->spi_cfg.mode = 1;
+    dev->spi_cfg.queue_size = 1;
+    dev->spi_cfg.cs_ena_pretrans = 1;
+
+    return spi_bus_add_device(host, &dev->spi_cfg, &dev->spi_dev);
+}
+
+esp_err_t ads130e08_free_desc(ads130e08_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return spi_bus_remove_device(dev->spi_dev);
+}
+
+esp_err_t ads130e08_send_system_cmd(ads130e08_t *dev, ads130e08_system_cmd_t cmd)
+{
+    CHECK_ARG(dev);
+
+    spi_transaction_t t;
+    memset(&t, 0, sizeof(spi_transaction_t));
+
+    uint8_t tx[] = { (cmd), 0 };
+
+    t.tx_buffer = tx;
+    t.length = sizeof(tx) * 8;
+
+    return spi_device_transmit(dev->spi_dev, &t);
+}
+
+esp_err_t ads130e08_send_data_read_cmd(ads130e08_t *dev, ads130e08_data_read_cmd_t cmd)
+{
+    CHECK_ARG(dev);
+
+    spi_transaction_t t;
+    memset(&t, 0, sizeof(spi_transaction_t));
+
+    uint8_t tx[] = { (cmd), 0 };
+
+    t.tx_buffer = tx;
+    t.length = sizeof(tx) * 8;
+
+    return spi_device_transmit(dev->spi_dev, &t);
+}
+
+esp_err_t ads130e08_get_device_id(ads130e08_t *dev, uint8_t *id)
+{
+    CHECK_ARG(dev);
+
+    uint8_t val;
+
+    CHECK(read_reg_8(dev, ADS130E08_REG_ID, &val));
+
+    *id = val;
+
+    return ESP_OK;
+}
+
+esp_err_t ads130e08_set_device_config(ads130e08_t *dev, ads130e08_dev_config_t config)
+{
+    CHECK_ARG(dev);
+
+    uint8_t config1 = 0x00, config2 = 0x00, config3 = 0x00;
+
+    config1 = (config.clk_en | CONFIG1_MASK_HIGH_BITS) & ~(CONFIG1_MASK_LOW_BITS);
+
+    CHECK(write_reg_8(dev, ADS130E08_REG_CONFIG1, config1));
+
+    config2
+        = (config.int_test | config.test_amp | config.test_freq | CONFIG2_MASK_HIGH_BITS) & ~(CONFIG2_MASK_LOW_BITS);
+
+    CHECK(write_reg_8(dev, ADS130E08_REG_CONFIG2, config2));
+
+    config3 = (config.pd_refbuf | config.vref_4v | config.opamp_ref | config.pd_opamp | CONFIG3_MASK_HIGH_BITS)
+              & ~(CONFIG3_MASK_LOW_BITS);
+
+    CHECK(write_reg_8(dev, ADS130E08_REG_CONFIG3, config3));
+
+    return ESP_OK;
+}
+
+esp_err_t ads130e08_get_device_config(ads130e08_t *dev, ads130e08_dev_config_t *config)
+{
+    CHECK_ARG(dev && config);
+
+    uint8_t config1, config2, config3;
+
+    CHECK(read_reg_8(dev, ADS130E08_REG_CONFIG1, &config1));
+
+    config->clk_en = (config1 & CONFIG1_MASK_BITS_CLK_EN);
+
+    CHECK(read_reg_8(dev, ADS130E08_REG_CONFIG2, &config2));
+
+    config->int_test = (config2 & CONFIG2_MASK_BITS_INT_TEST);
+    config->test_amp = (config2 & CONFIG2_MASK_BITS_TEST_AMP);
+    config->test_freq = (config2 & CONFIG2_MASK_BITS_TEST_FREQ);
+
+    CHECK(read_reg_8(dev, ADS130E08_REG_CONFIG3, &config3));
+
+    config->pd_refbuf = (config3 & CONFIG3_MASK_BITS_PD_REFBUF);
+    config->vref_4v = (config3 & CONFIG3_MASK_BITS_VREF_4V);
+    config->opamp_ref = (config3 & CONFIG3_MASK_BITS_OPAMP_REF);
+    config->pd_opamp = (config3 & CONFIG3_MASK_BITS_PD_OPAMP);
+
+    return ESP_OK;
+}
+
+#define UPPER_1_BITS 0x80
+#define LOWER_7_BITS 0x7f
+
+esp_err_t ads130e08_get_rdata(ads130e08_t *dev, ads130e08_raw_data_t *raw_data)
+{
+    // (24 status bits + 16 bits × 8 channels) = 152 bits data per device + 1 dummy bit when using daisy
+    // The format of the 24 status bits is (1100 + FAULT_STATP + FAULT_STATN + bits[7:4] of the GPIO: General- Purpose
+    // IO Register). The data format for each channel data is twos complement, MSB first. When channels are powered down
+    // using user register settings, the corresponding channel output is set to '0'.
+
+    CHECK_ARG(dev);
+
+    spi_transaction_t t;
+    memset(&t, 0, sizeof(spi_transaction_t));
+
+    uint8_t tx[] = { (ADS130E08_CMD_RDATA), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+    uint8_t rx[sizeof(tx)] = { 0 };
+
+    t.tx_buffer = tx;
+    t.rx_buffer = rx;
+    t.length = sizeof(tx) * 8;
+
+    CHECK(spi_device_transmit(dev->spi_dev, &t));
+
+    uint32_t status_bits;
+
+    status_bits = ((rx[1] | rx[2] | rx[3]) & 0x00FFFFFF);
+
+    raw_data->fault_statp = (uint8_t)(status_bits & 0x000FF000);
+    raw_data->fault_statn = (uint8_t)(status_bits & 0x00000FF0);
+    raw_data->gpios_level = (uint8_t)(status_bits & 0x0000000F);
+
+    uint16_t adc_raw_two_complememt;
+
+    size_t j = 0;
+
+    for (size_t i = 0; i < 8; i++)
+    {
+        adc_raw_two_complememt = ((uint16_t)(rx[4 + j] << 8) | (uint16_t)(rx[4 + j + 1] << 0));
+        raw_data->channels_raw[i] = (int16_t)adc_raw_two_complememt;
+
+        j += 2;
+    }
+
+    return ESP_OK;
+}
+
+esp_err_t ads130e08_convert_raw_to_voltage(int16_t raw, uint8_t gain, float *volts)
+{
+    *volts = (ADS130E08_CONVERSION_CONST * raw) / gain;
+
+    return ESP_OK;
+}
+
+esp_err_t ads130e08_set_fault_detect_control(ads130e08_t *dev, ads130e08_fault_threshold_t fault_mode)
+{
+    CHECK_ARG(dev);
+
+    uint8_t val = 0x00;
+
+    val = (fault_mode) & ~(FAULT_MASK_LOW_BITS);
+
+    CHECK(write_reg_8(dev, ADS130E08_REG_FAULT, val));
+
+    return ESP_OK;
+}
+
+esp_err_t ads130e08_get_fault_detect_control(ads130e08_t *dev, ads130e08_fault_threshold_t *fault_mode)
+{
+    CHECK_ARG(dev);
+
+    uint8_t val;
+
+    CHECK(read_reg_8(dev, ADS130E08_REG_FAULT, &val));
+
+    *fault_mode = val;
+
+    return ESP_OK;
+}
+
+esp_err_t ads130e08_set_channel_config(ads130e08_t *dev, ads130e08_channel_t channel, ads130e08_channel_config_t config)
+{
+    CHECK_ARG(dev && channel);
+
+    uint8_t val = 0x00;
+
+    val = (config.enable | config.pga_gain | config.mode) & ~(CHnSET_MASK_LOW_BITS);
+
+    CHECK(write_reg_8(dev, channel, val));
+
+    return ESP_OK;
+}
+
+esp_err_t ads130e08_get_channel_config(ads130e08_t *dev, ads130e08_channel_t channel,
+    ads130e08_channel_config_t *config)
+{
+    CHECK_ARG(dev && config);
+
+    uint8_t val;
+    CHECK(read_reg_8(dev, channel, &val));
+
+    config->enable = val & CHnSET_MASK_BITS_PD;
+    config->pga_gain = val & CHnSET_MASK_BITS_PGA;
+    config->mode = val & CHnSET_MASK_BITS_MUX;
+
+    return ESP_OK;
+}
+
+esp_err_t ads130e08_set_gpio_pin_mode(ads130e08_t *dev, ads130e08_gpio_pin_t gpio_pin, ads130e08_gpio_mode_t gpio_mode)
+{
+    CHECK_ARG(dev && gpio_pin && gpio_mode);
+
+    uint8_t val;
+
+    CHECK(read_reg_8(dev, ADS130E08_REG_GPIO, &val));
+
+    if (gpio_pin == ADS130E08_GPIO1)
+    {
+        val |= (gpio_mode & MASK_BITS_GPIOC1);
+    }
+    else if (gpio_pin == ADS130E08_GPIO2)
+    {
+        val |= (gpio_mode & MASK_BITS_GPIOC2);
+    }
+    else if (gpio_pin == ADS130E08_GPIO3)
+    {
+        val |= (gpio_mode & MASK_BITS_GPIOC3);
+    }
+    else if (gpio_pin == ADS130E08_GPIO4)
+    {
+        val |= (gpio_mode & MASK_BITS_GPIOC4);
+    }
+    else
+    {
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    CHECK(write_reg_8(dev, ADS130E08_REG_GPIO, val));
+
+    return ESP_OK;
+}
+
+esp_err_t ads130e08_get_gpio_pin_mode(ads130e08_t *dev, ads130e08_gpio_pin_t gpio_pin, ads130e08_gpio_mode_t *gpio_mode)
+{
+    CHECK_ARG(dev && gpio_pin && gpio_mode);
+
+    uint8_t val;
+
+    CHECK(read_reg_8(dev, ADS130E08_REG_GPIO, &val));
+
+    if (gpio_pin == ADS130E08_GPIO1)
+    {
+        *gpio_mode = (val & MASK_BITS_GPIOC1);
+    }
+    else if (gpio_pin == ADS130E08_GPIO2)
+    {
+        *gpio_mode = (val & MASK_BITS_GPIOC2);
+    }
+    else if (gpio_pin == ADS130E08_GPIO3)
+    {
+        *gpio_mode = (val & MASK_BITS_GPIOC3);
+    }
+    else if (gpio_pin == ADS130E08_GPIO4)
+    {
+        *gpio_mode = (val & MASK_BITS_GPIOC4);
+    }
+    else
+    {
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    return ESP_OK;
+}
+
+esp_err_t ads130e08_set_gpio_pin_level(ads130e08_t *dev, ads130e08_gpio_pin_t gpio_pin,
+    ads130e08_gpio_level_t gpio_level)
+{
+    CHECK_ARG(dev && gpio_pin && gpio_level);
+
+    uint8_t val;
+
+    CHECK(read_reg_8(dev, ADS130E08_REG_GPIO, &val));
+
+    if (gpio_pin == ADS130E08_GPIO1)
+    {
+        val |= (gpio_level & MASK_BITS_GPIOD1);
+    }
+    else if (gpio_pin == ADS130E08_GPIO2)
+    {
+        val |= (gpio_level & MASK_BITS_GPIOD2);
+    }
+    else if (gpio_pin == ADS130E08_GPIO3)
+    {
+        val |= (gpio_level & MASK_BITS_GPIOD3);
+    }
+    else if (gpio_pin == ADS130E08_GPIO4)
+    {
+        val |= (gpio_level & MASK_BITS_GPIOD4);
+    }
+    else
+    {
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    CHECK(write_reg_8(dev, ADS130E08_REG_GPIO, val));
+
+    return ESP_OK;
+}
+
+esp_err_t ads130e08_get_gpio_pin_level(ads130e08_t *dev, ads130e08_gpio_pin_t gpio_pin,
+    ads130e08_gpio_level_t *gpio_level)
+{
+    CHECK_ARG(dev && gpio_pin && gpio_level);
+
+    uint8_t val;
+
+    CHECK(read_reg_8(dev, ADS130E08_REG_GPIO, &val));
+
+    if (gpio_pin == ADS130E08_GPIO1)
+    {
+        *gpio_level = (val & MASK_BITS_GPIOD1);
+    }
+    else if (gpio_pin == ADS130E08_GPIO2)
+    {
+        *gpio_level = (val & MASK_BITS_GPIOD2);
+    }
+    else if (gpio_pin == ADS130E08_GPIO3)
+    {
+        *gpio_level = (val & MASK_BITS_GPIOD3);
+    }
+    else if (gpio_pin == ADS130E08_GPIO4)
+    {
+        *gpio_level = (val & MASK_BITS_GPIOD4);
+    }
+    else
+    {
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    return ESP_OK;
+}
+
+esp_err_t ads130e08_detect_fault_auto(ads130e08_t *dev, uint8_t *fault_statp, uint8_t *fault_statn)
+{
+    CHECK_ARG(dev);
+
+    uint8_t fault_bits;
+    CHECK(read_reg_8(dev, ADS130E08_REG_FAULT_STATP, &fault_bits));
+    *fault_statp = fault_bits;
+
+    if (fault_bits & MASK_BITS_IN1P_FAULT)
+    {
+        ESP_LOGE(TAG_ADS130E08, "Automatic fault detection on positive input 1");
+    }
+
+    if (fault_bits & MASK_BITS_IN2P_FAULT)
+    {
+        ESP_LOGE(TAG_ADS130E08, "Automatic fault detection on positive input 2");
+    }
+
+    if (fault_bits & MASK_BITS_IN3P_FAULT)
+    {
+        ESP_LOGE(TAG_ADS130E08, "Automatic fault detection on positive input 3");
+    }
+
+    if (fault_bits & MASK_BITS_IN4P_FAULT)
+    {
+        ESP_LOGE(TAG_ADS130E08, "Automatic fault detection on positive input 4");
+    }
+
+    if (fault_bits & MASK_BITS_IN5P_FAULT)
+    {
+        ESP_LOGE(TAG_ADS130E08, "Automatic fault detection on positive input 5");
+    }
+
+    if (fault_bits & MASK_BITS_IN6P_FAULT)
+    {
+        ESP_LOGE(TAG_ADS130E08, "Automatic fault detection on positive input 6");
+    }
+
+    if (fault_bits & MASK_BITS_IN7P_FAULT)
+    {
+        ESP_LOGE(TAG_ADS130E08, "Automatic fault detection on positive input 7");
+    }
+
+    if (fault_bits & MASK_BITS_IN8P_FAULT)
+    {
+        ESP_LOGE(TAG_ADS130E08, "Automatic fault detection on positive input 8");
+    }
+
+    CHECK(read_reg_8(dev, ADS130E08_REG_FAULT_STATN, &fault_bits));
+    *fault_statn = fault_bits;
+
+    if (fault_bits & MASK_BITS_IN1N_FAULT)
+    {
+        ESP_LOGE(TAG_ADS130E08, "Automatic fault detection on negative input 1");
+    }
+
+    if (fault_bits & MASK_BITS_IN2N_FAULT)
+    {
+        ESP_LOGE(TAG_ADS130E08, "Automatic fault detection on negative input 2");
+    }
+
+    if (fault_bits & MASK_BITS_IN3N_FAULT)
+    {
+        ESP_LOGE(TAG_ADS130E08, "Automatic fault detection on negative input 3");
+    }
+
+    if (fault_bits & MASK_BITS_IN4N_FAULT)
+    {
+        ESP_LOGE(TAG_ADS130E08, "Automatic fault detection on negative input 4");
+    }
+
+    if (fault_bits & MASK_BITS_IN5N_FAULT)
+    {
+        ESP_LOGE(TAG_ADS130E08, "Automatic fault detection on negative input 5");
+    }
+
+    if (fault_bits & MASK_BITS_IN6N_FAULT)
+    {
+        ESP_LOGE(TAG_ADS130E08, "Automatic fault detection on negative input 6");
+    }
+
+    if (fault_bits & MASK_BITS_IN7N_FAULT)
+    {
+        ESP_LOGE(TAG_ADS130E08, "Automatic fault detection on negative input 7");
+    }
+
+    if (fault_bits & MASK_BITS_IN8N_FAULT)
+    {
+        ESP_LOGE(TAG_ADS130E08, "Automatic fault detection on negative input 8");
+    }
+
+    return ESP_OK;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ads130e08/ads130e08.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,420 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2021 Weslley M. F. Duarte <weslleymfd@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file ads130e08.h
+ * @defgroup ads130e08 ads130e08
+ * @{
+ *
+ * ESP-IDF driver for ADS130E08 ADC
+ *
+ * Copyright (c) 2021 Weslley M. F. Duarte <weslleymfd@gmail.com>
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+
+#ifndef __ADS130E08_H__
+#define __ADS130E08_H__
+
+#include <driver/spi_master.h>
+#include <driver/gpio.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+    ADS130E08_CMD_WAKEUP = 0x02,
+    ADS130E08_CMD_STANDBY = 0x04,
+    ADS130E08_CMD_RESET = 0x06,
+    ADS130E08_CMD_START = 0x08,
+    ADS130E08_CMD_STOP = 0x0A
+} ads130e08_system_cmd_t;
+
+typedef enum {
+    ADS130E08_CMD_RDATAC = 0x10,
+    ADS130E08_CMD_SDATAC = 0x11,
+    ADS130E08_CMD_RDATA = 0x12
+} ads130e08_data_read_cmd_t;
+
+typedef enum {
+    ADS130E08_CLK_OUT_DISABLED = 0x00, /**< Oscillator clock output disabled (default) */
+    ADS130E08_CLK_OUT_ENABLED = 0x20   /**< Oscillator clock output enabled */
+} ads130e08_clk_en_t;
+
+typedef enum {
+    ADS130E08_INT_TEST_EXTERNAL = 0x00, /**< Test signals are driven externally (default) */
+    ADS130E08_INT_TEST_INTERNAL = 0x10  /**< Test signals are generated internally */
+} ads130e08_int_test_t;
+
+typedef enum {
+    ADS130E08_TEST_AMP_CALIB_1X = 0x00, /**< 1 × –(VREFP – VREFN) / 2.4 mV (default) */
+    ADS130E08_TEST_AMP_CALIB_2X = 0x04  /**< 2 × –(VREFP – VREFN) / 2.4 mV */
+} ads130e08_test_amp_t;
+
+typedef enum {
+    ADS130E08_TEST_FREQ_EXP_21 = 0x00, /**< Pulsed at fCLK / 2^21 (default) */
+    ADS130E08_TEST_FREQ_EXP_20 = 0x01, /**< Pulsed at fCLK / 2^20 */
+    ADS130E08_TEST_FREQ_AT_DC = 0x11   /**< At dc */
+} ads130e08_test_freq_t;
+
+typedef enum {
+    ADS130E08_INTERNAL_REF_BUFFER_DISABLED = 0x00, /**< Power-down internal reference buffer (default) */
+    ADS130E08_INTERNAL_REF_BUFFER_ENABLED = 0x80   /**< Enable internal reference buffer */
+} ads130e08_pd_refbuf_t;
+
+typedef enum {
+    ADS130E08_REF_VOLTAGE_2_4V = 0x00, /**< VREFP is set to 2.4 V (default) */
+    ADS130E08_REF_VOLTAGE_4_0V = 0x20, /**< VREFP is set to 4 V (only use with a 5-V analog supply) */
+} ads130e08_vref_4v_t;
+
+typedef enum {
+    ADS130E08_NON_INVERTING_CONNECT_OPAMP = 0x00, /**< Noninverting input connected to the OPAMPP pin (default) */
+    ADS130E08_NON_INVERTING_CONNECT_AV = 0x08,    /**< Noninverting input connected to (AVDD + AVSS) / 2 */
+} ads130e08_opamp_ref_t;
+
+typedef enum {
+    ADS130E08_OPAMP_DISABLED = 0x00, /**< Power-down op amp (default) */
+    ADS130E08_OPAMP_ENABLED = 0x04,  /**< Enable op amp */
+} ads130e08_pd_opamp_t;
+
+/**
+ * Channels
+ */
+typedef enum {
+    ADS130E08_CHANNEL_1 = 0x05,
+    ADS130E08_CHANNEL_2 = 0x06,
+    ADS130E08_CHANNEL_3 = 0x07,
+    ADS130E08_CHANNEL_4 = 0x08,
+    ADS130E08_CHANNEL_5 = 0x09,
+    ADS130E08_CHANNEL_6 = 0x0A,
+    ADS130E08_CHANNEL_7 = 0x0B,
+    ADS130E08_CHANNEL_8 = 0x0C
+} ads130e08_channel_t;
+
+/**
+ * Fault detect comparator threshold
+ */
+typedef enum {
+    ADS130E08_MODE_1 = 0x00, /**< Comparator positive threshold: 95% , negative threshold: 5% (default) */
+    ADS130E08_MODE_2 = 0x20, /**< Comparator positive threshold: 92.5% , negative threshold: 7.5% (default) */
+    ADS130E08_MODE_3 = 0x40, /**< Comparator positive threshold: 90% , negative threshold: 10% (default) */
+    ADS130E08_MODE_4 = 0x60, /**< Comparator positive threshold: 87.5% , negative threshold: 12.5% (default) */
+    ADS130E08_MODE_5 = 0x80, /**< Comparator positive threshold: 85% , negative threshold: 15% (default) */
+    ADS130E08_MODE_6 = 0xA0, /**< Comparator positive threshold: 80% , negative threshold: 20% (default) */
+    ADS130E08_MODE_7 = 0xC0, /**< Comparator positive threshold: 75% , negative threshold: 25% (default) */
+    ADS130E08_MODE_8 = 0xE0  /**< Comparator positive threshold: 70% , negative threshold: 30% (default) */
+} ads130e08_fault_threshold_t;
+
+/**
+ * Power down
+ */
+typedef enum {
+    ADS130E08_NORMAL_OPERATION = 0x00, /**< Normal operation (default) */
+    ADS130E08_POWER_DOWN = 0x80        /**< Channel power-down */
+} ads130e08_pd_t;
+
+/**
+ * PGA gain
+ */
+typedef enum {
+    ADS130E08_PGA_1 = 0x10, /**< x1 */
+    ADS130E08_PGA_2 = 0x20, /**< x2 */
+    ADS130E08_PGA_8 = 0x50  /**< x8 */
+} ads130e08_pga_gain_t;
+
+/**
+ * MUX
+ */
+typedef enum {
+    ADS130E08_NORMAL_INPUT = 0x00,       /**< Normal input (default) */
+    ADS130E08_INPUT_SHORTED = 0x01,      /**< Input shorted (for offset or noise measurements) */
+    ADS130E08_MVDD = 0x03,               /**< MVDD for supply measurement */
+    ADS130E08_TEMPERATURE_SENSOR = 0x04, /**< Temperature sensor */
+    ADS130E08_TEST_SIGNAL = 0x05         /**< Test signal */
+} ads130e08_mux_t;
+
+typedef enum { ADS130E08_FAULT_STATP = 0x12, ADS130E08_FAULT_STATN = 0x13 } ads130e08_fault_t;
+
+/**
+ * Fault status
+ */
+typedef enum {
+    ADS130E08_NO_FAULT_PRESENT = 0x00, /**< No fault present (default) */
+    ADS130E08_FAULT_PRESENT = 0x01     /**< Fault present */
+} ads130e08_fault_status_t;
+
+/**
+ * GPIO mode
+ */
+typedef enum {
+    ADS130E08_GPIO_OUTPUT = 0x00, /**< Output */
+    ADS130E08_GPIO_INPUT = 0x01   /**< Input (default) */
+} ads130e08_gpio_mode_t;
+
+/**
+ * GPIO level
+ */
+typedef enum { ADS130E08_GPIO_RESET = 0x00, ADS130E08_GPIO_SET = 0x01 } ads130e08_gpio_level_t;
+
+/**
+ * GPIOs
+ */
+typedef enum {
+    ADS130E08_GPIO1 = 0x01,
+    ADS130E08_GPIO2 = 0x02,
+    ADS130E08_GPIO3 = 0x03,
+    ADS130E08_GPIO4 = 0x04
+} ads130e08_gpio_pin_t;
+
+/**
+ * Number of devices
+ */
+typedef enum { ADS130E08_DEVICES_1 = 0x01, ADS130E08_DEVICES_2 = 0x02 } ads130e08_devices_n_t;
+
+/**
+ * Device descriptor
+ */
+typedef struct
+{
+    spi_device_interface_config_t spi_cfg; /**< SPI device configuration */
+    spi_device_handle_t spi_dev;           /**< SPI device handler */
+} ads130e08_t;
+
+/**
+ * Device configuration
+ */
+typedef struct
+{
+    ads130e08_clk_en_t clk_en;
+    ads130e08_int_test_t int_test;
+    ads130e08_test_amp_t test_amp;
+    ads130e08_test_freq_t test_freq;
+    ads130e08_pd_refbuf_t pd_refbuf;
+    ads130e08_vref_4v_t vref_4v;
+    ads130e08_opamp_ref_t opamp_ref;
+    ads130e08_pd_opamp_t pd_opamp;
+} ads130e08_dev_config_t;
+
+/**
+ * Channel configuration
+ */
+typedef struct
+{
+    ads130e08_pd_t enable;
+    ads130e08_pga_gain_t pga_gain;
+    ads130e08_mux_t mode;
+} ads130e08_channel_config_t;
+
+typedef struct
+{
+    uint8_t fault_statp;
+    uint8_t fault_statn;
+    uint8_t gpios_level;
+    int16_t channels_raw[8];
+} ads130e08_raw_data_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev    Device descriptor
+ * @param host   SPI host
+ * @param cs_pin CS GPIO number
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads130e08_init_desc(ads130e08_t *dev, spi_host_device_t host, gpio_num_t cs_pin);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads130e08_free_desc(ads130e08_t *dev);
+
+/**
+ * @brief Send system_command to device
+ *
+ * @param dev Device descriptor
+ * @param cmd Command
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads130e08_send_system_cmd(ads130e08_t *dev, ads130e08_system_cmd_t cmd);
+
+/**
+ * @brief Send data_read_command to device
+ *
+ * @param dev Device descriptor
+ * @param cmd Command
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads130e08_send_data_read_cmd(ads130e08_t *dev, ads130e08_data_read_cmd_t cmd);
+
+/**
+ * @brief Get device id
+ *
+ * @param dev Device descriptor
+ * @param id  Id
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads130e08_get_device_id(ads130e08_t *dev, uint8_t *id);
+
+/**
+ * @brief Set device configuration
+ *
+ * @param dev    Device descriptor
+ * @param config Device configurations
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads130e08_set_device_config(ads130e08_t *dev, ads130e08_dev_config_t config);
+
+/**
+ * @brief Get device configuration
+ *
+ * @param dev    Device descriptor
+ * @param config Device configurations
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads130e08_get_device_config(ads130e08_t *dev, ads130e08_dev_config_t *config);
+
+/**
+ * @brief Reads raw data in "Read data by command" mode
+ *
+ * @param dev           Device descriptor
+ * @param[out] raw_data Raw data
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads130e08_get_rdata(ads130e08_t *dev, ads130e08_raw_data_t *raw_data);
+
+/**
+ * @brief Converts raw adc value to voltage
+ *
+ * @param raw        Raw adc value
+ * @param gain       Channel gain
+ * @param[out] volts Voltage value
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads130e08_convert_raw_to_voltage(int16_t raw, uint8_t gain, float *volts);
+
+/**
+ * @brief Set fault detect control
+ *
+ * @param dev        Pointer to device descriptor
+ * @param fault_mode Fault mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads130e08_set_fault_detect_control(ads130e08_t *dev, ads130e08_fault_threshold_t fault_mode);
+
+/**
+ * @brief Get fault detect control
+ *
+ * @param dev             Pointer to device descriptor
+ * @param[out] fault_mode Fault mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads130e08_get_fault_detect_control(ads130e08_t *dev, ads130e08_fault_threshold_t *fault_mode);
+
+/**
+ * @brief Set channel configuration
+ *
+ * @param dev     Pointer to device descriptor
+ * @param channel Channel
+ * @param config  Channel configuration
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads130e08_set_channel_config(ads130e08_t *dev, ads130e08_channel_t channel,
+    ads130e08_channel_config_t config);
+
+/**
+ * @brief Get channel configuration
+ *
+ * @param dev         Pointer to device descriptor
+ * @param channel     Channel
+ * @param[out]config  Channel configuration
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads130e08_get_channel_config(ads130e08_t *dev, ads130e08_channel_t channel,
+    ads130e08_channel_config_t *config);
+
+/**
+ * @brief Set GPIO pin mode
+ *
+ * @param dev       Pointer to device descriptor
+ * @param gpio_pin  GPIO pin number
+ * @param gpio_mode GPIO pin mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads130e08_set_gpio_pin_mode(ads130e08_t *dev, ads130e08_gpio_pin_t gpio_pin, ads130e08_gpio_mode_t gpio_mode);
+
+/**
+ * @brief Get GPIO pin mode
+ *
+ * @param dev            Pointer to device descriptor
+ * @param gpio_pin       GPIO pin number
+ * @param[out] gpio_mode GPIO pin mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads130e08_get_gpio_pin_mode(ads130e08_t *dev, ads130e08_gpio_pin_t gpio_pin,
+    ads130e08_gpio_mode_t *gpio_mode);
+
+/**
+ * @brief Set GPIO pin level
+ *
+ * @param dev        Pointer to device descriptor
+ * @param gpio_pin   GPIO pin number
+ * @param gpio_level GPIO pin level
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads130e08_set_gpio_pin_level(ads130e08_t *dev, ads130e08_gpio_pin_t gpio_pin,
+    ads130e08_gpio_level_t gpio_level);
+
+/**
+ * @brief Get GPIO pin level
+ *
+ * @param dev             Pointer to device descriptor
+ * @param gpio_pin        GPIO pin number
+ * @param[out] gpio_level GPIO pin level
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads130e08_get_gpio_pin_level(ads130e08_t *dev, ads130e08_gpio_pin_t gpio_pin,
+    ads130e08_gpio_level_t *gpio_level);
+
+/**
+ * @brief Run automatical fault detection cycle
+ *
+ * @param dev Device descriptor
+ * @param [out] fault_statp See datasheet
+ * @param [out] fault_statn See datasheet
+ * @return `ESP_OK` on success
+ */
+esp_err_t ads130e08_detect_fault_auto(ads130e08_t *dev, uint8_t *fault_statp, uint8_t *fault_statn);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __ADS130E08_H__ */
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ads130e08/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = driver log
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/aht/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,25 @@
+---
+components:
+  - name: aht
+    description: |
+      Driver for AHT10/AHT15/AHT20 temperature and humidity sensor
+    group: temperature
+    groups:
+      - humidity
+    code_owners:
+      - name: UncleRus
+    depends:
+      - name: i2cdev
+      - name: log
+      - name: esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - name: UncleRus
+        year: 2021
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/aht/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS aht.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/aht/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+Copyright 2021 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/aht/aht.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file aht.c
+ *
+ * ESP-IDF driver for humidty/temperature sensors AHT10/AHT15/AHT20
+ *
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#include "aht.h"
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <esp_log.h>
+
+#define I2C_FREQ_HZ 400000 // 400kHz
+
+static const char *TAG = "aht";
+
+#define CMD_CALIBRATE_1X      (0xe1)
+#define CMD_CALIBRATE_20      (0xbe)
+#define CMD_RESET             (0xba)
+#define CMD_MODE_NORMAL       (0xa8)
+#define CMD_START_MEASUREMENT (0xac)
+
+#define ARG_MODE_NORMAL    (0x00)
+#define ARG_MODE_CYCLE     (0x20)
+#define ARG_MODE_CALIBRATE (0x08)
+#define ARG_MEAS_DATA      (0x33)
+
+#define BIT_STATUS_BUSY BIT(7)
+#define BIT_STATUS_CAL  BIT(3)
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+static esp_err_t send_cmd_nolock(aht_t *dev, uint8_t cmd0, uint8_t cmd1, uint8_t cmd2, uint32_t delay_ms)
+{
+    uint8_t buf[3] = { cmd0, cmd1, cmd2 };
+    CHECK(i2c_dev_write(&dev->i2c_dev, NULL, 0, &buf, 3));
+    vTaskDelay(pdMS_TO_TICKS(delay_ms));
+
+    return ESP_OK;
+}
+
+static esp_err_t setup_nolock(aht_t *dev)
+{
+    return send_cmd_nolock(dev, dev->type == AHT_TYPE_AHT1x ? CMD_CALIBRATE_1X : CMD_CALIBRATE_20,
+            (dev->mode == AHT_MODE_NORMAL ? ARG_MODE_NORMAL : ARG_MODE_CYCLE) | ARG_MODE_CALIBRATE, 0, 350);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t aht_init_desc(aht_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+
+    if (addr != AHT_I2C_ADDRESS_GND || addr > AHT_I2C_ADDRESS_VCC)
+    {
+        ESP_LOGE(TAG, "Invalid I2C address");
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    dev->i2c_dev.port = port;
+    dev->i2c_dev.addr = addr;
+    dev->i2c_dev.cfg.sda_io_num = sda_gpio;
+    dev->i2c_dev.cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(&dev->i2c_dev);
+}
+
+esp_err_t aht_free_desc(aht_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(&dev->i2c_dev);
+}
+
+esp_err_t aht_init(aht_t *dev)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, setup_nolock(dev));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t aht_reset(aht_t *dev)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    uint8_t cmd = CMD_RESET;
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_write(&dev->i2c_dev, NULL, 0, &cmd, 1));
+    vTaskDelay(pdMS_TO_TICKS(20));
+    I2C_DEV_CHECK(&dev->i2c_dev, setup_nolock(dev));
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t aht_get_status(aht_t *dev, bool *busy, bool *calibrated)
+{
+    CHECK_ARG(dev && (busy || calibrated));
+
+    uint8_t status;
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read(&dev->i2c_dev, NULL, 0, &status, 1));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+    if (busy)
+        *busy = (status & BIT_STATUS_BUSY) != 0;
+    if (calibrated)
+        *calibrated = (status & BIT_STATUS_CAL) != 0;
+
+    return ESP_OK;
+}
+
+esp_err_t aht_get_data(aht_t *dev, float *temperature, float *humidity)
+{
+    CHECK_ARG(dev && (temperature || humidity));
+
+    uint8_t buf[6];
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, send_cmd_nolock(dev, CMD_START_MEASUREMENT, ARG_MEAS_DATA, 0, 80));
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read(&dev->i2c_dev, NULL, 0, buf, 6));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    if (humidity)
+    {
+        uint32_t raw = ((uint32_t)buf[1] << 12) | ((uint32_t)buf[2] << 4) | (buf[3] >> 4);
+        *humidity = (float)raw * 100 / 0x100000;
+    }
+
+    if (temperature)
+    {
+        uint32_t raw = ((uint32_t)(buf[3] & 0x0f) << 16) | ((uint32_t)buf[4] << 8) | buf[5];
+        *temperature = (float)raw * 200 / 0x100000 - 50;
+    }
+
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/aht/aht.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file aht.h
+ * @defgroup aht aht
+ * @{
+ *
+ * ESP-IDF driver for humidty/temperature sensors AHT10/AHT15/AHT20
+ *
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __AHT_H__
+#define __AHT_H__
+
+#include <i2cdev.h>
+#include <stdbool.h>
+
+#define AHT_I2C_ADDRESS_GND 0x38 //!< Device address when ADDR pin connected to GND
+#define AHT_I2C_ADDRESS_VCC 0x39 //!< Device address when ADDR pin connected to VCC
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Device types
+ */
+typedef enum {
+    AHT_TYPE_AHT1x = 0, //!< AHT10, AHT15
+    AHT_TYPE_AHT20,     //!< AHT20
+} aht_type_t;
+
+/**
+ * Device modes
+ */
+typedef enum {
+    AHT_MODE_NORMAL = 0, //!< Normal mode
+    AHT_MODE_CYCLE,      //!< Continuous measurements mode, undocumented
+} aht_mode_t;
+
+/**
+ * Device descriptor
+ */
+typedef struct
+{
+    i2c_dev_t i2c_dev;
+    aht_type_t type;
+    aht_mode_t mode;
+} aht_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev      Device descriptor
+ * @param addr     Device I2C address
+ * @param port     I2C port
+ * @param sda_gpio SDA GPIO
+ * @param scl_gpio SCL GPIO
+ * @return `ESP_OK` on success
+ */
+esp_err_t aht_init_desc(aht_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t aht_free_desc(aht_t *dev);
+
+/**
+ * @brief Init device
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t aht_init(aht_t *dev);
+
+/**
+ * @brief Soft reset device
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t aht_reset(aht_t *dev);
+
+/**
+ * @brief Get device status
+ *
+ * @param dev Device descriptor
+ * @param[out] busy       Busy flag
+ *                        - true: device currently measuring
+ *                        - false: device in indle mode
+ * @param[out] calibrated Calibration success flag
+ *                        - true: sensor calibrated
+ *                        - false: sensor not calibrated
+ * @return `ESP_OK` on success
+ */
+esp_err_t aht_get_status(aht_t *dev, bool *busy, bool *calibrated);
+
+/**
+ * @brief Get temperature and relative humidity
+ *
+ * @param dev Device descriptor
+ * @param[out] temperature Temperature, degrees Celsius
+ * @param[out] humidity    Relative humidity, percents
+ * @return `ESP_OK` on success
+ */
+esp_err_t aht_get_data(aht_t *dev, float *temperature, float *humidity);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __AHT_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/aht/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/bh1750/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+---
+components:
+  - name: bh1750
+    description: Driver for BH1750 light sensor
+    group:
+      name: light
+    groups: []
+    code_owners:
+      - name: UncleRus
+    depends:
+      - name: i2cdev
+      - name: log
+      - name: esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - name: UncleRus
+        year: 2018
+      - name: Andrej
+        year: 2017
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/bh1750/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS bh1750.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/bh1750/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,27 @@
+Copyright (c) 2017 Andrej Krutak <dev@andree.sk>
+Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/bh1750/bh1750.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2017 Andrej Krutak <dev@andree.sk>
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file bh1750.c
+ *
+ * @ingroup bh1750 ESP-IDF driver for BH1750 light sensor
+ *
+ * ESP-IDF driver for BH1750 light sensor
+ *
+ * Datasheet: ROHM Semiconductor bh1750fvi-e.pdf
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2017 Andrej Krutak <dev@andree.sk>\n
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#include <stdio.h>
+#include <freertos/FreeRTOS.h>
+#include <esp_log.h>
+#include <esp_idf_lib_helpers.h>
+#include "bh1750.h"
+
+
+#define OPCODE_HIGH  0x0
+#define OPCODE_HIGH2 0x1
+#define OPCODE_LOW   0x3
+
+#define OPCODE_CONT 0x10
+#define OPCODE_OT   0x20
+
+#define OPCODE_POWER_DOWN 0x00
+#define OPCODE_POWER_ON   0x01
+#define OPCODE_MT_HI      0x40
+#define OPCODE_MT_LO      0x60
+
+#define I2C_FREQ_HZ 400000
+
+static const char *TAG = "bh1750";
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+inline static esp_err_t send_command_nolock(i2c_dev_t *dev, uint8_t cmd)
+{
+    return i2c_dev_write(dev, NULL, 0, &cmd, 1);
+}
+
+static esp_err_t send_command(i2c_dev_t *dev, uint8_t cmd)
+{
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, send_command_nolock(dev, cmd));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t bh1750_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+
+    if (addr != BH1750_ADDR_LO && addr != BH1750_ADDR_HI)
+    {
+        ESP_LOGE(TAG, "Invalid I2C address");
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    dev->port = port;
+    dev->addr = addr;
+    dev->cfg.sda_io_num = sda_gpio;
+    dev->cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+    return i2c_dev_create_mutex(dev);
+}
+
+esp_err_t bh1750_free_desc(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(dev);
+}
+
+esp_err_t bh1750_power_down(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return send_command(dev, OPCODE_POWER_DOWN);
+}
+
+esp_err_t bh1750_power_on(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return send_command(dev, OPCODE_POWER_ON);
+}
+
+esp_err_t bh1750_setup(i2c_dev_t *dev, bh1750_mode_t mode, bh1750_resolution_t resolution)
+{
+    CHECK_ARG(dev);
+
+    uint8_t opcode = mode == BH1750_MODE_CONTINUOUS ? OPCODE_CONT : OPCODE_OT;
+    switch (resolution)
+    {
+        case BH1750_RES_LOW:  opcode |= OPCODE_LOW;   break;
+        case BH1750_RES_HIGH: opcode |= OPCODE_HIGH;  break;
+        default:              opcode |= OPCODE_HIGH2; break;
+    }
+
+    CHECK(send_command(dev, opcode));
+
+    ESP_LOGD(TAG, "bh1750_setup(PORT = %d, ADDR = 0x%02x, VAL = 0x%02x)", dev->port, dev->addr, opcode);
+
+    return ESP_OK;
+}
+
+esp_err_t bh1750_set_measurement_time(i2c_dev_t *dev, uint8_t time)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, send_command_nolock(dev, OPCODE_MT_HI | (time >> 5)));
+    I2C_DEV_CHECK(dev, send_command_nolock(dev, OPCODE_MT_LO | (time & 0x1f)));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t bh1750_read(i2c_dev_t *dev, uint16_t *level)
+{
+    CHECK_ARG(dev && level);
+
+    uint8_t buf[2];
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read(dev, NULL, 0, buf, 2));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    *level = buf[0] << 8 | buf[1];
+    *level = (*level * 10) / 12; // convert to LUX
+
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/bh1750/bh1750.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2017 Andrej Krutak <dev@andree.sk>
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file bh1750.h
+ *
+ * @defgroup bh1750 bh1750
+ * @{
+ *
+ * ESP-IDF driver for BH1750 light sensor
+ *
+ * Datasheet: ROHM Semiconductor bh1750fvi-e.pdf
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2017 Andrej Krutak <dev@andree.sk>\n
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __BH1750_H__
+#define __BH1750_H__
+
+#include <stdint.h>
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define BH1750_ADDR_LO 0x23 //!< I2C address when ADDR pin floating/low
+#define BH1750_ADDR_HI 0x5c //!< I2C address when ADDR pin high
+
+/**
+ * Measurement mode
+ */
+typedef enum
+{
+    BH1750_MODE_ONE_TIME = 0, //!< One time measurement
+    BH1750_MODE_CONTINUOUS    //!< Continuous measurement
+} bh1750_mode_t;
+
+/**
+ * Measurement resolution
+ */
+typedef enum
+{
+    BH1750_RES_LOW = 0,  //!< 4 lx resolution, measurement time is usually 16 ms
+    BH1750_RES_HIGH,     //!< 1 lx resolution, measurement time is usually 120 ms
+    BH1750_RES_HIGH2     //!< 0.5 lx resolution, measurement time is usually 120 ms
+} bh1750_resolution_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param[out] dev Device descriptor
+ * @param[in] addr I2C address, ::BH1750_ADDR_LO or ::BH1750_ADDR_HI
+ * @param[in] port I2C port number
+ * @param[in] sda_gpio GPIO pin number for SDA
+ * @param[in] scl_gpio GPIO pin number for SCL
+ * @return `ESP_OK` on success
+ */
+esp_err_t bh1750_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Pointer to device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t bh1750_free_desc(i2c_dev_t *dev);
+
+/**
+ * @brief Power down device
+ *
+ * @param dev Pointer to device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t bh1750_power_down(i2c_dev_t *dev);
+
+/**
+ * @brief Power on device
+ *
+ * @param dev Pointer to device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t bh1750_power_on(i2c_dev_t *dev);
+
+/**
+ * @brief Setup device parameters
+ *
+ * @param dev Pointer to device descriptor
+ * @param mode Measurement mode
+ * @param resolution Measurement resolution
+ * @return `ESP_OK` on success
+ */
+esp_err_t bh1750_setup(i2c_dev_t *dev, bh1750_mode_t mode, bh1750_resolution_t resolution);
+
+/**
+ * @brief Set measurement time
+ *
+ * @param dev Pointer to device descriptor
+ * @param time Measurement time (see datasheet)
+ * @return `ESP_OK` on success
+ */
+esp_err_t bh1750_set_measurement_time(i2c_dev_t *dev, uint8_t time);
+
+/**
+ * @brief Read LUX value from the device.
+ *
+ * @param dev Pointer to device descriptor
+ * @param[out] level read value in lux units
+ * @return `ESP_OK` on success
+ */
+esp_err_t bh1750_read(i2c_dev_t *dev, uint16_t *level);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __BH1750_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/bh1750/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/bh1900nux/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,23 @@
+---
+components:
+  - name: bh1900nux
+    description: Driver for BH1900NUX temperature sensor
+    group: temperature
+    groups: []
+    code_owners: UncleRus
+    depends:
+      - i2cdev
+      - log
+      - esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2021
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/bh1900nux/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS bh1900nux.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/bh1900nux/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+Copyright (c) 2021 Ruslan V. Uss (https://github.com/UncleRus)
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/bh1900nux/bh1900nux.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,255 @@
+/*
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file bh1900nux.c
+ *
+ * ESP-IDF driver for BH1900NUX temperature sensor
+ *
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+
+#include <esp_log.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <esp_idf_lib_helpers.h>
+#include "bh1900nux.h"
+
+static const char *TAG = "bh1900nux";
+
+static const float t_lsb = 0.0625;
+
+#define BH1900NUX_I2C_ADDR_MAX 0x4f
+
+#define I2C_FREQ_HZ 400000 // 400kHz
+
+#define REG_TEMP  0
+#define REG_CONF  1
+#define REG_TLOW  2
+#define REG_THIGH 3
+#define REG_RST   4
+
+#define BIT_SHUTDOWN  0
+#define BIT_ALERT_POL 2
+#define BIT_FAULT_DQ0 3
+#define BIT_ALERT     6
+#define BIT_ONE_SHOT  7
+#define BIT_WT0       8
+
+#define CONV_TIME_MS 35
+#define RESET_TIME_MS 250
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+#define BV(x) (1 << (x))
+
+inline static uint16_t shuffle(uint16_t v)
+{
+    return (v << 8) | (v >> 8);
+}
+
+static esp_err_t read_reg(bh1900nux_t *dev, uint8_t reg, uint16_t *val)
+{
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, reg, val, 2));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    *val = shuffle(*val);
+
+    return ESP_OK;
+}
+
+static esp_err_t write_reg(bh1900nux_t *dev, uint8_t reg, uint16_t val)
+{
+    uint16_t v = shuffle(val);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_write_reg(&dev->i2c_dev, reg, &v, 2));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+static esp_err_t read_temp(bh1900nux_t *dev, uint8_t reg, float *temp)
+{
+    CHECK_ARG(dev && temp);
+
+    int16_t buf;
+
+    CHECK(read_reg(dev, reg, (uint16_t *)&buf));
+
+    *temp = ((int16_t)buf >> 4) * t_lsb;
+
+    return ESP_OK;
+}
+
+static esp_err_t write_temp(bh1900nux_t *dev, uint8_t reg, float temp)
+{
+    CHECK_ARG(dev);
+
+    return write_reg(dev, reg, (int16_t)(temp / t_lsb) << 4);
+}
+
+static esp_err_t update_reg(bh1900nux_t *dev, uint8_t reg, uint16_t val, uint16_t mask)
+{
+    CHECK_ARG(dev);
+
+    uint16_t tmp;
+    CHECK(read_reg(dev, reg, &tmp));
+    CHECK(write_reg(dev, reg, (tmp & ~mask) | (val & ~mask)));
+
+    return ESP_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t bh1900nux_init_desc(bh1900nux_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+    if (addr < BH1900NUX_I2C_ADDR_BASE || addr > BH1900NUX_I2C_ADDR_MAX)
+    {
+        ESP_LOGE(TAG, "Invalid device address: 0x%02x", addr);
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    dev->i2c_dev.port = port;
+    dev->i2c_dev.addr = addr;
+    dev->i2c_dev.cfg.sda_io_num = sda_gpio;
+    dev->i2c_dev.cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(&dev->i2c_dev);
+}
+
+esp_err_t bh1900nux_free_desc(bh1900nux_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(&dev->i2c_dev);
+}
+
+esp_err_t bh1900nux_reset(bh1900nux_t *dev)
+{
+    CHECK_ARG(dev);
+
+    CHECK(write_reg(dev, REG_RST, 1));
+    vTaskDelay(pdMS_TO_TICKS(RESET_TIME_MS));
+    return bh1900nux_set_mode(dev, dev->mode);
+}
+
+esp_err_t bh1900nux_set_mode(bh1900nux_t *dev, bh1900nux_mode_t mode)
+{
+    CHECK_ARG(dev);
+
+    dev->mode = mode;
+
+    uint16_t val, mask;
+    if (mode == BH1900NUX_MODE_CONTINUOUS)
+    {
+        val = BV(BIT_ONE_SHOT);
+        mask = BV(BIT_ONE_SHOT) | BV(BIT_SHUTDOWN);
+    }
+    else
+    {
+        val = BV(BIT_SHUTDOWN);
+        mask = BV(BIT_ONE_SHOT) | BV(BIT_SHUTDOWN);
+    }
+
+    return update_reg(dev, REG_CONF, val, mask);
+}
+
+esp_err_t bh1900nux_get_config(bh1900nux_t *dev, bh1900nux_wait_time_t *wt, bh1900nux_fault_queue_t *fq, bh1900nux_alert_polarity_t *ap)
+{
+    CHECK_ARG(dev && wt && fq && ap);
+
+    uint16_t val;
+    CHECK(read_reg(dev, REG_CONF, &val));
+
+    *wt = (val >> BIT_WT0) & 3;
+    *fq = (val >> BIT_FAULT_DQ0) & 3;
+    *ap = (val >> BIT_ALERT_POL) & 1;
+
+    return ESP_OK;
+}
+
+esp_err_t bh1900nux_set_config(bh1900nux_t *dev, bh1900nux_wait_time_t wt, bh1900nux_fault_queue_t fq, bh1900nux_alert_polarity_t ap)
+{
+    CHECK_ARG(dev && fq <= BH1900NUX_FAULTS_6 && wt <= BH1900NUX_WT_3);
+
+    uint16_t val = (wt << BIT_WT0) | (fq << BIT_FAULT_DQ0) | (ap != BH1900NUX_ALERT_LOW ? BV(BIT_ALERT_POL) : 0);
+
+    CHECK(write_reg(dev, REG_CONF, val));
+    CHECK(bh1900nux_set_mode(dev, dev->mode));
+
+    return ESP_OK;
+}
+
+esp_err_t bh1900nux_one_shot(bh1900nux_t *dev, float *temp)
+{
+    CHECK_ARG(dev);
+
+    if (dev->mode != BH1900NUX_MODE_SHUTDOWN)
+    {
+        ESP_LOGE(TAG, "Cannot perform one-shot measurement, device in invalid mode");
+        return ESP_ERR_INVALID_STATE;
+    }
+
+    CHECK(update_reg(dev, REG_CONF, BV(BIT_ONE_SHOT) | BV(BIT_SHUTDOWN), BV(BIT_ONE_SHOT) | BV(BIT_SHUTDOWN)));
+
+    vTaskDelay(pdMS_TO_TICKS(CONV_TIME_MS));
+
+    return read_temp(dev, REG_TEMP, temp);
+}
+
+esp_err_t bh1900nux_get_temperature(bh1900nux_t *dev, float *temp)
+{
+    return read_temp(dev, REG_TEMP, temp);
+}
+
+esp_err_t bh1900nux_get_t_low(bh1900nux_t *dev, float *temp)
+{
+    return read_temp(dev, REG_TLOW, temp);
+}
+
+esp_err_t bh1900nux_set_t_low(bh1900nux_t *dev, float temp)
+{
+    return write_temp(dev, REG_TLOW, temp);
+}
+
+esp_err_t bh1900nux_get_t_high(bh1900nux_t *dev, float *temp)
+{
+    return read_temp(dev, REG_THIGH, temp);
+}
+
+esp_err_t bh1900nux_set_t_high(bh1900nux_t *dev, float temp)
+{
+    return write_temp(dev, REG_THIGH, temp);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/bh1900nux/bh1900nux.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,217 @@
+/*
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file bh1900nux.h
+ * @defgroup bh1900nux bh1900nux
+ * @{
+ *
+ * ESP-IDF driver for BH1900NUX temperature sensor
+ *
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __BH1900NUX_H__
+#define __BH1900NUX_H__
+
+#include <stdbool.h>
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#define BH1900NUX_I2C_ADDR_BASE 0x48 //!< See full list in datasheet
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Fault queue size
+ */
+typedef enum {
+    BH1900NUX_FAULTS_1 = 0, //!< 1 fault to trigger ALERT pin
+    BH1900NUX_FAULTS_2,     //!< 2 faults to trigger ALERT pin
+    BH1900NUX_FAULTS_4,     //!< 4 faults to trigger ALERT pin
+    BH1900NUX_FAULTS_6      //!< 6 faults to trigger ALERT pin
+} bh1900nux_fault_queue_t;
+
+/**
+ * ALERT pin polarity
+ */
+typedef enum {
+    BH1900NUX_ALERT_LOW = 0, //!< ALERT active low (default)
+    BH1900NUX_ALERT_HIGH     //!< ALERT active high
+} bh1900nux_alert_polarity_t;
+
+/**
+ * Device operating mode
+ */
+typedef enum {
+    BH1900NUX_MODE_CONTINUOUS = 0, //!< Continuous measurement mode (default)
+    BH1900NUX_MODE_SHUTDOWN        //!< Shutdown mode
+} bh1900nux_mode_t;
+
+/**
+ * Delay between measurements in continuous mode
+ */
+typedef enum {
+    BH1900NUX_WT_0 = 0, //!< 186240 * 16 / 450000 ~ 6.622s (Fosc = 450kHz)
+    BH1900NUX_WT_1,     //!< 186240 * 4 / 450000 ~ 1.655s (Fosc = 450kHz)
+    BH1900NUX_WT_2,     //!< 186240 / 450000 ~ 0.414s (Fosc = 450kHz)
+    BH1900NUX_WT_3,     //!< 93120 / 450000 ~ 0.207s (Fosc = 450kHz)
+} bh1900nux_wait_time_t;
+
+/**
+ * Device descriptor
+ */
+typedef struct {
+    i2c_dev_t i2c_dev;
+    bh1900nux_mode_t mode;
+} bh1900nux_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev Device descriptor
+ * @param port I2C port number
+ * @param addr I2C address
+ * @param sda_gpio SDA GPIO
+ * @param scl_gpio SCL GPIO
+ * @return `ESP_OK` on success
+ */
+esp_err_t bh1900nux_init_desc(bh1900nux_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t bh1900nux_free_desc(bh1900nux_t *dev);
+
+/**
+ * @brief Software reset
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t bh1900nux_reset(bh1900nux_t *dev);
+
+/**
+ * @brief Set device operating mode
+ *
+ * @param dev Device descriptor
+ * @param mode Operating mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t bh1900nux_set_mode(bh1900nux_t *dev, bh1900nux_mode_t mode);
+
+/**
+ * @brief Read current device config
+ *
+ * @param dev Device descriptor
+ * @param[out] wt Delay between measurements
+ * @param[out] fq Fault queue size
+ * @param[out] ap ALERT pin polarity
+ * @return `ESP_OK` on success
+ */
+esp_err_t bh1900nux_get_config(bh1900nux_t *dev, bh1900nux_wait_time_t *wt, bh1900nux_fault_queue_t *fq, bh1900nux_alert_polarity_t *ap);
+
+/**
+ * @brief Configure device
+ *
+ * @param dev Device descriptor
+ * @param wt Delay between measurements
+ * @param fq Fault queue size
+ * @param ap ALERT pin polarity
+ * @return `ESP_OK` on success
+ */
+esp_err_t bh1900nux_set_config(bh1900nux_t *dev, bh1900nux_wait_time_t wt, bh1900nux_fault_queue_t fq, bh1900nux_alert_polarity_t ap);
+
+/**
+ * @brief Made a single-shot measurement
+ *
+ * Works only when device is in shutdown mode.
+ *
+ * @param dev Device descriptor
+ * @param[out] temp Temperature, deg.C
+ * @return `ESP_OK` on success
+ */
+esp_err_t bh1900nux_one_shot(bh1900nux_t *dev, float *temp);
+
+/**
+ * @brief Read temperature register
+ *
+ * @param dev Device descriptor
+ * @param[out] temp Temperature, deg.C
+ * @return `ESP_OK` on success
+ */
+esp_err_t bh1900nux_get_temperature(bh1900nux_t *dev, float *temp);
+
+/**
+ * @brief Read lower temperature limit register
+ *
+ * @param dev Device descriptor
+ * @param[out] temp Temperature, deg.C
+ * @return `ESP_OK` on success
+ */
+esp_err_t bh1900nux_get_t_low(bh1900nux_t *dev, float *temp);
+
+/**
+ * @brief Write lower temperature limit register
+ *
+ * @param dev Device descriptor
+ * @param temp Temperature, deg.C
+ * @return `ESP_OK` on success
+ */
+esp_err_t bh1900nux_set_t_low(bh1900nux_t *dev, float temp);
+
+/**
+ * @brief Read higher temperature limit register
+ *
+ * @param dev Device descriptor
+ * @param[out] temp Temperature, deg.C
+ * @return `ESP_OK` on success
+ */
+esp_err_t bh1900nux_get_t_high(bh1900nux_t *dev, float *temp);
+
+/**
+ * @brief Write higher temperature limit register
+ *
+ * @param dev Device descriptor
+ * @param temp Temperature, deg.C
+ * @return `ESP_OK` on success
+ */
+esp_err_t bh1900nux_set_t_high(bh1900nux_t *dev, float temp);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __BH1900NUX_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/bh1900nux/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/bme680/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,29 @@
+---
+components:
+  - name: bme680
+    description: |
+      Driver for BME680 digital environmental sensor
+    group:
+      name: pressure
+    groups:
+      - name: humidity
+      - name: temperature
+    code_owners:
+      - name: UncleRus
+    depends:
+      - name: i2cdev
+      - name: log
+      - name: esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - name: UncleRus
+        year: 2019
+      - name: gschorcht
+        year: 2017
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/bme680/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS bme680.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/bme680/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,27 @@
+Copyright (c) 2017 Gunar Schorcht (https://github.com/gschorcht)
+Copyright (c) 2019 Ruslan V. Uss (https://github.com/UncleRus)
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/bme680/README.md	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,342 @@
+# Driver for **BME680** digital **environmental sensor**
+
+Forked from [original driver by Gunar Schorcht](https://github.com/gschorcht/bme680-esp-idf).
+
+The driver supports multiple BME680 sensors which are connected to the same or
+different I2C interfaces with different addresses.  *SPI interface is not
+supported yet*.
+
+It is for the usage with the ESP8266 and [ESP8266 RTOS SDK](https://github.com/espressif/ESP8266_RTOS_SDK)
+or ESP32 and [ESP-IDF](https://github.com/espressif/esp-idf.git).
+
+## About the sensor
+
+BME680 is an ultra-low-power environmental sensor that integrates temperature,
+pressure, humidity and gas sensors in only one unit.
+
+## Communication interface
+
+The BME680 sensor can be connected using I2C.
+
+The I2C interface supports data rates up to 1 Mbps (ESP-IDF limitation). It is
+possible to connect multiple BME680 sensors with different I2C slave addresses
+to the same I2C bus or to different I2C buses. Possible I2C slave addresses
+are 0x76 and 0x77.
+
+## Measurement process
+
+Once the BME680 has been initialized, it can be used for measurements. The
+BME680 operates in two different modes, the **sleep mode** and the
+**forced mode**.
+
+The sensor starts after power-up automatically in the *sleep mode* where it
+does not perform any measurement and consumes only 0.15 μA. Measurements are
+only done in *forced mode*.
+
+**Please note:** There are two further undocumented modes, the *parallel* and
+the *sequential* mode. They can't be supported by the driver, since it is
+not clear what they do and how to use them.
+
+### Measurement cylce
+
+To perform measurements, the BME680 sensor has to be triggered to switch to
+the **forced mode**. In this mode, it performs exactly one measurement of
+temperature, pressure, humidity, and gas in that order, the so-called
+**TPHG measurement cycle**. After the execution of this TPHG measurement cycle,
+**raw sensor data** become available and the sensor returns automatically back
+to sleep mode.
+
+Each of the individual measurements can be configured or skipped separately
+via the sensor settings, see section **Measurement settings**. Dependent on
+the configuration, the **duration of a TPHG measurement cycle** can vary from
+some milliseconds up to about 4.5 seconds, especially if gas measurement is
+enabled.
+
+To avoid the blocking of the user task during measurements, the measurement
+process is therefore separated into the following steps:
+
+1. Trigger the sensor with function `bme680_force_measurement()` to switch
+   to *forced mode* in which it performs exactly one THPG measurement cycle.
+2. Wait the measurement duration using function `vTaskDelay()` and the value
+   returned from function `bme680_get_measurement_duration()` or wait as long
+   as function `bme680_is_measuring` returns true.
+3. Fetch the results as fixed point values with function
+   `bme680_get_results_fixed()` or as floating point values with function
+   `bme680_get_results_float()`.
+
+```C
+...
+// as long as sensor configuration isn't changed, the duration can be considered as constant
+uint32_t duration
+bme680_get_measurement_duration(sensor, &duration);
+...
+if (bme680_force_measurement(sensor) == ESP_OK) // STEP 1
+{
+    // STEP 2: passive waiting until measurement results are available
+    vTaskDelay(duration);
+
+    // STEP 3: get the results and do something with them
+    if (bme680_get_results_float(sensor, &values) == ESP_OK)
+        ...
+}
+...
+```
+
+Alternatively, busy waiting can be realized using function `bme680_is_measuring()`.
+
+```C
+...
+if (bme680_force_measurement(sensor) == ESP_OK) // STEP 1
+{
+    // STEP 2: busy waiting until measurement results are available
+    bool busy;
+    do
+    {
+        ESP_ERROR_CHECK(bme680_is_measuring(sensor, &busy));
+    }
+    while(busy);
+
+    // STEP 3: get the results and do something with them
+    if (bme680_get_results_float(sensor, &values) == ESP_OK)
+        ...
+}
+...
+```
+
+For convenience, it is also possible to use the high-level functions
+`bme680_measure_float()` or `bme680_measure_fixed()`. These functions combine
+all 3 steps above within a single function and are therefore very easy to use.
+**Please note** that these functions must not be used when they are called
+from a software timer callback function since the calling task is delayed
+using function *vTaskDelay*.
+
+```C
+...
+// ONE STEP: measure, wait, get the results and do something with them
+if (bme680_measure_float(sensor, &values) == ESP_OK)
+    ...
+...
+```
+
+#### Measurement results
+
+Once the sensor has finished the measurement raw data are available at the sensor.
+Either function `bme680_get_results_fixed()` or function
+`bme680_get_results_float()` can be used to fetch the results. Both functions
+read raw data from the sensor and converts them into utilizable fixed point or
+floating point sensor values.
+
+**Please note:** Conversion of raw sensor data into the final sensor values is
+based on very complex calculations that use a large number of calibration
+parameters. Therefore, the driver does not provide functions that only return
+the raw sensor data.
+
+Dependent on sensor value representation, measurement results contain different
+dimensions:
+
+| Value          | Fixed Point   | Floating Point | Conversion
+| -------------- | ------------- | -------------- |---------------------
+| temperature    | 1/100 °C      | °C             | float = fixed / 100
+| pressure       | Pascal        | hPascal        | float = fixed / 100
+| humidity       | 1/1000 %      | %              | float = fixed / 1000
+| gas_resistance | Ohm           | Ohm            | float = fixed
+
+The gas resistance value in Ohm represents the resistance of sensor's gas sensitive
+layer.
+
+If the TPHG measurement cycle or fetching the results fails, invalid sensor values
+are returned:
+
+| Invalid Value  | Fixed Point | Floating Point
+| -------------- | ----------- | ---------------
+| temperature    | INT16_MIN   | -327.68
+| pressure       | 0           | 0.0
+| humidity       | 0           | 0.0
+| gas_resistance | 0           | 0.0
+
+## Measurement settings
+
+The sensor allows to change a lot of measurement parameters.
+
+### Oversampling rates
+
+To increase the resolution of raw sensor data, the sensor supports
+oversampling for temperature,  pressure, and humidity measurements. Using
+function `bme680_set_oversampling_rates()`, individual **oversampling rates**
+can be defined for these measurements. With an oversampling rate *osr*, the
+resolution of the according raw sensor data can be increased from 16 bit to
+16+ld(*osr*) bit.
+
+Possible oversampling rates are 1x (default by the driver) 2x, 4x, 8x and 16x.
+It is also possible to define an oversampling rate of 0. This **deactivates**
+the corresponding measurement and the output values become invalid.
+
+```C
+...
+// Changes the oversampling rate for temperature to 4x and for pressure to 2x. Humidity measurement is skipped.
+ESP_ERROR_CHECK(bme680_set_oversampling_rates(sensor, osr_4x, osr_2x, osr_none));
+...
+```
+
+### IIR Filter
+
+The sensor also integrates an internal IIR filter (low pass filter) to reduce
+short-term changes in sensor output values caused by external disturbances.
+It effectively reduces the bandwidth of the sensor output values.
+
+The filter can optionally be used for pressure and temperature data that are
+subject to many short-term changes. With the IIR filter the resolution of
+pressure and temperature data increases to 20 bit. Humidity and gas inside
+the sensor does not fluctuate rapidly and does not require such a low pass
+filtering.
+
+Using function `bme680_set_filter_size()`, the user task can change the
+**size of the filter**. The default size is 3. If the size of the filter
+becomes 0, the filter is **not used**.
+
+```C
+...
+// Change the IIR filter size for temperature and pressure to 7.
+bme680_set_filter_size(sensor, iir_size_7);
+...
+// Don't use IIR filter
+bme680_set_filter_size(sensor, iir_size_0);
+...
+```
+
+### Heater profile
+
+For the gas measurement, the sensor integrates a heater. Parameters for this
+heater are defined by **heater profiles**. The sensor supports up to 10 such
+heater profiles, which are numbered from 0 to 9. Each profile consists of a
+temperature set-point (the target temperature) and a heating duration. By
+default, only the heater profile 0 with 320 degree Celsius as target temperature
+and 150 ms heating duration is defined.
+
+**Please note:** According to the data sheet, target temperatures between 200
+and 400 degrees Celsius are typical and about 20 to 30 ms are necessary for
+the heater to reach the desired target temperature.
+
+Function `bme680_set_heater_profile()` can be used to set the parameters
+for one of the heater profiles 0 ... 9. Once the parameters of a heater profile
+are defined, the gas measurement can be activated with that heater profile
+using function `bme680_use_heater_profile()`. If -1 or `BME680_HEATER_NOT_USED`
+is used as heater profile, gas measurement is deactivated completely.
+
+```C
+...
+// Change the heater profile 1 to 300 degree Celsius for 100 ms and activate it
+bme680_set_heater_profile (sensor, 1, 300, 100);
+bme680_use_heater_profile (sensor, 1);
+...
+// Deactivate gas measurement completely
+bme680_use_heater_profile (sensor, BME680_HEATER_NOT_USED);
+...
+
+```
+
+If several heater profiles have been defined with function
+`bme680_set_heater_profile()`, a sequence of gas measurements with different
+heater parameters can be realized by a sequence of activations of different
+heater profiles for successive TPHG measurements using function
+`bme680_use_heater_profile()`.
+
+For example, if there were 5 heater profiles defined with following code
+during the setup
+
+```C
+bme680_set_heater_profile(sensor, 0, 200, 100);
+bme680_set_heater_profile(sensor, 1, 250, 120);
+bme680_set_heater_profile(sensor, 2, 300, 140);
+bme680_set_heater_profile(sensor, 3, 350, 160);
+bme680_set_heater_profile(sensor, 4, 400, 180);
+```
+
+the user task could use them as a sequence like following:
+
+```C
+...
+while (1)
+{
+    switch (count++ % 5)
+    {
+        case 0: bme680_use_heater_profile(sensor, 0); break;
+        case 1: bme680_use_heater_profile(sensor, 1); break;
+        case 2: bme680_use_heater_profile(sensor, 2); break;
+        case 3: bme680_use_heater_profile(sensor, 3); break;
+        case 4: bme680_use_heater_profile(sensor, 4); break;
+    }
+
+    // measurement duration changes in each cycle
+    uint32_t duration;
+    bme680_get_measurement_duration(sensor, &duration);
+
+    // trigger the sensor to start one TPHG measurement cycle
+    if (bme680_force_measurement(sensor) == ESP_OK)
+    {
+        vTaskDelay(duration);
+
+        // get the results and do something with them
+        if (bme680_get_results_float(sensor, &values) == ESP_OK)
+            ...
+    }
+    ...
+}
+...
+```
+
+### Ambient temperature
+
+The heater resistance calculation algorithm takes into account the ambient
+temperature of the sensor. Using function `bme680_set_ambient_temperature()`,
+the ambient temperature either determined from the sensor itself or from
+another temperature sensor can be set.
+
+```C
+...
+bme680_set_ambient_temperature(sensor, ambient);
+...
+```
+
+## Usage
+
+First, the hardware configuration has to be established. This can differ
+dependent on the communication interface and the number of sensors used.
+
+### Hardware configurations
+
+The driver supports multiple BME680 sensors at the same time that are
+connected to I2C. Following figure show some possible hardware
+configurations.
+
+First figure shows the configuration with only one sensor at I2C bus 0.
+
+```text
+ +------------------+   +----------+
+ | ESP8266 / ESP32  |   | BME680   |
+ |                  |   |          |
+ |   GPIO 14 (SCL)  ----> SCL      |
+ |   GPIO 13 (SDA)  <---> SDA      |
+ +------------------+   +----------+
+```
+
+Next figure shows a possible configuration with two I2C buses. In that case,
+the sensors can have same or different I2C slave addresses.
+
+```text
+ +------------------+   +----------+
+ | ESP8266 / ESP32  |   | BME680_1 |
+ |                  |   |          |
+ |   GPIO 14 (SCL)  ----> SCL      |
+ |   GPIO 13 (SDA)  <---> SDA      |
+ |                  |   +----------+
+ |                  |   | BME680_2 |
+ |                  |   |          |
+ |   GPIO 5  (SCL)  ----> SCL      |
+ |   GPIO 4  (SDA)  <---> SDA      |
+ +------------------+   +----------+
+```
+
+Further configurations are possible, e.g., two sensors that are connected
+at the same I2C bus with different slave addresses.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/bme680/bme680.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,1010 @@
+/*
+ * Copyright (c) 2017 Gunar Schorcht <https://github.com/gschorcht>
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * ESP-IDF driver for BME680 digital environmental sensor
+ *
+ * Forked from <https://github.com/gschorcht/bme680-esp-idf>
+ *
+ * Copyright (c) 2017 Gunar Schorcht <https://github.com/gschorcht>\n
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#include <string.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <esp_log.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <esp_idf_lib_helpers.h>
+#include "bme680.h"
+
+#define I2C_FREQ_HZ 1000000 // Up to 3.4MHz, but esp-idf only supports 1MHz
+
+// modes: unfortunatly, only SLEEP_MODE and FORCED_MODE are documented
+#define BME680_SLEEP_MODE           0x00    // low power sleeping
+#define BME680_FORCED_MODE          0x01    // perform one TPHG cycle (field data 0 filled)
+#define BME680_PARALLEL_MODE        0x02    // no information what it does :-(
+#define BME680_SQUENTUAL_MODE       0x02    // no information what it does (field data 0+1+2 filled)
+
+// register addresses
+#define BME680_REG_RES_HEAT_VAL     0x00
+#define BME680_REG_RES_HEAT_RANGE   0x02
+#define BME680_REG_RANGE_SW_ERROR   0x06
+
+#define BME680_REG_IDAC_HEAT_BASE   0x50    // 10 regsrs idac_heat_0 ... idac_heat_9
+#define BME680_REG_RES_HEAT_BASE    0x5a    // 10 registers res_heat_0 ... res_heat_9
+#define BME680_REG_GAS_WAIT_BASE    0x64    // 10 registers gas_wait_0 ... gas_wait_9
+#define BME680_REG_CTRL_GAS_0       0x70
+#define BME680_REG_CTRL_GAS_1       0x71
+#define BME680_REG_CTRL_HUM         0x72
+#define BME680_REG_STATUS           0x73
+#define BME680_REG_CTRL_MEAS        0x74
+#define BME680_REG_CONFIG           0x75
+#define BME680_REG_ID               0xd0
+#define BME680_REG_RESET            0xe0
+
+// field data 0 registers
+#define BME680_REG_MEAS_STATUS_0    0x1d
+#define BME680_REG_MEAS_INDEX_0     0x1e
+#define BME680_REG_PRESS_MSB_0      0x1f
+#define BME680_REG_PRESS_LSB_0      0x20
+#define BME680_REG_PRESS_XLSB_0     0x21
+#define BME680_REG_TEMP_MSB_0       0x22
+#define BME680_REG_TEMP_LSB_0       0x23
+#define BME680_REG_TEMP_XLSB_0      0x24
+#define BME680_REG_HUM_MSB_0        0x25
+#define BME680_REG_HUM_LSB_0        0x26
+#define BME680_REG_GAS_R_MSB_0      0x2a
+#define BME680_REG_GAS_R_LSB_0      0x2b
+
+// field data 1 registers (not documented, used in SEQUENTIAL_MODE)
+#define BME680_REG_MEAS_STATUS_1    0x2e
+#define BME680_REG_MEAS_INDEX_1     0x2f
+
+// field data 2 registers (not documented, used in SEQUENTIAL_MODE)
+#define BME680_REG_MEAS_STATUS_2    0x3f
+#define BME680_REG_MEAS_INDEX_2     0x40
+
+// field data addresses
+#define BME680_REG_RAW_DATA_0       BME680_REG_MEAS_STATUS_0    // 0x1d ... 0x2b
+#define BME680_REG_RAW_DATA_1       BME680_REG_MEAS_STATUS_1    // 0x2e ... 0x3c
+#define BME680_REG_RAW_DATA_2       BME680_REG_MEAS_STATUS_2    // 0x40 ... 0x4d
+#define BME680_REG_RAW_DATA_LEN     (BME680_REG_GAS_R_LSB_0 - BME680_REG_MEAS_STATUS_0 + 1)
+
+// calibration data registers
+#define BME680_REG_CD1_ADDR         0x89    // 25 byte calibration data
+#define BME680_REG_CD1_LEN          25
+#define BME680_REG_CD2_ADDR         0xe1    // 16 byte calibration data
+#define BME680_REG_CD2_LEN          16
+#define BME680_REG_CD3_ADDR         0x00    //  8 byte device specific calibration data
+#define BME680_REG_CD3_LEN          8
+
+// register structure definitions
+#define BME680_NEW_DATA_BITS        0x80    // BME680_REG_MEAS_STATUS<7>
+#define BME680_NEW_DATA_SHIFT       7       // BME680_REG_MEAS_STATUS<7>
+#define BME680_GAS_MEASURING_BITS   0x40    // BME680_REG_MEAS_STATUS<6>
+#define BME680_GAS_MEASURING_SHIFT  6       // BME680_REG_MEAS_STATUS<6>
+#define BME680_MEASURING_BITS       0x20    // BME680_REG_MEAS_STATUS<5>
+#define BME680_MEASURING_SHIFT      5       // BME680_REG_MEAS_STATUS<5>
+#define BME680_GAS_MEAS_INDEX_BITS  0x0f    // BME680_REG_MEAS_STATUS<3:0>
+#define BME680_GAS_MEAS_INDEX_SHIFT 0       // BME680_REG_MEAS_STATUS<3:0>
+
+#define BME680_GAS_R_LSB_BITS       0xc0    // BME680_REG_GAS_R_LSB<7:6>
+#define BME680_GAS_R_LSB_SHIFT      6       // BME680_REG_GAS_R_LSB<7:6>
+#define BME680_GAS_VALID_BITS       0x20    // BME680_REG_GAS_R_LSB<5>
+#define BME680_GAS_VALID_SHIFT      5       // BME680_REG_GAS_R_LSB<5>
+#define BME680_HEAT_STAB_R_BITS     0x10    // BME680_REG_GAS_R_LSB<4>
+#define BME680_HEAT_STAB_R_SHIFT    4       // BME680_REG_GAS_R_LSB<4>
+#define BME680_GAS_RANGE_R_BITS     0x0f    // BME680_REG_GAS_R_LSB<3:0>
+#define BME680_GAS_RANGE_R_SHIFT    0       // BME680_REG_GAS_R_LSB<3:0>
+
+#define BME680_HEAT_OFF_BITS        0x04    // BME680_REG_CTRL_GAS_0<3>
+#define BME680_HEAT_OFF_SHIFT       3       // BME680_REG_CTRL_GAS_0<3>
+
+#define BME680_RUN_GAS_BITS         0x10    // BME680_REG_CTRL_GAS_1<4>
+#define BME680_RUN_GAS_SHIFT        4       // BME680_REG_CTRL_GAS_1<4>
+#define BME680_NB_CONV_BITS         0x0f    // BME680_REG_CTRL_GAS_1<3:0>
+#define BME680_NB_CONV_SHIFT        0       // BME680_REG_CTRL_GAS_1<3:0>
+
+#define BME680_SPI_3W_INT_EN_BITS   0x40    // BME680_REG_CTRL_HUM<6>
+#define BME680_SPI_3W_INT_EN_SHIFT  6       // BME680_REG_CTRL_HUM<6>
+#define BME680_OSR_H_BITS           0x07    // BME680_REG_CTRL_HUM<2:0>
+#define BME680_OSR_H_SHIFT          0       // BME680_REG_CTRL_HUM<2:0>
+
+#define BME680_OSR_T_BITS           0xe0    // BME680_REG_CTRL_MEAS<7:5>
+#define BME680_OSR_T_SHIFT          5       // BME680_REG_CTRL_MEAS<7:5>
+#define BME680_OSR_P_BITS           0x1c    // BME680_REG_CTRL_MEAS<4:2>
+#define BME680_OSR_P_SHIFT          2       // BME680_REG_CTRL_MEAS<4:2>
+#define BME680_MODE_BITS            0x03    // BME680_REG_CTRL_MEAS<1:0>
+#define BME680_MODE_SHIFT           0       // BME680_REG_CTRL_MEAS<1:0>
+
+#define BME680_FILTER_BITS          0x1c    // BME680_REG_CONFIG<4:2>
+#define BME680_FILTER_SHIFT         2       // BME680_REG_CONFIG<4:2>
+#define BME680_SPI_3W_EN_BITS       0x01    // BME680_REG_CONFIG<0>
+#define BME680_SPI_3W_EN_SHIFT      0       // BME680_REG_CONFIG<0>
+
+#define BME680_SPI_MEM_PAGE_BITS    0x10    // BME680_REG_STATUS<4>
+#define BME680_SPI_MEM_PAGE_SHIFT   4       // BME680_REG_STATUS<4>
+
+#define BME680_GAS_WAIT_BITS        0x3f    // BME680_REG_GAS_WAIT+x<5:0>
+#define BME680_GAS_WAIT_SHIFT       0       // BME680_REG_GAS_WAIT+x<5:0>
+#define BME680_GAS_WAIT_MULT_BITS   0xc0    // BME680_REG_GAS_WAIT+x<7:6>
+#define BME680_GAS_WAIT_MULT_SHIFT  6       // BME680_REG_GAS_WAIT+x<7:6>
+
+// commands
+#define BME680_RESET_CMD            0xb6    // BME680_REG_RESET<7:0>
+#define BME680_RESET_PERIOD         10      // reset time in ms
+
+#define BME680_RHR_BITS             0x30    // BME680_REG_RES_HEAT_RANGE<5:4>
+#define BME680_RHR_SHIFT            4       // BME680_REG_RES_HEAT_RANGE<5:4>
+#define BME680_RSWE_BITS            0xf0    // BME680_REG_RANGE_SW_ERROR<7:4>
+#define BME680_RSWE_SHIFT           4       // BME680_REG_RANGE_SW_ERROR<7:4>
+
+// calibration data are stored in a calibration data map
+#define BME680_CDM_SIZE (BME680_REG_CD1_LEN + BME680_REG_CD2_LEN + BME680_REG_CD3_LEN)
+#define BME680_CDM_OFF1 0
+#define BME680_CDM_OFF2 BME680_REG_CD1_LEN
+#define BME680_CDM_OFF3 BME680_CDM_OFF2 + BME680_REG_CD2_LEN
+
+// calibration parameter offsets in calibration data map
+// calibration data from 0x89
+#define BME680_CDM_T2   1
+#define BME680_CDM_T3   3
+#define BME680_CDM_P1   5
+#define BME680_CDM_P2   7
+#define BME680_CDM_P3   9
+#define BME680_CDM_P4   11
+#define BME680_CDM_P5   13
+#define BME680_CDM_P7   15
+#define BME680_CDM_P6   16
+#define BME680_CDM_P8   19
+#define BME680_CDM_P9   21
+#define BME680_CDM_P10  23
+// calibration data from 0e1
+#define BME680_CDM_H2   25
+#define BME680_CDM_H1   26
+#define BME680_CDM_H3   28
+#define BME680_CDM_H4   29
+#define BME680_CDM_H5   30
+#define BME680_CDM_H6   31
+#define BME680_CDM_H7   32
+#define BME680_CDM_T1   33
+#define BME680_CDM_GH2  35
+#define BME680_CDM_GH1  37
+#define BME680_CDM_GH3  38
+// device specific calibration data from 0x00
+#define BME680_CDM_RHV  41      // 0x00 - res_heat_val
+#define BME680_CDM_RHR  43      // 0x02 - res_heat_range
+#define BME680_CDM_RSWE 45      // 0x04 - range_sw_error
+
+static const char *TAG = "bme680";
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+#define CHECK_LOGE(x, msg, ...) do { \
+        esp_err_t __; \
+        if ((__ = x) != ESP_OK) { \
+            ESP_LOGE(TAG, msg, ## __VA_ARGS__); \
+            return __; \
+        } \
+    } while (0)
+
+/**
+ * @brief   Raw data (integer values) read from sensor
+ */
+typedef struct
+{
+
+    bool gas_valid;      // indicate that gas measurement results are valid
+    bool heater_stable;  // indicate that heater temperature was stable
+
+    uint32_t temperature;    // degree celsius x100
+    uint32_t pressure;       // pressure in Pascal
+    uint16_t humidity;       // relative humidity x1000 in %
+    uint16_t gas_resistance; // gas resistance data
+    uint8_t gas_range;      // gas resistance range
+
+    uint8_t gas_index;      // heater profile used (0 ... 9)
+    uint8_t meas_index;
+
+} bme680_raw_data_t;
+
+#define lsb_msb_to_type(t,b,o) (t)(((t)b[o+1] << 8) | b[o])
+#define lsb_to_type(t,b,o)     (t)(b[o])
+#define bme_set_reg_bit(byte, bitname, bit) ( (byte & ~bitname##_BITS) | \
+                                              ((bit << bitname##_SHIFT) & bitname##_BITS) )
+#define bme_get_reg_bit(byte, bitname)      ( (byte & bitname##_BITS) >> bitname##_SHIFT )
+
+static inline esp_err_t read_reg_8_nolock(bme680_t *dev, uint8_t reg, uint8_t *data)
+{
+    return i2c_dev_read_reg(&dev->i2c_dev, reg, data, 1);
+}
+
+static inline esp_err_t write_reg_8_nolock(bme680_t *dev, uint8_t reg, uint8_t data)
+{
+    return i2c_dev_write_reg(&dev->i2c_dev, reg, &data, 1);
+}
+
+static esp_err_t read_reg_8(bme680_t *dev, uint8_t reg, uint8_t *data)
+{
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, read_reg_8_nolock(dev, reg, data));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+static esp_err_t bme680_set_mode(bme680_t *dev, uint8_t mode)
+{
+    uint8_t reg;
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, read_reg_8_nolock(dev, BME680_REG_CTRL_MEAS, &reg));
+    reg = bme_set_reg_bit(reg, BME680_MODE, mode);
+    I2C_DEV_CHECK(&dev->i2c_dev, write_reg_8_nolock(dev, BME680_REG_CTRL_MEAS, reg));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+#define msb_lsb_xlsb_to_20bit(t,b,o) (t)((t) b[o] << 12 | (t) b[o+1] << 4 | b[o+2] >> 4)
+#define msb_lsb_to_type(t,b,o)       (t)(((t)b[o] << 8) | b[o+1])
+
+#define BME680_RAW_P_OFF BME680_REG_PRESS_MSB_0-BME680_REG_MEAS_STATUS_0
+#define BME680_RAW_T_OFF (BME680_RAW_P_OFF + BME680_REG_TEMP_MSB_0 - BME680_REG_PRESS_MSB_0)
+#define BME680_RAW_H_OFF (BME680_RAW_T_OFF + BME680_REG_HUM_MSB_0 - BME680_REG_TEMP_MSB_0)
+#define BME680_RAW_G_OFF (BME680_RAW_H_OFF + BME680_REG_GAS_R_MSB_0 - BME680_REG_HUM_MSB_0)
+
+static esp_err_t bme680_get_raw_data(bme680_t *dev, bme680_raw_data_t *raw_data)
+{
+    if (!dev->meas_started)
+    {
+        ESP_LOGE(TAG, "Measurement was not started");
+        return ESP_ERR_INVALID_STATE;
+    }
+
+    uint8_t raw[BME680_REG_RAW_DATA_LEN] = { 0 };
+
+    if (!(dev->meas_status & BME680_NEW_DATA_BITS))
+    {
+        // read measurement status from sensor
+        CHECK(read_reg_8(dev, BME680_REG_MEAS_STATUS_0, &dev->meas_status));
+        // test whether there are new data
+        if (!(dev->meas_status & BME680_NEW_DATA_BITS))
+        {
+            if (dev->meas_status & BME680_MEASURING_BITS)
+            {
+                ESP_LOGW(TAG, "Measurement is still running");
+                return ESP_ERR_INVALID_STATE;
+            }
+            ESP_LOGW(TAG, "No new data");
+            return ESP_ERR_INVALID_RESPONSE;
+        }
+    }
+
+    dev->meas_started = false;
+    raw_data->gas_index = dev->meas_status & BME680_GAS_MEAS_INDEX_BITS;
+
+    // if there are new data, read raw data from sensor
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, BME680_REG_RAW_DATA_0, raw, BME680_REG_RAW_DATA_LEN));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    raw_data->gas_valid     = bme_get_reg_bit(raw[BME680_RAW_G_OFF + 1], BME680_GAS_VALID);
+    raw_data->heater_stable = bme_get_reg_bit(raw[BME680_RAW_G_OFF + 1], BME680_HEAT_STAB_R);
+
+    raw_data->temperature    = msb_lsb_xlsb_to_20bit(uint32_t, raw, BME680_RAW_T_OFF);
+    raw_data->pressure       = msb_lsb_xlsb_to_20bit(uint32_t, raw, BME680_RAW_P_OFF);
+    raw_data->humidity       = msb_lsb_to_type(uint16_t, raw, BME680_RAW_H_OFF);
+    raw_data->gas_resistance = ((uint16_t) raw[BME680_RAW_G_OFF] << 2) | raw[BME680_RAW_G_OFF + 1] >> 6;
+    raw_data->gas_range      = raw[BME680_RAW_G_OFF + 1] & BME680_GAS_RANGE_R_BITS;
+
+    /*
+     * BME680_REG_MEAS_STATUS_1, BME680_REG_MEAS_STATUS_2
+     * These data are not documented and it is not really clear when they are filled
+     */
+    ESP_LOGD(TAG, "Raw data: %" PRIu32 " %" PRIu32 " %d %d %d", raw_data->temperature, raw_data->pressure,
+            raw_data->humidity, raw_data->gas_resistance, raw_data->gas_range);
+
+    return ESP_OK;
+}
+
+/**
+ * @brief   Calculate temperature from raw temperature value
+ * @ref     BME280 datasheet, page 50
+ */
+static int16_t bme680_convert_temperature(bme680_t *dev, uint32_t raw_temperature)
+{
+    bme680_calib_data_t *cd = &dev->calib_data;
+
+    int64_t var1;
+    int64_t var2;
+    int16_t temperature;
+
+    var1 = ((((raw_temperature >> 3) - ((int32_t) cd->par_t1 << 1))) * ((int32_t) cd->par_t2)) >> 11;
+    var2 = (((((raw_temperature >> 4) - ((int32_t) cd->par_t1)) * ((raw_temperature >> 4) - ((int32_t) cd->par_t1))) >> 12)
+            * ((int32_t) cd->par_t3)) >> 14;
+    cd->t_fine = (int32_t) (var1 + var2);
+    temperature = (cd->t_fine * 5 + 128) >> 8;
+
+    return temperature;
+}
+
+/**
+ * @brief       Calculate pressure from raw pressure value
+ * @copyright   Copyright (c) 2017 - 2018 Bosch Sensortec GmbH
+ *
+ * The algorithm was extracted from the original Bosch Sensortec BME680 driver
+ * published as open source. Divisions and multiplications by potences of 2
+ * were replaced by shift operations for effeciency reasons.
+ *
+ * @ref         [BME680_diver](https://github.com/BoschSensortec/BME680_driver)
+ * @ref         BME280 datasheet, page 50
+ */
+static uint32_t bme680_convert_pressure(bme680_t *dev, uint32_t raw_pressure)
+{
+    bme680_calib_data_t *cd = &dev->calib_data;
+
+    int32_t var1;
+    int32_t var2;
+    int32_t var3;
+    int32_t pressure_comp;
+
+    var1 = (((int32_t)cd->t_fine) >> 1) - 64000;
+    var2 = ((((var1 >> 2) * (var1 >> 2)) >> 11) *
+            (int32_t)cd->par_p6) >> 2;
+    var2 = var2 + ((var1 * (int32_t)cd->par_p5) << 1);
+    var2 = (var2 >> 2) + ((int32_t)cd->par_p4 << 16);
+    var1 = (((((var1 >> 2) * (var1 >> 2)) >> 13) *
+             ((int32_t)cd->par_p3 << 5)) >> 3) +
+           (((int32_t)cd->par_p2 * var1) >> 1);
+    var1 = var1 >> 18;
+    var1 = ((32768 + var1) * (int32_t)cd->par_p1) >> 15;
+    pressure_comp = 1048576 - raw_pressure;
+    pressure_comp = (int32_t)((pressure_comp - (var2 >> 12)) * ((uint32_t)3125));
+    if (pressure_comp >= BME680_MAX_OVERFLOW_VAL)
+        pressure_comp = ((pressure_comp / var1) << 1);
+    else
+        pressure_comp = ((pressure_comp << 1) / var1);
+    var1 = ((int32_t)cd->par_p9 * (int32_t)(((pressure_comp >> 3) *
+                                            (pressure_comp >> 3)) >> 13)) >> 12;
+    var2 = ((int32_t)(pressure_comp >> 2) *
+            (int32_t)cd->par_p8) >> 13;
+    var3 = ((int32_t)(pressure_comp >> 8) * (int32_t)(pressure_comp >> 8) *
+            (int32_t)(pressure_comp >> 8) *
+            (int32_t)cd->par_p10) >> 17;
+
+    pressure_comp = (int32_t)(pressure_comp) + ((var1 + var2 + var3 +
+                    ((int32_t)cd->par_p7 << 7)) >> 4);
+
+    return (uint32_t)pressure_comp;
+}
+
+/**
+ * @brief       Calculate humidty from raw humidity data
+ * @copyright   Copyright (c) 2017 - 2018 Bosch Sensortec GmbH
+ *
+ * The algorithm was extracted from the original Bosch Sensortec BME680 driver
+ * published as open source. Divisions and multiplications by potences of 2
+ * were replaced by shift operations for effeciency reasons.
+ *
+ * @ref         [BME680_diver](https://github.com/BoschSensortec/BME680_driver)
+ */
+static uint32_t bme680_convert_humidity(bme680_t *dev, uint16_t raw_humidity)
+{
+    bme680_calib_data_t *cd = &dev->calib_data;
+
+    int32_t var1;
+    int32_t var2;
+    int32_t var3;
+    int32_t var4;
+    int32_t var5;
+    int32_t var6;
+    int32_t temp_scaled;
+    int32_t humidity;
+
+    temp_scaled = (((int32_t) cd->t_fine * 5) + 128) >> 8;
+    var1 = (int32_t) (raw_humidity - ((int32_t) ((int32_t) cd->par_h1 << 4)))
+            - (((temp_scaled * (int32_t) cd->par_h3) / ((int32_t) 100)) >> 1);
+    var2 = ((int32_t) cd->par_h2
+            * (((temp_scaled * (int32_t) cd->par_h4) / ((int32_t) 100))
+                    + (((temp_scaled * ((temp_scaled * (int32_t) cd->par_h5) / ((int32_t) 100))) >> 6) / ((int32_t) 100))
+                    + (int32_t) (1 << 14))) >> 10;
+    var3 = var1 * var2;
+    var4 = (int32_t) cd->par_h6 << 7;
+    var4 = ((var4) + ((temp_scaled * (int32_t) cd->par_h7) / ((int32_t) 100))) >> 4;
+    var5 = ((var3 >> 14) * (var3 >> 14)) >> 10;
+    var6 = (var4 * var5) >> 1;
+    humidity = (((var3 + var6) >> 10) * ((int32_t) 1000)) >> 12;
+
+    if (humidity > 100000) /* Cap at 100%rH */
+        humidity = 100000;
+    else if (humidity < 0)
+        humidity = 0;
+
+    return (uint32_t) humidity;
+}
+
+/**
+ * @brief   Lookup table for gas resitance computation
+ * @ref     BME680 datasheet, page 19
+ */
+static float lookup_table[16][2] = {
+        // const1, const2       // gas_range
+        { 1.0,   8000000.0 },   // 0
+        { 1.0,   4000000.0 },   // 1
+        { 1.0,   2000000.0 },   // 2
+        { 1.0,   1000000.0 },   // 3
+        { 1.0,   499500.4995 }, // 4
+        { 0.99,  248262.1648 }, // 5
+        { 1.0,   125000.0 },    // 6
+        { 0.992, 63004.03226 }, // 7
+        { 1.0,   31281.28128 }, // 8
+        { 1.0,   15625.0 },     // 9
+        { 0.998, 7812.5 },      // 10
+        { 0.995, 3906.25 },     // 11
+        { 1.0,   1953.125 },    // 12
+        { 0.99,  976.5625 },    // 13
+        { 1.0,   488.28125 },   // 14
+        { 1.0,   244.140625 }   // 15
+};
+
+/**
+ * @brief   Calculate gas resistance from raw gas resitance value and gas range
+ * @ref     BME680 datasheet
+ */
+static uint32_t bme680_convert_gas(bme680_t *dev, uint16_t gas, uint8_t gas_range)
+{
+    bme680_calib_data_t *cd = &dev->calib_data;
+
+    float var1 = (1340.0 + 5.0 * cd->range_sw_err) * lookup_table[gas_range][0];
+    return var1 * lookup_table[gas_range][1] / (gas - 512.0 + var1);
+}
+
+/**
+ * @brief   Calculate internal duration representation
+ *
+ * Durations are internally representes as one byte
+ *
+ *  duration = value<5:0> * multiplier<7:6>
+ *
+ * where the multiplier is 1, 4, 16, or 64. Maximum duration is therefore
+ * 64*64 = 4032 ms. The function takes a real world duration value given
+ * in milliseconds and computes the internal representation.
+ *
+ * @ref Datasheet
+ */
+static uint8_t bme680_heater_duration(uint16_t duration)
+{
+    uint8_t multiplier = 0;
+
+    while (duration > 63)
+    {
+        duration = duration / 4;
+        multiplier++;
+    }
+    return (uint8_t) (duration | (multiplier << 6));
+}
+
+/**
+ * @brief  Calculate internal heater resistance value from real temperature.
+ *
+ * @ref Datasheet of BME680
+ */
+static uint8_t bme680_heater_resistance(const bme680_t *dev, uint16_t temp)
+{
+    if (!dev)
+        return 0;
+
+    if (temp < BME680_HEATER_TEMP_MIN)
+        temp = BME680_HEATER_TEMP_MIN;
+    else if (temp > BME680_HEATER_TEMP_MAX)
+        temp = BME680_HEATER_TEMP_MAX;
+
+    const bme680_calib_data_t *cd = &dev->calib_data;
+
+    // from datasheet
+    double var1;
+    double var2;
+    double var3;
+    double var4;
+    double var5;
+    uint8_t res_heat_x;
+
+    var1 = ((double) cd->par_gh1 / 16.0) + 49.0;
+    var2 = (((double) cd->par_gh2 / 32768.0) * 0.0005) + 0.00235;
+    var3 = (double) cd->par_gh3 / 1024.0;
+    var4 = var1 * (1.0 + (var2 * (double) temp));
+    var5 = var4 + (var3 * (double) dev->settings.ambient_temperature);
+    res_heat_x = (uint8_t) (3.4
+            * ((var5 * (4.0 / (4.0 + (double) cd->res_heat_range)) * (1.0 / (1.0 + ((double) cd->res_heat_val * 0.002)))) - 25));
+    return res_heat_x;
+
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t bme680_init_desc(bme680_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+
+    if (addr != BME680_I2C_ADDR_0 &&  addr != BME680_I2C_ADDR_1)
+    {
+        ESP_LOGE(TAG, "Invalid I2C address");
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    dev->i2c_dev.port = port;
+    dev->i2c_dev.addr = addr;
+    dev->i2c_dev.cfg.sda_io_num = sda_gpio;
+    dev->i2c_dev.cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(&dev->i2c_dev);
+}
+
+esp_err_t bme680_free_desc(bme680_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(&dev->i2c_dev);
+}
+
+esp_err_t bme680_init_sensor(bme680_t *dev)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    dev->meas_started = false;
+    dev->meas_status = 0;
+    dev->settings.ambient_temperature = 0;
+    dev->settings.osr_temperature = BME680_OSR_NONE;
+    dev->settings.osr_pressure = BME680_OSR_NONE;
+    dev->settings.osr_humidity = BME680_OSR_NONE;
+    dev->settings.filter_size = BME680_IIR_SIZE_0;
+    dev->settings.heater_profile = BME680_HEATER_NOT_USED;
+    memset(dev->settings.heater_temperature, 0, sizeof(uint16_t) * 10);
+    memset(dev->settings.heater_duration, 0, sizeof(uint16_t) * 10);
+
+    // reset the sensor
+    I2C_DEV_CHECK(&dev->i2c_dev, write_reg_8_nolock(dev, BME680_REG_RESET, BME680_RESET_CMD));
+    vTaskDelay(pdMS_TO_TICKS(BME680_RESET_PERIOD));
+
+    uint8_t chip_id = 0;
+    I2C_DEV_CHECK(&dev->i2c_dev, read_reg_8_nolock(dev, BME680_REG_ID, &chip_id));
+    if (chip_id != 0x61)
+    {
+        I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+        ESP_LOGE(TAG, "Chip id %02x is wrong, should be 0x61", chip_id);
+        return ESP_ERR_NOT_FOUND;
+    }
+
+    uint8_t buf[BME680_CDM_SIZE];
+
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, BME680_REG_CD1_ADDR, buf + BME680_CDM_OFF1, BME680_REG_CD1_LEN));
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, BME680_REG_CD2_ADDR, buf + BME680_CDM_OFF2, BME680_REG_CD2_LEN));
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, BME680_REG_CD3_ADDR, buf + BME680_CDM_OFF3, BME680_REG_CD3_LEN));
+
+    dev->calib_data.par_t1 = lsb_msb_to_type(uint16_t, buf, BME680_CDM_T1);
+    dev->calib_data.par_t2 = lsb_msb_to_type(int16_t, buf, BME680_CDM_T2);
+    dev->calib_data.par_t3 = lsb_to_type(int8_t, buf, BME680_CDM_T3);
+
+    // pressure compensation parameters
+    dev->calib_data.par_p1 = lsb_msb_to_type(uint16_t, buf, BME680_CDM_P1);
+    dev->calib_data.par_p2 = lsb_msb_to_type(int16_t, buf, BME680_CDM_P2);
+    dev->calib_data.par_p3 = lsb_to_type(int8_t, buf, BME680_CDM_P3);
+    dev->calib_data.par_p4 = lsb_msb_to_type(int16_t, buf, BME680_CDM_P4);
+    dev->calib_data.par_p5 = lsb_msb_to_type(int16_t, buf, BME680_CDM_P5);
+    dev->calib_data.par_p6 = lsb_to_type(int8_t, buf, BME680_CDM_P6);
+    dev->calib_data.par_p7 = lsb_to_type(int8_t, buf, BME680_CDM_P7);
+    dev->calib_data.par_p8 = lsb_msb_to_type(int16_t, buf, BME680_CDM_P8);
+    dev->calib_data.par_p9 = lsb_msb_to_type(int16_t, buf, BME680_CDM_P9);
+    dev->calib_data.par_p10 = lsb_to_type(uint8_t, buf, BME680_CDM_P10);
+
+    // humidity compensation parameters
+    dev->calib_data.par_h1 = (uint16_t) (((uint16_t) buf[BME680_CDM_H1 + 1] << 4) | (buf[BME680_CDM_H1] & 0x0F));
+    dev->calib_data.par_h2 = (uint16_t) (((uint16_t) buf[BME680_CDM_H2] << 4) | (buf[BME680_CDM_H2 + 1] >> 4));
+    dev->calib_data.par_h3 = lsb_to_type(int8_t, buf, BME680_CDM_H3);
+    dev->calib_data.par_h4 = lsb_to_type(int8_t, buf, BME680_CDM_H4);
+    dev->calib_data.par_h5 = lsb_to_type(int8_t, buf, BME680_CDM_H5);
+    dev->calib_data.par_h6 = lsb_to_type(uint8_t, buf, BME680_CDM_H6);
+    dev->calib_data.par_h7 = lsb_to_type(int8_t, buf, BME680_CDM_H7);
+
+    // gas sensor compensation parameters
+    dev->calib_data.par_gh1 = lsb_to_type(int8_t, buf, BME680_CDM_GH1);
+    dev->calib_data.par_gh2 = lsb_msb_to_type(int16_t, buf, BME680_CDM_GH2);
+    dev->calib_data.par_gh3 = lsb_to_type(int8_t, buf, BME680_CDM_GH3);
+
+    dev->calib_data.res_heat_range = (lsb_to_type(uint8_t, buf, BME680_CDM_RHR) & BME680_RHR_BITS) >> BME680_RHR_SHIFT;
+    dev->calib_data.res_heat_val = (lsb_to_type(int8_t, buf, BME680_CDM_RHV));
+    dev->calib_data.range_sw_err = (lsb_to_type(int8_t, buf, BME680_CDM_RSWE) & BME680_RSWE_BITS) >> BME680_RSWE_SHIFT;
+
+    // Set ambient temperature of sensor to default value (25 degree C)
+    dev->settings.ambient_temperature = 25;
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    CHECK(bme680_set_oversampling_rates(dev, BME680_OSR_1X, BME680_OSR_1X, BME680_OSR_1X));
+    CHECK(bme680_set_filter_size(dev, BME680_IIR_SIZE_3));
+
+    // Set heater default profile 0 to 320 degree Celcius for 150 ms
+    CHECK(bme680_set_heater_profile(dev, 0, 320, 150));
+    CHECK(bme680_use_heater_profile(dev, 0));
+
+    return ESP_OK;
+}
+
+esp_err_t bme680_force_measurement(bme680_t *dev)
+{
+    CHECK_ARG(dev);
+    if (dev->meas_started)
+    {
+        ESP_LOGE(TAG, "Measurement is already running");
+        return ESP_ERR_INVALID_STATE;
+    }
+
+    // Set the power mode to forced mode to trigger one TPHG measurement cycle
+    CHECK_LOGE(bme680_set_mode(dev, BME680_FORCED_MODE),
+            "Could not set forced mode to start TPHG measurement cycle");
+    dev->meas_started = true;
+    dev->meas_status = 0;
+
+    ESP_LOGD(TAG, "Started measurement");
+
+    return ESP_OK;
+}
+
+/**
+ * @brief Estimate the measurement duration in RTOS ticks
+ *
+ * Timing formulas extracted from BME280 datasheet and test in some
+ * experiments. They represent the maximum measurement duration.
+ */
+esp_err_t bme680_get_measurement_duration(const bme680_t *dev, uint32_t *duration)
+{
+    CHECK_ARG(dev && duration);
+
+    *duration = 0; /* Calculate in us */
+
+    // wake up duration from sleep into forced mode
+    *duration += 1250;
+
+    // THP cycle duration which consumes 1963 µs for each measurement at maximum
+    if (dev->settings.osr_temperature)
+        *duration += (1 << (dev->settings.osr_temperature - 1)) * 2300;
+    if (dev->settings.osr_pressure)
+        *duration += (1 << (dev->settings.osr_pressure - 1)) * 2300 + 575;
+    if (dev->settings.osr_humidity)
+        *duration += (1 << (dev->settings.osr_humidity - 1)) * 2300 + 575;
+
+    // if gas measurement is used
+    if (dev->settings.heater_profile != BME680_HEATER_NOT_USED && dev->settings.heater_duration[dev->settings.heater_profile]
+            && dev->settings.heater_temperature[dev->settings.heater_profile])
+    {
+        // gas heating time
+        *duration += dev->settings.heater_duration[dev->settings.heater_profile] * 1000;
+        // gas measurement duration;
+        *duration += 2300 + 575;
+    }
+
+    // round up to next ms (1 us ... 1000 us => 1 ms)
+    *duration += 999;
+    *duration /= 1000;
+
+    // some ms tolerance
+    *duration += 5;
+
+    // ceil to next integer value that is divisible by portTICK_PERIOD_MS and
+    // compute RTOS ticks (1 ... portTICK_PERIOD_MS =  1 tick)
+    *duration = (*duration + portTICK_PERIOD_MS - 1) / portTICK_PERIOD_MS;
+
+    // Since first RTOS tick can be shorter than the half of defined tick period,
+    // the delay caused by vTaskDelay(duration) might be 1 or 2 ms shorter than
+    // computed duration in rare cases. Since the duration is computed for maximum
+    // and not for the typical durations and therefore tends to be too long, this
+    // should not be a problem. Therefore, only one additional tick used.
+    *duration += 1;
+
+    return ESP_OK;
+}
+
+esp_err_t bme680_is_measuring(bme680_t *dev, bool *busy)
+{
+    CHECK_ARG(dev && busy);
+
+    // if measurement wasn't started, it is of course not measuring
+    if (!dev->meas_started)
+    {
+        *busy = false;
+        return ESP_OK;
+    }
+
+    CHECK(read_reg_8(dev, BME680_REG_MEAS_STATUS_0, &dev->meas_status));
+    *busy = dev->meas_status & BME680_MEASURING_BITS ? 1 : 0;
+
+    return ESP_OK;
+}
+
+esp_err_t bme680_get_results_fixed(bme680_t *dev, bme680_values_fixed_t *results)
+{
+    CHECK_ARG(dev && results);
+
+    // fill data structure with invalid values
+    results->temperature = INT16_MIN;
+    results->pressure = 0;
+    results->humidity = 0;
+    results->gas_resistance = 0;
+
+    bme680_raw_data_t raw;
+    CHECK(bme680_get_raw_data(dev, &raw));
+
+    // use compensation algorithms to compute sensor values in fixed point format
+    if (dev->settings.osr_temperature)
+        results->temperature = bme680_convert_temperature(dev, raw.temperature);
+    if (dev->settings.osr_pressure)
+        results->pressure = bme680_convert_pressure(dev, raw.pressure);
+    if (dev->settings.osr_humidity)
+        results->humidity = bme680_convert_humidity(dev, raw.humidity);
+
+    if (dev->settings.heater_profile != BME680_HEATER_NOT_USED)
+    {
+        // convert gas only if raw data are valid and heater was stable
+        if (raw.gas_valid && raw.heater_stable)
+            results->gas_resistance = bme680_convert_gas(dev, raw.gas_resistance, raw.gas_range);
+        else if (!raw.gas_valid)
+            ESP_LOGW(TAG, "Gas data is not valid");
+        else
+            ESP_LOGW(TAG, "Heater is not stable");
+    }
+
+    ESP_LOGD(TAG, "Fixed point sensor values - %d/100 deg.C, %" PRIu32 "/1000 %%, %" PRIu32 " Pa, %" PRIu32 " Ohm",
+            results->temperature, results->humidity, results->pressure, results->gas_resistance);
+
+    return ESP_OK;
+}
+
+esp_err_t bme680_get_results_float(bme680_t *dev, bme680_values_float_t *results)
+{
+    CHECK_ARG(dev && results);
+
+    bme680_values_fixed_t fixed;
+    CHECK(bme680_get_results_fixed(dev, &fixed));
+
+    results->temperature = fixed.temperature / 100.0f;
+    results->pressure = fixed.pressure / 100.0f;
+    results->humidity = fixed.humidity / 1000.0f;
+    results->gas_resistance = fixed.gas_resistance;
+
+    return ESP_OK;
+}
+
+esp_err_t bme680_measure_fixed(bme680_t *dev, bme680_values_fixed_t *results)
+{
+    CHECK_ARG(dev && results);
+
+    uint32_t duration;
+    CHECK(bme680_get_measurement_duration(dev, &duration));
+    if (duration == 0)
+    {
+        ESP_LOGE(TAG, "Failed to get measurement duration");
+        return ESP_FAIL;
+    }
+
+    CHECK(bme680_force_measurement(dev));
+    vTaskDelay(duration);
+
+    return bme680_get_results_fixed(dev, results);
+}
+
+esp_err_t bme680_measure_float(bme680_t *dev, bme680_values_float_t *results)
+{
+    CHECK_ARG(dev && results);
+
+    uint32_t duration;
+    CHECK(bme680_get_measurement_duration(dev, &duration));
+    if (duration == 0)
+    {
+        ESP_LOGE(TAG, "Failed to get measurement duration");
+        return ESP_FAIL;
+    }
+
+    CHECK(bme680_force_measurement(dev));
+    vTaskDelay(duration);
+
+    return bme680_get_results_float(dev, results);
+}
+
+esp_err_t bme680_set_oversampling_rates(bme680_t *dev, bme680_oversampling_rate_t ost,
+        bme680_oversampling_rate_t osp, bme680_oversampling_rate_t osh)
+{
+    CHECK_ARG(dev);
+
+    bool ost_changed = dev->settings.osr_temperature != ost;
+    bool osp_changed = dev->settings.osr_pressure != osp;
+    bool osh_changed = dev->settings.osr_humidity != osh;
+
+    if (!ost_changed && !osp_changed && !osh_changed)
+        return ESP_OK;
+
+    // Set the temperature, pressure and humidity oversampling
+    dev->settings.osr_temperature = ost;
+    dev->settings.osr_pressure = osp;
+    dev->settings.osr_humidity = osh;
+
+    uint8_t reg;
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    if (ost_changed || osp_changed)
+    {
+        // read the current register value
+        I2C_DEV_CHECK(&dev->i2c_dev, read_reg_8_nolock(dev, BME680_REG_CTRL_MEAS, &reg));
+
+        // set changed bit values
+        if (ost_changed)
+            reg = bme_set_reg_bit(reg, BME680_OSR_T, ost);
+        if (osp_changed)
+            reg = bme_set_reg_bit(reg, BME680_OSR_P, osp);
+
+        // write back the new register value
+        I2C_DEV_CHECK(&dev->i2c_dev, write_reg_8_nolock(dev, BME680_REG_CTRL_MEAS, reg));
+    }
+    if (osh_changed)
+    {
+        // read the current register value
+        I2C_DEV_CHECK(&dev->i2c_dev, read_reg_8_nolock(dev, BME680_REG_CTRL_HUM, &reg));
+
+        // set changed bit value
+        reg = bme_set_reg_bit(reg, BME680_OSR_H, osh);
+
+        // write back the new register value
+        I2C_DEV_CHECK(&dev->i2c_dev, write_reg_8_nolock(dev, BME680_REG_CTRL_HUM, reg));
+    }
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    ESP_LOGD(TAG, "Setting oversampling rates done: osrt=%d osp=%d osrh=%d",
+            dev->settings.osr_temperature, dev->settings.osr_pressure, dev->settings.osr_humidity);
+
+    return ESP_OK;
+}
+
+esp_err_t bme680_set_filter_size(bme680_t *dev, bme680_filter_size_t size)
+{
+    CHECK_ARG(dev);
+
+    if (dev->settings.filter_size == size)
+        return ESP_OK;
+
+    /* Set the temperature, pressure and humidity settings */
+    dev->settings.filter_size = size;
+
+    uint8_t reg;
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    // read the current register value
+    I2C_DEV_CHECK(&dev->i2c_dev, read_reg_8_nolock(dev, BME680_REG_CONFIG, &reg));
+    // set changed bit value
+    reg = bme_set_reg_bit(reg, BME680_FILTER, size);
+    // write back the new register value
+    I2C_DEV_CHECK(&dev->i2c_dev, write_reg_8_nolock(dev, BME680_REG_CONFIG, reg));
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    ESP_LOGD(TAG, "Setting filter size done: size=%d", dev->settings.filter_size);
+
+    return ESP_OK;
+}
+
+esp_err_t bme680_set_heater_profile(bme680_t *dev, uint8_t profile, uint16_t temperature, uint16_t duration)
+{
+    CHECK_ARG(dev && profile < BME680_HEATER_PROFILES);
+
+    bool temperature_changed = dev->settings.heater_temperature[profile] != temperature;
+    bool duration_changed = dev->settings.heater_duration[profile] != duration;
+
+    if (!temperature_changed && !duration_changed)
+        return ESP_OK;
+
+    // set external gas sensor configuration
+    dev->settings.heater_temperature[profile] = temperature; // degree Celsius
+    dev->settings.heater_duration[profile] = duration;       // milliseconds
+
+    // compute internal gas sensor configuration parameters
+    uint8_t heat_dur = bme680_heater_duration(duration);           // internal duration value
+    uint8_t heat_res = bme680_heater_resistance(dev, temperature); // internal temperature value
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    // set internal gas sensor configuration parameters if changed
+    if (temperature_changed)
+        I2C_DEV_CHECK(&dev->i2c_dev, write_reg_8_nolock(dev, BME680_REG_RES_HEAT_BASE + profile, heat_res));
+    if (duration_changed)
+        I2C_DEV_CHECK(&dev->i2c_dev, write_reg_8_nolock(dev, BME680_REG_GAS_WAIT_BASE + profile, heat_dur));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+
+    ESP_LOGD(TAG, "Setting heater profile %d done: temperature=%d duration=%d"
+            " heater_resistance=%02x heater_duration=%02x", profile, dev->settings.heater_temperature[profile],
+            dev->settings.heater_duration[profile], heat_dur, heat_res);
+
+    return ESP_OK;
+}
+
+esp_err_t bme680_use_heater_profile(bme680_t *dev, int8_t profile)
+{
+    CHECK_ARG(dev);
+    CHECK_ARG(profile >= -1 && profile < BME680_HEATER_PROFILES);
+
+    if (dev->settings.heater_profile == profile)
+        return ESP_OK;
+
+    dev->settings.heater_profile = profile;
+
+    uint8_t reg = 0; // set
+    // set active profile
+    reg = bme_set_reg_bit(reg, BME680_NB_CONV, profile != BME680_HEATER_NOT_USED ? profile : 0);
+
+    // enable or disable gas measurement
+    reg = bme_set_reg_bit(reg, BME680_RUN_GAS,
+            (profile != BME680_HEATER_NOT_USED && dev->settings.heater_temperature[profile] && dev->settings.heater_duration[profile]));
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, write_reg_8_nolock(dev, BME680_REG_CTRL_GAS_1, reg));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t bme680_set_ambient_temperature(bme680_t *dev, int16_t ambient)
+{
+    CHECK_ARG(dev);
+
+    if (dev->settings.ambient_temperature == ambient)
+        return ESP_OK;
+
+    // set ambient temperature configuration
+    dev->settings.ambient_temperature = ambient; // degree Celsius
+
+    // update all valid heater profiles
+    uint8_t data[10];
+    for (int i = 0; i < BME680_HEATER_PROFILES; i++)
+        data[i] = dev->settings.heater_temperature[i]
+                ? bme680_heater_resistance(dev, dev->settings.heater_temperature[i])
+                : 0;
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_write_reg(&dev->i2c_dev, BME680_REG_RES_HEAT_BASE, data, 10));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    ESP_LOGD(TAG, "Setting heater ambient temperature done: ambient=%d", dev->settings.ambient_temperature);
+
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/bme680/bme680.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,449 @@
+/*
+ * Copyright (c) 2017 Gunar Schorcht <https://github.com/gschorcht>
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file bme680.h
+ * @defgroup bme680 bme680
+ * @{
+ *
+ * ESP-IDF driver for BME680 digital environmental sensor
+ *
+ * Forked from <https://github.com/gschorcht/bme680-esp-idf>
+ *
+ * Copyright (c) 2017 Gunar Schorcht <https://github.com/gschorcht>\n
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __BME680_H__
+#define __BME680_H__
+
+#include <stdbool.h>
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define BME680_I2C_ADDR_0 0x76
+#define BME680_I2C_ADDR_1 0x77
+
+#define BME680_MAX_OVERFLOW_VAL      INT32_C(0x40000000) // overflow value used in pressure calculation (bme680_convert_pressure)
+
+#define BME680_HEATER_TEMP_MIN         200  //!< min. 200 degree Celsius
+#define BME680_HEATER_TEMP_MAX         400  //!< max. 200 degree Celsius
+#define BME680_HEATER_PROFILES         10   //!< max. 10 heater profiles 0 ... 9
+#define BME680_HEATER_NOT_USED         -1   //!< heater not used profile
+
+/**
+ * Fixed point sensor values (fixed THPG values)
+ */
+typedef struct
+{
+    int16_t temperature;     //!< temperature in degree C * 100 (Invalid value INT16_MIN)
+    uint32_t pressure;       //!< barometric pressure in Pascal (Invalid value 0)
+    uint32_t humidity;       //!< relative humidity in % * 1000 (Invalid value 0)
+    uint32_t gas_resistance; //!< gas resistance in Ohm         (Invalid value 0)
+} bme680_values_fixed_t;
+
+/**
+ * Floating point sensor values (real THPG values)
+ */
+typedef struct
+{
+    float temperature;    //!< temperature in degree C        (Invalid value -327.68)
+    float pressure;       //!< barometric pressure in hPascal (Invalid value 0.0)
+    float humidity;       //!< relative humidity in %         (Invalid value 0.0)
+    float gas_resistance; //!< gas resistance in Ohm          (Invalid value 0.0)
+} bme680_values_float_t;
+
+/**
+ * Filter size
+ */
+typedef enum {
+    BME680_IIR_SIZE_0 = 0, //!< Filter is not used
+    BME680_IIR_SIZE_1,
+    BME680_IIR_SIZE_3,
+    BME680_IIR_SIZE_7,
+    BME680_IIR_SIZE_15,
+    BME680_IIR_SIZE_31,
+    BME680_IIR_SIZE_63,
+    BME680_IIR_SIZE_127
+} bme680_filter_size_t;
+
+/**
+ * Oversampling rate
+ */
+typedef enum {
+    BME680_OSR_NONE = 0, //!< Measurement is skipped, output values are invalid
+    BME680_OSR_1X,       //!< Default oversampling rates
+    BME680_OSR_2X,
+    BME680_OSR_4X,
+    BME680_OSR_8X,
+    BME680_OSR_16X
+} bme680_oversampling_rate_t;
+
+/**
+ * @brief Sensor parameters that configure the TPHG measurement cycle
+ *
+ *  T - temperature measurement
+ *  P - pressure measurement
+ *  H - humidity measurement
+ *  G - gas measurement
+ */
+typedef struct
+{
+    bme680_oversampling_rate_t osr_temperature; //!< T oversampling rate (default `BME680_OSR_1X`)
+    bme680_oversampling_rate_t osr_pressure;    //!< P oversampling rate (default `BME680_OSR_1X`)
+    bme680_oversampling_rate_t osr_humidity;    //!< H oversampling rate (default `BME680_OSR_1X`)
+    bme680_filter_size_t filter_size;           //!< IIR filter size (default `BME680_IIR_SIZE_3`)
+
+    int8_t heater_profile;                      //!< Heater profile used (default 0)
+    uint16_t heater_temperature[10];            //!< Heater temperature for G (default 320)
+    uint16_t heater_duration[10];               //!< Heater duration for G (default 150)
+
+    int8_t ambient_temperature;                 //!< Ambient temperature for G (default 25)
+} bme680_settings_t;
+
+/**
+ * @brief   Data structure for calibration parameters
+ *
+ * These calibration parameters are used in compensation algorithms to convert
+ * raw sensor data to measurement results.
+ */
+typedef struct
+{
+    uint16_t par_t1;         //!< calibration data for temperature compensation
+    int16_t  par_t2;
+    int8_t   par_t3;
+
+    uint16_t par_p1;         //!< calibration data for pressure compensation
+    int16_t  par_p2;
+    int8_t   par_p3;
+    int16_t  par_p4;
+    int16_t  par_p5;
+    int8_t   par_p7;
+    int8_t   par_p6;
+    int16_t  par_p8;
+    int16_t  par_p9;
+    uint8_t  par_p10;
+
+    uint16_t par_h1;         //!< calibration data for humidity compensation
+    uint16_t par_h2;
+    int8_t   par_h3;
+    int8_t   par_h4;
+    int8_t   par_h5;
+    uint8_t  par_h6;
+    int8_t   par_h7;
+
+    int8_t   par_gh1;        //!< calibration data for gas compensation
+    int16_t  par_gh2;
+    int8_t   par_gh3;
+
+    int32_t  t_fine;         //!< temperature correction factor for P and G
+    uint8_t  res_heat_range;
+    int8_t   res_heat_val;
+    int8_t   range_sw_err;
+} bme680_calib_data_t;
+
+/**
+ * BME680 sensor device data structure type
+ */
+typedef struct
+{
+    i2c_dev_t i2c_dev;              //!< I2C device descriptor
+
+    bool meas_started;              //!< Indicates whether measurement started
+    uint8_t meas_status;            //!< Last sensor status (for internal use only)
+
+    bme680_settings_t settings;     //!< Sensor settings
+    bme680_calib_data_t calib_data; //!< Calibration data of the sensor
+} bme680_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev Device descriptor
+ * @param addr BME680 address
+ * @param port I2C port number
+ * @param sda_gpio GPIO pin for SDA
+ * @param scl_gpio GPIO pin for SCL
+ * @return `ESP_OK` on success
+ */
+esp_err_t bme680_init_desc(bme680_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t bme680_free_desc(bme680_t *dev);
+
+/**
+ * @brief   Initialize a BME680 sensor
+ *
+ * The function initializes the sensor device data structure, probes the
+ * sensor, soft resets the sensor, and configures the sensor with the
+ * the following default settings:
+ *
+ * - Oversampling rate for temperature, pressure, humidity is osr_1x
+ * - Filter size for pressure and temperature is iir_size 3
+ * - Heater profile 0 with 320 degree C and 150 ms duration
+ *
+ * The sensor must be connected to an I2C bus.
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t bme680_init_sensor(bme680_t *dev);
+
+/**
+ * @brief   Force one single TPHG measurement
+ *
+ * The function triggers the sensor to start one THPG measurement cycle.
+ * Parameters for the measurement like oversampling rates, IIR filter sizes
+ * and heater profile can be configured before.
+ *
+ * Once the TPHG measurement is started, the user task has to wait for the
+ * results. The duration of the TPHG measurement can be determined with
+ * function *bme680_get_measurement_duration*.
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t bme680_force_measurement(bme680_t *dev);
+
+/**
+ * @brief   Get estimated duration of a TPHG measurement
+ *
+ * The function returns an estimated duration of the TPHG measurement cycle
+ * in RTOS ticks for the current configuration of the sensor.
+ *
+ * This duration is the time required by the sensor for one TPHG measurement
+ * until the results are available. It strongly depends on which measurements
+ * are performed in the THPG measurement cycle and what configuration
+ * parameters were set. It can vary from 1 RTOS (10 ms) tick up to 4500 RTOS
+ * ticks (4.5 seconds).
+ *
+ * If the measurement configuration is not changed, the duration can be
+ * considered as constant.
+ *
+ * @param dev Device descriptor
+ * @param[out] duration Duration of TPHG measurement cycle in ticks or 0 on error
+ * @return `ESP_OK` on success
+ */
+esp_err_t bme680_get_measurement_duration(const bme680_t *dev, uint32_t *duration);
+
+/**
+ * @brief   Get the measurement status
+ *
+ * The function can be used to test whether a measurement that was started
+ * before is still running.
+ *
+ * @param dev Device descriptor
+ * @param[out] busy true if measurement is still running or false otherwise
+ * @return `ESP_OK` on success
+ */
+esp_err_t bme680_is_measuring(bme680_t *dev, bool *busy);
+
+/**
+ * @brief   Get results of a measurement in fixed point representation
+ *
+ * The function returns the results of a TPHG measurement that has been
+ * started before. If the measurement is still running, the function fails
+ * and returns invalid values (see type declaration).
+ *
+ * @param dev Device descriptor
+ * @param[out] results pointer to a data structure that is filled with results
+ * @return `ESP_OK` on success
+ */
+esp_err_t bme680_get_results_fixed(bme680_t *dev, bme680_values_fixed_t *results);
+
+/**
+ * @brief   Get results of a measurement in floating point representation
+ *
+ * The function returns the results of a TPHG measurement that has been
+ * started before. If the measurement is still running, the function fails
+ * and returns invalid values (see type declaration).
+ *
+ * @param dev Device descriptor
+ * @param[out] results pointer to a data structure that is filled with results
+ * @return `ESP_OK` on success
+ */
+esp_err_t bme680_get_results_float(bme680_t *dev, bme680_values_float_t *results);
+
+/**
+ * @brief   Start a measurement, wait and return the results (fixed point)
+ *
+ * This function is a combination of functions above. For convenience it
+ * starts a TPHG measurement using ::bme680_force_measurement(), then it waits
+ * the measurement duration for the results using `vTaskDelay()` and finally it
+ * returns the results using function ::bme680_get_results_fixed().
+ *
+ * Note: Since the calling task is delayed using function `vTaskDelay()`, this
+ * function must not be used when it is called from a software timer callback
+ * function.
+ *
+ * @param dev Device descriptor
+ * @param[out] results pointer to a data structure that is filled with results
+ * @return `ESP_OK` on success
+ */
+esp_err_t bme680_measure_fixed(bme680_t *dev, bme680_values_fixed_t *results);
+
+/**
+ * @brief   Start a measurement, wait and return the results (floating point)
+ *
+ * This function is a combination of functions above. For convenience it
+ * starts a TPHG measurement using ::bme680_force_measurement(), then it waits
+ * the measurement duration for the results using `vTaskDelay` and finally it
+ * returns the results using function ::bme680_get_results_float().
+ *
+ * Note: Since the calling task is delayed using function `vTaskDelay()`, this
+ * function must not be used when it is called from a software timer callback
+ * function.
+ *
+ * @param dev Device descriptor
+ * @param[out] results pointer to a data structure that is filled with results
+ * @return `ESP_OK` on success
+ */
+esp_err_t bme680_measure_float(bme680_t *dev, bme680_values_float_t *results);
+
+/**
+ * @brief   Set the oversampling rates for measurements
+ *
+ * The BME680 sensor allows to define individual oversampling rates for
+ * the measurements of temperature, pressure and humidity. Using an
+ * oversampling rate of *osr*, the resolution of raw sensor data can be
+ * increased by ld(*osr*) bits.
+ *
+ * Possible oversampling rates are 1x (default), 2x, 4x, 8x, 16x, see type
+ * ::bme680_oversampling_rate_t. The default oversampling rate is 1.
+ *
+ * Please note: Use ::BME680_OSR_NONE to skip the corresponding measurement.
+ *
+ * @param dev Device descriptor
+ * @param osr_t oversampling rate for temperature measurements
+ * @param osr_p oversampling rate for pressure measurements
+ * @param osr_h oversampling rate for humidity measurements
+ * @return `ESP_OK` on success
+ */
+esp_err_t bme680_set_oversampling_rates(bme680_t *dev, bme680_oversampling_rate_t osr_t,
+        bme680_oversampling_rate_t osr_p, bme680_oversampling_rate_t osr_h);
+
+/**
+ * @brief   Set the size of the IIR filter
+ *
+ * The sensor integrates an internal IIR filter (low pass filter) to reduce
+ * short-term changes in sensor output values caused by external disturbances.
+ * It effectively reduces the bandwidth of the sensor output values.
+ *
+ * The filter can optionally be used for pressure and temperature data that
+ * are subject to many short-term changes. Using the IIR filter, increases the
+ * resolution of pressure and temperature data to 20 bit. Humidity and gas
+ * inside the sensor does not fluctuate rapidly and does not require such a
+ * low pass filtering.
+ *
+ * The default filter size is 3 (::BME680_IIR_SIZE_3).
+ *
+ * Please note: If the size of the filter is 0, the filter is not used.
+ *
+ * @param dev Device descriptor
+ * @param size IIR filter size
+ * @return `ESP_OK` on success
+ */
+esp_err_t bme680_set_filter_size(bme680_t *dev, bme680_filter_size_t size);
+
+/**
+ * @brief   Set a heater profile for gas measurements
+ *
+ * The sensor integrates a heater for the gas measurement. Parameters for this
+ * heater are defined by so called heater profiles. The sensor supports up to
+ * 10 heater profiles, which are numbered from 0 to 9. Each profile consists of
+ * a temperature set-point (the target temperature) and a heating duration.
+ *
+ * This function sets the parameters for one of the heater profiles 0 ... 9.
+ * To activate the gas measurement with this profile, use function
+ * ::bme680_use_heater_profile(), see below.
+ *
+ * Please note: According to the data sheet, a target temperatures of between
+ * 200 and 400 degrees Celsius are typical and about 20 to 30 ms are necessary
+ * for the heater to reach the desired target temperature.
+ *
+ * @param dev Device descriptor
+ * @param profile heater profile 0 ... 9
+ * @param temperature target temperature in degree Celsius
+ * @param duration heating duration in milliseconds
+ * @return `ESP_OK` on success
+ */
+esp_err_t bme680_set_heater_profile(bme680_t *dev, uint8_t profile, uint16_t temperature, uint16_t duration);
+
+/**
+ * @brief   Activate gas measurement with a given heater profile
+ *
+ * The function activates the gas measurement with one of the heater
+ * profiles 0 ... 9 or deactivates the gas measurement completely when
+ * -1 or ::BME680_HEATER_NOT_USED is used as heater profile.
+ *
+ * Parameters of the activated heater profile have to be set before with
+ * function ::bme680_set_heater_profile() otherwise the function fails.
+ *
+ * If several heater profiles have been defined with function
+ * ::bme680_set_heater_profile(), a sequence of gas measurements with different
+ * heater parameters can be realized by a sequence of activations of different
+ * heater profiles for successive TPHG measurements using this function.
+ *
+ * @param dev Device descriptor
+ * @param profile 0 ... 9 to activate or -1 to deactivate gas measure
+ * @return `ESP_OK` on success
+ */
+esp_err_t bme680_use_heater_profile(bme680_t *dev, int8_t profile);
+
+/**
+ * @brief   Set ambient temperature
+ *
+ * The heater resistance calculation algorithm takes into account the ambient
+ * temperature of the sensor. This function can be used to set this ambient
+ * temperature. Either values determined from the sensor itself or from
+ * another temperature sensor can be used. The default ambient temperature
+ * is 25 degree Celsius.
+ *
+ * @param dev Device descriptor
+ * @param temperature ambient temperature in degree Celsius
+ * @return `ESP_OK` on success
+ */
+esp_err_t bme680_set_ambient_temperature(bme680_t *dev, int16_t temperature);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __BME680_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/bme680/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/bmp180/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,28 @@
+---
+components:
+  - name: bmp180
+    description: |
+      Driver for BMP180 digital pressure sensor
+    group:
+      name: pressure
+    groups:
+      - name: temperature
+    code_owners:
+      - name: UncleRus
+    depends:
+      - name: i2cdev
+      - name: log
+      - name: esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: MIT
+    copyrights:
+      - name: UncleRus
+        year: 2018
+      - name: FrankB
+        year: 2015
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/bmp180/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS bmp180.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/bmp180/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Frank Bargstedt
+Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/bmp180/bmp180.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,263 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 Frank Bargstedt
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file bmp180.c
+ *
+ * ESP-IDF driver for BMP180 digital pressure sensor
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2015 Frank Bargstedt\n
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+#include "bmp180.h"
+#include <inttypes.h>
+#include <esp_err.h>
+#include <esp_log.h>
+#include <ets_sys.h>
+#include <esp_idf_lib_helpers.h>
+
+#define I2C_FREQ_HZ 1000000 // Max 1MHz for esp-idf
+
+static const char *TAG = "bmp180";
+
+#define BMP180_RX_QUEUE_SIZE      10
+#define BMP180_TASK_PRIORITY      9
+
+#define BMP180_VERSION_REG        0xD0
+#define BMP180_CONTROL_REG        0xF4
+#define BMP180_RESET_REG          0xE0
+#define BMP180_OUT_MSB_REG        0xF6
+#define BMP180_OUT_LSB_REG        0xF7
+#define BMP180_OUT_XLSB_REG       0xF8
+
+#define BMP180_CALIBRATION_REG    0xAA
+
+// Values for BMP180_CONTROL_REG
+#define BMP180_MEASURE_TEMP       0x2E
+#define BMP180_MEASURE_PRESS      0x34
+
+// CHIP ID stored in BMP180_VERSION_REG
+#define BMP180_CHIP_ID            0x55
+
+// Reset value for BMP180_RESET_REG
+#define BMP180_RESET_VALUE        0xB6
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+static esp_err_t bmp180_read_reg_16(i2c_dev_t *dev, uint8_t reg, int16_t *r)
+{
+    uint8_t d[] = { 0, 0 };
+
+    CHECK(i2c_dev_read_reg(dev, reg, d, 2));
+    *r = ((int16_t)d[0] << 8) | (d[1]);
+
+    return ESP_OK;
+}
+
+static inline esp_err_t bmp180_start_measurement(i2c_dev_t *dev, uint8_t cmd)
+{
+    return i2c_dev_write_reg(dev, BMP180_CONTROL_REG, &cmd, 1);
+}
+
+static esp_err_t bmp180_get_uncompensated_temperature(i2c_dev_t *dev, int32_t *ut)
+{
+    // Write Start Code into reg 0xF4.
+    CHECK(bmp180_start_measurement(dev, BMP180_MEASURE_TEMP));
+
+    // Wait 5ms, datasheet states 4.5ms
+    ets_delay_us(5000);
+
+    int16_t v;
+    CHECK(bmp180_read_reg_16(dev, BMP180_OUT_MSB_REG, &v));
+    *ut = v;
+    return ESP_OK;
+}
+
+static esp_err_t bmp180_get_uncompensated_pressure(i2c_dev_t *dev, bmp180_mode_t oss, uint32_t *up)
+{
+    uint16_t us;
+
+    // Limit oss and set the measurement wait time. The datasheet
+    // states 4.5, 7.5, 13.5, 25.5ms for oss 0 to 3.
+    switch (oss)
+    {
+        case BMP180_MODE_ULTRA_LOW_POWER:                 us = 5000; break;
+        case BMP180_MODE_STANDARD:                        us = 8000; break;
+        case BMP180_MODE_HIGH_RESOLUTION:                 us = 14000; break;
+        default: oss = BMP180_MODE_ULTRA_HIGH_RESOLUTION; us = 26000; break;
+    }
+
+    // Write Start Code into reg 0xF4
+    CHECK(bmp180_start_measurement(dev, BMP180_MEASURE_PRESS | (oss << 6)));
+
+    ets_delay_us(us);
+
+    uint8_t d[] = { 0, 0, 0 };
+    uint8_t reg = BMP180_OUT_MSB_REG;
+    CHECK(i2c_dev_read_reg(dev, reg, d, 3));
+
+    uint32_t r = ((uint32_t)d[0] << 16) | ((uint32_t)d[1] << 8) | d[2];
+    r >>= 8 - oss;
+    *up = r;
+
+    return ESP_OK;
+}
+
+esp_err_t bmp180_init_desc(bmp180_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+
+    dev->i2c_dev.port = port;
+    dev->i2c_dev.addr = BMP180_DEVICE_ADDRESS;
+    dev->i2c_dev.cfg.sda_io_num = sda_gpio;
+    dev->i2c_dev.cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(&dev->i2c_dev);
+}
+
+esp_err_t bmp180_free_desc(bmp180_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(&dev->i2c_dev);
+}
+
+esp_err_t bmp180_init(bmp180_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    uint8_t id;
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, BMP180_VERSION_REG, &id, 1));
+    if (id != BMP180_CHIP_ID)
+    {
+        I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+        ESP_LOGE(TAG, "Invalid device ID: 0x%02x", id);
+        return ESP_ERR_NOT_FOUND;
+    }
+
+    I2C_DEV_CHECK(&dev->i2c_dev, bmp180_read_reg_16(&dev->i2c_dev, BMP180_CALIBRATION_REG + 0, &dev->AC1));
+    I2C_DEV_CHECK(&dev->i2c_dev, bmp180_read_reg_16(&dev->i2c_dev, BMP180_CALIBRATION_REG + 2, &dev->AC2));
+    I2C_DEV_CHECK(&dev->i2c_dev, bmp180_read_reg_16(&dev->i2c_dev, BMP180_CALIBRATION_REG + 4, &dev->AC3));
+    I2C_DEV_CHECK(&dev->i2c_dev, bmp180_read_reg_16(&dev->i2c_dev, BMP180_CALIBRATION_REG + 6, (int16_t *)&dev->AC4));
+    I2C_DEV_CHECK(&dev->i2c_dev, bmp180_read_reg_16(&dev->i2c_dev, BMP180_CALIBRATION_REG + 8, (int16_t *)&dev->AC5));
+    I2C_DEV_CHECK(&dev->i2c_dev, bmp180_read_reg_16(&dev->i2c_dev, BMP180_CALIBRATION_REG + 10, (int16_t *)&dev->AC6));
+    I2C_DEV_CHECK(&dev->i2c_dev, bmp180_read_reg_16(&dev->i2c_dev, BMP180_CALIBRATION_REG + 12, &dev->B1));
+    I2C_DEV_CHECK(&dev->i2c_dev, bmp180_read_reg_16(&dev->i2c_dev, BMP180_CALIBRATION_REG + 14, &dev->B2));
+    I2C_DEV_CHECK(&dev->i2c_dev, bmp180_read_reg_16(&dev->i2c_dev, BMP180_CALIBRATION_REG + 16, &dev->MB));
+    I2C_DEV_CHECK(&dev->i2c_dev, bmp180_read_reg_16(&dev->i2c_dev, BMP180_CALIBRATION_REG + 18, &dev->MC));
+    I2C_DEV_CHECK(&dev->i2c_dev, bmp180_read_reg_16(&dev->i2c_dev, BMP180_CALIBRATION_REG + 20, &dev->MD));
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    ESP_LOGD(TAG, "AC1:=%d AC2:=%d AC3:=%d AC4:=%u AC5:=%u AC6:=%u", dev->AC1, dev->AC2, dev->AC3, dev->AC4, dev->AC5, dev->AC6);
+    ESP_LOGD(TAG, "B1:=%d B2:=%d", dev->B1, dev->B2);
+    ESP_LOGD(TAG, "MB:=%d MC:=%d MD:=%d", dev->MB, dev->MC, dev->MD);
+
+    if (dev->AC1== 0  || dev->AC2 == 0 || dev->AC3 == 0 ||
+        dev->AC4 == 0 || dev->AC5 == 0 || dev->AC6 == 0 ||
+        dev->B1  == 0 || dev->B2  == 0 ||
+        dev->MB  == 0 || dev->MC  == 0 || dev->MD  == 0)
+    {
+        return ESP_ERR_INVALID_RESPONSE;
+    }
+
+    return ESP_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t bmp180_measure(bmp180_dev_t *dev, float *temperature, uint32_t *pressure, bmp180_mode_t oss)
+{
+    CHECK_ARG(dev && temperature && pressure);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    // Temperature is always needed, also required for pressure only.
+    //
+    // Calculation taken from BMP180 Datasheet
+    int32_t T, P;
+    int32_t UT, X1, X2, B5;
+    UT = 0;
+    I2C_DEV_CHECK(&dev->i2c_dev, bmp180_get_uncompensated_temperature(&dev->i2c_dev, &UT));
+
+    X1 = ((UT - (int32_t)dev->AC6) * (int32_t)dev->AC5) >> 15;
+    X2 = ((int32_t)dev->MC << 11) / (X1 + (int32_t)dev->MD);
+    B5 = X1 + X2;
+    T = (B5 + 8) >> 4;
+
+    if (temperature)
+        *temperature = T / 10.0;
+
+    ESP_LOGD(TAG, "T:= %" PRIi32 ".%d", T / 10, abs(T % 10));
+
+    if (pressure)
+    {
+        int32_t X3, B3, B6;
+        uint32_t B4, B7, UP = 0;
+
+        I2C_DEV_CHECK(&dev->i2c_dev, bmp180_get_uncompensated_pressure(&dev->i2c_dev, oss, &UP));
+
+        // Calculation taken from BMP180 Datasheet
+        B6 = B5 - 4000;
+        X1 = ((int32_t)dev->B2 * ((B6 * B6) >> 12)) >> 11;
+        X2 = ((int32_t)dev->AC2 * B6) >> 11;
+        X3 = X1 + X2;
+
+        B3 = ((((int32_t)dev->AC1 * 4 + X3) << oss) + 2) >> 2;
+        X1 = ((int32_t)dev->AC3 * B6) >> 13;
+        X2 = ((int32_t)dev->B1 * ((B6 * B6) >> 12)) >> 16;
+        X3 = ((X1 + X2) + 2) >> 2;
+        B4 = ((uint32_t)dev->AC4 * (uint32_t)(X3 + 32768)) >> 15;
+        B7 = ((uint32_t)UP - B3) * (uint32_t)(50000UL >> oss);
+
+        if (B7 < 0x80000000UL)
+            P = (B7 * 2) / B4;
+        else
+            P = (B7 / B4) * 2;
+
+        X1 = (P >> 8) * (P >> 8);
+        X1 = (X1 * 3038) >> 16;
+        X2 = (-7357 * P) >> 16;
+        P = P + ((X1 + X2 + (int32_t)3791) >> 4);
+
+        *pressure = P;
+
+        ESP_LOGD(TAG, "P:= %" PRIi32, P);
+    }
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/bmp180/bmp180.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,131 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 Frank Bargstedt
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file bmp180.h
+ * @defgroup bmp180 bmp180
+ * @{
+ *
+ * ESP-IDF driver for BMP180 digital pressure sensor
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2015 Frank Bargstedt\n
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+#ifndef __BMP180_H__
+#define __BMP180_H__
+
+#include <stdbool.h>
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#define BMP180_DEVICE_ADDRESS 0x77 //!< I2C address
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * BMP180 device descriptor
+ */
+typedef struct
+{
+    i2c_dev_t i2c_dev;
+
+    int16_t  AC1;
+    int16_t  AC2;
+    int16_t  AC3;
+    uint16_t AC4;
+    uint16_t AC5;
+    uint16_t AC6;
+
+    int16_t  B1;
+    int16_t  B2;
+
+    int16_t  MB;
+    int16_t  MC;
+    int16_t  MD;
+} bmp180_dev_t;
+
+/**
+ * Hardware accuracy mode.
+ * See Table 3 of the datasheet
+ */
+typedef enum
+{
+    BMP180_MODE_ULTRA_LOW_POWER = 0,  //!< 1 sample, 4.5 ms
+    BMP180_MODE_STANDARD,             //!< 2 samples, 7.5 ms
+    BMP180_MODE_HIGH_RESOLUTION,      //!< 4 samples, 13.5 ms
+    BMP180_MODE_ULTRA_HIGH_RESOLUTION //!< 8 samples, 25.5 ms
+} bmp180_mode_t;
+
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev Device descriptor
+ * @param port I2C port number
+ * @param sda_gpio GPIO pin number for SDA
+ * @param scl_gpio GPIO pin number for SCL
+ * @return `ESP_OK` on success
+ */
+esp_err_t bmp180_init_desc(bmp180_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Pointer to BMP180 device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t bmp180_free_desc(bmp180_dev_t *dev);
+
+/**
+ * @brief Initialize device
+ *
+ * @param dev Pointer to BMP180 device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t bmp180_init(bmp180_dev_t *dev);
+
+/**
+ * @brief Measure temperature and pressure
+ *
+ * @param dev Pointer to BMP180 device descriptor
+ * @param[out] temperature Temperature in degrees Celsius
+ * @param[out] pressure Pressure in Pa
+ * @param oss Measurement mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t bmp180_measure(bmp180_dev_t *dev, float *temperature, uint32_t *pressure, bmp180_mode_t oss);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __BMP180_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/bmp180/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/bmp280/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,28 @@
+---
+components:
+  - name: bmp280
+    description: |
+      Driver for BMP280/BME280 digital pressure sensor
+    group:
+      name: pressure
+    groups:
+      - name: temperature
+    code_owners:
+      - name: UncleRus
+    depends:
+      - name: i2cdev
+      - name: log
+      - name: esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: MIT
+    copyrights:
+      - name: UncleRus
+        year: 2018
+      - name: sheinz
+        year: 2016
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/bmp280/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS bmp280.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/bmp280/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 sheinz (https://github.com/sheinz)
+Copyright (c) 2018 Ruslan V. Uss (https://github.com/UncleRus)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/bmp280/bmp280.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,411 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2016 sheinz <https://github.com/sheinz>
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file bmp280.c
+ *
+ * ESP-IDF driver for BMP280/BME280 digital pressure sensor
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2016 sheinz <https://github.com/sheinz>\n
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+
+#include "bmp280.h"
+#include <inttypes.h>
+#include <esp_log.h>
+#include <esp_idf_lib_helpers.h>
+
+#define I2C_FREQ_HZ 1000000 // Max 1MHz for esp-idf
+
+static const char *TAG = "bmp280";
+
+/**
+ * BMP280 registers
+ */
+#define BMP280_REG_TEMP_XLSB   0xFC /* bits: 7-4 */
+#define BMP280_REG_TEMP_LSB    0xFB
+#define BMP280_REG_TEMP_MSB    0xFA
+#define BMP280_REG_TEMP        (BMP280_REG_TEMP_MSB)
+#define BMP280_REG_PRESS_XLSB  0xF9 /* bits: 7-4 */
+#define BMP280_REG_PRESS_LSB   0xF8
+#define BMP280_REG_PRESS_MSB   0xF7
+#define BMP280_REG_PRESSURE    (BMP280_REG_PRESS_MSB)
+#define BMP280_REG_CONFIG      0xF5 /* bits: 7-5 t_sb; 4-2 filter; 0 spi3w_en */
+#define BMP280_REG_CTRL        0xF4 /* bits: 7-5 osrs_t; 4-2 osrs_p; 1-0 mode */
+#define BMP280_REG_STATUS      0xF3 /* bits: 3 measuring; 0 im_update */
+#define BMP280_REG_CTRL_HUM    0xF2 /* bits: 2-0 osrs_h; */
+#define BMP280_REG_RESET       0xE0
+#define BMP280_REG_ID          0xD0
+#define BMP280_REG_CALIB       0x88
+#define BMP280_REG_HUM_CALIB   0x88
+
+#define BMP280_RESET_VALUE     0xB6
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+#define CHECK_LOGE(dev, x, msg, ...) do { \
+        esp_err_t __; \
+        if ((__ = x) != ESP_OK) { \
+            I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); \
+            ESP_LOGE(TAG, msg, ## __VA_ARGS__); \
+            return __; \
+        } \
+    } while (0)
+
+static esp_err_t read_register16(i2c_dev_t *dev, uint8_t reg, uint16_t *r)
+{
+    uint8_t d[] = { 0, 0 };
+
+    CHECK(i2c_dev_read_reg(dev, reg, d, 2));
+    *r = d[0] | (d[1] << 8);
+
+    return ESP_OK;
+}
+
+inline static esp_err_t write_register8(i2c_dev_t *dev, uint8_t addr, uint8_t value)
+{
+    return i2c_dev_write_reg(dev, addr, &value, 1);
+}
+
+static esp_err_t read_calibration_data(bmp280_t *dev)
+{
+    CHECK(read_register16(&dev->i2c_dev, 0x88, &dev->dig_T1));
+    CHECK(read_register16(&dev->i2c_dev, 0x8a, (uint16_t *)&dev->dig_T2));
+    CHECK(read_register16(&dev->i2c_dev, 0x8c, (uint16_t *)&dev->dig_T3));
+    CHECK(read_register16(&dev->i2c_dev, 0x8e, &dev->dig_P1));
+    CHECK(read_register16(&dev->i2c_dev, 0x90, (uint16_t *)&dev->dig_P2));
+    CHECK(read_register16(&dev->i2c_dev, 0x92, (uint16_t *)&dev->dig_P3));
+    CHECK(read_register16(&dev->i2c_dev, 0x94, (uint16_t *)&dev->dig_P4));
+    CHECK(read_register16(&dev->i2c_dev, 0x96, (uint16_t *)&dev->dig_P5));
+    CHECK(read_register16(&dev->i2c_dev, 0x98, (uint16_t *)&dev->dig_P6));
+    CHECK(read_register16(&dev->i2c_dev, 0x9a, (uint16_t *)&dev->dig_P7));
+    CHECK(read_register16(&dev->i2c_dev, 0x9c, (uint16_t *)&dev->dig_P8));
+    CHECK(read_register16(&dev->i2c_dev, 0x9e, (uint16_t *)&dev->dig_P9));
+
+    ESP_LOGD(TAG, "Calibration data received:");
+    ESP_LOGD(TAG, "dig_T1=%d", dev->dig_T1);
+    ESP_LOGD(TAG, "dig_T2=%d", dev->dig_T2);
+    ESP_LOGD(TAG, "dig_T3=%d", dev->dig_T3);
+    ESP_LOGD(TAG, "dig_P1=%d", dev->dig_P1);
+    ESP_LOGD(TAG, "dig_P2=%d", dev->dig_P2);
+    ESP_LOGD(TAG, "dig_P3=%d", dev->dig_P3);
+    ESP_LOGD(TAG, "dig_P4=%d", dev->dig_P4);
+    ESP_LOGD(TAG, "dig_P5=%d", dev->dig_P5);
+    ESP_LOGD(TAG, "dig_P6=%d", dev->dig_P6);
+    ESP_LOGD(TAG, "dig_P7=%d", dev->dig_P7);
+    ESP_LOGD(TAG, "dig_P8=%d", dev->dig_P8);
+    ESP_LOGD(TAG, "dig_P9=%d", dev->dig_P9);
+
+    return ESP_OK;
+}
+
+static esp_err_t read_hum_calibration_data(bmp280_t *dev)
+{
+    uint16_t h4, h5;
+
+    CHECK(i2c_dev_read_reg(&dev->i2c_dev, 0xa1, &dev->dig_H1, 1));
+    CHECK(read_register16(&dev->i2c_dev, 0xe1, (uint16_t *)&dev->dig_H2));
+    CHECK(i2c_dev_read_reg(&dev->i2c_dev, 0xe3, &dev->dig_H3, 1));
+    CHECK(read_register16(&dev->i2c_dev, 0xe4, &h4));
+    CHECK(read_register16(&dev->i2c_dev, 0xe5, &h5));
+    CHECK(i2c_dev_read_reg(&dev->i2c_dev, 0xe7, (uint8_t *)&dev->dig_H6, 1));
+
+    dev->dig_H4 = (h4 & 0x00ff) << 4 | (h4 & 0x0f00) >> 8;
+    dev->dig_H5 = h5 >> 4;
+    ESP_LOGD(TAG, "Calibration data received:");
+    ESP_LOGD(TAG, "dig_H1=%d", dev->dig_H1);
+    ESP_LOGD(TAG, "dig_H2=%d", dev->dig_H2);
+    ESP_LOGD(TAG, "dig_H3=%d", dev->dig_H3);
+    ESP_LOGD(TAG, "dig_H4=%d", dev->dig_H4);
+    ESP_LOGD(TAG, "dig_H5=%d", dev->dig_H5);
+    ESP_LOGD(TAG, "dig_H6=%d", dev->dig_H6);
+
+    return ESP_OK;
+}
+
+esp_err_t bmp280_init_desc(bmp280_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+
+    if (addr != BMP280_I2C_ADDRESS_0 && addr != BMP280_I2C_ADDRESS_1)
+    {
+        ESP_LOGE(TAG, "Invalid I2C address");
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    dev->i2c_dev.port = port;
+    dev->i2c_dev.addr = addr;
+    dev->i2c_dev.cfg.sda_io_num = sda_gpio;
+    dev->i2c_dev.cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(&dev->i2c_dev);
+}
+
+esp_err_t bmp280_free_desc(bmp280_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(&dev->i2c_dev);
+}
+
+esp_err_t bmp280_init_default_params(bmp280_params_t *params)
+{
+    CHECK_ARG(params);
+
+    params->mode = BMP280_MODE_NORMAL;
+    params->filter = BMP280_FILTER_OFF;
+    params->oversampling_pressure = BMP280_STANDARD;
+    params->oversampling_temperature = BMP280_STANDARD;
+    params->oversampling_humidity = BMP280_STANDARD;
+    params->standby = BMP280_STANDBY_250;
+
+    return ESP_OK;
+}
+
+esp_err_t bmp280_init(bmp280_t *dev, bmp280_params_t *params)
+{
+    CHECK_ARG(dev && params);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    CHECK_LOGE(dev, i2c_dev_read_reg(&dev->i2c_dev, BMP280_REG_ID, &dev->id, 1), "Sensor not found");
+
+    if (dev->id != BMP280_CHIP_ID && dev->id != BME280_CHIP_ID)
+    {
+        CHECK_LOGE(dev, ESP_ERR_INVALID_VERSION,
+                "Invalid chip ID: expected: 0x%x (BME280) or 0x%x (BMP280) got: 0x%x",
+                BME280_CHIP_ID, BMP280_CHIP_ID, dev->id);
+    }
+
+    // Soft reset.
+    CHECK_LOGE(dev, write_register8(&dev->i2c_dev, BMP280_REG_RESET, BMP280_RESET_VALUE), "Failed to reset sensor");
+
+    // Wait until finished copying over the NVP data.
+    while (1)
+    {
+        uint8_t status;
+        if (!i2c_dev_read_reg(&dev->i2c_dev, BMP280_REG_STATUS, &status, 1) && (status & 1) == 0)
+            break;
+    }
+
+    CHECK_LOGE(dev, read_calibration_data(dev), "Failed to read calibration data");
+
+    if (dev->id == BME280_CHIP_ID)
+    {
+        CHECK_LOGE(dev, read_hum_calibration_data(dev), "Failed to read humidity calibration data");
+    }
+
+    uint8_t config = (params->standby << 5) | (params->filter << 2);
+    ESP_LOGD(TAG, "Writing config reg=%x", config);
+
+    CHECK_LOGE(dev, write_register8(&dev->i2c_dev, BMP280_REG_CONFIG, config), "Failed to configure sensor");
+
+    if (params->mode == BMP280_MODE_FORCED)
+    {
+        params->mode = BMP280_MODE_SLEEP;  // initial mode for forced is sleep
+    }
+
+    uint8_t ctrl = (params->oversampling_temperature << 5) | (params->oversampling_pressure << 2) | (params->mode);
+
+    if (dev->id == BME280_CHIP_ID)
+    {
+        // Write crtl hum reg first, only active after write to BMP280_REG_CTRL.
+        uint8_t ctrl_hum = params->oversampling_humidity;
+        ESP_LOGD(TAG, "Writing ctrl hum reg=%x", ctrl_hum);
+        CHECK_LOGE(dev, write_register8(&dev->i2c_dev, BMP280_REG_CTRL_HUM, ctrl_hum), "Failed to control sensor");
+    }
+
+    ESP_LOGD(TAG, "Writing ctrl reg=%x", ctrl);
+    CHECK_LOGE(dev, write_register8(&dev->i2c_dev, BMP280_REG_CTRL, ctrl), "Failed to control sensor");
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t bmp280_force_measurement(bmp280_t *dev)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    uint8_t ctrl;
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, BMP280_REG_CTRL, &ctrl, 1));
+    ctrl &= ~0b11;  // clear two lower bits
+    ctrl |= BMP280_MODE_FORCED;
+    ESP_LOGD(TAG, "Writing ctrl reg=%x", ctrl);
+    CHECK_LOGE(dev, write_register8(&dev->i2c_dev, BMP280_REG_CTRL, ctrl), "Failed to start forced mode");
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t bmp280_is_measuring(bmp280_t *dev, bool *busy)
+{
+    CHECK_ARG(dev && busy);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    const uint8_t regs[2] = { BMP280_REG_STATUS, BMP280_REG_CTRL };
+    uint8_t status[2];
+    CHECK_LOGE(dev, i2c_dev_read(&dev->i2c_dev, regs, 2, status, 2), "Failed to read status registers");
+
+    // Check mode - FORCED means BM280 is busy (it switches to SLEEP mode when finished)
+    // Additionally, check 'measuring' bit in status register
+    *busy = ((status[1] & 0b11) == BMP280_MODE_FORCED) || (status[0] & (1 << 3));
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+/**
+ * Compensation algorithm is taken from BMP280 datasheet.
+ *
+ * Return value is in degrees Celsius.
+ */
+static inline int32_t compensate_temperature(bmp280_t *dev, int32_t adc_temp, int32_t *fine_temp)
+{
+    int32_t var1, var2;
+
+    var1 = ((((adc_temp >> 3) - ((int32_t)dev->dig_T1 << 1))) * (int32_t)dev->dig_T2) >> 11;
+    var2 = (((((adc_temp >> 4) - (int32_t)dev->dig_T1) * ((adc_temp >> 4) - (int32_t)dev->dig_T1)) >> 12) * (int32_t)dev->dig_T3) >> 14;
+
+    *fine_temp = var1 + var2;
+    return (*fine_temp * 5 + 128) >> 8;
+}
+
+/**
+ * Compensation algorithm is taken from BMP280 datasheet.
+ *
+ * Return value is in Pa, 24 integer bits and 8 fractional bits.
+ */
+static inline uint32_t compensate_pressure(bmp280_t *dev, int32_t adc_press, int32_t fine_temp)
+{
+    int64_t var1, var2, p;
+
+    var1 = (int64_t)fine_temp - 128000;
+    var2 = var1 * var1 * (int64_t)dev->dig_P6;
+    var2 = var2 + ((var1 * (int64_t)dev->dig_P5) << 17);
+    var2 = var2 + (((int64_t)dev->dig_P4) << 35);
+    var1 = ((var1 * var1 * (int64_t)dev->dig_P3) >> 8) + ((var1 * (int64_t)dev->dig_P2) << 12);
+    var1 = (((int64_t)1 << 47) + var1) * ((int64_t)dev->dig_P1) >> 33;
+
+    if (var1 == 0)
+    {
+        return 0;  // avoid exception caused by division by zero
+    }
+
+    p = 1048576 - adc_press;
+    p = (((p << 31) - var2) * 3125) / var1;
+    var1 = ((int64_t)dev->dig_P9 * (p >> 13) * (p >> 13)) >> 25;
+    var2 = ((int64_t)dev->dig_P8 * p) >> 19;
+
+    p = ((p + var1 + var2) >> 8) + ((int64_t)dev->dig_P7 << 4);
+    return p;
+}
+
+/**
+ * Compensation algorithm is taken from BME280 datasheet.
+ *
+ * Return value is in Pa, 24 integer bits and 8 fractional bits.
+ */
+static inline uint32_t compensate_humidity(bmp280_t *dev, int32_t adc_hum, int32_t fine_temp)
+{
+    int32_t v_x1_u32r;
+
+    v_x1_u32r = fine_temp - (int32_t)76800;
+    v_x1_u32r = ((((adc_hum << 14) - ((int32_t)dev->dig_H4 << 20) - ((int32_t)dev->dig_H5 * v_x1_u32r)) + (int32_t)16384) >> 15)
+            * (((((((v_x1_u32r * (int32_t)dev->dig_H6) >> 10) * (((v_x1_u32r * (int32_t)dev->dig_H3) >> 11) + (int32_t)32768)) >> 10)
+                    + (int32_t)2097152) * (int32_t)dev->dig_H2 + 8192) >> 14);
+    v_x1_u32r = v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * (int32_t)dev->dig_H1) >> 4);
+    v_x1_u32r = v_x1_u32r < 0 ? 0 : v_x1_u32r;
+    v_x1_u32r = v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r;
+    return v_x1_u32r >> 12;
+}
+
+esp_err_t bmp280_read_fixed(bmp280_t *dev, int32_t *temperature, uint32_t *pressure, uint32_t *humidity)
+{
+    CHECK_ARG(dev && temperature && pressure);
+
+    int32_t adc_pressure;
+    int32_t adc_temp;
+    uint8_t data[8];
+
+    // Only the BME280 supports reading the humidity.
+    if (dev->id != BME280_CHIP_ID)
+    {
+        if (humidity)
+            *humidity = 0;
+        humidity = NULL;
+    }
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    // Need to read in one sequence to ensure they match.
+    size_t size = humidity ? 8 : 6;
+    CHECK_LOGE(dev, i2c_dev_read_reg(&dev->i2c_dev, 0xf7, data, size), "Failed to read data");
+
+    adc_pressure = data[0] << 12 | data[1] << 4 | data[2] >> 4;
+    adc_temp = data[3] << 12 | data[4] << 4 | data[5] >> 4;
+    ESP_LOGD(TAG, "ADC temperature: %" PRIi32, adc_temp);
+    ESP_LOGD(TAG, "ADC pressure: %" PRIi32, adc_pressure);
+
+    int32_t fine_temp;
+    *temperature = compensate_temperature(dev, adc_temp, &fine_temp);
+    *pressure = compensate_pressure(dev, adc_pressure, fine_temp);
+
+    if (humidity)
+    {
+        int32_t adc_humidity = data[6] << 8 | data[7];
+        ESP_LOGD(TAG, "ADC humidity: %" PRIi32, adc_humidity);
+        *humidity = compensate_humidity(dev, adc_humidity, fine_temp);
+    }
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t bmp280_read_float(bmp280_t *dev, float *temperature, float *pressure, float *humidity)
+{
+    int32_t fixed_temperature;
+    uint32_t fixed_pressure;
+    uint32_t fixed_humidity;
+    CHECK(bmp280_read_fixed(dev, &fixed_temperature, &fixed_pressure, humidity ? &fixed_humidity : NULL));
+    *temperature = (float)fixed_temperature / 100;
+    *pressure = (float)fixed_pressure / 256;
+    if (humidity)
+        *humidity = (float)fixed_humidity / 1024;
+
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/bmp280/bmp280.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,250 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2016 sheinz <https://github.com/sheinz>
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file bmp280.h
+ * @defgroup bmp280 bmp280
+ * @{
+ *
+ * ESP-IDF driver for BMP280/BME280 digital pressure sensor
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2016 sheinz <https://github.com/sheinz>\n
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+#ifndef __BMP280_H__
+#define __BMP280_H__
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <esp_err.h>
+#include <i2cdev.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define BMP280_I2C_ADDRESS_0  0x76 //!< I2C address when SDO pin is low
+#define BMP280_I2C_ADDRESS_1  0x77 //!< I2C address when SDO pin is high
+
+#define BMP280_CHIP_ID  0x58 //!< BMP280 has chip-id 0x58
+#define BME280_CHIP_ID  0x60 //!< BME280 has chip-id 0x60
+
+/**
+ * Mode of BMP280 module operation.
+ */
+typedef enum {
+    BMP280_MODE_SLEEP = 0,  //!< Sleep mode
+    BMP280_MODE_FORCED = 1, //!< Measurement is initiated by user
+    BMP280_MODE_NORMAL = 3  //!< Continues measurement
+} BMP280_Mode;
+
+typedef enum {
+    BMP280_FILTER_OFF = 0,
+    BMP280_FILTER_2 = 1,
+    BMP280_FILTER_4 = 2,
+    BMP280_FILTER_8 = 3,
+    BMP280_FILTER_16 = 4
+} BMP280_Filter;
+
+/**
+ * Pressure oversampling settings
+ */
+typedef enum {
+    BMP280_SKIPPED = 0,          //!< no measurement
+    BMP280_ULTRA_LOW_POWER = 1,  //!< oversampling x1
+    BMP280_LOW_POWER = 2,        //!< oversampling x2
+    BMP280_STANDARD = 3,         //!< oversampling x4
+    BMP280_HIGH_RES = 4,         //!< oversampling x8
+    BMP280_ULTRA_HIGH_RES = 5    //!< oversampling x16
+} BMP280_Oversampling;
+
+/**
+ * Stand by time between measurements in normal mode
+ */
+typedef enum {
+    BMP280_STANDBY_05 = 0,      //!< stand by time 0.5ms
+    BMP280_STANDBY_62 = 1,      //!< stand by time 62.5ms
+    BMP280_STANDBY_125 = 2,     //!< stand by time 125ms
+    BMP280_STANDBY_250 = 3,     //!< stand by time 250ms
+    BMP280_STANDBY_500 = 4,     //!< stand by time 500ms
+    BMP280_STANDBY_1000 = 5,    //!< stand by time 1s
+    BMP280_STANDBY_2000 = 6,    //!< stand by time 2s BMP280, 10ms BME280
+    BMP280_STANDBY_4000 = 7,    //!< stand by time 4s BMP280, 20ms BME280
+} BMP280_StandbyTime;
+
+/**
+ * Configuration parameters for BMP280 module.
+ * Use function ::bmp280_init_default_params() to use default configuration.
+ */
+typedef struct {
+    BMP280_Mode mode;
+    BMP280_Filter filter;
+    BMP280_Oversampling oversampling_pressure;
+    BMP280_Oversampling oversampling_temperature;
+    BMP280_Oversampling oversampling_humidity;
+    BMP280_StandbyTime standby;
+} bmp280_params_t;
+
+/**
+ * Device descriptor
+ */
+typedef struct {
+    uint16_t dig_T1;
+    int16_t  dig_T2;
+    int16_t  dig_T3;
+    uint16_t dig_P1;
+    int16_t  dig_P2;
+    int16_t  dig_P3;
+    int16_t  dig_P4;
+    int16_t  dig_P5;
+    int16_t  dig_P6;
+    int16_t  dig_P7;
+    int16_t  dig_P8;
+    int16_t  dig_P9;
+
+    /* Humidity compensation for BME280 */
+    uint8_t  dig_H1;
+    int16_t  dig_H2;
+    uint8_t  dig_H3;
+    int16_t  dig_H4;
+    int16_t  dig_H5;
+    int8_t   dig_H6;
+
+    i2c_dev_t i2c_dev;  //!< I2C device descriptor
+    uint8_t   id;       //!< Chip ID
+} bmp280_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev Device descriptor
+ * @param addr BMP280 address
+ * @param port I2C port number
+ * @param sda_gpio GPIO pin for SDA
+ * @param scl_gpio GPIO pin for SCL
+ * @return `ESP_OK` on success
+ */
+esp_err_t bmp280_init_desc(bmp280_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t bmp280_free_desc(bmp280_t *dev);
+
+/**
+ * @brief Initialize default parameters
+ *
+ * Default configuration:
+ *
+ *  - mode: NORMAL
+ *  - filter: OFF
+ *  - oversampling: x4
+ *  - standby time: 250ms
+ *
+ * @param[out] params Default parameters
+ * @return `ESP_OK` on success
+ */
+esp_err_t bmp280_init_default_params(bmp280_params_t *params);
+
+/**
+ * @brief Initialize BMP280 module
+ *
+ * Probes for the device, soft resets the device, reads the calibration
+ * constants, and configures the device using the supplied parameters.
+ *
+ * This may be called again to soft reset the device and initialize it again.
+ *
+ * @param dev Device descriptor
+ * @param params Parameters
+ * @return `ESP_OK` on success
+ */
+esp_err_t bmp280_init(bmp280_t *dev, bmp280_params_t *params);
+
+/**
+ * @brief Start measurement in forced mode
+ *
+ * The module remains in forced mode after this call.
+ * Do not call this method in normal mode.
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t bmp280_force_measurement(bmp280_t *dev);
+
+/**
+ * @brief Check if BMP280 is busy
+ *
+ * @param dev Device descriptor
+ * @param[out] busy true if BMP280 measures temperature/pressure
+ * @return `ESP_OK` on success
+ */
+esp_err_t bmp280_is_measuring(bmp280_t *dev, bool *busy);
+
+/**
+ * @brief Read raw compensated temperature and pressure data
+ *
+ * Temperature in degrees Celsius times 100.
+ *
+ * Pressure in Pascals in fixed point 24 bit integer 8 bit fraction format.
+ *
+ * Humidity is optional and only read for the BME280, in percent relative
+ * humidity as a fixed point 22 bit integer and 10 bit fraction format.
+ *
+ * @param dev Device descriptor
+ * @param[out] temperature Temperature, deg.C * 100
+ * @param[out] pressure Pressure
+ * @param[out] humidity Humidity, optional
+ * @return `ESP_OK` on success
+ */
+esp_err_t bmp280_read_fixed(bmp280_t *dev, int32_t *temperature,
+                            uint32_t *pressure, uint32_t *humidity);
+
+/**
+ * @brief Read compensated temperature and pressure data
+ *
+ * Humidity is optional and only read for the BME280.
+ *
+ * @param dev Device descriptor
+ * @param[out] temperature Temperature, deg.C
+ * @param[out] pressure Pressure, Pascal
+ * @param[out] humidity Relative humidity, percents (optional)
+ * @return `ESP_OK` on success
+ */
+esp_err_t bmp280_read_float(bmp280_t *dev, float *temperature,
+                            float *pressure, float *humidity);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif  // __BMP280_H__
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/bmp280/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/button/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,22 @@
+---
+components:
+  - name: button
+    description: |
+      HW timer-based driver for GPIO buttons
+    group: input
+    groups: []
+    code_owners:
+      - name: UncleRus
+    depends:
+      - name: driver
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: MIT
+    copyrights:
+      - name: UncleRus
+        year: 2021
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/button/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,13 @@
+if(${IDF_TARGET} STREQUAL esp8266)
+    set(req esp8266 esp_timer)
+elseif(${IDF_VERSION_MAJOR} STREQUAL 4 AND ${IDF_VERSION_MINOR} STREQUAL 1 AND ${IDF_VERSION_PATCH} STREQUAL 3)
+    set(req driver)
+else()    
+    set(req driver esp_timer)
+endif()
+
+idf_component_register(
+    SRCS button.c
+    INCLUDE_DIRS .
+    REQUIRES ${req}
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/button/Kconfig	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,28 @@
+menu "Button"
+
+	config BUTTON_MAX
+		int "Maximum number of buttons"
+		range 1 10
+		default 5
+	
+	config BUTTON_POLL_TIMEOUT
+		int "Poll timeout, ms"
+		range 1 1000
+		default 10
+
+	config BUTTON_LONG_PRESS_TIMEOUT
+		int "Timeout of long press, ms"
+		range 100 10000
+		default 1000
+
+	config BUTTON_AUTOREPEAT_TIMEOUT
+		int "Timeout before autorepeat, ms"
+		range 100 10000
+		default 500
+
+	config BUTTON_AUTOREPEAT_INTERVAL
+		int "Autorepeat interval, ms"
+		range 100 10000
+		default 250
+		
+endmenu
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/button/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 Ruslan V. Uss
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/button/button.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,182 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file button.c
+ *
+ * ESP-IDF driver for simple GPIO buttons.
+ *
+ * Supports anti-jitter, autorepeat, long press.
+ *
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+#include "button.h"
+#include <esp_timer.h>
+
+#define DEAD_TIME_US 50000 // 50ms
+
+#define POLL_TIMEOUT_US        (CONFIG_BUTTON_POLL_TIMEOUT * 1000)
+#define AUTOREPEAT_TIMEOUT_US  (CONFIG_BUTTON_AUTOREPEAT_TIMEOUT * 1000)
+#define AUTOREPEAT_INTERVAL_US (CONFIG_BUTTON_AUTOREPEAT_INTERVAL * 1000)
+#define LONG_PRESS_TIMEOUT_US  (CONFIG_BUTTON_LONG_PRESS_TIMEOUT * 1000)
+
+static button_t *buttons[CONFIG_BUTTON_MAX] = { NULL };
+static esp_timer_handle_t timer = NULL;
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+static void poll_button(button_t *btn)
+{
+    if (btn->internal.state == BUTTON_PRESSED && btn->internal.pressed_time < DEAD_TIME_US)
+    {
+        // Dead time, ignore all
+        btn->internal.pressed_time += POLL_TIMEOUT_US;
+        return;
+    }
+
+    if (gpio_get_level(btn->gpio) == btn->pressed_level)
+    {
+        // button is pressed
+        if (btn->internal.state == BUTTON_RELEASED)
+        {
+            // pressing just started, reset pressing/repeating time and run callback
+            btn->internal.state = BUTTON_PRESSED;
+            btn->internal.pressed_time = 0;
+            btn->internal.repeating_time = 0;
+            btn->callback(btn, BUTTON_PRESSED);
+            return;
+        }
+        // increment pressing time
+        btn->internal.pressed_time += POLL_TIMEOUT_US;
+
+        // check autorepeat
+        if (btn->autorepeat)
+        {
+            // check autorepeat timeout
+            if (btn->internal.pressed_time < AUTOREPEAT_TIMEOUT_US)
+                return;
+            // increment repeating time
+            btn->internal.repeating_time += POLL_TIMEOUT_US;
+
+            if (btn->internal.repeating_time >= AUTOREPEAT_INTERVAL_US)
+            {
+                // reset repeating time and run callback
+                btn->internal.repeating_time = 0;
+                btn->callback(btn, BUTTON_CLICKED);
+            }
+            return;
+        }
+
+        if (btn->internal.state == BUTTON_PRESSED && btn->internal.pressed_time >= LONG_PRESS_TIMEOUT_US)
+        {
+            // button perssed long time, change state and run callback
+            btn->internal.state = BUTTON_PRESSED_LONG;
+            btn->callback(btn, BUTTON_PRESSED_LONG);
+        }
+    }
+    else if (btn->internal.state != BUTTON_RELEASED)
+    {
+        // button released
+        bool clicked = btn->internal.state == BUTTON_PRESSED;
+        btn->internal.state = BUTTON_RELEASED;
+        btn->callback(btn, BUTTON_RELEASED);
+        if (clicked)
+            btn->callback(btn, BUTTON_CLICKED);
+    }
+}
+
+static void poll(void *arg)
+{
+    for (size_t i = 0; i < CONFIG_BUTTON_MAX; i++)
+        if (buttons[i] && buttons[i]->callback)
+            poll_button(buttons[i]);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+static const esp_timer_create_args_t timer_args = {
+    .arg = NULL,
+    .name = "poll_buttons",
+    .dispatch_method = ESP_TIMER_TASK,
+    .callback = poll,
+};
+
+esp_err_t button_init(button_t *btn)
+{
+    CHECK_ARG(btn);
+
+    if (!timer)
+        CHECK(esp_timer_create(&timer_args, &timer));
+
+    esp_timer_stop(timer);
+
+    esp_err_t res = ESP_ERR_NO_MEM;
+
+    for (size_t i = 0; i < CONFIG_BUTTON_MAX; i++)
+    {
+        if (buttons[i] == btn)
+            break;
+
+        if (!buttons[i])
+        {
+            btn->internal.state = BUTTON_RELEASED;
+            btn->internal.pressed_time = 0;
+            btn->internal.repeating_time = 0;
+            res = gpio_set_direction(btn->gpio, GPIO_MODE_INPUT);
+            if (res != ESP_OK) break;
+            if (btn->internal_pull)
+            {
+                res = gpio_set_pull_mode(btn->gpio, btn->pressed_level ? GPIO_PULLDOWN_ONLY : GPIO_PULLUP_ONLY);
+                if (res != ESP_OK) break;
+            }
+            buttons[i] = btn;
+            break;
+        }
+    }
+
+    CHECK(esp_timer_start_periodic(timer, POLL_TIMEOUT_US));
+    return res;
+}
+
+esp_err_t button_done(button_t *btn)
+{
+    CHECK_ARG(btn);
+
+    esp_timer_stop(timer);
+
+    esp_err_t res = ESP_ERR_INVALID_ARG;
+
+    for (size_t i = 0; i < CONFIG_BUTTON_MAX; i++)
+        if (buttons[i] == btn)
+        {
+            buttons[i] = NULL;
+            res = ESP_OK;
+            break;
+        }
+
+    CHECK(esp_timer_start_periodic(timer, POLL_TIMEOUT_US));
+    return res;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/button/button.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,112 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file button.h
+ * @defgroup button button
+ * @{
+ *
+ * ESP-IDF driver for simple GPIO buttons.
+ *
+ * Supports anti-jitter, auto repeat, long press.
+ *
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+#ifndef __COMPONENTS_BUTTON_H__
+#define __COMPONENTS_BUTTON_H__
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <driver/gpio.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Typedef of button descriptor
+ */
+typedef struct button_s button_t;
+
+/**
+ * Button states/events
+ */
+typedef enum {
+    BUTTON_PRESSED = 0,
+    BUTTON_RELEASED,
+    BUTTON_CLICKED,
+    BUTTON_PRESSED_LONG,
+} button_state_t;
+
+/**
+ * Callback prototype
+ *
+ * @param btn    Pointer to button descriptor
+ * @param state  Button action (new state)
+ */
+typedef void (*button_event_cb_t)(button_t *btn, button_state_t state);
+
+/**
+ * Button descriptor struct
+ */
+struct button_s
+{
+    gpio_num_t gpio;                //!< GPIO
+    bool internal_pull;             //!< Enable internal pull-up/pull-down
+    uint8_t pressed_level;          //!< Logic level of pressed button
+    bool autorepeat;                //!< Enable autorepeat
+    button_event_cb_t callback;     //!< Button callback
+    void *ctx;                      //!< User data
+    struct {
+        button_state_t state;
+        uint32_t pressed_time;
+        uint32_t repeating_time;
+    } internal;                     //!< Internal button state
+};
+
+/**
+ * @brief Init button
+ *
+ * @param btn Pointer to button descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t button_init(button_t *btn);
+
+/**
+ * @brief Deinit button
+ *
+ * @param btn Pointer to button descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t button_done(button_t *btn);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __COMPONENTS_BUTTON_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/button/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,7 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+
+ifdef CONFIG_IDF_TARGET_ESP8266
+COMPONENT_DEPENDS = esp8266
+else
+COMPONENT_DEPENDS = driver
+endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ccs811/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,27 @@
+---
+components:
+  - name: ccs811
+    description: |
+      Driver for AMS CCS811 digital gas sensor
+    group: air-quality
+    groups:
+      - name: gas
+    code_owners:
+      - name: UncleRus
+    depends:
+      - name: i2cdev
+      - name: log
+      - name: esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - name: UncleRus
+        year: 2021
+      - name: gschorcht
+        year: 2017
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ccs811/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS ccs811.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ccs811/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,27 @@
+Copyright (c) 2017 Gunar Schorcht (https://github.com/gschorcht)
+Copyright (c) 2020 Ruslan V. Uss (https://github.com/UncleRus)
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ccs811/README.md	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,426 @@
+# Driver for the ams CCS811 digital gas sensor for monitoring indoor air quality
+
+The driver is for the usage with the ESP-IDF.
+
+## About the sensor
+
+The CCS811 is an ultra-low power digital sensor which detects
+**Volatile Organic Compounds (VOC)** for Indoor Air Quality (IAQ) monitoring
+that. The sensor allows to
+
+- convert raw sensor data to Total Volatile Organic Compound (TVOC) and
+  equivalent CO2 (eCO2),
+- compensate gas readings due to temperature and humidity using an external
+  sensor,
+- trigger interrupts when new measurement results are available or eCO2 value
+  exceeds thresholds,
+- correct baseline automatically or manually
+- connect a NTC thermistor to provide means of calculating the local ambient
+  temperature.
+
+The sensor uses an I2C interface and supports clock stretching. See the notes
+on clock stretching during I2C interface intialization.
+
+## Measurement Process
+
+### Sensor modes
+
+The CCS811 can operate in 5 different modes:
+
+Mode | Driver symbol | Period | RAW data | IAQ values
+---- | ------------- | ------ |:--------:|:----------:
+Idle, Low Current Mode | `CCS811_MODE_IDLE` | -  | - | -
+Constant Power Mode | `CCS811_MODE_1S` | 1 s | X | X
+Pulse Heating Mode | `CCS811_MODE_10S` | 10 s | X | X
+Low Power Pulse Heating Mode | `CCS811_MODE_60S` | 60 s | X | X
+Constant Power Mode | `CCS811_MODE_250MS` | 250 ms | X | -
+
+After power up, the sensor starts automatically in *Idle, Low Current Mode*
+(`CCS811_MODE_IDLE`). To start periodic measurements, the mode of the sensor
+has to be changed to any measurement mode. Measurement modes with with
+different rates of periodic measurements are available, see table above.
+
+**Please note:** In *Constant Power Mode* with measurements every 250 ms
+(`CCS811_MODE_250MS`) only raw data are available. In all other measurement
+modes, the Indoor Air Quality (IAQ) values are available additionally.
+The *Constant Power Mode* with measurements every 250 ms (`CCS811_MODE_250ms`)
+is only intended for systems where an external host system wants to run an
+algorithm with raw data.
+
+Once the sensor is initialized with function `ccs811_init()`, function
+`ccs811_set_mode()` can be used to start periodic measurements with a given
+period.
+
+```C
+ESP_ERROR_CHECK(i2cdev_init());
+...
+static ccs811_dev_t sensor;
+memset(&sensor, 0, sizeof(ccs811_dev_t)); // Zero descriptor
+ESP_ERROR_CHECK(ccs811_init_desc(&sensor, 0, CCS811_I2C_ADDRESS_1, 5, 4);
+...
+if (ccs811_init(&sensor) == ESP_OK)
+{
+   ...
+   // start periodic measurement with one measurement per second
+   ESP_ERROR_CHECK(ccs811_set_mode(&sensor, CCS811_MODE_1S));
+}
+...
+```
+
+**Please note:**
+
+1. After setting the mode, the sensor is in conditioning period that needs up
+   to 20 minutes, before accurate readings are generated, see the data sheet for
+   more details.
+
+2. During the early-live (burn-in) period, the CCS811 sensor should run for
+   48 hours in the selected mode of operation to ensure sensor performance is
+   stable, see the datasheet for more details.
+
+3. When the sensor operating mode is changed to a new mode with a lower sample
+   rate, e.g., from *Pulse Heating Mode* (`CCS811_MODE_10S`) to *Low Power Pulse
+   Heating Mode* (`CCS811_MODE_60S`), it should be placed in *Idle, Low Current
+   Mode* (`CCS811_MODE_IDLE`) for at least 10 minutes before enabling the new
+   mode.
+
+When a sensor operating mode is changed to a new mode with a higher sample
+rate, e.g., from *Low Power Pulse Heating Mode* (`CCS811_MODE_60S`) to
+*Pulse Heating Mode* (`CCS811_MODE_10S`), there is no requirement to wait
+before enabling the new mode.
+
+## Measurement results
+
+Once the measurement mode is set, the user task can use function
+`ccs811_get_results()` with same rate as the measurement rate to fetch the
+results. The function returns **raw data** as well as **Indoor Air Quality
+(IAQ)** values.
+
+While raw data represents simply the current through the sensor and the voltage
+across the sensor with the selected current, IAQ values are the results of the
+processing these raw data by the sensor. IAQ values consist of the **equivalent
+CO2 (eCO2)** with a range from 400 ppm to 8192 ppm and **Total Volatile Organic
+Compound (TVOC)** with a range from 0 ppb to 1187 ppb.
+
+```C
+uint16_t iaq_tvoc;
+uint16_t iaq_eco2;
+uint8_t  raw_i;
+uint16_t raw_v;
+...
+// get the results and do something with them
+if (ccs811_get_results(&sensor, &tvoc, &eco2, &raw_i, &raw_v) == ESP_OK)
+{
+    ...
+}
+...
+```
+
+If some of the results are not needed, the corresponding pointer parameters
+can be set to NULL.
+
+If the function `ccs811_get_results()` is called and no new data are
+available, e.g., due to the sensor mode time tolerance of 2%, the function
+still returns successfully. In this case, the results of the last measurement
+are returned and the error code CCS811_ERR_NO_NEW_DATA.
+
+**Please note:**
+
+1. In *Constant Power Mode* with measurements every 250 ms (`CCS811_MODE_250MS`)
+   only raw data are available.
+
+2. The rate of fetching data must not be greater than the rate of measurement.
+   Due to the sensor mode timing tolerance of 2 %, the rate of fetching data
+   should be lower than the measurement rate.
+
+3. If the function is called and no new data are available, the results of the
+   latest measurement are returned and error code CCS811_ERR_NO_NEW_DATA is set.
+
+### Compensation
+
+If information about the environment like temperature and humidity are available
+from another sensor, they can be used by CCS811 to compensate gas readings due
+to temperature and humidity changes. Function `ccs811_set_environmental_data()`
+can be used to set these environmental data.
+
+```C
+float    temperature;
+float    humidity;
+...
+if (sht3x_get_results(sht3x, &temperature, &humidity) == ESP_OK)
+    // set CCS811 environmental data with values fetched from SHT3x
+    ccs811_set_environmental_data(ccs811, temperature, humidity);
+...
+```
+
+### NTC
+
+CCS811 supports an external interface for connecting a negative thermal
+coefficient thermistor (R_NTC) to provide a cost effective and power efficient
+means of calculating the local ambient temperature. The sensor measures the
+voltage V_NTC across R_NTC as well as the voltage V_REF across a connected
+reference resistor (R_REF). Function `ccs811_get_ntc_resistance()` can be used
+to fetch the current resistance of R_NTC. It uses the resistance of R_REF and
+measured voltages V_REF and V_NTV with the following equation:
+
+```text
+          R_NTC = R_REF / V_REF * V_NTC
+```
+
+Using the data sheet of the NTC, the ambient temperature can be calculated. See
+application note ams AN000372 for more details. For example, with Adafruit
+CCS811 Air Quality Sensor Breakout the ambienttemperature can be determined as
+following:
+
+```C
+...
+#define CCS811_R_REF        100000      // resistance of the reference resistor
+#define CCS811_R_NTC        10000       // resistance of NTC at a reference temperature
+#define CCS811_R_NTC_TEMP   25          // reference temperature for NTC
+#define CCS811_BCONSTANT    3380        // B constant
+
+// get NTC resistance
+uint32_t r_ntc;
+ccs811_get_ntc_resistance(&sensor, CCS811_R_REF, &r_ntc);
+
+// calculation of temperature from application note ams AN000372
+double ntc_temp;
+ntc_temp  = log((double)r_ntc / CCS811_R_NTC);      // 1
+ntc_temp /= CCS811_BCONSTANT;                       // 2
+ntc_temp += 1.0 / (CCS811_R_NTC_TEMP + 273.15);     // 3
+ntc_temp  = 1.0 / ntc_temp;                         // 4
+ntc_temp -= 273.15;                                 // 5
+....
+```
+
+### Interrupts
+
+CCS811 supports two types of interrupts that can be used to fetch data:
+
+- data ready interrupt (INT_DATA_RDY)
+- threshold interrupt (INT_THRESHOLD)
+
+#### Data ready interrupt
+
+At the end of each measurement cycle (every 250 ms, 1 second, 10 seconds, or
+60 seconds), CCS811 can optionally trigger an interrupt. The signal *nINT* is
+driven low as soon as new sensor values are ready to read. It will stop being
+driven low when sensor data are read with function `ccs811_get_results()`.
+
+The interrupt is disabled by default. It can be enabled with function
+`ccs811_enable_interrupt()`.
+
+```C
+...
+// enable the data ready interrupt
+ESP_ERROR_CHECK(ccs811_enable_interrupt(&sensor, true));
+...
+```
+
+#### Threshold interrupt
+
+The user task can choose that the data ready interrupt is not generated every
+time when new sensor values become ready but only if the eCO2 value moves from
+the current range (LOW, MEDIUM, or HIGH) into another range by more than a
+hysteresis value. Hysteresis is used to prevent multiple interrupts close to a
+threshold.
+
+The interrupt is disabled by default and can be enabled with function
+`ccs811_set_eco2_thresholds()`. The ranges are defined by parameters *low* and
+*high* as following
+
+- **LOW** - below parameter value *low*
+- **MEDIUM** - between parameter values *low* and *high*
+- **HIGH** - above parameter value *high* is range **HIGH**.
+
+If all parameters have valid values, the function sets the thresholds and
+enables the data ready interrupt. Using 0 for all parameters disables the
+interrupt.
+
+```C
+...
+// set threshold parameters and enable threshold interrupt mode
+ESP_ERROR_CHECK(ccs811_set_eco2_thresholds(&sensor, 600, 1100, 40));
+...
+```
+
+### Baseline
+
+CCS81 supports automatic baseline correction over a minimum time of 24 hours.
+Using function `ccs811_get_baseline()`, the current baseline value can be saved
+before the sensor is powered down. This baseline can then be restored with
+function `ccs811_set_baseline()` after sensor is powered up again to continue
+the automatic baseline process.
+
+## Usage
+
+First, the hardware configuration has to be established.
+
+### Communication interface settings
+
+Dependent on the hardware configuration, the communication interface settings
+have to be defined.
+
+```C
+// define I2C interfaces at which CCS811 sensors can be connected
+#define I2C_PORT       0
+#define I2C_SCL_PIN   14
+#define I2C_SDA_PIN   13
+
+// define GPIO for interrupt
+#define INT_GPIO      5
+```
+
+### Main program
+
+Before using the CCS811 driver, function `i2cdev_init()` needs to be called.
+
+**Please note:** CCS811 uses clock streching that can be longer than the
+default I2C clock stretching. Therefore the clock stretching parameter of I2C
+has to be set to at least `CCS811_I2C_CLOCK_STRETCH`.
+
+```C
+static ccs811_dev_t sensor;    // pointer to sensor device data structure
+...
+memset(&sensor, 0, sizeof(ccs811_dev_t));
+i2cdev_init();    // Init i2cdev library
+i2c_set_timeout(I2C_PORT, CCS811_I2C_CLOCK_STRETCH);
+...
+ccs811_init_desc(&sensor, I2C_PORT, CCS811_I2C_ADDRESS_1, I2C_SDA_PIN, I2C_SCL_PIN);
+```
+
+Once I2C library initialized, function `ccs811_init()` has to be called
+for each CCS811 sensor to initialize the sensor and to check its availability
+as well as its error state.
+
+```C
+...
+if (ccs811_init(&sensor) == ESP_OK)
+{
+    ...
+}
+...
+```
+
+If initialization of the sensor was successful, the sensor mode has be set to
+start periodic measurement. The sensor mode can be changed anytime later.
+
+```C
+...
+// start periodic measurement with one measurement per second
+ccs811_set_mode(&sensor, CCS811_MODE_1S);
+...
+```
+
+Finally, a user task that uses the sensor has to be created.
+
+```C
+xTaskCreate(user_task, "user_task", 256, NULL, 2, 0);
+```
+
+The user task can use different approaches to fetch new data. Either new data
+are fetched periodically or the interrupt signal *nINT* is used when new data
+are available or eCO2 value exceeds defined thresholds.
+
+If new data are fetched **periodically** the implementation of the user task is
+quite simply and could look like following.
+
+```C
+void user_task(void *pvParameters)
+{
+    uint16_t tvoc;
+    uint16_t eco2;
+
+    TickType_t last_wakeup = xTaskGetTickCount();
+
+    while (1)
+    {
+        // get the results and do something with them
+        if (ccs811_get_results(&sensor, &tvoc, &eco2, 0, 0) == ESP_OK)
+            ...
+        // passive waiting until 1 second is over
+        vTaskDelayUntil(&last_wakeup, 1000 / portTICK_PERIOD_MS);
+    }
+}
+...
+```
+
+The user task simply fetches new data with the same rate as the measurements
+are performed.
+
+**Please note:** The rate of fetching the measurement results must be not
+greater than the rate of periodic measurements of the sensor, however, it
+*should be less* to avoid conflicts caused by the timing tolerance of the
+sensor.
+
+A different approach is to use the **interrupt** *nINT*. This interrupt signal
+is either triggered every time when new data are available (INT_DATA_RDY) or
+only whenever eCO2 value exceeds defined thresholds (INT_THRESHOLD). In both
+cases, the user has to implement an interrupt handler that either fetches the
+data directly or triggers a task, that is waiting to fetch the data.
+
+```C
+...
+TaskHandle_t nINT_task;
+
+// Interrupt handler which resumes user_task_interrupt on interrupt
+
+void nINT_handler(uint8_t gpio)
+{
+    xTaskResumeFromISR(nINT_task);
+}
+
+// User task that fetches the sensor values.
+
+void user_task_interrupt(void *pvParameters)
+{
+    uint16_t tvoc;
+    uint16_t eco2;
+
+    while (1)
+    {
+        // task suspends itself and waits to be resumed by interrupt handler
+        vTaskSuspend(NULL);
+
+        // after resume get the results and do something with them
+        if (ccs811_get_results(&sensor, &tvoc, &eco2, 0, 0) == ESP_OK)
+            ...
+    }
+}
+...
+
+xTaskCreate(user_task_interrupt, "user_task_interrupt", 256, NULL, 2, &nINT_task);
+...
+```
+
+In this example, a task is defined which suspends itself in each cycle to wait
+for fetching the data. The task is resumed by the interrupt handler.
+
+Finally, the interrupt handler has to be activated for the GPIO which is
+connected to the interrupt signal. Furthermore, the interrupt has to be enabled
+in the CCS811 sensor.
+
+Function `ccs811_enable_interrupt()` enables the interrupt that is triggered
+whenever new data are available (INT_DATA_RDY).
+
+```C
+...
+// activate the interrupt for INT_GPIO and set the interrupt handler
+gpio_set_interrupt(INT_GPIO, GPIO_INTTYPE_EDGE_NEG, nINT_handler);
+
+// enable the data ready interrupt INT_DATA_RDY
+ccs811_enable_interrupt(&sensor, true);
+...
+```
+
+Function `ccs811_set_eco2_thresholds()` enables the interrupt that is triggered
+whenever eCO2 value exceeds the thresholds (INT_THRESHOLD) defined by parameters.
+
+```C
+...
+// activate the interrupt for INT_GPIO and set the interrupt handler
+gpio_set_interrupt(INT_GPIO, GPIO_INTTYPE_EDGE_NEG, nINT_handler);
+
+// set threshold parameters and enable threshold interrupt mode INT_THRESHOLD
+ccs811_set_eco2_thresholds(&sensor, 600, 1100, 40);
+...
+```
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ccs811/ccs811.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,582 @@
+/*
+ * Copyright (c) 2017 Gunar Schorcht <https://github.com/gschorcht>
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file ccs811.c
+ *
+ * ESP-IDF driver for AMS CCS811 digital gas sensor connected to I2C
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2017 Gunar Schorcht <https://github.com/gschorcht>\n
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>\n
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#include <string.h>
+#include <inttypes.h>
+#include <esp_log.h>
+#include <esp_idf_lib_helpers.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+
+#include "ccs811.h"
+
+#define I2C_FREQ_HZ 400000 // 400kHz max
+
+static const char *TAG = "ccs811";
+
+/* CCS811 register addresses */
+#define CCS811_REG_STATUS          0x00
+#define CCS811_REG_MEAS_MODE       0x01
+#define CCS811_REG_ALG_RESULT_DATA 0x02
+#define CCS811_REG_RAW_DATA        0x03
+#define CCS811_REG_ENV_DATA        0x05
+#define CCS811_REG_NTC             0x06
+#define CCS811_REG_THRESHOLDS      0x10
+#define CCS811_REG_BASELINE        0x11
+
+#define CCS811_REG_HW_ID           0x20
+#define CCS811_REG_HW_VER          0x21
+#define CCS811_REG_FW_BOOT_VER     0x23
+#define CCS811_REG_FW_APP_VER      0x24
+
+#define CCS811_REG_ERROR_ID        0xe0
+
+#define CCS811_REG_APP_ERASE       0xf1
+#define CCS811_REG_APP_DATA        0xf2
+#define CCS811_REG_APP_VERIFY      0xf3
+#define CCS811_REG_APP_START       0xf4
+#define CCS811_REG_SW_RESET        0xff
+
+// status register bits
+#define CCS811_STATUS_ERROR        0x01  // error, details in CCS811_REG_ERROR
+#define CCS811_STATUS_DATA_RDY     0x08  // new data sample in ALG_RESULT_DATA
+#define CCS811_STATUS_APP_VALID    0x10  // valid application firmware loaded
+#define CCS811_STATUS_FW_MODE      0x80  // firmware is in application mode
+
+// error register bits
+#define CCS811_ERR_WRITE_REG_INV   0x01  // invalid register address on write
+#define CCS811_ERR_READ_REG_INV    0x02  // invalid register address on read
+#define CCS811_ERR_MEASMODE_INV    0x04  // invalid requested measurement mode
+#define CCS811_ERR_MAX_RESISTANCE  0x08  // maximum sensor resistance exceeded 
+#define CCS811_ERR_HEATER_FAULT    0x10  // heater current not in range
+#define CCS811_ERR_HEATER_SUPPLY   0x20  // heater voltage not applied correctly
+
+/**
+ * Type declarations
+ */
+
+typedef struct
+{
+    uint8_t reserved_1 :2;
+    uint8_t int_thresh :1;  // interrupt if new ALG_RESULT_DAT crosses on of the thresholds
+    uint8_t int_datardy:1;  // interrupt if new sample is ready in ALG_RESULT_DAT
+    uint8_t drive_mode :3;  // mode number binary coded
+} ccs811_meas_mode_reg_t;
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+#define CHECK_LOGE(x, msg, ...) do { \
+        esp_err_t __; \
+        if ((__ = x) != ESP_OK) { \
+            ESP_LOGE(TAG, msg, ## __VA_ARGS__); \
+            return __; \
+        } \
+    } while (0)
+
+///////////////////////////////////////////////////////////////////////////////
+/// Static functions
+
+static esp_err_t read_reg_nolock(ccs811_dev_t *dev, uint8_t reg, uint8_t *data, uint32_t len)
+{
+    ESP_LOGD(TAG, "Read %" PRIu32 " bytes from i2c slave starting at reg addr %02x.", len, reg);
+
+    esp_err_t res = i2c_dev_read_reg(&dev->i2c_dev, reg, data, len);
+    if (res != ESP_OK)
+    {
+        ESP_LOGE(TAG, "Error %d on read %" PRIu32 " bytes from I2C slave reg addr %02x.", res, len, reg);
+        return res;
+    }
+
+    return ESP_OK;
+}
+
+static esp_err_t write_reg_nolock(ccs811_dev_t *dev, uint8_t reg, uint8_t *data, uint32_t len)
+{
+    ESP_LOGD(TAG, "Write %" PRIu32 " bytes to i2c slave starting at reg addr %02x", len, reg);
+
+    esp_err_t res = i2c_dev_write_reg(&dev->i2c_dev, reg, data, len);
+    if (res != ESP_OK)
+    {
+        ESP_LOGE(TAG, "Error %d on write %" PRIu32 " bytes to i2c slave register %02x.", res, len, reg);
+        return res;
+    }
+
+    return ESP_OK;
+}
+
+static esp_err_t ccs811_is_available(ccs811_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    uint8_t reg_data[5];
+
+    // check hardware id (register 0x20) and hardware version (register 0x21)
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, read_reg_nolock(dev, CCS811_REG_HW_ID, reg_data, 5));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    if (reg_data[0] != 0x81)
+    {
+        ESP_LOGE(TAG, "Wrong hardware ID %02x, should be 0x81", reg_data[0]);
+        return CCS811_ERR_HW_ID;
+    }
+
+    ESP_LOGD(TAG, "hardware version:      %02x", reg_data[1]);
+    ESP_LOGD(TAG, "firmware boot version: %02x", reg_data[3]);
+    ESP_LOGD(TAG, "firmware app version:  %02x", reg_data[4]);
+
+    return ESP_OK;
+}
+
+static esp_err_t ccs811_enable_threshold(ccs811_dev_t *dev, bool enabled)
+{
+    CHECK_ARG(dev);
+
+    ccs811_meas_mode_reg_t reg;
+
+    // first, enable/disable the data ready interrupt
+    CHECK(ccs811_enable_interrupt(dev, enabled));
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    // read measurement mode register value
+    I2C_DEV_CHECK(&dev->i2c_dev, read_reg_nolock(dev, CCS811_REG_MEAS_MODE, (uint8_t *)&reg, 1));
+
+    // second, enable/disable the threshold interrupt mode
+    reg.int_thresh = enabled;
+
+    // write back measurement mode register
+    I2C_DEV_CHECK_LOGE(&dev->i2c_dev,
+            write_reg_nolock(dev, CCS811_REG_MEAS_MODE, (uint8_t *)&reg, 1),
+            "Could not set measurement mode register.");
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+static esp_err_t ccs811_check_error_status(ccs811_dev_t *dev)
+{
+    uint8_t status;
+    uint8_t err_reg;
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    // check status register
+    I2C_DEV_CHECK(&dev->i2c_dev, read_reg_nolock(dev, CCS811_REG_STATUS, &status, 1));
+
+    if (!status & CCS811_STATUS_ERROR)
+    {
+        // everything is fine
+        I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+        return ESP_OK;
+    }
+
+    // Check the error register
+    I2C_DEV_CHECK(&dev->i2c_dev, read_reg_nolock(dev, CCS811_REG_ERROR_ID, &err_reg, 1));
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    if (err_reg & CCS811_ERR_WRITE_REG_INV)
+    {
+        ESP_LOGE(TAG, "Received an invalid register for write.");
+        return CCS811_ERR_WR_REG_INV;
+    }
+
+    if (err_reg & CCS811_ERR_READ_REG_INV)
+    {
+        ESP_LOGE(TAG, "Received an invalid register for read.");
+        return CCS811_ERR_RD_REG_INV;
+    }
+
+    if (err_reg & CCS811_ERR_MEASMODE_INV)
+    {
+        ESP_LOGE(TAG, "Received an invalid measurement mode request.");
+        return CCS811_ERR_MM_INV;
+    }
+
+    if (err_reg & CCS811_ERR_MAX_RESISTANCE)
+    {
+        ESP_LOGE(TAG, "Sensor resistance measurement has reached or exceeded the maximum range.");
+        return CCS811_ERR_MAX_RESIST;
+    }
+
+    if (err_reg & CCS811_ERR_HEATER_FAULT)
+    {
+        ESP_LOGE(TAG, "Heater current not in range.");
+        return CCS811_ERR_HEAT_FAULT;
+    }
+
+    if (err_reg & CCS811_ERR_HEATER_SUPPLY)
+    {
+        ESP_LOGE(TAG, "Heater voltage is not being applied correctly.");
+        return CCS811_ERR_HEAT_SUPPLY;
+    }
+
+    return ESP_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/// Public functions
+
+esp_err_t ccs811_init_desc(ccs811_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+    if (addr != CCS811_I2C_ADDRESS_1 && addr != CCS811_I2C_ADDRESS_2)
+    {
+        ESP_LOGE(TAG, "Invalid device address: 0x%02x", addr);
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    dev->i2c_dev.port = port;
+    dev->i2c_dev.addr = addr;
+    dev->i2c_dev.cfg.sda_io_num = sda_gpio;
+    dev->i2c_dev.cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+    dev->i2c_dev.timeout_ticks = I2CDEV_MAX_STRETCH_TIME;
+
+    return i2c_dev_create_mutex(&dev->i2c_dev);
+}
+
+esp_err_t ccs811_free_desc(ccs811_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(&dev->i2c_dev);
+}
+
+esp_err_t ccs811_init(ccs811_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    // init sensor data structure
+    dev->mode = CCS811_MODE_IDLE;
+
+    // check whether sensor is available including the check of the hardware
+    // id and the error state
+    CHECK_LOGE(ccs811_is_available(dev), "Sensor is not available.");
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    const static uint8_t sw_reset[4] = { 0x11, 0xe5, 0x72, 0x8a };
+
+    // doing a software reset first
+    I2C_DEV_CHECK_LOGE(&dev->i2c_dev,
+            write_reg_nolock(dev, CCS811_REG_SW_RESET, (uint8_t *)sw_reset, 4),
+            "Could not reset the sensor.");
+
+    uint8_t status;
+
+    // wait 100 ms after the reset
+    vTaskDelay(pdMS_TO_TICKS(100));
+
+    // get the status to check whether sensor is in bootloader mode
+    I2C_DEV_CHECK_LOGE(&dev->i2c_dev,
+            read_reg_nolock(dev, CCS811_REG_STATUS, &status, 1),
+            "Could not read status register 0x%02x.", CCS811_REG_STATUS);
+
+    // if sensor is in bootloader mode (FW_MODE == 0), it has to switch
+    // to the application mode first
+    if (!(status & CCS811_STATUS_FW_MODE))
+    {
+        // check whether valid application firmware is loaded
+        if (!(status & CCS811_STATUS_APP_VALID))
+        {
+            ESP_LOGE(TAG, "Sensor is in boot mode, but has no valid application.");
+            I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+            return CCS811_ERR_NO_APP;
+        }
+
+        // swtich to application mode
+        uint8_t r = CCS811_REG_APP_START;
+        I2C_DEV_CHECK_LOGE(&dev->i2c_dev,
+                i2c_dev_write(&dev->i2c_dev, NULL, 0, &r, 1),
+                "Could not start application.");
+
+        // wait 100 ms after starting the app
+        vTaskDelay(pdMS_TO_TICKS(100));
+
+        // get the status to check whether sensor switched to application mode
+        I2C_DEV_CHECK_LOGE(&dev->i2c_dev,
+                read_reg_nolock(dev, CCS811_REG_STATUS, &status, 1),
+                "Could not read application status.");
+        if (!(status & CCS811_STATUS_FW_MODE))
+        {
+            ESP_LOGE(TAG, "Could not start application, invalid status 0x%02x.", status);
+            I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+            return CCS811_ERR_APP_START_FAIL;
+        }
+    }
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    // try to set default measurement mode to CCS811_MODE_1S
+    CHECK(ccs811_set_mode(dev, CCS811_MODE_1S));
+
+    return ESP_OK;
+}
+
+esp_err_t ccs811_set_mode(ccs811_dev_t *dev, ccs811_mode_t mode)
+{
+    CHECK_ARG(dev);
+
+    ccs811_meas_mode_reg_t reg;
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    // read measurement mode register value
+    I2C_DEV_CHECK(&dev->i2c_dev, read_reg_nolock(dev, CCS811_REG_MEAS_MODE, (uint8_t *)&reg, 1));
+
+    reg.drive_mode = mode;
+
+    // write back measurement mode register
+    I2C_DEV_CHECK_LOGE(&dev->i2c_dev,
+            write_reg_nolock(dev, CCS811_REG_MEAS_MODE, (uint8_t *)&reg, 1),
+            "Could not set measurement mode.");
+
+    // check whether setting measurement mode were succesfull
+    I2C_DEV_CHECK_LOGE(&dev->i2c_dev,
+            read_reg_nolock(dev, CCS811_REG_MEAS_MODE, (uint8_t *)&reg, 1),
+            "Could not set measurement mode.");
+
+    if (reg.drive_mode != mode)
+    {
+        ESP_LOGE(TAG, "Could not set measurement mode to %d", mode);
+        I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+        return CCS811_ERR_MM_INV;
+    }
+
+    dev->mode = mode;
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+#define CCS811_ALG_DATA_ECO2_HB   0
+#define CCS811_ALG_DATA_ECO2_LB   1
+#define CCS811_ALG_DATA_TVOC_HB   2
+#define CCS811_ALG_DATA_TVOC_LB   3
+#define CCS811_ALG_DATA_STATUS    4
+#define CCS811_ALG_DATA_ERROR_ID  5
+#define CCS811_ALG_DATA_RAW_HB    6
+#define CCS811_ALG_DATA_RAW_LB    7
+
+esp_err_t ccs811_get_results(ccs811_dev_t *dev, uint16_t *iaq_tvoc,
+        uint16_t *iaq_eco2, uint8_t *raw_i, uint16_t *raw_v)
+{
+    CHECK_ARG(dev);
+
+    if (dev->mode == CCS811_MODE_IDLE)
+    {
+        ESP_LOGE(TAG, "Sensor is in idle mode and not performing measurements.");
+        return CCS811_ERR_WRONG_MODE;
+    }
+
+    if (dev->mode == CCS811_MODE_250MS && (iaq_tvoc || iaq_eco2))
+    {
+        ESP_LOGE(TAG, "Sensor is in constant power mode, only raw data are available every 250ms");
+        return CCS811_ERR_NO_IAQ_DATA;
+    }
+
+    uint8_t data[8];
+
+    // read IAQ sensor values and RAW sensor data including status and error id
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK_LOGE(&dev->i2c_dev,
+            read_reg_nolock(dev, CCS811_REG_ALG_RESULT_DATA, data, 8),
+            "Could not read sensor data.");
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    // check for errors
+    if (data[CCS811_ALG_DATA_STATUS] & CCS811_STATUS_ERROR)
+        return ccs811_check_error_status(dev);
+
+    // check whether new data are ready, if not, latest values are read from sensor
+    if (!(data[CCS811_ALG_DATA_STATUS] & CCS811_STATUS_DATA_RDY))
+    {
+        ESP_LOGD(TAG, "No new data.");
+        return CCS811_ERR_NO_NEW_DATA;
+    }
+
+    // if *iaq* is not NULL return IAQ sensor values
+    if (iaq_tvoc)
+        *iaq_tvoc = data[CCS811_ALG_DATA_TVOC_HB] << 8 | data[CCS811_ALG_DATA_TVOC_LB];
+    if (iaq_eco2)
+        *iaq_eco2 = data[CCS811_ALG_DATA_ECO2_HB] << 8 | data[CCS811_ALG_DATA_ECO2_LB];
+
+    // if *raw* is not NULL return RAW sensor data
+    if (raw_i)
+        *raw_i = data[CCS811_ALG_DATA_RAW_HB] >> 2;
+    if (raw_v)
+        *raw_v = (data[CCS811_ALG_DATA_RAW_HB] & 0x03) << 8 | data[CCS811_ALG_DATA_RAW_LB];
+
+    return ESP_OK;
+}
+
+esp_err_t ccs811_get_ntc_resistance(ccs811_dev_t *dev, uint32_t r_ref,
+        uint32_t *res)
+{
+    CHECK_ARG(dev && res);
+
+    uint8_t data[4];
+
+    // read baseline register
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, read_reg_nolock(dev, CCS811_REG_NTC, data, 4));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    // calculation from application note ams AN000372
+    uint16_t v_ref = (uint16_t) (data[0]) << 8 | data[1];
+    uint16_t v_ntc = (uint16_t) (data[2]) << 8 | data[3];
+
+    *res = (v_ntc * r_ref / v_ref);
+
+    return ESP_OK;
+}
+
+esp_err_t ccs811_set_environmental_data(ccs811_dev_t *dev,
+        float temperature, float humidity)
+{
+    CHECK_ARG(dev);
+
+    uint16_t hum_conv = humidity * 512.0f + 0.5f;
+    uint16_t temp_conv = (temperature + 25.0f) * 512.0f + 0.5f;
+    
+
+    // fill environmental data
+    uint8_t data[4] = {
+        (uint8_t)((hum_conv >> 8) & 0xFF), (uint8_t)(hum_conv & 0xFF),
+        (uint8_t)((temp_conv >> 8) & 0xFF), (uint8_t)(temp_conv & 0xFF)
+    };
+
+    // send environmental data to the sensor
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK_LOGE(&dev->i2c_dev,
+            write_reg_nolock(dev, CCS811_REG_ENV_DATA, data, 4),
+            "Could not write environmental data to sensor.");
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t ccs811_set_eco2_thresholds(ccs811_dev_t *dev, uint16_t low,
+        uint16_t high, uint8_t hysteresis)
+{
+    CHECK_ARG(dev);
+
+    // check whether interrupt has to be disabled
+    if (!low && !high && !hysteresis)
+        return ccs811_enable_threshold(dev, false);
+
+    // check parameters
+    if (low < CCS_ECO2_RANGE_MIN || high > CCS_ECO2_RANGE_MAX || low > high || !hysteresis)
+    {
+        ESP_LOGE(TAG, "Wrong threshold parameters");
+        CHECK(ccs811_enable_threshold(dev, false));
+        return CCS811_ERR_WRONG_PARAMS;
+    }
+
+    // fill the threshold data
+    uint8_t data[5] = { low >> 8, low & 0xff, high >> 8, high & 0xff, hysteresis };
+
+    // write threshold data to the sensor
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK_LOGE(&dev->i2c_dev,
+            write_reg_nolock(dev, CCS811_REG_THRESHOLDS, data, 5),
+            "Could not write threshold interrupt data to sensor.");
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    // finally enable the threshold interrupt mode    
+    return ccs811_enable_threshold(dev, true);
+}
+
+esp_err_t ccs811_enable_interrupt(ccs811_dev_t *dev, bool enabled)
+{
+    CHECK_ARG(dev);
+
+    ccs811_meas_mode_reg_t reg;
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    // read measurement mode register value
+    I2C_DEV_CHECK(&dev->i2c_dev, read_reg_nolock(dev, CCS811_REG_MEAS_MODE, (uint8_t *)&reg, 1));
+
+    reg.int_datardy = enabled;
+    reg.int_thresh = false;      // threshold mode must not enabled
+
+    // write back measurement mode register
+    I2C_DEV_CHECK(&dev->i2c_dev, write_reg_nolock(dev, CCS811_REG_MEAS_MODE, (uint8_t *)&reg, 1));
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t ccs811_get_baseline(ccs811_dev_t *dev, uint16_t *baseline)
+{
+    CHECK_ARG(dev && baseline);
+
+    uint8_t data[2];
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    // read baseline register
+    I2C_DEV_CHECK(&dev->i2c_dev, read_reg_nolock(dev, CCS811_REG_BASELINE, data, 2));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    *baseline = (uint16_t) (data[0]) << 8 | data[1];
+
+    return ESP_OK;
+}
+
+esp_err_t ccs811_set_baseline(ccs811_dev_t *dev, uint16_t baseline)
+{
+    CHECK_ARG(dev);
+
+    uint8_t data[2] = { baseline >> 8, baseline & 0xff };
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    // write baseline register
+    I2C_DEV_CHECK(&dev->i2c_dev, write_reg_nolock(dev, CCS811_REG_BASELINE, data, 2));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ccs811/ccs811.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,313 @@
+/*
+ * Copyright (c) 2017 Gunar Schorcht <https://github.com/gschorcht>
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file ccs811.h
+ * @defgroup ccs811 ccs811
+ * @{
+ *
+ * ESP-IDF driver for AMS CCS811 digital gas sensor connected to I2C
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2017 Gunar Schorcht <https://github.com/gschorcht>\n
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>\n
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __CCS811_H__
+#define __CCS811_H__
+
+#include <stdbool.h>
+#include <i2cdev.h>
+#include <esp_err.h>
+
+//!< CCS811 I2C addresses
+#define CCS811_I2C_ADDRESS_1      0x5a      //!< default
+#define CCS811_I2C_ADDRESS_2      0x5b
+
+#define CCS811_ERR_BASE 0xa000
+
+//!< CCS811 driver error codes ORed with error codes for I2C the interface
+#define CCS811_ERR_BOOT_MODE      (CCS811_ERR_BASE + 1) //!< firmware is in boot mode
+#define CCS811_ERR_NO_APP         (CCS811_ERR_BASE + 2) //!< no application firmware loaded
+#define CCS811_ERR_NO_NEW_DATA    (CCS811_ERR_BASE + 3) //!< no new data samples are ready
+#define CCS811_ERR_NO_IAQ_DATA    (CCS811_ERR_BASE + 4) //!< no new data samples are ready
+#define CCS811_ERR_HW_ID          (CCS811_ERR_BASE + 5) //!< wrong hardware ID
+#define CCS811_ERR_INV_SENS       (CCS811_ERR_BASE + 6) //!< invalid sensor ID
+#define CCS811_ERR_WR_REG_INV     (CCS811_ERR_BASE + 7) //!< invalid register addr on write
+#define CCS811_ERR_RD_REG_INV     (CCS811_ERR_BASE + 8) //!< invalid register addr on read
+#define CCS811_ERR_MM_INV         (CCS811_ERR_BASE + 9) //!< invalid measurement mode
+#define CCS811_ERR_MAX_RESIST     (CCS811_ERR_BASE + 10) //!< max sensor resistance reached
+#define CCS811_ERR_HEAT_FAULT     (CCS811_ERR_BASE + 11) //!< heater current not in range
+#define CCS811_ERR_HEAT_SUPPLY    (CCS811_ERR_BASE + 12) //!< heater voltage not correct
+#define CCS811_ERR_WRONG_MODE     (CCS811_ERR_BASE + 13) //!< wrong measurement mode
+#define CCS811_ERR_RD_STAT_FAILED (CCS811_ERR_BASE + 14) //!< read status register failed
+#define CCS811_ERR_RD_DATA_FAILED (CCS811_ERR_BASE + 15) //!< read sensor data failed
+#define CCS811_ERR_APP_START_FAIL (CCS811_ERR_BASE + 16) //!< sensor app start failure
+#define CCS811_ERR_WRONG_PARAMS   (CCS811_ERR_BASE + 17) //!< wrong parameters used
+
+// ranges
+#define CCS_ECO2_RANGE_MIN 400
+#define CCS_ECO2_RANGE_MAX 8192
+#define CCS_TVOC_RANGE_MIN 0
+#define CCS_TVOC_RANGE_MAX 1187
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief CCS811 operation modes
+ */
+typedef enum
+{
+    CCS811_MODE_IDLE = 0, //!< Idle, low current mode
+    CCS811_MODE_1S = 1,   //!< Constant Power mode, IAQ values every 1 s
+    CCS811_MODE_10S = 2,  //!< Pulse Heating mode, IAQ values every 10 s
+    CCS811_MODE_60S = 3,  //!< Low Power Pulse Heating, IAQ values every 60 s
+    CCS811_MODE_250MS = 4 //!< Constant Power mode, RAW data every 250 ms
+} ccs811_mode_t;
+
+/**
+ * @brief CCS811 sensor device data structure
+ */
+typedef struct
+{
+    i2c_dev_t i2c_dev;  //!< I2C device handle
+    ccs811_mode_t mode; //!< operation mode
+} ccs811_dev_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev Pointer to the sensor device data structure
+ * @param addr Sensor address
+ * @param port I2C port number
+ * @param sda_gpio GPIO pin number for SDA
+ * @param scl_gpio GPIO pin number for SCL
+ * @returns ESP_OK on success
+ */
+esp_err_t ccs811_init_desc(ccs811_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Pointer to the sensor device data structure
+ * @returns ESP_OK on success
+ */
+esp_err_t ccs811_free_desc(ccs811_dev_t *dev);
+
+/**
+ * @brief Initialize a CCS811 sensor
+ *
+ * The function initializes the CCS811 sensor and checks its availability.
+ *
+ * @param dev Pointer to the sensor device data structure
+ *
+ * @returns ESP_OK on success
+ */
+esp_err_t ccs811_init(ccs811_dev_t *dev);
+
+/**
+ * @brief Set the operation mode of the sensor
+ *
+ * The function sets the operating mode of the sensor. If the parameter
+ * *mode* is either ::CCS811_MODE_1S, ::CCS811_MODE_10S, ::CCS811_MODE_60S
+ * or ::CCS811_MODE_250MS, the sensor starts a periodic measurement with
+ * the specified period. Function ::ccs811_get_results() can then be used at
+ * the same rate to get the results.
+ *
+ * In ::CCS811_MODE_1S, ::CCS811_MODE_10S and ::CCS811_MODE_60S, raw sensor
+ * data as well as IAQ values calculated by the  sensor values are available.
+ * In ::CCS811_MODE_250MS, only raw data are available.
+ *
+ * In case, parameter mode is ::CCS811_MODE_IDLE, the sensor does not perform
+ * any measurements.
+ *
+ * Please note: Mode timings are subject to typical 2% tolerance due
+ * to accuracy of internal sensor clock.
+ *
+ * Please note: After setting the sensor mode, the sensor needs up to
+ * 20 minutes, before accurate readings are generated.
+ *
+ * Please note: When a sensor operating mode is changed to a new mode with
+ * a lower sample rate, e.g., from ::CCS811_MODE_60S to ::CCS811_MODE_1S, it
+ * should be placed in *mode_idle* for at least 10 minutes before enabling
+ * the new mode.
+ *
+ * @param dev Pointer to the sensor device data structure
+ * @param mode Operation mode of the sensor
+ *
+ * @returns ESP_OK on success
+ */
+esp_err_t ccs811_set_mode(ccs811_dev_t *dev, ccs811_mode_t mode);
+
+/**
+ * @brief Get latest IAQ sensor values and/or RAW sensor data
+ *
+ * The function reads the IAQ sensor values (TVOC and eCO2) and/or the raw
+ * sensor data. If some of the results are not needed, the corresponding
+ * pointer parameters can be set to NULL.
+ *
+ * Please note: If the function is called and no new data are available,
+ * e.g., due to the sensor mode time tolerance of 2%, the function still
+ * returns successfully. In this case, the results of the last measurement
+ * are returned and the error code CCS811_ERR_NO_NEW_DATA is set.
+ *
+ * Please note: In ::CCS811_MODE_250MS, only RAW data are available. In
+ * that case, the function fails with error_code CCS811_ERR_NO_IAQ_DATA
+ * if parameters *iaq_tvoc* and *iaq_eco2* are not NULL.
+ *
+ * @param dev pointer to the sensor device data structure
+ * @param iaq_tvoc TVOC total volatile organic compound (0 - 1187 ppb)
+ * @param iaq_eco2 eCO2 equivalent CO2 (400 - 8192 ppm)
+ * @param raw_i current through the sensor used for measuring (0 - 63 uA)
+ * @param raw_v voltage across the sensor measured (0 - 1023 = 1.65 V)
+ *
+ * @returns ESP_OK on success
+ */
+esp_err_t ccs811_get_results(ccs811_dev_t *dev, uint16_t *iaq_tvoc, uint16_t *iaq_eco2, uint8_t *raw_i, uint16_t *raw_v);
+
+/**
+ * @brief Get the resistance of connected NTC thermistor
+ *
+ * CCS811 supports an external interface for connecting a negative thermal
+ * coefficient thermistor (R_NTC) to provide a cost effective and power
+ * efficient means of calculating the local ambient temperature. The sensor
+ * measures the voltage V_NTC across the R_NTC as well as the voltage V_REF
+ * across a connected reference resistor (R_REF).
+ * The function returns the current resistance of R_NTC using the equation
+ *
+ *          R_NTC = R_REF / V_REF * V_NTC
+ *
+ * Using the data sheet of the NTC, the ambient temperature can be calculated.
+ *
+ * @param dev pointer to the sensor device data structure
+ * @param r_ref resistance of R_REF in Ohm
+ * @param[out] res resistance of R_NTC in Ohm
+ * @returns ESP_OK on success
+ */
+esp_err_t ccs811_get_ntc_resistance(ccs811_dev_t *dev, uint32_t r_ref, uint32_t *res);
+
+/**
+ * @brief Set environmental data
+ *
+ * If information about the environment are available from another sensor,
+ * they can be used by CCS811 to compensate gas readings due to
+ * temperature and humidity changes.
+ *
+ * @param dev pointer to the sensor device data structure
+ * @param temperature measured temperature in degree Celsius
+ * @param humidity measured relative humidity in percent
+ * @returns ESP_OK on success
+ */
+esp_err_t ccs811_set_environmental_data(ccs811_dev_t *dev, float temperature, float humidity);
+
+/**
+ * @brief Enable or disable data ready interrupt signal *nINT*
+ *
+ * At the end of each measurement cycle (250ms, 1s, 10s, 60s), CCS811 can
+ * optionally trigger an interrupt. The signal *nINT* is driven low as soon
+ * as new sensor values are ready to read. It will stop being driven low
+ * when sensor data are read with function *ccs811_get_results*.
+ *
+ * The interrupt is disabled by default.
+ *
+ * @param dev pointer to the sensor device data structure
+ * @param enabled if true, the interrupt is enabled, or disabled otherwise
+ * @returns ESP_OK on success
+ */
+esp_err_t ccs811_enable_interrupt(ccs811_dev_t *dev, bool enabled);
+
+/**
+ * @brief Set eCO2 threshold mode for data ready interrupts
+ *
+ * The user task can choose that the data ready interrupt is not generated
+ * every time when new sensor values become ready but only if the eCO2 value
+ * moves from the current range (LOW, MEDIUM, or HIGH) into another range by
+ * more than a hysteresis value. Hysteresis is used to prevent multiple
+ * interrupts close to a threshold.
+ *
+ *   - LOW     below parameter value *low*
+ *   - MEDIUM  between parameter values *low* and *high*
+ *   - HIGH    above parameter value *high* is range HIGH.
+ *
+ * If all parameters have valid values, the function sets the thresholds and
+ * enables the data ready interrupt. Using 0 for all parameters disables the
+ * interrupt.
+ *
+ * The interrupt is disabled by default.
+ *
+ * @param dev pointer to the sensor device data structure
+ * @param low threshold LOW to MEDIUM  (>  400, default 1500)
+ * @param high threshold MEDIUM to HIGH (< 8192, default 2500)
+ * @param hysteresis hysteresis value (default 50)
+ * @returns ESP_OK on success
+ */
+esp_err_t ccs811_set_eco2_thresholds(ccs811_dev_t *dev, uint16_t low, uint16_t high, uint8_t hysteresis);
+
+/**
+ * @brief Get the current baseline value from sensor
+ *
+ * The sensor supports automatic baseline correction over a minimum time of
+ * 24 hours. Using this function, the current baseline value can be saved
+ * before the sensor is powered down. This baseline can then be restored after
+ * sensor is powered up again to continue the automatic baseline process.
+ *
+ * @param dev pointer to the sensor device data structure
+ * @param[out] baseline current baseline value on success, or 0 on error
+ * @returns ESP_OK on success
+ */
+esp_err_t ccs811_get_baseline(ccs811_dev_t *dev, uint16_t *baseline);
+
+/**
+ * @brief Write a previously stored baseline value to the sensor
+ *
+ * The sensor supports automatic baseline correction over a minimum time of
+ * 24 hours. Using this function, a previously saved baseline value be
+ * restored after the sensor is powered up to continue the automatic baseline
+ * process.
+ *
+ * Please note: The baseline must be written after the conditioning period
+ * of 20 min after power up.
+ *
+ * @param dev pointer to the sensor device data structure
+ * @param baseline baseline to be set
+ * @returns ESP_OK on success
+ */
+esp_err_t ccs811_set_baseline(ccs811_dev_t *dev, uint16_t baseline);
+
+#ifdef __cplusplus
+}
+#endif /* End of CPP guard */
+
+/**@}*/
+
+#endif /* __CCS811_H__ */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ccs811/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/color/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,22 @@
+---
+components:
+  - name: color
+    description: |
+      Common library for RGB and HSV colors
+    group: common
+    groups: []
+    code_owners:
+      - name: UncleRus
+    depends:
+      - name: lib8tion
+    thread_safe: N/A
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: MIT
+    copyrights:
+      - name: FastLED
+        year: 2013
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/color/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,13 @@
+if(${IDF_VERSION_MAJOR} STREQUAL 4 AND ${IDF_VERSION_MINOR} STREQUAL 1 AND ${IDF_VERSION_PATCH} STREQUAL 3)
+	message("Not including esp_timer in color")
+    	set(req lib8tion)	
+else()
+    message("Including esp_timer in color")
+    set(req lib8tion)	
+endif()
+
+idf_component_register(
+    SRCS color.c
+    INCLUDE_DIRS .
+    REQUIRES ${req}
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/color/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2013 FastLED
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/color/color.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,1102 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013 FastLED
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "color.h"
+#include <math.h>
+#include <lib8tion.h>
+
+////////////////////////////////////////////////////////////////////////////////
+
+#define APPLY_DIMMING(X) (X)
+#define HSV_SECTION_6 (0x20)
+#define HSV_SECTION_3 (0x40)
+
+rgb_t hsv2rgb_raw(hsv_t hsv)
+{
+    // Convert hue, saturation and brightness ( HSV/HSB ) to RGB
+    // "Dimming" is used on saturation and brightness to make
+    // the output more visually linear.
+
+    // Apply dimming curves
+    uint8_t value = APPLY_DIMMING(hsv.val);
+    uint8_t saturation = hsv.sat;
+
+    // The brightness floor is minimum number that all of
+    // R, G, and B will be set to.
+    uint8_t invsat = APPLY_DIMMING(255 - saturation);
+    uint8_t brightness_floor = (value * invsat) / 256;
+
+    // The color amplitude is the maximum amount of R, G, and B
+    // that will be added on top of the brightness_floor to
+    // create the specific hue desired.
+    uint8_t color_amplitude = value - brightness_floor;
+
+    // Figure out which section of the hue wheel we're in,
+    // and how far offset we are withing that section
+    uint8_t section = hsv.hue / HSV_SECTION_3; // 0..2
+    uint8_t offset = hsv.hue % HSV_SECTION_3;  // 0..63
+
+    uint8_t rampup = offset; // 0..63
+    uint8_t rampdown = (HSV_SECTION_3 - 1) - offset; // 63..0
+
+    // We now scale rampup and rampdown to a 0-255 range -- at least
+    // in theory, but here's where architecture-specific decsions
+    // come in to play:
+    // To scale them up to 0-255, we'd want to multiply by 4.
+    // But in the very next step, we multiply the ramps by other
+    // values and then divide the resulting product by 256.
+    // So which is faster?
+    //   ((ramp * 4) * othervalue) / 256
+    // or
+    //   ((ramp    ) * othervalue) /  64
+    // It depends on your processor architecture.
+    // On 8-bit AVR, the "/ 256" is just a one-cycle register move,
+    // but the "/ 64" might be a multicycle shift process. So on AVR
+    // it's faster do multiply the ramp values by four, and then
+    // divide by 256.
+    // On ARM, the "/ 256" and "/ 64" are one cycle each, so it's
+    // faster to NOT multiply the ramp values by four, and just to
+    // divide the resulting product by 64 (instead of 256).
+    // Moral of the story: trust your profiler, not your insticts.
+
+    // Since there's an AVR assembly version elsewhere, we'll
+    // assume what we're on an architecture where any number of
+    // bit shifts has roughly the same cost, and we'll remove the
+    // redundant math at the source level:
+
+    //  // scale up to 255 range
+    //  //rampup *= 4; // 0..252
+    //  //rampdown *= 4; // 0..252
+
+    // compute color-amplitude-scaled-down versions of rampup and rampdown
+    uint8_t rampup_amp_adj = (rampup * color_amplitude) / (256 / 4);
+    uint8_t rampdown_amp_adj = (rampdown * color_amplitude) / (256 / 4);
+
+    // add brightness_floor offset to everything
+    uint8_t rampup_adj_with_floor = rampup_amp_adj + brightness_floor;
+    uint8_t rampdown_adj_with_floor = rampdown_amp_adj + brightness_floor;
+
+    rgb_t rgb;
+    if (section)
+    {
+        if (section == 1)
+        {
+            // section 1: 0x40..0x7F
+            rgb.r = brightness_floor;
+            rgb.g = rampdown_adj_with_floor;
+            rgb.b = rampup_adj_with_floor;
+        }
+        else
+        {
+            // section 2; 0x80..0xBF
+            rgb.r = rampup_adj_with_floor;
+            rgb.g = brightness_floor;
+            rgb.b = rampdown_adj_with_floor;
+        }
+    }
+    else
+    {
+        // section 0: 0x00..0x3F
+        rgb.r = rampdown_adj_with_floor;
+        rgb.g = rampup_adj_with_floor;
+        rgb.b = brightness_floor;
+    }
+
+    return rgb;
+}
+
+rgb_t hsv2rgb_spectrum(hsv_t hsv)
+{
+    hsv.hue = scale8(hsv.hue, HUE_MAX_RAW);
+    return hsv2rgb_raw(hsv);
+}
+
+#define K255 255
+#define K171 171
+#define K170 170
+#define K85  85
+
+rgb_t hsv2rgb_rainbow(hsv_t hsv)
+{
+    // Yellow has a higher inherent brightness than
+    // any other color; 'pure' yellow is perceived to
+    // be 93% as bright as white.  In order to make
+    // yellow appear the correct relative brightness,
+    // it has to be rendered brighter than all other
+    // colors.
+    // Level Y1 is a moderate boost, the default.
+    // Level Y2 is a strong boost.
+    const uint8_t Y1 = 1;
+    const uint8_t Y2 = 0;
+
+    // G2: Whether to divide all greens by two.
+    // Depends GREATLY on your particular LEDs
+    const uint8_t G2 = 0;
+
+    // Gscale: what to scale green down by.
+    // Depends GREATLY on your particular LEDs
+    const uint8_t Gscale = 0;
+
+
+    uint8_t hue = hsv.hue;
+    uint8_t sat = hsv.sat;
+    uint8_t val = hsv.val;
+
+    uint8_t offset = hue & 0x1F; // 0..31
+
+    // offset8 = offset * 8
+    uint8_t offset8 = offset << 3;
+    uint8_t third = scale8(offset8, (256 / 3)); // max = 85
+
+    uint8_t r, g, b;
+
+    if (!(hue & 0x80))
+    {
+        // 0XX
+        if (!(hue & 0x40))
+        {
+            // 00X
+            //section 0-1
+            if (!(hue & 0x20))
+            {
+                // 000
+                //case 0: // R -> O
+                r = K255 - third;
+                g = third;
+                b = 0;
+            }
+            else
+            {
+                // 001
+                //case 1: // O -> Y
+                if (Y1)
+                {
+                    r = K171;
+                    g = K85 + third;
+                    b = 0;
+                }
+                if (Y2)
+                {
+                    r = K170 + third;
+                    //uint8_t twothirds = (third << 1);
+                    uint8_t twothirds = scale8(offset8, ((256 * 2) / 3)); // max=170
+                    g = K85 + twothirds;
+                    b = 0;
+                }
+            }
+        }
+        else
+        {
+            //01X
+            // section 2-3
+            if (!(hue & 0x20))
+            {
+                // 010
+                //case 2: // Y -> G
+                if (Y1)
+                {
+                    //uint8_t twothirds = (third << 1);
+                    uint8_t twothirds = scale8(offset8, ((256 * 2) / 3)); // max=170
+                    r = K171 - twothirds;
+                    g = K170 + third;
+                    b = 0;
+                }
+                if (Y2)
+                {
+                    r = K255 - offset8;
+                    g = K255;
+                    b = 0;
+                }
+            }
+            else
+            {
+                // 011
+                // case 3: // G -> A
+                r = 0;
+                g = K255 - third;
+                b = third;
+            }
+        }
+    }
+    else
+    {
+        // section 4-7
+        // 1XX
+        if (!(hue & 0x40))
+        {
+            // 10X
+            if (!(hue & 0x20))
+            {
+                // 100
+                //case 4: // A -> B
+                r = 0;
+                //uint8_t twothirds = (third << 1);
+                uint8_t twothirds = scale8(offset8, ((256 * 2) / 3)); // max=170
+                g = K171 - twothirds; //K170?
+                b = K85 + twothirds;
+
+            }
+            else
+            {
+                // 101
+                //case 5: // B -> P
+                r = third;
+                g = 0;
+                b = K255 - third;
+
+            }
+        }
+        else
+        {
+            if (!(hue & 0x20))
+            {
+                // 110
+                //case 6: // P -- K
+                r = K85 + third;
+                g = 0;
+                b = K171 - third;
+
+            }
+            else
+            {
+                // 111
+                //case 7: // K -> R
+                r = K170 + third;
+                g = 0;
+                b = K85 - third;
+
+            }
+        }
+    }
+
+    // This is one of the good places to scale the green down,
+    // although the client can scale green down as well.
+    if (G2)
+        g = g >> 1;
+    if (Gscale)
+        g = scale8_video(g, Gscale);
+
+    // Scale down colors if we're desaturated at all
+    // and add the brightness_floor to r, g, and b.
+    if (sat != 255)
+    {
+        if (sat == 0)
+        {
+            r = 255;
+            b = 255;
+            g = 255;
+        }
+        else
+        {
+            uint8_t desat = 255 - sat;
+            desat = scale8_video(desat, desat);
+
+            uint8_t satscale = 255 - desat;
+            //satscale = sat; // uncomment to revert to pre-2021 saturation behavior
+
+            //nscale8x3_video( r, g, b, sat);
+            r = scale8(r, satscale);
+            g = scale8(g, satscale);
+            b = scale8(b, satscale);
+
+            uint8_t brightness_floor = desat;
+            r += brightness_floor;
+            g += brightness_floor;
+            b += brightness_floor;
+        }
+    }
+
+    // Now scale everything down if we're at value < 255.
+    if (val != 255)
+    {
+
+        val = scale8_video(val, val);
+        if (val == 0)
+        {
+            r = 0;
+            g = 0;
+            b = 0;
+        }
+        else
+        {
+            // nscale8x3_video( r, g, b, val);
+            r = scale8(r, val);
+            g = scale8(g, val);
+            b = scale8(b, val);
+        }
+    }
+
+    return rgb_from_values(r, g, b);
+}
+
+#define FIXFRAC8(N,D) (((N) * 256) / (D))
+
+// This function is only an approximation, and it is not
+// nearly as fast as the normal HSV-to-RGB conversion.
+// See extended notes in the .h file.
+hsv_t rgb2hsv_approximate(rgb_t rgb)
+{
+    uint8_t r = rgb.r;
+    uint8_t g = rgb.g;
+    uint8_t b = rgb.b;
+    uint8_t h, s, v;
+
+    // find desaturation
+    uint8_t desat = 255;
+    if (r < desat) desat = r;
+    if (g < desat) desat = g;
+    if (b < desat) desat = b;
+
+    // remove saturation from all channels
+    r -= desat;
+    g -= desat;
+    b -= desat;
+
+    s = 255 - desat;
+    if (s != 255)
+    {
+        // undo 'dimming' of saturation
+        s = 255 - sqrt16((255 - s) * 256);
+    }
+    // without lib8tion: float ... ew ... sqrt... double ew, or rather, ew ^ 0.5
+    // if( s != 255 ) s = (255 - (256.0 * sqrt( (float)(255-s) / 256.0)));
+
+    // at least one channel is now zero
+    // if all three channels are zero, we had a
+    // shade of gray.
+    if ((r + g + b) == 0)
+    {
+        // we pick hue zero for no special reason
+        hsv_t res = {
+           .h = 0, .s = 0, .v = 255 - s
+        };
+        return res;
+    }
+
+    // scale all channels up to compensate for desaturation
+    if (s < 255)
+    {
+        if (s == 0)
+            s = 1;
+        uint32_t scaleup = 65535 / (s);
+        r = ((uint32_t) (r) * scaleup) / 256;
+        g = ((uint32_t) (g) * scaleup) / 256;
+        b = ((uint32_t) (b) * scaleup) / 256;
+    }
+
+    uint16_t total = r + g + b;
+
+    // scale all channels up to compensate for low values
+    if (total < 255)
+    {
+        if (total == 0)
+            total = 1;
+        uint32_t scaleup = 65535 / (total);
+        r = ((uint32_t) (r) * scaleup) / 256;
+        g = ((uint32_t) (g) * scaleup) / 256;
+        b = ((uint32_t) (b) * scaleup) / 256;
+    }
+
+    if (total > 255)
+    {
+        v = 255;
+    }
+    else
+    {
+        v = qadd8(desat, total);
+        // undo 'dimming' of brightness
+        if (v != 255)
+            v = sqrt16(v * 256);
+        // without lib8tion: float ... ew ... sqrt... double ew, or rather, ew ^ 0.5
+        // if( v != 255) v = (256.0 * sqrt( (float)(v) / 256.0));
+    }
+
+    // since this wasn't a pure shade of gray,
+    // the interesting question is what hue is it
+
+    // start with which channel is highest
+    // (ties don't matter)
+    uint8_t highest = r;
+    if (g > highest) highest = g;
+    if (b > highest) highest = b;
+
+    if (highest == r)
+    {
+        // Red is highest.
+        // Hue could be Purple/Pink-Red,Red-Orange,Orange-Yellow
+        if (g == 0)
+        {
+            // if green is zero, we're in Purple/Pink-Red
+            h = (HUE_PURPLE + HUE_PINK) / 2;
+            h += scale8(qsub8(r, 128), FIXFRAC8(48, 128));
+        }
+        else if ((r - g) > g)
+        {
+            // if R-G > G then we're in Red-Orange
+            h = HUE_RED;
+            h += scale8(g, FIXFRAC8(32, 85));
+        }
+        else
+        {
+            // R-G < G, we're in Orange-Yellow
+            h = HUE_ORANGE;
+            h += scale8(qsub8((g - 85) + (171 - r), 4), FIXFRAC8(32, 85)); //221
+        }
+
+    }
+    else if (highest == g)
+    {
+        // Green is highest
+        // Hue could be Yellow-Green, Green-Aqua
+        if (b == 0)
+        {
+            // if Blue is zero, we're in Yellow-Green
+            //   G = 171..255
+            //   R = 171..  0
+            h = HUE_YELLOW;
+            uint8_t radj = scale8(qsub8(171, r), 47); //171..0 -> 0..171 -> 0..31
+            uint8_t gadj = scale8(qsub8(g, 171), 96); //171..255 -> 0..84 -> 0..31;
+            uint8_t rgadj = radj + gadj;
+            uint8_t hueadv = rgadj / 2;
+            h += hueadv;
+            //h += scale8( qadd8( 4, qadd8((g - 128), (128 - r))),
+            //             FIXFRAC8(32,255)); //
+        }
+        else
+        {
+            // if Blue is nonzero we're in Green-Aqua
+            if ((g - b) > b)
+            {
+                h = HUE_GREEN;
+                h += scale8(b, FIXFRAC8(32, 85));
+            }
+            else
+            {
+                h = HUE_AQUA;
+                h += scale8(qsub8(b, 85), FIXFRAC8(8, 42));
+            }
+        }
+
+    }
+    else /* highest == b */
+    {
+        // Blue is highest
+        // Hue could be Aqua/Blue-Blue, Blue-Purple, Purple-Pink
+        if (r == 0)
+        {
+            // if red is zero, we're in Aqua/Blue-Blue
+            h = HUE_AQUA + ((HUE_BLUE - HUE_AQUA) / 4);
+            h += scale8(qsub8(b, 128), FIXFRAC8(24, 128));
+        }
+        else if ((b - r) > r)
+        {
+            // B-R > R, we're in Blue-Purple
+            h = HUE_BLUE;
+            h += scale8(r, FIXFRAC8(32, 85));
+        }
+        else
+        {
+            // B-R < R, we're in Purple-Pink
+            h = HUE_PURPLE;
+            h += scale8(qsub8(r, 85), FIXFRAC8(32, 85));
+        }
+    }
+
+    h += 1;
+
+    return hsv_from_values(h, s, v);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+rgb_t rgb_heat_color(uint8_t temperature)
+{
+    rgb_t heatcolor;
+
+    // Scale 'heat' down from 0-255 to 0-191,
+    // which can then be easily divided into three
+    // equal 'thirds' of 64 units each.
+    uint8_t t192 = scale8_video(temperature, 191);
+
+    // calculate a value that ramps up from
+    // zero to 255 in each 'third' of the scale.
+    uint8_t heatramp = t192 & 0x3F; // 0..63
+    heatramp <<= 2; // scale up to 0..252
+
+    // now figure out which third of the spectrum we're in:
+    if (t192 & 0x80)
+    {
+        // we're in the hottest third
+        heatcolor.r = 255; // full red
+        heatcolor.g = 255; // full green
+        heatcolor.b = heatramp; // ramp up blue
+    }
+    else if (t192 & 0x40)
+    {
+        // we're in the middle third
+        heatcolor.r = 255; // full red
+        heatcolor.g = heatramp; // ramp up green
+        heatcolor.b = 0; // no blue
+    }
+    else
+    {
+        // we're in the coolest third
+        heatcolor.r = heatramp; // ramp up red
+        heatcolor.g = 0; // no green
+        heatcolor.b = 0; // no blue
+    }
+
+    return heatcolor;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+typedef uint16_t saccum87;
+
+void hsv_fill_solid_hsv(hsv_t *target, hsv_t color, size_t num)
+{
+    for (size_t i = 0; i < num; ++i)
+        target[i] = color;
+}
+
+void rgb_fill_solid_hsv(rgb_t *target, hsv_t color, size_t num)
+{
+    rgb_t rgb = hsv2rgb_rainbow(color);
+    for (size_t i = 0; i < num; ++i)
+        target[i] = rgb;
+}
+
+void rgb_fill_solid_rgb(rgb_t *target, rgb_t color, size_t num)
+{
+    for (size_t i = 0; i < num; ++i)
+        target[i] = color;
+}
+
+void hsv_fill_gradient_hsv(hsv_t *target, size_t startpos, hsv_t startcolor, size_t endpos, hsv_t endcolor,
+        color_gradient_direction_t direction)
+{
+    // if the points are in the wrong order, straighten them
+    if (endpos < startpos)
+    {
+        size_t t = endpos;
+        hsv_t tc = endcolor;
+        endcolor = startcolor;
+        endpos = startpos;
+        startpos = t;
+        startcolor = tc;
+    }
+
+    // If we're fading toward black (val=0) or white (sat=0),
+    // then set the endhue to the starthue.
+    // This lets us ramp smoothly to black or white, regardless
+    // of what 'hue' was set in the endcolor (since it doesn't matter)
+    if (endcolor.value == 0 || endcolor.saturation == 0)
+        endcolor.hue = startcolor.hue;
+
+    // Similarly, if we're fading in from black (val=0) or white (sat=0)
+    // then set the starthue to the endhue.
+    // This lets us ramp smoothly up from black or white, regardless
+    // of what 'hue' was set in the startcolor (since it doesn't matter)
+    if (startcolor.value == 0 || startcolor.saturation == 0)
+        startcolor.hue = endcolor.hue;
+
+    saccum87 huedistance87;
+    saccum87 satdistance87;
+    saccum87 valdistance87;
+
+    satdistance87 = (endcolor.sat - startcolor.sat) << 7;
+    valdistance87 = (endcolor.val - startcolor.val) << 7;
+
+    uint8_t huedelta8 = endcolor.hue - startcolor.hue;
+
+    if (direction == COLOR_SHORTEST_HUES)
+    {
+        direction = COLOR_FORWARD_HUES;
+        if (huedelta8 > 127)
+            direction = COLOR_BACKWARD_HUES;
+    }
+
+    if (direction == COLOR_LONGEST_HUES)
+    {
+        direction = COLOR_FORWARD_HUES;
+        if (huedelta8 < 128)
+            direction = COLOR_BACKWARD_HUES;
+    }
+
+    if (direction == COLOR_FORWARD_HUES)
+    {
+        huedistance87 = huedelta8 << 7;
+    }
+    else /* direction == BACKWARD_HUES */
+    {
+        huedistance87 = (uint8_t) (256 - huedelta8) << 7;
+        huedistance87 = -huedistance87;
+    }
+
+    size_t pixeldistance = endpos - startpos;
+    int16_t divisor = pixeldistance ? pixeldistance : 1;
+
+    saccum87 huedelta87 = huedistance87 / divisor;
+    saccum87 satdelta87 = satdistance87 / divisor;
+    saccum87 valdelta87 = valdistance87 / divisor;
+
+    huedelta87 *= 2;
+    satdelta87 *= 2;
+    valdelta87 *= 2;
+
+    accum88 hue88 = startcolor.hue << 8;
+    accum88 sat88 = startcolor.sat << 8;
+    accum88 val88 = startcolor.val << 8;
+    for (size_t i = startpos; i <= endpos; ++i)
+    {
+        target[i].hue = hue88 >> 8;
+        target[i].sat = sat88 >> 8;
+        target[i].val = val88 >> 8;
+        hue88 += huedelta87;
+        sat88 += satdelta87;
+        val88 += valdelta87;
+    }
+}
+
+void rgb_fill_gradient_hsv(rgb_t *target, size_t startpos, hsv_t startcolor, size_t endpos, hsv_t endcolor,
+        color_gradient_direction_t direction)
+{
+    // if the points are in the wrong order, straighten them
+    if (endpos < startpos)
+    {
+        size_t t = endpos;
+        hsv_t tc = endcolor;
+        endcolor = startcolor;
+        endpos = startpos;
+        startpos = t;
+        startcolor = tc;
+    }
+
+    // If we're fading toward black (val=0) or white (sat=0),
+    // then set the endhue to the starthue.
+    // This lets us ramp smoothly to black or white, regardless
+    // of what 'hue' was set in the endcolor (since it doesn't matter)
+    if (endcolor.value == 0 || endcolor.saturation == 0)
+        endcolor.hue = startcolor.hue;
+
+    // Similarly, if we're fading in from black (val=0) or white (sat=0)
+    // then set the starthue to the endhue.
+    // This lets us ramp smoothly up from black or white, regardless
+    // of what 'hue' was set in the startcolor (since it doesn't matter)
+    if (startcolor.value == 0 || startcolor.saturation == 0)
+        startcolor.hue = endcolor.hue;
+
+    saccum87 huedistance87;
+    saccum87 satdistance87;
+    saccum87 valdistance87;
+
+    satdistance87 = (endcolor.sat - startcolor.sat) << 7;
+    valdistance87 = (endcolor.val - startcolor.val) << 7;
+
+    uint8_t huedelta8 = endcolor.hue - startcolor.hue;
+
+    if (direction == COLOR_SHORTEST_HUES)
+    {
+        direction = COLOR_FORWARD_HUES;
+        if (huedelta8 > 127)
+            direction = COLOR_BACKWARD_HUES;
+    }
+
+    if (direction == COLOR_LONGEST_HUES)
+    {
+        direction = COLOR_FORWARD_HUES;
+        if (huedelta8 < 128)
+            direction = COLOR_BACKWARD_HUES;
+    }
+
+    if (direction == COLOR_FORWARD_HUES)
+    {
+        huedistance87 = huedelta8 << 7;
+    }
+    else /* direction == BACKWARD_HUES */
+    {
+        huedistance87 = (uint8_t) (256 - huedelta8) << 7;
+        huedistance87 = -huedistance87;
+    }
+
+    size_t pixeldistance = endpos - startpos;
+    int16_t divisor = pixeldistance ? pixeldistance : 1;
+
+    saccum87 huedelta87 = huedistance87 / divisor;
+    saccum87 satdelta87 = satdistance87 / divisor;
+    saccum87 valdelta87 = valdistance87 / divisor;
+
+    huedelta87 *= 2;
+    satdelta87 *= 2;
+    valdelta87 *= 2;
+
+    accum88 hue88 = startcolor.hue << 8;
+    accum88 sat88 = startcolor.sat << 8;
+    accum88 val88 = startcolor.val << 8;
+    for (size_t i = startpos; i <= endpos; ++i)
+    {
+        target[i] = hsv2rgb_rainbow(hsv_from_values(hue88 >> 8, sat88 >> 8, val88 >> 8));
+        hue88 += huedelta87;
+        sat88 += satdelta87;
+        val88 += valdelta87;
+    }
+}
+
+void rgb_fill_gradient_rgb(rgb_t *leds, size_t startpos, rgb_t startcolor, size_t endpos, rgb_t endcolor)
+{
+    // if the points are in the wrong order, straighten them
+    if (endpos < startpos)
+    {
+        size_t t = endpos;
+        rgb_t tc = endcolor;
+        endcolor = startcolor;
+        endpos = startpos;
+        startpos = t;
+        startcolor = tc;
+    }
+
+    saccum87 rdistance87;
+    saccum87 gdistance87;
+    saccum87 bdistance87;
+
+    rdistance87 = (endcolor.r - startcolor.r) << 7;
+    gdistance87 = (endcolor.g - startcolor.g) << 7;
+    bdistance87 = (endcolor.b - startcolor.b) << 7;
+
+    size_t pixeldistance = endpos - startpos;
+    int16_t divisor = pixeldistance ? pixeldistance : 1;
+
+    saccum87 rdelta87 = rdistance87 / divisor;
+    saccum87 gdelta87 = gdistance87 / divisor;
+    saccum87 bdelta87 = bdistance87 / divisor;
+
+    rdelta87 *= 2;
+    gdelta87 *= 2;
+    bdelta87 *= 2;
+
+    accum88 r88 = startcolor.r << 8;
+    accum88 g88 = startcolor.g << 8;
+    accum88 b88 = startcolor.b << 8;
+    for (uint16_t i = startpos; i <= endpos; ++i)
+    {
+        leds[i].r = r88 >> 8;
+        leds[i].g = g88 >> 8;
+        leds[i].b = b88 >> 8;
+        r88 += rdelta87;
+        g88 += gdelta87;
+        b88 += bdelta87;
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+hsv_t color_from_palette_hsv(const hsv_t *palette, uint8_t pal_size, uint8_t index, uint8_t brightness, bool blend)
+{
+    uint8_t div = 256 / pal_size;
+
+    uint8_t hi = index / div;
+    uint8_t lo = index % div;
+
+    const hsv_t *entry = palette + hi;
+
+    uint8_t hue1   = entry->hue;
+    uint8_t sat1   = entry->sat;
+    uint8_t val1   = entry->val;
+
+    if (blend && lo)
+    {
+        if (hi == pal_size - 1)
+            entry = palette;
+        else
+            ++entry;
+
+        uint8_t f2 = lo * pal_size;
+        uint8_t f1 = 255 - f2;
+
+        uint8_t hue2  = entry->hue;
+        uint8_t sat2  = entry->sat;
+        uint8_t val2  = entry->val;
+
+        // Now some special casing for blending to or from
+        // either black or white.  Black and white don't have
+        // proper 'hue' of their own, so when ramping from
+        // something else to/from black/white, we set the 'hue'
+        // of the black/white color to be the same as the hue
+        // of the other color, so that you get the expected
+        // brightness or saturation ramp, with hue staying
+        // constant:
+
+        // If we are starting from white (sat=0)
+        // or black (val=0), adopt the target hue.
+        if (sat1 == 0 || val1 == 0)
+            hue1 = hue2;
+
+        // If we are ending at white (sat=0)
+        // or black (val=0), adopt the starting hue.
+        if (sat2 == 0 || val2 == 0)
+            hue2 = hue1;
+
+        sat1 = scale8(sat1, f1);
+        val1 = scale8(val1, f1);
+
+        sat2 = scale8(sat2, f2);
+        val2 = scale8(val2, f2);
+
+        // These sums can't overflow, so no qadd8 needed.
+        sat1 += sat2;
+        val1 += val2;
+
+        uint8_t delta_hue = (uint8_t) (hue2 - hue1);
+        if (delta_hue & 0x80)
+            // go backwards
+            hue1 -= scale8(256 - delta_hue, f2);
+        else
+            // go forwards
+            hue1 += scale8(delta_hue, f2);
+    }
+
+    if (brightness != 255)
+        val1 = scale8_video(val1, brightness);
+
+    return hsv_from_values(hue1, sat1, val1);
+}
+
+#include <stdlib.h>
+
+rgb_t color_from_palette_rgb(const rgb_t *palette, uint8_t pal_size, uint8_t index, uint8_t brightness, bool blend)
+{
+    uint8_t div = 256 / pal_size;
+
+    uint8_t hi = index / div;
+    uint8_t lo = index % div;
+
+    const rgb_t *entry = palette + hi;
+
+    uint8_t red1   = entry->red;
+    uint8_t green1 = entry->green;
+    uint8_t blue1  = entry->blue;
+
+    if (blend && lo)
+    {
+        if (hi == pal_size - 1)
+            entry = palette;
+        else
+            ++entry;
+
+        uint8_t f2 = lo * pal_size;
+        uint8_t f1 = 255 - f2;
+
+        uint8_t red2 = entry->red;
+        red1 = scale8(red1, f1);
+        red2 = scale8(red2, f2);
+        red1 += red2;
+
+        uint8_t green2 = entry->green;
+        green1 = scale8(green1, f1);
+        green2 = scale8(green2, f2);
+        green1 += green2;
+
+        uint8_t blue2 = entry->blue;
+        blue1 = scale8(blue1, f1);
+        blue2 = scale8(blue2, f2);
+        blue1 += blue2;
+    }
+
+    if (brightness != 255)
+    {
+        if (brightness)
+        {
+            ++brightness; // adjust for rounding
+            // Now, since brightness is nonzero, we don't need the full scale8_video logic;
+            // we can just to scale8 and then add one (unless scale8 fixed) to all nonzero inputs.
+            if (red1)
+                red1 = scale8(red1, brightness);
+            if (green1)
+                green1 = scale8(green1, brightness);
+            if (blue1)
+                blue1 = scale8(blue1, brightness);
+        }
+        else
+        {
+            red1 = 0;
+            green1 = 0;
+            blue1 = 0;
+        }
+    }
+
+    return rgb_from_values(red1, green1, blue1);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+hsv_t blend(hsv_t existing, hsv_t overlay, fract8 amount, color_gradient_direction_t direction)
+{
+    if (amount == 0)
+        return existing;
+
+    if (amount == 255)
+    {
+        existing = overlay;
+        return existing;
+    }
+
+    fract8 amount_of_keep = 255 - amount;
+    uint8_t huedelta8 = overlay.hue - existing.hue;
+
+    if (direction == COLOR_SHORTEST_HUES)
+    {
+        direction = COLOR_FORWARD_HUES;
+        if (huedelta8 > 127)
+            direction = COLOR_BACKWARD_HUES;
+    }
+
+    if (direction == COLOR_LONGEST_HUES)
+    {
+        direction = COLOR_FORWARD_HUES;
+        if (huedelta8 < 128)
+            direction = COLOR_BACKWARD_HUES;
+    }
+
+    if (direction == COLOR_FORWARD_HUES)
+    {
+        existing.hue = existing.hue + scale8(huedelta8, amount);
+    }
+    else /* direction == BACKWARD_HUES */
+    {
+        huedelta8 = -huedelta8;
+        existing.hue = existing.hue - scale8(huedelta8, amount);
+    }
+
+    existing.sat = scale8(existing.sat, amount_of_keep) + scale8(overlay.sat, amount);
+    existing.val = scale8(existing.val, amount_of_keep) + scale8(overlay.val, amount);
+
+    return existing;
+}
+
+void blur1d(rgb_t *leds, size_t num_leds, fract8 blur_amount)
+{
+    uint8_t keep = 255 - blur_amount;
+    uint8_t seep = blur_amount >> 1;
+    rgb_t carryover = rgb_from_code(0);
+    for (size_t i = 0; i < num_leds; ++i)
+    {
+        rgb_t cur = leds[i];
+        rgb_t part = cur;
+        part = rgb_scale(part, seep);
+        cur = rgb_add_rgb(rgb_scale(cur, keep), carryover);
+        if (i)
+            leds[i - 1] = rgb_add_rgb(leds[i - 1], part);
+        leds[i] = cur;
+        carryover = part;
+    }
+}
+
+void blur_columns(rgb_t *leds, size_t width, size_t height, fract8 blur_amount, xy_to_offs_cb xy, void *ctx)
+{
+    // blur columns
+    uint8_t keep = 255 - blur_amount;
+    uint8_t seep = blur_amount >> 1;
+    for (size_t col = 0; col < width; ++col)
+    {
+        rgb_t carryover = rgb_from_code(0);
+        for (size_t i = 0; i < height; ++i)
+        {
+            size_t offs = xy(ctx, col, i);
+            rgb_t cur = leds[offs];
+            rgb_t part = cur;
+            part = rgb_scale(part, seep);
+            cur = rgb_add_rgb(rgb_scale(cur, keep), carryover);
+            if (i)
+            {
+                size_t prev_offs = xy(ctx, col, i - 1);
+                leds[prev_offs] = rgb_add_rgb(leds[prev_offs], part);
+            }
+            leds[offs] = cur;
+            carryover = part;
+        }
+    }
+}
+
+void blur_rows(rgb_t *leds, size_t width, size_t height, fract8 blur_amount, xy_to_offs_cb xy, void *ctx)
+{
+    // blur rows same as columns, for irregular matrix
+    uint8_t keep = 255 - blur_amount;
+    uint8_t seep = blur_amount >> 1;
+    for (size_t row = 0; row < height; row++)
+    {
+        rgb_t carryover = rgb_from_code(0);
+        for (size_t i = 0; i < width; i++)
+        {
+            size_t offs = xy(ctx, i, row);
+            rgb_t cur = leds[offs];
+            rgb_t part = cur;
+            part = rgb_scale(part, seep);
+            cur = rgb_add_rgb(rgb_scale(cur, keep), carryover);
+            if (i)
+            {
+                size_t prev_offs = xy(ctx, i - 1, row);
+                leds[prev_offs] = rgb_add_rgb(leds[prev_offs], part);
+            }
+            leds[offs] = cur;
+            carryover = part;
+        }
+    }
+}
+
+void blur2d(rgb_t *leds, size_t width, size_t height, fract8 blur_amount, xy_to_offs_cb xy, void *ctx)
+{
+    blur_rows(leds, width, height, blur_amount, xy, ctx);
+    blur_columns(leds, width, height, blur_amount, xy, ctx);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+uint8_t apply_gamma2brightness(uint8_t brightness, float gamma)
+{
+    float orig = (float)brightness / 255.0;
+    float adj = powf(orig, gamma) * 255.0;
+    uint8_t result = (uint8_t)adj;
+    if (brightness > 0 && !result)
+        result = 1; // never gamma-adjust a positive number down to zero
+    return result;
+}
+
+rgb_t apply_gamma2rgb(rgb_t c, float gamma)
+{
+    rgb_t res = {
+        .r = apply_gamma2brightness(c.r, gamma),
+        .g = apply_gamma2brightness(c.g, gamma),
+        .b = apply_gamma2brightness(c.b, gamma),
+    };
+    return res;
+}
+
+rgb_t apply_gamma2rgb_channels(rgb_t c, float gamma_r, float gamma_g, float gamma_b)
+{
+    rgb_t res = {
+        .r = apply_gamma2brightness(c.r, gamma_r),
+        .g = apply_gamma2brightness(c.g, gamma_g),
+        .b = apply_gamma2brightness(c.b, gamma_b),
+    };
+    return res;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/color/color.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,366 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013 FastLED
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file color.h
+ * @defgroup color color
+ * @{
+ *
+ * Functions for RGB and HSV colors
+ *
+ * Ported from FastLED
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+#ifndef __COLOR_H__
+#define __COLOR_H__
+
+#include <stdint.h>
+
+#include "rgb.h"
+#include "hsv.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define HUE_MAX_RAINBOW  255
+#define HUE_MAX_SPECTRUM 255
+#define HUE_MAX_RAW      191
+
+////////////////////////////////////////////////////////////////////////////////
+// Color conversion
+
+/**
+ * @brief Convert HSV to RGB using balanced rainbow
+ *
+ * Convert a hue, saturation, and value to RGB using a visually balanced
+ * rainbow (vs a straight mathematical spectrum). This 'rainbow' yields
+ * better yellow and orange than a straight 'spectrum'.
+ *
+ * @note here hue is 0-255, not just 0-191
+ *
+ * @param hsv   HSV color
+ * @return      RGB color
+ */
+rgb_t hsv2rgb_rainbow(hsv_t hsv);
+
+/**
+ * @brief Convert HSV to RGB using mathematically straight spectrum
+ *
+ * Convert a hue, saturation, and value to RGB using a mathematically straight
+ * spectrum (vs a visually balanced rainbow). This 'spectrum' will have more
+ * green & blue than a 'rainbow', and less yellow and orange.
+ *
+ * @note here hue is 0-255, not just 0-191
+ *
+ * @param hsv   HSV color
+ * @return      RGB color
+ */
+rgb_t hsv2rgb_spectrum(hsv_t hsv);
+
+/**
+ * @brief Convert HSV to RGB using spectrum
+ *
+ * Convert hue, saturation, and value to RGB. This 'spectrum' conversion will
+ * be more green & blue than a real 'rainbow', and the hue is specified just in
+ * the range 0-191.
+ * Together, these result in a slightly faster conversion speed, at the expense
+ * of color balance.
+ *
+ * @note Hue is 0-191 only! Saturation & value are 0-255 each.
+ *
+ * @param hsv   HSV color
+ * @return      RGB color
+ */
+rgb_t hsv2rgb_raw(hsv_t hsv);
+
+/**
+ * @brief Recover approximate HSV values from RGB
+ *
+ * This function is a long-term work in process; expect results to change
+ * slightly over time as this function is refined and improved.
+ *
+ * This function is most accurate when the input is an RGB color that came
+ * from a fully-saturated HSV color to start with.
+ *
+ * This function is not nearly as fast as HSV-to-RGB. It is provided for
+ * those situations when the need for this function cannot be avoided, or
+ * when extremely high performance is not needed.
+ *
+ * Why is this 'only' an "approximation"? Not all RGB colors have HSV
+ * equivalents! For example, there is no HSV value that will ever convert
+ * to RGB(255,255,0) using the code provided in this library.
+ * So if you try to convert RGB(255,255,0) 'back' to HSV, you'll necessarily
+ * get only an approximation.
+ * Emphasis has been placed on getting the 'hue' as close as usefully possible,
+ * but even that's a bit of a challenge.
+ * The 8-bit HSV and 8-bit RGB color spaces are not a "bijection".
+ * Nevertheless, this function does a pretty good job, particularly at
+ * recovering the 'hue' from fully saturated RGB colors that originally came
+ * from HSV rainbow colors.
+ * The more desaturated the original RGB color is, the rougher the approximation,
+ * and the less accurate the results.
+ *
+ * @param rgb   RGB color
+ * @return      Approximated HSV color
+ */
+hsv_t rgb2hsv_approximate(rgb_t rgb);
+
+/**
+ * @brief Approximates a 'black body radiation' spectrum for a given 'heat' level.
+ *
+ * This is useful for animations of 'fire'. Heat is specified as an arbitrary scale
+ * from 0 (cool) to 255 (hot).
+ * This is NOT a chromatically correct 'black body radiation' spectrum, but it's
+ * surprisingly close, and it's fast and small.
+ */
+rgb_t rgb_heat_color(uint8_t temperature);
+
+////////////////////////////////////////////////////////////////////////////////
+// Fill functions
+
+/**
+ * Fill an array of HSV colors with a solid HSV color
+ */
+void hsv_fill_solid_hsv(hsv_t *target, hsv_t color, size_t num);
+
+/**
+ * Fill an array of RGB colors with a solid HSV color
+ */
+void rgb_fill_solid_hsv(rgb_t *target, hsv_t color, size_t num);
+
+/**
+ * Fill an array of RGB colors with a solid RGB color
+ */
+void rgb_fill_solid_rgb(rgb_t *target, rgb_t color, size_t num);
+
+/**
+ * @brief Fill an array of HSV colors with a smooth HSV gradient between two
+ *        specified HSV colors.
+ *
+ * Since 'hue' is a value around a color wheel,
+ * there are always two ways to sweep from one hue
+ * to another.
+ * This function lets you specify which way you want
+ * the hue gradient to sweep around the color wheel:
+ *
+ *     FORWARD_HUES: hue always goes clockwise
+ *     BACKWARD_HUES: hue always goes counter-clockwise
+ *     SHORTEST_HUES: hue goes whichever way is shortest
+ *     LONGEST_HUES: hue goes whichever way is longest
+ *
+ * The default is SHORTEST_HUES, as this is nearly
+ * always what is wanted.
+ */
+void hsv_fill_gradient_hsv(hsv_t *target, size_t startpos, hsv_t startcolor, size_t endpos, hsv_t endcolor,
+        color_gradient_direction_t direction);
+
+static inline void hsv_fill_gradient2_hsv(hsv_t *target, size_t num, hsv_t c1, hsv_t c2,
+        color_gradient_direction_t direction)
+{
+    hsv_fill_gradient_hsv(target, 0, c1, num - 1, c2, direction);
+}
+
+static inline void hsv_fill_gradient3_hsv(hsv_t *target, size_t num, hsv_t c1, hsv_t c2, hsv_t c3,
+        color_gradient_direction_t direction)
+{
+    size_t half = num / 2;
+    hsv_fill_gradient_hsv(target, 0,    c1, half,    c2, direction);
+    hsv_fill_gradient_hsv(target, half, c2, num - 1, c3, direction);
+}
+
+static inline void hsv_fill_gradient4_hsv(hsv_t *target, size_t num, hsv_t c1, hsv_t c2, hsv_t c3, hsv_t c4,
+        color_gradient_direction_t direction)
+{
+    size_t onethird = num / 3;
+    size_t twothirds = num * 2 / 3;
+    hsv_fill_gradient_hsv(target, 0,         c1, onethird,  c2, direction);
+    hsv_fill_gradient_hsv(target, onethird,  c2, twothirds, c3, direction);
+    hsv_fill_gradient_hsv(target, twothirds, c3, num - 1,   c4, direction);
+}
+
+/**
+ * Same as ::hsv_fill_gradient_hsv(), but for array of RGB
+ *
+ * The gradient is computed in HSV space, and then HSV values are
+ * converted to RGB as they're written into the RGB array.
+ */
+void rgb_fill_gradient_hsv(rgb_t *target, size_t startpos, hsv_t startcolor, size_t endpos, hsv_t endcolor,
+        color_gradient_direction_t direction);
+
+static inline void rgb_fill_gradient2_hsv(rgb_t *target, size_t num, hsv_t c1, hsv_t c2,
+        color_gradient_direction_t direction)
+{
+    rgb_fill_gradient_hsv(target, 0, c1, num - 1, c2, direction);
+}
+
+static inline void rgb_fill_gradient3_hsv(rgb_t *target, size_t num, hsv_t c1, hsv_t c2, hsv_t c3,
+        color_gradient_direction_t direction)
+{
+    size_t half = num / 2;
+    rgb_fill_gradient_hsv(target, 0,    c1, half,    c2, direction);
+    rgb_fill_gradient_hsv(target, half, c2, num - 1, c3, direction);
+}
+
+static inline void rgb_fill_gradient4_hsv(rgb_t *target, size_t num, hsv_t c1, hsv_t c2, hsv_t c3, hsv_t c4,
+        color_gradient_direction_t direction)
+{
+    size_t onethird = num / 3;
+    size_t twothirds = num * 2 / 3;
+    rgb_fill_gradient_hsv(target, 0,         c1, onethird,  c2, direction);
+    rgb_fill_gradient_hsv(target, onethird,  c2, twothirds, c3, direction);
+    rgb_fill_gradient_hsv(target, twothirds, c3, num - 1,   c4, direction);
+}
+
+/**
+ * @brief Fill a range of LEDs with a smooth RGB gradient
+ *        between two specified RGB colors.
+ *
+ * Unlike HSV, there is no 'color wheel' in RGB space, and therefore
+ * there's only one 'direction' for the gradient to go, and no
+ * 'direction' is needed.
+ */
+void rgb_fill_gradient_rgb(rgb_t *leds, size_t startpos, rgb_t startcolor, size_t endpos, rgb_t endcolor);
+
+static inline void rgb_fill_gradient2_rgb(rgb_t *target, size_t num, rgb_t c1, rgb_t c2)
+{
+    rgb_fill_gradient_rgb(target, 0, c1, num - 1, c2);
+}
+
+static inline void rgb_fill_gradient3_rgb(rgb_t *target, size_t num, rgb_t c1, rgb_t c2, rgb_t c3)
+{
+    size_t half = num / 2;
+    rgb_fill_gradient_rgb(target, 0,    c1, half,    c2);
+    rgb_fill_gradient_rgb(target, half, c2, num - 1, c3);
+}
+
+static inline void rgb_fill_gradient4_rgb(rgb_t *target, size_t num, rgb_t c1, rgb_t c2, rgb_t c3, rgb_t c4)
+{
+    size_t onethird = num / 3;
+    size_t twothirds = num * 2 / 3;
+    rgb_fill_gradient_rgb(target, 0,         c1, onethird,  c2);
+    rgb_fill_gradient_rgb(target, onethird,  c2, twothirds, c3);
+    rgb_fill_gradient_rgb(target, twothirds, c3, num - 1,   c4);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Palette functions
+
+/**
+ * Return an HSV color with 'index' from 'palette' (array of HSV colors).
+ * Even though palette has lesser than 256 explicily defined entries, you
+ * can use an 'index' from 0..255.  The 'pal_size' explicit palette entries will
+ * be spread evenly across the 0..255 range, and the intermedate values
+ * will be HSV-interpolated between adjacent explicit entries.
+ */
+hsv_t color_from_palette_hsv(const hsv_t *palette, uint8_t pal_size, uint8_t index, uint8_t brightness, bool blend);
+
+/**
+ * Same for RGB palette
+ */
+rgb_t color_from_palette_rgb(const rgb_t *palette, uint8_t pal_size, uint8_t index, uint8_t brightness, bool blend);
+
+////////////////////////////////////////////////////////////////////////////////
+// Filter functions
+
+/**
+ * Function which must be provided by the application for use in two-dimensional
+ * filter functions.
+ */
+typedef size_t (*xy_to_offs_cb)(void *ctx, size_t x, size_t y);
+
+/**
+ * @brief One-dimensional blur filter.
+ *
+ * Spreads light to 2 line neighbors.
+ *
+ *   0 = no spread at all
+ *  64 = moderate spreading
+ * 172 = maximum smooth, even spreading
+ *
+ * 173..255 = wider spreading, but increasing flicker
+ *
+ * Total light is NOT entirely conserved, so many repeated calls to 'blur'
+ * will also result in the light fading, eventually all the way to black;
+ * this is by design so that it can be used to (slowly) clear the LEDs
+ * to black.
+ */
+void blur1d(rgb_t *leds, size_t num_leds, fract8 blur_amount);
+
+/**
+ * @brief Perform a blur1d on each column of a rectangular matrix
+ */
+void blur_columns(rgb_t *leds, size_t width, size_t height, fract8 blur_amount, xy_to_offs_cb xy, void *ctx);
+
+/**
+ * @brief Perform a blur1d on each row of a rectangular matrix
+ */
+void blur_rows(rgb_t *leds, size_t width, size_t height, fract8 blur_amount, xy_to_offs_cb xy, void *ctx);
+
+/**
+ * @brief Two-dimensional blur filter.
+ *
+ * Spreads light to 8 XY neighbors.
+ *
+ *   0 = no spread at all
+ *  64 = moderate spreading
+ * 172 = maximum smooth, even spreading
+ *
+ * 173..255 = wider spreading, but increasing flicker
+ *
+ * Total light is NOT entirely conserved, so many repeated calls to 'blur'
+ * will also result in the light fading, eventually all the way to black;
+ * this is by design so that it can be used to (slowly) clear the LEDs
+ * to black.
+ */
+void blur2d(rgb_t *leds, size_t width, size_t height, fract8 blur_amount, xy_to_offs_cb xy, void *ctx);
+
+////////////////////////////////////////////////////////////////////////////////
+// Gamma functions
+
+/**
+ * @brief Single gamma adjustment to a single scalar value.
+ *
+ * Bear in mind that RGB leds have only eight bits per channel of color resolution,
+ * and that very small, subtle shadings may not be visible.
+ */
+uint8_t apply_gamma2brightness(uint8_t brightness, float gamma);
+
+/**
+ * @brief Single gamma adjustment to each channel of a RGB color.
+ */
+rgb_t apply_gamma2rgb(rgb_t c, float gamma);
+
+/**
+ * @brief Different gamma adjustments for each channel of a RGB color.
+ */
+rgb_t apply_gamma2rgb_channels(rgb_t c, float gamma_r, float gamma_g, float gamma_b);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __COLOR_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/color/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = lib8tion
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/color/hsv.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,109 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013 FastLED
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file hsv.h
+ * @defgroup hsv hsv
+ * @{
+ *
+ * Functions for HSV colors
+ *
+ * Ported from FastLED
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+#ifndef __COLOR_HSV_H__
+#define __COLOR_HSV_H__
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <lib8tion.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define HUE_RED    0
+#define HUE_ORANGE 32
+#define HUE_YELLOW 64
+#define HUE_GREEN  96
+#define HUE_AQUA   128
+#define HUE_BLUE   160
+#define HUE_PURPLE 192
+#define HUE_PINK   224
+
+typedef enum
+{
+    COLOR_FORWARD_HUES = 0,
+    COLOR_BACKWARD_HUES,
+    COLOR_SHORTEST_HUES,
+    COLOR_LONGEST_HUES
+} color_gradient_direction_t;
+
+/// HSV color representation
+typedef struct
+{
+    union {
+        uint8_t h;
+        uint8_t hue;
+    };
+    union {
+        uint8_t s;
+        uint8_t sat;
+        uint8_t saturation;
+    };
+    union {
+        uint8_t v;
+        uint8_t val;
+        uint8_t value;
+    };
+} hsv_t;
+
+/// This allows testing a HSV for zero-ness
+static inline bool hsv_is_zero(hsv_t a)
+{
+    return !(a.h | a.s | a.v);
+}
+
+/// Create HSV color from values
+static inline hsv_t hsv_from_values(uint8_t h, uint8_t s, uint8_t v)
+{
+    hsv_t res = {
+        .h = h,
+        .s = s,
+        .v = v
+    };
+    return res;
+}
+
+/// Computes a new color blended some fraction of the way between two other
+/// colors.
+hsv_t blend(hsv_t existing, hsv_t overlay, fract8 amount, color_gradient_direction_t direction);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __COLOR_HSV_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/color/rgb.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,280 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013 FastLED
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file rgb.h
+ * @defgroup rgb rgb
+ * @{
+ *
+ * Functions for RGB colors
+ *
+ * Ported from FastLED
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+#ifndef __COLOR_RGB_H__
+#define __COLOR_RGB_H__
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <lib8tion.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/// RGB color representation
+typedef struct
+{
+    union {
+        uint8_t r;
+        uint8_t red;
+    };
+    union {
+        uint8_t g;
+        uint8_t green;
+    };
+    union {
+        uint8_t b;
+        uint8_t blue;
+    };
+} rgb_t;
+
+/// This allows testing a RGB for zero-ness
+static inline bool rgb_is_zero(rgb_t a)
+{
+    return !(a.r | a.g | a.b);
+}
+
+/// Create rgb_t color from 24-bit color code 0x00RRGGBB
+static inline rgb_t rgb_from_code(uint32_t color_code)
+{
+    rgb_t res = {
+        .r = (uint8_t)((color_code >> 16) & 0xff),
+        .g = (uint8_t)((color_code >> 8) & 0xff),
+        .b = (uint8_t)(color_code & 0xff),
+    };
+    return res;
+}
+
+/// Create rgb_t color from values
+static inline rgb_t rgb_from_values(uint8_t r, uint8_t g, uint8_t b)
+{
+    rgb_t res = {
+        .r = r,
+        .g = g,
+        .b = b,
+    };
+    return res;
+}
+
+/// Convert RGB color to 24-bit color code 0x00RRGGBB
+static inline uint32_t rgb_to_code(rgb_t color)
+{
+    return ((uint32_t)color.r << 16) | ((uint32_t)color.g << 8) | color.b;
+}
+
+/// Add a constant to each channel of RGB color,
+/// saturating at 0xFF
+static inline rgb_t rgb_add(rgb_t a, uint8_t val)
+{
+    rgb_t res = {
+        .r = qadd8(a.r, val),
+        .g = qadd8(a.g, val),
+        .b = qadd8(a.b, val),
+    };
+    return res;
+}
+
+/// Subtract a constant from each channel of RGB color,
+/// saturating at 0x00
+static inline rgb_t rgb_sub(rgb_t a, uint8_t val)
+{
+    rgb_t res = {
+        .r = qsub8(a.r, val),
+        .g = qsub8(a.g, val),
+        .b = qsub8(a.b, val),
+    };
+    return res;
+}
+
+/// Multiply each of the channels by a constant,
+/// saturating each channel at 0xFF
+static inline rgb_t rgb_mul(rgb_t a, uint8_t val)
+{
+    rgb_t res = {
+        .r = qmul8(a.r, val),
+        .g = qmul8(a.g, val),
+        .b = qmul8(a.b, val),
+    };
+    return res;
+}
+
+/// Add one RGB to another, saturating at 0xFF for each channel
+static inline rgb_t rgb_add_rgb(rgb_t a, rgb_t b)
+{
+    rgb_t res = {
+        .r = qadd8(a.r, b.r),
+        .g = qadd8(a.g, b.g),
+        .b = qadd8(a.b, b.b),
+    };
+    return res;
+}
+
+/// Subtract one RGB from another, saturating at 0x00 for each channel
+static inline rgb_t rgb_sub_rgb(rgb_t a, rgb_t b)
+{
+    rgb_t res = {
+        .r = qsub8(a.r, b.r),
+        .g = qsub8(a.g, b.g),
+        .b = qsub8(a.b, b.b),
+    };
+    return res;
+}
+
+/// Scale down a RGB to N 256ths of it's current brightness, using
+/// 'plain math' dimming rules, which means that if the low light levels
+/// may dim all the way to 100% black.
+static inline rgb_t rgb_scale(rgb_t a, uint8_t scaledown)
+{
+    rgb_t res = {
+        .r = scale8(a.r, scaledown),
+        .g = scale8(a.g, scaledown),
+        .b = scale8(a.b, scaledown),
+    };
+    return res;
+}
+
+/// Scale down a RGB to N 256ths of it's current brightness, using
+/// 'video' dimming rules, which means that unless the scale factor is ZERO
+/// each channel is guaranteed NOT to dim down to zero.  If it's already
+/// nonzero, it'll stay nonzero, even if that means the hue shifts a little
+/// at low brightness levels.
+static inline rgb_t rgb_scale_video(rgb_t a, uint8_t scaledown)
+{
+    rgb_t res = {
+        .r = scale8_video(a.r, scaledown),
+        .g = scale8_video(a.g, scaledown),
+        .b = scale8_video(a.b, scaledown),
+    };
+    return res;
+}
+
+/// rgb_fade_light is a synonym for rgb_scale_video(..., 255 - fade_factor)
+static inline rgb_t rgb_fade_light(rgb_t a, uint8_t fade_factor)
+{
+    return rgb_scale_video(a, ~fade_factor);
+}
+
+/// rgb_fade_light is a synonym for rgb_scale(..., 255 - fade_factor)
+static inline rgb_t rgb_fade(rgb_t a, uint8_t fade_factor)
+{
+    return rgb_scale(a, ~fade_factor);
+}
+
+/// Invert each channel of RGB color
+static inline rgb_t rgb_invert(rgb_t a)
+{
+    rgb_t res = {
+        .r = (uint8_t) ~a.r,
+        .g = (uint8_t) ~a.g,
+        .b = (uint8_t) ~a.b,
+    };
+    return res;
+}
+
+/// Get the 'luma' of a RGB color - aka roughly how much light the RGB pixel
+/// is putting out (from 0 to 255).
+static inline uint8_t rgb_luma(rgb_t a)
+{
+    return scale8(a.r, 54) + scale8(a.g, 183) + scale8(a.b, 18);
+}
+
+/// Get the average of the R, G, and B values
+static inline uint8_t rgb_average_light(rgb_t a)
+{
+    return scale8(a.r, 85) + scale8(a.g, 85) + scale8(a.b, 85);
+}
+
+/// Maximize the brightness of this RGB color
+static inline rgb_t rgb_max_brightness(rgb_t a, uint8_t limit)
+{
+    uint8_t max = a.r;
+    if (a.g > max) max = a.g;
+    if (a.b > max) max = a.b;
+
+    // stop div/0 when color is black
+    if (!max) return a;
+
+    uint16_t factor = ((uint16_t)(limit) * 256) / max;
+    rgb_t res = {
+        .r = (uint8_t)((a.r * factor) / 256),
+        .g = (uint8_t)((a.g * factor) / 256),
+        .b = (uint8_t)((a.b * factor) / 256),
+    };
+    return res;
+}
+
+/// Return a new RGB color after performing a linear interpolation between
+/// colors a and b
+static inline rgb_t rgb_lerp8(rgb_t a, rgb_t b, fract8 frac)
+{
+    rgb_t res = {
+        .r = lerp8by8(a.r, b.r, frac),
+        .g = lerp8by8(a.g, b.g, frac),
+        .b = lerp8by8(a.b, b.b, frac),
+    };
+    return res;
+}
+
+/// Return a new RGB color after performing a linear interpolation between
+/// colors a and b
+static inline rgb_t rgb_lerp16(rgb_t a, rgb_t b, fract16 frac)
+{
+    rgb_t res = {
+        .r = (uint8_t)lerp16by16(a.r, b.r, frac),
+        .g = (uint8_t)lerp16by16(a.g, b.g, frac),
+        .b = (uint8_t)lerp16by16(a.b, b.b, frac),
+    };
+    return res;
+}
+
+/// Computes a new color blended some fraction of the way between two other
+/// colors.
+static inline rgb_t rgb_blend(rgb_t existing, rgb_t overlay, fract8 amount)
+{
+    rgb_t res = {
+        .r = blend8(existing.r, overlay.r, amount),
+        .g = blend8(existing.g, overlay.g, amount),
+        .b = blend8(existing.b, overlay.b, amount),
+    };
+    return res;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __COLOR_RGB_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/dht/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,28 @@
+---
+components:
+  - name: dht
+    description: |
+      Driver for DHT11, AM2301 (DHT21, DHT22, AM2302, AM2321), Itead Si7021
+    group: humidity
+    groups:
+      - name: temperature
+    code_owners:
+      - name: UncleRus
+    depends:
+      - name: log
+      - name: esp_idf_lib_helpers
+      - name: freertos
+      - name: driver
+    thread_safe: no
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - name: UncleRus
+        year: 2018
+      - name: jsuiker
+        year: 2016
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/dht/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,11 @@
+if(${IDF_TARGET} STREQUAL esp8266)
+    set(req esp8266 freertos log esp_idf_lib_helpers)
+else()
+    set(req driver freertos log esp_idf_lib_helpers)
+endif()
+
+idf_component_register(
+    SRCS dht.c
+    INCLUDE_DIRS .
+    REQUIRES ${req}
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/dht/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,27 @@
+Copyright (c) 2016 Jonathan Hartsuiker (https://github.com/jsuiker)
+Copyright (c) 2018 Ruslan V. Uss (https://github.com/UncleRus)
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/dht/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,7 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+
+ifdef CONFIG_IDF_TARGET_ESP8266
+COMPONENT_DEPENDS = esp8266 freertos log esp_idf_lib_helpers
+else
+COMPONENT_DEPENDS = driver freertos log esp_idf_lib_helpers
+endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/dht/dht.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,259 @@
+/*
+ * Copyright (c) 2016 Jonathan Hartsuiker <https://github.com/jsuiker>
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file dht.c
+ *
+ * ESP-IDF driver for DHT11, AM2301 (DHT21, DHT22, AM2302, AM2321), Itead Si7021
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2016 Jonathan Hartsuiker <https://github.com/jsuiker>\n
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>\n
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#include "dht.h"
+
+#include <freertos/FreeRTOS.h>
+#include <string.h>
+#include <esp_log.h>
+#include <ets_sys.h>
+#include <esp_idf_lib_helpers.h>
+
+// DHT timer precision in microseconds
+#define DHT_TIMER_INTERVAL 2
+#define DHT_DATA_BITS 40
+#define DHT_DATA_BYTES (DHT_DATA_BITS / 8)
+
+/*
+ *  Note:
+ *  A suitable pull-up resistor should be connected to the selected GPIO line
+ *
+ *  __           ______          _______                              ___________________________
+ *    \    A    /      \   C    /       \   DHT duration_data_low    /                           \
+ *     \_______/   B    \______/    D    \__________________________/   DHT duration_data_high    \__
+ *
+ *
+ *  Initializing communications with the DHT requires four 'phases' as follows:
+ *
+ *  Phase A - MCU pulls signal low for at least 18000 us
+ *  Phase B - MCU allows signal to float back up and waits 20-40us for DHT to pull it low
+ *  Phase C - DHT pulls signal low for ~80us
+ *  Phase D - DHT lets signal float back up for ~80us
+ *
+ *  After this, the DHT transmits its first bit by holding the signal low for 50us
+ *  and then letting it float back high for a period of time that depends on the data bit.
+ *  duration_data_high is shorter than 50us for a logic '0' and longer than 50us for logic '1'.
+ *
+ *  There are a total of 40 data bits transmitted sequentially. These bits are read into a byte array
+ *  of length 5.  The first and third bytes are humidity (%) and temperature (C), respectively.  Bytes 2 and 4
+ *  are zero-filled and the fifth is a checksum such that:
+ *
+ *  byte_5 == (byte_1 + byte_2 + byte_3 + byte_4) & 0xFF
+ *
+ */
+
+static const char *TAG = "dht";
+
+#if HELPER_TARGET_IS_ESP32
+static portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
+#define PORT_ENTER_CRITICAL() portENTER_CRITICAL(&mux)
+#define PORT_EXIT_CRITICAL() portEXIT_CRITICAL(&mux)
+
+#elif HELPER_TARGET_IS_ESP8266
+#define PORT_ENTER_CRITICAL() portENTER_CRITICAL()
+#define PORT_EXIT_CRITICAL() portEXIT_CRITICAL()
+#endif
+
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+#define CHECK_LOGE(x, msg, ...) do { \
+        esp_err_t __; \
+        if ((__ = x) != ESP_OK) { \
+            PORT_EXIT_CRITICAL(); \
+            ESP_LOGE(TAG, msg, ## __VA_ARGS__); \
+            return __; \
+        } \
+    } while (0)
+
+
+/**
+ * Wait specified time for pin to go to a specified state.
+ * If timeout is reached and pin doesn't go to a requested state
+ * false is returned.
+ * The elapsed time is returned in pointer 'duration' if it is not NULL.
+ */
+static esp_err_t dht_await_pin_state(gpio_num_t pin, uint32_t timeout,
+       int expected_pin_state, uint32_t *duration)
+{
+    /* XXX dht_await_pin_state() should save pin direction and restore
+     * the direction before return. however, the SDK does not provide
+     * gpio_get_direction().
+     */
+    gpio_set_direction(pin, GPIO_MODE_INPUT);
+    for (uint32_t i = 0; i < timeout; i += DHT_TIMER_INTERVAL)
+    {
+        // need to wait at least a single interval to prevent reading a jitter
+        ets_delay_us(DHT_TIMER_INTERVAL);
+        if (gpio_get_level(pin) == expected_pin_state)
+        {
+            if (duration)
+                *duration = i;
+            return ESP_OK;
+        }
+    }
+
+    return ESP_ERR_TIMEOUT;
+}
+
+/**
+ * Request data from DHT and read raw bit stream.
+ * The function call should be protected from task switching.
+ * Return false if error occurred.
+ */
+static inline esp_err_t dht_fetch_data(dht_sensor_type_t sensor_type, gpio_num_t pin, uint8_t data[DHT_DATA_BYTES])
+{
+    uint32_t low_duration;
+    uint32_t high_duration;
+
+    // Phase 'A' pulling signal low to initiate read sequence
+    gpio_set_direction(pin, GPIO_MODE_OUTPUT_OD);
+    gpio_set_level(pin, 0);
+    ets_delay_us(sensor_type == DHT_TYPE_SI7021 ? 500 : 20000);
+    gpio_set_level(pin, 1);
+
+    // Step through Phase 'B', 40us
+    CHECK_LOGE(dht_await_pin_state(pin, 40, 0, NULL),
+            "Initialization error, problem in phase 'B'");
+    // Step through Phase 'C', 88us
+    CHECK_LOGE(dht_await_pin_state(pin, 88, 1, NULL),
+            "Initialization error, problem in phase 'C'");
+    // Step through Phase 'D', 88us
+    CHECK_LOGE(dht_await_pin_state(pin, 88, 0, NULL),
+            "Initialization error, problem in phase 'D'");
+
+    // Read in each of the 40 bits of data...
+    for (int i = 0; i < DHT_DATA_BITS; i++)
+    {
+        CHECK_LOGE(dht_await_pin_state(pin, 65, 1, &low_duration),
+                "LOW bit timeout");
+        CHECK_LOGE(dht_await_pin_state(pin, 75, 0, &high_duration),
+                "HIGH bit timeout");
+
+        uint8_t b = i / 8;
+        uint8_t m = i % 8;
+        if (!m)
+            data[b] = 0;
+
+        data[b] |= (high_duration > low_duration) << (7 - m);
+    }
+
+    return ESP_OK;
+}
+
+/**
+ * Pack two data bytes into single value and take into account sign bit.
+ */
+static inline int16_t dht_convert_data(dht_sensor_type_t sensor_type, uint8_t msb, uint8_t lsb)
+{
+    int16_t data;
+
+    if (sensor_type == DHT_TYPE_DHT11)
+    {
+        data = msb * 10;
+    }
+    else
+    {
+        data = msb & 0x7F;
+        data <<= 8;
+        data |= lsb;
+        if (msb & BIT(7))
+            data = -data;       // convert it to negative
+    }
+
+    return data;
+}
+
+esp_err_t dht_read_data(dht_sensor_type_t sensor_type, gpio_num_t pin,
+        int16_t *humidity, int16_t *temperature)
+{
+    CHECK_ARG(humidity || temperature);
+
+    uint8_t data[DHT_DATA_BYTES] = { 0 };
+
+    gpio_set_direction(pin, GPIO_MODE_OUTPUT_OD);
+    gpio_set_level(pin, 1);
+
+    PORT_ENTER_CRITICAL();
+    esp_err_t result = dht_fetch_data(sensor_type, pin, data);
+    if (result == ESP_OK)
+        PORT_EXIT_CRITICAL();
+
+    /* restore GPIO direction because, after calling dht_fetch_data(), the
+     * GPIO direction mode changes */
+    gpio_set_direction(pin, GPIO_MODE_OUTPUT_OD);
+    gpio_set_level(pin, 1);
+
+    if (result != ESP_OK)
+        return result;
+
+    if (data[4] != ((data[0] + data[1] + data[2] + data[3]) & 0xFF))
+    {
+        ESP_LOGE(TAG, "Checksum failed, invalid data received from sensor");
+        return ESP_ERR_INVALID_CRC;
+    }
+
+    if (humidity)
+        *humidity = dht_convert_data(sensor_type, data[0], data[1]);
+    if (temperature)
+        *temperature = dht_convert_data(sensor_type, data[2], data[3]);
+
+    ESP_LOGD(TAG, "Sensor data: humidity=%d, temp=%d", *humidity, *temperature);
+
+    return ESP_OK;
+}
+
+esp_err_t dht_read_float_data(dht_sensor_type_t sensor_type, gpio_num_t pin,
+        float *humidity, float *temperature)
+{
+    CHECK_ARG(humidity || temperature);
+
+    int16_t i_humidity, i_temp;
+
+    esp_err_t res = dht_read_data(sensor_type, pin, humidity ? &i_humidity : NULL, temperature ? &i_temp : NULL);
+    if (res != ESP_OK)
+        return res;
+
+    if (humidity)
+        *humidity = i_humidity / 10.0;
+    if (temperature)
+        *temperature = i_temp / 10.0;
+
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/dht/dht.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2016 Jonathan Hartsuiker <https://github.com/jsuiker>
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file dht.h
+ * @defgroup dht dht
+ * @{
+ *
+ * ESP-IDF driver for DHT11, AM2301 (DHT21, DHT22, AM2302, AM2321), Itead Si7021
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2016 Jonathan Hartsuiker <https://github.com/jsuiker>\n
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>\n
+ *
+ * BSD Licensed as described in the file LICENSE
+ *
+ * @note A suitable pull-up resistor should be connected to the selected GPIO line
+ *
+ */
+#ifndef __DHT_H__
+#define __DHT_H__
+
+#include <driver/gpio.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Sensor type
+ */
+typedef enum
+{
+    DHT_TYPE_DHT11 = 0,   //!< DHT11
+    DHT_TYPE_AM2301,      //!< AM2301 (DHT21, DHT22, AM2302, AM2321)
+    DHT_TYPE_SI7021       //!< Itead Si7021
+} dht_sensor_type_t;
+
+/**
+ * @brief Read integer data from sensor on specified pin
+ *
+ * Humidity and temperature are returned as integers.
+ * For example: humidity=625 is 62.5 %, temperature=244 is 24.4 degrees Celsius
+ *
+ * @param sensor_type DHT11 or DHT22
+ * @param pin GPIO pin connected to sensor OUT
+ * @param[out] humidity Humidity, percents * 10, nullable
+ * @param[out] temperature Temperature, degrees Celsius * 10, nullable
+ * @return `ESP_OK` on success
+ */
+esp_err_t dht_read_data(dht_sensor_type_t sensor_type, gpio_num_t pin,
+        int16_t *humidity, int16_t *temperature);
+
+/**
+ * @brief Read float data from sensor on specified pin
+ *
+ * Humidity and temperature are returned as floats.
+ *
+ * @param sensor_type DHT11 or DHT22
+ * @param pin GPIO pin connected to sensor OUT
+ * @param[out] humidity Humidity, percents, nullable
+ * @param[out] temperature Temperature, degrees Celsius, nullable
+ * @return `ESP_OK` on success
+ */
+esp_err_t dht_read_float_data(dht_sensor_type_t sensor_type, gpio_num_t pin,
+        float *humidity, float *temperature);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif  // __DHT_H__
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/dps310/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+---
+components:
+  - name: dps310
+    description: |
+      Driver for DPS310 barometric pressure sensor
+    group:
+      name: pressure
+    groups:
+      - name: temperature
+    code_owners:
+      - name: trombik
+    depends:
+      - name: i2cdev
+      - name: log
+      - name: esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: ISC
+    copyrights:
+      - name: trombik
+        year: 2022
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/dps310/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,22 @@
+idf_component_register(
+
+    # sources to compile
+    SRCS src/dps310.c src/helper_i2c.c
+
+    # public headers
+    INCLUDE_DIRS include
+
+    # private headers
+    PRIV_INCLUDE_DIRS priv_include
+
+    # Component Requirements
+    # https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/build-system.html#component-requirements
+
+    # components whose header files are #included from the public header files
+    # of this component.
+    REQUIRES log i2cdev
+
+    # components whose header files are #included from any source files in
+    # this component, unless already listed in REQUIRES
+    PRIV_REQUIRES esp_idf_lib_helpers i2cdev
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/dps310/Kconfig	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,10 @@
+menu "DPS310 driver"
+    choice DPS310_PROTOCOL
+        prompt "Choose protocol for digital interface"
+        default DPS310_PROTOCOL_USING_I2C
+        help
+            The protocol to use for digital interface
+        config DPS310_PROTOCOL_USING_I2C
+            bool "I2C"
+    endchoice
+endmenu
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/dps310/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,13 @@
+Copyright (c) 2022 Tomoyuki Sakurai <y@trombik.org>
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/dps310/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,4 @@
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
+COMPONENT_ADD_INCLUDEDIRS = include
+COMPONENT_PRIV_INCLUDEDIRS = priv_include
+COMPONENT_SRCDIRS := src
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/dps310/include/dps310.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,946 @@
+/*
+ * Copyright (c) 2022 Tomoyuki Sakurai <y@trombik.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Sponsored by @beriberikix <https://github.com/beriberikix>
+ */
+
+/**
+ * @file dps310.h
+ * @defgroup dps310 dps310
+ * @{
+ *
+ * ESP-IDF driver for DPS310 barometric pressure sensor. Sponsored by @beriberikix.
+ *
+ * DPS310 supports I2C and SPI (3-wires and 4-wires) as digital interface. The
+ * driver currently supports:
+ *
+ * * I2C
+ *
+ * The driver currently does not support:
+ *
+ * * SPI
+ * * read measurements by interrupt
+ * * multi-master I2C configuration
+ *
+ * Note that the unit of pressure in this driver is pascal (Pa), not
+ * hectopascals (hPa).
+ *
+ * Note that the unit of altitude in this driver is meter.
+ *
+ */
+#if !defined(__DPS310_H__)
+#define __DPS310_H__
+
+/* standard headers */
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+/* esp-idf headers */
+#include <esp_err.h>
+
+/* esp-idf-lib headers */
+#include <i2cdev.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define DPS310_I2C_ADDRESS_0  0x76 //!< I2C address when SDO pin is low
+#define DPS310_I2C_ADDRESS_1  0x77 //!< I2C address when SDO pin is high
+
+#define DPS310_PRODUCT_ID   0x01 //!< Product ID
+#define DPS310_REVISION_ID  0x00 //!< Revision ID
+
+#define DPS310_AVERAGE_SEA_LEVEL_PRESSURE_hPa   (1013.25) //!< Average sea-level pressure in hPa
+#define DPS310_AVERAGE_SEA_LEVEL_PRESSURE_Pa    (DPS310_AVERAGE_SEA_LEVEL_PRESSURE_hPa * 10) //!< Average sea-level pressure in Pa
+
+/**
+ * Mode of DPS310 module operation. See 4.1 Operating Modes.
+ */
+typedef enum {
+    DPS310_MODE_STANDBY = 0b000,  //!< Standby mode
+    DPS310_MODE_COMMAND_PRESSURE = 0b001, //!<  Command mode, pressure measurement
+    DPS310_MODE_COMMAND_TEMPERATURE = 0b010, //!<  Command mode, temperature measurement
+    DPS310_MODE_BACKGROUND_PRESSURE = 0b101, //!<  Background mode, continuous pressure measurement
+    DPS310_MODE_BACKGROUND_TEMPERATURE = 0b110, //!<  Background mode, continuous temperature measurement
+    DPS310_MODE_BACKGROUND_ALL = 0b111, //!<  Background mode, continuous pressure and temperature measurement
+} dps310_mode_t;
+
+/**
+ * Pressure measurement rate.
+ */
+typedef enum {
+    DPS310_PM_RATE_1   = 0b000, //!<   1 measurements / sec
+    DPS310_PM_RATE_2   = 0b001, //!<   2 measurements / sec
+    DPS310_PM_RATE_4   = 0b010, //!<   4 measurements / sec
+    DPS310_PM_RATE_8   = 0b011, //!<   8 measurements / sec
+    DPS310_PM_RATE_16  = 0b100, //!<  16 measurements / sec
+    DPS310_PM_RATE_32  = 0b101, //!<  32 measurements / sec
+    DPS310_PM_RATE_64  = 0b110, //!<  64 measurements / sec
+    DPS310_PM_RATE_128 = 0b111, //!< 128 measurements / sec
+} dps310_pm_rate_t;
+
+/**
+ * Pressure resolution, or oversampling rate.
+ */
+typedef enum {
+    DPS310_PM_PRC_1   = 0b000, //!<   Single (Low Precision)
+    DPS310_PM_PRC_2   = 0b001, //!<   2 times (Low Power).
+    DPS310_PM_PRC_4   = 0b010, //!<   4 times
+    DPS310_PM_PRC_8   = 0b011, //!<   8 times
+    DPS310_PM_PRC_16  = 0b100, //!<  16 times (Standard)
+    DPS310_PM_PRC_32  = 0b101, //!<  32 times
+    DPS310_PM_PRC_64  = 0b110, //!<  64 times (High Precision)
+    DPS310_PM_PRC_128 = 0b111, //!< 128 times
+} dps310_pm_oversampling_t;
+
+/**
+ * Temperature measurement source. Used for temperature measurement and
+ * temperature coefficients.
+ */
+
+typedef enum {
+    DPS310_TMP_SRC_INTERNAL = 0,    //!< Internal sensor (in ASIC)
+    DPS310_TMP_SRC_EXTERNAL = 1,    //!< External sensor (in pressure sensor MEMS element)
+} dps310_tmp_src_ext_t;
+
+/**
+ * Temperature measurement rate.
+ */
+typedef enum {
+    DPS310_TMP_RATE_1   = 0b000, //!<   1 measurements / sec
+    DPS310_TMP_RATE_2   = 0b001, //!<   2 measurements / sec
+    DPS310_TMP_RATE_4   = 0b010, //!<   4 measurements / sec
+    DPS310_TMP_RATE_8   = 0b011, //!<   8 measurements / sec
+    DPS310_TMP_RATE_16  = 0b100, //!<  16 measurements / sec
+    DPS310_TMP_RATE_32  = 0b101, //!<  32 measurements / sec
+    DPS310_TMP_RATE_64  = 0b110, //!<  64 measurements / sec
+    DPS310_TMP_RATE_128 = 0b111, //!< 128 measurements / sec
+} dps310_tmp_rate_t;
+
+/**
+ * Pressure resolution, or oversampling rate.
+ */
+typedef enum {
+    DPS310_TMP_PRC_1   = 0b000, //!<   Single (Low Precision)
+    DPS310_TMP_PRC_2   = 0b001, //!<   2 times (Low Power).
+    DPS310_TMP_PRC_4   = 0b010, //!<   4 times
+    DPS310_TMP_PRC_8   = 0b011, //!<   8 times
+    DPS310_TMP_PRC_16  = 0b100, //!<  16 times (Standard)
+    DPS310_TMP_PRC_32  = 0b101, //!<  32 times
+    DPS310_TMP_PRC_64  = 0b110, //!<  64 times (High Precision)
+    DPS310_TMP_PRC_128 = 0b111, //!< 128 times
+} dps310_tmp_oversampling_t;
+
+/**
+ * Interupt (on SDO pin) active level.
+ */
+typedef enum {
+    DPS310_INT_HL_ACTIVE_LOW = 0,  //!< Active low
+    DPS310_INT_HL_ACTIVE_HIGH = 1, //!< Active high
+} dps310_int_hl_active_level_t;
+
+/**
+ * Mode of interupt when the FIFO is full.
+ */
+typedef enum {
+    DPS310_INT_FIFO_DISABLE = 0, //!< Disable interrupt when the FIFO is full
+    DPS310_INT_FIFO_ENABLE  = 1, //!< Enable interrupt when the FIFO is full
+} dps310_int_fifo_mode_t;
+
+/**
+ * Mode of interupt when a temperature measurement is ready
+ */
+typedef enum {
+    DPS310_INT_TMP_DISABLE  = 0, //!< Disable interrupt when a temperature measurement is ready
+    DPS310_INT_TMP_ENABLE   = 1, //!< Enable interrupt when a temperature measurement is ready
+} dps310_int_tmp_mode_t;
+
+/**
+ * Mode of interupt when a pressure measurement is ready
+ */
+typedef enum {
+    DPS310_INT_PRS_DISABLE  = 0, //!< Disable interrupt when a pressure measurement is ready
+    DPS310_INT_PRS_ENABLE   = 1, //!< Enable interrupt when a pressure measurement is ready
+} dps310_int_prs_mode_t;
+
+/**
+ * Mode of temperature result bit-shift.
+ */
+typedef enum {
+    DPS310_T_SHIFT_DISABLE  = 0, //!< No shift.
+    DPS310_T_SHIFT_ENABLE   = 1, //!< Shift result right in data register.
+                                 //   Must be set to '1' when the oversampling
+                                 //   rate is >8 times.
+} dps310_t_shift_mode_t;
+
+/**
+ * Mode of pressure result bit-shift.
+ */
+typedef enum {
+    DPS310_P_SHIFT_DISABLE  = 0, //!< No shift.
+    DPS310_P_SHIFT_ENABLE   = 1, //!< Shift result right in data register.
+                                 //   Must be set to '1' when the oversampling
+                                 //   rate is >8 times.
+} dps310_p_shift_mode_t;
+
+/**
+ * Mode of FIFO.
+ */
+typedef enum {
+    DPS310_FIFO_DISABLE = 0, //!< Disable FIFO.
+    DPS310_FIFO_ENABLE  = 1, //!< Enable FIFO.
+} dps310_fifo_en_mode_t;
+
+/**
+ * SPI mode.
+ */
+typedef enum {
+    DPS310_SPI_MODE_4WIRE = 0, //!< SPI 4-wires
+    DPS310_SPI_MODE_3WIRE = 1, //!< SPI 3-wires
+} dps310_spi_mode_t;
+
+/**
+ * Type of measurement result in FIFO.
+ *
+ * When the type is DPS310_MEASUREMENT_EMPTY, the result is always zero.
+ * Otherwise, the result is the compensated value of each type.
+ */
+typedef enum {
+    DPS310_MEASUREMENT_TEMPERATURE = 0, //!< Temperature
+    DPS310_MEASUREMENT_PRESSURE,        //!< Pressure
+    DPS310_MEASUREMENT_EMPTY,           //!< Empty, no measurement available
+} dps310_fifo_measurement_type_t;
+
+typedef struct {
+    dps310_fifo_measurement_type_t type;
+    float result;
+} dps310_fifo_measurement_t;
+
+/**
+ * Configuration parameters for DPS310.
+ */
+typedef struct {
+    dps310_pm_rate_t pm_rate;
+    dps310_pm_oversampling_t pm_oversampling;
+    dps310_tmp_rate_t tmp_rate;
+    dps310_tmp_oversampling_t tmp_oversampling;
+    dps310_tmp_src_ext_t tmp_src;
+    dps310_tmp_src_ext_t tmp_coef;
+    dps310_int_fifo_mode_t int_fifo_mode;
+    dps310_int_tmp_mode_t int_tmp_mode;
+    dps310_int_prs_mode_t int_prs_mode;
+    dps310_t_shift_mode_t t_shift_mode;
+    dps310_p_shift_mode_t p_shift_mode;
+    dps310_fifo_en_mode_t fifo_en_mode;
+    dps310_spi_mode_t spi_mode;
+
+} dps310_config_t;
+
+/**
+ * A macro to set default dps310_config_t.
+ */
+
+#define DPS310_CONFIG_DEFAULT() { \
+    .pm_rate = DPS310_PM_RATE_1, \
+    .pm_oversampling = DPS310_PM_PRC_16, \
+    .tmp_rate = DPS310_TMP_RATE_1, \
+    .tmp_oversampling = DPS310_TMP_PRC_16, \
+    .tmp_src = DPS310_TMP_SRC_EXTERNAL, \
+    .tmp_coef = DPS310_TMP_SRC_EXTERNAL, \
+    .int_fifo_mode = DPS310_INT_FIFO_DISABLE, \
+    .int_tmp_mode = DPS310_INT_TMP_DISABLE, \
+    .int_prs_mode = DPS310_INT_PRS_DISABLE, \
+    .t_shift_mode = DPS310_T_SHIFT_ENABLE, \
+    .p_shift_mode = DPS310_P_SHIFT_ENABLE, \
+    .fifo_en_mode = DPS310_FIFO_DISABLE, \
+    .spi_mode = DPS310_SPI_MODE_4WIRE, \
+}
+
+/**
+ * Calibration Coefficients (COEF).
+ */
+typedef struct {
+    int32_t c0;
+    int32_t c1;
+    int32_t c00;
+    int32_t c10;
+    int32_t c01;
+    int32_t c11;
+    int32_t c20;
+    int32_t c21;
+    int32_t c30;
+} dps310_coef_t;
+
+/**
+ * Device descriptor.
+ */
+typedef struct {
+    i2c_dev_t i2c_dev;          //!< I2C device descriptor
+    uint8_t prod_id;            //!< Product ID
+    uint8_t prod_rev;           //!< Product revision
+    uint8_t t_rate;             //!< latest P_rate
+    uint8_t p_rate;             //!< latest T_rate
+    int32_t t_raw;              //!< latest T_raw
+    dps310_coef_t coef;         //!< coefficients
+    float offset;               //!< offset in meter
+    float pressure_s;           //!< calculated pressure at sea-level
+} dps310_t;
+
+/**
+ * DPS310 registers
+ */
+
+/* 7 Register Map */
+#define DPS310_REG_PRS_B2       0x00
+#define DPS310_REG_PRS_B1       0x01
+#define DPS310_REG_PRS_B0       0x02
+#define DPS310_REG_TMP_B2       0x03
+#define DPS310_REG_TMP_B1       0x04
+#define DPS310_REG_TMP_B0       0x05
+#define DPS310_REG_PRS_CFG      0x06
+#define DPS310_REG_TMP_CFG      0x07
+#define DPS310_REG_MEAS_CFG     0x08
+#define DPS310_REG_CFG_REG      0x09
+#define DPS310_REG_INT_STS      0x0a
+#define DPS310_REG_FIFO_STS     0x0b
+#define DPS310_REG_RESET        0x0c
+#define DPS310_REG_ID           0x0d
+#define DPS310_REG_COEF         0x10
+#define DPS310_REG_COEF_LEN     (18)
+#define DPS310_REG_COEF_SRCE    0x28
+
+/* various masks */
+#define DPS310_REG_ID_REV_MASK              (0x0f)
+#define DPS310_REG_ID_PROD_MASK             (0xf0)
+#define DPS310_REG_RESET_FIFO_FLUSH_MASK    (1 << 7)
+#define DPS310_REG_RESET_SOFT_RST_MASK      (0x0f)
+#define DPS310_REG_PRS_CFG_PM_RATE_MASK     (0b111 << 4)
+#define DPS310_REG_PRS_CFG_TMP_RATE_MASK    (0b111 << 4)
+#define DPS310_REG_PRS_CFG_PM_PRC_MASK      (0b1111)
+#define DPS310_REG_PRS_CFG_TMP_EXT_MASK     (1 << 7)
+#define DPS310_REG_TMP_CFG_TMP_PRC_MASK     (0b1111)
+#define DPS310_REG_CFG_REG_INT_HL_MASK      (1 << 7)
+#define DPS310_REG_CFG_REG_INT_FIFO_MASK    (1 << 6)
+#define DPS310_REG_CFG_REG_INT_TMP_MASK     (1 << 5)
+#define DPS310_REG_CFG_REG_INT_PRS_MASK     (1 << 4)
+#define DPS310_REG_CFG_REG_T_SHIFT_MASK     (1 << 3)
+#define DPS310_REG_CFG_REG_P_SHIFT_MASK     (1 << 2)
+#define DPS310_REG_CFG_REG_FIFO_EN_MASK     (1 << 1)
+#define DPS310_REG_CFG_REG_SPI_MODE_MASK    (1 << 0)
+#define DPS310_REG_MEAS_CFG_COEF_RDY_MASK   (1 << 7)
+#define DPS310_REG_MEAS_CFG_SENSOR_RDY_MASK (1 << 6)
+#define DPS310_REG_MEAS_CFG_TMP_RDY_MASK    (1 << 5)
+#define DPS310_REG_MEAS_CFG_PRS_RDY_MASK    (1 << 4)
+#define DPS310_REG_MEAS_CFG_MEAS_CTRL_MASK  (0b111)
+#define DPS310_REG_COEF_SRCE_MASK           (1 << 7)
+#define DPS310_REG_FIFO_STS_FIFO_EMPTY_MASK (1)
+#define DPS310_REG_FIFO_STS_FIFO_FULL_MASK  (1 << 1)
+
+/* See 3.6 Timing Characteristics */
+#define DPS310_I2C_FREQ_MAX_HZ  (3400000)  // Max 3.4 MHz
+                                           //
+/* I2C master driver does not support higher than 1MHz
+ * https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2c.html#_CPPv4N12i2c_config_t9clk_speedE
+ */
+#define DPS310_I2C_FREQ_MAX_ESP_IDF_HZ  (1000000)
+#define DPS310_SPI_FREQ_MAX_HZ  (10000000) // Max 10 MHz
+                                           //
+/* See 4.3 Start-up sequence
+ *
+ * XXX the datasheet is ambiguous in the start-up sequence. Trim_complete is
+ * mentioned in nowhere. the DPS310-Pressure-Sensor by Infineon uses 50 ms.
+ * don't know what the "40ms" in the chart means. to be safe, use the sum of
+ * all the numbers in the chart.
+ */
+#define DPS310_TRIM_READY_DELAY_MS  (3) // 2.5 ms
+#define DPS310_SENSOR_READY_DELAY_MS  (12)
+#define DPS310_COEFFICIENTS_READY_DELAY_MS  (40)
+#define DPS310_STARTUP_DELAY_MS (DPS310_TRIM_READY_DELAY_MS + DPS310_SENSOR_READY_DELAY_MS + DPS310_COEFFICIENTS_READY_DELAY_MS)
+
+#define DPS310_PROD_ID  0x01
+
+/* temperature and pressure use three resisters for 24 bits values. */
+#define DPS310_REG_SENSOR_VALUE_LEN (3)
+
+/* 4.8 FIFO Operation */
+#define DPS310_REG_FIFO     DPS310_REG_PRS_B2   //! Resister address of FIFO.
+#define DPS310_FIFO_EMPTY   (0xff800000)        //! the value of two's complement in the resisters when no measurement is in the FIFO.
+
+/* See 8.9 Soft Reset and FIFO flush (RESET) */
+#define DPS310_FIFO_FLUSH_VALUE (1 << 7)
+#define DPS310_SOFT_RST_VALUE   (0b1001)
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param[out] dev      The device descriptor.
+ * @param[in]  addr     DPS310's I2C address
+ * @param[in]  port     I2C port number to use.
+ *                 See available I2C port at:
+ *                 https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2c.html#_CPPv410i2c_port_t
+ * @param[in]  sda_gpio GPIO pin for SDA
+ * @param[in]  scl_gpio GPIO pin for SCL
+ * @return `ESP_OK` on success
+ */
+esp_err_t dps310_init_desc(dps310_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free the device descriptor.
+ *
+ * The device descriptor must NOT be NULL. `dps310_free_desc()` does not
+ * `free()` the device descriptor on error.
+ *
+ * @param[out] dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t dps310_free_desc(dps310_t *dev);
+
+/**
+ * @brief Initialize DPS310 module
+ *
+ * The function does the followings:
+ *
+ * - read the DPS310_REG_ID, and identify the product ID. Return ESP_FAIL if
+ *   the product ID does not match expected product ID.
+ * - reset the chip
+ * - perform a quirk
+ *
+ * @param[in] dev    Device descriptor
+ * @param[in] config Configuration
+ * @return `ESP_OK` on success
+ */
+esp_err_t dps310_init(dps310_t *dev, dps310_config_t *config);
+
+/**
+ * @brief Reset the device.
+ *
+ * Perform "soft reset" and ensure the chip is fully functional by delaying
+ * `DPS310_STARTUP_DELAY_MS`.
+ *
+ * @param[in] dev The device descriptor
+ * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` and/or `config` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_reset(dps310_t *dev);
+
+/**
+ * @brief Get pressure measurement rate.
+ *
+ * @param[in] dev The device descriptor
+ * @param[out] value the value in the resister
+ * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` and/or
+ * `value` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_get_rate_p(dps310_t *dev, dps310_pm_rate_t *value);
+
+/**
+ * @brief Set pressure measurement rate.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[in] value The value to set.
+ * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` and/or `config` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_set_rate_p(dps310_t *dev, dps310_pm_rate_t value);
+
+/**
+ * @brief Get temperature measurement rate.
+ *
+ * @param[in] dev The device descriptor
+ * @param[out] value the value in the resister
+ * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` and/or
+ * `value` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_get_rate_t(dps310_t *dev, dps310_tmp_rate_t *value);
+
+/**
+ * @brief Set temperature measurement rate.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[in] value The value to set.
+ * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` and/or `config` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_set_rate_t(dps310_t *dev, dps310_tmp_rate_t value);
+
+/**
+ * @brief Get pressure oversampling rate.
+ *
+ * @param[in] dev The device descriptor
+ * @param[out] value the value in the resister
+ * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` and/or
+ * `value` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_get_oversampling_p(dps310_t *dev, dps310_pm_oversampling_t *value);
+
+/**
+ * @brief Set pressure oversampling rate.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[in] value The value to set.
+ * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` and/or `config` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_set_oversampling_p(dps310_t *dev, dps310_pm_oversampling_t value);
+
+/**
+ * @brief Get temperature oversampling rate.
+ *
+ * @param[in] dev The device descriptor
+ * @param[out] value the value in the resister
+ * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` and/or
+ * `value` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_get_oversampling_t(dps310_t *dev, dps310_tmp_oversampling_t *value);
+
+/**
+ * @brief Set temperature oversampling rate.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[in] value The value to set.
+ * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` and/or `config` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_set_oversampling_t(dps310_t *dev, dps310_tmp_oversampling_t value);
+
+/**
+ * @brief Get temperature measurement source.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[out] value the value in the resister.
+ * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` and/or `value` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_get_tmp_ext(dps310_t *dev, dps310_tmp_src_ext_t *value);
+
+/**
+ * @brief Set temperature measurement source.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[in] value The value to set.
+ * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_set_tmp_ext(dps310_t *dev, dps310_tmp_src_ext_t value);
+
+/**
+ * @brief Set temperature coefficient source.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[in] value The value to set.
+ * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_set_tmp_coef_ext(dps310_t *dev, dps310_tmp_src_ext_t value);
+
+/**
+ * @brief Get interrupt active level.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[out] value the value in the resister.
+ * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` and/or `value` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_get_int_hl(dps310_t *dev, dps310_int_hl_active_level_t *value);
+
+/**
+ * @brief Set interrupt active level.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[in] value The value to set.
+ * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_set_int_hl(dps310_t *dev, dps310_int_hl_active_level_t value);
+
+/**
+ * @brief Get the status of FIFO interrupt.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[out] value Current configuration of INT_FIFO.
+ * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` and/or `value` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_get_int_fifo(dps310_t *dev, dps310_int_fifo_mode_t *value);
+
+/**
+ * @brief Set the status of FIFO interrupt.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[out] value The value to set.
+ * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_set_int_fifo(dps310_t *dev, dps310_int_fifo_mode_t value);
+
+/**
+ * @brief Get the status of temperature interrupt.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[out] value Current configuration of INT_TMP.
+ * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` and/or `value` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_get_int_tmp(dps310_t *dev, dps310_int_tmp_mode_t *value);
+
+/**
+ * @brief Set the status of temperature interrupt.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[out] value The value to set.
+ * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_set_int_tmp(dps310_t *dev, dps310_int_tmp_mode_t value);
+
+/**
+ * @brief Get the status of pressure interrupt.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[out] value Current configuration of INT_PRS.
+ * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` and/or `value` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_get_int_prs(dps310_t *dev, dps310_int_prs_mode_t *value);
+
+/**
+ * @brief Set the status of pressure interrupt.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[out] value The value to set.
+ * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_set_int_prs(dps310_t *dev, dps310_int_prs_mode_t value);
+
+/**
+ * @brief Get the status of temperature result bit-shift.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[out] value Current configuration of T_SHIFT.
+ * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` and/or `value` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_get_t_shift(dps310_t *dev, dps310_t_shift_mode_t *value);
+
+/**
+ * @brief Set the status of temperature result bit-shift.
+ *
+ * Must be set to DPS310_T_SHIFT_ENABLE when the oversampling rate is >8 times.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[out] value The value to set.
+ * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_set_t_shift(dps310_t *dev, dps310_t_shift_mode_t value);
+
+/**
+ * @brief Get the status of pressure result bit-shift.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[out] value Current configuration of T_SHIFT.
+ * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` and/or `value` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_get_p_shift(dps310_t *dev, dps310_p_shift_mode_t *value);
+
+/**
+ * @brief Set the status of pressure result bit-shift.
+ *
+ * Must be set to DPS310_P_SHIFT_ENABLE when the oversampling rate is >8 times.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[out] value The value to set.
+ * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_set_p_shift(dps310_t *dev, dps310_p_shift_mode_t value);
+
+/**
+ * @brief Get the status of FIFO.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[out] value Current configuration of FIFO_EN.
+ * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` and/or `value` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_get_fifo_en(dps310_t *dev, dps310_fifo_en_mode_t *value);
+
+/**
+ * @brief Set the status of FIFO.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[out] value The value to set.
+ * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_set_fifo_en(dps310_t *dev, dps310_fifo_en_mode_t value);
+
+/**
+ * @brief Get the mode of SPI.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[out] value Current configuration of SPI_MODE.
+ * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` and/or `value` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_get_spi_mode(dps310_t *dev, dps310_spi_mode_t *value);
+
+/**
+ * @brief Set the mode of SPI.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[out] value The value to set.
+ * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_set_spi_mode(dps310_t *dev, dps310_spi_mode_t value);
+
+/**
+ * @brief Get Calibration Coefficients (COEF), update COEF in the device
+ * descriptor.
+ *
+ * @param[in] dev The device descriptor.
+ * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_get_coef(dps310_t *dev);
+
+/**
+ * @brief Get operating mode.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[out] mode The operating mode.
+ */
+esp_err_t dps310_get_mode(dps310_t *dev, dps310_mode_t *mode);
+
+/**
+ * @brief Set operating mode.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[in] mode The operating mode.
+ * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_set_mode(dps310_t *dev, dps310_mode_t mode);
+
+/**
+ * @brief Flush FIFO.
+ *
+ * @param[in] dev The device descriptor.
+ * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_flush_fifo(dps310_t *dev);
+
+/**
+ * @brief Enable or disable FIFO.
+ *
+ * The function performs flush (`dps310_flush_fifo()`) before disabling FIFO.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[in] enable Enable FIFO when true, disable FIFO when false.
+ * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_enable_fifo(dps310_t *dev, bool enable);
+
+/**
+ * @brief Read the raw sensor value from resisters.
+ *
+ * The real raw value is 2's complement. The function internally converts the
+ * value from the 2's complement to uint32_t number.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[in] reg Either `DPS310_REG_TMP_B2` or `DPS310_REG_PRS_B2`.
+ * @param[out] value The raw value in the three resisters.
+ * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_read_raw(dps310_t *dev, uint8_t reg, int32_t *value);
+
+/**
+ * @brief Read compensated pressure value.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[out] pressure Compensated pressure value in Pascal (not hPa).
+ * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_read_pressure(dps310_t *dev, float *pressure);
+
+/**
+ * @brief Read compensated temperature value after waiting for PRES_RDY bit.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[in] delay_ms Time in microseconds to wait when the value is not ready.
+ * @param[in] max_attempt Number of attempt to read.
+ * @param[out] pressure Compensated pressure value in Pascal (not hPa).
+ * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL. ESP_ERR_TIMEOUT when failed to read the measurement within max_attempt, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_read_pressure_wait(dps310_t *dev, uint16_t delay_ms, uint8_t max_attempt, float *pressure);
+
+/**
+ * @brief Read compensated temperature value.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[out] temperature Compensated temperature value.
+ * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_read_temp(dps310_t *dev, float *temperature);
+
+/**
+ * @brief Read compensated temperature value after waiting for TMP_RDY bit.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[in] delay_ms Time in microseconds to wait when the value is not ready.
+ * @param[in] max_attempt Number of attempt to read.
+ * @param[out] temperature Compensated temperature value.
+ * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL. ESP_ERR_TIMEOUT when failed to read the measurement within max_attempt, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_read_temp_wait(dps310_t *dev, uint16_t delay_ms, uint8_t max_attempt, float *temperature);
+
+/**
+ * @brief Test if a single bit in a resister is set.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[in] reg The resister
+ * @param[in] mask bit mask to test
+ * @param[out] ready true when the bit is set, false when the bit is cleared.
+ * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_is_ready_for(dps310_t *dev, uint8_t reg, uint8_t mask, bool *ready);
+
+/**
+ * @brief Test COEF_RDY in MEAS_CFG resister is set.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[out] ready true when the bit is set, false when the bit is cleared.
+ * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_is_ready_for_coef(dps310_t *dev, bool *ready);
+
+/**
+ * @brief Test SENSOR_RDY in MEAS_CFG resister is set.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[out] ready true when the bit is set, false when the bit is cleared.
+ * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_is_ready_for_sensor(dps310_t *dev, bool *ready);
+
+/**
+ * @brief Test TMP_RDY in MEAS_CFG resister is set.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[out] ready true when the bit is set, false when the bit is cleared.
+ * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_is_ready_for_temp(dps310_t *dev, bool *ready);
+
+/**
+ * @brief Test PRS_RDY in MEAS_CFG resister is set.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[out] ready true when the bit is set, false when the bit is cleared.
+ * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_is_ready_for_pressure(dps310_t *dev, bool *ready);
+
+/**
+ * @brief Reset undocumented internal resisters.
+ *
+ * The function is supposed to fix an issue in the sensor by writing magic
+ * values to magic resisters. However, the issue is not documented. The
+ * latest data sheet does not mention the issue, nor an errata.
+ *
+ * After issuing magic commands, the function re-reads COEF and temperature
+ * once so that the subsequent pressure reads return compensated values with
+ * internal cached parameters.
+ *
+ * See:
+ * https://github.com/Infineon/DPS310-Pressure-Sensor#temperature-measurement-issue
+ * https://github.com/Infineon/DPS310-Pressure-Sensor/blob/3edb0e58dfd7691491ae8d7f6a86277b001ad93f/src/DpsClass.cpp#L442-L461
+ * https://github.com/Infineon/DPS310-Pressure-Sensor/blob/ed02f803fc780cbcab54ed8b35dd3d718f2ebbda/src/Dps310.cpp#L84-L86
+ * https://github.com/Infineon/DPS310-Pressure-Sensor/issues/15#issuecomment-475394536
+ *
+ * @param[in] dev The device descriptor.
+ * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_quirk(dps310_t *dev);
+
+/**
+ * @brief See if FIFO is empty.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[out] result The result. true if empty, false otherwise.
+ * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_is_fifo_empty(dps310_t *dev, bool *result);
+
+/**
+ * @brief Read measurement result from FIFO.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[out] measurement Measured value.
+ * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_read_fifo(dps310_t *dev, dps310_fifo_measurement_t *measurement);
+
+/**
+ * @brief Start background measurement.
+ *
+ * This function is a syntax-sugar of `dps310_set_mode()` just for readbility
+ * and for an emphasis on a fact that measurement starts immediately after
+ * this.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[in] mode The mode of background measurement.
+ * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_backgorund_start(dps310_t *dev, dps310_mode_t mode);
+
+/**
+ * @brief Stop background measurement.
+ *
+ * This function is a syntax-sugar of `dps310_set_mode()`.
+ *
+ * @param[in] dev The device descriptor.
+ * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails.
+ */
+esp_err_t dps310_backgorund_stop(dps310_t *dev);
+
+/**
+ * @brief Calibrate altitude offset from the altitude of the device.
+ *
+ * Call this function before dps310_read_altitude() for higher accuracy.
+ *
+ * By default, the driver calculates altitude using average sea-level
+ * pressure. This function updates internal offset of altitude by reading
+ * pressure from the sensor, and given altitude. There are public web services
+ * that provide altitude at a specific location, such as Google Earth.
+ *
+ * The function attempts to keep original oversampling rates during
+ * calibration. When it fails to do so due to errors, the oversampling rates
+ * might be different.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[in] altitude_real Real (known) altitude.
+ */
+esp_err_t dps310_calibrate_altitude(dps310_t *dev, float altitude_real);
+
+/**
+ * @brief Calculate altitude from pressure.
+ *
+ * Calculates altitude from pressure given. Call dps310_calibrate_altitude()
+ * before this function for higher accuracy. The function adds the offset to
+ * calculated altitude.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[in] pressure The pressure.
+ * @param[out] altitude The calicurated altitude.
+ */
+esp_err_t dps310_calc_altitude(dps310_t *dev, float pressure, float *altitude);
+
+/**
+ * @brief Read pressure from the sensor, calculates altitude.
+ *
+ * Make sure that pressure measurement value is available.
+ *
+ * @param[in] dev The device descriptor.
+ * @param[out] altitude The calculated altitude.
+ */
+esp_err_t dps310_read_altitude(dps310_t *dev, float *altitude);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif  // __DPS310_H__
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/dps310/priv_include/helper_i2c.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2022 Tomoyuki Sakurai <y@trombik.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * A private header for I2C interface. Most of them are a wrapper of i2cdev
+ * functions.
+ *
+ * Functions in the header should be prefixed with `_` to indicate they are
+ * a private function.
+ *
+ * A postfix, `_nolock` in function name means the function does not acquire
+ * I2C lock in `i2cdev`.
+ */
+
+#if !defined(__DPS310__HELPER_I2C__H__)
+#define __DPS310__HELPER_I2C__H__
+
+/* standard headers */
+#include <inttypes.h>
+
+/* esp-idf headers */
+#include <esp_err.h>
+#include <i2cdev.h>
+
+/* private headers */
+#include "helper_macro.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Read a single byte from a 8-bit resister with locking.
+ */
+esp_err_t _read_reg(i2c_dev_t *dev, uint8_t reg, uint8_t *val);
+
+/**
+ * @brief Read a masked value from a 8-bit resister with locking.
+ *
+ * The returned value is bit-shifted. When `mask` is `0b1111000`, and the
+ * value in the resister is `0b00010000`, `val` is `0b0001`.
+ */
+esp_err_t _read_reg_mask(i2c_dev_t *dev, uint8_t reg, uint8_t mask, uint8_t *val);
+
+/**
+ * @brief Write a single byte to a 8-bit resister with locking.
+ */
+esp_err_t _write_reg(i2c_dev_t *dev, uint8_t reg, uint8_t *value);
+
+/**
+ * @brief Update a 8-bit resister with a masked value without locking.
+ *
+ * `mask` is the mask to update, and `val` is the value. The function reads
+ * the resister, and see if the bit values in the resister needs update. If
+ * the bit value is identical with `val`, it does not update the resister.
+ *
+ * `val` is automatically bit-shifted when updating the resister.
+ *
+ * Useful to update specific bits in a resister.
+ *
+ * See also: _update_reg().
+ */
+esp_err_t _update_reg_nolock(i2c_dev_t *dev, uint8_t reg, uint8_t mask, uint8_t val);
+
+/**
+ * @brief Update a 8-bit resister with a masked value.
+ */
+esp_err_t _update_reg(i2c_dev_t *dev, uint8_t reg, uint8_t mask, uint8_t val);
+
+esp_err_t _wait_for_reg_bits(i2c_dev_t *dev, uint8_t reg, uint8_t mask, uint8_t val, uint8_t max_attempt, uint16_t delay_ms);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // __DPS310__HELPER_I2C__H__
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/dps310/priv_include/helper_macro.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2022 Tomoyuki Sakurai <y@trombik.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#if !defined(__DPS310_HELPER_MACRO_H__)
+#define __DPS310_HELPER_MACRO_H__
+
+#define BV(x) ((uint8_t)(1 << (x)))
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+#define CHECK_LOGE(dev, x, msg, ...) do { \
+        esp_err_t __; \
+        if ((__ = x) != ESP_OK) { \
+            I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); \
+            ESP_LOGE(TAG, msg, ## __VA_ARGS__); \
+            return __; \
+        } \
+    } while (0)
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/dps310/src/dps310.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,1212 @@
+/*
+ * Copyright (c) 2022 Tomoyuki Sakurai <y@trombik.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/**
+ * @file dps310.c
+ *
+ * ESP-IDF driver for DPS310. Sponserd by @beriberikix.
+ *
+ * Note that the driver always tries to compensate raw values from the sensor.
+ * When compensation is not required, or undesired, users should implement
+ * their own functions to bypass compensation.
+ */
+
+/* standard headers */
+#include <inttypes.h>
+#include <string.h>
+#include <assert.h>
+#include <math.h>
+
+/* esp-idf headers */
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <esp_log.h>
+#include <esp_idf_lib_helpers.h>
+#include <esp_err.h>
+#include <i2cdev.h>
+
+/* private headers */
+#include "helper_macro.h"
+#include "helper_i2c.h"
+
+/* public headers */
+#include "dps310.h"
+
+#define DPS310_QUIRK_DELAY_MS       (10)
+#define DPS310_QUIRK_MAX_ATTEMPT    (5)
+
+static const char *TAG = "dps310";
+
+/* see 4.9.3 Compensation Scale Factors */
+#define N_SCALE_FACTORS (8)
+static const int32_t scale_factors[N_SCALE_FACTORS] = {
+    524288,
+    1572864,
+    3670016,
+    7864320,
+    253952,
+    516096,
+    1040384,
+    2088960
+};
+
+esp_err_t dps310_quirk(dps310_t *dev)
+{
+    esp_err_t err = ESP_FAIL;
+    const int magic_command_len = 5;
+    float ignore = 0;
+    const uint8_t magic_commands[5][2] = {
+
+        /* reg address, value */
+        { 0xA5, 0x0E },
+        { 0x0F, 0x96 },
+        { 0x62, 0x02 },
+        { 0x0E, 0x00 },
+        { 0x0F, 0x00 },
+    };
+    dps310_mode_t original_mode = 0;
+
+    CHECK_ARG(dev);
+    ESP_LOGD(TAG, "dps310_quirk(): resetting resisters with magic numbers");
+    for (int i = 0; i < magic_command_len; i++)
+    {
+        uint8_t reg = magic_commands[i][0];
+        uint8_t value = magic_commands[i][1];
+        err = _write_reg(&dev->i2c_dev, reg, &value);
+        if (err != ESP_OK)
+        {
+            ESP_LOGE(TAG, "_write_reg(): %s (reg: 0x%02X)", esp_err_to_name(err), reg);
+            break;
+        }
+    }
+    if (err != ESP_OK)
+    {
+        goto fail;
+    }
+    vTaskDelay(pdMS_TO_TICKS(DPS310_STARTUP_DELAY_MS));
+
+    /* sensor is ready? */
+    err = _wait_for_reg_bits(&dev->i2c_dev, DPS310_REG_MEAS_CFG, DPS310_REG_MEAS_CFG_SENSOR_RDY_MASK, 1, DPS310_QUIRK_MAX_ATTEMPT, DPS310_QUIRK_DELAY_MS);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "_wait_for_reg_bits(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+    /* coef is ready? */
+    err = _wait_for_reg_bits(&dev->i2c_dev, DPS310_REG_MEAS_CFG, DPS310_REG_MEAS_CFG_COEF_RDY_MASK, 1, DPS310_QUIRK_MAX_ATTEMPT, DPS310_QUIRK_DELAY_MS);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "_wait_for_reg_bits(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+
+    ESP_LOGD(TAG, "dps310_quirk(): reading COEF");
+    err = dps310_get_coef(dev);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "dps310_get_coef(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+
+    ESP_LOGD(TAG, "dps310_quirk(): keep the original operation mode");
+    err = dps310_get_mode(dev, &original_mode);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "dps310_get_mode(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+
+    ESP_LOGD(TAG, "dps310_quirk(): setting mode to DPS310_MODE_COMMAND_TEMPERATURE");
+    err = dps310_set_mode(dev, DPS310_MODE_COMMAND_TEMPERATURE);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "dps310_set_mode(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+
+    ESP_LOGD(TAG, "dps310_quirk(): reading temperature just once");
+    err = dps310_read_temp_wait(dev, DPS310_QUIRK_DELAY_MS, DPS310_QUIRK_MAX_ATTEMPT, &ignore);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "dps310_read_temp_wait(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+
+    ESP_LOGD(TAG, "dps310_quirk(): restore the original mode");
+    err = dps310_set_mode(dev, original_mode);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "dps310_set_mode(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+
+fail:
+    return err;
+}
+
+esp_err_t dps310_init_desc(dps310_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    esp_err_t err = ESP_FAIL;
+
+    if (dev == NULL)
+    {
+        err = ESP_ERR_INVALID_ARG;
+        goto fail;
+    }
+    if (addr != DPS310_I2C_ADDRESS_0 && addr != DPS310_I2C_ADDRESS_1)
+    {
+        ESP_LOGE(TAG, "Invalid I2C address: expected %0X or %0X, got %0X",
+                 DPS310_I2C_ADDRESS_0, DPS310_I2C_ADDRESS_1, addr);
+        err = ESP_ERR_INVALID_ARG;
+        goto fail;
+    }
+
+    dev->i2c_dev.port = port;
+    dev->i2c_dev.addr = addr;
+    dev->i2c_dev.cfg.sda_io_num = sda_gpio;
+    dev->i2c_dev.cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->i2c_dev.cfg.master.clk_speed = DPS310_I2C_FREQ_MAX_ESP_IDF_HZ;
+#endif
+
+    ESP_LOGD(TAG, "Port: %u SCL: GPIO%i SDA: GPIO%i",
+             port,
+             dev->i2c_dev.cfg.scl_io_num,
+             dev->i2c_dev.cfg.sda_io_num);
+    err = i2c_dev_create_mutex(&dev->i2c_dev);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "i2c_dev_create_mutex(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+    dev->pressure_s = DPS310_AVERAGE_SEA_LEVEL_PRESSURE_Pa;
+    dev->offset = 0;
+
+fail:
+    return err;
+
+}
+
+esp_err_t dps310_free_desc(dps310_t *dev)
+{
+    esp_err_t err = ESP_FAIL;
+
+    err = i2c_dev_delete_mutex(&dev->i2c_dev);
+    if (err != ESP_OK)
+    {
+        ESP_LOGW(TAG, "i2c_dev_delete_mutex(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+fail:
+    return err;
+}
+
+static int pow_int(int base, int x)
+{
+    int result = 1;
+
+    for (int i = 0; i < x ; ++i)
+    {
+        result *= base;
+    }
+    return result;
+}
+
+esp_err_t dps310_init(dps310_t *dev, dps310_config_t *config)
+{
+    uint8_t reg_value = 0;
+    esp_err_t err = ESP_FAIL;
+
+    if (dev == NULL)
+    {
+        ESP_LOGE(TAG, "dps310_init(): invalid dev");
+        err = ESP_ERR_INVALID_ARG;
+        goto fail;
+    }
+    if (config == NULL)
+    {
+        ESP_LOGE(TAG, "dps310_init(): invalid config");
+        err = ESP_ERR_INVALID_ARG;
+        goto fail;
+    }
+
+    err = _read_reg(&dev->i2c_dev, DPS310_REG_ID, &reg_value);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "Device not found");
+        goto fail;
+    }
+
+    dev->prod_id = (DPS310_REG_ID_PROD_MASK & reg_value) >> 4;
+    dev->prod_rev = DPS310_REG_ID_REV_MASK & reg_value;
+    ESP_LOGD(TAG, "prod_id: 0x%x prod_rev: 0x%x", dev->prod_id, dev->prod_rev);
+    if (dev->prod_id != DPS310_PROD_ID)
+    {
+        ESP_LOGE(TAG, "Invalid prod ID: expected: 0x%x (DPS310) got: 0x%x", DPS310_PROD_ID, dev->prod_id);
+        err = ESP_FAIL;
+        goto fail;
+    }
+
+    err = dps310_reset(dev);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "dps310_reset(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+
+    err = dps310_quirk(dev);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "dps310_quirk(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+
+    ESP_LOGD(TAG, "Pressure measurement rate: %i measurements / sec", pow_int(2, config->pm_rate));
+    err = dps310_set_rate_p(dev, config->pm_rate);
+    if (err != ESP_OK)
+    {
+        goto fail;
+    }
+    ESP_LOGD(TAG, "Pressure oversampling: %i time(s)", pow_int(2, config->pm_oversampling));
+    err = dps310_set_oversampling_p(dev, config->pm_oversampling);
+    if (err != ESP_OK)
+    {
+        goto fail;
+    }
+    ESP_LOGD(TAG, "Temperature measurement rate: %i measurements / sec", pow_int(2, config->tmp_rate));
+    err = dps310_set_rate_t(dev, config->tmp_rate);
+    if (err != ESP_OK)
+    {
+        goto fail;
+    }
+    ESP_LOGD(TAG, "Temperature oversampling: %i time(s)", pow_int(2, config->tmp_oversampling));
+    err = dps310_set_oversampling_t(dev, config->tmp_oversampling);
+    if (err != ESP_OK)
+    {
+        goto fail;
+    }
+    ESP_LOGD(TAG, "Temperature source: %s", config->tmp_src == DPS310_TMP_SRC_INTERNAL ? "internal" : "external");
+    err = dps310_set_tmp_ext(dev, config->tmp_src);
+    if (err != ESP_OK)
+    {
+        goto fail;
+    }
+    ESP_LOGD(TAG, "Temperature COEF source: %s", config->tmp_coef == DPS310_TMP_SRC_INTERNAL ? "internal" : "external");
+    err = dps310_set_tmp_coef_ext(dev, config->tmp_coef);
+    if (err != ESP_OK)
+    {
+        goto fail;
+    }
+    if (config->tmp_src != config->tmp_coef)
+    {
+
+        /* XXX don't know when DPS310_TMP_SRC_INTERNAL should be used. the
+         * datasheet does not mention the differece.
+         */
+        ESP_LOGW(TAG, "tmp_src and tmp_coef should be an identical source. Use DPS310_TMP_SRC_EXTERNAL in the config");
+    }
+    ESP_LOGD(TAG, "Interrupt FIFO: %s", config->int_fifo_mode == DPS310_INT_FIFO_ENABLE ? "enabled" : "disabled");
+    err = dps310_set_int_fifo(dev, config->int_fifo_mode);
+    if (err != ESP_OK)
+    {
+        goto fail;
+    }
+    ESP_LOGD(TAG, "Interrupt temperature: %s", config->int_tmp_mode == DPS310_INT_TMP_ENABLE ? "enabled" : "disabled");
+    err = dps310_set_int_tmp(dev, config->int_tmp_mode);
+    if (err != ESP_OK)
+    {
+        goto fail;
+    }
+    ESP_LOGD(TAG, "Interrupt pressure: %s", config->int_prs_mode == DPS310_INT_PRS_ENABLE ? "enabled" : "disabled");
+    err = dps310_set_int_prs(dev, config->int_prs_mode);
+    if (err != ESP_OK)
+    {
+        goto fail;
+    }
+
+    ESP_LOGD(TAG, "Temperature result bit-shift: %s", config->t_shift_mode == DPS310_T_SHIFT_ENABLE ? "enabled" : "disabled");
+    if (config->tmp_oversampling > DPS310_TMP_PRC_8 && config->t_shift_mode != DPS310_T_SHIFT_ENABLE)
+    {
+        ESP_LOGW(TAG, "Temperature result bit-shift must be enabled, but is disabled. Set DPS310_T_SHIFT_ENABLE");
+    }
+    err = dps310_set_t_shift(dev, config->t_shift_mode);
+    if (err != ESP_OK)
+    {
+        goto fail;
+    }
+
+    ESP_LOGD(TAG, "Pressure result bit-shift: %s", config->p_shift_mode == DPS310_P_SHIFT_ENABLE ? "enabled" : "disabled");
+    if (config->pm_oversampling > DPS310_PM_PRC_8 && config->p_shift_mode != DPS310_P_SHIFT_ENABLE)
+    {
+        ESP_LOGW(TAG, "Pressure result bit-shift must be enabled, but is disabled. Set DPS310_P_SHIFT_ENABLE");
+    }
+    err = dps310_set_p_shift(dev, config->p_shift_mode);
+    if (err != ESP_OK)
+    {
+        goto fail;
+    }
+
+    ESP_LOGD(TAG, "FIFO: %s", config->fifo_en_mode == DPS310_FIFO_ENABLE ? "enabled" : "disabled");
+    err = dps310_set_fifo_en(dev, config->fifo_en_mode);
+    if (err != ESP_OK)
+    {
+        goto fail;
+    }
+
+    err = ESP_OK;
+
+fail:
+    return err;
+}
+
+esp_err_t dps310_get_rate_p(dps310_t *dev, dps310_pm_rate_t *value)
+{
+    CHECK_ARG(dev && value);
+
+    return _read_reg_mask(&dev->i2c_dev, DPS310_REG_PRS_CFG, DPS310_REG_PRS_CFG_PM_RATE_MASK, (uint8_t *)value);
+}
+
+esp_err_t dps310_set_rate_p(dps310_t *dev, dps310_pm_rate_t value)
+{
+    CHECK_ARG(dev);
+
+    return _update_reg(&dev->i2c_dev, DPS310_REG_PRS_CFG, DPS310_REG_PRS_CFG_PM_RATE_MASK, value);
+}
+
+esp_err_t dps310_get_rate_t(dps310_t *dev, dps310_tmp_rate_t *value)
+{
+    CHECK_ARG(dev && value);
+
+    return _read_reg_mask(&dev->i2c_dev, DPS310_REG_TMP_CFG, DPS310_REG_PRS_CFG_TMP_RATE_MASK, (uint8_t *)value);
+}
+
+esp_err_t dps310_set_rate_t(dps310_t *dev, dps310_tmp_rate_t value)
+{
+    CHECK_ARG(dev);
+
+    return _update_reg(&dev->i2c_dev, DPS310_REG_TMP_CFG, DPS310_REG_PRS_CFG_TMP_RATE_MASK, value);
+}
+
+esp_err_t dps310_reset(dps310_t *dev)
+{
+    esp_err_t err = ESP_FAIL;
+    uint8_t value = DPS310_SOFT_RST_VALUE;
+
+    CHECK_ARG(dev);
+
+    ESP_LOGD(TAG, "Resetting the device");
+    err = _write_reg(&dev->i2c_dev, DPS310_REG_RESET, &value);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "failed to reset the device");
+        goto fail;
+    }
+    vTaskDelay(pdMS_TO_TICKS(DPS310_STARTUP_DELAY_MS));
+fail:
+    return err;
+}
+
+esp_err_t dps310_get_oversampling_p(dps310_t *dev, dps310_pm_oversampling_t *value)
+{
+    esp_err_t err = ESP_FAIL;
+
+    CHECK_ARG(dev && value);
+    err = _read_reg_mask(&dev->i2c_dev, DPS310_REG_PRS_CFG, DPS310_REG_PRS_CFG_PM_PRC_MASK, (uint8_t *)value);
+    if (err == ESP_OK)
+    {
+        /* XXX when new p_rate is available, always keep it in dev as a cache */
+        dev->p_rate = *value;
+    }
+    return err;
+}
+
+esp_err_t dps310_set_oversampling_p(dps310_t *dev, dps310_pm_oversampling_t value)
+{
+    esp_err_t err = ESP_FAIL;
+
+    CHECK_ARG(dev);
+    err = _update_reg(&dev->i2c_dev, DPS310_REG_PRS_CFG, DPS310_REG_PRS_CFG_PM_PRC_MASK, value);
+    if (err == ESP_OK)
+    {
+        /* XXX when new p_rate is available, always keep it in dev as a cache */
+        dev->p_rate = value;
+    }
+    return err;
+}
+
+esp_err_t dps310_get_oversampling_t(dps310_t *dev, dps310_tmp_oversampling_t *value)
+{
+    esp_err_t err = ESP_FAIL;
+
+    CHECK_ARG(dev && value);
+    err = _read_reg_mask(&dev->i2c_dev, DPS310_REG_TMP_CFG, DPS310_REG_TMP_CFG_TMP_PRC_MASK, (uint8_t *)value);
+    if (err == ESP_OK)
+    {
+        /* XXX when new t_rate is available, always keep it in dev as a cache */
+        dev->t_rate = *value;
+    }
+    return err;
+}
+
+esp_err_t dps310_set_oversampling_t(dps310_t *dev, dps310_tmp_oversampling_t value)
+{
+    esp_err_t err = ESP_FAIL;
+
+    CHECK_ARG(dev);
+    err = _update_reg(&dev->i2c_dev, DPS310_REG_TMP_CFG, DPS310_REG_TMP_CFG_TMP_PRC_MASK, value);
+    if (err == ESP_OK)
+    {
+        /* XXX when new t_rate is available, always keep it in dev as a cache */
+        dev->t_rate = value;
+    }
+    return err;
+}
+
+esp_err_t dps310_get_tmp_ext(dps310_t *dev, dps310_tmp_src_ext_t *value)
+{
+    CHECK_ARG(dev && value);
+
+    return _read_reg_mask(&dev->i2c_dev, DPS310_REG_TMP_CFG, DPS310_REG_PRS_CFG_TMP_EXT_MASK, (uint8_t *)value);
+}
+
+esp_err_t dps310_set_tmp_ext(dps310_t *dev, dps310_tmp_src_ext_t value)
+{
+    CHECK_ARG(dev);
+
+    return _update_reg(&dev->i2c_dev, DPS310_REG_TMP_CFG, DPS310_REG_PRS_CFG_TMP_EXT_MASK, value);
+}
+
+esp_err_t dps310_set_tmp_coef_ext(dps310_t *dev, dps310_tmp_src_ext_t value)
+{
+    CHECK_ARG(dev);
+
+    return _update_reg(&dev->i2c_dev, DPS310_REG_COEF_SRCE, DPS310_REG_COEF_SRCE_MASK, value);
+}
+
+esp_err_t dps310_get_int_hl(dps310_t *dev, dps310_int_hl_active_level_t *value)
+{
+    CHECK_ARG(dev && value);
+
+    return _read_reg_mask(&dev->i2c_dev, DPS310_REG_CFG_REG, DPS310_REG_CFG_REG_INT_HL_MASK, (uint8_t *)value);
+}
+
+esp_err_t dps310_set_int_hl(dps310_t *dev, dps310_int_hl_active_level_t value)
+{
+    CHECK_ARG(dev);
+
+    return _update_reg(&dev->i2c_dev, DPS310_REG_CFG_REG, DPS310_REG_PRS_CFG_TMP_EXT_MASK, value);
+}
+
+esp_err_t dps310_get_int_fifo(dps310_t *dev, dps310_int_fifo_mode_t *value)
+{
+    CHECK_ARG(dev && value);
+
+    return _read_reg_mask(&dev->i2c_dev, DPS310_REG_CFG_REG, DPS310_REG_CFG_REG_INT_FIFO_MASK, (uint8_t *)value);
+}
+
+esp_err_t dps310_set_int_fifo(dps310_t *dev, dps310_int_fifo_mode_t value)
+{
+    CHECK_ARG(dev);
+
+    return _update_reg(&dev->i2c_dev, DPS310_REG_CFG_REG, DPS310_REG_CFG_REG_INT_FIFO_MASK, value);
+}
+
+esp_err_t dps310_get_int_tmp(dps310_t *dev, dps310_int_tmp_mode_t *value)
+{
+    CHECK_ARG(dev && value);
+
+    return _read_reg_mask(&dev->i2c_dev, DPS310_REG_CFG_REG, DPS310_REG_CFG_REG_INT_TMP_MASK, (uint8_t *)value);
+}
+
+esp_err_t dps310_set_int_tmp(dps310_t *dev, dps310_int_tmp_mode_t value)
+{
+    return _update_reg(&dev->i2c_dev, DPS310_REG_CFG_REG, DPS310_REG_CFG_REG_INT_TMP_MASK, value);
+}
+
+esp_err_t dps310_get_int_prs(dps310_t *dev, dps310_int_prs_mode_t *value)
+{
+    CHECK_ARG(dev && value);
+
+    return _read_reg_mask(&dev->i2c_dev, DPS310_REG_CFG_REG, DPS310_REG_CFG_REG_INT_PRS_MASK, (uint8_t *)value);
+}
+
+esp_err_t dps310_set_int_prs(dps310_t *dev, dps310_int_prs_mode_t value)
+{
+    CHECK_ARG(dev);
+
+    return _update_reg(&dev->i2c_dev, DPS310_REG_CFG_REG, DPS310_REG_CFG_REG_INT_PRS_MASK, value);
+}
+
+esp_err_t dps310_get_t_shift(dps310_t *dev, dps310_t_shift_mode_t *value)
+{
+    CHECK_ARG(dev && value);
+
+    return _read_reg_mask(&dev->i2c_dev, DPS310_REG_CFG_REG, DPS310_REG_CFG_REG_T_SHIFT_MASK, (uint8_t *)value);
+}
+
+esp_err_t dps310_set_t_shift(dps310_t *dev, dps310_t_shift_mode_t value)
+{
+    CHECK_ARG(dev);
+
+    return _update_reg(&dev->i2c_dev, DPS310_REG_CFG_REG, DPS310_REG_CFG_REG_T_SHIFT_MASK, value);
+}
+
+esp_err_t dps310_get_p_shift(dps310_t *dev, dps310_p_shift_mode_t *value)
+{
+    CHECK_ARG(dev && value);
+
+    return _read_reg_mask(&dev->i2c_dev, DPS310_REG_CFG_REG, DPS310_REG_CFG_REG_P_SHIFT_MASK, (uint8_t *)value);
+}
+
+esp_err_t dps310_set_p_shift(dps310_t *dev, dps310_p_shift_mode_t value)
+{
+    CHECK_ARG(dev);
+
+    return _update_reg(&dev->i2c_dev, DPS310_REG_CFG_REG, DPS310_REG_CFG_REG_P_SHIFT_MASK, value);
+}
+
+esp_err_t dps310_get_fifo_en(dps310_t *dev, dps310_fifo_en_mode_t *value)
+{
+    CHECK_ARG(dev && value);
+
+    return _read_reg_mask(&dev->i2c_dev, DPS310_REG_CFG_REG, DPS310_REG_CFG_REG_FIFO_EN_MASK, (uint8_t *)value);
+}
+
+esp_err_t dps310_set_fifo_en(dps310_t *dev, dps310_fifo_en_mode_t value)
+{
+    return _update_reg(&dev->i2c_dev, DPS310_REG_CFG_REG, DPS310_REG_CFG_REG_FIFO_EN_MASK, value);
+}
+
+esp_err_t dps310_get_spi_mode(dps310_t *dev, dps310_spi_mode_t *value)
+{
+    CHECK_ARG(dev && value);
+
+    return _read_reg_mask(&dev->i2c_dev, DPS310_REG_CFG_REG, DPS310_REG_CFG_REG_SPI_MODE_MASK, (uint8_t *)value);
+}
+
+esp_err_t dps310_set_spi_mode(dps310_t *dev, dps310_spi_mode_t value)
+{
+    CHECK_ARG(dev);
+
+    return _update_reg(&dev->i2c_dev, DPS310_REG_CFG_REG, DPS310_REG_CFG_REG_SPI_MODE_MASK, value);
+}
+
+static int32_t two_complement_of(uint32_t value, uint8_t length)
+{
+    int32_t result = (uint32_t)value;
+    if (value & ((uint32_t)1 << (length - (uint32_t)1)))
+    {
+        result -= ((uint32_t) 1 << length);
+    }
+    return result;
+}
+
+esp_err_t dps310_get_coef(dps310_t *dev)
+{
+    uint8_t reg_values[DPS310_REG_COEF_LEN];
+
+    esp_err_t err = ESP_FAIL;
+
+    CHECK_ARG(dev);
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    err = i2c_dev_read_reg(&dev->i2c_dev, DPS310_REG_COEF, reg_values, DPS310_REG_COEF_LEN);
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "i2c_dev_read_reg(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+
+    dev->coef.c0 = ((uint32_t)reg_values[0] << 4) | (((uint32_t)reg_values[1] >> 4) & 0x0F);
+    dev->coef.c0 = two_complement_of(dev->coef.c0, 12);
+
+    dev->coef.c1 = (((uint32_t)reg_values[1] & 0x0F) << 8) | (uint32_t)reg_values[2];
+    dev->coef.c1 = two_complement_of(dev->coef.c1, 12);
+
+    dev->coef.c00 = ((uint32_t)reg_values[3] << 12) | ((uint32_t)reg_values[4] << 4) | (((uint32_t)reg_values[5] >> 4) & 0x0F);
+    dev->coef.c00 = two_complement_of(dev->coef.c00, 20);
+
+    dev->coef.c10 = (((uint32_t)reg_values[5] & 0x0F) << 16) | ((uint32_t)reg_values[6] << 8) | (uint32_t)reg_values[7];
+    dev->coef.c10 = two_complement_of(dev->coef.c10, 20);
+
+    dev->coef.c01 = ((uint32_t)reg_values[8] << 8) | (uint32_t)reg_values[9];
+    dev->coef.c01 = two_complement_of(dev->coef.c01, 16);
+
+    dev->coef.c11 = ((uint32_t)reg_values[10] << 8) | (uint32_t)reg_values[11];
+    dev->coef.c11 = two_complement_of(dev->coef.c11, 16);
+
+    dev->coef.c20 = ((uint32_t)reg_values[12] << 8) | (uint32_t)reg_values[13];
+    dev->coef.c20 = two_complement_of(dev->coef.c20, 16);
+
+    dev->coef.c21 = ((uint32_t)reg_values[14] << 8) | (uint32_t)reg_values[15];
+    dev->coef.c21 = two_complement_of(dev->coef.c21, 16);
+
+    dev->coef.c30 = ((uint32_t)reg_values[16] << 8) | (uint32_t)reg_values[17];
+    dev->coef.c30 = two_complement_of(dev->coef.c30, 16);
+fail:
+    return err;
+
+}
+
+esp_err_t dps310_get_mode(dps310_t *dev, dps310_mode_t *mode)
+{
+    return _read_reg_mask(&dev->i2c_dev, DPS310_REG_MEAS_CFG, DPS310_REG_MEAS_CFG_MEAS_CTRL_MASK, (uint8_t *)mode);
+}
+
+esp_err_t dps310_set_mode(dps310_t *dev, dps310_mode_t mode)
+{
+    return _update_reg(&dev->i2c_dev, DPS310_REG_MEAS_CFG, DPS310_REG_MEAS_CFG_MEAS_CTRL_MASK, mode);
+}
+
+esp_err_t dps310_flush_fifo(dps310_t *dev)
+{
+    uint8_t value = DPS310_FIFO_FLUSH_VALUE;
+
+    CHECK_ARG(dev);
+    return _write_reg(&dev->i2c_dev, DPS310_REG_RESET, &value);
+}
+
+esp_err_t dps310_enable_fifo(dps310_t *dev, bool enable)
+{
+    esp_err_t err = ESP_FAIL;
+
+    CHECK_ARG(dev);
+    if (!enable)
+    {
+        err = dps310_flush_fifo(dev);
+        if (err != ESP_OK)
+        {
+            ESP_LOGE(TAG, "dps310_flush_fifo(): %s", esp_err_to_name(err));
+            goto fail;
+        }
+    }
+    err = _update_reg(&dev->i2c_dev, DPS310_REG_CFG_REG, DPS310_REG_CFG_REG_FIFO_EN_MASK, enable ? 1 : 0);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "_update_reg(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+fail:
+    return err;
+}
+
+esp_err_t dps310_read_raw(dps310_t *dev, uint8_t reg, int32_t *value)
+{
+    uint8_t reg_values[DPS310_REG_SENSOR_VALUE_LEN] = {0};
+    esp_err_t err = ESP_FAIL;
+
+    CHECK_ARG(dev && value);
+    if (reg != DPS310_REG_TMP_B2 && reg != DPS310_REG_PRS_B2)
+    {
+        err = ESP_ERR_INVALID_ARG;
+        goto fail;
+    }
+    err = i2c_dev_read_reg(&dev->i2c_dev, reg, reg_values, DPS310_REG_SENSOR_VALUE_LEN);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "i2c_dev_read_reg(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+    *value = ((uint32_t)reg_values[0] << 16) | ((uint32_t)reg_values[1] << 8) | (uint32_t)reg_values[2];
+    *value = two_complement_of(*value, DPS310_REG_SENSOR_VALUE_LEN * 8);
+    ESP_LOGD(TAG, "raw value in %s resister: %" PRIi32, reg == DPS310_REG_TMP_B2 ? "temperature" : "pressure", *value);
+fail:
+    return err;
+}
+
+/* calcurate and return kT, or kP. */
+static float raw_to_scaled(int32_t raw, uint8_t rate)
+{
+    int32_t k = 0;
+
+    assert(rate <= N_SCALE_FACTORS - 1);
+    k = scale_factors[rate];
+    ESP_LOGD(TAG, "scale_factor: %" PRIi32, k);
+
+    /* Traw_sc = Traw / kT */
+    assert(k != 0);
+    return (float)raw / (float)k;
+}
+
+static float compensate_temp(dps310_t *dev, uint32_t T_raw, uint8_t rate)
+{
+
+    /* 4.9.2 How to Calculate Compensated Temperature Values */
+    float result = 0;
+    float T_raw_scaled = 0;
+
+    CHECK_ARG(dev);
+    T_raw_scaled = raw_to_scaled(T_raw, rate);
+
+    /* Tcomp (°C) = c0 * 0.5 + c1 * Traw_sc */
+    result = ((float)dev->coef.c0 * 0.5) + ((float)dev->coef.c1 * T_raw_scaled);
+    return result;
+}
+
+static float compensate_pressure(dps310_t *dev, int32_t T_raw, int32_t T_rate, int32_t P_raw, int32_t P_rate)
+{
+
+    /* 4.9.1 How to Calculate Compensated Pressure Values */
+    float T_raw_scaled = 0;
+    float P_raw_scaled = 0;
+
+    CHECK_ARG(dev);
+
+    T_raw_scaled = raw_to_scaled(T_raw, T_rate);
+    P_raw_scaled = raw_to_scaled(P_raw, P_rate);
+
+    /* Pcomp(Pa) = c00
+     *             + Praw_sc * (c10 + Praw_sc * (c20 + Praw_sc * c30))
+     *             + Traw_sc * c01
+     *             + Traw_sc * Praw_sc * (c11 + Praw_sc * c21)
+     */
+    return (float)dev->coef.c00
+           + P_raw_scaled * ((float)dev->coef.c10 + P_raw_scaled * ((float)dev->coef.c20 + P_raw_scaled * (float)dev->coef.c30))
+           + T_raw_scaled * (float)dev->coef.c01
+           + T_raw_scaled * P_raw_scaled * ((float)dev->coef.c11 + P_raw_scaled * (float)dev->coef.c21);
+}
+
+esp_err_t dps310_read_pressure(dps310_t *dev, float *pressure)
+{
+    esp_err_t err = ESP_FAIL;
+    int32_t T_raw = 0;
+    int32_t P_raw = 0;
+    dps310_tmp_oversampling_t T_rate = 0;
+    dps310_pm_oversampling_t P_rate = 0;
+
+    CHECK_ARG(dev && pressure);
+    err = dps310_get_oversampling_t(dev, &T_rate);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "dps310_get_oversampling_t(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+
+    err = dps310_get_oversampling_p(dev, &P_rate);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "dps310_get_oversampling_p(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+
+    err = dps310_read_raw(dev, DPS310_REG_TMP_B2, &T_raw);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "dps310_read_raw(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+    err = dps310_read_raw(dev, DPS310_REG_PRS_B2, &P_raw);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "dps310_read_raw(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+    *pressure = compensate_pressure(dev, T_raw, T_rate, P_raw, P_rate);
+fail:
+    return err;
+}
+
+esp_err_t dps310_read_pressure_wait(dps310_t *dev, uint16_t delay_ms, uint8_t max_attempt, float *pressure)
+{
+    esp_err_t err = ESP_FAIL;
+
+    CHECK_ARG(dev && pressure);
+    err = _wait_for_reg_bits(&dev->i2c_dev, DPS310_REG_MEAS_CFG, DPS310_REG_MEAS_CFG_PRS_RDY_MASK, 1, max_attempt, delay_ms);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "_wait_for_reg_bits(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+    err = dps310_read_pressure(dev, pressure);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "dps310_read_pressure(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+fail:
+    return err;
+}
+
+esp_err_t dps310_read_temp(dps310_t *dev, float *temperature)
+{
+    esp_err_t err = ESP_FAIL;
+    int32_t T_raw = 0;
+    dps310_tmp_oversampling_t rate = 0;
+
+    CHECK_ARG(dev && temperature);
+    err = dps310_get_oversampling_t(dev, &rate);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "dps310_get_oversampling_t(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+    err = dps310_read_raw(dev, DPS310_REG_TMP_B2, &T_raw);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "dps310_read_raw(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+
+    /* XXX when latest t_raw is available, always keep it in dev as a cache */
+    dev->t_raw = T_raw;
+    *temperature = compensate_temp(dev, dev->t_raw, rate);
+fail:
+    return err;
+}
+
+esp_err_t dps310_read_temp_wait(dps310_t *dev, uint16_t delay_ms, uint8_t max_attempt, float *temperature)
+{
+    esp_err_t err = ESP_FAIL;
+
+    CHECK_ARG(dev && temperature);
+    err = _wait_for_reg_bits(&dev->i2c_dev, DPS310_REG_MEAS_CFG, DPS310_REG_MEAS_CFG_TMP_RDY_MASK, 1, max_attempt, delay_ms);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "_wait_for_reg_bits(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+    err = dps310_read_temp(dev, temperature);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "dps310_read_temp(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+fail:
+    return err;
+}
+
+esp_err_t dps310_is_ready_for(dps310_t *dev, uint8_t reg, uint8_t mask, bool *ready)
+{
+    esp_err_t err = ESP_FAIL;
+    uint8_t reg_value = 0;
+
+    CHECK_ARG(dev && ready);
+
+    err = _read_reg_mask(&dev->i2c_dev, reg, mask, &reg_value);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "_read_reg_mask(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+    *ready = reg_value == 1 ? true : false;
+fail:
+    return err;
+}
+
+esp_err_t dps310_is_ready_for_coef(dps310_t *dev, bool *ready)
+{
+    CHECK_ARG(dev && ready);
+    return dps310_is_ready_for(dev, DPS310_REG_MEAS_CFG, DPS310_REG_MEAS_CFG_COEF_RDY_MASK, ready);
+}
+
+esp_err_t dps310_is_ready_for_sensor(dps310_t *dev, bool *ready)
+{
+    CHECK_ARG(dev && ready);
+    return dps310_is_ready_for(dev, DPS310_REG_MEAS_CFG, DPS310_REG_MEAS_CFG_SENSOR_RDY_MASK, ready);
+}
+
+esp_err_t dps310_is_ready_for_temp(dps310_t *dev, bool *ready)
+{
+    CHECK_ARG(dev && ready);
+    return dps310_is_ready_for(dev, DPS310_REG_MEAS_CFG, DPS310_REG_MEAS_CFG_TMP_RDY_MASK, ready);
+}
+
+esp_err_t dps310_is_ready_for_pressure(dps310_t *dev, bool *ready)
+{
+    CHECK_ARG(dev && ready);
+    return dps310_is_ready_for(dev, DPS310_REG_MEAS_CFG, DPS310_REG_MEAS_CFG_PRS_RDY_MASK, ready);
+}
+
+static inline bool dps310_is_pressure_result(int32_t data)
+{
+    return (uint8_t)data & 0x01;
+}
+
+static inline bool dps310_is_temp_result(int32_t data)
+{
+    return !dps310_is_pressure_result(data);
+}
+
+static esp_err_t dps310_read_reg_sensor_raw(dps310_t *dev, uint8_t reg, int32_t *value)
+{
+    uint8_t reg_values[DPS310_REG_SENSOR_VALUE_LEN] = {0};
+    esp_err_t err = ESP_FAIL;
+
+    CHECK_ARG(dev && value);
+    if (reg != DPS310_REG_TMP_B2 && reg != DPS310_REG_PRS_B2)
+    {
+        err = ESP_ERR_INVALID_ARG;
+        goto fail;
+    }
+    err = i2c_dev_read_reg(&dev->i2c_dev, reg, reg_values, DPS310_REG_SENSOR_VALUE_LEN);
+    *value = (uint32_t)reg_values[0] << 16 | (uint32_t)reg_values[1] << 8 | (uint32_t)reg_values[2];
+    ESP_LOGD(TAG, "reg_values: %" PRIi32, *value);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "i2c_dev_read_reg(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+fail:
+    return err;
+}
+
+esp_err_t dps310_is_fifo_empty(dps310_t *dev, bool *result)
+{
+    esp_err_t err = ESP_FAIL;
+    uint8_t value = 0;
+
+    CHECK_ARG(dev && result);
+    err = _read_reg_mask(&dev->i2c_dev, DPS310_REG_FIFO_STS, DPS310_REG_FIFO_STS_FIFO_EMPTY_MASK, &value);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "_read_reg_mask(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+    *result = value == 1 ? true : false;
+
+fail:
+    return err;
+
+}
+
+esp_err_t dps310_read_fifo(dps310_t *dev, dps310_fifo_measurement_t *measurement)
+{
+    int32_t raw_value = 0;
+    esp_err_t err = ESP_OK;
+
+    CHECK_ARG(dev && measurement);
+
+    /* Read a measurement from FIFO. to compensate the value, additional
+     * parameters, such as t_rate, are necessary. Use cached parameters in the
+     * device descriptor so that the value can be returned by only one I2C
+     * reading. This means that the driver does not work in multi-master
+     * configuration. As long as cached parameters are in sync with the real
+     * values in registers, the returned value is always correct even if a
+     * parameter is modified during the background mode.
+     */
+    err = dps310_read_reg_sensor_raw(dev, DPS310_REG_FIFO, &raw_value);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "dps310_read_reg_sensor_raw(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+    measurement->type = dps310_is_pressure_result(raw_value) ? DPS310_MEASUREMENT_PRESSURE : DPS310_MEASUREMENT_TEMPERATURE;
+    raw_value = two_complement_of(raw_value, 24);
+    if (raw_value == DPS310_FIFO_EMPTY)
+    {
+        measurement->type = DPS310_MEASUREMENT_EMPTY;
+    }
+
+    switch (measurement->type)
+    {
+        case DPS310_MEASUREMENT_TEMPERATURE:
+            ESP_LOGD(TAG, "DPS310_MEASUREMENT_TEMPERATURE: raw_value: %" PRIi32, raw_value);
+            measurement->result = compensate_temp(dev, raw_value, dev->t_rate);
+
+            /* XXX when new t_raw is available, always keep it in dev as a
+             * cache */
+            dev->t_raw = raw_value;
+            break;
+            ;;
+        case DPS310_MEASUREMENT_PRESSURE:
+            ESP_LOGD(TAG, "DPS310_MEASUREMENT_PRESSURE: raw_value: %" PRIi32, raw_value);
+            measurement->result = compensate_pressure(dev, dev->t_raw, dev->t_rate, raw_value, dev->p_rate);
+            break;
+            ;;
+        case DPS310_MEASUREMENT_EMPTY:
+            ESP_LOGD(TAG, "DPS310_MEASUREMENT_EMPTY: raw_value %" PRIi32, raw_value);
+            measurement->result = 0;
+            break;
+        default:
+
+            /* NOT REACHED */
+            abort();
+            ;;
+    }
+
+fail:
+    return err;
+}
+
+inline esp_err_t dps310_backgorund_start(dps310_t *dev, dps310_mode_t mode)
+{
+    CHECK_ARG(dev);
+    return dps310_set_mode(dev, mode);
+}
+
+inline esp_err_t dps310_backgorund_stop(dps310_t *dev)
+{
+    CHECK_ARG(dev);
+    return dps310_set_mode(dev, DPS310_MODE_STANDBY);
+}
+
+static int32_t dps310_calc_sea_level_pressure(float pressure, float altitude)
+{
+    return (int32_t)(pressure / pow(1.0 - altitude / 44330, 5.255));
+}
+
+static inline float calc_altitude(float pressure, float pressure_s)
+{
+    return 44330 * (1.0 - pow(pressure / pressure_s, 0.1903));
+}
+
+esp_err_t dps310_calibrate_altitude(dps310_t *dev, float altitude_real)
+{
+    esp_err_t err = ESP_FAIL;
+    float pressure = 0;
+    float temperature = 0;
+    float altitude_guess = 0;
+    dps310_pm_oversampling_t orig_oversmapling_p = 0;
+    dps310_tmp_oversampling_t orig_oversmapling_t = 0;
+
+    CHECK_ARG(dev);
+
+    /* keep original oversampling values */
+    err = dps310_get_oversampling_p(dev, &orig_oversmapling_p);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "dps310_get_oversampling_p(): %s", esp_err_to_name(err));
+        goto get_oversampling_fail;
+    }
+    err = dps310_get_oversampling_t(dev, &orig_oversmapling_t);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "dps310_get_oversampling_t(): %s", esp_err_to_name(err));
+        goto get_oversampling_fail;
+    }
+
+    /* modify oversampling values for accuracy */
+    err = dps310_set_oversampling_p(dev, DPS310_PM_PRC_64);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "dps310_set_oversampling_p(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+    err = dps310_set_oversampling_t(dev, DPS310_TMP_PRC_64);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "dps310_set_oversampling_t(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+
+    /* read temperature once in command mode */
+    err = dps310_set_mode(dev, DPS310_MODE_COMMAND_TEMPERATURE);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "dps310_set_mode(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+    err = dps310_read_temp_wait(dev, 100, 10, &temperature);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "dps310_read_temp(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+
+    /* read pressure once in command mode */
+    err = dps310_set_mode(dev, DPS310_MODE_COMMAND_PRESSURE);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "dps310_set_mode(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+    err = dps310_read_pressure_wait(dev, 100, 10, &pressure);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "dps310_read_pressure(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+
+    /* calibrate offset */
+    dev->pressure_s = dps310_calc_sea_level_pressure(pressure, altitude_real);
+    altitude_guess = calc_altitude(pressure, dev->pressure_s);
+    dev->offset = altitude_real - altitude_guess;
+
+    ESP_LOGI(TAG, "Calibration result:");
+    ESP_LOGI(TAG, "\tPressure: %0.2f (Pa)", pressure);
+    ESP_LOGI(TAG, "\tCalculated Pressure at sea level: %0.2f (Pa)", dev->pressure_s);
+    ESP_LOGI(TAG, "\tCalculated altitude: %0.2f (m)", altitude_guess);
+    ESP_LOGI(TAG, "\tReal altitude: %0.2f (m)", altitude_real);
+    ESP_LOGI(TAG, "\tOffset: %0.2f (m)", dev->offset);
+fail:
+    if (dps310_set_oversampling_t(dev, orig_oversmapling_t) != ESP_OK)
+    {
+        ESP_LOGW(TAG, "Restoring temperature oversampling failed");
+    }
+    if (dps310_set_oversampling_p(dev, orig_oversmapling_p) != ESP_OK)
+    {
+        ESP_LOGW(TAG, "Restoring pressure oversampling failed");
+    }
+get_oversampling_fail:
+    return err;
+}
+
+
+static float pressure_to_altitude(float pressure, float pressure_s, float *altitude)
+{
+    esp_err_t err = ESP_FAIL;
+
+    CHECK_ARG(altitude);
+    if (pressure_s == 0)
+    {
+        err = ESP_ERR_INVALID_ARG;
+        goto fail;
+    }
+    *altitude = 44330 * (1.0 - pow(pressure / pressure_s, 0.1903));
+    err = ESP_OK;
+
+fail:
+    return err;
+}
+
+esp_err_t dps310_calc_altitude(dps310_t *dev, float pressure, float *altitude)
+{
+    esp_err_t err = ESP_FAIL;
+
+    CHECK_ARG(dev && altitude);
+
+    err = pressure_to_altitude(pressure, dev->pressure_s, altitude);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "pressure_to_altitude(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+    *altitude = *altitude + dev->offset;
+fail:
+    return err;
+}
+
+esp_err_t dps310_read_altitude(dps310_t *dev, float *altitude)
+{
+    esp_err_t err = ESP_FAIL;
+    float pressure = 0;
+
+    CHECK_ARG(dev && altitude);
+
+    err = dps310_read_pressure(dev, &pressure);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "dps310_read_pressure(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+    err = dps310_calc_altitude(dev, pressure, altitude);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "dps310_calc_altitude(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+
+fail:
+    return err;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/dps310/src/helper_i2c.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2022 Tomoyuki Sakurai <y@trombik.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#if !defined(__DPS310__HELPER_I2C__H__)
+#define __DPS310__HELPER_I2C__H__
+
+/* standard headers */
+#include <inttypes.h>
+
+/* esp-idf headers */
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <esp_err.h>
+#include <esp_log.h>
+#include <i2cdev.h>
+
+/* private headers */
+#include "helper_macro.h"
+#include "helper_i2c.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static const char *TAG = "dps310/helper_i2c";
+
+/**
+ * @brief Read a single byte from a 8-bit resister without locking,
+ */
+inline esp_err_t _read_reg_nolock(i2c_dev_t *dev, uint8_t reg, uint8_t *val);
+
+/**
+ * @brief Write a single byte to a 8-bit resister without locking.
+ */
+inline esp_err_t _write_reg_nolock(i2c_dev_t *dev, uint8_t reg, uint8_t val);
+
+/* count the number of trailing zero in a value, usually bitmasks. */
+static uint8_t _count_trailing_zero_bits(uint8_t v)
+{
+    uint8_t count = 0;
+    if (v)
+    {
+        v = (v ^ (v - 1)) >> 1;
+        for (count = 0; v; count++)
+        {
+            v >>= 1;
+        }
+    }
+    else
+    {
+        count = 8;
+    }
+    return count;
+}
+
+esp_err_t _read_reg_nolock(i2c_dev_t *dev, uint8_t reg, uint8_t *val)
+{
+    return i2c_dev_read_reg(dev, reg, val, 1);
+}
+
+esp_err_t _read_reg(i2c_dev_t *dev, uint8_t reg, uint8_t *val)
+{
+    CHECK_ARG(dev);
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, _read_reg_nolock(dev, reg, val));
+    I2C_DEV_GIVE_MUTEX(dev);
+    return ESP_OK;
+}
+
+esp_err_t _read_reg_mask(i2c_dev_t *dev, uint8_t reg, uint8_t mask, uint8_t *val)
+{
+    uint8_t reg_value = 0;
+
+    CHECK_ARG(dev && val);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, _read_reg_nolock(dev, reg, &reg_value));
+    I2C_DEV_GIVE_MUTEX(dev);
+    *val = (reg_value & mask) >> _count_trailing_zero_bits(mask);
+    ESP_LOGV(TAG, "reg_value: %02X, val: %02X", reg_value, *val);
+    return ESP_OK;
+}
+
+esp_err_t _write_reg_nolock(i2c_dev_t *dev, uint8_t reg, uint8_t val)
+{
+    return i2c_dev_write_reg(dev, reg, &val, 1);
+}
+
+esp_err_t _update_reg_nolock(i2c_dev_t *dev, uint8_t reg, uint8_t mask, uint8_t val)
+{
+    uint8_t reg_value = 0;
+    uint8_t n_shift = 0;
+
+    CHECK_ARG(dev);
+    CHECK(_read_reg_nolock(dev, reg, &reg_value));
+
+    n_shift = _count_trailing_zero_bits(mask);
+    if (((reg_value >> n_shift) & val) == val)
+    {
+        ESP_LOGD(TAG, "register unchanged");
+    }
+    else
+    {
+        reg_value = (reg_value & (~mask)) | (val << n_shift);
+        CHECK(_write_reg_nolock(dev, reg, reg_value));
+    }
+    return ESP_OK;
+}
+
+esp_err_t _write_reg(i2c_dev_t *dev, uint8_t reg, uint8_t *value)
+{
+    esp_err_t err = ESP_FAIL;
+
+    CHECK_ARG(dev);
+    I2C_DEV_TAKE_MUTEX(dev);
+    err = _write_reg_nolock(dev, reg, *value);
+    I2C_DEV_GIVE_MUTEX(dev);
+    if (err != ESP_OK)
+    {
+        ESP_LOGE(TAG, "i2c_dev_write_reg(): %s", esp_err_to_name(err));
+    }
+    return err;
+}
+
+esp_err_t _update_reg(i2c_dev_t *dev, uint8_t reg, uint8_t mask, uint8_t val)
+{
+    CHECK_ARG(dev);
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, _update_reg_nolock(dev, reg, mask, val));
+    I2C_DEV_GIVE_MUTEX(dev);
+    return ESP_OK;
+}
+
+esp_err_t _wait_for_reg_bits(i2c_dev_t *dev, uint8_t reg, uint8_t mask, uint8_t val, uint8_t max_attempt, uint16_t delay_ms)
+{
+    esp_err_t err = ESP_FAIL;
+    uint8_t reg_value = 0;
+    uint8_t attempts = 0;
+
+    while (attempts < max_attempt)
+    {
+        attempts++;
+        err = _read_reg_mask(dev, reg, mask, &reg_value);
+        if (err != ESP_OK)
+        {
+            ESP_LOGE(TAG, "_wait_for_reg_bits(): %s", esp_err_to_name(err));
+            return err;
+        }
+        if (reg_value == val)
+        {
+            return ESP_OK;
+        }
+        vTaskDelay(pdMS_TO_TICKS(delay_ms));
+    }
+    ESP_LOGE(TAG, "Timeout. delay_ms: %" PRIu16 "max_attempt: %" PRIu8, delay_ms, max_attempt);
+    return ESP_ERR_TIMEOUT;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // __DPS310__HELPER_I2C__H__
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ds1302/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,27 @@
+---
+components:
+  - name: ds1302
+    description: |
+      Driver for DS1302 RTC module
+    group: rtc
+    groups: []
+    code_owners:
+      - name: UncleRus
+    depends:
+      - name: log
+      - name: esp_idf_lib_helpers
+      - name: freertos
+      - name: driver
+    thread_safe: no
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - name: UncleRus
+        year: 2021
+      - name: PavelM
+        year: 2016
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ds1302/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,11 @@
+if(${IDF_TARGET} STREQUAL esp8266)
+    set(req esp8266 freertos log esp_idf_lib_helpers)
+else()
+    set(req driver freertos log esp_idf_lib_helpers)
+endif()
+
+idf_component_register(
+    SRCS ds1302.c
+    INCLUDE_DIRS .
+    REQUIRES ${req}
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ds1302/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,27 @@
+Copyright (c) 2016, 2019 Ruslan V. Uss <unclerus@gmail.com>
+Copyright (c) 2016 Pavel Merzlyakov <merzlyakovpavel@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ds1302/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,7 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+
+ifdef CONFIG_IDF_TARGET_ESP8266
+COMPONENT_DEPENDS = esp8266 freertos log esp_idf_lib_helpers
+else
+COMPONENT_DEPENDS = driver freertos log esp_idf_lib_helpers
+endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ds1302/ds1302.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,309 @@
+/*
+ * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com>
+ * Copyright (c) 2016 Pavel Merzlyakov <merzlyakovpavel@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file ds1302.h
+ *
+ * ESP-IDF driver for DS1302 RTC
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com>\n
+ * Copyright (c) 2016 Pavel Merzlyakov <merzlyakovpavel@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#include <freertos/FreeRTOS.h>
+#include <string.h>
+#include <ets_sys.h>
+#include <esp_idf_lib_helpers.h>
+#include "ds1302.h"
+
+#define CH_REG   0x80
+#define WP_REG   0x8e
+
+#define CH_BIT     (1 << 7)
+#define WP_BIT     (1 << 7)
+#define HOUR12_BIT (1 << 7)
+#define PM_BIT     (1 << 5)
+
+#define CH_MASK ((uint8_t)(~CH_BIT))
+#define WP_MASK ((uint8_t)(~WP_BIT))
+
+#define CLOCK_BURST 0xbe
+#define RAM_BURST   0xfe
+
+#define SECONDS_MASK 0x7f
+#define HOUR12_MASK  0x1f
+#define HOUR24_MASK  0x3f
+
+#define GPIO_BIT(x) (1ULL << (x))
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+#if HELPER_TARGET_IS_ESP32
+static portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
+#define PORT_ENTER_CRITICAL portENTER_CRITICAL(&mux)
+#define PORT_EXIT_CRITICAL portEXIT_CRITICAL(&mux)
+#else
+#define PORT_ENTER_CRITICAL portENTER_CRITICAL()
+#define PORT_EXIT_CRITICAL portEXIT_CRITICAL()
+#endif
+
+#define CHECK_MUX(x) do { esp_err_t __; if ((__ = (x)) != ESP_OK) { PORT_EXIT_CRITICAL; return __; } } while (0)
+
+static uint8_t bcd2dec(uint8_t val)
+{
+    return (val >> 4) * 10 + (val & 0x0f);
+}
+
+static uint8_t dec2bcd(uint8_t val)
+{
+    return ((val / 10) << 4) + (val % 10);
+}
+
+inline static esp_err_t chip_enable(ds1302_t *dev)
+{
+    CHECK(gpio_set_level(dev->ce_pin, 1));
+    ets_delay_us(4);
+    return ESP_OK;
+}
+
+inline static esp_err_t chip_disable(ds1302_t *dev)
+{
+    return gpio_set_level(dev->ce_pin, 0);
+}
+
+inline static esp_err_t prepare(ds1302_t *dev, gpio_mode_t mode)
+{
+    CHECK(gpio_set_direction(dev->io_pin, mode));
+    CHECK(gpio_set_level(dev->sclk_pin, 0));
+    return chip_enable(dev);
+}
+
+inline static esp_err_t toggle_clock(ds1302_t *dev)
+{
+    CHECK(gpio_set_level(dev->sclk_pin, 1));
+    ets_delay_us(1);
+    CHECK(gpio_set_level(dev->sclk_pin, 0));
+    ets_delay_us(1);
+    return ESP_OK;
+}
+
+static esp_err_t write_byte(ds1302_t *dev, uint8_t b)
+{
+    for (uint8_t i = 0; i < 8; i++)
+    {
+        CHECK(gpio_set_level(dev->io_pin, (b >> i) & 1));
+        CHECK(toggle_clock(dev));
+    }
+    return ESP_OK;
+}
+
+static esp_err_t read_byte(ds1302_t *dev, uint8_t *b)
+{
+    *b = 0;
+    for (uint8_t i = 0; i < 8; i++)
+    {
+        *b |= gpio_get_level(dev->io_pin) << i;
+        CHECK(toggle_clock(dev));
+    }
+    return ESP_OK;
+}
+
+static esp_err_t read_register(ds1302_t *dev, uint8_t reg, uint8_t *val)
+{
+    PORT_ENTER_CRITICAL;
+    CHECK_MUX(prepare(dev, GPIO_MODE_OUTPUT));
+    CHECK_MUX(write_byte(dev, reg | 0x01));
+    CHECK_MUX(prepare(dev, GPIO_MODE_INPUT));
+    CHECK_MUX(read_byte(dev, val));
+    PORT_EXIT_CRITICAL;
+    return chip_disable(dev);
+}
+
+static esp_err_t write_register(ds1302_t *dev, uint8_t reg, uint8_t val)
+{
+    PORT_ENTER_CRITICAL;
+    CHECK_MUX(prepare(dev, GPIO_MODE_OUTPUT));
+    CHECK_MUX(write_byte(dev, reg));
+    CHECK_MUX(write_byte(dev, val));
+    PORT_EXIT_CRITICAL;
+    return chip_disable(dev);
+}
+
+static esp_err_t burst_read(ds1302_t *dev, uint8_t reg, uint8_t *dst, uint8_t len)
+{
+    PORT_ENTER_CRITICAL;
+    CHECK_MUX(prepare(dev, GPIO_MODE_OUTPUT));
+    CHECK_MUX(write_byte(dev, reg | 0x01));
+    CHECK_MUX(prepare(dev, GPIO_MODE_INPUT));
+    for (uint8_t i = 0; i < len; i++, dst++)
+        CHECK_MUX(read_byte(dev, dst));
+    PORT_EXIT_CRITICAL;
+    return chip_disable(dev);
+}
+
+static esp_err_t burst_write(ds1302_t *dev, uint8_t reg, uint8_t *src, uint8_t len)
+{
+    PORT_ENTER_CRITICAL;
+    CHECK_MUX(prepare(dev, GPIO_MODE_OUTPUT));
+    CHECK_MUX(write_byte(dev, reg));
+    for (uint8_t i = 0; i < len; i++, src++)
+        CHECK_MUX(write_byte(dev, *src));
+    PORT_EXIT_CRITICAL;
+    return chip_disable(dev);
+}
+
+static esp_err_t update_register(ds1302_t *dev, uint8_t reg, uint8_t mask, uint8_t val)
+{
+    uint8_t r;
+    CHECK(read_register(dev, reg, &r));
+    return write_register(dev, reg, (r & mask) | val);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t ds1302_init(ds1302_t *dev)
+{
+    CHECK_ARG(dev);
+
+    gpio_config_t io_conf;
+    memset(&io_conf, 0, sizeof(gpio_config_t));
+    io_conf.mode = GPIO_MODE_OUTPUT;
+    io_conf.pin_bit_mask =
+            GPIO_BIT(dev->ce_pin) |
+            GPIO_BIT(dev->io_pin) |
+            GPIO_BIT(dev->sclk_pin);
+    CHECK(gpio_config(&io_conf));
+
+    bool r;
+    CHECK(ds1302_is_running(dev, &r));
+    dev->ch = !r;
+
+    return ESP_OK;
+}
+
+esp_err_t ds1302_start(ds1302_t *dev, bool start)
+{
+    CHECK_ARG(dev);
+
+    CHECK(update_register(dev, CH_REG, CH_MASK, start ? 0 : CH_BIT));
+    dev->ch = !start;
+
+    return ESP_OK;
+}
+
+esp_err_t ds1302_is_running(ds1302_t *dev, bool *running)
+{
+    CHECK_ARG(dev && running);
+
+    uint8_t r;
+    CHECK(read_register(dev, CH_REG, &r));
+    *running = !(r & CH_BIT);
+    dev->ch = !*running;
+
+    return ESP_OK;
+}
+
+esp_err_t ds1302_set_write_protect(ds1302_t *dev, bool wp)
+{
+    CHECK_ARG(dev);
+
+    return update_register(dev, WP_REG, WP_MASK, wp ? WP_BIT : 0);
+}
+
+esp_err_t ds1302_get_write_protect(ds1302_t *dev, bool *wp)
+{
+    CHECK_ARG(dev && wp);
+
+    uint8_t r;
+    CHECK(read_register(dev, WP_REG, &r));
+    *wp = (r & WP_BIT) != 0;
+
+    return ESP_OK;
+}
+
+esp_err_t ds1302_get_time(ds1302_t *dev, struct tm *time)
+{
+    CHECK_ARG(dev && time);
+
+    uint8_t buf[7];
+    CHECK(burst_read(dev, CLOCK_BURST, buf, 7));
+
+    time->tm_sec = bcd2dec(buf[0] & SECONDS_MASK);
+    time->tm_min = bcd2dec(buf[1]);
+    if (buf[2] & HOUR12_BIT)
+    {
+        // RTC in 12-hour mode
+        time->tm_hour = bcd2dec(buf[2] & HOUR12_MASK) - 1;
+        if (buf[2] & PM_BIT)
+            time->tm_hour += 12;
+    }
+    else time->tm_hour = bcd2dec(buf[2] & HOUR24_MASK);
+    time->tm_mday = bcd2dec(buf[3]);
+    time->tm_mon  = bcd2dec(buf[4]) - 1;
+    time->tm_wday = bcd2dec(buf[5]) - 1;
+    time->tm_year = bcd2dec(buf[6]) + 100;
+
+    return ESP_OK;
+}
+
+esp_err_t ds1302_set_time(ds1302_t *dev, const struct tm *time)
+{
+    CHECK_ARG(dev && time);
+
+    uint8_t buf[8] = {
+        dec2bcd(time->tm_sec) | (dev->ch ? CH_BIT : 0),
+        dec2bcd(time->tm_min),
+        dec2bcd(time->tm_hour),
+        dec2bcd(time->tm_mday),
+        dec2bcd(time->tm_mon  + 1),
+        dec2bcd(time->tm_wday + 1),
+        dec2bcd(time->tm_year - 100),
+        0
+    };
+    return burst_write(dev, CLOCK_BURST, buf, 8);
+}
+
+esp_err_t ds1302_read_sram(ds1302_t *dev, uint8_t offset, void *buf, uint8_t len)
+{
+    CHECK_ARG(dev && buf && len);
+    CHECK_ARG(offset + len <= DS1302_RAM_SIZE);
+
+    return burst_read(dev, RAM_BURST, (uint8_t *)buf, len);
+}
+
+esp_err_t ds1302_write_sram(ds1302_t *dev, uint8_t offset, void *buf, uint8_t len)
+{
+    CHECK_ARG(dev && buf && len);
+    CHECK_ARG(offset + len <= DS1302_RAM_SIZE);
+
+    return burst_write(dev, RAM_BURST, (uint8_t *)buf, len);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ds1302/ds1302.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com>
+ * Copyright (c) 2016 Pavel Merzlyakov <merzlyakovpavel@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file ds1302.h
+ * @defgroup ds1302 ds1302
+ * @{
+ *
+ * ESP-IDF driver for DS1302 RTC
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com>\n
+ * Copyright (c) 2016 Pavel Merzlyakov <merzlyakovpavel@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __DS1302_H__
+#define __DS1302_H__
+
+#include <stdbool.h>
+#include <driver/gpio.h>
+#include <time.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define DS1302_RAM_SIZE 31
+
+/**
+ * Device descriptor
+ */
+typedef struct
+{
+    gpio_num_t ce_pin;     //!< GPIO pin connected to CE
+    gpio_num_t io_pin;     //!< GPIO pin connected to chip I/O
+    gpio_num_t sclk_pin;   //!< GPIO pin connected to SCLK
+    bool ch;               //!< true if clock is halted
+} ds1302_t;
+
+/**
+ * @brief Initialize device
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t ds1302_init(ds1302_t *dev);
+
+/**
+ * @brief Start/stop clock
+ *
+ * @param dev Device descriptor
+ * @param start Start clock if true
+ * @return `ESP_OK` on success
+ */
+esp_err_t ds1302_start(ds1302_t *dev, bool start);
+
+/**
+ * @brief Get current clock state
+ *
+ * @param dev Device descriptor
+ * @param[out] running true if clock running
+ * @return `ESP_OK` on success
+ */
+esp_err_t ds1302_is_running(ds1302_t *dev, bool *running);
+
+/**
+ * @brief Enable/disable write protection
+ *
+ * @param dev Device descriptor
+ * @param wp Set RTC write-protected if true
+ * @return `ESP_OK` on success
+ */
+esp_err_t ds1302_set_write_protect(ds1302_t *dev, bool wp);
+
+/**
+ * @brief Get write protection status
+ *
+ * @param dev Device descriptor
+ * @param[out] wp true if RTC write-protected
+ * @return `ESP_OK` on success
+ */
+esp_err_t ds1302_get_write_protect(ds1302_t *dev, bool *wp);
+
+/**
+ * @brief Get current time
+ *
+ * @param dev Device descriptor
+ * @param[out] time Current time
+ * @return `ESP_OK` on success
+ */
+esp_err_t ds1302_get_time(ds1302_t *dev, struct tm *time);
+
+/**
+ * @brief Set time to RTC
+ *
+ * @param dev Device descriptor
+ * @param time Time
+ * @return `ESP_OK` on success
+ */
+esp_err_t ds1302_set_time(ds1302_t *dev, const struct tm *time);
+
+/**
+ * @brief Read RAM contents into the buffer
+ *
+ * @param dev Device descriptor
+ * @param offset Start byte, 0..55
+ * @param[out] buf Buffer
+ * @param len Bytes to read, 1..56
+ *
+ * @return `ESP_OK` on success
+ */
+esp_err_t ds1302_read_sram(ds1302_t *dev, uint8_t offset, void *buf, uint8_t len);
+
+/**
+ * @brief Write buffer to RTC RAM
+ *
+ * @param dev Device descriptor
+ * @param offset Start byte, 0..55
+ * @param buf Buffer
+ * @param len Bytes to write, 1..56
+ *
+ * @return `ESP_OK` on success
+ */
+esp_err_t ds1302_write_sram(ds1302_t *dev, uint8_t offset, void *buf, uint8_t len);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __DS1302_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ds1307/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,24 @@
+---
+components:
+  - name: ds1307
+    description: |
+      Driver for DS1307 RTC module
+    group: rtc
+    groups: []
+    code_owners:
+      - name: UncleRus
+    depends:
+      - name: i2cdev
+      - name: log
+      - name: esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - name: UncleRus
+        year: 2016
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ds1307/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS ds1307.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ds1307/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+Copyright 2016, 2018 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ds1307/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ds1307/ds1307.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file ds1307.c
+ *
+ * ESP-IDF driver for DS1307 real-time clock
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2016, 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#include <esp_err.h>
+#include <esp_idf_lib_helpers.h>
+#include "ds1307.h"
+
+#define I2C_FREQ_HZ 400000
+
+#define RAM_SIZE 56
+
+#define TIME_REG    0
+#define CONTROL_REG 7
+#define RAM_REG     8
+
+#define CH_BIT      (1 << 7)
+#define HOUR12_BIT  (1 << 6)
+#define PM_BIT      (1 << 5)
+#define SQWE_BIT    (1 << 4)
+#define OUT_BIT     (1 << 7)
+
+#define CH_MASK      0x7f
+#define SECONDS_MASK 0x7f
+#define HOUR12_MASK  0x1f
+#define HOUR24_MASK  0x3f
+#define SQWEF_MASK   0xfc
+#define SQWE_MASK    0xef
+#define OUT_MASK     0x7f
+
+#define CHECK_ARG(ARG) do { if (!(ARG)) return ESP_ERR_INVALID_ARG; } while (0)
+
+static uint8_t bcd2dec(uint8_t val)
+{
+    return (val >> 4) * 10 + (val & 0x0f);
+}
+
+static uint8_t dec2bcd(uint8_t val)
+{
+    return ((val / 10) << 4) + (val % 10);
+}
+
+static esp_err_t update_register(i2c_dev_t *dev, uint8_t reg, uint8_t mask, uint8_t val)
+{
+    CHECK_ARG(dev);
+
+    uint8_t old;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, reg, &old, 1));
+    uint8_t buf = (old & mask) | val;
+    esp_err_t res = i2c_dev_write_reg(dev, reg, &buf, 1);
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return res;
+}
+
+esp_err_t ds1307_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+
+    dev->port = port;
+    dev->addr = DS1307_ADDR;
+    dev->cfg.sda_io_num = sda_gpio;
+    dev->cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+    return i2c_dev_create_mutex(dev);
+}
+
+esp_err_t ds1307_free_desc(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(dev);
+}
+
+esp_err_t ds1307_start(i2c_dev_t *dev, bool start)
+{
+    return update_register(dev, TIME_REG, CH_MASK, start ? 0 : CH_BIT);
+}
+
+esp_err_t ds1307_is_running(i2c_dev_t *dev, bool *running)
+{
+    CHECK_ARG(dev && running);
+
+    uint8_t val;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, TIME_REG, &val, 1));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    *running = val & CH_BIT ? false : true;
+
+    return ESP_OK;
+}
+
+esp_err_t ds1307_get_time(i2c_dev_t *dev, struct tm *time)
+{
+    CHECK_ARG(dev && time);
+
+    uint8_t buf[7];
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, TIME_REG, buf, 7));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    time->tm_sec = bcd2dec(buf[0] & SECONDS_MASK);
+    time->tm_min = bcd2dec(buf[1]);
+    if (buf[2] & HOUR12_BIT)
+    {
+        // RTC in 12-hour mode
+        time->tm_hour = bcd2dec(buf[2] & HOUR12_MASK) - 1;
+        if (buf[2] & PM_BIT)
+            time->tm_hour += 12;
+    }
+    else
+        time->tm_hour = bcd2dec(buf[2] & HOUR24_MASK);
+    time->tm_wday = bcd2dec(buf[3]) - 1;
+    time->tm_mday = bcd2dec(buf[4]);
+    time->tm_mon  = bcd2dec(buf[5]) - 1;
+    time->tm_year = bcd2dec(buf[6]) + 100;
+
+    return ESP_OK;
+}
+
+esp_err_t ds1307_set_time(i2c_dev_t *dev, const struct tm *time)
+{
+    CHECK_ARG(dev && time);
+
+    uint8_t buf[7] = {
+        dec2bcd(time->tm_sec),
+        dec2bcd(time->tm_min),
+        dec2bcd(time->tm_hour),
+        dec2bcd(time->tm_wday + 1),
+        dec2bcd(time->tm_mday),
+        dec2bcd(time->tm_mon + 1),
+        dec2bcd(time->tm_year - 100)
+    };
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, TIME_REG, buf, sizeof(buf)));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t ds1307_enable_squarewave(i2c_dev_t *dev, bool enable)
+{
+    return update_register(dev, CONTROL_REG, SQWE_MASK, enable ? SQWE_BIT : 0);
+}
+
+esp_err_t ds1307_is_squarewave_enabled(i2c_dev_t *dev, bool *sqw_en)
+{
+    CHECK_ARG(dev && sqw_en);
+
+    uint8_t val;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, CONTROL_REG, &val, 1));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    *sqw_en = val & SQWE_BIT;
+
+    return ESP_OK;
+}
+
+esp_err_t ds1307_set_squarewave_freq(i2c_dev_t *dev, ds1307_squarewave_freq_t freq)
+{
+    return update_register(dev, CONTROL_REG, SQWEF_MASK, freq);
+}
+
+esp_err_t ds1307_get_squarewave_freq(i2c_dev_t *dev, ds1307_squarewave_freq_t *sqw_freq)
+{
+    CHECK_ARG(dev && sqw_freq);
+
+    uint8_t val;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, CONTROL_REG, &val, 1));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    *sqw_freq = val & ~SQWEF_MASK;
+
+    return ESP_OK;
+}
+
+esp_err_t ds1307_get_output(i2c_dev_t *dev, bool *out)
+{
+    CHECK_ARG(dev && out);
+
+    uint8_t val;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, CONTROL_REG, &val, 1));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    *out = val & OUT_BIT;
+
+    return ESP_OK;
+}
+
+esp_err_t ds1307_set_output(i2c_dev_t *dev, bool value)
+{
+    return update_register(dev, CONTROL_REG, OUT_MASK, value ? OUT_BIT : 0);
+}
+
+esp_err_t ds1307_read_ram(i2c_dev_t *dev, uint8_t offset, uint8_t *buf, uint8_t len)
+{
+    CHECK_ARG(dev && buf);
+
+    if (offset + len > RAM_SIZE)
+        return ESP_ERR_NO_MEM;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, RAM_REG + offset, buf, len));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t ds1307_write_ram(i2c_dev_t *dev, uint8_t offset, uint8_t *buf, uint8_t len)
+{
+    CHECK_ARG(dev && buf);
+
+    if (offset + len > RAM_SIZE)
+        return ESP_ERR_NO_MEM;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, RAM_REG + offset, buf, len));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ds1307/ds1307.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file ds1307.h
+ * @defgroup ds1307 ds1307
+ * @{
+ *
+ * ESP-IDF driver for DS1307 real-time clock
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __DS1307_H__
+#define __DS1307_H__
+
+#include <stdbool.h>
+#include <time.h>
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define DS1307_ADDR 0x68 //!< I2C address
+
+/**
+ * Squarewave frequency
+ */
+typedef enum
+{
+    DS1307_1HZ = 0, //!< 1 Hz
+    DS1307_4096HZ,  //!< 4096 Hz
+    DS1307_8192HZ,  //!< 8192 Hz
+    DS1307_32768HZ  //!< 32768 Hz
+} ds1307_squarewave_freq_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev Device descriptor
+ * @param port I2C port
+ * @param sda_gpio SDA GPIO
+ * @param scl_gpio SCL GPIO
+ * @return `ESP_OK` on success
+ */
+esp_err_t ds1307_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t ds1307_free_desc(i2c_dev_t *dev);
+
+/**
+ * @brief Start/stop clock
+ *
+ * @param dev Device descriptor
+ * @param start Start clock if true
+ * @return `ESP_OK` on success
+ */
+esp_err_t ds1307_start(i2c_dev_t *dev, bool start);
+
+/**
+ * @brief Get current clock state
+ *
+ * @param dev Device descriptor
+ * @param[out] running true if clock running
+ * @return `ESP_OK` on success
+ */
+esp_err_t ds1307_is_running(i2c_dev_t *dev, bool *running);
+
+/**
+ * @brief Get current time
+ *
+ * @param dev Device descriptor
+ * @param[out] time Pointer to the time struct to fill
+ * @return `ESP_OK` on success
+ */
+esp_err_t ds1307_get_time(i2c_dev_t *dev, struct tm *time);
+
+/**
+ * @brief Set time to RTC
+ *
+ * @param dev Device descriptor
+ * @param[in] time Pointer to the time struct
+ * @return `ESP_OK` on success
+ */
+esp_err_t ds1307_set_time(i2c_dev_t *dev, const struct tm *time);
+
+/**
+ * @brief Enable or disable square-wave oscillator output
+ *
+ * @param dev Device descriptor
+ * @param enable Enable oscillator if true
+ * @return `ESP_OK` on success
+ */
+esp_err_t ds1307_enable_squarewave(i2c_dev_t *dev, bool enable);
+
+/**
+ * @brief Get square-wave oscillator output
+ *
+ * @param dev Device descriptor
+ * @param[out] sqw_en true if square-wave oscillator enabled
+ * @return `ESP_OK` on success
+ */
+esp_err_t ds1307_is_squarewave_enabled(i2c_dev_t *dev, bool *sqw_en);
+
+/**
+ * @brief Set square-wave oscillator frequency
+ *
+ * @param dev Device descriptor
+ * @param freq Frequency
+ * @return `ESP_OK` on success
+ */
+esp_err_t ds1307_set_squarewave_freq(i2c_dev_t *dev, ds1307_squarewave_freq_t freq);
+
+/**
+ * @brief Get current square-wave oscillator frequency
+ *
+ * @param dev Device descriptor
+ * @param[out] sqw_freq Frequency
+ * @return `ESP_OK` on success
+ */
+esp_err_t ds1307_get_squarewave_freq(i2c_dev_t *dev, ds1307_squarewave_freq_t *sqw_freq);
+
+/**
+ * @brief Get current output level of the SQW/OUT pin
+ *
+ * @param dev Device descriptor
+ * @param[out] out current output level of the SQW/OUT pin, true if high
+ * @return `ESP_OK` on success
+ */
+esp_err_t ds1307_get_output(i2c_dev_t *dev, bool *out);
+
+/**
+ * @brief Set output level of the SQW/OUT pin
+ *
+ * Set output level if square-wave output is disabled
+ *
+ * @param dev Device descriptor
+ * @param value High level if true
+ * @return `ESP_OK` on success
+ */
+esp_err_t ds1307_set_output(i2c_dev_t *dev, bool value);
+
+/**
+ * @brief Read RAM contents into the buffer
+ *
+ * @param dev Device descriptor
+ * @param offset Start byte, 0..55
+ * @param[out] buf Buffer to store data
+ * @param len Bytes to read, 1..56
+ * @return `ESP_OK` on success
+ */
+esp_err_t ds1307_read_ram(i2c_dev_t *dev, uint8_t offset, uint8_t *buf, uint8_t len);
+
+/**
+ * @brief Write buffer to RTC RAM
+ *
+ * @param dev Device descriptor
+ * @param offset Start byte, 0..55
+ * @param buf Buffer
+ * @param len Bytes to write, 1..56
+ * @return `ESP_OK` on success
+ */
+esp_err_t ds1307_write_ram(i2c_dev_t *dev, uint8_t offset, uint8_t *buf, uint8_t len);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __DS1307_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ds18x20/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,29 @@
+---
+components:
+  - name: ds18x20
+    description: |
+      Driver for DS18B20/DS18S20 families of 1-Wire temperature sensor ICs
+    group: temperature
+    groups: []
+    code_owners:
+      - name: UncleRus
+    depends:
+      - name: log
+      - name: esp_idf_lib_helpers
+      - name: onewire
+      - name: freertos
+    thread_safe: no
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - name: UncleRus
+        year: 2018
+      - name: AlexS
+        year: 2016
+      - name: GrzegorzH
+        year: 2016
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ds18x20/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS ds18x20.c
+    INCLUDE_DIRS .
+    REQUIRES onewire freertos log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ds18x20/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,28 @@
+Copyright (c) 2016 Grzegorz Hetman <ghetman@gmail.com>
+Copyright (c) 2016 Alex Stewart <foogod@gmail.com>
+Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ds18x20/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = onewire freertos log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ds18x20/ds18x20.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,292 @@
+/*
+ * Copyright (c) 2016 Grzegorz Hetman <ghetman@gmail.com>
+ * Copyright (c) 2016 Alex Stewart <foogod@gmail.com>
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file ds18x20.c
+ *
+ * ESP-IDF driver for the DS18S20/DS18B20 one-wire temperature sensor ICs
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2016 Grzegorz Hetman <ghetman@gmail.com>\n
+ * Copyright (c) 2016 Alex Stewart <foogod@gmail.com>\n
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+
+#include <math.h>
+#include <esp_log.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <esp_idf_lib_helpers.h>
+#include "ds18x20.h"
+
+#define ds18x20_WRITE_SCRATCHPAD 0x4E
+#define ds18x20_READ_SCRATCHPAD  0xBE
+#define ds18x20_COPY_SCRATCHPAD  0x48
+#define ds18x20_READ_EEPROM      0xB8
+#define ds18x20_READ_PWRSUPPLY   0xB4
+#define ds18x20_SEARCHROM        0xF0
+#define ds18x20_SKIP_ROM         0xCC
+#define ds18x20_READROM          0x33
+#define ds18x20_MATCHROM         0x55
+#define ds18x20_ALARMSEARCH      0xEC
+#define ds18x20_CONVERT_T        0x44
+
+#define SLEEP_MS(x) vTaskDelay(((x) + portTICK_PERIOD_MS - 1) / portTICK_PERIOD_MS)
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+#if HELPER_TARGET_IS_ESP32
+static portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
+#define PORT_ENTER_CRITICAL portENTER_CRITICAL(&mux)
+#define PORT_EXIT_CRITICAL portEXIT_CRITICAL(&mux)
+
+#elif HELPER_TARGET_IS_ESP8266
+#define PORT_ENTER_CRITICAL portENTER_CRITICAL()
+#define PORT_EXIT_CRITICAL portEXIT_CRITICAL()
+#endif
+
+static const char *TAG = "ds18x20";
+
+esp_err_t ds18x20_measure(gpio_num_t pin, ds18x20_addr_t addr, bool wait)
+{
+    if (!onewire_reset(pin))
+        return ESP_ERR_INVALID_RESPONSE;
+
+    if (addr == DS18X20_ANY)
+        onewire_skip_rom(pin);
+    else
+        onewire_select(pin, addr);
+
+    PORT_ENTER_CRITICAL;
+    onewire_write(pin, ds18x20_CONVERT_T);
+    // For parasitic devices, power must be applied within 10us after issuing
+    // the convert command.
+    onewire_power(pin);
+    PORT_EXIT_CRITICAL;
+
+    if (wait)
+    {
+        SLEEP_MS(750);
+        onewire_depower(pin);
+    }
+
+    return ESP_OK;
+}
+
+esp_err_t ds18x20_read_scratchpad(gpio_num_t pin, ds18x20_addr_t addr, uint8_t *buffer)
+{
+    CHECK_ARG(buffer);
+
+    uint8_t crc;
+    uint8_t expected_crc;
+
+    if (!onewire_reset(pin))
+        return ESP_ERR_INVALID_RESPONSE;
+
+    if (addr == DS18X20_ANY)
+        onewire_skip_rom(pin);
+    else
+        onewire_select(pin, addr);
+    onewire_write(pin, ds18x20_READ_SCRATCHPAD);
+
+    for (int i = 0; i < 8; i++)
+        buffer[i] = onewire_read(pin);
+    crc = onewire_read(pin);
+
+    expected_crc = onewire_crc8(buffer, 8);
+    if (crc != expected_crc)
+    {
+        ESP_LOGE(TAG, "CRC check failed reading scratchpad: %02x %02x %02x %02x %02x %02x %02x %02x : %02x (expected %02x)", buffer[0], buffer[1],
+                buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7], crc, expected_crc);
+        return ESP_ERR_INVALID_CRC;
+    }
+
+    return ESP_OK;
+}
+
+esp_err_t ds18x20_write_scratchpad(gpio_num_t pin, ds18x20_addr_t addr, uint8_t *buffer)
+{
+    CHECK_ARG(buffer);
+
+    if (!onewire_reset(pin))
+        return ESP_ERR_INVALID_RESPONSE;
+
+    if (addr == DS18X20_ANY)
+        onewire_skip_rom(pin);
+    else
+        onewire_select(pin, addr);
+    onewire_write(pin, ds18x20_WRITE_SCRATCHPAD);
+
+    for (int i = 0; i < 3; i++)
+        onewire_write(pin, buffer[i]);
+
+    return ESP_OK;
+}
+
+esp_err_t ds18x20_copy_scratchpad(gpio_num_t pin, ds18x20_addr_t addr)
+{
+    if (!onewire_reset(pin))
+        return ESP_ERR_INVALID_RESPONSE;
+
+    if (addr == DS18X20_ANY)
+        onewire_skip_rom(pin);
+    else
+        onewire_select(pin, addr);
+
+    PORT_ENTER_CRITICAL;
+    onewire_write(pin, ds18x20_COPY_SCRATCHPAD);
+    // For parasitic devices, power must be applied within 10us after issuing
+    // the convert command.
+    onewire_power(pin);
+    PORT_EXIT_CRITICAL;
+
+    // And then it needs to keep that power up for 10ms.
+    SLEEP_MS(10);
+    onewire_depower(pin);
+
+    return ESP_OK;
+}
+
+esp_err_t ds18b20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, float *temperature)
+{
+    CHECK_ARG(temperature);
+
+    uint8_t scratchpad[8];
+    int16_t temp;
+
+    CHECK(ds18x20_read_scratchpad(pin, addr, scratchpad));
+
+    temp = scratchpad[1] << 8 | scratchpad[0];
+
+    *temperature = ((float)temp * 625.0) / 10000;
+
+    return ESP_OK;
+}
+
+esp_err_t ds18s20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, float *temperature)
+{
+    CHECK_ARG(temperature);
+
+    uint8_t scratchpad[8];
+    int16_t temp;
+
+    CHECK(ds18x20_read_scratchpad(pin, addr, scratchpad));
+
+    temp = scratchpad[1] << 8 | scratchpad[0];
+
+    temp = ((temp & 0xfffe) << 3) + (16 - scratchpad[6]) - 4;
+    *temperature = ((float)temp * 625.0) / 10000 - 0.25;
+
+    return ESP_OK;
+}
+
+esp_err_t ds18x20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, float *temperature)
+{
+    if ((uint8_t)addr == DS18B20_FAMILY_ID) {
+        return ds18b20_read_temperature(pin, addr, temperature);
+    }
+    else
+    {
+        return ds18s20_read_temperature(pin, addr, temperature);
+    }
+}
+
+esp_err_t ds18b20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, float *temperature)
+{
+    CHECK_ARG(temperature);
+
+    CHECK(ds18x20_measure(pin, addr, true));
+    return ds18b20_read_temperature(pin, addr, temperature);
+}
+
+esp_err_t ds18s20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, float *temperature)
+{
+    CHECK_ARG(temperature);
+
+    CHECK(ds18x20_measure(pin, addr, true));
+    return ds18s20_read_temperature(pin, addr, temperature);
+}
+
+esp_err_t ds18x20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, float *temperature)
+{
+    CHECK_ARG(temperature);
+
+    CHECK(ds18x20_measure(pin, addr, true));
+    return ds18x20_read_temperature(pin, addr, temperature);
+}
+
+esp_err_t ds18x20_measure_and_read_multi(gpio_num_t pin, ds18x20_addr_t *addr_list, size_t addr_count, float *result_list)
+{
+    CHECK_ARG(result_list && addr_count);
+
+    CHECK(ds18x20_measure(pin, DS18X20_ANY, true));
+
+    return ds18x20_read_temp_multi(pin, addr_list, addr_count, result_list);
+}
+
+esp_err_t ds18x20_scan_devices(gpio_num_t pin, ds18x20_addr_t *addr_list, size_t addr_count, size_t *found)
+{
+    CHECK_ARG(addr_list && addr_count);
+
+    onewire_search_t search;
+    onewire_addr_t addr;
+
+    *found = 0;
+    onewire_search_start(&search);
+    while ((addr = onewire_search_next(&search, pin)) != ONEWIRE_NONE)
+    {
+        uint8_t family_id = (uint8_t)addr;
+        if (family_id == DS18B20_FAMILY_ID || family_id == DS18S20_FAMILY_ID)
+        {
+            if (*found < addr_count)
+                addr_list[*found] = addr;
+            *found += 1;
+        }
+    }
+
+    return ESP_OK;
+}
+
+esp_err_t ds18x20_read_temp_multi(gpio_num_t pin, ds18x20_addr_t *addr_list, size_t addr_count, float *result_list)
+{
+    CHECK_ARG(result_list);
+
+    esp_err_t res = ESP_OK;
+    for (size_t i = 0; i < addr_count; i++)
+    {
+        esp_err_t tmp = ds18x20_read_temperature(pin, addr_list[i], &result_list[i]);
+        if (tmp != ESP_OK)
+            res = tmp;
+    }
+    return res;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ds18x20/ds18x20.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,279 @@
+/*
+ * Copyright (c) 2016 Grzegorz Hetman <ghetman@gmail.com>
+ * Copyright (c) 2016 Alex Stewart <foogod@gmail.com>
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file ds18x20.h
+ * @defgroup ds18x20 ds18x20
+ * @{
+ *
+ * ESP-IDF driver for the DS18S20/DS18B20 one-wire temperature sensor ICs
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2016 Grzegorz Hetman <ghetman@gmail.com>\n
+ * Copyright (c) 2016 Alex Stewart <foogod@gmail.com>\n
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __DS18X20_H__
+#define __DS18X20_H__
+
+#include <esp_err.h>
+#include <onewire.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef onewire_addr_t ds18x20_addr_t;
+
+/** An address value which can be used to indicate "any device on the bus" */
+#define DS18X20_ANY ONEWIRE_NONE
+
+/** Family ID (lower address byte) of DS18B20 sensors */
+#define DS18B20_FAMILY_ID 0x28
+
+/** Family ID (lower address byte) of DS18S20 sensors */
+#define DS18S20_FAMILY_ID 0x10
+
+/**
+ * @brief Find the addresses of all ds18x20 devices on the bus.
+ *
+ * Scans the bus for all devices and places their addresses in the supplied
+ * array. If there are more than `addr_count` devices on the bus, only the
+ * first `addr_count` are recorded.
+ *
+ * @param pin         The GPIO pin connected to the ds18x20 bus
+ * @param addr_list   A pointer to an array of ::ds18x20_addr_t values.
+ *                    This will be populated with the addresses of the found
+ *                    devices.
+ * @param addr_count  Number of slots in the `addr_list` array. At most this
+ *                    many addresses will be returned.
+ * @param found       The number of devices found. Note that this may be less
+ *                    than, equal to, or more than `addr_count`, depending on
+ *                    how many ds18x20 devices are attached to the bus.
+ *
+ * @returns `ESP_OK` if the command was successfully issued
+ */
+esp_err_t ds18x20_scan_devices(gpio_num_t pin, ds18x20_addr_t *addr_list, size_t addr_count, size_t *found);
+
+/**
+ * @brief Tell one or more sensors to perform a temperature measurement and
+ * conversion (CONVERT_T) operation.
+ *
+ * This operation can take up to 750ms to complete.
+ *
+ * If `wait=true`, this routine will automatically drive the pin high for the
+ * necessary 750ms after issuing the command to ensure parasitically-powered
+ * devices have enough power to perform the conversion operation (for
+ * non-parasitically-powered devices, this is not necessary but does not
+ * hurt). If `wait=false`, this routine will drive the pin high, but will
+ * then return immediately. It is up to the caller to wait the requisite time
+ * and then depower the bus using onewire_depower() or by issuing another
+ * command once conversion is done.
+ *
+ * @param pin   The GPIO pin connected to the ds18x20 device
+ * @param addr  The 64-bit address of the device on the bus. This can be set
+ *              to ::DS18X20_ANY to send the command to all devices on the bus
+ *              at the same time.
+ * @param wait  Whether to wait for the necessary 750ms for the ds18x20 to
+ *              finish performing the conversion before returning to the
+ *              caller (You will normally want to do this).
+ *
+ * @returns `ESP_OK` if the command was successfully issued
+ */
+esp_err_t ds18x20_measure(gpio_num_t pin, ds18x20_addr_t addr, bool wait);
+
+/**
+ * @brief Read the value from the last CONVERT_T operation.
+ *
+ * This should be called after ds18x20_measure() to fetch the result of the
+ * temperature measurement.
+ *
+ * @param pin         The GPIO pin connected to the ds18x20 device
+ * @param addr        The 64-bit address of the device to read. This can be set
+ *                    to ::DS18X20_ANY to read any device on the bus (but note
+ *                    that this will only work if there is exactly one device
+ *                    connected, or they will corrupt each others' transmissions)
+ * @param temperature The temperature in degrees Celsius
+ *
+ * @returns `ESP_OK` if the command was successfully issued
+ */
+esp_err_t ds18x20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, float *temperature);
+
+/**
+ * @brief Read the value from the last CONVERT_T operation (ds18b20 version).
+ *
+ * This should be called after ds18x20_measure() to fetch the result of the
+ * temperature measurement.
+ *
+ * @param pin         The GPIO pin connected to the ds18x20 device
+ * @param addr        The 64-bit address of the device to read. This can be set
+ *                    to ::DS18X20_ANY to read any device on the bus (but note
+ *                    that this will only work if there is exactly one device
+ *                    connected, or they will corrupt each others' transmissions)
+ * @param temperature The temperature in degrees Celsius
+ *
+ * @returns `ESP_OK` if the command was successfully issued
+ */
+esp_err_t ds18b20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, float *temperature);
+
+/**
+ * @brief Read the value from the last CONVERT_T operation (ds18s20 version).
+ *
+ * This should be called after ds18x20_measure() to fetch the result of the
+ * temperature measurement.
+ *
+ * @param pin         The GPIO pin connected to the ds18x20 device
+ * @param addr        The 64-bit address of the device to read. This can be set
+ *                    to ::DS18X20_ANY to read any device on the bus (but note
+ *                    that this will only work if there is exactly one device
+ *                    connected, or they will corrupt each others' transmissions)
+ * @param temperature The temperature in degrees Celsius
+ *
+ * @returns `ESP_OK` if the command was successfully issued
+ */
+esp_err_t ds18s20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, float *temperature);
+
+/**
+ * @brief Read the value from the last CONVERT_T operation for multiple devices.
+ *
+ * This should be called after ds18x20_measure() to fetch the result of the
+ * temperature measurement.
+ *
+ * @param pin         The GPIO pin connected to the ds18x20 bus
+ * @param addr_list   A list of addresses for devices to read.
+ * @param addr_count  The number of entries in `addr_list`.
+ * @param result_list An array of floats to hold the returned temperature
+ *                     values. It should have at least `addr_count` entries.
+ *
+ * @returns `ESP_OK` if all temperatures were fetched successfully
+ */
+esp_err_t ds18x20_read_temp_multi(gpio_num_t pin, ds18x20_addr_t *addr_list, size_t addr_count, float *result_list);
+
+/** Perform a ds18x20_measure() followed by ds18s20_read_temperature()
+ *
+ *  @param pin         The GPIO pin connected to the ds18s20 device
+ *  @param addr        The 64-bit address of the device to read. This can be set
+ *                     to ::DS18X20_ANY to read any device on the bus (but note
+ *                     that this will only work if there is exactly one device
+ *                     connected, or they will corrupt each others' transmissions)
+ *  @param temperature The temperature in degrees Celsius
+ */
+esp_err_t ds18s20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, float *temperature);
+
+/** Perform a ds18x20_measure() followed by ds18b20_read_temperature()
+ *
+ *  @param pin         The GPIO pin connected to the ds18x20 device
+ *  @param addr        The 64-bit address of the device to read. This can be set
+ *                     to ::DS18X20_ANY to read any device on the bus (but note
+ *                     that this will only work if there is exactly one device
+ *                     connected, or they will corrupt each others' transmissions)
+ *  @param temperature The temperature in degrees Celsius
+ */
+esp_err_t ds18b20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, float *temperature);
+
+/** Perform a ds18x20_measure() followed by ds18x20_read_temperature()
+ *
+ *  @param pin         The GPIO pin connected to the ds18x20 device
+ *  @param addr        The 64-bit address of the device to read. This can be set
+ *                     to ::DS18X20_ANY to read any device on the bus (but note
+ *                     that this will only work if there is exactly one device
+ *                     connected, or they will corrupt each others' transmissions)
+ *  @param temperature The temperature in degrees Celsius
+ */
+esp_err_t ds18x20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, float *temperature);
+
+/**
+ * @brief Perform a ds18x20_measure() followed by ds18x20_read_temp_multi()
+ *
+ * @param pin         The GPIO pin connected to the ds18x20 bus
+ * @param addr_list   A list of addresses for devices to read.
+ * @param addr_count  The number of entries in `addr_list`.
+ * @param result_list An array of floats to hold the returned temperature
+ *                    values. It should have at least `addr_count` entries.
+ *
+ * @returns `ESP_OK` if all temperatures were fetched successfully
+ */
+esp_err_t ds18x20_measure_and_read_multi(gpio_num_t pin, ds18x20_addr_t *addr_list, size_t addr_count, float *result_list);
+
+/**
+ * @brief Read the scratchpad data for a particular ds18x20 device.
+ *
+ * This is not generally necessary to do directly. It is done automatically
+ * as part of ds18x20_read_temperature().
+ *
+ * @param pin     The GPIO pin connected to the ds18x20 device
+ * @param addr    The 64-bit address of the device to read. This can be set
+ *                to ::DS18X20_ANY to read any device on the bus (but note
+ *                that this will only work if there is exactly one device
+ *                connected, or they will corrupt each others' transmissions)
+ * @param buffer  An 8-byte buffer to hold the read data.
+ *
+ * @returns `ESP_OK` if the command was successfully issued
+ */
+esp_err_t ds18x20_read_scratchpad(gpio_num_t pin, ds18x20_addr_t addr, uint8_t *buffer);
+
+/**
+ * @brief Write the scratchpad data for a particular ds18x20 device.
+ *
+ * @param pin     The GPIO pin connected to the ds18x20 device
+ * @param addr    The 64-bit address of the device to write. This can be set
+ *                to ::DS18X20_ANY to read any device on the bus (but note
+ *                that this will only work if there is exactly one device
+ *                connected, or they will corrupt each others' transmissions)
+ * @param buffer  An 3-byte buffer to hold the data to write
+ *
+ * @returns `ESP_OK` if the command was successfully issued
+ */
+esp_err_t ds18x20_write_scratchpad(gpio_num_t pin, ds18x20_addr_t addr, uint8_t *buffer);
+
+/**
+ * @brief Issue the copy scratchpad command, copying current scratchpad to
+ *        EEPROM.
+ *
+ * @param pin     The GPIO pin connected to the ds18x20 device
+ * @param addr    The 64-bit address of the device to command. This can be set
+ *                to ::DS18X20_ANY to read any device on the bus (but note
+ *                that this will only work if there is exactly one device
+ *                connected, or they will corrupt each others' transmissions)
+ *
+ * @returns `ESP_OK` if the command was successfully issued
+ */
+esp_err_t ds18x20_copy_scratchpad(gpio_num_t pin, ds18x20_addr_t addr);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif  /* __DS18X20_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ds3231/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,28 @@
+---
+components:
+  - name: ds3231
+    description: |
+      Driver for DS1337 RTC and DS3231 high precision RTC module
+    group: rtc
+    groups: []
+    code_owners:
+      - name: UncleRus
+    depends:
+      - name: i2cdev
+      - name: log
+      - name: esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: MIT
+    copyrights:
+      - name: UncleRus
+        year: 2018
+      - name: BhuvanchandraD
+        year: 2016
+      - name: RichardA
+        year: 2015
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ds3231/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS ds3231.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ds3231/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,23 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Richard A Burton <richardaburton@gmail.com>
+Copyright (c) 2016 Bhuvanchandra DV <bhuvanchandra.dv@gmail.com>
+Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ds3231/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ds3231/ds3231.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,479 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 Richard A Burton <richardaburton@gmail.com>
+ * Copyright (c) 2016 Bhuvanchandra DV <bhuvanchandra.dv@gmail.com>
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file ds3231.c
+ *
+ * ESP-IDF driver for DS337 RTC and DS3231 high precision RTC module
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2015 Richard A Burton <richardaburton@gmail.com>\n
+ * Copyright (c) 2016 Bhuvanchandra DV <bhuvanchandra.dv@gmail.com>\n
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+#include <esp_err.h>
+#include <esp_idf_lib_helpers.h>
+#include "ds3231.h"
+
+#define I2C_FREQ_HZ 400000
+
+#define DS3231_STAT_OSCILLATOR 0x80
+#define DS3231_STAT_32KHZ      0x08
+#define DS3231_STAT_ALARM_2    0x02
+#define DS3231_STAT_ALARM_1    0x01
+
+#define DS3231_CTRL_OSCILLATOR    0x80
+#define DS3231_CTRL_TEMPCONV      0x20
+#define DS3231_CTRL_ALARM_INTS    0x04
+#define DS3231_CTRL_ALARM2_INT    0x02
+#define DS3231_CTRL_ALARM1_INT    0x01
+
+#define DS3231_ALARM_WDAY   0x40
+#define DS3231_ALARM_NOTSET 0x80
+
+#define DS3231_ADDR_TIME    0x00
+#define DS3231_ADDR_ALARM1  0x07
+#define DS3231_ADDR_ALARM2  0x0b
+#define DS3231_ADDR_CONTROL 0x0e
+#define DS3231_ADDR_STATUS  0x0f
+#define DS3231_ADDR_AGING   0x10
+#define DS3231_ADDR_TEMP    0x11
+
+#define DS3231_12HOUR_FLAG  0x40
+#define DS3231_12HOUR_MASK  0x1f
+#define DS3231_PM_FLAG      0x20
+#define DS3231_MONTH_MASK   0x1f
+
+#define CHECK_ARG(ARG) do { if (!(ARG)) return ESP_ERR_INVALID_ARG; } while (0)
+
+enum {
+    DS3231_SET = 0,
+    DS3231_CLEAR,
+    DS3231_REPLACE
+};
+
+static uint8_t bcd2dec(uint8_t val)
+{
+    return (val >> 4) * 10 + (val & 0x0f);
+}
+
+static uint8_t dec2bcd(uint8_t val)
+{
+    return ((val / 10) << 4) + (val % 10);
+}
+
+esp_err_t ds3231_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+
+    dev->port = port;
+    dev->addr = DS3231_ADDR;
+    dev->cfg.sda_io_num = sda_gpio;
+    dev->cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+    return i2c_dev_create_mutex(dev);
+}
+
+esp_err_t ds3231_free_desc(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(dev);
+}
+
+esp_err_t ds3231_set_time(i2c_dev_t *dev, struct tm *time)
+{
+    CHECK_ARG(dev && time);
+
+    uint8_t data[7];
+
+    /* time/date data */
+    data[0] = dec2bcd(time->tm_sec);
+    data[1] = dec2bcd(time->tm_min);
+    data[2] = dec2bcd(time->tm_hour);
+    /* The week data must be in the range 1 to 7, and to keep the start on the
+     * same day as for tm_wday have it start at 1 on Sunday. */
+    data[3] = dec2bcd(time->tm_wday + 1);
+    data[4] = dec2bcd(time->tm_mday);
+    data[5] = dec2bcd(time->tm_mon + 1);
+    data[6] = dec2bcd(time->tm_year - 100);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, DS3231_ADDR_TIME, data, 7));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t ds3231_set_alarm(i2c_dev_t *dev, ds3231_alarm_t alarms, struct tm *time1,
+        ds3231_alarm1_rate_t option1, struct tm *time2, ds3231_alarm2_rate_t option2)
+{
+    CHECK_ARG(dev);
+
+    int i = 0;
+    uint8_t data[7];
+
+    /* alarm 1 data */
+    if (alarms != DS3231_ALARM_2)
+    {
+        CHECK_ARG(time1);
+        data[i++] = (option1 >= DS3231_ALARM1_MATCH_SEC ? dec2bcd(time1->tm_sec) : DS3231_ALARM_NOTSET);
+        data[i++] = (option1 >= DS3231_ALARM1_MATCH_SECMIN ? dec2bcd(time1->tm_min) : DS3231_ALARM_NOTSET);
+        data[i++] = (option1 >= DS3231_ALARM1_MATCH_SECMINHOUR ? dec2bcd(time1->tm_hour) : DS3231_ALARM_NOTSET);
+        data[i++] = (option1 == DS3231_ALARM1_MATCH_SECMINHOURDAY ? (dec2bcd(time1->tm_wday + 1) & DS3231_ALARM_WDAY) :
+            (option1 == DS3231_ALARM1_MATCH_SECMINHOURDATE ? dec2bcd(time1->tm_mday) : DS3231_ALARM_NOTSET));
+    }
+
+    /* alarm 2 data */
+    if (alarms != DS3231_ALARM_1)
+    {
+        CHECK_ARG(time2);
+        data[i++] = (option2 >= DS3231_ALARM2_MATCH_MIN ? dec2bcd(time2->tm_min) : DS3231_ALARM_NOTSET);
+        data[i++] = (option2 >= DS3231_ALARM2_MATCH_MINHOUR ? dec2bcd(time2->tm_hour) : DS3231_ALARM_NOTSET);
+        data[i++] = (option2 == DS3231_ALARM2_MATCH_MINHOURDAY ? (dec2bcd(time2->tm_wday + 1) & DS3231_ALARM_WDAY) :
+            (option2 == DS3231_ALARM2_MATCH_MINHOURDATE ? dec2bcd(time2->tm_mday) : DS3231_ALARM_NOTSET));
+    }
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, (alarms == DS3231_ALARM_2 ? DS3231_ADDR_ALARM2 : DS3231_ADDR_ALARM1), data, i));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+/* Get a byte containing just the requested bits
+ * pass the register address to read, a mask to apply to the register and
+ * an uint* for the output
+ * you can test this value directly as true/false for specific bit mask
+ * of use a mask of 0xff to just return the whole register byte
+ * returns true to indicate success
+ */
+static esp_err_t ds3231_get_flag(i2c_dev_t *dev, uint8_t addr, uint8_t mask, uint8_t *flag)
+{
+    uint8_t data;
+
+    /* get register */
+    esp_err_t res = i2c_dev_read_reg(dev, addr, &data, 1);
+    if (res != ESP_OK)
+        return res;
+
+    /* return only requested flag */
+    *flag = (data & mask);
+    return ESP_OK;
+}
+
+/* Set/clear bits in a byte register, or replace the byte altogether
+ * pass the register address to modify, a byte to replace the existing
+ * value with or containing the bits to set/clear and one of
+ * DS3231_SET/DS3231_CLEAR/DS3231_REPLACE
+ * returns true to indicate success
+ */
+static esp_err_t ds3231_set_flag(i2c_dev_t *dev, uint8_t addr, uint8_t bits, uint8_t mode)
+{
+    uint8_t data;
+
+    /* get status register */
+    esp_err_t res = i2c_dev_read_reg(dev, addr, &data, 1);
+    if (res != ESP_OK)
+        return res;
+    /* clear the flag */
+    if (mode == DS3231_REPLACE)
+        data = bits;
+    else if (mode == DS3231_SET)
+        data |= bits;
+    else
+        data &= ~bits;
+
+    return i2c_dev_write_reg(dev, addr, &data, 1);
+}
+
+esp_err_t ds3231_get_oscillator_stop_flag(i2c_dev_t *dev, bool *flag)
+{
+    CHECK_ARG(dev && flag);
+
+    uint8_t f;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, ds3231_get_flag(dev, DS3231_ADDR_STATUS, DS3231_STAT_OSCILLATOR, &f));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    *flag = (f ? true : false);
+
+    return ESP_OK;
+}
+
+esp_err_t ds3231_clear_oscillator_stop_flag(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, ds3231_set_flag(dev, DS3231_ADDR_STATUS, DS3231_STAT_OSCILLATOR, DS3231_CLEAR));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t ds3231_get_alarm_flags(i2c_dev_t *dev, ds3231_alarm_t *alarms)
+{
+    CHECK_ARG(dev && alarms);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, ds3231_get_flag(dev, DS3231_ADDR_STATUS, DS3231_ALARM_BOTH, (uint8_t *)alarms));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t ds3231_clear_alarm_flags(i2c_dev_t *dev, ds3231_alarm_t alarms)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, ds3231_set_flag(dev, DS3231_ADDR_STATUS, alarms, DS3231_CLEAR));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t ds3231_enable_alarm_ints(i2c_dev_t *dev, ds3231_alarm_t alarms)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, ds3231_set_flag(dev, DS3231_ADDR_CONTROL, DS3231_CTRL_ALARM_INTS | alarms, DS3231_SET));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t ds3231_disable_alarm_ints(i2c_dev_t *dev, ds3231_alarm_t alarms)
+{
+    CHECK_ARG(dev);
+
+    /* Just disable specific alarm(s) requested
+     * does not disable alarm interrupts generally (which would enable the squarewave)
+     */
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, ds3231_set_flag(dev, DS3231_ADDR_CONTROL, alarms, DS3231_CLEAR));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t ds3231_enable_32khz(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, ds3231_set_flag(dev, DS3231_ADDR_STATUS, DS3231_STAT_32KHZ, DS3231_SET));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t ds3231_disable_32khz(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, ds3231_set_flag(dev, DS3231_ADDR_STATUS, DS3231_STAT_32KHZ, DS3231_CLEAR));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t ds3231_enable_squarewave(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, ds3231_set_flag(dev, DS3231_ADDR_CONTROL, DS3231_CTRL_ALARM_INTS, DS3231_CLEAR));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t ds3231_disable_squarewave(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, ds3231_set_flag(dev, DS3231_ADDR_CONTROL, DS3231_CTRL_ALARM_INTS, DS3231_SET));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t ds3231_set_squarewave_freq(i2c_dev_t *dev, ds3231_sqwave_freq_t freq)
+{
+    CHECK_ARG(dev);
+
+    uint8_t flag = 0;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, ds3231_get_flag(dev, DS3231_ADDR_CONTROL, 0xff, &flag));
+    flag &= ~DS3231_SQWAVE_8192HZ;
+    flag |= freq;
+    I2C_DEV_CHECK(dev, ds3231_set_flag(dev, DS3231_ADDR_CONTROL, flag, DS3231_REPLACE));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+
+esp_err_t ds3231_get_squarewave_freq(i2c_dev_t *dev, ds3231_sqwave_freq_t* freq)
+{
+    CHECK_ARG(dev);
+
+    uint8_t flag = 0;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, ds3231_get_flag(dev, DS3231_ADDR_CONTROL, 0xff, &flag));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    flag &= DS3231_SQWAVE_8192HZ;
+    *freq = (ds3231_sqwave_freq_t) flag;
+
+    return ESP_OK;
+}
+
+
+esp_err_t ds3231_get_raw_temp(i2c_dev_t *dev, int16_t *temp)
+{
+    CHECK_ARG(dev && temp);
+
+    uint8_t data[2];
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, DS3231_ADDR_TEMP, data, sizeof(data)));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    *temp = (int16_t)(int8_t)data[0] << 2 | data[1] >> 6;
+
+    return ESP_OK;
+}
+
+esp_err_t ds3231_get_temp_integer(i2c_dev_t *dev, int8_t *temp)
+{
+    CHECK_ARG(temp);
+
+    int16_t t_int;
+
+    esp_err_t res = ds3231_get_raw_temp(dev, &t_int);
+    if (res == ESP_OK)
+        *temp = t_int >> 2;
+
+    return res;
+}
+
+esp_err_t ds3231_get_temp_float(i2c_dev_t *dev, float *temp)
+{
+    CHECK_ARG(temp);
+
+    int16_t t_int;
+
+    esp_err_t res = ds3231_get_raw_temp(dev, &t_int);
+    if (res == ESP_OK)
+        *temp = t_int * 0.25;
+
+    return res;
+}
+
+esp_err_t ds3231_get_time(i2c_dev_t *dev, struct tm *time)
+{
+    CHECK_ARG(dev && time);
+
+    uint8_t data[7];
+
+    /* read time */
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, DS3231_ADDR_TIME, data, 7));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    /* convert to unix time structure */
+    time->tm_sec = bcd2dec(data[0]);
+    time->tm_min = bcd2dec(data[1]);
+    if (data[2] & DS3231_12HOUR_FLAG)
+    {
+        /* 12H */
+        time->tm_hour = bcd2dec(data[2] & DS3231_12HOUR_MASK) - 1;
+        /* AM/PM? */
+        if (data[2] & DS3231_PM_FLAG) time->tm_hour += 12;
+    }
+    else time->tm_hour = bcd2dec(data[2]); /* 24H */
+    time->tm_wday = bcd2dec(data[3]) - 1;
+    time->tm_mday = bcd2dec(data[4]);
+    time->tm_mon  = bcd2dec(data[5] & DS3231_MONTH_MASK) - 1;
+    time->tm_year = bcd2dec(data[6]) + 100;
+    time->tm_isdst = 0;
+
+    // apply a time zone (if you are not using localtime on the rtc or you want to check/apply DST)
+    //applyTZ(time);
+
+    return ESP_OK;
+}
+
+
+esp_err_t ds3231_set_aging_offset(i2c_dev_t *dev, int8_t age)
+{
+    CHECK_ARG(dev);
+
+    uint8_t age_u8 = (uint8_t) age;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, DS3231_ADDR_AGING, &age_u8, sizeof(uint8_t)));
+
+    /**
+     * To see the effects of the aging register on the 32kHz output
+     * frequency immediately, a manual conversion should be started
+     * after each aging register change.
+     */
+    I2C_DEV_CHECK(dev, ds3231_set_flag(dev, DS3231_ADDR_CONTROL, DS3231_CTRL_TEMPCONV, DS3231_SET));
+
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+
+esp_err_t ds3231_get_aging_offset(i2c_dev_t *dev, int8_t *age)
+{
+    CHECK_ARG(dev && age);
+
+    uint8_t age_u8;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, DS3231_ADDR_AGING, &age_u8, sizeof(uint8_t)));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    *age = (int8_t) age_u8;
+
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ds3231/ds3231.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,356 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 Richard A Burton <richardaburton@gmail.com>
+ * Copyright (c) 2016 Bhuvanchandra DV <bhuvanchandra.dv@gmail.com>
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file ds3231.h
+ * @defgroup ds3231 ds3231
+ * @{
+ *
+ * ESP-IDF driver for DS337 RTC and DS3231 high precision RTC module
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2015 Richard A Burton <richardaburton@gmail.com>\n
+ * Copyright (c) 2016 Bhuvanchandra DV <bhuvanchandra.dv@gmail.com>\n
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+#ifndef __DS3231_H__
+#define __DS3231_H__
+
+#include <time.h>
+#include <stdbool.h>
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+#define DS3231_ADDR 0x68 //!< I2C address
+
+/**
+ * Alarms
+ */
+typedef enum {
+    DS3231_ALARM_NONE = 0,//!< No alarms
+    DS3231_ALARM_1,       //!< First alarm
+    DS3231_ALARM_2,       //!< Second alarm
+    DS3231_ALARM_BOTH     //!< Both alarms
+} ds3231_alarm_t;
+
+/**
+ * First alarm rate
+ */
+typedef enum {
+    DS3231_ALARM1_EVERY_SECOND = 0,
+    DS3231_ALARM1_MATCH_SEC,
+    DS3231_ALARM1_MATCH_SECMIN,
+    DS3231_ALARM1_MATCH_SECMINHOUR,
+    DS3231_ALARM1_MATCH_SECMINHOURDAY,
+    DS3231_ALARM1_MATCH_SECMINHOURDATE
+} ds3231_alarm1_rate_t;
+
+/**
+ * Second alarm rate
+ */
+typedef enum {
+    DS3231_ALARM2_EVERY_MIN = 0,
+    DS3231_ALARM2_MATCH_MIN,
+    DS3231_ALARM2_MATCH_MINHOUR,
+    DS3231_ALARM2_MATCH_MINHOURDAY,
+    DS3231_ALARM2_MATCH_MINHOURDATE
+} ds3231_alarm2_rate_t;
+
+/**
+ * Squarewave frequency
+ */
+typedef enum {
+    DS3231_SQWAVE_1HZ    = 0x00,
+    DS3231_SQWAVE_1024HZ = 0x08,
+    DS3231_SQWAVE_4096HZ = 0x10,
+    DS3231_SQWAVE_8192HZ = 0x18
+} ds3231_sqwave_freq_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev I2C device descriptor
+ * @param port I2C port
+ * @param sda_gpio SDA GPIO
+ * @param scl_gpio SCL GPIO
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ds3231_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev I2C device descriptor
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ds3231_free_desc(i2c_dev_t *dev);
+
+/**
+ * @brief Set the time on the RTC
+ *
+ * Timezone agnostic, pass whatever you like.
+ * I suggest using GMT and applying timezone and DST when read back.
+ *
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ds3231_set_time(i2c_dev_t *dev, struct tm *time);
+
+/**
+ * @brief Get the time from the RTC, populates a supplied tm struct
+ *
+ * @param dev Device descriptor
+ * @param[out] time RTC time
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ds3231_get_time(i2c_dev_t *dev, struct tm *time);
+
+/**
+ * @brief Set alarms
+ *
+ * `alarm1` works with seconds, minutes, hours and day of week/month, or fires every second.
+ * `alarm2` works with minutes, hours and day of week/month, or fires every minute.
+ *
+ * Not all combinations are supported, see `DS3231_ALARM1_*` and `DS3231_ALARM2_*` defines
+ * for valid options you only need to populate the fields you are using in the `tm` struct,
+ * and you can set both alarms at the same time (pass `DS3231_ALARM_1`/`DS3231_ALARM_2`/`DS3231_ALARM_BOTH`).
+ *
+ * If only setting one alarm just pass 0 for `tm` struct and `option` field for the other alarm.
+ * If using ::DS3231_ALARM1_EVERY_SECOND/::DS3231_ALARM2_EVERY_MIN you can pass 0 for `tm` struct.
+ *
+ * If you want to enable interrupts for the alarms you need to do that separately.
+ *
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ds3231_set_alarm(i2c_dev_t *dev, ds3231_alarm_t alarms, struct tm *time1,
+        ds3231_alarm1_rate_t option1, struct tm *time2, ds3231_alarm2_rate_t option2);
+
+/**
+ * @brief Check if oscillator has previously stopped
+ *
+ * E.g. no power/battery or disabled
+ * sets flag to true if there has been a stop
+ *
+ * @param dev Device descriptor
+ * @param[out] flag Stop flag
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ds3231_get_oscillator_stop_flag(i2c_dev_t *dev, bool *flag);
+
+/**
+ * @brief Clear the oscillator stopped flag
+ *
+ * @param dev Device descriptor
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ds3231_clear_oscillator_stop_flag(i2c_dev_t *dev);
+
+/**
+ * @brief Check which alarm(s) have past
+ *
+ * Sets alarms to `DS3231_ALARM_NONE`/`DS3231_ALARM_1`/`DS3231_ALARM_2`/`DS3231_ALARM_BOTH`
+ *
+ * @param dev Device descriptor
+ * @param[out] alarms Alarms
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ds3231_get_alarm_flags(i2c_dev_t *dev, ds3231_alarm_t *alarms);
+
+/**
+ * @brief Clear alarm past flag(s)
+ *
+ * Pass `DS3231_ALARM_1`/`DS3231_ALARM_2`/`DS3231_ALARM_BOTH`
+ *
+ * @param dev Device descriptor
+ * @param alarms Alarms
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ds3231_clear_alarm_flags(i2c_dev_t *dev, ds3231_alarm_t alarms);
+
+/**
+ * @brief enable alarm interrupts (and disables squarewave)
+ *
+ * Pass `DS3231_ALARM_1`/`DS3231_ALARM_2`/`DS3231_ALARM_BOTH`.
+ *
+ * If you set only one alarm the status of the other is not changed
+ * you must also clear any alarm past flag(s) for alarms with
+ * interrupt enabled, else it will trigger immediately.
+ *
+ * @param dev Device descriptor
+ * @param alarms Alarms
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ds3231_enable_alarm_ints(i2c_dev_t *dev, ds3231_alarm_t alarms);
+
+/**
+ * @brief Disable alarm interrupts
+ *
+ * Does not (re-)enable squarewave
+ *
+ * @param dev Device descriptor
+ * @param alarms Alarm
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ds3231_disable_alarm_ints(i2c_dev_t *dev, ds3231_alarm_t alarms);
+
+/**
+ * @brief Enable the output of 32khz signal
+ *
+ * **Supported only by DS3231**
+ *
+ * @param dev Device descriptor
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ds3231_enable_32khz(i2c_dev_t *dev);
+
+/**
+ * @brief Disable the output of 32khz signal
+ *
+ * **Supported only by DS3231**
+ *
+ * @param dev Device descriptor
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ds3231_disable_32khz(i2c_dev_t *dev);
+
+/**
+ * @brief Enable the squarewave output
+ *
+ * Disables alarm interrupt functionality.
+ *
+ * @param dev Device descriptor
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ds3231_enable_squarewave(i2c_dev_t *dev);
+
+/**
+ * @brief Disable the squarewave output
+ *
+ * Which re-enables alarm interrupts, but individual alarm interrupts also
+ * need to be enabled, if not already, before they will trigger.
+ *
+ * @param dev Device descriptor
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ds3231_disable_squarewave(i2c_dev_t *dev);
+
+/**
+ * @brief Set the frequency of the squarewave output
+ *
+ * Does not enable squarewave output.
+ *
+ * @param dev Device descriptor
+ * @param freq Squarewave frequency
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ds3231_set_squarewave_freq(i2c_dev_t *dev, ds3231_sqwave_freq_t freq);
+
+/**
+ * @brief Get the frequency of the squarewave output
+ *
+ * Does not enable squarewave output.
+ *
+ * @param dev Device descriptor
+ * @param freq Squarewave frequency to store the output
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ds3231_get_squarewave_freq(i2c_dev_t *dev, ds3231_sqwave_freq_t* freq);
+
+/**
+ * @brief Get the raw temperature value
+ *
+ * **Supported only by DS3231**
+ *
+ * @param dev Device descriptor
+ * @param[out] temp Raw temperature value
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ds3231_get_raw_temp(i2c_dev_t *dev, int16_t *temp);
+
+/**
+ * @brief Get the temperature as an integer
+ *
+ * **Supported only by DS3231**
+ *
+ * @param dev Device descriptor
+ * @param[out] temp Temperature, degrees Celsius
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ds3231_get_temp_integer(i2c_dev_t *dev, int8_t *temp);
+
+/**
+ * @brief Get the temperature as a float
+ *
+ * **Supported only by DS3231**
+ *
+ * @param dev Device descriptor
+ * @param[out] temp Temperature, degrees Celsius
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ds3231_get_temp_float(i2c_dev_t *dev, float *temp);
+
+
+/**
+ * @brief Set the aging offset register to a new value.
+ *
+ * Positive aging values add capacitance to the array,
+ * slowing the oscillator frequency. Negative values remove
+ * capacitance from the array, increasing the oscillator
+ * frequency.
+ *
+ * **Supported only by DS3231**
+ *
+ * @param dev Device descriptor
+ * @param age Aging offset (in range [-128, 127]) to be set
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ds3231_set_aging_offset(i2c_dev_t *dev, int8_t age);
+
+
+/**
+ * @brief Get the aging offset register.
+ *
+ * **Supported only by DS3231**
+ *
+ * @param dev Device descriptor
+ * @param[out] age Aging offset in range [-128, 127]
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ds3231_get_aging_offset(i2c_dev_t *dev, int8_t *age);
+
+
+#ifdef	__cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif  /* __DS3231_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ds3502/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,23 @@
+---
+components:
+  - name: ds3502
+    description: |
+      Driver for nonvolatile digital potentiometer DS3502
+    group: misc
+    groups: []
+    code_owners:
+      - name: UncleRus
+    depends:
+      - name: i2cdev
+      - name: esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - name: UncleRus
+        year: 2021
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ds3502/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS ds3502.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ds3502/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Ruslan V. Uss
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ds3502/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ds3502/ds3502.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,120 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file ds3502.c
+ *
+ * ESP-IDF driver for nonvolatile digital potentiometer DS3502
+ *
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+
+#include <esp_idf_lib_helpers.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include "ds3502.h"
+
+#define I2C_FREQ_HZ 400000 // 400kHz
+
+#define REG_WR_IVR 0x00
+#define REG_CR     0x02
+
+#define MODE_WR_IVR 0x00
+#define MODE_WR     0x80
+
+#define IVR_SET_DELAY 100
+
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+esp_err_t ds3502_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev && addr >= DS3502_ADDR_0 && addr <= DS3502_ADDR_3);
+
+    dev->port = port;
+    dev->addr = addr;
+    dev->cfg.sda_io_num = sda_gpio;
+    dev->cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(dev);
+}
+
+esp_err_t ds3502_free_desc(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(dev);
+}
+
+esp_err_t ds3502_init(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    // MODE1 by default (only write to SRAM)
+    uint8_t mode = MODE_WR;
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, REG_CR, &mode, 1));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t ds3502_get(i2c_dev_t *dev, uint8_t *pos)
+{
+    CHECK_ARG(dev && pos);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, REG_WR_IVR, pos, 1));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t ds3502_set(i2c_dev_t *dev, uint8_t pos, bool save)
+{
+    CHECK_ARG(dev && pos >= DS3502_MAX);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    if (save)
+    {
+        // Set mode to MODE0 (write both SRAM and NVS)
+        uint8_t mode = MODE_WR_IVR;
+        I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, REG_CR, &mode, 1));
+    }
+    I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, REG_WR_IVR, &pos, 1));
+    if (save)
+    {
+        // NVS writing delay
+        vTaskDelay(pdMS_TO_TICKS(IVR_SET_DELAY));
+        // Set device back to MODE0
+        uint8_t mode = MODE_WR;
+        I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, REG_CR, &mode, 1));
+    }
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ds3502/ds3502.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,109 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file ds3502.h
+ * @defgroup ds3502 ds3502
+ * @{
+ *
+ * ESP-IDF driver for nonvolatile digital potentiometer DS3502
+ *
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+#ifndef __DS3502_H__
+#define __DS3502_H__
+
+#include <stdbool.h>
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define DS3502_ADDR_0 0x28
+#define DS3502_ADDR_1 0x29
+#define DS3502_ADDR_2 0x2a
+#define DS3502_ADDR_3 0x2b
+
+/**
+ * Maximal wiper position value
+ */
+#define DS3502_MAX 0x7f
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev Device descriptor
+ * @param addr Device address, `DS3502_ADDR_...`
+ * @param port I2C port number
+ * @param sda_gpio GPIO pin number for SDA
+ * @param scl_gpio GPIO pin number for SCL
+ * @return `ESP_OK` on success
+ */
+esp_err_t ds3502_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t ds3502_free_desc(i2c_dev_t *dev);
+
+/**
+ * @brief Initialize device
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t ds3502_init(i2c_dev_t *dev);
+
+/**
+ * @brief Get wiper position
+ *
+ * @param dev Device descriptor
+ * @param[out] pos Position, `0..DS3502_MAX_WIPER`
+ * @return `ESP_OK` on success
+ */
+esp_err_t ds3502_get(i2c_dev_t *dev, uint8_t *pos);
+
+/**
+ * @brief Set wiper position
+ *
+ * @param dev Device descriptor
+ * @param pos Wiper position, `0..DS3502_MAX_WIPER`
+ * @param save Save position to nonvolatile memory
+ * @return `ESP_OK` on success
+ */
+esp_err_t ds3502_set(i2c_dev_t *dev, uint8_t pos, bool save);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __DS3502_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/encoder/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,24 @@
+---
+components:
+  - name: encoder
+    description: |
+      HW timer-based driver for incremental rotary encoders
+    group: input
+    groups: []
+    code_owners:
+      - name: UncleRus
+    depends:
+      - name: driver
+      - name: freertos
+      - name: log
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - name: UncleRus
+        year: 2019
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/encoder/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,13 @@
+if(${IDF_TARGET} STREQUAL esp8266)
+    set(req esp8266 freertos log esp_timer)
+elseif(${IDF_VERSION_MAJOR} STREQUAL 4 AND ${IDF_VERSION_MINOR} STREQUAL 1 AND ${IDF_VERSION_PATCH} STREQUAL 3)
+    set(req driver freertos log)
+else()
+    set(req driver freertos log esp_timer)
+endif()
+
+idf_component_register(
+    SRCS encoder.c
+    INCLUDE_DIRS .
+    REQUIRES ${req}
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/encoder/Kconfig	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,27 @@
+menu "Rotary encoders"
+
+	config RE_MAX
+		int "Maximum number of rotary encoders"
+		default 1
+			
+	config RE_INTERVAL_US
+		int "Polling interval, us"
+		default 1000
+		
+	config RE_BTN_DEAD_TIME_US
+		int "Button dead time, us"
+		default 10000
+			
+	choice RE_BTN_PRESSED_LEVEL
+		prompt "Logical level on pressed button"
+		config RE_BTN_PRESSED_LEVEL_0
+			bool "0"
+		config RE_BTN_PRESSED_LEVEL_1
+			bool "1"
+	endchoice
+	
+	config RE_BTN_LONG_PRESS_TIME_US
+		int "Long press timeout, us"
+		default 500000
+
+endmenu
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/encoder/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+Copyright 2019 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/encoder/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,7 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+
+ifdef CONFIG_IDF_TARGET_ESP8266
+COMPONENT_DEPENDS = esp8266 freertos log
+else
+COMPONENT_DEPENDS = driver freertos log
+endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/encoder/encoder.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,244 @@
+/*
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file encoder.c
+ *
+ * ESP-IDF HW timer-based driver for rotary encoders
+ *
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#include "encoder.h"
+#include <esp_log.h>
+#include <string.h>
+#include <freertos/semphr.h>
+#include <esp_timer.h>
+
+#define MUTEX_TIMEOUT 10
+
+#ifdef CONFIG_RE_BTN_PRESSED_LEVEL_0
+#define BTN_PRESSED_LEVEL 0
+#else
+#define BTN_PRESSED_LEVEL 1
+#endif
+
+static const char *TAG = "encoder";
+static rotary_encoder_t *encs[CONFIG_RE_MAX] = { 0 };
+static const int8_t valid_states[] = { 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 };
+static SemaphoreHandle_t mutex;
+static QueueHandle_t _queue;
+
+#define GPIO_BIT(x) ((x) < 32 ? BIT(x) : ((uint64_t)(((uint64_t)1)<<(x))))
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+inline static void read_encoder(rotary_encoder_t *re)
+{
+    rotary_encoder_event_t ev = {
+        .sender = re
+    };
+
+    if (re->pin_btn < GPIO_NUM_MAX)
+    do
+    {
+        if (re->btn_state == RE_BTN_PRESSED && re->btn_pressed_time_us < CONFIG_RE_BTN_DEAD_TIME_US)
+        {
+            // Dead time
+            re->btn_pressed_time_us += CONFIG_RE_INTERVAL_US;
+            break;
+        }
+
+        // read button state
+        if (gpio_get_level(re->pin_btn) == BTN_PRESSED_LEVEL)
+        {
+            if (re->btn_state == RE_BTN_RELEASED)
+            {
+                // first press
+                re->btn_state = RE_BTN_PRESSED;
+                re->btn_pressed_time_us = 0;
+                ev.type = RE_ET_BTN_PRESSED;
+                xQueueSendToBack(_queue, &ev, 0);
+                break;
+            }
+
+            re->btn_pressed_time_us += CONFIG_RE_INTERVAL_US;
+
+            if (re->btn_state == RE_BTN_PRESSED && re->btn_pressed_time_us >= CONFIG_RE_BTN_LONG_PRESS_TIME_US)
+            {
+                // Long press
+                re->btn_state = RE_BTN_LONG_PRESSED;
+                ev.type = RE_ET_BTN_LONG_PRESSED;
+                xQueueSendToBack(_queue, &ev, 0);
+            }
+        }
+        else if (re->btn_state != RE_BTN_RELEASED)
+        {
+            bool clicked = re->btn_state == RE_BTN_PRESSED;
+            // released
+            re->btn_state = RE_BTN_RELEASED;
+            ev.type = RE_ET_BTN_RELEASED;
+            xQueueSendToBack(_queue, &ev, 0);
+            if (clicked)
+            {
+                ev.type = RE_ET_BTN_CLICKED;
+                xQueueSendToBack(_queue, &ev, 0);
+            }
+        }
+    } while(0);
+
+    re->code <<= 2;
+    re->code |= gpio_get_level(re->pin_a);
+    re->code |= gpio_get_level(re->pin_b) << 1;
+    re->code &= 0xf;
+
+    if (!valid_states[re->code])
+        return;
+
+    int8_t inc = 0;
+
+    re->store = (re->store << 4) | re->code;
+
+    if (re->store == 0xe817) inc = 1;
+    if (re->store == 0xd42b) inc = -1;
+
+    if (inc)
+    {
+        ev.type = RE_ET_CHANGED;
+        ev.diff = inc;
+        xQueueSendToBack(_queue, &ev, 0);
+    }
+}
+
+static void timer_handler(void *arg)
+{
+    if (!xSemaphoreTake(mutex, 0))
+        return;
+
+    for (size_t i = 0; i < CONFIG_RE_MAX; i++)
+        if (encs[i])
+            read_encoder(encs[i]);
+
+    xSemaphoreGive(mutex);
+}
+
+static const esp_timer_create_args_t timer_args = {
+        .name = "__encoder__",
+        .arg = NULL,
+        .callback = timer_handler,
+        .dispatch_method = ESP_TIMER_TASK
+};
+
+static esp_timer_handle_t timer;
+
+esp_err_t rotary_encoder_init(QueueHandle_t queue)
+{
+    CHECK_ARG(queue);
+    _queue = queue;
+
+    mutex = xSemaphoreCreateMutex();
+    if (!mutex)
+    {
+        ESP_LOGE(TAG, "Failed to create mutex");
+        return ESP_ERR_NO_MEM;
+    }
+
+    CHECK(esp_timer_create(&timer_args, &timer));
+    CHECK(esp_timer_start_periodic(timer, CONFIG_RE_INTERVAL_US));
+
+    ESP_LOGI(TAG, "Initialization complete, timer interval: %dms", CONFIG_RE_INTERVAL_US / 1000);
+    return ESP_OK;
+}
+
+esp_err_t rotary_encoder_add(rotary_encoder_t *re)
+{
+    CHECK_ARG(re);
+    if (!xSemaphoreTake(mutex, MUTEX_TIMEOUT))
+    {
+        ESP_LOGE(TAG, "Failed to take mutex");
+        return ESP_ERR_INVALID_STATE;
+    }
+
+    bool ok = false;
+    for (size_t i = 0; i < CONFIG_RE_MAX; i++)
+        if (!encs[i])
+        {
+            re->index = i;
+            encs[i] = re;
+            ok = true;
+            break;
+        }
+    if (!ok)
+    {
+        ESP_LOGE(TAG, "Too many encoders");
+        xSemaphoreGive(mutex);
+        return ESP_ERR_NO_MEM;
+    }
+
+    // setup GPIO
+    gpio_config_t io_conf;
+    memset(&io_conf, 0, sizeof(gpio_config_t));
+    io_conf.mode = GPIO_MODE_INPUT;
+    io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
+    io_conf.intr_type = GPIO_INTR_DISABLE;
+    io_conf.pin_bit_mask = GPIO_BIT(re->pin_a) | GPIO_BIT(re->pin_b);
+    if (re->pin_btn < GPIO_NUM_MAX)
+        io_conf.pin_bit_mask |= GPIO_BIT(re->pin_btn);
+    CHECK(gpio_config(&io_conf));
+
+    re->btn_state = RE_BTN_RELEASED;
+    re->btn_pressed_time_us = 0;
+
+    xSemaphoreGive(mutex);
+
+    ESP_LOGI(TAG, "Added rotary encoder %d, A: %d, B: %d, BTN: %d", re->index, re->pin_a, re->pin_b, re->pin_btn);
+    return ESP_OK;
+}
+
+esp_err_t rotary_encoder_remove(rotary_encoder_t *re)
+{
+    CHECK_ARG(re);
+    if (!xSemaphoreTake(mutex, MUTEX_TIMEOUT))
+    {
+        ESP_LOGE(TAG, "Failed to take mutex");
+        return ESP_ERR_INVALID_STATE;
+    }
+
+    for (size_t i = 0; i < CONFIG_RE_MAX; i++)
+        if (encs[i] == re)
+        {
+            encs[i] = NULL;
+            ESP_LOGI(TAG, "Removed rotary encoder %d", i);
+            xSemaphoreGive(mutex);
+            return ESP_OK;
+        }
+
+    ESP_LOGE(TAG, "Unknown encoder");
+    xSemaphoreGive(mutex);
+    return ESP_ERR_NOT_FOUND;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/encoder/encoder.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file encoder.h
+ * @defgroup encoder encoder
+ * @{
+ *
+ * ESP-IDF HW timer-based driver for rotary encoders
+ *
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __ENCODER_H__
+#define __ENCODER_H__
+
+#include <esp_err.h>
+#include <driver/gpio.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/queue.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Button state
+ */
+typedef enum {
+    RE_BTN_RELEASED = 0,      //!< Button currently released
+    RE_BTN_PRESSED = 1,       //!< Button currently pressed
+    RE_BTN_LONG_PRESSED = 2   //!< Button currently long pressed
+} rotary_encoder_btn_state_t;
+
+/**
+ * Rotary encoder descriptor
+ */
+typedef struct
+{
+    gpio_num_t pin_a, pin_b, pin_btn; //!< Encoder pins. pin_btn can be >= GPIO_NUM_MAX if no button used
+    uint8_t code;
+    uint16_t store;
+    size_t index;
+    uint64_t btn_pressed_time_us;
+    rotary_encoder_btn_state_t btn_state;
+} rotary_encoder_t;
+
+/**
+ * Event type
+ */
+typedef enum {
+    RE_ET_CHANGED = 0,      //!< Encoder turned
+    RE_ET_BTN_RELEASED,     //!< Button released
+    RE_ET_BTN_PRESSED,      //!< Button pressed
+    RE_ET_BTN_LONG_PRESSED, //!< Button long pressed (press time (us) > RE_BTN_LONG_PRESS_TIME_US)
+    RE_ET_BTN_CLICKED       //!< Button was clicked
+} rotary_encoder_event_type_t;
+
+/**
+ * Event
+ */
+typedef struct
+{
+    rotary_encoder_event_type_t type;  //!< Event type
+    rotary_encoder_t *sender;          //!< Pointer to descriptor
+    int32_t diff;                      //!< Difference between new and old positions (only if type == RE_ET_CHANGED)
+} rotary_encoder_event_t;
+
+/**
+ * @brief Initialize library
+ *
+ * @param queue Event queue to send encoder events
+ * @return `ESP_OK` on success
+ */
+esp_err_t rotary_encoder_init(QueueHandle_t queue);
+
+/**
+ * @brief Add new rotary encoder
+ *
+ * @param re Encoder descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t rotary_encoder_add(rotary_encoder_t *re);
+
+/**
+ * @brief Remove previously added rotary encoder
+ *
+ * @param re Encoder descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t rotary_encoder_remove(rotary_encoder_t *re);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __ENCODER_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/esp_idf_lib_helpers/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,23 @@
+---
+components:
+  - name: esp_idf_lib_helpers
+    description: |
+      Common support library for esp-idf-lib
+    group: common
+    groups: []
+    code_owners:
+      - name: UncleRus
+      - name: trombik
+    depends:
+      - name: freertos
+    thread_safe: N/A
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: ISC
+    copyrights:
+      - name: trombik
+        year: 2019
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/esp_idf_lib_helpers/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,4 @@
+idf_component_register(
+    INCLUDE_DIRS .
+    REQUIRES freertos
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/esp_idf_lib_helpers/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,13 @@
+Copyright (c) 2019 Tomoyuki Sakurai <y@trombik.org>
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/esp_idf_lib_helpers/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,8 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+
+ifdef CONFIG_IDF_TARGET_ESP8266
+COMPONENT_DEPENDS = esp8266 freertos
+else
+COMPONENT_DEPENDS = freertos
+endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/esp_idf_lib_helpers/esp_idf_lib_helpers.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2019 Tomoyuki Sakurai <y@trombik.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#if !defined(__ESP_IDF_LIB_HELPERS__H__)
+#define __ESP_IDF_LIB_HELPERS__H__
+
+/* XXX this header file does not need to include freertos/FreeRTOS.h.
+ * but without it, ESP8266 RTOS SDK does not include `sdkconfig.h` in correct
+ * order. as this header depends on sdkconfig.h, sdkconfig.h must be included
+ * first. however, the SDK includes this header first, then includes
+ * `sdkconfig.h` when freertos/FreeRTOS.h is not explicitly included. an
+ * evidence can be found in `build/${COMPONENT}/${COMPONENT}.d` in a failed
+ * build.
+ */
+#include <freertos/FreeRTOS.h>
+#include <esp_idf_version.h>
+
+#if !defined(ESP_IDF_VERSION) || !defined(ESP_IDF_VERSION_VAL)
+#error Unknown ESP-IDF/ESP8266 RTOS SDK version
+#endif
+
+/* Minimal supported version for ESP32, ESP32S2 */
+#define HELPER_ESP32_MIN_VER    ESP_IDF_VERSION_VAL(3, 3, 5)
+/* Minimal supported version for ESP8266 */
+#define HELPER_ESP8266_MIN_VER  ESP_IDF_VERSION_VAL(3, 3, 0)
+
+/* HELPER_TARGET_IS_ESP32
+ * 1 when the target is esp32
+ */
+#if defined(CONFIG_IDF_TARGET_ESP32) \
+        || defined(CONFIG_IDF_TARGET_ESP32S2) \
+        || defined(CONFIG_IDF_TARGET_ESP32S3) \
+        || defined(CONFIG_IDF_TARGET_ESP32C2) \
+        || defined(CONFIG_IDF_TARGET_ESP32C3)
+#define HELPER_TARGET_IS_ESP32     (1)
+#define HELPER_TARGET_IS_ESP8266   (0)
+
+/* HELPER_TARGET_IS_ESP8266
+ * 1 when the target is esp8266
+ */
+#elif defined(CONFIG_IDF_TARGET_ESP8266)
+#define HELPER_TARGET_IS_ESP32     (0)
+#define HELPER_TARGET_IS_ESP8266   (1)
+#else
+#error BUG: cannot determine the target
+#endif
+
+#if HELPER_TARGET_IS_ESP32 && ESP_IDF_VERSION < HELPER_ESP32_MIN_VER
+#error Unsupported ESP-IDF version. Please update!
+#endif
+
+#if HELPER_TARGET_IS_ESP8266 && ESP_IDF_VERSION < HELPER_ESP8266_MIN_VER
+#error Unsupported ESP8266 RTOS SDK version. Please update!
+#endif
+
+/* show the actual values for debugging */
+#if DEBUG
+#define VALUE_TO_STRING(x) #x
+#define VALUE(x) VALUE_TO_STRING(x)
+#define VAR_NAME_VALUE(var) #var "="  VALUE(var)
+#pragma message(VAR_NAME_VALUE(CONFIG_IDF_TARGET_ESP32C3))
+#pragma message(VAR_NAME_VALUE(CONFIG_IDF_TARGET_ESP32S2))
+#pragma message(VAR_NAME_VALUE(CONFIG_IDF_TARGET_ESP32))
+#pragma message(VAR_NAME_VALUE(CONFIG_IDF_TARGET_ESP8266))
+#pragma message(VAR_NAME_VALUE(ESP_IDF_VERSION_MAJOR))
+#endif
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/esp_idf_lib_helpers/ets_sys.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,21 @@
+#if CONFIG_IDF_TARGET_ESP32
+#include <esp32/rom/ets_sys.h>
+#elif CONFIG_IDF_TARGET_ESP32C2
+#include <esp32c2/rom/ets_sys.h>
+#elif CONFIG_IDF_TARGET_ESP32C3
+#include <esp32c3/rom/ets_sys.h>
+#elif CONFIG_IDF_TARGET_ESP32C6
+#include <esp32c6/rom/ets_sys.h>
+#elif CONFIG_IDF_TARGET_ESP32H2
+#include <esp32h2/rom/ets_sys.h>
+#elif CONFIG_IDF_TARGET_ESP32H4
+#include <esp32h4/rom/ets_sys.h>
+#elif CONFIG_IDF_TARGET_ESP32S2
+#include <esp32s2/rom/ets_sys.h>
+#elif CONFIG_IDF_TARGET_ESP32S3
+#include <esp32s3/rom/ets_sys.h>
+#elif CONFIG_IDF_TARGET_ESP8266
+#include <rom/ets_sys.h>
+#else
+#error "ets_sys: Unknown target"
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/example/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,23 @@
+---
+components:
+  - name: example
+    description: An example component
+    group: misc
+    groups: []
+    code_owners: trombik
+    depends:
+      # FIXME conditional depends
+      - name: driver
+      - name: log
+    thread_safe: N/A
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: ISC
+    copyrights:
+      - author:
+          name: trombik
+        year: 2021
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/example/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,11 @@
+if(${IDF_TARGET} STREQUAL esp8266)
+    set(req esp8266 log)
+else()
+    set(req driver log)
+endif()
+
+idf_component_register(
+    SRCS example.c
+    INCLUDE_DIRS .
+    REQUIRES ${req}
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/example/Kconfig	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,17 @@
+menu "Example"
+    choice EXAMPLE_CHOOSE
+        prompt "Choose one"
+        default EXAMPLE_USING_FOO
+        help
+            An example to choose one from choices
+        config EXAMPLE_USING_FOO
+            bool "FOO"
+        config EXAMPLE_USING_BAR
+            bool "BAR"
+    endchoice
+    config EXAMPLE_NUMBER
+        int "Number example"
+        default 1
+        help
+            An example to choose number
+endmenu
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/example/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,13 @@
+Copyright (c) YYYY YOUR NAME HERE <user@your.dom.ain>
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/example/README.md	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,7 @@
+# `example`
+
+An example driver. Use this as an template for new driver.
+
+## Usage
+
+As this is an example, no usage is available.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/example/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = log
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/example/example.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) YYYY YOUR NAME HERE <user@your.dom.ain>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * this file is supposed to conform the code style.
+ */
+
+/* inculde external headers. sort header files by name. if the order matters,
+ * create another header block separated by an empty line. */
+#include <freertos/FreeRTOS.h>
+
+#include <esp_err.h>
+#include <esp_log.h>
+
+/* insert an empty line after external header files. use double-quotes for
+ * local header file name */
+#include "example.h"
+
+#define USE_UPPER_CASE_FOR_MACRO (1)
+
+/* do NOT use upper case for variables. use `snake_case` instead of
+ * `CamelCase`. */
+static char *tag = "example";
+
+/* prefix public function names with the component name, `example_` in this
+ * case. */
+esp_err_t example_do_something(int foo, int *p)
+{
+    /* indent with four spaces, not a tab */
+
+    /* declare variables at the beginning of the function, not in the middle
+     * of code */
+    esp_err_t err; // an inline comment should use `//`, not `/* *`/
+
+    if (foo == 0)
+    {
+        ESP_LOGE(tag, "example(): argument must not be zero");
+        err = ESP_ERR_INVALID_ARG;
+        goto fail;
+    }
+
+    if (foo > 0)
+    {
+
+        /* do something */
+        *p = foo;
+    }
+    else
+    {
+
+        /* do other thing */
+        p = NULL;
+    }
+
+    switch (foo)
+    {
+        case 1:
+            break;
+        case 2:
+            break;
+        default:
+            break;
+    }
+
+    err = ESP_OK;
+fail:
+    return err;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/example/example.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) YYYY YOUR NAME HERE <user@your.dom.ain>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/**
+ * @file example.h
+ * @defgroup example example
+ * @{
+ *
+ * An example component.
+ *
+ */
+
+#if !defined(__EXAMPLE__H__)
+#define __EXAMPLE__H__
+
+#ifdef __cplusplus
+
+/* in most cases, break before an opening brace, but do not in case of `extern
+ * "C"`. otherwise, all the code would have been indented.
+ */
+extern "C" {
+#endif
+
+#include <esp_err.h>
+
+/**
+ * @brief An example function
+ *
+ * This is an example function in `example` component.
+ *
+ * @param foo An integer of something.
+ * @param p A pointer to an integer.
+ * @return `ESP_OK` on success.
+ */
+esp_err_t example_do_something(int foo, int *p);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif // __EXAMPLE__H__
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/framebuffer/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,21 @@
+---
+components:
+  - name: framebuffer
+    description: RGB framebuffer component
+    group: common
+    groups: []
+    code_owners: UncleRus
+    depends:
+      - name: log
+      - name: color
+    thread_safe: N/A
+    targets:
+      - name: esp32
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: MIT
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2021
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/framebuffer/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,6 @@
+idf_component_register(
+    SRCS framebuffer.c 
+         fbanimation.c
+    INCLUDE_DIRS .
+    REQUIRES log color
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/framebuffer/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,3 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_SRCDIRS = . led_effects
+COMPONENT_DEPENDS = log color lib8tion noise
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/framebuffer/fbanimation.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,100 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file animation.c
+ *
+ * ESP-IDF abstraction of framebuffer animation
+ *
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+#include <esp_err.h>
+#include <esp_log.h>
+#include "fbanimation.h"
+
+static const char *TAG = "animation";
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+static void display_frame(void *ctx)
+{
+    fb_animation_t *animation = (fb_animation_t *)ctx;
+
+    // run effect
+    esp_err_t res = animation->draw ? animation->draw(animation->fb) : ESP_FAIL;
+    if (res != ESP_OK)
+    {
+        ESP_LOGE(TAG, "Error running effect %d (%s)", res, esp_err_to_name(res));
+        return;
+    }
+    // render frame
+    res = fb_render(animation->fb, animation->render_ctx);
+    if (res != ESP_OK)
+    {
+        ESP_LOGE(TAG, "Error rendering frame %d (%s)", res, esp_err_to_name(res));
+        return;
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+esp_err_t fb_animation_init(fb_animation_t *animation, framebuffer_t *fb)
+{
+    CHECK_ARG(animation && fb);
+
+    animation->fb = fb;
+    animation->timer = NULL;
+    esp_timer_create_args_t timer_args = {
+        .arg = animation,
+        .callback = display_frame,
+        .dispatch_method = ESP_TIMER_TASK,
+    };
+    return esp_timer_create(&timer_args, &animation->timer);
+}
+
+esp_err_t fb_animation_play(fb_animation_t *animation, uint8_t fps, fb_draw_cb_t draw, void *render_ctx)
+{
+    CHECK_ARG(animation && fps && draw);
+
+    animation->render_ctx = render_ctx;
+    animation->draw = draw;
+    return esp_timer_start_periodic(animation->timer, 1000000 / fps);
+}
+
+esp_err_t fb_animation_stop(fb_animation_t *animation)
+{
+    CHECK_ARG(animation);
+
+    return esp_timer_stop(animation->timer);
+}
+
+esp_err_t fb_animation_free(fb_animation_t *animation)
+{
+    CHECK_ARG(animation);
+
+    esp_timer_stop(animation->timer);
+    return esp_timer_delete(animation->timer);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/framebuffer/fbanimation.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,103 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file fbanimation.h
+ * @defgroup animation animation
+ * @{
+ *
+ * ESP-IDF abstraction of framebuffer animation
+ *
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+#ifndef __FBANIMATION_H__
+#define __FBANIMATION_H__
+
+#include <esp_timer.h>
+#include "framebuffer.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Draw funtion type
+ */
+typedef esp_err_t (*fb_draw_cb_t)(framebuffer_t *fb);
+
+/**
+ * Animation descriptor
+ */
+typedef struct
+{
+    framebuffer_t *fb;         ///< Framebuffer descriptor
+    void *render_ctx;          ///< Renderer context
+    esp_timer_handle_t timer;  ///< Animation timer
+    fb_draw_cb_t draw;         ///< Draw function
+} fb_animation_t;
+
+/**
+ * @brief Create animation based on LED effect
+ *
+ * @param animation     Animation descriptor
+ * @param fb            Framebuffer descriptor
+ * @return              ESP_OK on success
+ */
+esp_err_t fb_animation_init(fb_animation_t *animation, framebuffer_t *fb);
+
+/**
+ * @brief Play animation
+ *
+ * @param animation     Animation descriptor
+ * @param fps           Target FPS
+ * @param draw          Function for drawing on a framebuffer
+ * @param render_ctx    Renderer callback argument
+ * @return              ESP_OK on success
+ */
+esp_err_t fb_animation_play(fb_animation_t *animation, uint8_t fps, fb_draw_cb_t draw, void *render_ctx);
+
+/**
+ * @brief Stop playing animation
+ *
+ * @param animation     Animation descriptor
+ * @return              ESP_OK on success
+ */
+esp_err_t fb_animation_stop(fb_animation_t *animation);
+
+/**
+ * @brief Create animation based on LED effect
+ *
+ * @param animation     Animation descriptor
+ * @return              ESP_OK on success
+ */
+esp_err_t fb_animation_free(fb_animation_t *animation);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __FBANIMATION_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/framebuffer/framebuffer.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,249 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file framebuffer.c
+ *
+ * Simple abstraction of RGB framebuffer for ESP-IDF
+ *
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+#include <stdlib.h>
+#include "framebuffer.h"
+
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+#define CHECK(x) do { esp_err_t __; if ((__ = (x)) != ESP_OK) return __; } while (0)
+
+static size_t xy(void *ctx, size_t x, size_t y)
+{
+    framebuffer_t *fb = (framebuffer_t *)ctx;
+    return y * fb->width + x;
+}
+
+esp_err_t fb_init(framebuffer_t *fb, size_t width, size_t height, fb_render_cb_t render_cb)
+{
+    CHECK_ARG(fb && width && height && render_cb);
+
+    fb->width = width;
+    fb->height = height;
+    fb->frame_num = 0;
+    fb->last_frame_us = 0;
+    fb->render = render_cb;
+    fb->internal = NULL;
+    fb->mutex = xSemaphoreCreateMutex();
+    if (!fb->mutex)
+        return ESP_ERR_NO_MEM;
+    fb->data = calloc(1, FB_SIZE(fb));
+    if (!fb->data)
+        return ESP_ERR_NO_MEM;
+
+    return ESP_OK;
+}
+
+esp_err_t fb_free(framebuffer_t *fb)
+{
+    CHECK_ARG(fb);
+
+    if (fb->data)
+        free(fb->data);
+    if (fb->mutex)
+        vSemaphoreDelete(fb->mutex);
+
+    return ESP_OK;
+}
+
+esp_err_t fb_render(framebuffer_t *fb, void *render_ctx)
+{
+    CHECK_ARG(fb && fb->data && fb->render);
+
+    if (xSemaphoreTake(fb->mutex, 0) != pdTRUE)
+        return ESP_ERR_INVALID_STATE;
+    CHECK(fb->render(fb, render_ctx));
+    xSemaphoreGive(fb->mutex);
+
+    return ESP_OK;
+}
+
+esp_err_t fb_set_pixel_rgb(framebuffer_t *fb, size_t x, size_t y, rgb_t color)
+{
+    CHECK_ARG(fb && fb->data && x < fb->width && y < fb->height);
+
+    fb->data[FB_OFFSET(fb, x, y)] = color;
+
+    return ESP_OK;
+}
+
+esp_err_t fb_set_pixel_hsv(framebuffer_t *fb, size_t x, size_t y, hsv_t color)
+{
+    CHECK_ARG(fb && fb->data && x < fb->width && y < fb->height);
+
+    fb->data[FB_OFFSET(fb, x, y)] = hsv2rgb_rainbow(color);
+
+    return ESP_OK;
+}
+
+esp_err_t fb_get_pixel_rgb(framebuffer_t *fb, size_t x, size_t y, rgb_t *color)
+{
+    CHECK_ARG(color && fb && fb->data && x < fb->width && y < fb->height);
+
+    *color = fb->data[FB_OFFSET(fb, x, y)];
+
+    return ESP_OK;
+}
+
+esp_err_t fb_get_pixel_hsv(framebuffer_t *fb, size_t x, size_t y, hsv_t *color)
+{
+    CHECK_ARG(color && fb && fb->data && x < fb->width && y < fb->height);
+
+    *color = rgb2hsv_approximate(fb->data[FB_OFFSET(fb, x, y)]);
+
+    return ESP_OK;
+}
+
+#define WU_WEIGHT(a, b) ((uint8_t)(((a) * (b) + (a) + (b)) >> 8))
+
+esp_err_t fb_set_pixelf_rgb(framebuffer_t *fb, float x, float y, rgb_t color)
+{
+    CHECK_ARG(fb && fb->data);
+
+    // extract the fractional parts and derive their inverses
+    uint8_t xx = (x - (int)x) * 255;
+    uint8_t yy = (y - (int)y) * 255;
+    uint8_t ix = 255 - xx;
+    uint8_t iy = 255 - yy;
+
+    // calculate the intensities for each affected pixel
+    uint8_t weights[4] = {
+        WU_WEIGHT(ix, iy),
+        WU_WEIGHT(xx, iy),
+        WU_WEIGHT(ix, yy),
+        WU_WEIGHT(xx, yy)
+    };
+    // multiply the intensities by the colour, and saturating-add them to the pixels
+    for (uint8_t i = 0; i < 4; i++)
+    {
+        int xn = x + (i & 1);
+        int yn = y + ((i >> 1) & 1);
+        rgb_t clr = { 0 };
+        fb_get_pixel_rgb(fb, xn, yn, &clr);
+        clr.r = qadd8(clr.r, (color.r * weights[i]) >> 8);
+        clr.g = qadd8(clr.g, (color.g * weights[i]) >> 8);
+        clr.b = qadd8(clr.b, (color.b * weights[i]) >> 8);
+        fb_set_pixel_rgb(fb, xn, yn, clr);
+    }
+
+    return ESP_OK;
+}
+
+esp_err_t fb_set_pixelf_hsv(framebuffer_t *fb, float x, float y, hsv_t color)
+{
+    return fb_set_pixelf_rgb(fb, x, y, hsv2rgb_rainbow(color));
+}
+
+esp_err_t fb_clear(framebuffer_t *fb)
+{
+    CHECK_ARG(fb && fb->data);
+
+    memset(fb->data, 0, FB_SIZE(fb));
+
+    return ESP_OK;
+}
+
+esp_err_t fb_shift(framebuffer_t *fb, size_t offs, fb_shift_direction_t dir)
+{
+    CHECK_ARG(fb && fb->data && offs);
+
+    if (((dir == FB_SHIFT_LEFT || dir == FB_SHIFT_RIGHT) && offs >= fb->width)
+            || ((dir == FB_SHIFT_UP || dir == FB_SHIFT_DOWN) && offs >= fb->height))
+        return ESP_OK;
+
+    switch (dir)
+    {
+        case FB_SHIFT_LEFT:
+            for (size_t row = 0; row < fb->height; row++)
+                memmove(fb->data + row * fb->width,
+                        fb->data + row * fb->width + offs,
+                        sizeof(rgb_t) * (fb->width - offs));
+            break;
+        case FB_SHIFT_RIGHT:
+            for (size_t row = 0; row < fb->height; row++)
+                memmove(fb->data + row * fb->width + offs,
+                        fb->data + row * fb->width,
+                        sizeof(rgb_t) * (fb->width - offs));
+            break;
+        case FB_SHIFT_UP:
+            memmove(fb->data + offs * fb->width,
+                    fb->data,
+                    FB_SIZE(fb) - offs * fb->width * sizeof(rgb_t));
+            break;
+        case FB_SHIFT_DOWN:
+            memmove(fb->data,
+                    fb->data + offs * fb->width,
+                    FB_SIZE(fb) - offs * fb->width * sizeof(rgb_t));
+            break;
+    }
+
+    return ESP_OK;
+}
+
+esp_err_t fb_fade(framebuffer_t *fb, uint8_t scale)
+{
+    CHECK_ARG(fb && fb->data);
+
+    for (size_t i = 0; i < fb->width * fb->height; i++)
+        fb->data[i] = rgb_fade(fb->data[i], scale);
+
+    return ESP_OK;
+}
+
+esp_err_t fb_blur2d(framebuffer_t *fb, fract8 amount)
+{
+    CHECK_ARG(fb && fb->data);
+
+    blur2d(fb->data, fb->width, fb->height, amount, xy, fb);
+
+    return ESP_OK;
+}
+
+esp_err_t fb_begin(framebuffer_t *fb)
+{
+    CHECK_ARG(fb);
+
+    if (xSemaphoreTake(fb->mutex, 0) != pdTRUE)
+        return ESP_ERR_INVALID_STATE;
+
+    return ESP_OK;
+}
+
+esp_err_t fb_end(framebuffer_t *fb)
+{
+    CHECK_ARG(fb);
+
+    fb->frame_num++;
+    fb->last_frame_us = esp_timer_get_time();
+    xSemaphoreGive(fb->mutex);
+
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/framebuffer/framebuffer.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,250 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file framebuffer.h
+ * @defgroup framebuffer framebuffer
+ * @{
+ *
+ * Simple abstraction of RGB framebuffer for ESP-IDF
+ *
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+#ifndef __FRAMEBUFFER_H__
+#define __FRAMEBUFFER_H__
+
+#include <esp_err.h>
+#include <color.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/semphr.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define FB_OFFSET(fb, x, y) ((fb)->width * (y) + (x))
+
+#define FB_SIZE(fb) ((fb)->width * (fb)->height * sizeof(rgb_t))
+
+typedef enum {
+    FB_SHIFT_LEFT  = 0,
+    FB_SHIFT_RIGHT,
+    FB_SHIFT_UP,
+    FB_SHIFT_DOWN
+} fb_shift_direction_t;
+
+typedef struct framebuffer_s framebuffer_t;
+
+/**
+ * Renderer callback prototype
+ */
+typedef esp_err_t (*fb_render_cb_t)(framebuffer_t *fb, void *arg);
+
+/**
+ * Framebuffer descriptor descriptor
+ */
+struct framebuffer_s
+{
+    rgb_t *data;                   ///< RGB framebuffer
+    size_t width;                  ///< Framebuffer width
+    size_t height;                 ///< Framebuffer height
+    size_t frame_num;              ///< Number of rendered frames
+    uint64_t last_frame_us;        ///< Time of last rendered frame since boot in microseconds
+    fb_render_cb_t render;         ///< See ::fb_render()
+    uint8_t *internal;             ///< Buffer for effect settings, internal vars, palettes and so on
+    SemaphoreHandle_t mutex;
+};
+
+/**
+ * @brief Initialize framebuffer
+ *
+ * @param fb        Framebuffer descriptor
+ * @param width     Frame width in pixels
+ * @param height    Frame height in pixels
+ * @param render_cb Renderer callback function
+ *
+ * @return          ESP_OK on success
+ */
+esp_err_t fb_init(framebuffer_t *fb, size_t width, size_t height, fb_render_cb_t render_cb);
+
+/**
+ * @brief Free Framebuffer descriptor buffers
+ *
+ * @param fb     Framebuffer descriptor
+ * @return          ESP_OK on success
+ */
+esp_err_t fb_free(framebuffer_t *fb);
+
+/**
+ * @brief Render frambuffer to actual display or LED strip
+ *
+ * Rendering is performed by calling the callback function with passing
+ * it as arguments \p fb and \p ctx
+ *
+ * @param fb   Framebuffer descriptor
+ * @param ctx  Argument to pass to callback
+ * @return     ESP_OK on success
+ */
+esp_err_t fb_render(framebuffer_t *fb, void *ctx);
+
+/**
+ * @brief Set RGB color of framebuffer pixel
+ *
+ * @param fb        Framebuffer descriptor
+ * @param x         X coordinate
+ * @param y         Y coordinate
+ * @param color     RGB color
+ * @return          ESP_OK on success
+ */
+esp_err_t fb_set_pixel_rgb(framebuffer_t *fb, size_t x, size_t y, rgb_t color);
+
+/**
+ * @brief Set HSV color of framebuffer pixel
+ *
+ * @param fb        Framebuffer descriptor
+ * @param x         X coordinate
+ * @param y         Y coordinate
+ * @param color     HSV color
+ * @return          ESP_OK on success
+ */
+esp_err_t fb_set_pixel_hsv(framebuffer_t *fb, size_t x, size_t y, hsv_t color);
+
+/**
+ * @brief Set RGB pixel with subpixel resolution
+ *
+ * @param fb        Framebuffer descriptor
+ * @param x         X coordinate
+ * @param y         Y coordinate
+ * @param color     RGB color
+ * @return          ESP_OK on success
+ */
+esp_err_t fb_set_pixelf_rgb(framebuffer_t *fb, float x, float y, rgb_t color);
+
+/**
+ * @brief Set HSV pixel with subpixel resolution
+ *
+ * @param fb        Framebuffer descriptor
+ * @param x         X coordinate
+ * @param y         Y coordinate
+ * @param color     HSV color
+ * @return          ESP_OK on success
+ */
+esp_err_t fb_set_pixelf_hsv(framebuffer_t *fb, float x, float y, hsv_t color);
+
+/**
+ * @brief Get RGB color of framebuffer pixel
+ *
+ * @param fb          Framebuffer descriptor
+ * @param x           X coordinate
+ * @param y           Y coordinate
+ * @param[out] color  RGB color
+ * @return            ESP_OK on success
+ */
+esp_err_t fb_get_pixel_rgb(framebuffer_t *fb, size_t x, size_t y, rgb_t *color);
+
+/**
+ * @brief Get HSV color of framebuffer pixel
+ *
+ * @param fb          Framebuffer descriptor
+ * @param x           X coordinate
+ * @param y           Y coordinate
+ * @param[out] color  HSV color
+ * @return            ESP_OK on success
+ */
+esp_err_t fb_get_pixel_hsv(framebuffer_t *fb, size_t x, size_t y, hsv_t *color);
+
+/**
+ * @brief Clear framebuffer
+ *
+ * @param fb     Framebuffer descriptor
+ * @return       ESP_OK on success
+ */
+esp_err_t fb_clear(framebuffer_t *fb);
+
+/**
+ * @brief Shift framebuffer
+ *
+ * @param fb        Framebuffer descriptor
+ * @param offs      Shift size
+ * @param dir       Shift direction
+ * @return          ESP_OK on success
+ */
+esp_err_t fb_shift(framebuffer_t *fb, size_t offs, fb_shift_direction_t dir);
+
+/**
+ * @brief Fade pixels to black
+ *
+ * rgb_fade(pixel, scale) for all pixels in framebuffer
+ *
+ * @param fb        Framebuffer descriptor
+ * @param scale     Amount of scaling
+ * @return          ESP_OK on success
+ */
+esp_err_t fb_fade(framebuffer_t *fb, uint8_t scale);
+
+/**
+ * @brief Aplly two-dimensional blur filter on framebuffer
+ *
+ * Spreads light to 8 XY neighbors.
+ *
+ *   0 = no spread at all
+ *  64 = moderate spreading
+ * 172 = maximum smooth, even spreading
+ *
+ * 173..255 = wider spreading, but increasing flicker
+ *
+ * @param fb        Framebuffer descriptor
+ * @param amount    Amount of bluring
+ * @return          ESP_OK on success
+ */
+esp_err_t fb_blur2d(framebuffer_t *fb, fract8 amount);
+
+/**
+ * @brief Start frame rendering
+ *
+ * This function must be called in effects at the beginning of rendering frame
+ *
+ * @param fb     Framebuffer descriptor
+ * @return       ESP_OK on success
+ */
+esp_err_t fb_begin(framebuffer_t *fb);
+
+/**
+ * @brief Finish frame rendering
+ *
+ * This function must be called in effects at the end of rendering frame
+ *
+ * @param fb     Framebuffer descriptor
+ * @return       ESP_OK on success
+ */
+esp_err_t fb_end(framebuffer_t *fb);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __FRAMEBUFFER_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/hd44780/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,22 @@
+---
+components:
+  - name: hd44780
+    description: |
+      Driver for HD44780 compatible LCD text displays
+    group: misc
+    groups: []
+    code_owners:
+      - name: UncleRus
+    depends:
+      - name: driver
+    thread_safe: no
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - name: UncleRus
+        year: 2016
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/hd44780/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,11 @@
+if(${IDF_TARGET} STREQUAL esp8266)
+    set(req esp8266 freertos esp_idf_lib_helpers)
+else()
+    set(req driver freertos esp_idf_lib_helpers)
+endif()
+
+idf_component_register(
+    SRCS hd44780.c
+    INCLUDE_DIRS .
+    REQUIRES ${req}
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/hd44780/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+Copyright (c) 2016, 2018 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/hd44780/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,7 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+
+ifdef CONFIG_IDF_TARGET_ESP8266
+COMPONENT_DEPENDS = esp8266 freertos log esp_idf_lib_helpers
+else
+COMPONENT_DEPENDS = driver freertos log esp_idf_lib_helpers
+endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/hd44780/hd44780.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,291 @@
+/*
+ * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file hd44780.c
+ *
+ * ESP-IDF driver for HD44780 compatible LCD text displays
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#include <string.h>
+#include <esp_system.h>
+#include <esp_idf_lib_helpers.h>
+#include <ets_sys.h>
+#include "hd44780.h"
+
+#define MS 1000
+
+#define BV(x) (1 << (x))
+#define GPIO_BIT(x) (1ULL << (x))
+
+#define DELAY_CMD_LONG  (3 * MS) // >1.53ms according to datasheet
+#define DELAY_CMD_SHORT (60)     // >39us according to datasheet
+#define DELAY_TOGGLE    (1)      // E cycle time >= 1μs, E pulse width >= 450ns, Data set-up time >= 195ns
+#define DELAY_INIT      (5 * MS)
+
+#define CMD_CLEAR        0x01
+#define CMD_RETURN_HOME  0x02
+#define CMD_ENTRY_MODE   0x04
+#define CMD_DISPLAY_CTRL 0x08
+#define CMD_SHIFT        0x10
+#define CMD_FUNC_SET     0x20
+#define CMD_CGRAM_ADDR   0x40
+#define CMD_DDRAM_ADDR   0x80
+
+#define ARG_MOVE_RIGHT 0x04
+#define ARG_MOVE_LEFT 0x00
+#define CMD_SHIFT_LEFT  (CMD_SHIFT | CMD_DISPLAY_CTRL | ARG_MOVE_LEFT)
+#define CMD_SHIFT_RIGHT (CMD_SHIFT | CMD_DISPLAY_CTRL | ARG_MOVE_RIGHT)
+
+// CMD_ENTRY_MODE
+#define ARG_EM_INCREMENT    BV(1)
+#define ARG_EM_SHIFT        (1)
+
+// CMD_DISPLAY_CTRL
+#define ARG_DC_DISPLAY_ON   BV(2)
+#define ARG_DC_CURSOR_ON    BV(1)
+#define ARG_DC_CURSOR_BLINK (1)
+
+// CMD_FUNC_SET
+#define ARG_FS_8_BIT        BV(4)
+#define ARG_FS_2_LINES      BV(3)
+#define ARG_FS_FONT_5X10    BV(2)
+
+#define init_delay()   do { ets_delay_us(DELAY_INIT); } while (0)
+#define short_delay()  do { ets_delay_us(DELAY_CMD_SHORT); } while (0)
+#define long_delay()   do { ets_delay_us(DELAY_CMD_LONG); } while (0)
+#define toggle_delay() do { ets_delay_us(DELAY_TOGGLE); } while (0)
+
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+
+static const uint8_t line_addr[] = { 0x00, 0x40, 0x14, 0x54 };
+
+static esp_err_t write_nibble(const hd44780_t *lcd, uint8_t b, bool rs)
+{
+    if (lcd->write_cb)
+    {
+        uint8_t data = (((b >> 3) & 1) << lcd->pins.d7)
+                     | (((b >> 2) & 1) << lcd->pins.d6)
+                     | (((b >> 1) & 1) << lcd->pins.d5)
+                     | ((b & 1) << lcd->pins.d4)
+                     | (rs ? 1 << lcd->pins.rs : 0)
+                     | (lcd->backlight ? 1 << lcd->pins.bl : 0);
+        CHECK(lcd->write_cb(lcd, data | (1 << lcd->pins.e)));
+        toggle_delay();
+        CHECK(lcd->write_cb(lcd, data));
+    }
+    else
+    {
+        CHECK(gpio_set_level(lcd->pins.rs, rs));
+        ets_delay_us(1); // Address Setup time >= 60ns.
+        CHECK(gpio_set_level(lcd->pins.e, true));
+        CHECK(gpio_set_level(lcd->pins.d7, (b >> 3) & 1));
+        CHECK(gpio_set_level(lcd->pins.d6, (b >> 2) & 1));
+        CHECK(gpio_set_level(lcd->pins.d5, (b >> 1) & 1));
+        CHECK(gpio_set_level(lcd->pins.d4, b & 1));
+        toggle_delay();
+        CHECK(gpio_set_level(lcd->pins.e, false));
+    }
+
+    return ESP_OK;
+}
+
+static esp_err_t write_byte(const hd44780_t *lcd, uint8_t b, bool rs)
+{
+    CHECK(write_nibble(lcd, b >> 4, rs));
+    CHECK(write_nibble(lcd, b, rs));
+
+    return ESP_OK;
+}
+
+esp_err_t hd44780_init(const hd44780_t *lcd)
+{
+    CHECK_ARG(lcd && lcd->lines > 0 && lcd->lines < 5);
+
+    if (!lcd->write_cb)
+    {
+        gpio_config_t io_conf;
+        memset(&io_conf, 0, sizeof(gpio_config_t));
+        io_conf.mode = GPIO_MODE_OUTPUT;
+        io_conf.pin_bit_mask =
+                GPIO_BIT(lcd->pins.rs) |
+                GPIO_BIT(lcd->pins.e) |
+                GPIO_BIT(lcd->pins.d4) |
+                GPIO_BIT(lcd->pins.d5) |
+                GPIO_BIT(lcd->pins.d6) |
+                GPIO_BIT(lcd->pins.d7);
+        if (lcd->pins.bl != HD44780_NOT_USED)
+            io_conf.pin_bit_mask |= GPIO_BIT(lcd->pins.bl);
+        CHECK(gpio_config(&io_conf));
+    }
+
+    // switch to 4 bit mode
+    for (uint8_t i = 0; i < 3; i ++)
+    {
+        CHECK(write_nibble(lcd, (CMD_FUNC_SET | ARG_FS_8_BIT) >> 4, false));
+        init_delay();
+    }
+    CHECK(write_nibble(lcd, CMD_FUNC_SET >> 4, false));
+    short_delay();
+
+    // Specify the number of display lines and character font
+    CHECK(write_byte(lcd,
+        CMD_FUNC_SET
+            | (lcd->lines > 1 ? ARG_FS_2_LINES : 0)
+            | (lcd->font == HD44780_FONT_5X10 ? ARG_FS_FONT_5X10 : 0),
+        false));
+    short_delay();
+    // Display off
+    CHECK(hd44780_control(lcd, false, false, false));
+    // Clear
+    CHECK(hd44780_clear(lcd));
+    // Entry mode set
+    CHECK(write_byte(lcd, CMD_ENTRY_MODE | ARG_EM_INCREMENT, false));
+    short_delay();
+    // Display on
+    CHECK(hd44780_control(lcd, true, false, false));
+
+    return ESP_OK;
+}
+
+esp_err_t hd44780_control(const hd44780_t *lcd, bool on, bool cursor, bool cursor_blink)
+{
+    CHECK_ARG(lcd);
+
+    CHECK(write_byte(lcd,
+        CMD_DISPLAY_CTRL
+            | (on ? ARG_DC_DISPLAY_ON : 0)
+            | (cursor ? ARG_DC_CURSOR_ON : 0)
+            | (cursor_blink ? ARG_DC_CURSOR_BLINK : 0),
+        false));
+    short_delay();
+
+    return ESP_OK;
+}
+
+esp_err_t hd44780_clear(const hd44780_t *lcd)
+{
+    CHECK_ARG(lcd);
+
+    CHECK(write_byte(lcd, CMD_CLEAR, false));
+    long_delay();
+
+    return ESP_OK;
+}
+
+esp_err_t hd44780_gotoxy(const hd44780_t *lcd, uint8_t col, uint8_t line)
+{
+    CHECK_ARG(lcd && line < lcd->lines && line < sizeof(line_addr));
+
+    CHECK(write_byte(lcd, CMD_DDRAM_ADDR + line_addr[line] + col, false));
+    short_delay();
+
+    return ESP_OK;
+}
+
+esp_err_t hd44780_putc(const hd44780_t *lcd, char c)
+{
+    CHECK_ARG(lcd);
+
+    CHECK(write_byte(lcd, c, true));
+    short_delay();
+
+    return ESP_OK;
+}
+
+esp_err_t hd44780_puts(const hd44780_t *lcd, const char *s)
+{
+    CHECK_ARG(lcd && s);
+
+    while (*s)
+    {
+        CHECK(hd44780_putc(lcd, *s));
+        s++;
+    }
+
+    return ESP_OK;
+}
+
+esp_err_t hd44780_switch_backlight(hd44780_t *lcd, bool on)
+{
+    CHECK_ARG(lcd);
+    if (lcd->pins.bl == HD44780_NOT_USED)
+        return ESP_ERR_NOT_SUPPORTED;
+
+    if (!lcd->write_cb)
+        CHECK(gpio_set_level(lcd->pins.bl, on));
+    else
+        CHECK(lcd->write_cb(lcd, on ? BV(lcd->pins.bl) : 0));
+
+    lcd->backlight = on;
+
+    return ESP_OK;
+}
+
+esp_err_t hd44780_upload_character(const hd44780_t *lcd, uint8_t num, const uint8_t *data)
+{
+    CHECK_ARG(lcd && data && num < 8);
+
+    uint8_t bytes = lcd->font == HD44780_FONT_5X8 ? 8 : 10;
+    CHECK(write_byte(lcd, CMD_CGRAM_ADDR + num * bytes, false));
+    short_delay();
+    for (uint8_t i = 0; i < bytes; i ++)
+    {
+        CHECK(write_byte(lcd, data[i], true));
+        short_delay();
+    }
+
+    CHECK(hd44780_gotoxy(lcd, 0, 0));
+
+    return ESP_OK;
+}
+
+esp_err_t hd44780_scroll_left(const hd44780_t *lcd)
+{
+    CHECK_ARG(lcd);
+
+    CHECK(write_byte(lcd, CMD_SHIFT_LEFT, false));
+    short_delay();
+
+    return ESP_OK;
+}
+
+esp_err_t hd44780_scroll_right(const hd44780_t *lcd)
+{
+    CHECK_ARG(lcd);
+
+    CHECK(write_byte(lcd, CMD_SHIFT_RIGHT, false));
+    short_delay();
+
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/hd44780/hd44780.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file hd44780.h
+ * @defgroup hd44780 hd44780
+ * @{
+ *
+ * ESP-IDF driver for HD44780 compatible LCD text displays
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __HD44780_H__
+#define __HD44780_H__
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <driver/gpio.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define HD44780_NOT_USED 0xff
+
+/**
+ * LCD font type. Please refer to the datasheet
+ * of your module.
+ */
+typedef enum
+{
+    HD44780_FONT_5X8 = 0,
+    HD44780_FONT_5X10
+} hd44780_font_t;
+
+typedef struct hd44780 hd44780_t;
+
+typedef esp_err_t (*hd44780_write_cb_t)(const hd44780_t *lcd, uint8_t data);
+
+/**
+ * LCD descriptor. Fill it before use.
+ */
+struct hd44780
+{
+    hd44780_write_cb_t write_cb; //!< Data write callback. Set it to NULL in case of direct LCD connection to GPIO
+    struct
+    {
+        uint8_t rs;        //!< GPIO/register bit used for RS pin
+        uint8_t e;         //!< GPIO/register bit used for E pin
+        uint8_t d4;        //!< GPIO/register bit used for D4 pin
+        uint8_t d5;        //!< GPIO/register bit used for D5 pin
+        uint8_t d6;        //!< GPIO/register bit used for D5 pin
+        uint8_t d7;        //!< GPIO/register bit used for D5 pin
+        uint8_t bl;        //!< GPIO/register bit used for backlight. Set it `HD44780_NOT_USED` if no backlight used
+    } pins;
+    hd44780_font_t font;   //!< LCD Font type
+    uint8_t lines;         //!< Number of lines for LCD. Many 16x1 LCD has two lines (like 8x2)
+    bool backlight;        //!< Current backlight state
+};
+
+/**
+ * @brief Init LCD
+ *
+ * Set cursor position to (0, 0)
+ *
+ * @param lcd LCD descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t hd44780_init(const hd44780_t *lcd);
+
+/**
+ * @brief Control LCD
+ *
+ * On/off LCD, show/hide cursor, set cursor blink
+ *
+ * @param lcd LCD descriptor
+ * @param on Switch LCD on if true
+ * @param cursor Show cursor if true
+ * @param cursor_blink Enable cursor blinking if true
+ * @return `ESP_OK` on success
+ */
+esp_err_t hd44780_control(const hd44780_t *lcd, bool on, bool cursor, bool cursor_blink);
+
+/**
+ * @brief Clear LCD
+ *
+ * Clear memory and move cursor to (0, 0)
+ *
+ * @param lcd LCD descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t hd44780_clear(const hd44780_t *lcd);
+
+/**
+ * @brief Move cursor
+ *
+ * @param lcd LCD descriptor
+ * @param col Column
+ * @param line Line
+ * @return `ESP_OK` on success
+ */
+esp_err_t hd44780_gotoxy(const hd44780_t *lcd, uint8_t col, uint8_t line);
+
+/**
+ * @brief Write character at cursor position
+ *
+ * @param lcd LCD descriptor
+ * @param c Character to write
+ * @return `ESP_OK` on success
+ */
+esp_err_t hd44780_putc(const hd44780_t *lcd, char c);
+
+/**
+ * @brief Write NULL-terminated string at cursor position
+ *
+ * @param lcd LCD descriptor
+ * @param s String to write
+ * @return `ESP_OK` on success
+ */
+esp_err_t hd44780_puts(const hd44780_t *lcd, const char *s);
+
+/**
+ * @brief Switch backlight
+ *
+ * @param lcd LCD descriptor
+ * @param on Turn backlight on if true
+ * @return `ESP_OK` on success
+ */
+esp_err_t hd44780_switch_backlight(hd44780_t *lcd, bool on);
+
+/**
+ * @brief Upload character data to the CGRAM
+ *
+ * After upload cursor will be moved to (0, 0).
+ *
+ * @param lcd LCD descriptor
+ * @param num Character number (0..7)
+ * @param data Character data: 8 or 10 bytes depending on the font
+ * @return `ESP_OK` on success
+ */
+esp_err_t hd44780_upload_character(const hd44780_t *lcd, uint8_t num, const uint8_t *data);
+
+/**
+ * @brief Scroll the display content to left by one character
+ *
+ * @param lcd LCD descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t hd44780_scroll_left(const hd44780_t *lcd);
+
+/**
+ * @brief Scroll the display content to right by one character
+ *
+ * @param lcd LCD descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t hd44780_scroll_right(const hd44780_t *lcd);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __HD44780_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/hdc1000/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,25 @@
+---
+components:
+  - name: hdc1000
+    description: |
+      Driver for HDC1000 temperature and humidity sensor
+    group: temperature
+    groups:
+      - humidity
+    code_owners:
+      - name: UncleRus
+    depends:
+      - name: i2cdev
+      - name: log
+      - name: esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - name: UncleRus
+        year: 2021
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/hdc1000/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,11 @@
+if(${IDF_VERSION_MAJOR} STREQUAL 4 AND ${IDF_VERSION_MINOR} STREQUAL 1 AND ${IDF_VERSION_PATCH} STREQUAL 3)
+	set(req i2cdev log esp_idf_lib_helpers)
+else()
+	set(req i2cdev log esp_idf_lib_helpers esp_timer)
+endif()
+
+idf_component_register(
+    SRCS hdc1000.c
+    INCLUDE_DIRS .
+    REQUIRES ${req}
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/hdc1000/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+Copyright 2021 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/hdc1000/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/hdc1000/hdc1000.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,334 @@
+/*
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file hdc1000.c
+ *
+ * ESP-IDF driver for HDC1000 temperature and humidity sensor
+ *
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#include "hdc1000.h"
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <esp_log.h>
+#include <esp_timer.h>
+
+#define I2C_FREQ_HZ 400000 // 400kHz
+
+static const char *TAG = "hdc1000";
+
+#define REG_TEMPERATURE 0x00
+#define REG_HUMIDITY    0x01
+#define REG_CONFIG      0x02
+#define REG_SERIAL_H    0xfb
+#define REG_SERIAL_M    0xfc
+#define REG_SERIAL_L    0xfd
+#define REG_MANUF_ID    0xfe
+#define REG_DEVICE_ID   0xff
+
+#define BIT_CONFIG_RST  15
+#define BIT_CONFIG_HEAT 13
+#define BIT_CONFIG_MODE 12
+#define BIT_CONFIG_BTST 11
+#define BIT_CONFIG_TRES 10
+#define BIT_CONFIG_HRES 8
+
+#define MASK_CONFIG_HRES (3 << BIT_CONFIG_HRES)
+
+#define RESET_TIMEOUT_US (200 * 1000)
+#define MEASURE_TIMEOUT_MS 10 // 6.5 ms
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+static inline uint16_t shuffle(uint16_t v)
+{
+    return (v >> 8) | (v << 8);
+}
+
+static esp_err_t read_reg_nolock(hdc1000_t *dev, uint8_t reg, uint16_t *val)
+{
+    CHECK(i2c_dev_read_reg(&dev->i2c_dev, reg, val, 2));
+    *val = shuffle(*val);
+    return ESP_OK;
+}
+
+static esp_err_t write_reg_nolock(hdc1000_t *dev, uint8_t reg, uint16_t val)
+{
+    uint16_t v = shuffle(val);
+    return i2c_dev_write_reg(&dev->i2c_dev, reg, &v, 2);
+}
+
+static esp_err_t update_reg_nolock(hdc1000_t *dev, uint8_t reg, uint16_t val, uint16_t mask)
+{
+    uint16_t v;
+    CHECK(read_reg_nolock(dev, reg, &v));
+    v = (v & mask) | (val & mask);
+    return write_reg_nolock(dev, reg, v);
+}
+
+static esp_err_t read_reg(hdc1000_t *dev, uint8_t reg, uint16_t *val)
+{
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, read_reg_nolock(dev, reg, val));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+    return ESP_OK;
+}
+
+static esp_err_t update_reg(hdc1000_t *dev, uint8_t reg, uint16_t val, uint16_t mask)
+{
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, update_reg_nolock(dev, reg, val, mask));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+    return ESP_OK;
+}
+
+static float calc_temperature(uint8_t *buf)
+{
+    uint16_t raw = ((uint16_t)buf[0] << 8) | buf[1];
+    return raw / 65536.0f * 165.0f - 40;
+}
+
+static float calc_humidity(uint8_t *buf)
+{
+    uint16_t raw = ((uint16_t)buf[0] << 8) | buf[1];
+    return raw / 65536.0f * 100.0f;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t hdc1000_init_desc(hdc1000_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+
+    if (addr < HDC1000_I2C_ADDRESS_0 || addr > HDC1000_I2C_ADDRESS_3)
+    {
+        ESP_LOGE(TAG, "Invalid I2C address: 0x%02x", addr);
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    dev->i2c_dev.port = port;
+    dev->i2c_dev.addr = addr;
+    dev->i2c_dev.cfg.sda_io_num = sda_gpio;
+    dev->i2c_dev.cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(&dev->i2c_dev);
+}
+
+esp_err_t hdc1000_free_desc(hdc1000_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(&dev->i2c_dev);
+}
+
+esp_err_t hdc1000_init(hdc1000_t *dev)
+{
+    CHECK_ARG(dev);
+
+    esp_err_t res = ESP_OK;
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    // reset
+    I2C_DEV_CHECK(&dev->i2c_dev, update_reg_nolock(dev, REG_CONFIG, BIT(BIT_CONFIG_RST), BIT(BIT_CONFIG_RST)));
+    // wait
+    int64_t start = esp_timer_get_time();
+    while (true)
+    {
+        if ((esp_timer_get_time() - start) >= RESET_TIMEOUT_US)
+        {
+            ESP_LOGE(TAG, "Timeout while reseting device");
+            res = ESP_ERR_TIMEOUT;
+            goto exit;
+        }
+        uint16_t r;
+        I2C_DEV_CHECK(&dev->i2c_dev, read_reg_nolock(dev, REG_CONFIG, &r));
+        if (!(r & BIT(BIT_CONFIG_RST)))
+            break;
+        vTaskDelay(1);
+    }
+    dev->mode = HDC1000_MEASURE_BOTH;
+
+exit:
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+    return res;
+}
+
+esp_err_t hdc1000_get_serial(hdc1000_t *dev, uint64_t *serial)
+{
+    CHECK_ARG(dev && serial);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    *serial = 0;
+
+    uint16_t *dst = (uint16_t *)serial;
+    I2C_DEV_CHECK(&dev->i2c_dev, read_reg_nolock(dev, REG_SERIAL_L, dst));
+    I2C_DEV_CHECK(&dev->i2c_dev, read_reg_nolock(dev, REG_SERIAL_M, ++dst));
+    I2C_DEV_CHECK(&dev->i2c_dev, read_reg_nolock(dev, REG_SERIAL_H, ++dst));
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t hdc1000_get_manufacturer_id(hdc1000_t *dev, uint16_t *id)
+{
+    CHECK_ARG(dev && id);
+
+    return read_reg(dev, REG_MANUF_ID, id);
+}
+
+esp_err_t hdc1000_get_device_id(hdc1000_t *dev, uint16_t *id)
+{
+    CHECK_ARG(dev && id);
+
+    return read_reg(dev, REG_DEVICE_ID, id);
+}
+
+esp_err_t hdc1000_get_battery_status(hdc1000_t *dev, bool *undervolt)
+{
+    CHECK_ARG(dev && undervolt);
+
+    uint16_t v;
+    CHECK(read_reg(dev, REG_CONFIG, &v));
+    *undervolt = v & BIT(BIT_CONFIG_BTST) ? true : false;
+
+    return ESP_OK;
+}
+
+esp_err_t hdc1000_get_heater(hdc1000_t *dev, bool *on)
+{
+    CHECK_ARG(dev && on);
+
+    uint16_t v;
+    CHECK(read_reg(dev, REG_CONFIG, &v));
+    *on = v & BIT(BIT_CONFIG_HEAT) ? true : false;
+
+    return ESP_OK;
+}
+
+esp_err_t hdc1000_set_heater(hdc1000_t *dev, bool on)
+{
+    CHECK_ARG(dev);
+
+    return update_reg(dev, REG_CONFIG, on ? BIT(BIT_CONFIG_HEAT) : 0, BIT(BIT_CONFIG_HEAT));
+}
+
+esp_err_t hdc1000_set_measurement_mode(hdc1000_t *dev, hdc1000_measurement_mode_t mode)
+{
+    CHECK_ARG(dev && mode <= HDC1000_MEASURE_BOTH);
+
+    dev->mode = mode;
+    return update_reg(dev, REG_CONFIG, mode == HDC1000_MEASURE_BOTH ? BIT(BIT_CONFIG_MODE) : 0, BIT(BIT_CONFIG_MODE));
+}
+
+esp_err_t hdc1000_get_resolution(hdc1000_t *dev, hdc1000_temperature_resolution_t *tres, hdc1000_humidity_resolution_t *hres)
+{
+    CHECK_ARG(dev && (tres || hres));
+
+    uint16_t v;
+    CHECK(read_reg(dev, REG_CONFIG, &v));
+
+    if (tres)
+        *tres = (v >> BIT_CONFIG_TRES) & 1;
+    if (hres)
+        *hres = (v >> BIT_CONFIG_HRES) & 3;
+
+    return ESP_OK;
+}
+
+esp_err_t hdc1000_set_resolution(hdc1000_t *dev, hdc1000_temperature_resolution_t tres, hdc1000_humidity_resolution_t hres)
+{
+    CHECK_ARG(dev && tres <= HDC1000_T_RES_11 && hres <= HDC1000_H_RES_8);
+
+    return update_reg(dev, REG_CONFIG, (tres << BIT_CONFIG_TRES) | (hres << BIT_CONFIG_HRES),
+            BIT(BIT_CONFIG_TRES) | MASK_CONFIG_HRES);
+}
+
+esp_err_t hdc1000_trigger_measurement(hdc1000_t *dev)
+{
+    CHECK_ARG(dev);
+
+    uint8_t r = dev->mode == HDC1000_MEASURE_HUMIDITY ? REG_HUMIDITY : REG_TEMPERATURE;
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_write(&dev->i2c_dev, NULL, 0, &r, 1));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t hdc1000_get_data(hdc1000_t *dev, float *t, float *rh)
+{
+    CHECK_ARG(dev && (t || rh));
+
+    if ((!t && dev->mode == HDC1000_MEASURE_TEMPERATURE)
+            || (!rh && dev->mode == HDC1000_MEASURE_HUMIDITY))
+    {
+        ESP_LOGE(TAG, "Measurement mode does not match hdc1000_get_data() arguments");
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    uint8_t buf[4] = { 0 };
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read(&dev->i2c_dev, NULL, 0, buf, dev->mode == HDC1000_MEASURE_BOTH ? 4 : 2));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    switch (dev->mode)
+    {
+        case HDC1000_MEASURE_TEMPERATURE:
+            *t = calc_temperature(buf);
+            break;
+        case HDC1000_MEASURE_HUMIDITY:
+            *rh = calc_humidity(buf);
+            break;
+        case HDC1000_MEASURE_BOTH:
+            if (t)
+                *t = calc_temperature(buf);
+            if (rh)
+                *rh = calc_humidity(buf + 2);
+            break;
+    }
+
+    return ESP_OK;
+}
+
+esp_err_t hdc1000_measure(hdc1000_t *dev, float *t, float *rh)
+{
+    CHECK(hdc1000_trigger_measurement(dev));
+    vTaskDelay(pdMS_TO_TICKS(dev->mode == HDC1000_MEASURE_BOTH ? 2 * MEASURE_TIMEOUT_MS : MEASURE_TIMEOUT_MS));
+    return hdc1000_get_data(dev, t, rh);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/hdc1000/hdc1000.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,239 @@
+/*
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file hdc1000.h
+ * @defgroup hdc1000 hdc1000
+ * @{
+ *
+ * ESP-IDF driver for HDC1000 temperature and humidity sensor
+ *
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __HDC1000_H__
+#define __HDC1000_H__
+
+#include <i2cdev.h>
+#include <stdbool.h>
+
+#define HDC1000_I2C_ADDRESS_0 0x40 //!< I2C address when ADR1 = 0, ADR0 = 0
+#define HDC1000_I2C_ADDRESS_1 0x41 //!< I2C address when ADR1 = 0, ADR0 = 1
+#define HDC1000_I2C_ADDRESS_2 0x42 //!< I2C address when ADR1 = 1, ADR0 = 0
+#define HDC1000_I2C_ADDRESS_3 0x43 //!< I2C address when ADR1 = 1, ADR0 = 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Temperature resolution
+ */
+typedef enum {
+    HDC1000_T_RES_14 = 0, /**< 14 bits, default */
+    HDC1000_T_RES_11,     /**< 11 bits */
+} hdc1000_temperature_resolution_t;
+
+/**
+ * Humidity resolution
+ */
+typedef enum {
+    HDC1000_H_RES_14 = 0, /**< 14 bits, default */
+    HDC1000_H_RES_11,     /**< 11 bits */
+    HDC1000_H_RES_8,      /**< 8 bits */
+} hdc1000_humidity_resolution_t;
+
+/**
+ * Measurement mode
+ */
+typedef enum {
+    HDC1000_MEASURE_TEMPERATURE = 0, /**< Temperature only */
+    HDC1000_MEASURE_HUMIDITY,        /**< Humidity only */
+    HDC1000_MEASURE_BOTH,            /**< Both temperature and humidity, default */
+} hdc1000_measurement_mode_t;
+
+/**
+ * Device descriptor
+ */
+typedef struct
+{
+    i2c_dev_t i2c_dev;
+    hdc1000_measurement_mode_t mode;
+} hdc1000_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev      Device descriptor
+ * @param addr     Device I2C address
+ * @param port     I2C port
+ * @param sda_gpio SDA GPIO
+ * @param scl_gpio SCL GPIO
+ * @return `ESP_OK` on success
+ */
+esp_err_t hdc1000_init_desc(hdc1000_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t hdc1000_free_desc(hdc1000_t *dev);
+
+/**
+ * @brief Init device
+ *
+ * Soft-reset device, set default measurement mode and resolutions
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t hdc1000_init(hdc1000_t *dev);
+
+/**
+ * @brief Read serial number of device
+ *
+ * @param dev Device descriptor
+ * @param[out] serial Serial number
+ * @return `ESP_OK` on success
+ */
+esp_err_t hdc1000_get_serial(hdc1000_t *dev, uint64_t *serial);
+
+/**
+ * @brief Read manufacturer ID of device
+ *
+ * @param dev Device descriptor
+ * @param[out] id Manufacturer ID
+ * @return `ESP_OK` on success
+ */
+esp_err_t hdc1000_get_manufacturer_id(hdc1000_t *dev, uint16_t *id);
+
+/**
+ * @brief Read device ID
+ *
+ * @param dev Device descriptor
+ * @param[out] id Device ID
+ * @return `ESP_OK` on success
+ */
+esp_err_t hdc1000_get_device_id(hdc1000_t *dev, uint16_t *id);
+
+/**
+ * @brief Read battery status
+ *
+ * @param dev Device descriptor
+ * @param[out] undervolt true when battery voltage is lower than 2.8V
+ * @return `ESP_OK` on success
+ */
+esp_err_t hdc1000_get_battery_status(hdc1000_t *dev, bool *undervolt);
+
+/**
+ * @brief Get heater status
+ *
+ * @param dev Device descriptor
+ * @param[out] on true when heater is enabled
+ * @return `ESP_OK` on success
+ */
+esp_err_t hdc1000_get_heater(hdc1000_t *dev, bool *on);
+
+/**
+ * @brief Switch heater on/off
+ *
+ * @param dev Device descriptor
+ * @param on true to enable heater
+ * @return `ESP_OK` on success
+ */
+esp_err_t hdc1000_set_heater(hdc1000_t *dev, bool on);
+
+/**
+ * @brief Set measurement mode
+ *
+ * @param dev Device descriptor
+ * @param mode Measurement mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t hdc1000_set_measurement_mode(hdc1000_t *dev, hdc1000_measurement_mode_t mode);
+
+/**
+ * @brief Get measurement resolutions
+ *
+ * @param dev Device descriptor
+ * @param[out] tres Temperature measurement resultion
+ * @param[out] hres Humidity measurement resultion
+ * @return `ESP_OK` on success
+ */
+esp_err_t hdc1000_get_resolution(hdc1000_t *dev, hdc1000_temperature_resolution_t *tres, hdc1000_humidity_resolution_t *hres);
+
+/**
+ * @brief Set measurement resolutions
+ *
+ * @param dev Device descriptor
+ * @param tres Temperature measurement resultion
+ * @param hres Humidity measurement resultion
+ * @return `ESP_OK` on success
+ */
+esp_err_t hdc1000_set_resolution(hdc1000_t *dev, hdc1000_temperature_resolution_t tres, hdc1000_humidity_resolution_t hres);
+
+/**
+ * @brief Trigger measurement
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t hdc1000_trigger_measurement(hdc1000_t *dev);
+
+/**
+ * @brief Read output data
+ *
+ * @param dev Device descriptor
+ * @param[out] t Temperature, degrees Celsius (nullable)
+ * @param[out] rh Relative humidity, % (nullable)
+ * @return `ESP_OK` on success
+ */
+esp_err_t hdc1000_get_data(hdc1000_t *dev, float *t, float *rh);
+
+/**
+ * @brief Measure
+ *
+ * Trigger measurement, wait and read output data
+ *
+ * @param dev Device descriptor
+ * @param[out] t Temperature, degrees Celsius (nullable)
+ * @param[out] rh Relative humidity, % (nullable)
+ * @return `ESP_OK` on success
+ */
+esp_err_t hdc1000_measure(hdc1000_t *dev, float *t, float *rh);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __HDC1000_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/hmc5883l/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,24 @@
+---
+components:
+  - name: hmc5883l
+    description: |
+      Driver for 3-axis digital compass HMC5883L and HMC5983L
+    group: magnetic
+    groups: []
+    code_owners:
+      - name: UncleRus
+    depends:
+      - name: i2cdev
+      - name: log
+      - name: esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - name: UncleRus
+        year: 2016
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/hmc5883l/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,11 @@
+if(${IDF_VERSION_MAJOR} STREQUAL 4 AND ${IDF_VERSION_MINOR} STREQUAL 1 AND ${IDF_VERSION_PATCH} STREQUAL 3)
+	set(req i2cdev log esp_idf_lib_helpers)
+else()
+	set(req i2cdev log esp_idf_lib_helpers esp_timer)
+endif()
+
+idf_component_register(
+    SRCS hmc5883l.c
+    INCLUDE_DIRS .
+    REQUIRES ${req}
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/hmc5883l/Kconfig	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,8 @@
+menu "HMC5883L"
+
+config HMC5883L_MEAS_TIMEOUT
+    int "Measurement timeout, microseconds"
+    default 6000
+    range 500 10000
+
+endmenu
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/hmc5883l/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+Copyright 2016, 2018 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/hmc5883l/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/hmc5883l/hmc5883l.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,358 @@
+/*
+ * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file hmc5883l.c
+ *
+ * ESP-IDF Driver for 3-axis digital compass HMC5883L and HMC5983L
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2016, 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#include "hmc5883l.h"
+#include <inttypes.h>
+#include <esp_log.h>
+#include <esp_err.h>
+#include <esp_timer.h>
+#include <esp_idf_lib_helpers.h>
+
+#define REG_CR_A 0x00
+#define REG_CR_B 0x01
+#define REG_MODE 0x02
+#define REG_DX_H 0x03
+#define REG_DX_L 0x04
+#define REG_DZ_H 0x05
+#define REG_DZ_L 0x06
+#define REG_DY_H 0x07
+#define REG_DY_L 0x08
+#define REG_STAT 0x09
+#define REG_ID_A 0x0a
+#define REG_ID_B 0x0b
+#define REG_ID_C 0x0c
+
+#define BIT_MA  5
+#define BIT_DO  2
+#define BIT_GN  5
+
+#define MASK_MD 0x03
+#define MASK_MA 0x60
+#define MASK_DO 0x1c
+#define MASK_MS 0x03
+#define MASK_DR 0x01
+#define MASK_DL 0x02
+
+#define I2C_FREQ_HZ 400000
+
+static const char *TAG = "hmc5883l";
+
+static const float gain_values [] = {
+    [HMC5883L_GAIN_1370] = 0.73,
+    [HMC5883L_GAIN_1090] = 0.92,
+    [HMC5883L_GAIN_820]  = 1.22,
+    [HMC5883L_GAIN_660]  = 1.52,
+    [HMC5883L_GAIN_440]  = 2.27,
+    [HMC5883L_GAIN_390]  = 2.56,
+    [HMC5883L_GAIN_330]  = 3.03,
+    [HMC5883L_GAIN_230]  = 4.35
+};
+
+#define timeout_expired(start, len) ((uint64_t)(esp_timer_get_time() - (start)) >= (len))
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+esp_err_t hmc5883l_init_desc(hmc5883l_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+
+    dev->i2c_dev.port = port;
+    dev->i2c_dev.addr = HMC5883L_ADDR;
+    dev->i2c_dev.cfg.sda_io_num = sda_gpio;
+    dev->i2c_dev.cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+    return i2c_dev_create_mutex(&dev->i2c_dev);
+}
+
+esp_err_t hmc5883l_free_desc(hmc5883l_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(&dev->i2c_dev);
+}
+
+static esp_err_t write_register(hmc5883l_dev_t *dev, uint8_t reg, uint8_t val)
+{
+    esp_err_t ret = i2c_dev_write_reg(&dev->i2c_dev, reg, &val, 1);
+    if (ret != ESP_OK)
+        ESP_LOGE(TAG, "Could not write 0x%02x to register 0x%02x, err = %d", val, reg, ret);
+    return ret;
+}
+
+static inline esp_err_t read_register(hmc5883l_dev_t *dev, uint8_t reg, uint8_t *val)
+{
+    esp_err_t ret = i2c_dev_read_reg(&dev->i2c_dev, reg, val, 1);
+    if (ret != ESP_OK)
+        ESP_LOGE(TAG, "Could not read register 0x%02x, err = %d", reg, ret);
+    return ret;
+}
+
+static esp_err_t update_register(hmc5883l_dev_t *dev, uint8_t reg, uint8_t mask, uint8_t val)
+{
+    uint8_t old;
+    esp_err_t ret = read_register(dev, reg, &old);
+    if (ret != ESP_OK)
+        return ret;
+    return write_register(dev, reg, (old & mask) | val);
+}
+
+#define CHECK(X) do { \
+        esp_err_t __ = X; \
+        if (__ != ESP_OK) return __; \
+    } while (0)
+
+esp_err_t hmc5883l_init(hmc5883l_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    uint32_t id = 0;
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, REG_ID_A, &id, 3));
+    if (id != HMC5883L_ID)
+    {
+        I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+        ESP_LOGE(TAG, "Unknown ID: 0x%08" PRIx32 " != 0x%08x", id, HMC5883L_ID);
+        return ESP_ERR_NOT_FOUND;
+    }
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    hmc5883l_gain_t gain;
+    CHECK(hmc5883l_get_gain(dev, &gain));
+    dev->gain = gain_values[gain];
+
+    CHECK(hmc5883l_get_opmode(dev, &dev->opmode));
+
+    return ESP_OK;
+}
+
+esp_err_t hmc5883l_get_opmode(hmc5883l_dev_t *dev, hmc5883l_opmode_t *val)
+{
+    CHECK_ARG(dev && val);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, REG_MODE, (uint8_t *)val));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    *val = (*val & MASK_MD) == 0 ? HMC5883L_MODE_CONTINUOUS : HMC5883L_MODE_SINGLE;
+    return ESP_OK;
+}
+
+esp_err_t hmc5883l_set_opmode(hmc5883l_dev_t *dev, hmc5883l_opmode_t mode)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, REG_MODE, mode));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    dev->opmode = mode;
+    return ESP_OK;
+}
+
+esp_err_t hmc5883l_get_samples_averaged(hmc5883l_dev_t *dev, hmc5883l_samples_averaged_t *val)
+{
+    CHECK_ARG(dev && val);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, REG_CR_A, (uint8_t *)val));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    *val = (*val & MASK_MA) >> BIT_MA;
+    return ESP_OK;
+}
+
+esp_err_t hmc5883l_set_samples_averaged(hmc5883l_dev_t *dev, hmc5883l_samples_averaged_t samples)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, update_register(dev, REG_CR_A, MASK_MA, samples << BIT_MA));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t hmc5883l_get_data_rate(hmc5883l_dev_t *dev, hmc5883l_data_rate_t *val)
+{
+    CHECK_ARG(dev && val);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, REG_CR_A, (uint8_t *)val));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    *val = (*val & MASK_DO) >> BIT_DO;
+    return ESP_OK;
+}
+
+esp_err_t hmc5883l_set_data_rate(hmc5883l_dev_t *dev, hmc5883l_data_rate_t rate)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, update_register(dev, REG_CR_A, MASK_DO, rate << BIT_DO));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t hmc5883l_get_bias(hmc5883l_dev_t *dev, hmc5883l_bias_t *val)
+{
+    CHECK_ARG(dev && val);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, REG_CR_A, (uint8_t *)val));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    *val &= MASK_MS;
+    return ESP_OK;
+}
+
+esp_err_t hmc5883l_set_bias(hmc5883l_dev_t *dev, hmc5883l_bias_t bias)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, update_register(dev, REG_CR_A, MASK_MS, bias));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t hmc5883l_get_gain(hmc5883l_dev_t *dev, hmc5883l_gain_t *val)
+{
+    CHECK_ARG(dev && val);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, REG_CR_B, (uint8_t *)val));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    *val >>= BIT_GN;
+    return ESP_OK;
+}
+
+esp_err_t hmc5883l_set_gain(hmc5883l_dev_t *dev, hmc5883l_gain_t gain)
+{
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, REG_CR_B, gain << BIT_GN));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    dev->gain = gain_values[gain];
+    return ESP_OK;
+}
+
+esp_err_t hmc5883l_data_is_locked(hmc5883l_dev_t *dev, bool *val)
+{
+    CHECK_ARG(dev && val);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, REG_STAT, (uint8_t *)val));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    *val &= MASK_DL;
+    return ESP_OK;
+}
+
+esp_err_t hmc5883l_data_is_ready(hmc5883l_dev_t *dev, bool *val)
+{
+    CHECK_ARG(dev && val);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, REG_STAT, (uint8_t *)val));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    *val &= MASK_DR;
+    return ESP_OK;
+}
+
+esp_err_t hmc5883l_get_raw_data(hmc5883l_dev_t *dev, hmc5883l_raw_data_t *data)
+{
+    CHECK_ARG(dev && data);
+
+    if (dev->opmode == HMC5883L_MODE_SINGLE)
+    {
+        // overwrite mode register for measurement
+        CHECK(hmc5883l_set_opmode(dev, dev->opmode));
+        // wait for data
+        uint64_t start = esp_timer_get_time();
+        bool dready = false;
+        do
+        {
+            CHECK(hmc5883l_data_is_ready(dev, &dready));
+            if (timeout_expired(start, CONFIG_HMC5883L_MEAS_TIMEOUT))
+                return ESP_ERR_TIMEOUT;
+        } while (!dready);
+    }
+    uint8_t buf[6];
+    uint8_t reg = REG_DX_H;
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, reg, buf, 6));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    data->x = ((int16_t)buf[REG_DX_H - REG_DX_H] << 8) | buf[REG_DX_L - REG_DX_H];
+    data->y = ((int16_t)buf[REG_DY_H - REG_DX_H] << 8) | buf[REG_DY_L - REG_DX_H];
+    data->z = ((int16_t)buf[REG_DZ_H - REG_DX_H] << 8) | buf[REG_DZ_L - REG_DX_H];
+
+    return ESP_OK;
+}
+
+esp_err_t hmc5883l_raw_to_mg(const hmc5883l_dev_t *dev, const hmc5883l_raw_data_t *raw, hmc5883l_data_t *mg)
+{
+    CHECK_ARG(dev && raw && mg);
+
+    mg->x = raw->x * dev->gain;
+    mg->y = raw->y * dev->gain;
+    mg->z = raw->z * dev->gain;
+
+    return ESP_OK;
+}
+
+esp_err_t hmc5883l_get_data(hmc5883l_dev_t *dev, hmc5883l_data_t *data)
+{
+    CHECK_ARG(data);
+
+    hmc5883l_raw_data_t raw;
+
+    CHECK(hmc5883l_get_raw_data(dev, &raw));
+    CHECK(hmc5883l_raw_to_mg(dev, &raw, data));
+
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/hmc5883l/hmc5883l.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,325 @@
+/*
+ * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file hmc5883l.h
+ * @defgroup hmc5883l hmc5883l
+ * @{
+ *
+ * ESP-IDF Driver for 3-axis digital compass HMC5883L and HMC5983L
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __HMC5883L_H__
+#define __HMC5883L_H__
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define HMC5883L_ADDR 0x1e //!< I2C address
+
+#define HMC5883L_ID 0x00333448  //!< Chip ID, "H43"
+
+/**
+ * Device operating mode
+ */
+typedef enum
+{
+    HMC5883L_MODE_CONTINUOUS = 0, //!< Continuous mode
+    HMC5883L_MODE_SINGLE          //!< Single measurement mode, default
+} hmc5883l_opmode_t;
+
+/**
+ * Number of samples averaged per measurement
+ */
+typedef enum
+{
+    HMC5883L_SAMPLES_1 = 0, //!< 1 sample, default
+    HMC5883L_SAMPLES_2,     //!< 2 samples
+    HMC5883L_SAMPLES_4,     //!< 4 samples
+    HMC5883L_SAMPLES_8      //!< 8 samples
+} hmc5883l_samples_averaged_t;
+
+/**
+ * Data output rate in continuous measurement mode
+ */
+typedef enum
+{
+    HMC5883L_DATA_RATE_00_75 = 0, //!< 0.75 Hz
+    HMC5883L_DATA_RATE_01_50,     //!< 1.5 Hz
+    HMC5883L_DATA_RATE_03_00,     //!< 3 Hz
+    HMC5883L_DATA_RATE_07_50,     //!< 7.5 Hz
+    HMC5883L_DATA_RATE_15_00,     //!< 15 Hz, default
+    HMC5883L_DATA_RATE_30_00,     //!< 30 Hz
+    HMC5883L_DATA_RATE_75_00,     //!< 75 Hz
+    HMC5883L_DATA_RATE_220_00     //!< 220 Hz, HMC5983 only
+} hmc5883l_data_rate_t;
+
+/**
+ * Measurement mode of the device (bias)
+ */
+typedef enum
+{
+    HMC5883L_BIAS_NORMAL = 0, //!< Default flow, no bias
+    HMC5883L_BIAS_POSITIVE,   //!< Positive bias configuration all axes, used for self test (see datasheet)
+    HMC5883L_BIAS_NEGATIVE    //!< Negative bias configuration all axes, used for self test (see datasheet)
+} hmc5883l_bias_t;
+
+/**
+ * Device gain
+ */
+typedef enum
+{
+    HMC5883L_GAIN_1370 = 0, //!< 0.73 mG/LSb, range -0.88..+0.88 G
+    HMC5883L_GAIN_1090,     //!< 0.92 mG/LSb, range -1.3..+1.3 G, default
+    HMC5883L_GAIN_820,      //!< 1.22 mG/LSb, range -1.9..+1.9 G
+    HMC5883L_GAIN_660,      //!< 1.52 mG/LSb, range -2.5..+2.5 G
+    HMC5883L_GAIN_440,      //!< 2.27 mG/LSb, range -4.0..+4.0 G
+    HMC5883L_GAIN_390,      //!< 2.56 mG/LSb, range -4.7..+4.7 G
+    HMC5883L_GAIN_330,      //!< 3.03 mG/LSb, range -5.6..+5.6 G
+    HMC5883L_GAIN_230       //!< 4.35 mG/LSb, range -8.1..+8.1 G
+} hmc5883l_gain_t;
+
+/**
+ * Device descriptor
+ */
+typedef struct
+{
+    i2c_dev_t i2c_dev;        //!< I2C device descriptor
+    hmc5883l_opmode_t opmode; //!< Operating mode
+    float gain;               //!< Gain
+} hmc5883l_dev_t;
+
+/**
+ * Raw measurement result
+ */
+typedef struct
+{
+    int16_t x;
+    int16_t y;
+    int16_t z;
+} hmc5883l_raw_data_t;
+
+/**
+ * Measurement result, milligauss
+ */
+typedef struct
+{
+    float x;
+    float y;
+    float z;
+} hmc5883l_data_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev Device descriptor
+ * @param port I2C port number
+ * @param sda_gpio GPIO pin number for SDA
+ * @param scl_gpio GPIO pin number for SCL
+ * @return `ESP_OK` on success
+ */
+esp_err_t hmc5883l_init_desc(hmc5883l_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t hmc5883l_free_desc(hmc5883l_dev_t *dev);
+
+/**
+ * @brief Initialize device
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t hmc5883l_init(hmc5883l_dev_t *dev);
+
+/**
+ * @brief Get operating mode
+ *
+ * @param dev Device descriptor
+ * @param[out] val Operating mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t hmc5883l_get_opmode(hmc5883l_dev_t *dev, hmc5883l_opmode_t *val);
+
+/**
+ * @brief Set operating mode
+ *
+ * @param dev Device descriptor
+ * @param mode Operating mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t hmc5883l_set_opmode(hmc5883l_dev_t *dev, hmc5883l_opmode_t mode);
+
+/**
+ * @brief Get number of samples averaged per measurement output
+ *
+ * @param dev Device descriptor
+ * @param[out] val Number of samples
+ * @return `ESP_OK` on success
+ */
+esp_err_t hmc5883l_get_samples_averaged(hmc5883l_dev_t *dev, hmc5883l_samples_averaged_t *val);
+
+/**
+ * @brief Set number of samples averaged per measurement output
+ *
+ * @param dev Device descriptor
+ * @param samples Number of samples
+ * @return `ESP_OK` on success
+ */
+esp_err_t hmc5883l_set_samples_averaged(hmc5883l_dev_t *dev, hmc5883l_samples_averaged_t samples);
+
+/**
+ * @brief Get data output rate in continuous measurement mode
+ *
+ * @param dev Device descriptor
+ * @param[out] val Data output rate
+ * @return `ESP_OK` on success
+ */
+esp_err_t hmc5883l_get_data_rate(hmc5883l_dev_t *dev, hmc5883l_data_rate_t *val);
+
+/**
+ * @brief Set data output rate in continuous measurement mode
+ *
+ * @param dev Device descriptor
+ * @param rate Data output rate
+ * @return `ESP_OK` on success
+ */
+esp_err_t hmc5883l_set_data_rate(hmc5883l_dev_t *dev, hmc5883l_data_rate_t rate);
+
+/**
+ * @brief Get measurement mode (bias of the axes)
+ *
+ * See datasheet for self test description.
+ *
+ * @param dev Device descriptor
+ * @param[out] val Bias
+ * @return `ESP_OK` on success
+ */
+esp_err_t hmc5883l_get_bias(hmc5883l_dev_t *dev, hmc5883l_bias_t *val);
+
+/**
+ * @brief Set measurement mode (bias of the axes)
+ *
+ * See datasheet for self test description.
+ *
+ * @param dev Device descriptor
+ * @param bias Bias
+ * @return `ESP_OK` on success
+ */
+esp_err_t hmc5883l_set_bias(hmc5883l_dev_t *dev, hmc5883l_bias_t bias);
+
+/**
+ * @brief Get device gain
+ *
+ * @param dev Device descriptor
+ * @param[out] val Current gain
+ * @return `ESP_OK` on success
+ */
+esp_err_t hmc5883l_get_gain(hmc5883l_dev_t *dev, hmc5883l_gain_t *val);
+
+/**
+ * @brief Set device gain
+ * @param dev Device descriptor
+ * @param gain Gain
+ * @return `ESP_OK` on success
+ */
+esp_err_t hmc5883l_set_gain(hmc5883l_dev_t *dev, hmc5883l_gain_t gain);
+
+/**
+ * @brief Get data state
+ *
+ * @param dev Device descriptor
+ * @param[out] val true when data is written to all six data registers
+ * @return `ESP_OK` on success
+ */
+esp_err_t hmc5883l_data_is_ready(hmc5883l_dev_t *dev, bool *val);
+
+/**
+ * @brief Get lock state
+ *
+ * If data is locked, any new data will not be placed in data registers until
+ * one of these conditions are met:
+ *  - data have been read,
+ *  - operating mode is changed,
+ *  - the measurement configuration (bias) is changed,
+ *  - power is reset.
+ *
+ * @param dev Device descriptor
+ * @param[out] val true when data registers is locked
+ * @return `ESP_OK` on success
+ */
+esp_err_t hmc5883l_data_is_locked(hmc5883l_dev_t *dev, bool *val);
+
+/**
+ * @brief Get raw magnetic data
+ *
+ * @param dev Device descriptor
+ * @param[out] data Raw data
+ * @return `ESP_OK` on success
+ */
+esp_err_t hmc5883l_get_raw_data(hmc5883l_dev_t *dev, hmc5883l_raw_data_t *data);
+
+/**
+ * @brief Convert raw magnetic data to milligausses
+ *
+ * @param dev Device descriptor
+ * @param raw Source raw data
+ * @param[out] mg Converted data
+ */
+esp_err_t hmc5883l_raw_to_mg(const hmc5883l_dev_t *dev, const hmc5883l_raw_data_t *raw, hmc5883l_data_t *mg);
+
+/**
+ * @brief Get magnetic data in milligausses
+ *
+ * @param dev Device descriptor
+ * @param[out] data Magnetic data
+ * @return `ESP_OK` on success
+ */
+esp_err_t hmc5883l_get_data(hmc5883l_dev_t *dev, hmc5883l_data_t *data);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __HMC5883L_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ht16k33/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,22 @@
+---
+components:
+  - name: ht16k33
+    description: HT16K33 LED controller driver
+    group: led
+    groups: []
+    code_owners: chudsaviet
+    depends:
+      - name: i2cdev
+      - name: log
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: MIT
+    copyrights:
+      - author:
+          name: chudsaviet
+        year: 2022
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ht16k33/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS ht16k33.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ht16k33/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,13 @@
+Copyright (c) 2022 Timofei Korostelev <timofei_public@dranik.dev>
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ht16k33/README.md	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,19 @@
+# HT16K33 driver
+
+Driver for Holtek HT16K33 LED Controller.
+
+## Usage
+
+See the datasheet at https://www.holtek.com/documents/10179/116711/HT16K33v120.pdf .
+Chip RAM is 16-byte memory where each bit represents idividual pixel.
+It is possbile to change I2C address using jumpers, see the datasheet.
+
+*  Init descriptor with `ht16k33_init_desc()`.
+*  Init device with `ht16k33_init()`.
+*  Optionally, set up brightness using `ht16k33_set_brightness()`.
+*  Turn display on and set blinking mode using `ht16k33_display_setup()`. Unfortunately, its a single command in HT16K33.
+*  Write RAM state to set individual pixels using `ht16k33_ram_write()`.
+*  At the end, deinitialize using `ht16k33_free_desc()`.
+
+See the example at `examples/ht16k33`.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ht16k33/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ht16k33/ht16k33.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,217 @@
+/*
+ * Copyright (c) 2022 Timofei Korostelev <timofei_public@dranik.dev>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include "ht16k33.h"
+
+#include <string.h>
+#include <freertos/FreeRTOS.h>
+#include <esp_err.h>
+#include <esp_log.h>
+#include <esp_idf_lib_helpers.h>
+
+#if HELPER_TRAGET_IS_ESP32 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)
+#include <esp_check.h>
+#endif
+
+static const char* TAG = "ht16k33";
+
+#define HT16K33_I2C_FREQ_HZ 400000 // 400kHz
+
+// All commands are one byte long.
+// First 4 bits are command type.
+// Last 4 bits are parameters.
+#define HT16K33_CMD_RAM_SET_POINTER 0b0000
+#define HT16K33_CMD_SYSTEM_SETUP    0b0010
+#define HT16K33_CMD_SET_BRIGHTNESS  0b1110
+#define HT16K33_CMD_DISPLAY_SETUP   0b1000
+#define HT16K33_CMD_ROW_INT_SET     0b1010
+
+#define HT16K33_SET_RAM_CMD_SIZE_BYTES (HT16K33_RAM_SIZE_BYTES + 1)
+
+#define HT16K33_BLINKING_0HZ  0b00
+#define HT16K33_BLINKING_2HZ  0b01
+#define HT16K33_BLINKING_1HZ  0b10
+#define HT16K33_BLINKING_05HZ 0b11
+
+#define CHECK_ARG(VAL)                  \
+    do {                                \
+        if (!(VAL))                     \
+            return ESP_ERR_INVALID_ARG; \
+    } while (0);
+
+#ifndef ESP_RETURN_ON_ERROR
+#define ESP_RETURN_ON_ERROR(x, log_tag, format, ...) do {                                \
+        esp_err_t err_rc_ = (x);                                                         \
+        if (err_rc_ != ESP_OK) {                                                         \
+            ESP_LOGE(log_tag, "%s(%d): " format, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
+            return err_rc_;                                                              \
+        }                                                                                \
+    } while(0)
+#endif
+
+static uint8_t ht16k33_blinking_freq_t_to_bits(ht16k33_blinking_freq_t freq)
+{
+    uint8_t result = 0;
+    switch (freq) {
+    case HTK16K33_F_0HZ:
+        result = HT16K33_BLINKING_0HZ;
+        break;
+    case HTK16K33_F_2HZ:
+        result = HT16K33_BLINKING_1HZ;
+        break;
+    case HTK16K33_F_1HZ:
+        result = HT16K33_BLINKING_1HZ;
+        break;
+    case HTK16K33_F_05HZ:
+        result = HT16K33_BLINKING_05HZ;
+        break;
+    }
+    return result;
+}
+
+static esp_err_t zero_ram(i2c_dev_t* dev)
+{
+    CHECK_ARG(dev);
+
+    uint8_t all_zeros[HT16K33_RAM_SIZE_BYTES];
+    memset(all_zeros, 0, HT16K33_RAM_SIZE_BYTES);
+
+    return ht16k33_ram_write(dev, all_zeros);
+}
+
+// Send one-byte command.
+static esp_err_t send_short_cmd(i2c_dev_t* dev, uint8_t cmd)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_write(dev, NULL, 0, &cmd, sizeof(uint8_t)));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t ht16k33_init_desc(i2c_dev_t* dev, i2c_port_t port,
+    gpio_num_t sda_gpio, gpio_num_t scl_gpio,
+    uint8_t addr)
+{
+    CHECK_ARG(dev);
+
+    memset(dev, 0, sizeof(i2c_dev_t));
+    dev->port = port;
+    dev->addr = addr;
+    dev->cfg.sda_io_num = sda_gpio;
+    dev->cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->cfg.master.clk_speed = HT16K33_I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(dev);
+}
+
+esp_err_t ht16k33_init(i2c_dev_t* dev)
+{
+    CHECK_ARG(dev);
+
+    // Enable system oscillator.
+    ESP_RETURN_ON_ERROR(
+        send_short_cmd(dev, HT16K33_CMD_SYSTEM_SETUP << 4 | 0b0001), TAG,
+        "Can't start oscillator.");
+
+    // Set display off, no blinking.
+    ESP_RETURN_ON_ERROR(ht16k33_display_setup(dev, 0, HTK16K33_F_0HZ), TAG,
+        "Can't set display off, no blinking.");
+
+    // Set to ROW output.
+    ESP_RETURN_ON_ERROR(
+        send_short_cmd(dev, HT16K33_CMD_ROW_INT_SET << 4 | 0b0000), TAG,
+        "Can't set to ROW output");
+
+    // Set half brightness.
+    ESP_RETURN_ON_ERROR(ht16k33_set_brightness(dev, HT16K33_MAX_BRIGHTNESS / 2),
+        TAG, "Can't set initial brightness.");
+
+    // RAM initializes with random values. Zero it.
+    ESP_RETURN_ON_ERROR(zero_ram(dev), TAG, "Can't zero the RAM.");
+
+    return ESP_OK;
+}
+
+esp_err_t ht16k33_free_desc(i2c_dev_t* dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(dev);
+}
+
+esp_err_t ht16k33_set_brightness(i2c_dev_t* dev, uint8_t brightness)
+{
+    CHECK_ARG(dev);
+
+    if (brightness > 15) {
+        ESP_LOGE(TAG, "Brightness shall be set in range 0-15.");
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    ESP_RETURN_ON_ERROR(
+        send_short_cmd(dev, HT16K33_CMD_SET_BRIGHTNESS << 4 | brightness), TAG,
+        "Can't set brightness.");
+
+    return ESP_OK;
+}
+
+esp_err_t ht16k33_display_setup(i2c_dev_t* dev, uint8_t on_flag,
+    ht16k33_blinking_freq_t blinking)
+{
+    CHECK_ARG(dev);
+
+    // on_flag is 1 bit.
+    if (on_flag > 0b1) {
+        ESP_LOGE(TAG, "on_flag can only be 0 or 1.");
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    // Blinking mode is just 2 bits.
+    if (blinking > 0b11) {
+        ESP_LOGE(TAG, "Use only HTK16K33_F_* constants.");
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    uint8_t blinking_bits = ht16k33_blinking_freq_t_to_bits(blinking);
+
+    ESP_RETURN_ON_ERROR(send_short_cmd(dev, HT16K33_CMD_DISPLAY_SETUP << 4 | blinking_bits << 1 | on_flag),
+        TAG, "Can't do display setup.");
+
+    return ESP_OK;
+}
+
+esp_err_t ht16k33_ram_write(i2c_dev_t* dev, uint8_t* data)
+{
+    CHECK_ARG(dev);
+
+    // First byte is pointer set command.
+    // Other bytes are the RAM values.
+    uint8_t cmd_seq[HT16K33_SET_RAM_CMD_SIZE_BYTES];
+    // Set write pointer to position 0x00.
+    cmd_seq[0] = HT16K33_CMD_RAM_SET_POINTER << 4 | 0b0000;
+    memcpy(cmd_seq + 1, data, HT16K33_RAM_SIZE_BYTES);
+
+    // Send whole command sequence at once.
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_write(dev, NULL, 0, cmd_seq, HT16K33_SET_RAM_CMD_SIZE_BYTES));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ht16k33/ht16k33.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2022 Timofei Korostelev <timofei_public@dranik.dev>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/**
+ * @file ht16k33.h
+ * @defgroup ht16k33 ht16k33
+ * @{
+ *
+ * Holtek HT16K33 LED Controller driver.
+ * No keyscan features implemented.
+ * Tip: PWM brightness frequency depends on I2C clock.
+ *
+ * Manufacturer link: https://www.holtek.com/productdetail/-/vg/HT16K33
+ * Datasheet: https://www.holtek.com/documents/10179/116711/HT16K33v120.pdf
+ *
+ */
+
+#if !defined(__HT16K33_H__)
+#define __HT16K33_H__
+
+#include <driver/gpio.h>
+#include <esp_err.h>
+#include <i2cdev.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Default I2C address
+ */
+#define HT16K33_DEFAULT_ADDR 0x70
+#define HT16K33_MAX_BRIGHTNESS 15
+#define HT16K33_RAM_SIZE_BYTES 16
+
+/**
+ * Display blinking frequencies.
+ */
+typedef enum { 
+    HTK16K33_F_0HZ,
+    HTK16K33_F_2HZ,
+    HTK16K33_F_1HZ,
+    HTK16K33_F_05HZ,
+} ht16k33_blinking_freq_t;
+
+/**
+ * @brief Initialize the HT16K33 device descriptor.
+ *
+ * @param[out] dev Device descriptor
+ * @param port     I2C port number
+ * @param sda_gpio GPIO pin number for SDA
+ * @param scl_gpio GPIO pin number for SCL
+ * @param addr     I2C address
+ * @return `ESP_OK` on success
+ */
+esp_err_t ht16k33_init_desc(i2c_dev_t *dev, i2c_port_t port,
+                            gpio_num_t sda_gpio, gpio_num_t scl_gpio,
+                            uint8_t addr);
+
+/**
+ * @brief Initialize HT16K33 device, reset all settings and zero chip RAM.
+ *
+ * @return ESP_OK in case of success
+ */
+esp_err_t ht16k33_init(i2c_dev_t *dev);
+
+/**
+ * @brief Free device descriptor.
+ *
+ * @param dev I2C device descriptor
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ht16k33_free_desc(i2c_dev_t *dev);
+
+/**
+ * @brief Set brightness.
+ *
+ * @param dev I2C device descriptor
+ * @param brightness Brighness value in 0-15 range.
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ht16k33_set_brightness(i2c_dev_t *dev, uint8_t brightness);
+
+/**
+ * @brief Display setup. ON/OFF and blinkng frequency.
+ *
+ * @param dev I2C device descriptor
+ * @param on_flag On flag, 0 or 1.
+ * @param blinking Blinking frequence. Limited options. See HT16K33_BLINKING_*
+ * constants.
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ht16k33_display_setup(i2c_dev_t *dev, uint8_t on_flag,
+                                ht16k33_blinking_freq_t blinking);
+
+/**
+ * @brief Write whole HT16K33_RAM_SIZE_BYTES into RAM.
+ *
+ * @param data Bytes to write.
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ht16k33_ram_write(i2c_dev_t *dev, uint8_t *data);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif // __HT16K33__H__
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/hts221/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,24 @@
+---
+components:
+  - name: hts221
+    description: |
+      Driver for HTS221 temperature and humidity sensor.
+    group: temperature
+    groups:
+      - humidity
+    code_owners:
+      -name: saasaa
+    depends:
+      - name: i2cdev
+      - name: log
+      - name: esp_idf_lib_helpers
+    thread_safe: N/A
+    targets:
+      - name: esp32
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: ISC
+    copyrights:
+      - name: saasaa
+        year: 2021
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/hts221/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,11 @@
+if(${IDF_VERSION_MAJOR} STREQUAL 4 AND ${IDF_VERSION_MINOR} STREQUAL 1 AND ${IDF_VERSION_PATCH} STREQUAL 3)
+	set(req i2cdev log esp_idf_lib_helpers)
+else()
+	set(req i2cdev log esp_idf_lib_helpers esp_timer)
+endif()
+
+idf_component_register(
+    SRCS hts221.c
+    INCLUDE_DIRS .
+    REQUIRES ${req}
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/hts221/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,13 @@
+Copyright (c) 2021 saasaa <mail@saasaa.xyz>
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/hts221/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/hts221/hts221.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,400 @@
+/*
+ * Copyright (c) 2021 saasaa <mail@saasaa.xyz>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/**
+ * @file hts221.c
+ * @brief HTS221 driver
+ * @author saasaa
+ *
+ * ESP-IDF driver for HTS221 temperature and humidity sensor
+ *
+ * Datasheet: http://www.st.com/resource/en/datasheet/hts221.pdf
+ * Technical note on interpreting humidity and temperature readings in the
+ * HTS221 digital humidity sensor:
+ * https://www.st.com/resource/en/technical_note/tn1218-interpreting-humidity-and-temperature-readings-in-the-hts221-digital-humidity-sensor-stmicroelectronics.pdf
+ * Copyright (c) 2021 saasaa <mail@saasaa.xyz>
+ *
+ * ISC Licensed as described in the file LICENSE
+ *
+ */
+#include "hts221.h"
+
+#include <esp_err.h>
+#include <esp_log.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <i2cdev.h>
+#include <esp_timer.h>
+
+#define I2C_FREQ_HZ 400000 // 400kHz
+
+#define REG_WHOAMI 0x0F         // R   Device identification register
+#define REG_AV_CONF 0x10        // R/W Humidity and temperature resolution mode
+#define REG_CTRL_REG1 0x20      // R/W Control register 1
+#define REG_CTRL_REG2 0x21      // R/W Control register 2
+#define REG_CTRL_REG3 0x22      // R/W Control register 3 for data ready output signal
+#define REG_STATUS_REG 0x27     // R   Status register
+#define REG_HUMIDITY_OUT_L 0x28 // R   Relative humidity output register (LSB)
+#define REG_HUMIDITY_OUT_H 0x29 // R   Relative humidity output register (MSB)
+#define REG_TEMP_OUT_L 0x2A     // R   Temperature output register (LSB)
+#define REG_TEMP_OUT_H 0x2B     // R   Temperature output register (MSB)
+#define REG_CALIB_START 0x30    // R/W Calibration start register
+
+#define BIT_AV_CONF_AVGT0      3
+#define BIT_AV_CONF_AVGH0      0
+
+#define BIT_CTRL_REG1_PD       7
+#define BIT_CTRL_REG1_BDU      2
+#define BIT_CTRL_REG1_ODR      0
+
+#define BIT_CTRL_REG2_BOOT     7
+#define BIT_CTRL_REG2_HEATER   1
+#define BIT_CTRL_REG2_ONE_SHOT 7
+
+#define BIT_CTRL_REG3_DRDY_H_L 7
+#define BIT_CTRL_REG3_PP_OD    6
+#define BIT_CTRL_REG3_DRDY     2
+
+#define BIT_STATUS_REG_H_DA    1
+#define BIT_STATUS_REG_T_DA    0
+
+#define MASK_AV_CONF_AVG   0x07
+#define MASK_CTRL_REG1_ODR 0x03
+#define MASK_STATUS_REG_DA 0x03
+
+#define WHOAMI 0xbc
+#define REBOOT_TIMEOUT_US (100 * 1000)
+
+#define HTS221_AV_CONF_DEFAULT 0x1B // DEFAULT AV_CONF status register value according to datasheet
+
+#define CHECK(x)                                                                                                       \
+    do                                                                                                                 \
+    {                                                                                                                  \
+        esp_err_t __;                                                                                                  \
+        if ((__ = x) != ESP_OK)                                                                                        \
+            return __;                                                                                                 \
+    } while (0)
+
+#define CHECK_ARG(VAL)                                                                                                 \
+    do                                                                                                                 \
+    {                                                                                                                  \
+        if (!(VAL))                                                                                                    \
+            return ESP_ERR_INVALID_ARG;                                                                                \
+    } while (0)
+
+static const char *TAG = "hts221";
+
+static esp_err_t write_reg(hts221_t *dev, uint8_t reg, uint8_t val)
+{
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, reg, &val, 1));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+static esp_err_t read_reg(hts221_t *dev, uint8_t reg, uint8_t *val)
+{
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, reg, val, 1));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+static esp_err_t update_reg(hts221_t *dev, uint8_t reg, uint8_t val, uint8_t mask)
+{
+    uint8_t b;
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, reg, &b, 1));
+    b = (b & mask) | val;
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_write_reg(&dev->i2c_dev, reg, &b, 1));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+inline static esp_err_t read_calibration_coeff(hts221_t *dev)
+{
+    uint8_t T0_T1_msb;
+    uint8_t H0_rH_x2;
+    uint8_t H1_rH_x2;
+    uint8_t T0_degC_x8_lsb;
+    uint8_t T1_degC_x8_lsb;
+    uint8_t H0_T0_OUT_lsb;
+    uint8_t H0_T0_OUT_msb;
+    uint8_t H1_T0_OUT_lsb;
+    uint8_t H1_T0_OUT_msb;
+    uint8_t T0_OUT_lsb;
+    uint8_t T0_OUT_msb;
+    uint8_t T1_OUT_lsb;
+    uint8_t T1_OUT_msb;
+
+    // TODO: replace with block reading
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, 0x30, &H0_rH_x2, 1));
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, 0x31, &H1_rH_x2, 1));
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, 0x32, &T0_degC_x8_lsb, 1));
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, 0x33, &T1_degC_x8_lsb, 1));
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, 0x35, &T0_T1_msb, 1));
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, 0x36, &H0_T0_OUT_lsb, 1));
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, 0x37, &H0_T0_OUT_msb, 1));
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, 0x3A, &H1_T0_OUT_lsb, 1));
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, 0x3B, &H1_T0_OUT_msb, 1));
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, 0x3C, &T0_OUT_lsb, 1));
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, 0x3D, &T0_OUT_msb, 1));
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, 0x3E, &T1_OUT_lsb, 1));
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, 0x3F, &T1_OUT_msb, 1));
+
+    dev->cal.H0_rH = (H0_rH_x2 >> 1);
+    dev->cal.H1_rH = (H1_rH_x2 >> 1);
+    dev->cal.T0_degC = ((T0_T1_msb & 0x03) << 8 | T0_degC_x8_lsb) >> 3;
+    dev->cal.T1_degC = ((T0_T1_msb & 0x0C) << 6 | T1_degC_x8_lsb) >> 3;
+    dev->cal.H0_T0_OUT = (H0_T0_OUT_msb << 8) | H0_T0_OUT_lsb;
+    dev->cal.H1_T0_OUT = (H1_T0_OUT_msb << 8) | H1_T0_OUT_lsb;
+    dev->cal.T0_OUT = (T0_OUT_msb << 8) | T0_OUT_lsb;
+    dev->cal.T1_OUT = (T1_OUT_msb << 8) | T1_OUT_lsb;
+
+    ESP_LOGD(TAG, "Read calibration data");
+
+    return ESP_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t hts221_init_desc(hts221_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+
+    dev->i2c_dev.port = port;
+    dev->i2c_dev.addr = HTS221_I2C_ADDRESS;
+    dev->i2c_dev.cfg.sda_io_num = sda_gpio;
+    dev->i2c_dev.cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+    return i2c_dev_create_mutex(&dev->i2c_dev);
+}
+
+esp_err_t hts221_free_desc(hts221_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(&dev->i2c_dev);
+}
+
+esp_err_t hts221_init(hts221_t *dev)
+{
+    CHECK_ARG(dev);
+
+    esp_err_t res = ESP_OK;
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    uint8_t b;
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, REG_WHOAMI, &b, 1));
+    if (b != WHOAMI)
+    {
+        ESP_LOGE(TAG, "Invalid WHOAMI value: 0x%02x", b);
+        res = ESP_ERR_INVALID_RESPONSE;
+        goto exit;
+    }
+
+    // Reboot
+    b = BIT(BIT_CTRL_REG2_BOOT);
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_write_reg(&dev->i2c_dev, REG_CTRL_REG2, &b, 1));
+    // Wait
+    int64_t start = esp_timer_get_time();
+    while (true)
+    {
+        if ((esp_timer_get_time() - start) >= REBOOT_TIMEOUT_US)
+        {
+            ESP_LOGE(TAG, "Timeout while rebooting device");
+            res = ESP_ERR_TIMEOUT;
+            goto exit;
+        }
+        I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, REG_CTRL_REG2, &b, 1));
+        if (!(b & BIT(BIT_CTRL_REG2_BOOT)))
+            break;
+        vTaskDelay(1);
+    }
+
+    // Read calibration data
+    I2C_DEV_CHECK(&dev->i2c_dev, read_calibration_coeff(dev));
+
+    // Enable BDU, switch to active mode, DATA RATE = one shot
+    b = BIT(BIT_CTRL_REG1_PD) | BIT(BIT_CTRL_REG1_BDU);
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_write_reg(&dev->i2c_dev, REG_CTRL_REG2, &b, 1));
+
+    // DRDY mode = Push-pull, DRDY active level = HIGH
+    b = 0;
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_write_reg(&dev->i2c_dev, REG_CTRL_REG3, &b, 1));
+
+exit:
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+    return res;
+}
+
+esp_err_t hts221_get_power_mode(hts221_t *dev, bool *power_down)
+{
+    CHECK_ARG(dev && power_down);
+
+    uint8_t raw;
+    CHECK(read_reg(dev, REG_CTRL_REG1, &raw));
+    *power_down = (raw & BIT(BIT_CTRL_REG1_PD)) ? true : false;
+
+    return ESP_OK;
+}
+
+esp_err_t hts221_set_power_mode(hts221_t *dev, bool power_down)
+{
+    CHECK_ARG(dev);
+
+    return update_reg(dev, REG_CTRL_REG1, power_down ? BIT(BIT_CTRL_REG1_PD) : 0, BIT(BIT_CTRL_REG1_PD));
+}
+
+
+esp_err_t hts221_get_data_rate(hts221_t *dev, hts221_data_rate_t *dr)
+{
+    CHECK_ARG(dev && dr);
+
+    uint8_t raw;
+    CHECK(read_reg(dev, REG_CTRL_REG1, &raw));
+    *dr = (raw & MASK_CTRL_REG1_ODR);
+
+    return ESP_OK;
+}
+
+esp_err_t hts221_set_data_rate(hts221_t *dev, hts221_data_rate_t dr)
+{
+    CHECK_ARG(dev && dr <= HTS221_12_5HZ);
+
+    return update_reg(dev, REG_CTRL_REG1, dr, MASK_CTRL_REG1_ODR);
+}
+
+esp_err_t hts221_get_heater(hts221_t *dev, bool *enable)
+{
+    CHECK_ARG(dev && enable);
+
+    uint8_t raw;
+    CHECK(read_reg(dev, REG_CTRL_REG2, &raw));
+    *enable = (raw & BIT(BIT_CTRL_REG2_HEATER)) ? true : false;
+
+    return ESP_OK;
+}
+
+esp_err_t hts221_set_heater(hts221_t *dev, bool enable)
+{
+    CHECK_ARG(dev);
+
+    return update_reg(dev, REG_CTRL_REG2, enable ? BIT(BIT_CTRL_REG2_HEATER) : 0, BIT(BIT_CTRL_REG2_HEATER));
+}
+
+esp_err_t hts221_get_averaging(hts221_t *dev, hts221_temperature_avg_t *t_avg, hts221_humidity_avg_t *rh_avg)
+{
+    CHECK_ARG(dev && (t_avg || rh_avg));
+
+    uint8_t raw;
+    CHECK(read_reg(dev, REG_AV_CONF, &raw));
+    if (t_avg)
+        *t_avg = (raw >> BIT_AV_CONF_AVGT0) & MASK_AV_CONF_AVG;
+    if (rh_avg)
+        *t_avg = (raw >> BIT_AV_CONF_AVGH0) & MASK_AV_CONF_AVG;
+
+    return ESP_OK;
+}
+
+esp_err_t hts221_set_averaging(hts221_t *dev, hts221_temperature_avg_t t_avg, hts221_humidity_avg_t rh_avg)
+{
+    CHECK_ARG(dev && t_avg <= HTS221_AVGT_256 && rh_avg <= HTS221_AVGH_512);
+
+    return write_reg(dev, REG_AV_CONF, (t_avg << BIT_AV_CONF_AVGT0) | (rh_avg << BIT_AV_CONF_AVGH0));
+}
+
+esp_err_t hts221_get_drdy_config(hts221_t *dev, bool *enable, hts221_drdy_mode_t *mode, hts221_drdy_level_t *active)
+{
+    CHECK_ARG(dev && (enable || mode || active));
+
+    uint8_t raw;
+    CHECK(read_reg(dev, REG_CTRL_REG3, &raw));
+
+    if (enable)
+        *enable = (raw >> BIT_CTRL_REG3_DRDY) & 1;
+    if (mode)
+        *mode = (raw >> BIT_CTRL_REG3_PP_OD) & 1;
+    if (active)
+        *active = (raw >> BIT_CTRL_REG3_DRDY_H_L) & 1;
+
+    return ESP_OK;
+}
+
+esp_err_t hts221_set_drdy_config(hts221_t *dev, bool enable, hts221_drdy_mode_t mode, hts221_drdy_level_t active)
+{
+    CHECK_ARG(dev && mode <= HTS221_DRDY_OPEN_DRAIN && active <= HTS221_DRDY_ACTIVE_LOW);
+
+    return write_reg(dev, REG_CTRL_REG3,
+            (enable ? BIT(BIT_CTRL_REG3_DRDY) : 0) | (mode << BIT_CTRL_REG3_PP_OD) | (active << BIT_CTRL_REG3_DRDY_H_L));
+}
+
+esp_err_t hts221_is_data_ready(hts221_t *dev, bool *ready)
+{
+    CHECK_ARG(dev && ready);
+
+    uint8_t raw;
+    CHECK(read_reg(dev, REG_STATUS_REG, &raw));
+
+    *ready = (raw & MASK_STATUS_REG_DA) == MASK_STATUS_REG_DA;
+
+    return ESP_OK;
+}
+
+esp_err_t hts221_start_oneshot(hts221_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return update_reg(dev, REG_CTRL_REG2, BIT(BIT_CTRL_REG2_ONE_SHOT), BIT(BIT_CTRL_REG2_ONE_SHOT));
+}
+
+esp_err_t hts221_get_data(hts221_t *dev, float *t, float *rh)
+{
+    CHECK_ARG(dev && (t || rh));
+
+    int16_t raw_rh;
+    int16_t raw_t;
+    uint8_t raw[4];
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, REG_HUMIDITY_OUT_L, &raw[0], 1));
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, REG_HUMIDITY_OUT_H, &raw[1], 1));
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, REG_TEMP_OUT_L, &raw[2], 1));
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, REG_TEMP_OUT_H, &raw[3], 1));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    raw_rh = (raw[1] << 8) | raw[0];
+    raw_t  = (raw[3] << 8) | raw[2];
+
+    if (rh)
+    {
+        *rh = (float)(dev->cal.H1_rH - dev->cal.H0_rH) * (raw_rh - dev->cal.H0_T0_OUT) /
+                (float)(dev->cal.H1_T0_OUT - dev->cal.H0_T0_OUT) + (float)dev->cal.H0_rH;
+        if (*rh > 100)
+            *rh = 100;
+    }
+    if (t)
+        *t = (float)(dev->cal.T1_degC - dev->cal.T0_degC) * (raw_t - dev->cal.T0_OUT) /
+                (float)(dev->cal.T1_OUT - dev->cal.T0_OUT) + (float)dev->cal.T0_degC;
+
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/hts221/hts221.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,294 @@
+/*
+ * Copyright (c) 2021 saasaa <mail@saasaa.xyz>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/**
+ * @file hts221.h
+ * @brief HTS221 driver
+ * @author saasaa
+ * @defgroup hts221 hts221
+ * @{
+ *
+ * ESP-IDF driver for HTS221 temperature and humidity sensor
+ *
+ * Datasheet: http://www.st.com/resource/en/datasheet/hts221.pdf
+ * Technical note on interpreting humidity and temperature readings in the
+ * HTS221 digital humidity sensor:
+ * https://www.st.com/resource/en/technical_note/tn1218-interpreting-humidity-and-temperature-readings-in-the-hts221-digital-humidity-sensor-stmicroelectronics.pdf
+ * Copyright (c) 2021 saasaa <mail@saasaa.xyz>
+ *
+ * ISC Licensed as described in the file LICENSE
+ *
+ */
+#ifndef __HTS221_H__
+#define __HTS221_H__
+
+#include <esp_err.h>
+#include <i2cdev.h>
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define HTS221_I2C_ADDRESS 0x5F
+
+/**
+ * @brief Struct for storing calibration parameters.
+ *
+ * Unique for every HTS221 Sensor.
+ */
+typedef struct
+{
+    uint8_t H0_rH;
+    uint8_t H1_rH;
+    uint16_t T0_degC;
+    uint16_t T1_degC;
+    int16_t H0_T0_OUT;
+    int16_t H1_T0_OUT;
+    int16_t T0_OUT;
+    int16_t T1_OUT;
+} hts221_calibration_param_t;
+
+/**
+ * @brief Number of averaged temperature samples
+ */
+typedef enum
+{
+    HTS221_AVGT_2 = 0,   //!< 2 averaged temperature samples
+    HTS221_AVGT_4,       //!< 4 averaged temperature samples
+    HTS221_AVGT_8,       //!< 8 averaged temperature samples
+    HTS221_AVGT_1,       //!< 16 averaged temperature samples
+    HTS221_AVGT_32,      //!< 32 averaged temperature samples
+    HTS221_AVGT_64,      //!< 64 averaged temperature samples
+    HTS221_AVGT_128,     //!< 128 averaged temperature samples
+    HTS221_AVGT_256,     //!< 256 averaged temperature samples
+} hts221_temperature_avg_t;
+
+/**
+ * @brief Number of averaged humidity samples
+ */
+typedef enum
+{
+    HTS221_AVGH_4 = 0,   //!< 4 averaged humidity samples
+    HTS221_AVGH_8,       //!< 8 averaged humidity samples
+    HTS221_AVGH_16,      //!< 16 averaged humidity samples
+    HTS221_AVGH_32,      //!< 32 averaged humidity samples
+    HTS221_AVGH_64,      //!< 64 averaged humidity samples
+    HTS221_AVGH_128,     //!< 128 averaged humidity samples
+    HTS221_AVGH_256,     //!< 256 averaged humidity samples
+    HTS221_AVGH_512,     //!< 512 averaged humidity samples
+} hts221_humidity_avg_t;
+
+/**
+ * @brief Output data rate
+ */
+typedef enum
+{
+    HTS221_ONE_SHOT = 0,
+    HTS221_1HZ,
+    HTS221_7HZ,
+    HTS221_12_5HZ,
+} hts221_data_rate_t;
+
+typedef enum
+{
+    HTS221_DRDY_ACTIVE_HIGH = 0,
+    HTS221_DRDY_ACTIVE_LOW,
+} hts221_drdy_level_t;
+
+typedef enum
+{
+    HTS221_DRDY_PUSH_PULL = 0,
+    HTS221_DRDY_OPEN_DRAIN,
+} hts221_drdy_mode_t;
+
+typedef struct
+{
+    i2c_dev_t i2c_dev;
+    hts221_calibration_param_t cal;
+} hts221_t;
+
+/**
+ * @brief Initialize the HTS221 Device descriptor
+ *
+ * @param[out] dev Device descriptor
+ * @param port     I2C port number
+ * @param sda_gpio GPIO pin number for SDA
+ * @param scl_gpio GPIO pin number for SCL
+ * @return `ESP_OK` on success
+ */
+esp_err_t hts221_init_desc(hts221_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t hts221_free_desc(hts221_t *dev);
+
+/**
+ * @brief Reset device parameters, read calibration data
+ *
+ * Reboot device, reset calibration data
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t hts221_init(hts221_t *dev);
+
+/**
+ * @brief Get power mode
+ *
+ * @param dev             Device descriptor
+ * @param[out] power_down true if device in power down mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t hts221_get_power_mode(hts221_t *dev, bool *power_down);
+
+/**
+ * @brief Set power mode
+ *
+ * @param dev        Device descriptor
+ * @param power_down true to set device to power down mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t hts221_set_power_mode(hts221_t *dev, bool power_down);
+
+/**
+ * @brief Get output data rate
+ *
+ * @param dev     Device descriptor
+ * @param[out] dr Data rate
+ * @return `ESP_OK` on success
+ */
+esp_err_t hts221_get_data_rate(hts221_t *dev, hts221_data_rate_t *dr);
+
+/**
+ * @brief Set output data rate
+ *
+ * @param dev Device descriptor
+ * @param dr  Data rate
+ * @return `ESP_OK` on success
+ */
+esp_err_t hts221_set_data_rate(hts221_t *dev, hts221_data_rate_t dr);
+
+/**
+ * @brief Get heater state
+ *
+ * @param dev         Device descriptor
+ * @param[out] enable true when heater is enabled
+ * @return `ESP_OK` on success
+ */
+esp_err_t hts221_get_heater(hts221_t *dev, bool *enable);
+
+/**
+ * @brief Switch heater on/off
+ *
+ * @param dev    Device descriptor
+ * @param enable true to switch heater on
+ * @return `ESP_OK` on success
+ */
+esp_err_t hts221_set_heater(hts221_t *dev, bool enable);
+
+/**
+ * @brief Get average configuration
+ *
+ * @param dev         Device descriptor
+ * @param[out] t_avg  Temperature average configuration
+ * @param[out] rh_avg Humidity average configuration
+ * @return `ESP_OK` on success
+ */
+esp_err_t hts221_get_averaging(hts221_t *dev, hts221_temperature_avg_t *t_avg, hts221_humidity_avg_t *rh_avg);
+
+/**
+ * @brief Set average configuration
+ *
+ * @param dev    Device descriptor
+ * @param t_avg  Temperature average configuration
+ * @param rh_avg Humidity average configuration
+ * @return `ESP_OK` on success
+ */
+esp_err_t hts221_set_averaging(hts221_t *dev, hts221_temperature_avg_t t_avg, hts221_humidity_avg_t rh_avg);
+
+/**
+ * @brief Get configuration of DRDY pin
+ *
+ * @param dev         Device descriptor
+ * @param[out] enable true if DRDY pin is enabled
+ * @param[out] mode   DRDY pin mode (Push-pull or open drain)
+ * @param[out] active Pin active level
+ * @return `ESP_OK` on success
+ */
+esp_err_t hts221_get_drdy_config(hts221_t *dev, bool *enable, hts221_drdy_mode_t *mode, hts221_drdy_level_t *active);
+
+/**
+ * @brief Set configuration of DRDY pin
+ *
+ * @param dev    Device descriptor
+ * @param enable true if DRDY pin is enabled
+ * @param mode   DRDY pin mode (Push-pull or open drain)
+ * @param active Pin active level
+ * @return `ESP_OK` on success
+ */
+esp_err_t hts221_set_drdy_config(hts221_t *dev, bool enable, hts221_drdy_mode_t mode, hts221_drdy_level_t active);
+
+/**
+ * @brief Check the availability of new RH/T data
+ *
+ * \p ready parameter will be true only if new data for both temperature and humidity is available.
+ *
+ * @param dev        Device descriptor
+ * @param[out] ready true if new RH/T data available
+ * @return `ESP_OK` on success
+ */
+esp_err_t hts221_is_data_ready(hts221_t *dev, bool *ready);
+
+/**
+ * @brief Start one shot measurement
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t hts221_start_oneshot(hts221_t *dev);
+
+/**
+ * @brief Get temperature and relative humidity
+ *
+ * @param dev     Device descriptor
+ * @param[out] t  Temperature, degrees Celsius
+ * @param[out] rh Relative humidity, %
+ * @return `ESP_OK` on success
+ */
+esp_err_t hts221_get_data(hts221_t *dev, float *t, float *rh);
+
+/**
+ * @brief Measure temperature and relative humidity in one shot mode
+ *
+ * @param dev     Device descriptor
+ * @param[out] t  Temperature, degrees Celsius
+ * @param[out] rh Relative humidity, %
+ * @return `ESP_OK` on success
+ */
+esp_err_t hts221_measure(hts221_t *dev, float *t, float *rh);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif // __HTS221_H__
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/hx711/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,24 @@
+---
+components:
+  - name: hx711
+    description: |
+      Driver for HX711 24-bit ADC for weigh scales
+    group: adc-dac
+    groups: []
+    code_owners:
+      - name: UncleRus
+    depends:
+      - name: driver
+      - name: freertos
+      - name: esp_idf_lib_helpers
+    thread_safe: no
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - name: UncleRus
+        year: 2019
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/hx711/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,13 @@
+if(${IDF_TARGET} STREQUAL esp8266)
+    set(req esp8266 freertos esp_idf_lib_helpers esp_timer)
+elseif(${IDF_VERSION_MAJOR} STREQUAL 4 AND ${IDF_VERSION_MINOR} STREQUAL 1 AND ${IDF_VERSION_PATCH} STREQUAL 3)
+    	set(req driver freertos esp_idf_lib_helpers)
+else()
+    set(req driver freertos esp_idf_lib_helpers esp_timer)
+endif()
+
+idf_component_register(
+    SRCS hx711.c
+    INCLUDE_DIRS .
+    REQUIRES ${req}
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/hx711/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/hx711/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,7 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+
+ifdef CONFIG_IDF_TARGET_ESP8266
+COMPONENT_DEPENDS = esp8266 freertos esp_idf_lib_helpers
+else
+COMPONENT_DEPENDS = driver freertos esp_idf_lib_helpers
+endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/hx711/hx711.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file hx711.c
+ *
+ * ESP-IDF driver for HX711 24-bit ADC for weigh scales
+ *
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <esp_timer.h>
+#include <ets_sys.h>
+#include <esp_idf_lib_helpers.h>
+#include "hx711.h"
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+#if HELPER_TARGET_IS_ESP32
+static portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
+#endif
+
+static uint32_t read_raw(gpio_num_t dout, gpio_num_t pd_sck, hx711_gain_t gain)
+{
+#if HELPER_TARGET_IS_ESP32
+    portENTER_CRITICAL(&mux);
+#elif HELPER_TARGET_IS_ESP8266
+    portENTER_CRITICAL();
+#endif
+
+    // read data
+    uint32_t data = 0;
+    for (size_t i = 0; i < 24; i++)
+    {
+        gpio_set_level(pd_sck, 1);
+        ets_delay_us(1);
+        data |= gpio_get_level(dout) << (23 - i);
+        gpio_set_level(pd_sck, 0);
+        ets_delay_us(1);
+    }
+
+    // config gain + channel for next read
+    for (size_t i = 0; i <= gain; i++)
+    {
+        gpio_set_level(pd_sck, 1);
+        ets_delay_us(1);
+        gpio_set_level(pd_sck, 0);
+        ets_delay_us(1);
+    }
+
+#if HELPER_TARGET_IS_ESP32
+    portEXIT_CRITICAL(&mux);
+#elif HELPER_TARGET_IS_ESP8266
+    portEXIT_CRITICAL();
+#endif
+
+    return data;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t hx711_init(hx711_t *dev)
+{
+    CHECK_ARG(dev);
+
+    CHECK(gpio_set_direction(dev->dout, GPIO_MODE_INPUT));
+    CHECK(gpio_set_direction(dev->pd_sck, GPIO_MODE_OUTPUT));
+
+    CHECK(hx711_power_down(dev, false));
+
+    return hx711_set_gain(dev, dev->gain);
+}
+
+esp_err_t hx711_power_down(hx711_t *dev, bool down)
+{
+    CHECK_ARG(dev);
+
+    CHECK(gpio_set_level(dev->pd_sck, down));
+    vTaskDelay(1);
+
+    return ESP_OK;
+}
+
+esp_err_t hx711_set_gain(hx711_t *dev, hx711_gain_t gain)
+{
+    CHECK_ARG(dev && gain <= HX711_GAIN_A_64);
+
+    CHECK(hx711_wait(dev, 200)); // 200 ms timeout
+
+    read_raw(dev->dout, dev->pd_sck, gain);
+    dev->gain = gain;
+
+    return ESP_OK;
+}
+
+esp_err_t hx711_is_ready(hx711_t *dev, bool *ready)
+{
+    CHECK_ARG(dev && ready);
+
+    *ready = !gpio_get_level(dev->dout);
+
+    return ESP_OK;
+}
+
+esp_err_t hx711_wait(hx711_t *dev, size_t timeout_ms)
+{
+    uint64_t started = esp_timer_get_time() / 1000;
+    while (esp_timer_get_time() / 1000 - started < timeout_ms)
+    {
+        if (!gpio_get_level(dev->dout))
+            return ESP_OK;
+        vTaskDelay(1);
+    }
+
+    return ESP_ERR_TIMEOUT;
+}
+
+esp_err_t hx711_read_data(hx711_t *dev, int32_t *data)
+{
+    CHECK_ARG(dev && data);
+
+    uint32_t raw = read_raw(dev->dout, dev->pd_sck, dev->gain);
+    if (raw & 0x800000)
+        raw |= 0xff000000;
+    *data = *((int32_t *)&raw);
+
+    return ESP_OK;
+}
+
+esp_err_t hx711_read_average(hx711_t *dev, size_t times, int32_t *data)
+{
+    CHECK_ARG(dev && times && data);
+
+    int32_t v;
+    *data = 0;
+    for (size_t i = 0; i < times; i++)
+    {
+        CHECK(hx711_wait(dev, 200));
+        CHECK(hx711_read_data(dev, &v));
+        *data += v;
+    }
+    *data /= (int32_t) times;
+
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/hx711/hx711.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file hx711.h
+ * @defgroup hx711 hx711
+ * @{
+ *
+ * ESP-IDF driver for HX711 24-bit ADC for weigh scales
+ *
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __HX711_H__
+#define __HX711_H__
+
+#include <driver/gpio.h>
+#include <stdbool.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Gain/channel
+ */
+typedef enum {
+    HX711_GAIN_A_128 = 0, //!< Channel A, gain factor 128
+    HX711_GAIN_B_32,      //!< Channel B, gain factor 32
+    HX711_GAIN_A_64       //!< Channel A, gain factor 64
+} hx711_gain_t;
+
+/**
+ * Device descriptor
+ */
+typedef struct
+{
+    gpio_num_t dout;
+    gpio_num_t pd_sck;
+    hx711_gain_t gain;
+} hx711_t;
+
+/**
+ * @brief Initialize device
+ *
+ * Prepare GPIO pins, power up device and set gain
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success, `ESP_ERR_TIMEOUT` if device not found
+ */
+esp_err_t hx711_init(hx711_t *dev);
+
+/**
+ * @brief Set device power up/down
+ *
+ * @param dev Device descriptor
+ * @param down Set device power down if true, power up otherwise
+ * @return `ESP_OK` on success
+ */
+esp_err_t hx711_power_down(hx711_t *dev, bool down);
+
+/**
+ * @brief Set device gain and channel
+ *
+ * @param dev Device descriptor
+ * @param gain Gain + channel value
+ * @return `ESP_OK` on success, `ESP_ERR_TIMEOUT` if device not found
+ */
+esp_err_t hx711_set_gain(hx711_t *dev, hx711_gain_t gain);
+
+/**
+ * @brief Check if device ready to send data
+ *
+ * @param dev Device descriptor
+ * @param[out] ready true if data ready
+ * @return `ESP_OK` on success
+ */
+esp_err_t hx711_is_ready(hx711_t *dev, bool *ready);
+
+/**
+ * @brief Wait for device to become ready
+ *
+ * @param dev Device descriptor
+ * @param timeout_ms Maximum time to wait, milliseconds
+ * @return `ESP_OK` on success
+ */
+esp_err_t hx711_wait(hx711_t *dev, size_t timeout_ms);
+
+/**
+ * @brief Read raw data from device.
+ *
+ * Please call this function only when device is ready,
+ * otherwise communication errors may occur
+ *
+ * @param dev Device descriptor
+ * @param[out] data Raw ADC data
+ * @return `ESP_OK` on success
+ */
+esp_err_t hx711_read_data(hx711_t *dev, int32_t *data);
+
+/**
+ * @brief Read average data
+ *
+ * @param dev Device descriptor
+ * @param times Count of samples to read
+ * @param[out] data Average ADC data
+ * @return `ESP_OK` on success
+ */
+esp_err_t hx711_read_average(hx711_t *dev, size_t times, int32_t *data);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __HX711_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/i2cdev/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,24 @@
+---
+components:
+  - name: i2cdev
+    description: |
+      ESP-IDF I2C master thread-safe utilities
+    group: common
+    groups: []
+    code_owners:
+      - name: UncleRus
+    depends:
+      - name: driver
+      - name: freertos
+      - name: esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: MIT
+    copyrights:
+      - name: UncleRus
+        year: 2018
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/i2cdev/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,11 @@
+if(${IDF_TARGET} STREQUAL esp8266)
+    set(req esp8266 freertos esp_idf_lib_helpers)
+else()
+    set(req driver freertos esp_idf_lib_helpers)
+endif()
+
+idf_component_register(
+    SRCS i2cdev.c
+    INCLUDE_DIRS .
+    REQUIRES ${req}
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/i2cdev/Kconfig	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,17 @@
+menu "I2C"
+
+config I2CDEV_TIMEOUT
+    int "I2C transaction timeout, milliseconds"
+    default 1000
+    range 10 5000
+    
+config I2CDEV_NOLOCK
+	bool "Disable the use of mutexes"
+	default n
+	help
+		Attention! After enabling this option, all I2C device
+		drivers will become non-thread safe. 
+		Use this option if you need to access your I2C devices
+		from interrupt handlers. 
+    
+endmenu
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/i2cdev/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2018 Ruslan V. Uss (https://github.com/UncleRus)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/i2cdev/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,7 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+
+ifdef CONFIG_IDF_TARGET_ESP8266
+COMPONENT_DEPENDS = esp8266 freertos esp_idf_lib_helpers
+else
+COMPONENT_DEPENDS = driver freertos esp_idf_lib_helpers
+endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/i2cdev/i2cdev.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,338 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file i2cdev.c
+ *
+ * ESP-IDF I2C master thread-safe functions for communication with I2C slave
+ *
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+#include <string.h>
+#include <inttypes.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <esp_log.h>
+#include "i2cdev.h"
+
+static const char *TAG = "i2cdev";
+
+typedef struct {
+    SemaphoreHandle_t lock;
+    i2c_config_t config;
+    bool installed;
+} i2c_port_state_t;
+
+static i2c_port_state_t states[I2C_NUM_MAX];
+
+#if CONFIG_I2CDEV_NOLOCK
+#define SEMAPHORE_TAKE(port)
+#else
+#define SEMAPHORE_TAKE(port) do { \
+        if (!xSemaphoreTake(states[port].lock, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT))) \
+        { \
+            ESP_LOGE(TAG, "Could not take port mutex %d", port); \
+            return ESP_ERR_TIMEOUT; \
+        } \
+        } while (0)
+#endif
+
+#if CONFIG_I2CDEV_NOLOCK
+#define SEMAPHORE_GIVE(port)
+#else
+#define SEMAPHORE_GIVE(port) do { \
+        if (!xSemaphoreGive(states[port].lock)) \
+        { \
+            ESP_LOGE(TAG, "Could not give port mutex %d", port); \
+            return ESP_FAIL; \
+        } \
+        } while (0)
+#endif
+
+esp_err_t i2cdev_init()
+{
+    memset(states, 0, sizeof(states));
+
+#if !CONFIG_I2CDEV_NOLOCK
+    for (int i = 0; i < I2C_NUM_MAX; i++)
+    {
+        states[i].lock = xSemaphoreCreateMutex();
+        if (!states[i].lock)
+        {
+            ESP_LOGE(TAG, "Could not create port mutex %d", i);
+            return ESP_FAIL;
+        }
+    }
+#endif
+
+    return ESP_OK;
+}
+
+esp_err_t i2cdev_done()
+{
+    for (int i = 0; i < I2C_NUM_MAX; i++)
+    {
+        if (!states[i].lock) continue;
+
+        if (states[i].installed)
+        {
+            SEMAPHORE_TAKE(i);
+            i2c_driver_delete(i);
+            states[i].installed = false;
+            SEMAPHORE_GIVE(i);
+        }
+#if !CONFIG_I2CDEV_NOLOCK
+        vSemaphoreDelete(states[i].lock);
+#endif
+        states[i].lock = NULL;
+    }
+    return ESP_OK;
+}
+
+esp_err_t i2c_dev_create_mutex(i2c_dev_t *dev)
+{
+#if !CONFIG_I2CDEV_NOLOCK
+    if (!dev) return ESP_ERR_INVALID_ARG;
+
+    ESP_LOGV(TAG, "[0x%02x at %d] creating mutex", dev->addr, dev->port);
+
+    dev->mutex = xSemaphoreCreateMutex();
+    if (!dev->mutex)
+    {
+        ESP_LOGE(TAG, "[0x%02x at %d] Could not create device mutex", dev->addr, dev->port);
+        return ESP_FAIL;
+    }
+#endif
+
+    return ESP_OK;
+}
+
+esp_err_t i2c_dev_delete_mutex(i2c_dev_t *dev)
+{
+#if !CONFIG_I2CDEV_NOLOCK
+    if (!dev) return ESP_ERR_INVALID_ARG;
+
+    ESP_LOGV(TAG, "[0x%02x at %d] deleting mutex", dev->addr, dev->port);
+
+    vSemaphoreDelete(dev->mutex);
+#endif
+    return ESP_OK;
+}
+
+esp_err_t i2c_dev_take_mutex(i2c_dev_t *dev)
+{
+#if !CONFIG_I2CDEV_NOLOCK
+    if (!dev) return ESP_ERR_INVALID_ARG;
+
+    ESP_LOGV(TAG, "[0x%02x at %d] taking mutex", dev->addr, dev->port);
+
+    if (!xSemaphoreTake(dev->mutex, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT)))
+    {
+        ESP_LOGE(TAG, "[0x%02x at %d] Could not take device mutex", dev->addr, dev->port);
+        return ESP_ERR_TIMEOUT;
+    }
+#endif
+    return ESP_OK;
+}
+
+esp_err_t i2c_dev_give_mutex(i2c_dev_t *dev)
+{
+#if !CONFIG_I2CDEV_NOLOCK
+    if (!dev) return ESP_ERR_INVALID_ARG;
+
+    ESP_LOGV(TAG, "[0x%02x at %d] giving mutex", dev->addr, dev->port);
+
+    if (!xSemaphoreGive(dev->mutex))
+    {
+        ESP_LOGE(TAG, "[0x%02x at %d] Could not give device mutex", dev->addr, dev->port);
+        return ESP_FAIL;
+    }
+#endif
+    return ESP_OK;
+}
+
+inline static bool cfg_equal(const i2c_config_t *a, const i2c_config_t *b)
+{
+    return a->scl_io_num == b->scl_io_num
+        && a->sda_io_num == b->sda_io_num
+#if HELPER_TARGET_IS_ESP32
+        && a->master.clk_speed == b->master.clk_speed
+#elif HELPER_TARGET_IS_ESP8266
+        && a->clk_stretch_tick == b->clk_stretch_tick
+#endif
+        && a->scl_pullup_en == b->scl_pullup_en
+        && a->sda_pullup_en == b->sda_pullup_en;
+}
+
+static esp_err_t i2c_setup_port(const i2c_dev_t *dev)
+{
+    if (dev->port >= I2C_NUM_MAX) return ESP_ERR_INVALID_ARG;
+
+    esp_err_t res;
+    if (!cfg_equal(&dev->cfg, &states[dev->port].config) || !states[dev->port].installed)
+    {
+        ESP_LOGD(TAG, "Reconfiguring I2C driver on port %d", dev->port);
+        i2c_config_t temp;
+        memcpy(&temp, &dev->cfg, sizeof(i2c_config_t));
+        temp.mode = I2C_MODE_MASTER;
+
+        // Driver reinstallation
+        if (states[dev->port].installed)
+        {
+            i2c_driver_delete(dev->port);
+            states[dev->port].installed = false;
+        }
+#if HELPER_TARGET_IS_ESP32
+#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)
+        // See https://github.com/espressif/esp-idf/issues/10163
+        if ((res = i2c_driver_install(dev->port, temp.mode, 0, 0, 0)) != ESP_OK)
+            return res;
+        if ((res = i2c_param_config(dev->port, &temp)) != ESP_OK)
+            return res;
+#else
+        if ((res = i2c_param_config(dev->port, &temp)) != ESP_OK)
+            return res;
+        if ((res = i2c_driver_install(dev->port, temp.mode, 0, 0, 0)) != ESP_OK)
+            return res;
+#endif
+#endif
+#if HELPER_TARGET_IS_ESP8266
+        // Clock Stretch time, depending on CPU frequency
+        temp.clk_stretch_tick = dev->timeout_ticks ? dev->timeout_ticks : I2CDEV_MAX_STRETCH_TIME;
+        if ((res = i2c_driver_install(dev->port, temp.mode)) != ESP_OK)
+            return res;
+        if ((res = i2c_param_config(dev->port, &temp)) != ESP_OK)
+            return res;
+#endif
+        states[dev->port].installed = true;
+
+        memcpy(&states[dev->port].config, &temp, sizeof(i2c_config_t));
+        ESP_LOGD(TAG, "I2C driver successfully reconfigured on port %d", dev->port);
+    }
+#if HELPER_TARGET_IS_ESP32
+    int t;
+    if ((res = i2c_get_timeout(dev->port, &t)) != ESP_OK)
+        return res;
+    // Timeout cannot be 0
+    uint32_t ticks = dev->timeout_ticks ? dev->timeout_ticks : I2CDEV_MAX_STRETCH_TIME;
+    if ((ticks != t) && (res = i2c_set_timeout(dev->port, ticks)) != ESP_OK)
+        return res;
+    ESP_LOGD(TAG, "Timeout: ticks = %" PRIu32 " (%" PRIu32 " usec) on port %d", dev->timeout_ticks, dev->timeout_ticks / 80, dev->port);
+#endif
+
+    return ESP_OK;
+}
+
+esp_err_t i2c_dev_probe(const i2c_dev_t *dev, i2c_dev_type_t operation_type)
+{
+    if (!dev) return ESP_ERR_INVALID_ARG;
+
+    SEMAPHORE_TAKE(dev->port);
+
+    esp_err_t res = i2c_setup_port(dev);
+    if (res == ESP_OK)
+    {
+        i2c_cmd_handle_t cmd = i2c_cmd_link_create();
+        i2c_master_start(cmd);
+        i2c_master_write_byte(cmd, dev->addr << 1 | (operation_type == I2C_DEV_READ ? 1 : 0), true);
+        i2c_master_stop(cmd);
+
+        res = i2c_master_cmd_begin(dev->port, cmd, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT));
+
+        i2c_cmd_link_delete(cmd);
+    }
+
+    SEMAPHORE_GIVE(dev->port);
+
+    return res;
+}
+
+esp_err_t i2c_dev_read(const i2c_dev_t *dev, const void *out_data, size_t out_size, void *in_data, size_t in_size)
+{
+    if (!dev || !in_data || !in_size) return ESP_ERR_INVALID_ARG;
+
+    SEMAPHORE_TAKE(dev->port);
+
+    esp_err_t res = i2c_setup_port(dev);
+    if (res == ESP_OK)
+    {
+        i2c_cmd_handle_t cmd = i2c_cmd_link_create();
+        if (out_data && out_size)
+        {
+            i2c_master_start(cmd);
+            i2c_master_write_byte(cmd, dev->addr << 1, true);
+            i2c_master_write(cmd, (void *)out_data, out_size, true);
+        }
+        i2c_master_start(cmd);
+        i2c_master_write_byte(cmd, (dev->addr << 1) | 1, true);
+        i2c_master_read(cmd, in_data, in_size, I2C_MASTER_LAST_NACK);
+        i2c_master_stop(cmd);
+
+        res = i2c_master_cmd_begin(dev->port, cmd, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT));
+        if (res != ESP_OK)
+            ESP_LOGE(TAG, "Could not read from device [0x%02x at %d]: %d (%s)", dev->addr, dev->port, res, esp_err_to_name(res));
+
+        i2c_cmd_link_delete(cmd);
+    }
+
+    SEMAPHORE_GIVE(dev->port);
+    return res;
+}
+
+esp_err_t i2c_dev_write(const i2c_dev_t *dev, const void *out_reg, size_t out_reg_size, const void *out_data, size_t out_size)
+{
+    if (!dev || !out_data || !out_size) return ESP_ERR_INVALID_ARG;
+
+    SEMAPHORE_TAKE(dev->port);
+
+    esp_err_t res = i2c_setup_port(dev);
+    if (res == ESP_OK)
+    {
+        i2c_cmd_handle_t cmd = i2c_cmd_link_create();
+        i2c_master_start(cmd);
+        i2c_master_write_byte(cmd, dev->addr << 1, true);
+        if (out_reg && out_reg_size)
+            i2c_master_write(cmd, (void *)out_reg, out_reg_size, true);
+        i2c_master_write(cmd, (void *)out_data, out_size, true);
+        i2c_master_stop(cmd);
+        res = i2c_master_cmd_begin(dev->port, cmd, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT));
+        if (res != ESP_OK)
+            ESP_LOGE(TAG, "Could not write to device [0x%02x at %d]: %d (%s)", dev->addr, dev->port, res, esp_err_to_name(res));
+        i2c_cmd_link_delete(cmd);
+    }
+
+    SEMAPHORE_GIVE(dev->port);
+    return res;
+}
+
+esp_err_t i2c_dev_read_reg(const i2c_dev_t *dev, uint8_t reg, void *in_data, size_t in_size)
+{
+    return i2c_dev_read(dev, &reg, 1, in_data, in_size);
+}
+
+esp_err_t i2c_dev_write_reg(const i2c_dev_t *dev, uint8_t reg, const void *out_data, size_t out_size)
+{
+    return i2c_dev_write(dev, &reg, 1, out_data, out_size);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/i2cdev/i2cdev.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,251 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file i2cdev.h
+ * @defgroup i2cdev i2cdev
+ * @{
+ *
+ * ESP-IDF I2C master thread-safe functions for communication with I2C slave
+ *
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+#ifndef __I2CDEV_H__
+#define __I2CDEV_H__
+
+#include <driver/i2c.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/semphr.h>
+#include <esp_err.h>
+#include <esp_idf_lib_helpers.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if HELPER_TARGET_IS_ESP8266
+
+#define I2CDEV_MAX_STRETCH_TIME 0xffffffff
+
+#else
+
+#include <soc/i2c_reg.h>
+#if defined(I2C_TIME_OUT_VALUE_V)
+#define I2CDEV_MAX_STRETCH_TIME I2C_TIME_OUT_VALUE_V
+#elif defined(I2C_TIME_OUT_REG_V)
+#define I2CDEV_MAX_STRETCH_TIME I2C_TIME_OUT_REG_V
+#else
+#define I2CDEV_MAX_STRETCH_TIME 0x00ffffff
+#endif
+
+#endif /* HELPER_TARGET_IS_ESP8266 */
+
+/**
+ * I2C device descriptor
+ */
+typedef struct
+{
+    i2c_port_t port;         //!< I2C port number
+    i2c_config_t cfg;        //!< I2C driver configuration
+    uint8_t addr;            //!< Unshifted address
+    SemaphoreHandle_t mutex; //!< Device mutex
+    uint32_t timeout_ticks;  /*!< HW I2C bus timeout (stretch time), in ticks. 80MHz APB clock
+                                  ticks for ESP-IDF, CPU ticks for ESP8266.
+                                  When this value is 0, I2CDEV_MAX_STRETCH_TIME will be used */
+} i2c_dev_t;
+
+/**
+ * I2C transaction type
+ */
+typedef enum {
+    I2C_DEV_WRITE = 0, /**< Write operation */
+    I2C_DEV_READ       /**< Read operation */
+} i2c_dev_type_t;
+
+/**
+ * @brief Init library
+ *
+ * The function must be called before any other
+ * functions of this library.
+ *
+ * @return ESP_OK on success
+ */
+esp_err_t i2cdev_init();
+
+/**
+ * @brief Finish work with library
+ *
+ * Uninstall i2c drivers.
+ *
+ * @return ESP_OK on success
+ */
+esp_err_t i2cdev_done();
+
+/**
+ * @brief Create mutex for device descriptor
+ *
+ * This function does nothing if option CONFIG_I2CDEV_NOLOCK is enabled.
+ *
+ * @param dev Device descriptor
+ * @return ESP_OK on success
+ */
+esp_err_t i2c_dev_create_mutex(i2c_dev_t *dev);
+
+/**
+ * @brief Delete mutex for device descriptor
+ *
+ * This function does nothing if option CONFIG_I2CDEV_NOLOCK is enabled.
+ *
+ * @param dev Device descriptor
+ * @return ESP_OK on success
+ */
+esp_err_t i2c_dev_delete_mutex(i2c_dev_t *dev);
+
+/**
+ * @brief Take device mutex
+ *
+ * This function does nothing if option CONFIG_I2CDEV_NOLOCK is enabled.
+ *
+ * @param dev Device descriptor
+ * @return ESP_OK on success
+ */
+esp_err_t i2c_dev_take_mutex(i2c_dev_t *dev);
+
+/**
+ * @brief Give device mutex
+ *
+ * This function does nothing if option CONFIG_I2CDEV_NOLOCK is enabled.
+ *
+ * @param dev Device descriptor
+ * @return ESP_OK on success
+ */
+esp_err_t i2c_dev_give_mutex(i2c_dev_t *dev);
+
+/**
+ * @brief Check the availability of the device
+ *
+ * Issue an operation of \p operation_type to the I2C device then stops.
+ *
+ * @param dev Device descriptor
+ * @param operation_type Operation type
+ * @return ESP_OK if device is available
+ */
+esp_err_t i2c_dev_probe(const i2c_dev_t *dev, i2c_dev_type_t operation_type);
+
+/**
+ * @brief Read from slave device
+ *
+ * Issue a send operation of \p out_data register address, followed by reading \p in_size bytes
+ * from slave into \p in_data .
+ * Function is thread-safe.
+ *
+ * @param dev Device descriptor
+ * @param out_data Pointer to data to send if non-null
+ * @param out_size Size of data to send
+ * @param[out] in_data Pointer to input data buffer
+ * @param in_size Number of byte to read
+ * @return ESP_OK on success
+ */
+esp_err_t i2c_dev_read(const i2c_dev_t *dev, const void *out_data,
+        size_t out_size, void *in_data, size_t in_size);
+
+/**
+ * @brief Write to slave device
+ *
+ * Write \p out_size bytes from \p out_data to slave into \p out_reg register address.
+ * Function is thread-safe.
+ *
+ * @param dev Device descriptor
+ * @param out_reg Pointer to register address to send if non-null
+ * @param out_reg_size Size of register address
+ * @param out_data Pointer to data to send
+ * @param out_size Size of data to send
+ * @return ESP_OK on success
+ */
+esp_err_t i2c_dev_write(const i2c_dev_t *dev, const void *out_reg,
+        size_t out_reg_size, const void *out_data, size_t out_size);
+
+/**
+ * @brief Read from register with an 8-bit address
+ *
+ * Shortcut to ::i2c_dev_read().
+ *
+ * @param dev Device descriptor
+ * @param reg Register address
+ * @param[out] in_data Pointer to input data buffer
+ * @param in_size Number of byte to read
+ * @return ESP_OK on success
+ */
+esp_err_t i2c_dev_read_reg(const i2c_dev_t *dev, uint8_t reg,
+        void *in_data, size_t in_size);
+
+/**
+ * @brief Write to register with an 8-bit address
+ *
+ * Shortcut to ::i2c_dev_write().
+ *
+ * @param dev Device descriptor
+ * @param reg Register address
+ * @param out_data Pointer to data to send
+ * @param out_size Size of data to send
+ * @return ESP_OK on success
+ */
+esp_err_t i2c_dev_write_reg(const i2c_dev_t *dev, uint8_t reg,
+        const void *out_data, size_t out_size);
+
+#define I2C_DEV_TAKE_MUTEX(dev) do { \
+        esp_err_t __ = i2c_dev_take_mutex(dev); \
+        if (__ != ESP_OK) return __;\
+    } while (0)
+
+#define I2C_DEV_GIVE_MUTEX(dev) do { \
+        esp_err_t __ = i2c_dev_give_mutex(dev); \
+        if (__ != ESP_OK) return __;\
+    } while (0)
+
+#define I2C_DEV_CHECK(dev, X) do { \
+        esp_err_t ___ = X; \
+        if (___ != ESP_OK) { \
+            I2C_DEV_GIVE_MUTEX(dev); \
+            return ___; \
+        } \
+    } while (0)
+
+#define I2C_DEV_CHECK_LOGE(dev, X, msg, ...) do { \
+        esp_err_t ___ = X; \
+        if (___ != ESP_OK) { \
+            I2C_DEV_GIVE_MUTEX(dev); \
+            ESP_LOGE(TAG, msg, ## __VA_ARGS__); \
+            return ___; \
+        } \
+    } while (0)
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __I2CDEV_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/icm42670/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,23 @@
+---
+components:
+  - name: icm42670
+    description: Driver for TDK ICM-42670-P 6-Axis IMU (found on ESP-RS board, https://github.com/esp-rs/esp-rust-board)
+    group: imu
+    groups: []
+    code_owners: janveeh
+    depends:
+      - i2cdev
+      - log
+      - esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: ICS
+    copyrights:
+      - author:
+          name: janveeh
+        year: 2022
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/icm42670/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS icm42670.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/icm42670/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,16 @@
+
+SPDX-License-Identifier: ISC
+
+Copyright (c) 2022 Jan Veeh <jan.veeh@motius.de>
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/icm42670/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/icm42670/icm42670.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,722 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2022 Jan Veeh <jan.veeh@motius.de>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/**
+ * @file icm42670.c
+ *
+ * ESP-IDF driver for TDK ICM-42670-P IMU (found on ESP-RS board)
+ *
+ * Copyright (c) 2022 Jan Veeh (jan.veeh@motius.de)
+ *
+ * ISC Licensed as described in the file LICENSE
+ *
+ * Open TODOs:
+ * - FIFO reading and handling
+ * - APEX functions like pedometer, tilt-detection, low-g detection, freefall detection, ...
+ *
+ *
+ */
+
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <esp_log.h>
+#include <esp_idf_lib_helpers.h>
+#include <ets_sys.h>
+#include "icm42670.h"
+
+#define I2C_FREQ_HZ 1000000 // 1MHz
+
+static const char *TAG = "icm42670";
+
+// register structure definitions
+#define ICM42670_MCLK_RDY_BITS  0x08 // ICM42670_REG_MCLK_RDY<3>
+#define ICM42670_MCLK_RDY_SHIFT 3    // ICM42670_REG_MCLK_RDY<3>
+
+#define ICM42670_SPI_AP_4WIRE_BITS  0x04 // ICM42670_REG_DEVICE_CONFIG<2>
+#define ICM42670_SPI_AP_4WIRE_SHIFT 2    // ICM42670_REG_DEVICE_CONFIG<2>
+#define ICM42670_SPI_MODE_BITS      0x01 // ICM42670_REG_DEVICE_CONFIG<0>
+#define ICM42670_SPI_MODE_SHIFT     0    // ICM42670_REG_DEVICE_CONFIG<0>
+
+#define ICM42670_SOFT_RESET_DEVICE_CONFIG_BITS  0x10 // ICM42670_REG_SIGNAL_PATH_RESET<4>
+#define ICM42670_SOFT_RESET_DEVICE_CONFIG_SHIFT 4    // ICM42670_REG_SIGNAL_PATH_RESET<4>
+#define ICM42670_FIFO_FLUSH_BITS                0x04 // ICM42670_REG_SIGNAL_PATH_RESET<2>
+#define ICM42670_FIFO_FLUSH_SHIFT               2    // ICM42670_REG_SIGNAL_PATH_RESET<2>
+
+#define ICM42670_I3C_DDR_SLEW_RATE_BITS  0x38 // ICM42670_REG_DRIVE_CONFIG1<5:3>
+#define ICM42670_I3C_DDR_SLEW_RATE_SHIFT 3    // ICM42670_REG_DRIVE_CONFIG1<5:3>
+#define ICM42670_I3C_SDR_SLEW_RATE_BITS  0x07 // ICM42670_REG_DRIVE_CONFIG1<2:0>
+#define ICM42670_I3C_SDR_SLEW_RATE_SHIFT 0    // ICM42670_REG_DRIVE_CONFIG1<2:0>
+
+#define ICM42670_I2C_DDR_SLEW_RATE_BITS  0x38 // ICM42670_REG_DRIVE_CONFIG2<5:3>
+#define ICM42670_I2C_DDR_SLEW_RATE_SHIFT 3    // ICM42670_REG_DRIVE_CONFIG2<5:3>
+#define ICM42670_I2C_SDR_SLEW_RATE_BITS  0x07 // ICM42670_REG_DRIVE_CONFIG2<2:0>
+#define ICM42670_I2C_SDR_SLEW_RATE_SHIFT 0    // ICM42670_REG_DRIVE_CONFIG2<2:0>
+
+#define ICM42670_SPI_SLEW_RATE_BITS  0x07 // ICM42670_REG_DRIVE_CONFIG3<2:0>
+#define ICM42670_SPI_SLEW_RATE_SHIFT 0    // ICM42670_REG_DRIVE_CONFIG3<2:0>
+
+#define ICM42670_INT2_MODE_BITS           0x20 // ICM42670_REG_INT_CONFIG<5>
+#define ICM42670_INT2_MODE_SHIFT          5    // ICM42670_REG_INT_CONFIG<5>
+#define ICM42670_INT2_DRIVE_CIRCUIT_BITS  0x10 // ICM42670_REG_INT_CONFIG<4>
+#define ICM42670_INT2_DRIVE_CIRCUIT_SHIFT 4    // ICM42670_REG_INT_CONFIG<4>
+#define ICM42670_INT2_POLARITY_BITS       0x08 // ICM42670_REG_INT_CONFIG<3>
+#define ICM42670_INT2_POLARITY_SHIFT      3    // ICM42670_REG_INT_CONFIG<3>
+#define ICM42670_INT1_MODE_BITS           0x04 // ICM42670_REG_INT_CONFIG<2>
+#define ICM42670_INT1_MODE_SHIFT          2    // ICM42670_REG_INT_CONFIG<2>
+#define ICM42670_INT1_DRIVE_CIRCUIT_BITS  0x02 // ICM42670_REG_INT_CONFIG<1>
+#define ICM42670_INT1_DRIVE_CIRCUIT_SHIFT 1    // ICM42670_REG_INT_CONFIG<1>
+#define ICM42670_INT1_POLARITY_BITS       0x01 // ICM42670_REG_INT_CONFIG<0>
+#define ICM42670_INT1_POLARITY_SHIFT      0    // ICM42670_REG_INT_CONFIG<0>
+
+#define ICM42670_ACCEL_LP_CLK_SEL_BITS  0x80 // ICM42670_REG_PWR_MGMT0<7>
+#define ICM42670_ACCEL_LP_CLK_SEL_SHIFT 7    // ICM42670_REG_PWR_MGMT0<7>
+#define ICM42670_IDLE_BITS              0x10 // ICM42670_REG_PWR_MGMT0<4>
+#define ICM42670_IDLE_SHIFT             4    // ICM42670_REG_PWR_MGMT0<4>
+#define ICM42670_GYRO_MODE_BITS         0x0C // ICM42670_REG_PWR_MGMT0<3:2>
+#define ICM42670_GYRO_MODE_SHIFT        2    // ICM42670_REG_PWR_MGMT0<3:2>
+#define ICM42670_ACCEL_MODE_BITS        0x03 // ICM42670_REG_PWR_MGMT0<1:0>
+#define ICM42670_ACCEL_MODE_SHIFT       0    // ICM42670_REG_PWR_MGMT0<1:0>
+
+#define ICM42670_GYRO_UI_FS_SEL_BITS  0x60 // ICM42670_REG_GYRO_CONFIG0<6:5>
+#define ICM42670_GYRO_UI_FS_SEL_SHIFT 5    // ICM42670_REG_GYRO_CONFIG0<6:5>
+#define ICM42670_GYRO_ODR_BITS        0x0F // ICM42670_REG_GYRO_CONFIG0<3:0>
+#define ICM42670_GYRO_ODR_SHIFT       0    // ICM42670_REG_GYRO_CONFIG0<3:0>
+
+#define ICM42670_ACCEL_UI_FS_SEL_BITS  0x60 // ICM42670_REG_ACCEL_CONFIG0<6:5>
+#define ICM42670_ACCEL_UI_FS_SEL_SHIFT 5    // ICM42670_REG_ACCEL_CONFIG0<6:5>
+#define ICM42670_ACCEL_ODR_BITS        0x0F // ICM42670_REG_ACCEL_CONFIG0<3:0>
+#define ICM42670_ACCEL_ODR_SHIFT       0    // ICM42670_REG_ACCEL_CONFIG0<3:0>
+
+#define ICM42670_TEMP_FILT_BW_BITS  0x70 // ICM42670_REG_TEMP_CONFIG0<6:4>
+#define ICM42670_TEMP_FILT_BW_SHIFT 4    // ICM42670_REG_TEMP_CONFIG0<6:4>
+
+#define ICM42670_GYRO_UI_FILT_BW_BITS  0x07 // ICM42670_REG_GYRO_CONFIG1<2:0>
+#define ICM42670_GYRO_UI_FILT_BW_SHIFT 0    // ICM42670_REG_GYRO_CONFIG1<2:0>
+
+#define ICM42670_ACCEL_UI_AVG_BITS      0x70 // ICM42670_REG_ACCEL_CONFIG1<6:4>
+#define ICM42670_ACCEL_UI_AVG_SHIFT     4    // ICM42670_REG_ACCEL_CONFIG1<6:4>
+#define ICM42670_ACCEL_UI_FILT_BW_BITS  0x07 // ICM42670_REG_ACCEL_CONFIG1<2:0>
+#define ICM42670_ACCEL_UI_FILT_BW_SHIFT 0    // ICM42670_REG_ACCEL_CONFIG1<2:0>
+
+#define ICM42670_DMP_POWER_SAVE_EN_BITS  0x08 // ICM42670_REG_APEX_CONFIG0<3>
+#define ICM42670_DMP_POWER_SAVE_EN_SHIFT 3    // ICM42670_REG_APEX_CONFIG0<3>
+#define ICM42670_DMP_INIT_EN_BITS        0x04 // ICM42670_REG_APEX_CONFIG0<2>
+#define ICM42670_DMP_INIT_EN_SHIFT       2    // ICM42670_REG_APEX_CONFIG0<2>
+#define ICM42670_DMP_MEM_RESET_EN_BITS   0x01 // ICM42670_REG_APEX_CONFIG0<0>
+#define ICM42670_DMP_MEM_RESET_EN_SHIFT  0    // ICM42670_REG_APEX_CONFIG0<0>
+
+#define ICM42670_SMD_ENABLE_BITS   0x40 // ICM42670_REG_APEX_CONFIG1<6>
+#define ICM42670_SMD_ENABLE_SHIFT  6    // ICM42670_REG_APEX_CONFIG1<6>
+#define ICM42670_FF_ENABLE_BITS    0x20 // ICM42670_REG_APEX_CONFIG1<5>
+#define ICM42670_FF_ENABLE_SHIFT   5    // ICM42670_REG_APEX_CONFIG1<5>
+#define ICM42670_TILT_ENABLE_BITS  0x10 // ICM42670_REG_APEX_CONFIG1<4>
+#define ICM42670_TILT_ENABLE_SHIFT 4    // ICM42670_REG_APEX_CONFIG1<4>
+#define ICM42670_PED_ENABLE_BITS   0x08 // ICM42670_REG_APEX_CONFIG1<3>
+#define ICM42670_PED_ENABLE_SHIFT  3    // ICM42670_REG_APEX_CONFIG1<3>
+#define ICM42670_DMP_ODR_BITS      0x03 // ICM42670_REG_APEX_CONFIG1<1:0>
+#define ICM42670_DMP_ODR_SHIFT     0    // ICM42670_REG_APEX_CONFIG1<1:0>
+
+#define ICM42670_WOM_INT_DUR_BITS   0x18 // ICM42670_REG_WOM_CONFIG<4:3>
+#define ICM42670_WOM_INT_DUR_SHIFT  3    // ICM42670_REG_WOM_CONFIG<4:3>
+#define ICM42670_WOM_INT_MODE_BITS  0x04 // ICM42670_REG_WOM_CONFIG<2>
+#define ICM42670_WOM_INT_MODE_SHIFT 2    // ICM42670_REG_WOM_CONFIG<2>
+#define ICM42670_WOM_MODE_BITS      0x02 // ICM42670_REG_WOM_CONFIG<1>
+#define ICM42670_WOM_MODE_SHIFT     1    // ICM42670_REG_WOM_CONFIG<1>
+#define ICM42670_WOM_EN_BITS        0x01 // ICM42670_REG_WOM_CONFIG<0>
+#define ICM42670_WOM_EN_SHIFT       0    // ICM42670_REG_WOM_CONFIG<0>
+
+#define ICM42670_FIFO_MODE_BITS    0x02 // ICM42670_REG_FIFO_CONFIG1<1>
+#define ICM42670_FIFO_MODE_SHIFT   1    // ICM42670_REG_FIFO_CONFIG1<1>
+#define ICM42670_FIFO_BYPASS_BITS  0x01 // ICM42670_REG_FIFO_CONFIG1<0>
+#define ICM42670_FIFO_BYPASS_SHIFT 0    // ICM42670_REG_FIFO_CONFIG1<0>
+
+#define ICM42670_ST_INT1_EN_BITS          0x80 // ICM42670_REG_INT_SOURCE0<7>
+#define ICM42670_ST_INT1_EN_SHIFT         7    // ICM42670_REG_INT_SOURCE0<7>
+#define ICM42670_FSYNC_INT1_EN_BITS       0x40 // ICM42670_REG_INT_SOURCE0<6>
+#define ICM42670_FSYNC_INT1_EN_SHIFT      6    // ICM42670_REG_INT_SOURCE0<6>
+#define ICM42670_PLL_RDY_INT1_EN_BITS     0x20 // ICM42670_REG_INT_SOURCE0<5>
+#define ICM42670_PLL_RDY_INT1_EN_SHIFT    5    // ICM42670_REG_INT_SOURCE0<5>
+#define ICM42670_RESET_DONE_INT1_EN_BITS  0x10 // ICM42670_REG_INT_SOURCE0<4>
+#define ICM42670_RESET_DONE_INT1_EN_SHIFT 4    // ICM42670_REG_INT_SOURCE0<4>
+#define ICM42670_DRDY_INT1_EN_BITS        0x08 // ICM42670_REG_INT_SOURCE0<3>
+#define ICM42670_DRDY_INT1_EN_SHIFT       3    // ICM42670_REG_INT_SOURCE0<3>
+#define ICM42670_FIFO_THS_INT1_EN_BITS    0x04 // ICM42670_REG_INT_SOURCE0<2>
+#define ICM42670_FIFO_THS_INT1_EN_SHIFT   2    // ICM42670_REG_INT_SOURCE0<2>
+#define ICM42670_FIFO_FULL_INT1_EN_BITS   0x02 // ICM42670_REG_INT_SOURCE0<1>
+#define ICM42670_FIFO_FULL_INT1_EN_SHIFT  1    // ICM42670_REG_INT_SOURCE0<1>
+#define ICM42670_AGC_RDY_INT1_EN_BITS     0x01 // ICM42670_REG_INT_SOURCE0<0>
+#define ICM42670_AGC_RDY_INT1_EN_SHIFT    0    // ICM42670_REG_INT_SOURCE0<0>
+
+#define ICM42670_I3C_PROTOCOL_ERROR_INT1_EN_BITS  0x40 // ICM42670_REG_INT_SOURCE1<6>
+#define ICM42670_I3C_PROTOCOL_ERROR_INT1_EN_SHIFT 6    // ICM42670_REG_INT_SOURCE1<6>
+#define ICM42670_SMD_INT1_EN_BITS                 0x08 // ICM42670_REG_INT_SOURCE1<3>
+#define ICM42670_SMD_INT1_EN_SHIFT                3    // ICM42670_REG_INT_SOURCE1<3>
+#define ICM42670_WOM_Z_INT1_EN_BITS               0x04 // ICM42670_REG_INT_SOURCE1<2>
+#define ICM42670_WOM_Z_INT1_EN_SHIFT              2    // ICM42670_REG_INT_SOURCE1<2>
+#define ICM42670_WOM_Y_INT1_EN_BITS               0x02 // ICM42670_REG_INT_SOURCE1<1>
+#define ICM42670_WOM_Y_INT1_EN_SHIFT              1    // ICM42670_REG_INT_SOURCE1<1>
+#define ICM42670_WOM_X_INT1_EN_BITS               0x01 // ICM42670_REG_INT_SOURCE1<0>
+#define ICM42670_WOM_X_INT1_EN_SHIFT              0    // ICM42670_REG_INT_SOURCE1<0>
+
+// ICM42670_REG_INT_SOURCE3 and ICM42670_REG_INT_SOURCE4 same as 0 and 1
+
+#define ICM42670_DMP_IDLE_BITS        0x04 // ICM42670_REG_APEX_DATA3<2>
+#define ICM42670_DMP_IDLE_SHIFT       2    // ICM42670_REG_APEX_DATA3<2>
+#define ICM42670_ACTIVITY_CLASS_BITS  0x03 // ICM42670_REG_APEX_DATA3<1:0>
+#define ICM42670_ACTIVITY_CLASS_SHIFT 0    // ICM42670_REG_APEX_DATA3<1:0>
+
+#define ICM42670_FIFO_COUNT_FORMAT_BITS   0x40 // ICM42670_REG_INTF_CONFIG0<6>
+#define ICM42670_FIFO_COUNT_FORMAT_SHIFT  6    // ICM42670_REG_INTF_CONFIG0<6>
+#define ICM42670_FIFO_COUNT_ENDIAN_BITS   0x20 // ICM42670_REG_INTF_CONFIG0<5>
+#define ICM42670_FIFO_COUNT_ENDIAN_SHIFT  5    // ICM42670_REG_INTF_CONFIG0<5>
+#define ICM42670_SENSOR_DATA_ENDIAN_BITS  0x10 // ICM42670_REG_INTF_CONFIG0<4>
+#define ICM42670_SENSOR_DATA_ENDIAN_SHIFT 4    // ICM42670_REG_INTF_CONFIG0<4>
+
+#define ICM42670_I3C_SDR_EN_BITS  0x08 // ICM42670_REG_INTF_CONFIG1<3>
+#define ICM42670_I3C_SDR_EN_SHIFT 3    // ICM42670_REG_INTF_CONFIG1<3>
+#define ICM42670_I3C_DDR_EN_BITS  0x04 // ICM42670_REG_INTF_CONFIG1<2>
+#define ICM42670_I3C_DDR_EN_SHIFT 2    // ICM42670_REG_INTF_CONFIG1<2>
+#define ICM42670_CLKSEL_BITS      0x03 // ICM42670_REG_INTF_CONFIG1<1:0>
+#define ICM42670_CLKSEL_SHIFT     0    // ICM42670_REG_INTF_CONFIG1<1:0>
+
+#define ICM42670_DATA_RDY_INT_BITS  0x01 // ICM42670_REG_INT_STATUS_DRDY<0>
+#define ICM42670_DATA_RDY_INT_SHIFT 0    // ICM42670_REG_INT_STATUS_DRDY<0>
+
+#define ICM42670_ST_INT_BITS          0x80 // ICM42670_REG_INT_STATUS<7>
+#define ICM42670_ST_INT_SHIFT         7    // ICM42670_REG_INT_STATUS<7>
+#define ICM42670_FSYNC_INT_BITS       0x40 // ICM42670_REG_INT_STATUS<6>
+#define ICM42670_FSYNC_INT_SHIFT      6    // ICM42670_REG_INT_STATUS<6>
+#define ICM42670_PLL_RDY_INT_BITS     0x20 // ICM42670_REG_INT_STATUS<5>
+#define ICM42670_PLL_RDY_INT_SHIFT    5    // ICM42670_REG_INT_STATUS<5>
+#define ICM42670_RESET_DONE_INT_BITS  0x10 // ICM42670_REG_INT_STATUS<4>
+#define ICM42670_RESET_DONE_INT_SHIFT 4    // ICM42670_REG_INT_STATUS<4>
+#define ICM42670_FIFO_THS_INT_BITS    0x04 // ICM42670_REG_INT_STATUS<2>
+#define ICM42670_FIFO_THS_INT_SHIFT   2    // ICM42670_REG_INT_STATUS<2>
+#define ICM42670_FIFO_FULL_INT_BITS   0x02 // ICM42670_REG_INT_STATUS<1>
+#define ICM42670_FIFO_FULL_INT_SHIFT  1    // ICM42670_REG_INT_STATUS<1>
+#define ICM42670_AGC_RDY_INT_BITS     0x01 // ICM42670_REG_INT_STATUS<0>
+#define ICM42670_AGC_RDY_INT_SHIFT    0    // ICM42670_REG_INT_STATUS<0>
+
+#define ICM42670_SMD_INT_BITS    0x08 // ICM42670_REG_INT_STATUS2<3>
+#define ICM42670_SMD_INT_SHIFT   3    // ICM42670_REG_INT_STATUS2<3>
+#define ICM42670_WOM_X_INT_BITS  0x04 // ICM42670_REG_INT_STATUS2<2>
+#define ICM42670_WOM_X_INT_SHIFT 2    // ICM42670_REG_INT_STATUS2<2>
+#define ICM42670_WOM_Y_INT_BITS  0x02 // ICM42670_REG_INT_STATUS2<1>
+#define ICM42670_WOM_Y_INT_SHIFT 1    // ICM42670_REG_INT_STATUS2<1>
+#define ICM42670_WOM_Z_INT_BITS  0x01 // ICM42670_REG_INT_STATUS2<0>
+#define ICM42670_WOM_Z_INT_SHIFT 0    // ICM42670_REG_INT_STATUS2<0>
+
+#define ICM42670_STEP_DET_INT_BITS      0x20 // ICM42670_REG_INT_STATUS3<5>
+#define ICM42670_STEP_DET_INT_SHIFT     5    // ICM42670_REG_INT_STATUS3<5>
+#define ICM42670_STEP_CNT_OVF_INT_BITS  0x10 // ICM42670_REG_INT_STATUS3<4>
+#define ICM42670_STEP_CNT_OVF_INT_SHIFT 4    // ICM42670_REG_INT_STATUS3<4>
+#define ICM42670_TILT_DET_INT_BITS      0x08 // ICM42670_REG_INT_STATUS3<3>
+#define ICM42670_TILT_DET_INT_SHIFT     3    // ICM42670_REG_INT_STATUS3<3>
+#define ICM42670_FF_DET_INT_BITS        0x04 // ICM42670_REG_INT_STATUS3<2>
+#define ICM42670_FF_DET_INT_SHIFT       2    // ICM42670_REG_INT_STATUS3<2>
+#define ICM42670_LOWG_DET_INT_BITS      0x02 // ICM42670_REG_INT_STATUS3<1>
+#define ICM42670_LOWG_DET_INT_SHIFT     1    // ICM42670_REG_INT_STATUS3<1>
+
+#define CHECK(x)                                                                                                       \
+    do                                                                                                                 \
+    {                                                                                                                  \
+        esp_err_t __;                                                                                                  \
+        if ((__ = x) != ESP_OK)                                                                                        \
+            return __;                                                                                                 \
+    }                                                                                                                  \
+    while (0)
+#define CHECK_ARG(VAL)                                                                                                 \
+    do                                                                                                                 \
+    {                                                                                                                  \
+        if (!(VAL))                                                                                                    \
+            return ESP_ERR_INVALID_ARG;                                                                                \
+    }                                                                                                                  \
+    while (0)
+
+static inline esp_err_t write_register(icm42670_t *dev, uint8_t reg, uint8_t value)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_write_reg(&dev->i2c_dev, reg, &value, 1);
+}
+
+static inline esp_err_t read_register(icm42670_t *dev, uint8_t reg, uint8_t *value)
+{
+    CHECK_ARG(dev && value);
+
+    return i2c_dev_read_reg(&dev->i2c_dev, reg, value, 1);
+}
+
+static inline esp_err_t read_register_16(icm42670_t *dev, uint8_t upper_byte_reg, int16_t *value)
+{
+    CHECK_ARG(dev && value);
+
+    esp_err_t err;
+    uint8_t reg_0, reg_1;
+    err = read_register(dev, upper_byte_reg, &reg_1);
+    err = read_register(dev, upper_byte_reg + 1, &reg_0);
+    *value = reg_0 | (reg_1 << 8);
+
+    return err;
+}
+
+static inline esp_err_t manipulate_register(icm42670_t *dev, uint8_t reg_addr, uint8_t mask, uint8_t shift,
+    uint8_t value)
+{
+    CHECK_ARG(dev);
+
+    uint8_t reg;
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, reg_addr, &reg));
+    reg = (reg & ~mask) | (value << shift);
+    I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, reg_addr, reg));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+static inline esp_err_t read_mreg_register(icm42670_t *dev, icm42670_mreg_number_t mreg_num, uint8_t reg,
+    uint8_t *value)
+{
+    CHECK_ARG(dev && value);
+
+    bool mclk_rdy;
+    CHECK(icm42670_get_mclk_rdy(dev, &mclk_rdy));
+    if (!mclk_rdy)
+    {
+        ESP_LOGE(TAG, "MCLK not running, required to access MREG");
+        return ESP_ERR_INVALID_RESPONSE;
+    }
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, ICM42670_REG_BLK_SEL_R, mreg_num));
+    I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, ICM42670_REG_MADDR_R, reg));
+    ets_delay_us(10); // Wait for 10us until MREG write is complete
+    I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, ICM42670_REG_M_R, value));
+    ets_delay_us(10);
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+static inline esp_err_t write_mreg_register(icm42670_t *dev, icm42670_mreg_number_t mreg_num, uint8_t reg,
+    uint8_t value)
+{
+    CHECK_ARG(dev);
+
+    bool mclk_rdy;
+    CHECK(icm42670_get_mclk_rdy(dev, &mclk_rdy));
+    if (!mclk_rdy)
+    {
+        ESP_LOGE(TAG, "MCLK not running, required to access MREG");
+        return ESP_ERR_INVALID_RESPONSE;
+    }
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, ICM42670_REG_BLK_SEL_W, mreg_num));
+    I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, ICM42670_REG_MADDR_W, reg));
+    I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, ICM42670_REG_M_W, value));
+    ets_delay_us(10); // Wait for 10us until MREG write is complete
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+static inline esp_err_t manipulate_mreg_register(icm42670_t *dev, icm42670_mreg_number_t mreg_num, uint8_t reg_addr,
+    uint8_t mask, uint8_t shift, uint8_t value)
+{
+    CHECK_ARG(dev);
+
+    uint8_t reg;
+    CHECK(read_mreg_register(dev, mreg_num, reg_addr, &reg));
+    reg = (reg & ~mask) | (value << shift);
+    CHECK(write_mreg_register(dev, mreg_num, reg_addr, reg));
+
+    return ESP_OK;
+}
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t icm42670_init_desc(icm42670_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+
+    if (addr != ICM42670_I2C_ADDR_GND && addr != ICM42670_I2C_ADDR_VCC)
+    {
+        ESP_LOGE(TAG, "Invalid I2C address `0x%x`: must be one of 0x%x, 0x%x", addr, ICM42670_I2C_ADDR_GND,
+            ICM42670_I2C_ADDR_VCC);
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    dev->i2c_dev.port = port;
+    dev->i2c_dev.addr = addr;
+    dev->i2c_dev.cfg.sda_io_num = sda_gpio;
+    dev->i2c_dev.cfg.scl_io_num = scl_gpio;
+    dev->i2c_dev.timeout_ticks = 0; // set to default
+#if HELPER_TARGET_IS_ESP32
+    dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(&dev->i2c_dev);
+}
+
+esp_err_t icm42670_free_desc(icm42670_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(&dev->i2c_dev);
+}
+
+esp_err_t icm42670_init(icm42670_t *dev)
+{
+    CHECK_ARG(dev);
+    uint8_t reg;
+
+    // check who_am_i register
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, ICM42670_REG_WHO_AM_I, &reg));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+    if (reg != 0x67)
+    {
+        ESP_LOGE(TAG, "Error initializing ICM42670, who_am_i register did not return 0x67");
+        return ESP_ERR_INVALID_RESPONSE;
+    }
+    ESP_LOGD(TAG, "Init: Chip ICM42670 detected");
+
+    // flush FIFO
+    CHECK(icm42670_flush_fifo(dev));
+    // perform signal path reset
+    CHECK(icm42670_reset(dev));
+    ESP_LOGD(TAG, "Init: Soft-Reset performed");
+
+    // wait 10ms
+    vTaskDelay(pdMS_TO_TICKS(10));
+
+    // set device in IDLE power state
+    CHECK(icm42670_set_idle_pwr_mode(dev, true));
+
+    // wait 10ms
+    vTaskDelay(pdMS_TO_TICKS(10));
+
+    // check if internal clock is running
+    bool mclk_rdy = false;
+    CHECK(icm42670_get_mclk_rdy(dev, &mclk_rdy));
+    if (!mclk_rdy)
+    {
+        ESP_LOGE(TAG, "Error initializing icm42670, Internal clock not running");
+        return ESP_ERR_INVALID_RESPONSE;
+    }
+    ESP_LOGD(TAG, "Init: Internal clock running");
+
+    return ESP_OK;
+}
+
+esp_err_t icm42670_set_idle_pwr_mode(icm42670_t *dev, bool enable_idle)
+{
+    CHECK_ARG(dev);
+
+    CHECK(manipulate_register(dev, ICM42670_REG_PWR_MGMT0, ICM42670_IDLE_BITS, ICM42670_IDLE_SHIFT,
+        (uint8_t)enable_idle));
+
+    return ESP_OK;
+}
+
+esp_err_t icm42670_set_gyro_pwr_mode(icm42670_t *dev, icm42670_gyro_pwr_mode_t pwr_mode)
+{
+    CHECK_ARG(dev);
+
+    CHECK(manipulate_register(dev, ICM42670_REG_PWR_MGMT0, ICM42670_GYRO_MODE_BITS, ICM42670_GYRO_MODE_SHIFT, pwr_mode));
+    // no register writes should be performed within the next 200us
+    ets_delay_us(300);
+
+    return ESP_OK;
+}
+
+esp_err_t icm42670_set_accel_pwr_mode(icm42670_t *dev, icm42670_accel_pwr_mode_t pwr_mode)
+{
+    CHECK_ARG(dev);
+
+    // certain odr and avg settings are not allowed in LP or LN mode
+    icm42670_accel_odr_t odr;
+    icm42670_accel_avg_t avg;
+    CHECK(icm42670_get_accel_odr(dev, &odr));
+    CHECK(icm42670_get_accel_avg(dev, &avg));
+
+    if ((pwr_mode == ICM42670_ACCEL_ENABLE_LP_MODE)
+        && ((odr == ICM42670_ACCEL_ODR_800HZ) || (odr == ICM42670_ACCEL_ODR_1_6KHZ)
+            || ((odr == ICM42670_ACCEL_ODR_200HZ) && (avg == ICM42670_ACCEL_AVG_64X))))
+    {
+        ESP_LOGE(TAG, "Accel ODR and AVG settings invalid for Low-power mode");
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    if ((pwr_mode == ICM42670_ACCEL_ENABLE_LN_MODE)
+        && ((odr == ICM42670_ACCEL_ODR_6_25HZ) || (odr == ICM42670_ACCEL_ODR_3_125HZ)
+            || (odr == ICM42670_ACCEL_ODR_1_5625HZ)))
+    {
+        ESP_LOGE(TAG, "Accel ODR settings invalid for Low-noise mode");
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    CHECK(manipulate_register(dev, ICM42670_REG_PWR_MGMT0, ICM42670_ACCEL_MODE_BITS, ICM42670_ACCEL_MODE_SHIFT,
+        pwr_mode));
+
+    return ESP_OK;
+}
+
+esp_err_t icm42670_set_low_power_clock(icm42670_t *dev, icm42670_lp_clock_source_t clock_source)
+{
+    CHECK_ARG(dev);
+
+    CHECK(manipulate_register(dev, ICM42670_REG_PWR_MGMT0, ICM42670_ACCEL_LP_CLK_SEL_BITS,
+        ICM42670_ACCEL_LP_CLK_SEL_SHIFT, clock_source));
+
+    return ESP_OK;
+}
+
+esp_err_t icm42670_read_raw_data(icm42670_t *dev, uint8_t data_register, int16_t *data)
+{
+    CHECK_ARG(dev && data);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, read_register_16(dev, data_register, data));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t icm42670_read_temperature(icm42670_t *dev, float *temperature)
+{
+    CHECK_ARG(dev && temperature);
+
+    int16_t reg;
+    CHECK(icm42670_read_raw_data(dev, ICM42670_REG_TEMP_DATA1, &reg));
+    *temperature = (reg / 128.0) + 25;
+
+    return ESP_OK;
+}
+
+esp_err_t icm42670_reset(icm42670_t *dev)
+{
+    CHECK_ARG(dev);
+
+    uint8_t reg = 1 << ICM42670_SOFT_RESET_DEVICE_CONFIG_SHIFT;
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, ICM42670_REG_SIGNAL_PATH_RESET, reg));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t icm42670_flush_fifo(icm42670_t *dev)
+{
+    CHECK_ARG(dev);
+
+    uint8_t reg = 1 << ICM42670_FIFO_FLUSH_SHIFT;
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, ICM42670_REG_SIGNAL_PATH_RESET, reg));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+    ets_delay_us(2); // flush is done within 1.5us
+
+    return ESP_OK;
+}
+
+esp_err_t icm42670_set_gyro_fsr(icm42670_t *dev, icm42670_gyro_fsr_t range)
+{
+    CHECK_ARG(dev);
+    return manipulate_register(dev, ICM42670_REG_GYRO_CONFIG0, ICM42670_GYRO_UI_FS_SEL_BITS,
+        ICM42670_GYRO_UI_FS_SEL_SHIFT, range);
+}
+
+esp_err_t icm42670_set_gyro_odr(icm42670_t *dev, icm42670_gyro_odr_t odr)
+{
+    CHECK_ARG(dev);
+    return manipulate_register(dev, ICM42670_REG_GYRO_CONFIG0, ICM42670_GYRO_ODR_BITS, ICM42670_GYRO_ODR_SHIFT, odr);
+}
+
+esp_err_t icm42670_set_accel_fsr(icm42670_t *dev, icm42670_accel_fsr_t range)
+{
+    CHECK_ARG(dev);
+    return manipulate_register(dev, ICM42670_REG_ACCEL_CONFIG0, ICM42670_ACCEL_UI_FS_SEL_BITS,
+        ICM42670_ACCEL_UI_FS_SEL_SHIFT, range);
+}
+
+esp_err_t icm42670_set_accel_odr(icm42670_t *dev, icm42670_accel_odr_t odr)
+{
+    CHECK_ARG(dev);
+    return manipulate_register(dev, ICM42670_REG_ACCEL_CONFIG0, ICM42670_ACCEL_ODR_BITS, ICM42670_ACCEL_ODR_SHIFT, odr);
+}
+
+esp_err_t icm42670_set_temp_lpf(icm42670_t *dev, icm42670_temp_lfp_t lpf_bw)
+{
+    CHECK_ARG(dev);
+    return manipulate_register(dev, ICM42670_REG_TEMP_CONFIG0, ICM42670_TEMP_FILT_BW_BITS, ICM42670_TEMP_FILT_BW_SHIFT,
+        lpf_bw);
+}
+
+esp_err_t icm42670_set_gyro_lpf(icm42670_t *dev, icm42670_gyro_lfp_t lpf_bw)
+{
+    CHECK_ARG(dev);
+    return manipulate_register(dev, ICM42670_REG_GYRO_CONFIG1, ICM42670_GYRO_UI_FILT_BW_BITS,
+        ICM42670_GYRO_UI_FILT_BW_SHIFT, lpf_bw);
+}
+
+esp_err_t icm42670_set_accel_lpf(icm42670_t *dev, icm42670_accel_lfp_t lpf_bw)
+{
+    CHECK_ARG(dev);
+    return manipulate_register(dev, ICM42670_REG_ACCEL_CONFIG1, ICM42670_ACCEL_UI_FILT_BW_BITS,
+        ICM42670_ACCEL_UI_FILT_BW_SHIFT, lpf_bw);
+}
+
+esp_err_t icm42670_set_accel_avg(icm42670_t *dev, icm42670_accel_avg_t avg)
+{
+    CHECK_ARG(dev);
+    return manipulate_register(dev, ICM42670_REG_ACCEL_CONFIG1, ICM42670_ACCEL_UI_AVG_BITS, ICM42670_ACCEL_UI_AVG_SHIFT,
+        avg);
+}
+
+esp_err_t icm42670_config_int_pin(icm42670_t *dev, uint8_t int_pin, icm42670_int_config_t config)
+{
+    CHECK_ARG(dev && int_pin < 3 && int_pin > 0);
+
+    uint8_t reg = config.mode << 2 | config.drive << 1 | config.polarity;
+    if (int_pin == 2)
+    {
+        return manipulate_register(dev, ICM42670_REG_INT_CONFIG, 0b00111000, ICM42670_INT2_POLARITY_SHIFT, reg);
+    }
+    else
+    {
+        return manipulate_register(dev, ICM42670_REG_INT_CONFIG, 0b00000111, ICM42670_INT1_POLARITY_SHIFT, reg);
+    }
+}
+
+esp_err_t icm42670_set_int_sources(icm42670_t *dev, uint8_t int_pin, icm42670_int_source_t sources)
+{
+    CHECK_ARG(dev && int_pin < 3 && int_pin > 0);
+
+    uint8_t reg1 = 0, reg2 = 0;
+    if (sources.self_test_done)
+        reg1 = reg1 | (1 << ICM42670_ST_INT1_EN_SHIFT);
+    if (sources.fsync)
+        reg1 = reg1 | (1 << ICM42670_FSYNC_INT1_EN_SHIFT);
+    if (sources.pll_ready)
+        reg1 = reg1 | (1 << ICM42670_PLL_RDY_INT1_EN_SHIFT);
+    if (sources.reset_done)
+        reg1 = reg1 | (1 << ICM42670_RESET_DONE_INT1_EN_SHIFT);
+    if (sources.data_ready)
+        reg1 = reg1 | (1 << ICM42670_DRDY_INT1_EN_SHIFT);
+    if (sources.fifo_threshold)
+        reg1 = reg1 | (1 << ICM42670_FIFO_THS_INT1_EN_SHIFT);
+    if (sources.fifo_full)
+        reg1 = reg1 | (1 << ICM42670_FIFO_FULL_INT1_EN_SHIFT);
+    if (sources.agc_ready)
+        reg1 = reg1 | (1 << ICM42670_AGC_RDY_INT1_EN_SHIFT);
+    if (sources.i3c_error)
+        reg2 = reg2 | (1 << ICM42670_I3C_PROTOCOL_ERROR_INT1_EN_SHIFT);
+    if (sources.smd)
+        reg2 = reg2 | (1 << ICM42670_SMD_INT1_EN_SHIFT);
+    if (sources.wom_z)
+        reg2 = reg2 | (1 << ICM42670_WOM_Z_INT1_EN_SHIFT);
+    if (sources.wom_y)
+        reg2 = reg2 | (1 << ICM42670_WOM_Y_INT1_EN_SHIFT);
+    if (sources.wom_x)
+        reg2 = reg2 | (1 << ICM42670_WOM_X_INT1_EN_SHIFT);
+
+    if (int_pin == 1)
+    {
+        I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+        I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, ICM42670_REG_INT_SOURCE0, reg1));
+        I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, ICM42670_REG_INT_SOURCE1, reg2));
+        I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+    }
+    else
+    {
+        I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+        I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, ICM42670_REG_INT_SOURCE3, reg1));
+        I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, ICM42670_REG_INT_SOURCE4, reg2));
+        I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+    }
+
+    return ESP_OK;
+}
+
+esp_err_t icm42670_config_wom(icm42670_t *dev, icm42670_wom_config_t config)
+{
+    CHECK_ARG(dev);
+
+    CHECK(manipulate_register(dev, ICM42670_REG_WOM_CONFIG, ICM42670_WOM_INT_DUR_BITS, ICM42670_WOM_INT_DUR_SHIFT,
+        config.trigger));
+    CHECK(manipulate_register(dev, ICM42670_REG_WOM_CONFIG, ICM42670_WOM_INT_MODE_BITS, ICM42670_WOM_INT_MODE_SHIFT,
+        config.logical_mode));
+    CHECK(manipulate_register(dev, ICM42670_REG_WOM_CONFIG, ICM42670_WOM_MODE_BITS, ICM42670_WOM_MODE_SHIFT,
+        config.reference));
+
+    // WoM threshold values
+    CHECK(write_mreg_register(dev, ICM42670_MREG1_RW, ICM42670_REG_ACCEL_WOM_X_THR, config.wom_x_threshold));
+    CHECK(write_mreg_register(dev, ICM42670_MREG1_RW, ICM42670_REG_ACCEL_WOM_Y_THR, config.wom_y_threshold));
+    CHECK(write_mreg_register(dev, ICM42670_MREG1_RW, ICM42670_REG_ACCEL_WOM_Z_THR, config.wom_z_threshold));
+
+    return ESP_OK;
+}
+
+esp_err_t icm42670_enable_wom(icm42670_t *dev, bool enable)
+{
+    CHECK_ARG(dev);
+    return manipulate_register(dev, ICM42670_REG_WOM_CONFIG, ICM42670_WOM_EN_BITS, ICM42670_WOM_EN_SHIFT, enable);
+}
+
+esp_err_t icm42670_get_mclk_rdy(icm42670_t *dev, bool *mclk_rdy)
+{
+    CHECK_ARG(dev && mclk_rdy);
+
+    uint8_t reg;
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, ICM42670_REG_MCLK_RDY, &reg));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+    if ((reg & ICM42670_MCLK_RDY_BITS) >> ICM42670_MCLK_RDY_SHIFT)
+        *mclk_rdy = true;
+    else
+        *mclk_rdy = false;
+
+    return ESP_OK;
+}
+
+esp_err_t icm42670_get_accel_odr(icm42670_t *dev, icm42670_accel_odr_t *odr)
+{
+    CHECK_ARG(dev && odr);
+
+    uint8_t reg;
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, ICM42670_REG_ACCEL_CONFIG0, &reg));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+    *odr = (reg & ICM42670_ACCEL_ODR_BITS) >> ICM42670_ACCEL_ODR_SHIFT;
+
+    return ESP_OK;
+}
+
+esp_err_t icm42670_get_accel_avg(icm42670_t *dev, icm42670_accel_avg_t *avg)
+{
+    CHECK_ARG(dev && avg);
+
+    uint8_t reg;
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, ICM42670_REG_ACCEL_CONFIG1, &reg));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+    *avg = (reg & ICM42670_ACCEL_UI_AVG_BITS) >> ICM42670_ACCEL_UI_AVG_SHIFT;
+
+    return ESP_OK;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/icm42670/icm42670.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,606 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2022 Jan Veeh <jan.veeh@motius.de>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/**
+ * @file icm42670.h
+ * @defgroup icm42670 icm42670
+ * @{
+ *
+ * ESP-IDF driver for TDK ICM-42670-P IMU (found on ESP-RS board)
+ *
+ * Copyright (c) 2022 Jan Veeh (jan.veeh@motius.de)
+ *
+ * ISC Licensed as described in the file LICENSE
+ */
+
+#ifndef __ICM42670_H__
+#define __ICM42670_H__
+
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define ICM42670_I2C_ADDR_GND 0x68
+#define ICM42670_I2C_ADDR_VCC 0x69
+
+// Registers USER BANK 0
+#define ICM42670_REG_MCLK_RDY          0x00
+#define ICM42670_REG_DEVICE_CONFIG     0x01
+#define ICM42670_REG_SIGNAL_PATH_RESET 0x02
+#define ICM42670_REG_DRIVE_CONFIG1     0x03
+#define ICM42670_REG_DRIVE_CONFIG2     0x04
+#define ICM42670_REG_DRIVE_CONFIG3     0x05
+#define ICM42670_REG_INT_CONFIG        0x06
+#define ICM42670_REG_TEMP_DATA1        0x09
+#define ICM42670_REG_TEMP_DATA0        0x0A
+#define ICM42670_REG_ACCEL_DATA_X1     0x0B
+#define ICM42670_REG_ACCEL_DATA_X0     0x0C
+#define ICM42670_REG_ACCEL_DATA_Y1     0x0D
+#define ICM42670_REG_ACCEL_DATA_Y0     0x0E
+#define ICM42670_REG_ACCEL_DATA_Z1     0x0F
+#define ICM42670_REG_ACCEL_DATA_Z0     0x10
+#define ICM42670_REG_GYRO_DATA_X1      0x11
+#define ICM42670_REG_GYRO_DATA_X0      0x12
+#define ICM42670_REG_GYRO_DATA_Y1      0x13
+#define ICM42670_REG_GYRO_DATA_Y0      0x14
+#define ICM42670_REG_GYRO_DATA_Z1      0x15
+#define ICM42670_REG_GYRO_DATA_Z0      0x16
+#define ICM42670_REG_TMST_FSYNCH       0x17
+#define ICM42670_REG_TMST_FSYNCL       0x18
+#define ICM42670_REG_APEX_DATA4        0x1D
+#define ICM42670_REG_APEX_DATA5        0x1E
+#define ICM42670_REG_PWR_MGMT0         0x1F
+#define ICM42670_REG_GYRO_CONFIG0      0x20
+#define ICM42670_REG_ACCEL_CONFIG0     0x21
+#define ICM42670_REG_TEMP_CONFIG0      0x22
+#define ICM42670_REG_GYRO_CONFIG1      0x23
+#define ICM42670_REG_ACCEL_CONFIG1     0x24
+#define ICM42670_REG_APEX_CONFIG0      0x25
+#define ICM42670_REG_APEX_CONFIG1      0x26
+#define ICM42670_REG_WOM_CONFIG        0x27
+#define ICM42670_REG_FIFO_CONFIG1      0x28
+#define ICM42670_REG_FIFO_CONFIG2      0x29
+#define ICM42670_REG_FIFO_CONFIG3      0x2A
+#define ICM42670_REG_INT_SOURCE0       0x2B
+#define ICM42670_REG_INT_SOURCE1       0x2C
+#define ICM42670_REG_INT_SOURCE3       0x2D
+#define ICM42670_REG_INT_SOURCE4       0x2E
+#define ICM42670_REG_FIFO_LOST_PKT0    0x2F
+#define ICM42670_REG_FIFO_LOST_PKT1    0x30
+#define ICM42670_REG_APEX_DATA0        0x31
+#define ICM42670_REG_APEX_DATA1        0x32
+#define ICM42670_REG_APEX_DATA2        0x33
+#define ICM42670_REG_APEX_DATA3        0x34
+#define ICM42670_REG_INTF_CONFIG0      0x35
+#define ICM42670_REG_INTF_CONFIG1      0x36
+#define ICM42670_REG_INT_STATUS_DRDY   0x39
+#define ICM42670_REG_INT_STATUS        0x3A
+#define ICM42670_REG_INT_STATUS2       0x3B
+#define ICM42670_REG_INT_STATUS3       0x3C
+#define ICM42670_REG_FIFO_COUNTH       0x3D
+#define ICM42670_REG_FIFO_COUNTL       0x3E
+#define ICM42670_REG_FIFO_DATA         0x3F
+#define ICM42670_REG_WHO_AM_I          0x75
+#define ICM42670_REG_BLK_SEL_W         0x79
+#define ICM42670_REG_MADDR_W           0x7A
+#define ICM42670_REG_M_W               0x7B
+#define ICM42670_REG_BLK_SEL_R         0x7C
+#define ICM42670_REG_MADDR_R           0x7D
+#define ICM42670_REG_M_R               0x7E
+
+// MREG1 registers
+#define ICM42670_REG_TMST_CONFIG1    0x00
+#define ICM42670_REG_FIFO_CONFIG5    0x01
+#define ICM42670_REG_FIFO_CONFIG6    0x02
+#define ICM42670_REG_FSYNC_CONFIG    0x03
+#define ICM42670_REG_INT_CONFIG0     0x04
+#define ICM42670_REG_INT_CONFIG1     0x05
+#define ICM42670_REG_SENSOR_CONFIG3  0x06
+#define ICM42670_REG_ST_CONFIG       0x13
+#define ICM42670_REG_SELFTEST        0x14
+#define ICM42670_REG_INTF_CONFIG6    0x23
+#define ICM42670_REG_INTF_CONFIG10   0x25
+#define ICM42670_REG_INTF_CONFIG7    0x28
+#define ICM42670_REG_OTP_CONFIG      0x2B
+#define ICM42670_REG_INT_SOURCE6     0x2F
+#define ICM42670_REG_INT_SOURCE7     0x30
+#define ICM42670_REG_INT_SOURCE8     0x31
+#define ICM42670_REG_INT_SOURCE9     0x32
+#define ICM42670_REG_INT_SOURCE10    0x33
+#define ICM42670_REG_APEX_CONFIG2    0x44
+#define ICM42670_REG_APEX_CONFIG3    0x45
+#define ICM42670_REG_APEX_CONFIG4    0x46
+#define ICM42670_REG_APEX_CONFIG5    0x47
+#define ICM42670_REG_APEX_CONFIG9    0x48
+#define ICM42670_REG_APEX_CONFIG10   0x49
+#define ICM42670_REG_APEX_CONFIG11   0x4A
+#define ICM42670_REG_ACCEL_WOM_X_THR 0x4B
+#define ICM42670_REG_ACCEL_WOM_Y_THR 0x4C
+#define ICM42670_REG_ACCEL_WOM_Z_THR 0x4D
+#define ICM42670_REG_OFFSET_USER0    0x4E
+#define ICM42670_REG_OFFSET_USER1    0x4F
+#define ICM42670_REG_OFFSET_USER2    0x50
+#define ICM42670_REG_OFFSET_USER3    0x51
+#define ICM42670_REG_OFFSET_USER4    0x52
+#define ICM42670_REG_OFFSET_USER5    0x53
+#define ICM42670_REG_OFFSET_USER6    0x54
+#define ICM42670_REG_OFFSET_USER7    0x55
+#define ICM42670_REG_OFFSET_USER8    0x56
+#define ICM42670_REG_ST_STATUS1      0x63
+#define ICM42670_REG_ST_STATUS2      0x64
+#define ICM42670_REG_FDR_CONFIG      0x66
+#define ICM42670_REG_APEX_CONFIG12   0x67
+
+// MREG2 registers
+#define ICM42670_REG_OTP_CTRL7 0x06
+
+// MREG3 registers
+#define ICM42670_REG_XA_ST_DATA 0x00
+#define ICM42670_REG_YA_ST_DATA 0x01
+#define ICM42670_REG_ZA_ST_DATA 0x02
+#define ICM42670_REG_XG_ST_DATA 0x03
+#define ICM42670_REG_YG_ST_DATA 0x04
+#define ICM42670_REG_ZG_ST_DATA 0x05
+
+/* Gyro power mode */
+typedef enum {
+    ICM42670_GYRO_DISABLE = 0b00,
+    ICM42670_GYRO_STANDBY = 0b01,
+    ICM42670_GYRO_ENABLE_LN_MODE = 0b11
+} icm42670_gyro_pwr_mode_t;
+
+/* Accelerometer power mode */
+typedef enum {
+    ICM42670_ACCEL_DISABLE = 0b00,
+    ICM42670_ACCEL_ENABLE_LP_MODE = 0b10,
+    ICM42670_ACCEL_ENABLE_LN_MODE = 0b11
+} icm42670_accel_pwr_mode_t;
+
+/* Accelerometer low power mode clock source */
+typedef enum { 
+    ICM42670_LP_CLK_WUO = 0, 
+    ICM42670_LP_CLK_RCO = 1 
+} icm42670_lp_clock_source_t;
+
+/* Gyro FSR (full scale range) */
+typedef enum {
+    ICM42670_GYRO_RANGE_2000DPS = 0b00,
+    ICM42670_GYRO_RANGE_1000DPS = 0b01,
+    ICM42670_GYRO_RANGE_500DPS = 0b10,
+    ICM42670_GYRO_RANGE_250DPS = 0b11
+} icm42670_gyro_fsr_t;
+
+/* Gyro ODR (output data rate) */
+typedef enum {
+    ICM42670_GYRO_ODR_12_5HZ = 0b1100,
+    ICM42670_GYRO_ODR_25HZ = 0b1011,
+    ICM42670_GYRO_ODR_50HZ = 0b1010,
+    ICM42670_GYRO_ODR_100HZ = 0b1001,
+    ICM42670_GYRO_ODR_200HZ = 0b1000,
+    ICM42670_GYRO_ODR_400HZ = 0b0111,
+    ICM42670_GYRO_ODR_800HZ = 0b0110,
+    ICM42670_GYRO_ODR_1_6KHZ = 0b0101
+} icm42670_gyro_odr_t;
+
+/* Accelerometer FSR (full scale range) */
+typedef enum {
+    ICM42670_ACCEL_RANGE_16G = 0b00,
+    ICM42670_ACCEL_RANGE_8G = 0b01,
+    ICM42670_ACCEL_RANGE_4G = 0b10,
+    ICM42670_ACCEL_RANGE_2G = 0b11
+} icm42670_accel_fsr_t;
+
+/* Accelerometer ODR (output data rate) */
+typedef enum {
+    ICM42670_ACCEL_ODR_1_5625HZ = 0b1111,
+    ICM42670_ACCEL_ODR_3_125HZ = 0b1110,
+    ICM42670_ACCEL_ODR_6_25HZ = 0b1101,
+    ICM42670_ACCEL_ODR_12_5HZ = 0b1100,
+    ICM42670_ACCEL_ODR_25HZ = 0b1011,
+    ICM42670_ACCEL_ODR_50HZ = 0b1010,
+    ICM42670_ACCEL_ODR_100HZ = 0b1001,
+    ICM42670_ACCEL_ODR_200HZ = 0b1000,
+    ICM42670_ACCEL_ODR_400HZ = 0b0111,
+    ICM42670_ACCEL_ODR_800HZ = 0b0110,
+    ICM42670_ACCEL_ODR_1_6KHZ = 0b0101
+} icm42670_accel_odr_t;
+
+/* Temperature LPF (low pass filter) */
+typedef enum {
+    ICM42670_TEMP_LFP_BYPASSED = 0b000,
+    ICM42670_TEMP_LFP_180HZ = 0b001,
+    ICM42670_TEMP_LFP_72HZ = 0b010,
+    ICM42670_TEMP_LFP_34HZ = 0b011,
+    ICM42670_TEMP_LFP_16HZ = 0b100,
+    ICM42670_TEMP_LFP_8HZ = 0b101,
+    ICM42670_TEMP_LFP_4HZ = 0b110
+} icm42670_temp_lfp_t;
+
+/* Gyro LPF (low pass filter) */
+typedef enum {
+    ICM42670_GYRO_LFP_BYPASSED = 0b000,
+    ICM42670_GYRO_LFP_180HZ = 0b001,
+    ICM42670_GYRO_LFP_121HZ = 0b010,
+    ICM42670_GYRO_LFP_73HZ = 0b011,
+    ICM42670_GYRO_LFP_53HZ = 0b100,
+    ICM42670_GYRO_LFP_34HZ = 0b101,
+    ICM42670_GYRO_LFP_25HZ = 0b110,
+    ICM42670_GYRO_LFP_16HZ = 0b111
+} icm42670_gyro_lfp_t;
+
+/* Accelerometer LPF (low pass filter) */
+typedef enum {
+    ICM42670_ACCEL_LFP_BYPASSED = 0b000,
+    ICM42670_ACCEL_LFP_180HZ = 0b001,
+    ICM42670_ACCEL_LFP_121HZ = 0b010,
+    ICM42670_ACCEL_LFP_73HZ = 0b011,
+    ICM42670_ACCEL_LFP_53HZ = 0b100,
+    ICM42670_ACCEL_LFP_34HZ = 0b101,
+    ICM42670_ACCEL_LFP_25HZ = 0b110,
+    ICM42670_ACCEL_LFP_16HZ = 0b111
+} icm42670_accel_lfp_t;
+
+/* Accelerometer averaging (for low power mode) */
+typedef enum {
+    ICM42670_ACCEL_AVG_2X = 0b000,
+    ICM42670_ACCEL_AVG_4X = 0b001,
+    ICM42670_ACCEL_AVG_8X = 0b010,
+    ICM42670_ACCEL_AVG_16X = 0b011,
+    ICM42670_ACCEL_AVG_32X = 0b100,
+    ICM42670_ACCEL_AVG_64X = 0b101
+} icm42670_accel_avg_t;
+
+/* Interrupt pin signal mode */
+typedef enum {
+    ICM42670_INT_MODE_PULSED = 0,
+    ICM42670_INT_MODE_LATCHED = 1
+} icm42670_int_mode_t;
+
+/* Interrupt pin signal type */
+typedef enum {
+    ICM42670_INT_DRIVE_OPEN_DRAIN = 0,
+    ICM42670_INT_DRIVE_PUSH_PULL = 1
+} icm42670_int_drive_t;
+
+/* Interrupt pin signal polarity */
+typedef enum {
+    ICM42670_INT_POLARITY_ACTIVE_LOW = 0,
+    ICM42670_INT_POLARITY_ACTIVE_HIGH = 1
+} icm42670_int_polarity_t;
+
+/* Interrupt pin configuration */
+typedef struct
+{
+    icm42670_int_mode_t mode;
+    icm42670_int_drive_t drive;
+    icm42670_int_polarity_t polarity;
+} icm42670_int_config_t;
+
+/* Interrupt source */
+typedef struct
+{
+    bool self_test_done;
+    bool fsync;
+    bool pll_ready;
+    bool reset_done;
+    bool data_ready;
+    bool fifo_threshold;
+    bool fifo_full;
+    bool agc_ready;
+    bool i3c_error;
+    bool smd;
+    bool wom_z;
+    bool wom_y;
+    bool wom_x;
+} icm42670_int_source_t;
+
+/* Wake on Motion interrupt assertion */
+typedef enum {
+    ICM42670_WOM_INT_DUR_FIRST = 0b00,
+    ICM42670_WOM_INT_DUR_SECOND = 0b01,
+    ICM42670_WOM_INT_DUR_THIRD = 0b10,
+    ICM42670_WOM_INT_DUR_FOURTH = 0b11
+} icm42670_wom_int_dur_t;
+
+/* Wake on Motion interrupt logical trigger */
+typedef enum {
+    ICM42670_WOM_INT_MODE_ALL_OR = 0,
+    ICM42670_WOM_INT_MODE_ALL_AND = 1
+} icm42670_wom_int_mode_t;
+
+/* Wake on Motion reference sample */
+typedef enum {
+    ICM42670_WOM_MODE_REF_INITIAL = 0,
+    ICM42670_WOM_MODE_REF_LAST = 1
+} icm42670_wom_mode_t;
+
+/* Wake on Motion configuration */
+typedef struct
+{
+    icm42670_wom_int_dur_t trigger;
+    icm42670_wom_int_mode_t logical_mode;
+    icm42670_wom_mode_t reference;
+    uint8_t wom_x_threshold; // 8-bit value between 0 and 1g (Resolution 1g/256=~3.9 mg)
+    uint8_t wom_y_threshold; // 8-bit value between 0 and 1g (Resolution 1g/256=~3.9 mg)
+    uint8_t wom_z_threshold; // 8-bit value between 0 and 1g (Resolution 1g/256=~3.9 mg)
+} icm42670_wom_config_t;
+
+/* MREG 1-3 access */
+typedef enum {
+    ICM42670_MREG1_RW = 0x00,
+    ICM42670_MREG2_RW = 0x28,
+    ICM42670_MREG3_RW = 0x50
+} icm42670_mreg_number_t;
+
+/**
+ * Device descriptor
+ */
+typedef struct
+{
+    i2c_dev_t i2c_dev;
+    // TODO: add more vars for configuration
+} icm42670_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev Device descriptor
+ * @param addr I2C device address, `ICM42670_I2C_ADDR_...` const
+ * @param port I2C port
+ * @param sda_gpio SDA GPIO pin
+ * @param scl_gpio SCL GPIO pin
+ * @return `ESP_OK` on success
+ */
+esp_err_t icm42670_init_desc(icm42670_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t icm42670_free_desc(icm42670_t *dev);
+
+/**
+ * @brief Initialize device
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t icm42670_init(icm42670_t *dev);
+
+/**
+ * @brief Set device power mode
+ *
+ * @param dev Device descriptor
+ * @param enable_idle bool to enable idle mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t icm42670_set_idle_pwr_mode(icm42670_t *dev, bool enable_idle);
+
+/**
+ * @brief Set gyro power mode
+ *
+ * @param dev Device descriptor
+ * @param pwr_mode struct of type icm42670_gyro_pwr_mode_t
+ * @return `ESP_OK` on success
+ */
+esp_err_t icm42670_set_gyro_pwr_mode(icm42670_t *dev, icm42670_gyro_pwr_mode_t pwr_mode);
+
+/**
+ * @brief Set accel power mode
+ *
+ * @param dev Device descriptor
+ * @param pwr_mode struct of type icm42670_accel_pwr_mode_t
+ * @return `ESP_OK` on success
+ */
+esp_err_t icm42670_set_accel_pwr_mode(icm42670_t *dev, icm42670_accel_pwr_mode_t pwr_mode);
+
+/**
+ * @brief Set clock source in LP mode
+ *
+ * @param dev Device descriptor
+ * @param clock_source struct of type icm42670_lp_clock_source_t
+ * @return `ESP_OK` on success
+ */
+esp_err_t icm42670_set_low_power_clock(icm42670_t *dev, icm42670_lp_clock_source_t clock_source);
+
+/**
+ * @brief Read temperature from device
+ *
+ * @param dev Device descriptor
+ * @param[out] temperature temperature, degree C
+ * @return `ESP_OK` on success
+ */
+esp_err_t icm42670_read_temperature(icm42670_t *dev, float *temperature);
+
+/**
+ * @brief Read 16-bit raw data registers (accelerometer and gyro values)
+ *
+ * @param dev Device descriptor
+ * @param data_register data register to read from
+ * @param[out] data accel or gyro data
+ * @return `ESP_OK` on success
+ */
+esp_err_t icm42670_read_raw_data(icm42670_t *dev, uint8_t data_register, int16_t *data);
+
+/**
+ * @brief Performs a soft-reset
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t icm42670_reset(icm42670_t *dev);
+
+/**
+ * @brief Wipes the FIFO
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t icm42670_flush_fifo(icm42670_t *dev);
+
+/**
+ * @brief Set the measurement FSR (Full Scale Range) of the gyro
+ *
+ * @param dev Device descriptor
+ * @param range struct of type icm42670_gyro_fsr_t
+ * @return `ESP_OK` on success
+ */
+esp_err_t icm42670_set_gyro_fsr(icm42670_t *dev, icm42670_gyro_fsr_t range);
+
+/**
+ * @brief Set the measurement ODR (Output Data Rate) of the gyro
+ *
+ * @param dev Device descriptor
+ * @param odr struct of type icm42670_gyro_odr_t
+ * @return `ESP_OK` on success
+ */
+esp_err_t icm42670_set_gyro_odr(icm42670_t *dev, icm42670_gyro_odr_t odr);
+
+/**
+ * @brief Set the measurement FSR (Full Scale Range) of the accelerometer
+ *
+ * @param dev Device descriptor
+ * @param range struct of type icm42670_accel_fsr_t
+ * @return `ESP_OK` on success
+ */
+esp_err_t icm42670_set_accel_fsr(icm42670_t *dev, icm42670_accel_fsr_t range);
+
+/**
+ * @brief Set the measurement ODR (Output Data Rate) of the accelerometer
+ *
+ * @param dev Device descriptor
+ * @param odr struct of type icm42670_accel_odr_t
+ * @return `ESP_OK` on success
+ */
+esp_err_t icm42670_set_accel_odr(icm42670_t *dev, icm42670_accel_odr_t odr);
+
+/**
+ * @brief Set the digital Low-Pass-Filter (LPF) of the temperature sensor
+ *
+ * @param dev Device descriptor
+ * @param lpf_bw struct of type icm42670_temp_lfp_t (bandwidth)
+ * @return `ESP_OK` on success
+ */
+esp_err_t icm42670_set_temp_lpf(icm42670_t *dev, icm42670_temp_lfp_t lpf_bw);
+
+/**
+ * @brief Set the digital Low-Pass-Filter (LPF) of the gyro
+ *
+ * @param dev Device descriptor
+ * @param lpf_bw struct of type icm42670_gyro_lfp_t (bandwidth)
+ * @return `ESP_OK` on success
+ */
+esp_err_t icm42670_set_gyro_lpf(icm42670_t *dev, icm42670_gyro_lfp_t lpf_bw);
+
+/**
+ * @brief Set the digital Low-Pass-Filter (LPF) of the accelerometer
+ *
+ * @param dev Device descriptor
+ * @param lpf_bw struct of type icm42670_accel_lfp_t (bandwidth)
+ * @return `ESP_OK` on success
+ */
+esp_err_t icm42670_set_accel_lpf(icm42670_t *dev, icm42670_accel_lfp_t lpf_bw);
+
+/**
+ * @brief Set the averaging filter of the accelerometer (ONLY IN LOW POWER MODE (LPM))
+ *        This field can not be changed, when accel sensor is in LPM!
+ *
+ * @param dev Device descriptor
+ * @param avg struct of type icm42670_accel_avg_t (averaging)
+ * @return `ESP_OK` on success
+ */
+esp_err_t icm42670_set_accel_avg(icm42670_t *dev, icm42670_accel_avg_t avg);
+
+/**
+ * @brief Configures the behaviour of an interrupt pin
+ *
+ * @param dev Device descriptor
+ * @param int_pin interrupt pin (1 or 2)
+ * @param config struct of type icm42670_int_config_t
+ * @return `ESP_OK` on success
+ */
+esp_err_t icm42670_config_int_pin(icm42670_t *dev, uint8_t int_pin, icm42670_int_config_t config);
+
+/**
+ * @brief Configures the sources for an interrupt
+ *
+ * @param dev Device descriptor
+ * @param int_pin interrupt pin (1 or 2)
+ * @param sources struct of type icm42670_int_source_t
+ * @return `ESP_OK` on success
+ */
+esp_err_t icm42670_set_int_sources(icm42670_t *dev, uint8_t int_pin, icm42670_int_source_t sources);
+
+/**
+ * @brief Configures the Wake on Motion (WoM) behaviour
+ *        WoM can only be configured if WoM is not enabled
+ *
+ * @param dev Device descriptor
+ * @param config struct of type icm42670_wom_config_t
+ * @return `ESP_OK` on success
+ */
+esp_err_t icm42670_config_wom(icm42670_t *dev, icm42670_wom_config_t config);
+
+/**
+ * @brief Enable or Disable Wake on Motion (WoM)
+ *
+ * @param dev Device descriptor
+ * @param enable true to enable, false to disable
+ * @return `ESP_OK` on success
+ */
+esp_err_t icm42670_enable_wom(icm42670_t *dev, bool enable);
+
+/**
+ * @brief Get the status of the internal clock
+ *
+ * @param dev Device descriptor
+ * @param mclk_rdy true if internal clock is running
+ * @return `ESP_OK` on success
+ */
+esp_err_t icm42670_get_mclk_rdy(icm42670_t *dev, bool *mclk_rdy);
+
+/**
+ * @brief Get the output data rate (ODR) of the accel
+ *
+ * @param dev Device descriptor
+ * @param odr pointer to icm42670_accel_odr_t
+ * @return `ESP_OK` on success
+ */
+esp_err_t icm42670_get_accel_odr(icm42670_t *dev, icm42670_accel_odr_t *odr);
+
+/**
+ * @brief Get the status of the accel averaging
+ *
+ * @param dev Device descriptor
+ * @param avg pointer to icm42670_accel_avg_t
+ * @return `ESP_OK` on success
+ */
+esp_err_t icm42670_get_accel_avg(icm42670_t *dev, icm42670_accel_avg_t *avg);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif // __ICM42670_H__
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ina219/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,23 @@
+---
+components:
+  - name: ina219
+    description: Driver for INA219/INA220 bidirectional current/power monitor
+    group: current
+    groups: []
+    code_owners: UncleRus
+    depends:
+      - name: i2cdev
+      - name: log
+      - name: esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2019
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ina219/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS ina219.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ina219/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+Copyright 2019 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ina219/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ina219/ina219.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,306 @@
+/*
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file ina219.c
+ *
+ * ESP-IDF driver for INA219/INA220 Zerø-Drift, Bidirectional
+ * Current/Power Monitor
+ *
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#include <esp_log.h>
+#include <math.h>
+#include <esp_idf_lib_helpers.h>
+#include "ina219.h"
+
+#define I2C_FREQ_HZ 1000000 // Max 1 MHz for esp-idf, but supports up to 2.56 MHz
+
+static const char *TAG = "ina219";
+
+#define REG_CONFIG      0
+#define REG_SHUNT_U     1
+#define REG_BUS_U       2
+#define REG_POWER       3
+#define REG_CURRENT     4
+#define REG_CALIBRATION 5
+
+#define BIT_RST   15
+#define BIT_BRNG  13
+#define BIT_PG0   11
+#define BIT_BADC0 7
+#define BIT_SADC0 3
+#define BIT_MODE  0
+
+#define MASK_PG   (3 << BIT_PG0)
+#define MASK_BADC (0xf << BIT_BADC0)
+#define MASK_SADC (0xf << BIT_SADC0)
+#define MASK_MODE (7 << BIT_MODE)
+#define MASK_BRNG (1 << BIT_BRNG)
+
+#define DEF_CONFIG 0x399f
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+static const float u_shunt_max[] = {
+    [INA219_GAIN_1]     = 0.04,
+    [INA219_GAIN_0_5]   = 0.08,
+    [INA219_GAIN_0_25]  = 0.16,
+    [INA219_GAIN_0_125] = 0.32,
+};
+
+static esp_err_t read_reg_16(ina219_t *dev, uint8_t reg, uint16_t *val)
+{
+    CHECK_ARG(val);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, reg, val, 2));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    *val = (*val >> 8) | (*val << 8);
+
+    return ESP_OK;
+}
+
+static esp_err_t write_reg_16(ina219_t *dev, uint8_t reg, uint16_t val)
+{
+    uint16_t v = (val >> 8) | (val << 8);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_write_reg(&dev->i2c_dev, reg, &v, 2));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+static esp_err_t read_conf_bits(ina219_t *dev, uint16_t mask, uint8_t bit, uint16_t *res)
+{
+    uint16_t raw;
+    CHECK(read_reg_16(dev, REG_CONFIG, &raw));
+
+    *res = (raw & mask) >> bit;
+
+    return ESP_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t ina219_init_desc(ina219_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+
+    if (addr < INA219_ADDR_GND_GND || addr > INA219_ADDR_SCL_SCL)
+    {
+        ESP_LOGE(TAG, "Invalid I2C address");
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    dev->i2c_dev.port = port;
+    dev->i2c_dev.addr = addr;
+    dev->i2c_dev.cfg.sda_io_num = sda_gpio;
+    dev->i2c_dev.cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(&dev->i2c_dev);
+}
+
+esp_err_t ina219_free_desc(ina219_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(&dev->i2c_dev);
+}
+
+esp_err_t ina219_init(ina219_t *dev)
+{
+    CHECK_ARG(dev);
+
+    CHECK(read_reg_16(dev, REG_CONFIG, &dev->config));
+
+    ESP_LOGD(TAG, "Initialize, config: 0x%04x", dev->config);
+
+    return ESP_OK;
+}
+
+esp_err_t ina219_reset(ina219_t *dev)
+{
+    CHECK_ARG(dev);
+    CHECK(write_reg_16(dev, REG_CONFIG, 1 << BIT_RST));
+
+    dev->config = DEF_CONFIG;
+
+    ESP_LOGD(TAG, "Device reset");
+
+    return ESP_OK;
+}
+
+esp_err_t ina219_configure(ina219_t *dev, ina219_bus_voltage_range_t u_range,
+        ina219_gain_t gain, ina219_resolution_t u_res,
+        ina219_resolution_t i_res, ina219_mode_t mode)
+{
+    CHECK_ARG(dev);
+    CHECK_ARG(u_range <= INA219_BUS_RANGE_32V);
+    CHECK_ARG(gain <= INA219_GAIN_0_125);
+    CHECK_ARG(u_res <= INA219_RES_12BIT_128S);
+    CHECK_ARG(i_res <= INA219_RES_12BIT_128S);
+    CHECK_ARG(mode <= INA219_MODE_CONT_SHUNT_BUS);
+
+    dev->config = (u_range << BIT_BRNG) |
+                  (gain << BIT_PG0) |
+                  (u_res << BIT_BADC0) |
+                  (i_res << BIT_SADC0) |
+                  (mode << BIT_MODE);
+
+    ESP_LOGD(TAG, "Config: 0x%04x", dev->config);
+
+    return write_reg_16(dev, REG_CONFIG, dev->config);
+}
+
+esp_err_t ina219_get_bus_voltage_range(ina219_t *dev, ina219_bus_voltage_range_t *range)
+{
+    CHECK_ARG(dev && range);
+    *range = 0;
+    return read_conf_bits(dev, MASK_BRNG, BIT_BRNG, (uint16_t *)range);
+}
+
+esp_err_t ina219_get_gain(ina219_t *dev, ina219_gain_t *gain)
+{
+    CHECK_ARG(dev && gain);
+    *gain = 0;
+    return read_conf_bits(dev, MASK_PG, BIT_PG0, (uint16_t *)gain);
+}
+
+esp_err_t ina219_get_bus_voltage_resolution(ina219_t *dev, ina219_resolution_t *res)
+{
+    CHECK_ARG(dev && res);
+    *res = 0;
+    return read_conf_bits(dev, MASK_BADC, BIT_BADC0, (uint16_t *)res);
+}
+
+esp_err_t ina219_get_shunt_voltage_resolution(ina219_t *dev, ina219_resolution_t *res)
+{
+    CHECK_ARG(dev && res);
+    *res = 0;
+    return read_conf_bits(dev, MASK_SADC, BIT_SADC0, (uint16_t *)res);
+}
+
+esp_err_t ina219_get_mode(ina219_t *dev, ina219_mode_t *mode)
+{
+    CHECK_ARG(dev && mode);
+    *mode = 0;
+    return read_conf_bits(dev, MASK_MODE, BIT_MODE, (uint16_t *)mode);
+}
+
+esp_err_t ina219_calibrate(ina219_t *dev, float i_expected_max, float r_shunt)
+{
+    CHECK_ARG(dev);
+
+    ina219_gain_t gain;
+    CHECK(ina219_get_gain(dev, &gain));
+
+    dev->i_lsb = (uint16_t)(u_shunt_max[gain] / r_shunt / 32767 * 100000000);
+    dev->i_lsb /= 100000000;
+    dev->i_lsb /= 0.0001;
+    dev->i_lsb = ceil(dev->i_lsb);
+    dev->i_lsb *= 0.0001;
+
+    dev->p_lsb = dev->i_lsb * 20;
+
+    uint16_t cal = (uint16_t)((0.04096) / (dev->i_lsb * r_shunt));
+
+    ESP_LOGD(TAG, "Calibration: %.04f A, %.04f Ohm, 0x%04x", i_expected_max, r_shunt, cal);
+
+    return write_reg_16(dev, REG_CALIBRATION, cal);
+}
+
+esp_err_t ina219_trigger(ina219_t *dev)
+{
+    CHECK_ARG(dev);
+
+    uint16_t mode = (dev->config & MASK_MODE) >> BIT_MODE;
+    if (mode < INA219_MODE_TRIG_SHUNT || mode > INA219_MODE_TRIG_SHUNT_BUS)
+    {
+        ESP_LOGE(TAG, "Could not trigger conversion in this mode: %d", mode);
+        return ESP_ERR_INVALID_STATE;
+    }
+
+    return write_reg_16(dev, REG_CONFIG, dev->config);
+}
+
+esp_err_t ina219_get_bus_voltage(ina219_t *dev, float *voltage)
+{
+    CHECK_ARG(dev && voltage);
+
+    int16_t raw;
+    CHECK(read_reg_16(dev, REG_BUS_U, (uint16_t *)&raw));
+
+    *voltage = (raw >> 3) * 0.004;
+
+    return ESP_OK;
+}
+
+esp_err_t ina219_get_shunt_voltage(ina219_t *dev, float *voltage)
+{
+    CHECK_ARG(dev && voltage);
+
+    int16_t raw;
+    CHECK(read_reg_16(dev, REG_SHUNT_U, (uint16_t *)&raw));
+
+    *voltage = raw / 100000.0;
+
+    return ESP_OK;
+}
+
+esp_err_t ina219_get_current(ina219_t *dev, float *current)
+{
+    CHECK_ARG(dev && current);
+
+    int16_t raw;
+    CHECK(read_reg_16(dev, REG_CURRENT, (uint16_t *)&raw));
+
+    *current = raw * dev->i_lsb;
+
+    return ESP_OK;
+}
+
+esp_err_t ina219_get_power(ina219_t *dev, float *power)
+{
+    CHECK_ARG(dev && power);
+
+    int16_t raw;
+    CHECK(read_reg_16(dev, REG_POWER, (uint16_t *)&raw));
+
+    *power = raw * dev->p_lsb;
+
+    return ESP_OK;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ina219/ina219.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,298 @@
+/*
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file ina219.h
+ * @defgroup ina219 ina219
+ * @{
+ *
+ * ESP-IDF driver for INA219/INA220 Zerø-Drift, Bidirectional
+ * Current/Power Monitor
+ *
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __INA219_H__
+#define __INA219_H__
+
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define INA219_ADDR_GND_GND 0x40 //!< I2C address, A1 pin - GND, A0 pin - GND
+#define INA219_ADDR_GND_VS  0x41 //!< I2C address, A1 pin - GND, A0 pin - VS+
+#define INA219_ADDR_GND_SDA 0x42 //!< I2C address, A1 pin - GND, A0 pin - SDA
+#define INA219_ADDR_GND_SCL 0x43 //!< I2C address, A1 pin - GND, A0 pin - SCL
+#define INA219_ADDR_VS_GND  0x44 //!< I2C address, A1 pin - VS+, A0 pin - GND
+#define INA219_ADDR_VS_VS   0x45 //!< I2C address, A1 pin - VS+, A0 pin - VS+
+#define INA219_ADDR_VS_SDA  0x46 //!< I2C address, A1 pin - VS+, A0 pin - SDA
+#define INA219_ADDR_VS_SCL  0x47 //!< I2C address, A1 pin - VS+, A0 pin - SCL
+#define INA219_ADDR_SDA_GND 0x48 //!< I2C address, A1 pin - SDA, A0 pin - GND
+#define INA219_ADDR_SDA_VS  0x49 //!< I2C address, A1 pin - SDA, A0 pin - VS+
+#define INA219_ADDR_SDA_SDA 0x4a //!< I2C address, A1 pin - SDA, A0 pin - SDA
+#define INA219_ADDR_SDA_SCL 0x4b //!< I2C address, A1 pin - SDA, A0 pin - SCL
+#define INA219_ADDR_SCL_GND 0x4c //!< I2C address, A1 pin - SCL, A0 pin - GND
+#define INA219_ADDR_SCL_VS  0x4d //!< I2C address, A1 pin - SCL, A0 pin - VS+
+#define INA219_ADDR_SCL_SDA 0x4e //!< I2C address, A1 pin - SCL, A0 pin - SDA
+#define INA219_ADDR_SCL_SCL 0x4f //!< I2C address, A1 pin - SCL, A0 pin - SCL
+
+/**
+ * Bus voltage range
+ */
+typedef enum {
+    INA219_BUS_RANGE_16V = 0, //!< 16V FSR
+    INA219_BUS_RANGE_32V      //!< 32V FSR (default)
+} ina219_bus_voltage_range_t;
+
+/**
+ * PGA gain for shunt voltage
+ */
+typedef enum {
+    INA219_GAIN_1 = 0, //!< Gain: 1, Range: +-40 mV
+    INA219_GAIN_0_5,   //!< Gain: 1/2, Range: +-80 mV
+    INA219_GAIN_0_25,  //!< Gain: 1/4, Range: +-160 mV
+    INA219_GAIN_0_125  //!< Gain: 1/8, Range: +-320 mV (default)
+} ina219_gain_t;
+
+/**
+ * ADC resolution/averaging
+ */
+typedef enum {
+    INA219_RES_9BIT_1S    = 0,  //!< 9 bit, 1 sample, conversion time 84 us
+    INA219_RES_10BIT_1S   = 1,  //!< 10 bit, 1 sample, conversion time 148 us
+    INA219_RES_11BIT_1S   = 2,  //!< 11 bit, 1 sample, conversion time 276 us
+    INA219_RES_12BIT_1S   = 3,  //!< 12 bit, 1 sample, conversion time 532 us (default)
+    INA219_RES_12BIT_2S   = 9,  //!< 12 bit, 2 samples, conversion time 1.06 ms
+    INA219_RES_12BIT_4S   = 10, //!< 12 bit, 4 samples, conversion time 2.13 ms
+    INA219_RES_12BIT_8S   = 11, //!< 12 bit, 8 samples, conversion time 4.26 ms
+    INA219_RES_12BIT_16S  = 12, //!< 12 bit, 16 samples, conversion time 8.51 ms
+    INA219_RES_12BIT_32S  = 13, //!< 12 bit, 32 samples, conversion time 17.02 ms
+    INA219_RES_12BIT_64S  = 14, //!< 12 bit, 64 samples, conversion time 34.05 ms
+    INA219_RES_12BIT_128S = 15, //!< 12 bit, 128 samples, conversion time 68.1 ms
+} ina219_resolution_t;
+
+/**
+ * Operating mode
+ */
+typedef enum {
+    INA219_MODE_POWER_DOWN = 0, //!< Power-done
+    INA219_MODE_TRIG_SHUNT,     //!< Shunt voltage, triggered
+    INA219_MODE_TRIG_BUS,       //!< Bus voltage, triggered
+    INA219_MODE_TRIG_SHUNT_BUS, //!< Shunt and bus, triggered
+    INA219_MODE_DISABLED,       //!< ADC off (disabled)
+    INA219_MODE_CONT_SHUNT,     //!< Shunt voltage, continuous
+    INA219_MODE_CONT_BUS,       //!< Bus voltage, continuous
+    INA219_MODE_CONT_SHUNT_BUS  //!< Shunt and bus, continuous (default)
+} ina219_mode_t;
+
+/**
+ * Device descriptor
+ */
+typedef struct
+{
+    i2c_dev_t i2c_dev;
+
+    uint16_t config;
+    float i_lsb, p_lsb;
+} ina219_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev Device descriptor
+ * @param addr Device I2C address
+ * @param port I2C port
+ * @param sda_gpio SDA GPIO
+ * @param scl_gpio SCL GPIO
+ * @return `ESP_OK` on success
+ */
+esp_err_t ina219_init_desc(ina219_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t ina219_free_desc(ina219_t *dev);
+
+/**
+ * @brief Init device
+ *
+ * Read current device configuration into `dev->config`
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t ina219_init(ina219_t *dev);
+
+/**
+ * @brief Reset device
+ *
+ * Same as power-on reset. Resets all registers to default values.
+ * You still need to calibrate device to read current, otherwise
+ * only shunt voltage readings will be valid.
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t ina219_reset(ina219_t *dev);
+
+/**
+ * @brief Set device configuration
+ *
+ * @param dev Device descriptor
+ * @param u_range Bus voltage range
+ * @param gain Shunt voltage gain
+ * @param u_res Bus voltage resolution and averaging
+ * @param i_res Shunt voltage resolution and averaging
+ * @param mode Device operational mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t ina219_configure(ina219_t *dev, ina219_bus_voltage_range_t u_range,
+        ina219_gain_t gain, ina219_resolution_t u_res,
+        ina219_resolution_t i_res, ina219_mode_t mode);
+
+/**
+ * @brief Get bus voltage range
+ *
+ * @param dev Device descriptor
+ * @param[out] range Bus voltage range
+ * @return `ESP_OK` on success
+ */
+esp_err_t ina219_get_bus_voltage_range(ina219_t *dev, ina219_bus_voltage_range_t *range);
+
+/**
+ * @brief Get shunt voltage gain
+ *
+ * @param dev Device descriptor
+ * @param[out] gain Shunt voltage gain
+ * @return `ESP_OK` on success
+ */
+esp_err_t ina219_get_gain(ina219_t *dev, ina219_gain_t *gain);
+
+/**
+ * @brief Get bus voltage resolution and averaging
+ *
+ * @param dev Device descriptor
+ * @param[out] res Bus voltage resolution and averaging
+ * @return `ESP_OK` on success
+ */
+esp_err_t ina219_get_bus_voltage_resolution(ina219_t *dev, ina219_resolution_t *res);
+
+/**
+ * @brief Get shunt voltage resolution and averaging
+ *
+ * @param dev Device descriptor
+ * @param[out] res Shunt voltage resolution and averaging
+ * @return `ESP_OK` on success
+ */
+esp_err_t ina219_get_shunt_voltage_resolution(ina219_t *dev, ina219_resolution_t *res);
+
+/**
+ * @brief Get operating mode
+ *
+ * @param dev Device descriptor
+ * @param[out] mode Operating mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t ina219_get_mode(ina219_t *dev, ina219_mode_t *mode);
+
+/**
+ * @brief Perform calibration
+ *
+ * Current readings will be valid only after calibration
+ *
+ * @param dev Device descriptor
+ * @param i_expected_max Maximum expected current, A
+ * @param r_shunt Shunt resistance, Ohm
+ * @return `ESP_OK` on success
+ */
+esp_err_t ina219_calibrate(ina219_t *dev, float i_expected_max, float r_shunt);
+
+/**
+ * @brief Trigger single conversion
+ *
+ * Function will return an error if current operating
+ * mode is not `INA219_MODE_TRIG_SHUNT`/`INA219_MODE_TRIG_BUS`/`INA219_MODE_TRIG_SHUNT_BUS`
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t ina219_trigger(ina219_t *dev);
+
+/**
+ * @brief Read bus voltage
+ *
+ * @param dev Device descriptor
+ * @param[out] voltage Bus voltage, V
+ * @return `ESP_OK` on success
+ */
+esp_err_t ina219_get_bus_voltage(ina219_t *dev, float *voltage);
+
+/**
+ * @brief Read shunt voltage
+ *
+ * @param dev Device descriptor
+ * @param[out] voltage Shunt voltage, V
+ * @return `ESP_OK` on success
+ */
+esp_err_t ina219_get_shunt_voltage(ina219_t *dev, float *voltage);
+
+/**
+ * @brief Read current
+ *
+ * This function works properly only after calibration.
+ *
+ * @param dev Device descriptor
+ * @param[out] current Current, A
+ * @return `ESP_OK` on success
+ */
+esp_err_t ina219_get_current(ina219_t *dev, float *current);
+
+/**
+ * @brief Read power
+ *
+ * This function works properly only after calibration.
+ *
+ * @param dev Device descriptor
+ * @param[out] power Power, W
+ * @return `ESP_OK` on success
+ */
+esp_err_t ina219_get_power(ina219_t *dev, float *power);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __INA219_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ina260/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,23 @@
+---
+components:
+  - name: ina260
+    description: Driver for INA260 precision digital current and power monitor
+    group: current
+    groups: []
+    code_owners: UncleRus
+    depends:
+      - name: i2cdev
+      - name: log
+      - name: esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2020
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ina260/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS ina260.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ina260/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+Copyright 2020 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ina260/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ina260/ina260.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,312 @@
+/*
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file ina260.c
+ *
+ * ESP-IDF driver for INA260 precision digital current and power monitor
+ *
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#include <esp_log.h>
+#include <math.h>
+#include <esp_idf_lib_helpers.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include "ina260.h"
+
+#define I2C_FREQ_HZ 400000 // no more than 400 kHz, otherwise enabling HS mode on the chip is required
+
+static const char *TAG = "ina260";
+
+#define REG_CONFIG      0
+#define REG_CURRENT     1
+#define REG_BUS_VOLTAGE 2
+#define REG_POWER       3
+#define REG_MASK_EN     6
+#define REG_ALERT_LIMIT 7
+#define REG_MFR_ID      0xfe
+#define REG_DIE_ID      0xff
+
+#define BIT_MODE   0
+#define BIT_ISHCT  3
+#define BIT_VBUSCT 6
+#define BIT_AVG    9
+#define BIT_RST    15
+
+#define BIT_LEN  0
+#define BIT_APOL 1
+#define BIT_OVF  2
+#define BIT_CVRF 3
+#define BIT_AFF  4
+#define BIT_CNVR 10
+#define BIT_POL  11
+#define BIT_BUL  12
+#define BIT_BOL  13
+#define BIT_UCL  14
+#define BIT_OCL  15
+
+#define MASK_MODE   (7 << BIT_MODE)
+#define MASK_ISHCT  (7 << BIT_ISHCT)
+#define MASK_VBUSCT (7 << BIT_VBUSCT)
+#define MASK_AVG    (7 << BIT_AVG)
+
+#define DEF_CONF 0x6000
+
+#define BV(x) (1 << (x))
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+static esp_err_t read_reg_16(ina260_t *dev, uint8_t reg, uint16_t *val)
+{
+    CHECK_ARG(val);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, reg, val, 2));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    *val = (*val >> 8) | (*val << 8);
+
+    return ESP_OK;
+}
+
+static esp_err_t write_reg_16(ina260_t *dev, uint8_t reg, uint16_t val)
+{
+    uint16_t v = (val >> 8) | (val << 8);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_write_reg(&dev->i2c_dev, reg, &v, 2));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t ina260_init_desc(ina260_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+
+    if (addr < INA260_ADDR(INA260_ADDR_PIN_GND, INA260_ADDR_PIN_GND)
+        || addr > INA260_ADDR(INA260_ADDR_PIN_SCL, INA260_ADDR_PIN_SCL))
+    {
+        ESP_LOGE(TAG, "Invalid I2C address");
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    dev->i2c_dev.port = port;
+    dev->i2c_dev.addr = addr;
+    dev->i2c_dev.cfg.sda_io_num = sda_gpio;
+    dev->i2c_dev.cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(&dev->i2c_dev);
+}
+
+esp_err_t ina260_free_desc(ina260_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(&dev->i2c_dev);
+}
+
+esp_err_t ina260_init(ina260_t *dev)
+{
+    CHECK_ARG(dev);
+
+    CHECK(read_reg_16(dev, REG_CONFIG, &dev->config));
+    CHECK(read_reg_16(dev, REG_MFR_ID, &dev->mfr_id));
+    CHECK(read_reg_16(dev, REG_DIE_ID, &dev->die_id));
+
+    ESP_LOGD(TAG, "[0x%02x@%u] Found device MFR_ID: %04x, DIE_ID: %04x",
+             dev->i2c_dev.addr, dev->i2c_dev.port, dev->mfr_id, dev->die_id);
+
+    return ESP_OK;
+}
+
+esp_err_t ina260_reset(ina260_t *dev)
+{
+    CHECK_ARG(dev);
+
+    ESP_LOGD(TAG, "[0x%02x@%u] Resetting...", dev->i2c_dev.addr, dev->i2c_dev.port);
+    CHECK(write_reg_16(dev, REG_CONFIG, 1 << BIT_RST));
+
+    vTaskDelay(pdMS_TO_TICKS(100));
+
+    return ina260_init(dev);
+}
+
+esp_err_t ina260_set_config(ina260_t *dev, ina260_mode_t mode, ina260_averaging_mode_t avg_mode,
+                            ina260_conversion_time_t vbus_ct, ina260_conversion_time_t ish_ct)
+{
+    CHECK_ARG(dev
+              && mode <= INA260_MODE_CONT_SHUNT_BUS
+              && avg_mode <= INA260_AVG_1024
+              && vbus_ct <= INA260_CT_8244
+              && ish_ct <= INA260_CT_8244);
+
+    dev->config = DEF_CONF
+            | (avg_mode << BIT_AVG)
+            | (vbus_ct << BIT_VBUSCT)
+            | (ish_ct << BIT_ISHCT)
+            | (mode << BIT_MODE);
+
+    return write_reg_16(dev, REG_CONFIG, dev->config);
+}
+
+esp_err_t ina260_get_config(ina260_t *dev, ina260_mode_t *mode, ina260_averaging_mode_t *avg_mode,
+                            ina260_conversion_time_t *vbus_ct, ina260_conversion_time_t *ish_ct)
+{
+    CHECK_ARG(dev);
+
+    CHECK(read_reg_16(dev, REG_CONFIG, &dev->config));
+    if (mode)
+        *mode = (dev->config & MASK_MODE) >> BIT_MODE;
+    if (avg_mode)
+        *avg_mode = (dev->config & MASK_AVG) >> BIT_AVG;
+    if (vbus_ct)
+        *vbus_ct = (dev->config & MASK_VBUSCT) >> BIT_VBUSCT;
+    if (ish_ct)
+        *ish_ct = (dev->config & MASK_ISHCT) >> BIT_ISHCT;
+    return ESP_OK;
+}
+
+esp_err_t ina260_set_alert(ina260_t *dev, ina260_alert_mode_t mode, float limit,
+                           bool cvrf, bool active_high, bool latch)
+{
+    CHECK_ARG(dev);
+
+    uint16_t reg = 0;
+    bool wl = true;
+    int16_t l = 0;
+    switch (mode)
+    {
+        case INA260_ALERT_OCL:
+            reg |= BV(BIT_OCL);
+            l = (int16_t)(limit / 0.00125);
+            break;
+        case INA260_ALERT_UCL:
+            reg |= BV(BIT_UCL);
+            l = (int16_t)(limit / 0.00125);
+            break;
+        case INA260_ALERT_BOL:
+            reg |= BV(BIT_BOL);
+            l = (int16_t)(limit / 0.00125);
+            break;
+        case INA260_ALERT_BUL:
+            reg |= BV(BIT_BUL);
+            l = (int16_t)(limit / 0.00125);
+            break;
+        case INA260_ALERT_POL:
+            reg |= BV(BIT_POL);
+            l = (int16_t)(limit / 0.01);
+            break;
+        default:
+            wl = false;
+    }
+    if (cvrf)        reg |= BV(BIT_CVRF);
+    if (active_high) reg |= BV(BIT_APOL);
+    if (latch)       reg |= BV(BIT_LEN);
+    if (wl)
+        CHECK(write_reg_16(dev, REG_ALERT_LIMIT, l));
+
+    CHECK(write_reg_16(dev, REG_MASK_EN, reg));
+
+    return ESP_OK;
+}
+
+esp_err_t ina260_trigger(ina260_t *dev)
+{
+    CHECK_ARG(dev);
+
+    uint16_t mode = (dev->config & MASK_MODE) >> BIT_MODE;
+    if (mode < INA260_MODE_TRIG_SHUNT || mode > INA260_MODE_TRIG_SHUNT_BUS)
+    {
+        ESP_LOGE(TAG, "Could not trigger conversion in this mode: %d", mode);
+        return ESP_ERR_INVALID_STATE;
+    }
+
+    return write_reg_16(dev, REG_CONFIG, dev->config);
+}
+
+esp_err_t ina260_get_status(ina260_t *dev, bool *ready, bool *alert, bool *overflow)
+{
+    CHECK_ARG(dev && (ready || alert || overflow));
+
+    uint16_t val;
+    CHECK(read_reg_16(dev, REG_MASK_EN, &val));
+
+    if (ready)
+        *ready = val & BIT_CVRF ? 1 : 0;
+    if (alert)
+        *alert = val & BIT_AFF ? 1 : 0;
+    if (overflow)
+        *overflow = val & BIT_OVF ? 1 : 0;
+
+    return ESP_OK;
+}
+
+esp_err_t ina260_get_current(ina260_t *dev, float *current)
+{
+    CHECK_ARG(dev && current);
+
+    int16_t raw;
+    CHECK(read_reg_16(dev, REG_CURRENT, (uint16_t *)&raw));
+    *current = raw * 0.00125;
+
+    return ESP_OK;
+}
+
+esp_err_t ina260_get_bus_voltage(ina260_t *dev, float *voltage)
+{
+    CHECK_ARG(dev && voltage);
+
+    uint16_t raw;
+    CHECK(read_reg_16(dev, REG_BUS_VOLTAGE, &raw));
+
+    *voltage = raw * 0.00125;
+
+    return ESP_OK;
+}
+
+esp_err_t ina260_get_power(ina260_t *dev, float *power)
+{
+    CHECK_ARG(dev && power);
+
+    uint16_t raw;
+    CHECK(read_reg_16(dev, REG_POWER, &raw));
+
+    *power = raw * 0.01;
+
+    return ESP_OK;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ina260/ina260.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,275 @@
+/*
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file ina260.h
+ * @defgroup ina260 ina260
+ * @{
+ *
+ * ESP-IDF driver for INA260 precision digital current and power monitor
+ *
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ *
+ * @todo Add support for SPI interface
+ */
+#ifndef __INA260_H__
+#define __INA260_H__
+
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define INA260_ADDR_PIN_GND 0x00
+#define INA260_ADDR_PIN_VS  0x01
+#define INA260_ADDR_PIN_SDA 0x02
+#define INA260_ADDR_PIN_SCL 0x03
+
+/**
+ * Macro to define I2C address
+ *
+ * Examples:
+ *    INA260_ADDR(INA260_ADDR_PIN_GND, INA260_ADDR_PIN_GND) = 0x40 (A0 = A1 = GND)
+ *    INA260_ADDR(INA260_ADDR_PIN_VS, INA260_ADDR_PIN_SDA)  = 0x49 (A0 = VS, A1 = SDA)
+ */
+#define INA260_ADDR(A0, A1) (0x40 | ((A1) << 2) | (A0))
+
+/**
+ * Averaging mode.
+ * Determines the number of samples that are collected and averaged.
+ */
+typedef enum
+{
+    INA260_AVG_1 = 0, //!< 1 sample, default
+    INA260_AVG_4,     //!< 4 samples
+    INA260_AVG_16,    //!< 16 samples
+    INA260_AVG_64,    //!< 64 samples
+    INA260_AVG_128,   //!< 128 samples
+    INA260_AVG_256,   //!< 256 samples
+    INA260_AVG_512,   //!< 512 samples
+    INA260_AVG_1024   //!< 1024 samples
+} ina260_averaging_mode_t;
+
+/**
+ * Conversion time
+ */
+typedef enum
+{
+    INA260_CT_140 = 0, //!< 140 us
+    INA260_CT_204,     //!< 204 us
+    INA260_CT_332,     //!< 332 us
+    INA260_CT_588,     //!< 588 us
+    INA260_CT_1100,    //!< 1.1 ms, default
+    INA260_CT_2116,    //!< 2.116 ms
+    INA260_CT_4156,    //!< 4.156 ms
+    INA260_CT_8244,    //!< 8.244 ms
+} ina260_conversion_time_t;
+
+/**
+ * Operating mode
+ */
+typedef enum
+{
+    INA260_MODE_POWER_DOWN     = 0, //!< Power-done
+    INA260_MODE_TRIG_SHUNT     = 1, //!< Shunt current, triggered
+    INA260_MODE_TRIG_BUS       = 2, //!< Bus voltage, triggered
+    INA260_MODE_TRIG_SHUNT_BUS = 3, //!< Shunt current and bus voltage, triggered
+    INA260_MODE_POWER_DOWN2    = 4, //!< Power-done
+    INA260_MODE_CONT_SHUNT     = 5, //!< Shunt current, continuous
+    INA260_MODE_CONT_BUS       = 6, //!< Bus voltage, continuous
+    INA260_MODE_CONT_SHUNT_BUS = 7  //!< Shunt current and bus voltage, continuous (default)
+} ina260_mode_t;
+
+/**
+ * Alert function mode
+ */
+typedef enum
+{
+    INA260_ALERT_DISABLED, //!< No alert function
+    INA260_ALERT_OCL,      //!< Over current limit
+    INA260_ALERT_UCL,      //!< Under current limit
+    INA260_ALERT_BOL,      //!< Bus voltage over-voltage
+    INA260_ALERT_BUL,      //!< Bus voltage under-voltage
+    INA260_ALERT_POL,      //!< Power over-limit
+} ina260_alert_mode_t;
+
+/**
+ * Device descriptor
+ */
+typedef struct
+{
+    i2c_dev_t i2c_dev; //!< I2C device descriptor
+    uint16_t config;   //!< Current config
+    uint16_t mfr_id;   //!< Manufacturer ID
+    uint16_t die_id;   //!< Die ID
+} ina260_t;
+
+/**
+ * @brief Initialize device descriptor.
+ *
+ * @param dev Device descriptor
+ * @param addr Device I2C address
+ * @param port I2C port
+ * @param sda_gpio SDA GPIO
+ * @param scl_gpio SCL GPIO
+ * @return `ESP_OK` on success
+ */
+esp_err_t ina260_init_desc(ina260_t *dev, uint8_t addr, i2c_port_t port,
+                           gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor.
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t ina260_free_desc(ina260_t *dev);
+
+/**
+ * @brief Initialize device.
+ *
+ * Reads sensor configuration.
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t ina260_init(ina260_t *dev);
+
+/**
+ * @brief Reset sensor.
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t ina260_reset(ina260_t *dev);
+
+/**
+ * @brief Configure sensor.
+ *
+ * @param dev Device descriptor
+ * @param mode Operating mode
+ * @param avg_mode Averaging mode
+ * @param vbus_ct Bus voltage conversion time
+ * @param ish_ct Shunt current conversion time
+ * @return `ESP_OK` on success
+ */
+esp_err_t ina260_set_config(ina260_t *dev, ina260_mode_t mode, ina260_averaging_mode_t avg_mode,
+                            ina260_conversion_time_t vbus_ct, ina260_conversion_time_t ish_ct);
+
+/**
+ * @brief Read sensor configuration.
+ *
+ * @param dev Device descriptor
+ * @param[out] mode Operating mode
+ * @param[out] avg_mode Averaging mode
+ * @param[out] vbus_ct Bus voltage conversion time
+ * @param[out] ish_ct Shunt current conversion time
+ * @return `ESP_OK` on success
+ */
+esp_err_t ina260_get_config(ina260_t *dev, ina260_mode_t *mode, ina260_averaging_mode_t *avg_mode,
+                            ina260_conversion_time_t *vbus_ct, ina260_conversion_time_t *ish_ct);
+
+/**
+ * @brief Setup ALERT pin.
+ *
+ * @param dev Device descriptor
+ * @param mode Alert function mode
+ * @param limit Alert limit value
+ * @param cvrf If true also assert ALERT pin when device is ready to next conversion
+ * @param active_high Set active ALERT pin level is high
+ * @param latch Enable latch mode on ALERT pin (see ::ina260_get_status())
+ * @return `ESP_OK` on success
+ */
+esp_err_t ina260_set_alert(ina260_t *dev, ina260_alert_mode_t mode, float limit,
+                           bool cvrf, bool active_high, bool latch);
+
+/**
+ * @brief Trigger single conversion.
+ *
+ * Function will return an error if current operating
+ * mode is not `INA260_MODE_TRIG_SHUNT`/`INA260_MODE_TRIG_BUS`/`INA260_MODE_TRIG_SHUNT_BUS`
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t ina260_trigger(ina260_t *dev);
+
+/**
+ * @brief Get device status.
+ *
+ * This function also clears ALERT state if latch mode is enabled for ALERT pin.
+ *
+ * @param dev Device descriptor
+ * @param[out] ready If true, device is ready for the next conversion
+ * @param[out] alert If true, there was alert
+ * @param[out] overflow If true, power data have exceeded max 419.43 W
+ * @return `ESP_OK` on success
+ */
+esp_err_t ina260_get_status(ina260_t *dev, bool *ready, bool *alert, bool *overflow);
+
+/**
+ * @brief Read current.
+ *
+ * This function works properly only after calibration.
+ *
+ * @param dev Device descriptor
+ * @param[out] current Current, A
+ * @return `ESP_OK` on success
+ */
+esp_err_t ina260_get_current(ina260_t *dev, float *current);
+
+/**
+ * @brief Read bus voltage.
+ *
+ * @param dev Device descriptor
+ * @param[out] voltage Bus voltage, V
+ * @return `ESP_OK` on success
+ */
+esp_err_t ina260_get_bus_voltage(ina260_t *dev, float *voltage);
+
+/**
+ * @brief Read power.
+ *
+ * This function works properly only after calibration.
+ *
+ * @param dev Device descriptor
+ * @param[out] power Power, W
+ * @return `ESP_OK` on success
+ */
+esp_err_t ina260_get_power(ina260_t *dev, float *power);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __INA260_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ina3221/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+---
+components:
+  - name: ina3221
+    description: Driver for INA3221 shunt and bus voltage monitor
+    group: current
+    groups: []
+    code_owners: UncleRus
+    depends:
+      - name: i2cdev
+      - name: log
+      - name: esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: ISC
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2019
+      - author:
+          name: Zaltora
+        year: 2016
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ina3221/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS ina3221.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ina3221/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Zaltora (https://github.com/Zaltora)
+Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ina3221/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ina3221/ina3221.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,321 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2016 Zaltora <https://github.com/Zaltora>
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file ina3221.c
+ *
+ * ESP-IDF driver for Shunt and Bus Voltage Monitor INA3221
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2016 Zaltora <https://github.com/Zaltora>\n
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+#include <esp_log.h>
+#include <esp_idf_lib_helpers.h>
+#include "ina3221.h"
+
+static const char *TAG = "ina3221";
+
+#define I2C_FREQ_HZ 1000000 // Max 1MHz for esp-idf, but device supports up to 2.44Mhz
+
+#define INA3221_REG_CONFIG                      (0x00)
+#define INA3221_REG_SHUNTVOLTAGE_1              (0x01)
+#define INA3221_REG_BUSVOLTAGE_1                (0x02)
+#define INA3221_REG_CRITICAL_ALERT_1            (0x07)
+#define INA3221_REG_WARNING_ALERT_1             (0x08)
+#define INA3221_REG_SHUNT_VOLTAGE_SUM           (0x0D)
+#define INA3221_REG_SHUNT_VOLTAGE_SUM_LIMIT     (0x0E)
+#define INA3221_REG_MASK                        (0x0F)
+#define INA3221_REG_VALID_POWER_UPPER_LIMIT     (0x10)
+#define INA3221_REG_VALID_POWER_LOWER_LIMIT     (0x11)
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+static esp_err_t read_reg_16(ina3221_t *dev, uint8_t reg, uint16_t *val)
+{
+    CHECK_ARG(val);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, reg, val, 2));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    *val = (*val >> 8) | (*val << 8);  // Swap
+
+    return ESP_OK;
+}
+
+static esp_err_t write_reg_16(ina3221_t *dev, uint8_t reg, uint16_t val)
+{
+    uint16_t v = (val >> 8) | (val << 8);  // Swap
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_write_reg(&dev->i2c_dev, reg, &v, 2));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+static esp_err_t write_config(ina3221_t *dev)
+{
+    return write_reg_16(dev, INA3221_REG_CONFIG, dev->config.config_register);
+}
+
+static esp_err_t write_mask(ina3221_t *dev)
+{
+    return write_reg_16(dev, INA3221_REG_MASK, dev->mask.mask_register & INA3221_MASK_CONFIG);
+}
+
+///////////////////////////////////////////////////////////////////////////////////
+
+esp_err_t ina3221_init_desc(ina3221_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+
+    if (addr < INA3221_I2C_ADDR_GND || addr > INA3221_I2C_ADDR_SCL)
+    {
+        ESP_LOGE(TAG, "Invalid I2C address");
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    dev->i2c_dev.port = port;
+    dev->i2c_dev.addr = addr;
+    dev->i2c_dev.cfg.sda_io_num = sda_gpio;
+    dev->i2c_dev.cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(&dev->i2c_dev);
+}
+
+esp_err_t ina3221_free_desc(ina3221_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(&dev->i2c_dev);
+}
+
+
+esp_err_t ina3221_sync(ina3221_t *dev)
+{
+    CHECK_ARG(dev);
+
+    uint16_t data;
+
+    // Sync config register
+    CHECK(read_reg_16(dev, INA3221_REG_CONFIG, &data));
+    if (data != dev->config.config_register)
+        CHECK(write_config(dev));
+
+    // Sync mask register
+    CHECK(read_reg_16(dev, INA3221_REG_MASK, &data));
+    if ((data & INA3221_MASK_CONFIG) != (dev->mask.mask_register & INA3221_MASK_CONFIG))
+        CHECK(write_mask(dev));
+
+    return ESP_OK;
+}
+
+esp_err_t ina3221_trigger(ina3221_t *dev)
+{
+    return write_config(dev);
+}
+
+esp_err_t ina3221_get_status(ina3221_t *dev)
+{
+    return read_reg_16(dev, INA3221_REG_MASK, &dev->mask.mask_register);
+}
+
+esp_err_t ina3221_set_options(ina3221_t *dev, bool mode, bool bus, bool shunt)
+{
+    CHECK_ARG(dev);
+
+    dev->config.mode = mode;
+    dev->config.ebus = bus;
+    dev->config.esht = shunt;
+    return write_config(dev);
+}
+
+esp_err_t ina3221_enable_channel(ina3221_t *dev, bool ch1, bool ch2, bool ch3)
+{
+    CHECK_ARG(dev);
+
+    dev->config.ch1 = ch1;
+    dev->config.ch2 = ch2;
+    dev->config.ch3 = ch3;
+    return write_config(dev);
+}
+
+esp_err_t ina3221_enable_channel_sum(ina3221_t *dev, bool ch1, bool ch2, bool ch3)
+{
+    CHECK_ARG(dev);
+
+    dev->mask.scc1 = ch1;
+    dev->mask.scc2 = ch2;
+    dev->mask.scc3 = ch3;
+    return write_mask(dev);
+}
+
+esp_err_t ina3221_enable_latch_pin(ina3221_t *dev, bool warning, bool critical)
+{
+    CHECK_ARG(dev);
+
+    dev->mask.wen = warning;
+    dev->mask.cen = critical;
+    return write_mask(dev);
+}
+
+esp_err_t ina3221_set_average(ina3221_t *dev, ina3221_avg_t avg)
+{
+    CHECK_ARG(dev);
+
+    dev->config.avg = avg;
+    return write_config(dev);
+}
+
+esp_err_t ina3221_set_bus_conversion_time(ina3221_t *dev, ina3221_ct_t ct)
+{
+    CHECK_ARG(dev);
+
+    dev->config.vbus = ct;
+    return write_config(dev);
+}
+
+esp_err_t ina3221_set_shunt_conversion_time(ina3221_t *dev, ina3221_ct_t ct)
+{
+    CHECK_ARG(dev);
+
+    dev->config.vsht = ct;
+    return write_config(dev);
+}
+
+esp_err_t ina3221_reset(ina3221_t *dev)
+{
+    CHECK_ARG(dev);
+
+    dev->config.config_register = INA3221_DEFAULT_CONFIG;
+    dev->mask.mask_register = INA3221_DEFAULT_CONFIG;
+    dev->config.rst = 1;
+    return write_config(dev);
+}
+
+esp_err_t ina3221_get_bus_voltage(ina3221_t *dev, ina3221_channel_t channel, float *voltage)
+{
+    CHECK_ARG(dev && voltage);
+
+    int16_t raw;
+
+    CHECK(read_reg_16(dev, INA3221_REG_BUSVOLTAGE_1 + channel * 2, (uint16_t *)&raw));
+    *voltage = raw * 0.001;
+
+    return ESP_OK;
+}
+
+esp_err_t ina3221_get_shunt_value(ina3221_t *dev, ina3221_channel_t channel, float *voltage, float *current)
+{
+    CHECK_ARG(dev);
+    CHECK_ARG(voltage || current);
+    if (current && !dev->shunt[channel])
+    {
+        ESP_LOGE(TAG, "No shunt configured for channel %u in device [0x%02x at %d]", channel, dev->i2c_dev.addr, dev->i2c_dev.port);
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    int16_t raw;
+    CHECK(read_reg_16(dev, INA3221_REG_SHUNTVOLTAGE_1 + channel * 2, (uint16_t *)&raw));
+    float mvolts = raw * 0.005; // mV, 40uV step
+
+    if (voltage)
+        *voltage = mvolts;
+
+    if (current)
+        *current = mvolts * 1000.0 / dev->shunt[channel];  // mA
+
+    return ESP_OK;
+}
+
+esp_err_t ina3221_get_sum_shunt_value(ina3221_t *dev, float *voltage)
+{
+    CHECK_ARG(dev && voltage);
+
+    int16_t raw;
+
+    CHECK(read_reg_16(dev, INA3221_REG_SHUNT_VOLTAGE_SUM, (uint16_t *)&raw));
+    *voltage = raw * 0.02; // mV
+
+    return ESP_OK;
+}
+
+esp_err_t ina3221_set_critical_alert(ina3221_t *dev, ina3221_channel_t channel, float current)
+{
+    CHECK_ARG(dev);
+
+    int16_t raw = current * dev->shunt[channel] * 0.2;
+    return write_reg_16(dev, INA3221_REG_CRITICAL_ALERT_1 + channel * 2, *(uint16_t *)&raw);
+}
+
+esp_err_t ina3221_set_warning_alert(ina3221_t *dev, ina3221_channel_t channel, float current)
+{
+    CHECK_ARG(dev);
+
+    int16_t raw = current * dev->shunt[channel] * 0.2;
+    return write_reg_16(dev, INA3221_REG_WARNING_ALERT_1 + channel * 2, *(uint16_t *)&raw);
+}
+
+esp_err_t ina3221_set_sum_warning_alert(ina3221_t *dev, float voltage)
+{
+    CHECK_ARG(dev);
+
+    int16_t raw = voltage * 50.0;
+    return write_reg_16(dev, INA3221_REG_SHUNT_VOLTAGE_SUM_LIMIT, *(uint16_t *)&raw);
+}
+
+esp_err_t ina3221_set_power_valid_upper_limit(ina3221_t *dev, float voltage)
+{
+    CHECK_ARG(dev);
+    if (!dev->config.ebus)
+    {
+        ESP_LOGE(TAG, "Bus is not enabled in device [0x%02x at %d]", dev->i2c_dev.addr, dev->i2c_dev.port);
+        return ESP_ERR_NOT_SUPPORTED;
+    }
+
+    int16_t raw = voltage * 1000.0;
+    return write_reg_16(dev, INA3221_REG_VALID_POWER_UPPER_LIMIT, *(uint16_t *)&raw);
+}
+
+esp_err_t ina3221_set_power_valid_lower_limit(ina3221_t *dev, float voltage)
+{
+    CHECK_ARG(dev);
+    if (!dev->config.ebus)
+    {
+        ESP_LOGE(TAG, "Bus is not enabled in device [0x%02x at %d]", dev->i2c_dev.addr, dev->i2c_dev.port);
+        return ESP_ERR_NOT_SUPPORTED;
+    }
+
+    int16_t raw = voltage * 1000.0;
+    return write_reg_16(dev, INA3221_REG_VALID_POWER_LOWER_LIMIT, *(uint16_t *)&raw);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ina3221/ina3221.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,387 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2016 Zaltora <https://github.com/Zaltora>
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file ina3221.h
+ * @defgroup ina3221 ina3221
+ * @{
+ *
+ * ESP-IDF driver for Shunt and Bus Voltage Monitor INA3221
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2016 Zaltora <https://github.com/Zaltora>
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+#ifndef __INA3221_H__
+#define __INA3221_H__
+
+#include <stdbool.h>
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define INA3221_I2C_ADDR_GND 0x40 ///< A0 to GND
+#define INA3221_I2C_ADDR_VS  0x41 ///< A0 to Vs+
+#define INA3221_I2C_ADDR_SDA 0x42 ///< A0 to SDA
+#define INA3221_I2C_ADDR_SCL 0x43 ///< A0 to SCL
+
+#define INA3221_BUS_NUMBER 3  ///< Number of shunt available
+
+/**
+ *  Default register values after reset
+ */
+#define INA3221_DEFAULT_CONFIG                   (0x7127)
+#define INA3221_DEFAULT_MASK                     (0x0002)
+#define INA3221_DEFAULT_POWER_UPPER_LIMIT        (0x2710) //10V
+#define INA3221_DEFAULT_POWER_LOWER_LIMIT        (0x2328) //9V
+
+#define INA3221_MASK_CONFIG (0x7C00)
+
+/**
+ * Number of samples
+ */
+typedef enum
+{
+    INA3221_AVG_1 = 0,  ///< Default
+    INA3221_AVG_4,
+    INA3221_AVG_16,
+    INA3221_AVG_64,
+    INA3221_AVG_128,
+    INA3221_AVG_256,
+    INA3221_AVG_512,
+    INA3221_AVG_1024,
+} ina3221_avg_t;
+
+/**
+ * Channel selection list
+ */
+typedef enum
+{
+    INA3221_CHANNEL_1 = 0,
+    INA3221_CHANNEL_2,
+    INA3221_CHANNEL_3,
+} ina3221_channel_t;
+
+/**
+ * Conversion time in us
+ */
+typedef enum
+{
+    INA3221_CT_140 = 0,
+    INA3221_CT_204,
+    INA3221_CT_332,
+    INA3221_CT_588,
+    INA3221_CT_1100,  ///< Default
+    INA3221_CT_2116,
+    INA3221_CT_4156,
+    INA3221_CT_8244,
+} ina3221_ct_t;
+
+/**
+ * Config description register
+ */
+typedef union
+{
+    struct
+    {
+        uint16_t esht :1; ///< Enable/Disable shunt measure    // LSB
+        uint16_t ebus :1; ///< Enable/Disable bus measure
+        uint16_t mode :1; ///< Single shot measure or continuous mode
+        uint16_t vsht :3; ///< Shunt voltage conversion time
+        uint16_t vbus :3; ///< Bus voltage conversion time
+        uint16_t avg  :3; ///< number of sample collected and averaged together
+        uint16_t ch3  :1; ///< Enable/Disable channel 3
+        uint16_t ch2  :1; ///< Enable/Disable channel 2
+        uint16_t ch1  :1; ///< Enable/Disable channel 1
+        uint16_t rst  :1; ///< Set this bit to 1 to reset device  // MSB
+    };
+    uint16_t config_register;
+} ina3221_config_t;
+
+/**
+ * Mask/enable description register
+ */
+typedef union
+{
+    struct
+    {
+        uint16_t cvrf :1; ///< Conversion ready flag (1: ready)   // LSB
+        uint16_t tcf  :1; ///< Timing control flag
+        uint16_t pvf  :1; ///< Power valid flag
+        uint16_t wf   :3; ///< Warning alert flag (Read mask to clear) (order : Channel1:channel2:channel3)
+        uint16_t sf   :1; ///< Sum alert flag (Read mask to clear)
+        uint16_t cf   :3; ///< Critical alert flag (Read mask to clear) (order : Channel1:channel2:channel3)
+        uint16_t cen  :1; ///< Critical alert latch (1:enable)
+        uint16_t wen  :1; ///< Warning alert latch (1:enable)
+        uint16_t scc3 :1; ///< channel 3 sum (1:enable)
+        uint16_t scc2 :1; ///< channel 2 sum (1:enable)
+        uint16_t scc1 :1; ///< channel 1 sum (1:enable)
+        uint16_t      :1; ///< Reserved         //MSB
+    };
+    uint16_t mask_register;
+} ina3221_mask_t;
+
+/**
+ *  Device descriptor
+ */
+typedef struct
+{
+    i2c_dev_t i2c_dev;                  ///< I2C device descriptor
+    uint16_t shunt[INA3221_BUS_NUMBER]; ///< Memory of shunt value (mOhm)
+    ina3221_config_t config;                  ///< Memory of ina3221 config
+    ina3221_mask_t mask;                      ///< Memory of mask_config
+} ina3221_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev Device descriptor
+ * @param addr Device I2C address
+ * @param port I2C port
+ * @param sda_gpio SDA GPIO
+ * @param scl_gpio SCL GPIO
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ina3221_init_desc(ina3221_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Device descriptor
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ina3221_free_desc(ina3221_t *dev);
+
+/**
+ * @brief Write current config to device
+ *
+ * Sync internal config buffer and mask with external device register. (When struct is set manually).
+ *
+ * @param dev Device descriptor
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ina3221_sync(ina3221_t *dev);
+
+/**
+ * @brief Trigger measurement
+ *
+ * Send current config register to trig a measurement in single-shot mode.
+ *
+ * @param dev Device descriptor
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ina3221_trigger(ina3221_t *dev);
+
+/**
+ * @brief Read status from device
+ *
+ * Get mask register from the device, used to read flags.
+ *
+ * @param dev Device descriptor
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ina3221_get_status(ina3221_t *dev);
+
+/**
+ * @brief Set options for bus and shunt
+ *
+ * @param dev Device descriptor
+ * @param mode Selection of measurement (true : continuous // false : single-shot)
+ * @param bus Enable/Disable bus measures
+ * @param shunt Enable/Disable shunt measures
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ina3221_set_options(ina3221_t *dev, bool mode, bool bus, bool shunt);
+
+/**
+ * @brief Select channels
+ *
+ * @param dev Device descriptor
+ * @param ch1 Enable/Disable channel 1 (true : enable // false : disable)
+ * @param ch2 Enable/Disable channel 2 (true : enable // false : disable)
+ * @param ch3 Enable/Disable channel 3 (true : enable // false : disable)
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ina3221_enable_channel(ina3221_t *dev, bool ch1, bool ch2, bool ch3);
+
+/**
+ * @brief Select channel to be sum (don't impact enable channel status)
+ *
+ * @param dev Device descriptor
+ * @param ch1 Enable/Disable channel 1 (true : enable // false : disable)
+ * @param ch2 Enable/Disable channel 2 (true : enable // false : disable)
+ * @param ch3 Enable/Disable channel 3 (true : enable // false : disable)
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ina3221_enable_channel_sum(ina3221_t *dev, bool ch1, bool ch2, bool ch3);
+
+/**
+ * @brief enable/disable latch on warning and critical alert pin
+ *
+ * @param dev Device descriptor
+ * @param warning Enable/Disable warning latch (true : Latch // false : Transparent)
+ * @param critical Enable/Disable critical latch (true : Latch // false : Transparent)
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ina3221_enable_latch_pin(ina3221_t *dev, bool warning, bool critical);
+
+/**
+ * @brief Set average (number of samples measured)
+ *
+ * @param dev Device descriptor
+ * @param avg Value of average selection
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ina3221_set_average(ina3221_t *dev, ina3221_avg_t avg);
+
+/**
+ * @brief Set conversion time for bus.
+ *
+ * @param dev Device descriptor
+ * @param ct Value of conversion time selection
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ina3221_set_bus_conversion_time(ina3221_t *dev, ina3221_ct_t ct);
+
+/**
+ * @brief Set conversion time for shunt.
+ *
+ * @param dev Device descriptor
+ * @param ct Value of conversion time selection
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ina3221_set_shunt_conversion_time(ina3221_t *dev, ina3221_ct_t ct);
+
+/**
+ * @brief Reset device
+ *
+ * Device will be configured like POR (Power-On-Reset)
+ *
+ * @param dev Device descriptor
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ina3221_reset(ina3221_t *dev);
+
+/**
+ * @brief Get Bus voltage (V)
+ *
+ * @param dev Device descriptor
+ * @param channel Select channel value to get
+ * @param voltage Data pointer to get bus voltage (V)
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ina3221_get_bus_voltage(ina3221_t *dev, ina3221_channel_t channel, float *voltage);
+
+/**
+ * @brief Get Shunt voltage (mV) and current (mA)
+ *
+ * @param dev Device descriptor
+ * @param channel Select channel value to get
+ * @param voltage Data pointer to get shunt voltage (mV)
+ * @param current Data pointer to get shunt voltage (mA)
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ina3221_get_shunt_value(ina3221_t *dev, ina3221_channel_t channel, float *voltage, float *current);
+
+/**
+ * @brief Get Shunt-voltage (mV) sum value of selected channels
+ *
+ * @param dev Device descriptor
+ * @param voltage Data pointer to get shunt voltage (mV)
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ina3221_get_sum_shunt_value(ina3221_t *dev, float *voltage);
+
+/**
+ * @brief Set Critical alert
+ *
+ * Alert when measurement(s) is greater
+ *
+ * @param dev Device descriptor
+ * @param channel Select channel value to set
+ * @param current Value to set (mA) // max : 163800/shunt (mOhm)
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ina3221_set_critical_alert(ina3221_t *dev, ina3221_channel_t channel, float current);
+
+/**
+ * @brief Set Warning alert
+ *
+ * Alert when average measurement(s) is greater
+ *
+ * @param dev Device descriptor
+ * @param channel Select channel value to set
+ * @param current Value to set (mA)  // max : 163800/shunt (mOhm)
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ina3221_set_warning_alert(ina3221_t *dev, ina3221_channel_t channel, float current);
+
+/**
+ * @brief Set Sum Warning alert
+ *
+ * Compared to each completed cycle of all selected channels : Sum register
+ *
+ * @param dev Device descriptor
+ * @param voltage voltage to set (mV) //  max : 655.32
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ina3221_set_sum_warning_alert(ina3221_t *dev, float voltage);
+
+/**
+ * @brief Set Power-valid upper-limit
+ *
+ * Used to determine if power conditions are met. Bus needs to be enabled.
+ * If bus voltage exceed the value set, PV pin will be set high.
+ *
+ * @param dev Device descriptor
+ * @param voltage voltage to set (V)
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ina3221_set_power_valid_upper_limit(ina3221_t *dev, float voltage);
+
+/**
+ * @brief Set Power-valid lower-limit
+ *
+ * Used to determine if power conditions are met. Bus needs to be enabled.
+ * If bus voltage drops below the value set, PV pin will be set low.
+ *
+ * @param dev Device descriptor
+ * @param voltage Voltage to set (V)
+ * @return ESP_OK to indicate success
+ */
+esp_err_t ina3221_set_power_valid_lower_limit(ina3221_t *dev, float voltage);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __INA3221_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/lc709203f/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,23 @@
+---
+components:
+  - name: lc709203f
+    description: |
+      Driver for LC709203F battery fuel gauge
+    group: misc
+    groups: []
+    code_owners: jmpmscorp
+    depends:
+      - name: i2cdev
+      - name: esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: ISC
+    copyrights:
+      - author:
+          name: jmpmscorp
+        year: 2022
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/lc709203f/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS lc709203f.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev esp_idf_lib_helpers
+)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/lc709203f/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,13 @@
+Copyright (c) 2022 Jose Manuel Perez <jmpmscorp@hotmail.com>
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/lc709203f/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/lc709203f/lc709203f.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,341 @@
+/*
+ * Copyright (c) 2022 Jose Manuel Perez <user@your.dom.ain>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * this file is supposed to conform the code style.
+ */
+
+#include <freertos/FreeRTOS.h>
+#include <esp_idf_lib_helpers.h>
+#include <esp_err.h>
+#include <esp_log.h>
+
+#include "lc709203f.h"
+
+#define LC709203F_I2C_ADDR        0x0B   ///< LC709203F default i2c address
+#define LC709003F_I2C_MAX_FREQ_HZ 400000 ///< 400kHz
+
+#define LC709203F_INIT_RSOC_VAL  0xAA55 ///< Value to init RSOC
+#define LC709203F_CRC_POLYNOMIAL 0x07   /// Polynomial to calculare CRC-8-ATM
+
+// static char *tag = "lc709203f";
+
+// clang-format off
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+// clang-format on
+
+static uint8_t s_lc709203f_calc_crc(uint8_t *data, size_t data_len)
+{
+    uint8_t crc = 0;
+
+    for (size_t j = data_len; j; --j)
+    {
+        crc ^= *data++;
+
+        for (size_t i = 8; i; --i)
+        {
+            crc = (crc & 0x80) ? (crc << 1) ^ LC709203F_CRC_POLYNOMIAL : (crc << 1);
+        }
+    }
+
+    return crc;
+}
+
+inline static esp_err_t s_i2c_dev_read_word(i2c_dev_t *dev, uint8_t reg, uint16_t *value)
+{
+    uint8_t read_data[6] = { 0 };
+    uint8_t crc = 0;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, reg, read_data + 3, 3));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    read_data[0] = dev->addr << 1;
+    read_data[1] = reg;
+    read_data[2] = read_data[0] | 0x01;
+
+    crc = s_lc709203f_calc_crc(read_data, 5);
+
+    if (crc != read_data[5])
+    {
+        return ESP_ERR_INVALID_CRC;
+    }
+
+    if (value)
+    {
+        *value = read_data[3] | (read_data[4] << 8);
+    }
+
+    return ESP_OK;
+}
+
+inline static esp_err_t s_i2c_dev_write_reg_word(i2c_dev_t *dev, uint8_t reg, uint16_t value)
+{
+    uint8_t write_data[5];
+    write_data[0] = dev->addr << 1;
+    write_data[1] = reg;
+    write_data[2] = value & 0xFF;
+    write_data[3] = value >> 8;
+    write_data[4] = s_lc709203f_calc_crc(write_data, 4);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, reg, write_data + 2, 3));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t lc709203f_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+
+    dev->addr = LC709203F_I2C_ADDR;
+    dev->port = port;
+    dev->cfg.sda_io_num = sda_gpio;
+    dev->cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->cfg.master.clk_speed = LC709003F_I2C_MAX_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(dev);
+}
+
+esp_err_t lc709203f_free_desc(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(dev);
+}
+
+esp_err_t lc709203f_before_rsoc(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+    return s_i2c_dev_write_reg_word(dev, LC709203F_REG_BEFORE_RSOC, LC709203F_INIT_RSOC_VAL);
+}
+
+esp_err_t lc709203f_initial_rsoc(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+    return s_i2c_dev_write_reg_word(dev, LC709203F_REG_INITIAL_RSOC, LC709203F_INIT_RSOC_VAL);
+}
+
+esp_err_t lc709203f_get_alarm_low_rsoc(i2c_dev_t *dev, uint8_t *rsoc)
+{
+    CHECK_ARG(dev);
+
+    return s_i2c_dev_read_word(dev, LC709203F_REG_ALARM_LOW_RSOC, (uint16_t *)rsoc);
+}
+
+esp_err_t lc709203f_get_alarm_low_voltage(i2c_dev_t *dev, uint16_t *voltage)
+{
+    CHECK_ARG(dev);
+
+    return s_i2c_dev_read_word(dev, LC709203F_REG_ALARM_LOW_VOLTAGE, voltage);
+}
+
+esp_err_t lc709203f_get_apa(i2c_dev_t *dev, uint8_t *apa)
+{
+    CHECK_ARG(dev);
+
+    return s_i2c_dev_read_word(dev, LC709203F_REG_APA, (uint16_t *)apa);
+}
+
+esp_err_t lc709203f_get_apt(i2c_dev_t *dev, uint16_t *apt)
+{
+    CHECK_ARG(dev);
+
+    return s_i2c_dev_read_word(dev, LC709203F_REG_APT, apt);
+}
+
+esp_err_t lc709203f_get_battery_profile(i2c_dev_t *dev, lc709203f_battery_profile_t *profile)
+{
+    CHECK_ARG(dev);
+
+    return s_i2c_dev_read_word(dev, LC709203F_REG_CHANGE_PARAMETER, (uint16_t *)profile);
+}
+
+esp_err_t lc709203f_get_battery_profile_code(i2c_dev_t *dev, uint16_t *code)
+{
+    CHECK_ARG(dev);
+
+    return s_i2c_dev_read_word(dev, LC709203F_REG_NUM_PARAMETER, code);
+}
+
+esp_err_t lc709203f_get_cell_ite(i2c_dev_t *dev, uint16_t *ite)
+{
+    CHECK_ARG(dev);
+
+    return s_i2c_dev_read_word(dev, LC709203F_REG_CELL_ITE, ite);
+}
+
+esp_err_t lc709203f_get_cell_temperature(i2c_dev_t *dev, float *temperature)
+{
+    CHECK_ARG(dev);
+
+    uint16_t temp = 0;
+
+    esp_err_t ret = s_i2c_dev_read_word(dev, LC709203F_REG_CELL_TEMPERATURE, &temp);
+
+    if (ret == ESP_OK)
+    {
+        *temperature = temp / 10.0;
+    }
+
+    return ret;
+}
+
+esp_err_t lc709203f_get_cell_temperature_celsius(i2c_dev_t *dev, float *temperature)
+{
+    esp_err_t ret = lc709203f_get_cell_temperature(dev, temperature);
+
+    if (ret == ESP_OK)
+    {
+        *temperature = *temperature - 273;
+    }
+
+    return ret;
+}
+
+esp_err_t lc709203f_get_cell_voltage(i2c_dev_t *dev, uint16_t *voltage)
+{
+    CHECK_ARG(dev);
+
+    return s_i2c_dev_read_word(dev, LC709203F_REG_CELL_VOLTAGE, voltage);
+}
+
+esp_err_t lc709203f_get_current_direction(i2c_dev_t *dev, lc709203f_direction_t *direction)
+{
+    CHECK_ARG(dev);
+
+    return s_i2c_dev_read_word(dev, LC709203F_REG_CELL_VOLTAGE, (uint16_t *)direction);
+}
+
+esp_err_t lc709203f_get_ic_version(i2c_dev_t *dev, uint16_t *ic_version)
+{
+    CHECK_ARG(dev);
+
+    return s_i2c_dev_read_word(dev, LC709203F_REG_IC_VERSION, ic_version);
+}
+
+esp_err_t lc709203f_get_power_mode(i2c_dev_t *dev, lc709203f_power_mode_t *mode)
+{
+    CHECK_ARG(dev);
+
+    return s_i2c_dev_read_word(dev, LC709203F_REG_IC_POWER_MODE, (uint16_t *)mode);
+}
+
+esp_err_t lc709203f_get_rsoc(i2c_dev_t *dev, uint16_t *rsoc)
+{
+    CHECK_ARG(dev);
+
+    return s_i2c_dev_read_word(dev, LC709203F_REG_RSOC, rsoc);
+}
+
+esp_err_t lc709203f_get_temp_mode(i2c_dev_t *dev, lc709203f_temp_mode_t *mode)
+{
+    CHECK_ARG(dev);
+
+    return s_i2c_dev_read_word(dev, LC709203F_REG_STATUS_BIT, (uint16_t *)mode);
+}
+
+esp_err_t lc709203f_get_thermistor_b(i2c_dev_t *dev, uint16_t *value)
+{
+    CHECK_ARG(dev);
+
+    return s_i2c_dev_read_word(dev, LC709203F_REG_THERMISTOR_B, value);
+}
+
+esp_err_t lc709203f_set_alarm_low_rsoc(i2c_dev_t *dev, uint8_t rsoc)
+{
+    CHECK_ARG(dev);
+    CHECK_ARG(rsoc <= 100);
+
+    return s_i2c_dev_write_reg_word(dev, LC709203F_REG_ALARM_LOW_RSOC, (uint16_t)rsoc);
+}
+
+esp_err_t lc709203f_set_alarm_low_voltage(i2c_dev_t *dev, uint16_t voltage)
+{
+    CHECK_ARG(dev);
+
+    return s_i2c_dev_write_reg_word(dev, LC709203F_REG_ALARM_LOW_VOLTAGE, voltage);
+}
+
+esp_err_t lc709203f_set_apa(i2c_dev_t *dev, uint8_t apa)
+{
+    CHECK_ARG(dev);
+
+    return s_i2c_dev_write_reg_word(dev, LC709203F_REG_APA, (uint16_t)apa);
+}
+
+esp_err_t lc709203f_set_apt(i2c_dev_t *dev, uint16_t apt)
+{
+    CHECK_ARG(dev);
+
+    return s_i2c_dev_write_reg_word(dev, LC709203F_REG_APT, apt);
+}
+
+esp_err_t lc709203f_set_battery_profile(i2c_dev_t *dev, lc709203f_battery_profile_t profile)
+{
+    CHECK_ARG(dev);
+
+    return s_i2c_dev_write_reg_word(dev, LC709203F_REG_CHANGE_PARAMETER, (uint16_t)profile);
+}
+
+esp_err_t lc709203f_set_cell_temperature(i2c_dev_t *dev, float temperature)
+{
+    uint16_t temp = (uint16_t)(temperature * 10);
+
+    CHECK_ARG(dev);
+    CHECK_ARG(temp >= 0x09e4 && temperature <= 0x0D04);
+
+    return s_i2c_dev_write_reg_word(dev, LC709203F_REG_CELL_TEMPERATURE, temp);
+}
+
+esp_err_t lc709203f_set_cell_temperature_celsius(i2c_dev_t *dev, float temperature)
+{
+    CHECK_ARG(dev);
+
+    return lc709203f_set_cell_temperature(dev, temperature + 273);
+}
+
+esp_err_t lc709203f_set_current_direction(i2c_dev_t *dev, lc709203f_direction_t direction)
+{
+    CHECK_ARG(dev);
+
+    return s_i2c_dev_write_reg_word(dev, LC709203F_REG_CURRENT_DIRECTION, (uint16_t)direction);
+}
+
+esp_err_t lc709203f_set_power_mode(i2c_dev_t *dev, lc709203f_power_mode_t mode)
+{
+    CHECK_ARG(dev);
+
+    return s_i2c_dev_write_reg_word(dev, LC709203F_REG_IC_POWER_MODE, (uint16_t)mode);
+}
+
+esp_err_t lc709203f_set_temp_mode(i2c_dev_t *dev, lc709203f_temp_mode_t mode)
+{
+    CHECK_ARG(dev);
+
+    return s_i2c_dev_write_reg_word(dev, LC709203F_REG_STATUS_BIT, (uint16_t)mode);
+}
+
+esp_err_t lc709203f_set_thermistor_b(i2c_dev_t *dev, uint16_t value)
+{
+    CHECK_ARG(dev);
+
+    return s_i2c_dev_write_reg_word(dev, LC709203F_REG_THERMISTOR_B, value);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/lc709203f/lc709203f.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,440 @@
+/*
+ * Copyright (c) 2022 Jose Manuel Perez <user@your.dom.ain>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/**
+ * @file lc709203f.h
+ * @defgroup lc709203f lc709203f
+ * @{
+ *
+ * LC709203F Battery Fuel Gauge driver
+ *
+ */
+
+#ifndef __LC709203F__H__
+#define __LC709203F__H__
+
+#include <esp_err.h>
+#include <i2cdev.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define LC709203F_REG_BEFORE_RSOC       0x04 ///< Initialize before RSOC
+#define LC709203F_REG_THERMISTOR_B      0x06 ///< Read/write thermistor B
+#define LC709203F_REG_INITIAL_RSOC      0x07 ///< Initialize RSOC calculation
+#define LC709203F_REG_CELL_TEMPERATURE  0x08 ///< Read/write batt temperature
+#define LC709203F_REG_CELL_VOLTAGE      0x09 ///< Read batt voltage
+#define LC709203F_REG_CURRENT_DIRECTION 0x0A ///< Read/write current direction
+#define LC709203F_REG_APA               0x0B ///< Read/write Adjustment Pack Application
+#define LC709203F_REG_APT               0x0C ///< Read/write Adjustment Pack Thermistor
+#define LC709203F_REG_RSOC              0x0D ///< Read state of charge
+#define LC709203F_REG_CELL_ITE          0x0F ///< Read batt indicator to empty
+#define LC709203F_REG_IC_VERSION        0x11 ///< Read IC version
+#define LC709203F_REG_CHANGE_PARAMETER  0x12 ///< Set the battery profile
+#define LC709203F_REG_ALARM_LOW_RSOC    0x13 ///< Alarm on percent threshold
+#define LC709203F_REG_ALARM_LOW_VOLTAGE 0x14 ///< Alarm on voltage threshold
+#define LC709203F_REG_IC_POWER_MODE     0x15 ///< Sets sleep/power mode
+#define LC709203F_REG_STATUS_BIT        0x16 ///< Temperature obtaining method
+#define LC709203F_REG_NUM_PARAMETER     0x1A ///< Batt profile code
+
+/*!  Battery temperature source */
+typedef enum {
+    LC709203F_TEMP_MODE_I2C = 0x0000,
+    LC709203F_TEMP_MODE_THERMISTOR = 0x0001,
+} lc709203f_temp_mode_t;
+
+/*!  Chip power state */
+typedef enum {
+    LC709203F_POWER_MODE_OPERATIONAL = 0x0001,
+    LC709203F_POWER_MODE_SLEEP = 0x0002,
+} lc709203f_power_mode_t;
+
+typedef enum {
+    LC709203F_DIRECTION_AUTO = 0x0000,
+    LC709203F_DIRECTION_CHARGE = 0x0001,
+    LC709203F_DIRECTION_DISCHARGE = 0xFFFF,
+} lc709203f_direction_t;
+
+typedef enum {
+    LC709203F_BATTERY_PROFILE_0 = 0x0000,
+    LC709203F_BATTERY_PROFILE_1 = 0x0001,
+} lc709203f_battery_profile_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param[in] dev Device descriptor
+ * @param[in] port I2C port number
+ * @param[in] sda_gpio GPIO pin number for SDA
+ * @param[in] scl_gpio GPIO pin number for SCL
+ * @return
+ *      `ESP_INVALID_ARG` null dev
+ *      `ESP_OK` on success
+ */
+esp_err_t lc709203f_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param[in] dev Device descriptor
+ * @return
+ *      `ESP_INVALID_ARG` null dev
+ *      `ESP_OK` on success
+ */
+esp_err_t lc709203f_free_desc(i2c_dev_t *dev);
+
+/**
+ * @brief Executes RSOC initialization with sampled maximum voltage when 0xAA55 is set.
+ *
+ * @param[in] dev Device descriptor
+ * @return
+ *      `ESP_INVALID_ARG` null dev
+ *      `ESP_OK` on success
+ */
+esp_err_t lc709203f_before_rsoc(i2c_dev_t *dev);
+
+/**
+ * @brief Executes RSOC initialization when 0xAA55 is set.
+ *
+ * @param[in] dev Device descriptor
+ * @return
+ *      `ESP_INVALID_ARG` null dev
+ *      `ESP_OK` on success
+ */
+esp_err_t lc709203f_initial_rsoc(i2c_dev_t *dev);
+
+/**
+ * @brief Get alarm low rsoc (% unit)
+ *
+ * @note Disable alarm setting RSOC as 0 (0x0000)
+ *       Enable alarm setting RSOC in
+ *
+ * @param[in] dev Device descriptor
+ * @param[out] rsoc RSOC value (%)
+ * @return
+ *      `ESP_INVALID_ARG` null dev or rsoc out of range 0-100
+ *      `ESP_OK` on success
+ */
+esp_err_t lc709203f_get_alarm_low_rsoc(i2c_dev_t *dev, uint8_t *rsoc);
+
+/**
+ * @brief Get alarm low voltage (mV unit)
+ *
+ * @param[in] dev Device descriptor
+ * @param[out] voltage Voltage value (mV)
+ * @return
+ *      `ESP_INVALID_ARG` null dev
+ *      `ESP_OK` on success
+ */
+esp_err_t lc709203f_get_alarm_low_voltage(i2c_dev_t *dev, uint16_t *voltage);
+
+/**
+ * @brief Get APA (adjustment pack application)
+ *
+ * @param[in] dev Device descriptor
+ * @param[out] apa Current APA value
+ * @return
+ *      `ESP_INVALID_ARG` null dev
+ *      `ESP_OK` on success
+ */
+esp_err_t lc709203f_get_apa(i2c_dev_t *dev, uint8_t *apa);
+
+/**
+ * @brief Get APT (adjustment pack thermistor)
+ *
+ * @param[in] dev Device descriptor
+ * @param[out] apt Current APT value
+ * @return
+ *      `ESP_INVALID_ARG` null dev
+ *      `ESP_OK` on success
+ */
+esp_err_t lc709203f_get_apt(i2c_dev_t *dev, uint16_t *apt);
+
+/**
+ * @brief Get battery profile
+ *
+ * @param[in] dev Device descriptor
+ * @param[out] profile Current profile
+ * @return
+ *      `ESP_INVALID_ARG` null dev
+ *      `ESP_OK` on success
+ */
+esp_err_t lc709203f_get_battery_profile(i2c_dev_t *dev, lc709203f_battery_profile_t *profile);
+
+/**
+ * @brief Get battery profile code
+ *
+ * @param[in] dev Device descriptor
+ * @param[out] code Current profile code. 0x3001 or 0x0504 only.
+ * @return
+ *      `ESP_INVALID_ARG` null dev
+ *      `ESP_OK` on success
+ */
+esp_err_t lc709203f_get_battery_profile_code(i2c_dev_t *dev, uint16_t *code);
+
+/**
+ * @brief Get ITE (indicator to empty) "RSOC (%) on 0-1000 scale"
+ *
+ * @param[in] dev Device descriptor
+ * @param[out] ite Current RSOC value
+ * @return
+ *      `ESP_INVALID_ARG` null dev
+ *      `ESP_OK` on success
+ */
+esp_err_t lc709203f_get_cell_ite(i2c_dev_t *dev, uint16_t *ite);
+
+/**
+ * @brief Get cell temperature in ºK. Min. unit is 0.1K
+ *
+ * @note Conversion ºK -> ºC = ºK + 273
+ *       Conversion ºC -> ºK = ºK + 273
+ *       Typical values:
+ *          0ºC -> 0x0AAC
+ *          25ºC -> 0x0BA6 (default)
+ *
+ * @param[in] dev Device descriptor
+ * @param[out] temperature Current temperature
+ * @return
+ *      `ESP_INVALID_ARG` null dev
+ *      `ESP_OK` on success
+ */
+esp_err_t lc709203f_get_cell_temperature(i2c_dev_t *dev, float *temperature);
+
+/**
+ * @brief Get cell temperature in ºC.
+ *
+ * @param[in] dev Device descriptor
+ * @param[out] temperature Current temperature
+ * @return
+ *      `ESP_INVALID_ARG` null dev
+ *      `ESP_OK` on success
+ */
+esp_err_t lc709203f_get_cell_temperature_celsius(i2c_dev_t *dev, float *temperature);
+
+/**
+ * @brief Get cell voltage
+ *
+ * @param[in] dev Device descriptor
+ * @param[out] voltage Current voltage
+ * @return
+ *      `ESP_INVALID_ARG` null dev
+ *      `ESP_OK` on success
+ */
+esp_err_t lc709203f_get_cell_voltage(i2c_dev_t *dev, uint16_t *voltage);
+
+/**
+ * @brief Set current direction
+ *
+ * @param[in] dev Device descriptor
+ * @param[out] direction Current direction
+ * @return
+ *      `ESP_INVALID_ARG` null dev
+ *      `ESP_OK` on success
+ */
+esp_err_t lc709203f_get_direction(i2c_dev_t *dev, lc709203f_direction_t *direction);
+
+/**
+ * @brief Get ID number of an IC
+ *
+ * @param[in] dev Device descriptor
+ * @param[out] ic_version IC ID number
+ * @return
+ *      `ESP_INVALID_ARG` null dev
+ *      `ESP_OK` on success
+ */
+esp_err_t lc709203f_get_ic_version(i2c_dev_t *dev, uint16_t *ic_version);
+
+/**
+ * @brief Get power mode
+ *
+ * @param[in] dev Device descriptor
+ * @param[out] mode Power mode
+ * @return
+ *      `ESP_INVALID_ARG` null dev
+ *      `ESP_OK` on success
+ */
+esp_err_t lc709203f_get_power_mode(i2c_dev_t *dev, lc709203f_power_mode_t *mode);
+
+/**
+ * @brief Get RSOC (%) on 0-100 scale
+ *
+ * @param[in] dev Device descriptor
+ * @param[out] rsoc Current RSOC value
+ * @return
+ *      `ESP_INVALID_ARG` null dev
+ *      `ESP_OK` on success
+ */
+esp_err_t lc709203f_get_rsoc(i2c_dev_t *dev, uint16_t *rsoc);
+
+/**
+ * @brief Get temperature mode
+ *
+ * @param[in] dev Device descriptor
+ * @param[out] mode Temperature obtaining mode
+ * @return
+ *      `ESP_INVALID_ARG` null dev
+ *      `ESP_OK` on success
+ */
+esp_err_t lc709203f_get_temp_mode(i2c_dev_t *dev, lc709203f_temp_mode_t *mode);
+
+/**
+ * @brief Get B-constant of the thermistor to be measured
+ *
+ * @param[in] dev Device descriptor
+ * @param[out] value B-constant value
+ * @return
+ *      `ESP_INVALID_ARG` null dev
+ *      `ESP_OK` on success
+ */
+esp_err_t lc709203f_get_thermistor_b(i2c_dev_t *dev, uint16_t *value);
+
+/**
+ * @brief Set alarm low rsoc (% unit)
+ *
+ * @note Disable alarm setting RSOC as 0 (0x0000)
+ *       Enable alarm setting RSOC in
+ *
+ * @param[in] dev Device descriptor
+ * @param[in] rsoc RSOC value (%)
+ * @return
+ *      `ESP_INVALID_ARG` null dev or rsoc out of range 0-100
+ *      `ESP_OK` on success
+ */
+esp_err_t lc709203f_set_alarm_low_rsoc(i2c_dev_t *dev, uint8_t rsoc);
+
+/**
+ * @brief Set alarm low voltage (mV unit)
+ *
+ * @param[in] dev Device descriptor
+ * @param[in] voltage Voltage value (mV)
+ * @return
+ *      `ESP_INVALID_ARG` null dev
+ *      `ESP_OK` on success
+ */
+esp_err_t lc709203f_set_alarm_low_voltage(i2c_dev_t *dev, uint16_t voltage);
+
+/**
+ * @brief Set APA (adjustment pack application)
+ *
+ * @param[in] dev Device descriptor
+ * @param[in] apa Value to set
+ * @return `ESP_OK` on success
+ */
+esp_err_t lc709203f_set_apa(i2c_dev_t *dev, uint8_t apa);
+
+/**
+ * @brief Set APA (adjustment pack thermistor)
+ *
+ * @param[in] dev Device descriptor
+ * @param[in] apt Value to set
+ * @return
+ * @return
+ *      `ESP_INVALID_ARG` null dev
+ *      `ESP_OK` on success
+ */
+esp_err_t lc709203f_set_apt(i2c_dev_t *dev, uint16_t apt);
+
+/**
+ * @brief Set battery profile
+ *
+ * @param[in] dev Device descriptor
+ * @param[in] profile Current profile
+ * @return
+ *      `ESP_INVALID_ARG` null dev
+ *      `ESP_OK` on success
+ */
+esp_err_t lc709203f_set_battery_profile(i2c_dev_t *dev, lc709203f_battery_profile_t profile);
+
+/**
+ * @brief Set cell temperature in ºK. Min. unit is 0.1ºK
+ *
+ * @note Conversion ºK -> ºC = ºK - 273
+ *       Conversion ºC -> ºK = ºK + 273
+ *       Typical values:
+ *          0ºC -> 0x0AAC
+ *          25ºC -> 0x0BA6 (default)
+ *
+ * @param[in] dev Device descriptor
+ * @param[in] temperature Temperature to set
+ * @return
+ *      `ESP_INVALID_ARG` null dev
+ *      `ESP_OK` on success
+ */
+esp_err_t lc709203f_set_cell_temperature(i2c_dev_t *dev, float temperature);
+
+/**
+ * @brief Set cell temperature in ºC.
+ *
+ * @param[in] dev Device descriptor
+ * @param[in] temperature Temperature to set
+ * @return
+ *      `ESP_INVALID_ARG` null dev
+ *      `ESP_OK` on success
+ */
+esp_err_t lc709203f_set_cell_temperature_celsius(i2c_dev_t *dev, float temperature);
+
+/**
+ * @brief Set current direction
+ *
+ * @param[in] dev Device descriptor
+ * @param[in] direction Current direction
+ * @return
+ *      `ESP_INVALID_ARG` null dev. In I2C mode, temperature not in range 0x09E4-0x0D04 ()
+ *      `ESP_OK` on success
+ */
+esp_err_t lc709203f_set_current_direction(i2c_dev_t *dev, lc709203f_direction_t direction);
+
+/**
+ * @brief Set power mode
+ *
+ * @param[in] dev Device descriptor
+ * @param[in] mode Power mode
+ * @return
+ *      `ESP_INVALID_ARG` null dev
+ *      `ESP_OK` on success
+ */
+esp_err_t lc709203f_set_power_mode(i2c_dev_t *dev, lc709203f_power_mode_t mode);
+
+/**
+ * @brief Set temperature mode
+ *
+ * @param[in] dev Device descriptor
+ * @param[in] mode Temperature obtaining mode
+ * @return
+ *      `ESP_INVALID_ARG` null dev
+ *      `ESP_OK` on success
+ */
+esp_err_t lc709203f_set_temp_mode(i2c_dev_t *dev, lc709203f_temp_mode_t mode);
+
+/**
+ * @brief Set B-constant of ther thermistor to be measured
+ *
+ * @param[in] dev Device descriptor
+ * @param[in] value B-constant value
+ * @return
+ *      `ESP_INVALID_ARG` null dev
+ *      `ESP_OK` on success
+ */
+esp_err_t lc709203f_set_thermistor_b(i2c_dev_t *dev, uint16_t value);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif // __LC709203F__H__
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/led_strip/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,23 @@
+---
+components:
+  - name: led_strip
+    description: RMT-based driver for WS2812B/SK6812/APA106/SM16703 LED strips
+    group: led
+    groups: []
+    code_owners: UncleRus
+    depends:
+      - name: driver
+      - name: log
+      - name: color
+      - name: esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: MIT
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2020
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/led_strip/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS led_strip.c
+    INCLUDE_DIRS .
+    REQUIRES driver log color esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/led_strip/Kconfig	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,17 @@
+menu "LED strip"
+
+config LED_STRIP_FLUSH_TIMEOUT
+    int "Strip flush timeout, ms"
+    default 1000
+
+config LED_STRIP_PAUSE_LENGTH
+	int "Delay between flushes, us"
+	default 50
+	help
+		This delay is between the sending full data to the all LEDs in strip.
+		Without this delay, a bug with "missing" pixels is possible: 
+		if delay between calls to led_strip_flush() is small, the LEDs consider
+		the new data package sent to all LEDs in strip to be a continuation of
+		the previous one.
+    
+endmenu
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/led_strip/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 Ruslan V. Uss
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/led_strip/README.md	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,8 @@
+# RMT-based ESP-IDF driver for WS2812B/SK6812/APA106 LED strips
+
+**WARNING!** If you try to use this driver simultaneously with Wi-Fi, you may
+encounter RMT transmission bugs. To avoid them, simply initialize device
+descriptor from the task bound to the second processor core.
+
+Interrupt handlers assigned during the initialization of the RMT driver are
+bound to the core on which the initialization took place.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/led_strip/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = driver log color esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/led_strip/led_strip.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,280 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file led_strip.c
+ *
+ * RMT-based ESP-IDF driver for WS2812B/SK6812/APA106/SM16703 LED strips
+ *
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+#include "led_strip.h"
+#include <esp_log.h>
+#include <esp_attr.h>
+#include <stdlib.h>
+#include <ets_sys.h>
+#include <esp_idf_lib_helpers.h>
+
+#if HELPER_TARGET_IS_ESP8266
+#error led_strip is not supported on ESP8266
+#endif
+
+#ifndef RMT_DEFAULT_CONFIG_TX
+#define RMT_DEFAULT_CONFIG_TX(gpio, channel_id)      \
+    {                                                \
+        .rmt_mode = RMT_MODE_TX,                     \
+        .channel = channel_id,                       \
+        .gpio_num = gpio,                            \
+        .clk_div = 80,                               \
+        .mem_block_num = 1,                          \
+        .tx_config = {                               \
+            .carrier_freq_hz = 38000,                \
+            .carrier_level = RMT_CARRIER_LEVEL_HIGH, \
+            .idle_level = RMT_IDLE_LEVEL_LOW,        \
+            .carrier_duty_percent = 33,              \
+            .carrier_en = false,                     \
+            .loop_en = false,                        \
+            .idle_output_en = true,                  \
+        }                                            \
+    }
+#endif
+
+static const char *TAG = "led_strip";
+
+#define LED_STRIP_RMT_CLK_DIV 2
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+#define COLOR_SIZE(strip) (3 + ((strip)->is_rgbw != 0))
+
+static void IRAM_ATTR _rmt_adapter(const void *src, rmt_item32_t *dest, size_t src_size,
+                                   size_t wanted_num, size_t *translated_size, size_t *item_num,
+                                   const rmt_item32_t *bit0, const rmt_item32_t *bit1)
+{
+    if (!src || !dest)
+    {
+        *translated_size = 0;
+        *item_num = 0;
+        return;
+    }
+    size_t size = 0;
+    size_t num = 0;
+    uint8_t *psrc = (uint8_t *)src;
+    rmt_item32_t *pdest = dest;
+#ifdef LED_STRIP_BRIGHTNESS
+    led_strip_t *strip;
+    esp_err_t r = rmt_translator_get_context(item_num, (void **)&strip);
+    uint8_t brightness = r == ESP_OK ? strip->brightness : 255;
+#endif
+    while (size < src_size && num < wanted_num)
+    {
+#ifdef LED_STRIP_BRIGHTNESS
+        uint8_t b = brightness != 255 ? scale8_video(*psrc, brightness) : *psrc;
+#else
+        uint8_t b = *psrc;
+#endif
+        for (int i = 0; i < 8; i++)
+        {
+            // MSB first
+            pdest->val = b & (1 << (7 - i)) ? bit1->val : bit0->val;
+            num++;
+            pdest++;
+        }
+        size++;
+        psrc++;
+    }
+    *translated_size = size;
+    *item_num = num;
+}
+
+typedef struct {
+    rmt_item32_t bit0, bit1;
+} led_rmt_t;
+
+static led_rmt_t rmt_items[LED_STRIP_TYPE_MAX] = { 0 };
+
+static void IRAM_ATTR ws2812_rmt_adapter(const void *src, rmt_item32_t *dest, size_t src_size,
+        size_t wanted_num, size_t *translated_size, size_t *item_num)
+{
+    _rmt_adapter(src, dest, src_size, wanted_num, translated_size, item_num, &rmt_items[LED_STRIP_WS2812].bit0, &rmt_items[LED_STRIP_WS2812].bit1);
+}
+
+static void IRAM_ATTR sk6812_rmt_adapter(const void *src, rmt_item32_t *dest, size_t src_size,
+        size_t wanted_num, size_t *translated_size, size_t *item_num)
+{
+    _rmt_adapter(src, dest, src_size, wanted_num, translated_size, item_num, &rmt_items[LED_STRIP_SK6812].bit0, &rmt_items[LED_STRIP_SK6812].bit1);
+}
+
+static void IRAM_ATTR apa106_rmt_adapter(const void *src, rmt_item32_t *dest, size_t src_size,
+        size_t wanted_num, size_t *translated_size, size_t *item_num)
+{
+    _rmt_adapter(src, dest, src_size, wanted_num, translated_size, item_num, &rmt_items[LED_STRIP_APA106].bit0, &rmt_items[LED_STRIP_APA106].bit1);
+}
+
+static void IRAM_ATTR sm16703_rmt_adapter(const void *src, rmt_item32_t *dest, size_t src_size,
+                                         size_t wanted_num, size_t *translated_size, size_t *item_num)
+{
+    _rmt_adapter(src, dest, src_size, wanted_num, translated_size, item_num, &rmt_items[LED_STRIP_SM16703].bit0, &rmt_items[LED_STRIP_SM16703].bit1);
+}
+
+typedef enum {
+    ORDER_GRB,
+    ORDER_RGB,
+} color_order_t;
+
+typedef struct {
+    uint32_t t0h, t0l, t1h, t1l;
+    color_order_t order;
+    sample_to_rmt_t adapter;
+} led_params_t;
+
+static const led_params_t led_params[] = {
+    [LED_STRIP_WS2812]  = { .t0h = 400, .t0l = 1000, .t1h = 1000, .t1l = 400, .order = ORDER_GRB, .adapter = ws2812_rmt_adapter },
+    [LED_STRIP_SK6812]  = { .t0h = 300, .t0l = 900,  .t1h = 600,  .t1l = 600, .order = ORDER_GRB, .adapter = sk6812_rmt_adapter },
+    [LED_STRIP_APA106]  = { .t0h = 350, .t0l = 1360, .t1h = 1360, .t1l = 350, .order = ORDER_RGB, .adapter = apa106_rmt_adapter },
+    [LED_STRIP_SM16703] = { .t0h = 300, .t0l = 900,  .t1h = 1360, .t1l = 350, .order = ORDER_RGB, .adapter = sm16703_rmt_adapter },
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+void led_strip_install()
+{
+    float ratio = (float)APB_CLK_FREQ / LED_STRIP_RMT_CLK_DIV / 1e09f;
+
+    for (size_t i = 0; i < LED_STRIP_TYPE_MAX; i++)
+    {
+        // 0 bit
+        rmt_items[i].bit0.duration0 = (uint32_t)(ratio * led_params[i].t0h);
+        rmt_items[i].bit0.level0 = 1;
+        rmt_items[i].bit0.duration1 = (uint32_t)(ratio * led_params[i].t0l);
+        rmt_items[i].bit0.level1 = 0;
+        // 1 bit
+        rmt_items[i].bit1.duration0 = (uint32_t)(ratio * led_params[i].t1h);
+        rmt_items[i].bit1.level0 = 1;
+        rmt_items[i].bit1.duration1 = (uint32_t)(ratio * led_params[i].t1l);
+        rmt_items[i].bit1.level1 = 0;
+    }
+}
+
+esp_err_t led_strip_init(led_strip_t *strip)
+{
+    CHECK_ARG(strip && strip->length > 0 && strip->type < LED_STRIP_TYPE_MAX);
+
+    strip->buf = calloc(strip->length, COLOR_SIZE(strip));
+    if (!strip->buf)
+    {
+        ESP_LOGE(TAG, "Not enough memory");
+        return ESP_ERR_NO_MEM;
+    }
+
+    rmt_config_t config = RMT_DEFAULT_CONFIG_TX(strip->gpio, strip->channel);
+    config.clk_div = LED_STRIP_RMT_CLK_DIV;
+
+    CHECK(rmt_config(&config));
+    CHECK(rmt_driver_install(config.channel, 0, 0));
+
+    CHECK(rmt_translator_init(config.channel, led_params[strip->type].adapter));
+#ifdef LED_STRIP_BRIGHTNESS
+    // No support for translator context prior to ESP-IDF 4.3
+    CHECK(rmt_translator_set_context(config.channel, strip));
+#endif
+
+    return ESP_OK;
+}
+
+esp_err_t led_strip_free(led_strip_t *strip)
+{
+    CHECK_ARG(strip && strip->buf);
+    free(strip->buf);
+
+    CHECK(rmt_driver_uninstall(strip->channel));
+
+    return ESP_OK;
+}
+
+esp_err_t led_strip_flush(led_strip_t *strip)
+{
+    CHECK_ARG(strip && strip->buf);
+
+    CHECK(rmt_wait_tx_done(strip->channel, pdMS_TO_TICKS(CONFIG_LED_STRIP_FLUSH_TIMEOUT)));
+    ets_delay_us(CONFIG_LED_STRIP_PAUSE_LENGTH);
+    return rmt_write_sample(strip->channel, strip->buf,
+                            strip->length * COLOR_SIZE(strip), false);
+}
+
+bool led_strip_busy(led_strip_t *strip)
+{
+    if (!strip) return false;
+    return rmt_wait_tx_done(strip->channel, 0) == ESP_ERR_TIMEOUT;
+}
+
+esp_err_t led_strip_wait(led_strip_t *strip, TickType_t timeout)
+{
+    CHECK_ARG(strip);
+
+    return rmt_wait_tx_done(strip->channel, timeout);
+}
+
+esp_err_t led_strip_set_pixel(led_strip_t *strip, size_t num, rgb_t color)
+{
+    CHECK_ARG(strip && strip->buf && num <= strip->length);
+    size_t idx = num * COLOR_SIZE(strip);
+    switch (led_params[strip->type].order)
+    {
+        case ORDER_GRB:
+            strip->buf[idx] = color.g;
+            strip->buf[idx + 1] = color.r;
+            strip->buf[idx + 2] = color.b;
+            if (strip->is_rgbw)
+                strip->buf[idx + 3] = rgb_luma(color);
+            break;
+        case ORDER_RGB:
+            strip->buf[idx] = color.r;
+            strip->buf[idx + 1] = color.g;
+            strip->buf[idx + 2] = color.b;
+            if (strip->is_rgbw)
+                strip->buf[idx + 3] = rgb_luma(color);
+            break;
+    }
+    return ESP_OK;
+}
+
+esp_err_t led_strip_set_pixels(led_strip_t *strip, size_t start, size_t len, rgb_t *data)
+{
+    CHECK_ARG(strip && strip->buf && len && start + len <= strip->length);
+    for (size_t i = 0; i < len; i++)
+        CHECK(led_strip_set_pixel(strip, i + start, data[i]));
+    return ESP_OK;
+}
+
+esp_err_t led_strip_fill(led_strip_t *strip, size_t start, size_t len, rgb_t color)
+{
+    CHECK_ARG(strip && strip->buf && len && start + len <= strip->length);
+
+    for (size_t i = start; i < start + len; i++)
+        CHECK(led_strip_set_pixel(strip, i, color));
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/led_strip/led_strip.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,176 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file led_strip.h
+ * @defgroup led_strip led_strip
+ * @{
+ *
+ * RMT-based ESP-IDF driver for WS2812B/SK6812/APA106/SM16703 LED strips
+ *
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+#ifndef __LED_STRIP_H__
+#define __LED_STRIP_H__
+
+#include <driver/gpio.h>
+#include <esp_err.h>
+#include <driver/rmt.h>
+#include <color.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0)
+#define LED_STRIP_BRIGHTNESS 1
+#endif
+
+/**
+ * LED type
+ */
+typedef enum
+{
+    LED_STRIP_WS2812 = 0,
+    LED_STRIP_SK6812,
+    LED_STRIP_APA106,
+    LED_STRIP_SM16703,
+
+    LED_STRIP_TYPE_MAX
+} led_strip_type_t;
+
+/**
+ * LED strip descriptor
+ */
+typedef struct
+{
+    led_strip_type_t type; ///< LED type
+    bool is_rgbw;          ///< true for RGBW strips
+#ifdef LED_STRIP_BRIGHTNESS
+    uint8_t brightness;    ///< Brightness 0..255, call ::led_strip_flush() after change.
+                           ///< Supported only for ESP-IDF version >= 4.3
+#endif
+    size_t length;         ///< Number of LEDs in strip
+    gpio_num_t gpio;       ///< Data GPIO pin
+    rmt_channel_t channel; ///< RMT channel
+    uint8_t *buf;
+} led_strip_t;
+
+/**
+ * @brief Setup library
+ *
+ * This method must be called before any other led_strip methods
+ */
+void led_strip_install();
+
+/**
+ * @brief Initialize LED strip and allocate buffer memory
+ *
+ * @param strip Descriptor of LED strip
+ * @return `ESP_OK` on success
+ */
+esp_err_t led_strip_init(led_strip_t *strip);
+
+/**
+ * @brief Deallocate buffer memory and release RMT channel
+ *
+ * @param strip Descriptor of LED strip
+ * @return `ESP_OK` on success
+ */
+esp_err_t led_strip_free(led_strip_t *strip);
+
+/**
+ * @brief Send strip buffer to LEDs
+ *
+ * @param strip Descriptor of LED strip
+ * @return `ESP_OK` on success
+ */
+esp_err_t led_strip_flush(led_strip_t *strip);
+
+/**
+ * @brief Check if associated RMT channel is busy
+ *
+ * @param strip Descriptor of LED strip
+ * @return true if RMT peripherals is busy
+ */
+bool led_strip_busy(led_strip_t *strip);
+
+/**
+ * @brief Wait until RMT peripherals is free to send buffer to LEDs
+ *
+ * @param strip Descriptor of LED strip
+ * @param timeout Timeout in RTOS ticks
+ * @return `ESP_OK` on success
+ */
+esp_err_t led_strip_wait(led_strip_t *strip, TickType_t timeout);
+
+/**
+ * @brief Set color of single LED in strip
+ *
+ * This function does not actually change colors of the LEDs.
+ * Call ::led_strip_flush() to send buffer to the LEDs.
+ *
+ * @param strip Descriptor of LED strip
+ * @param num LED number, 0..strip length - 1
+ * @param color RGB color
+ * @return `ESP_OK` on success
+ */
+esp_err_t led_strip_set_pixel(led_strip_t *strip, size_t num, rgb_t color);
+
+/**
+ * @brief Set colors of multiple LEDs
+ *
+ * This function does not actually change colors of the LEDs.
+ * Call ::led_strip_flush() to send buffer to the LEDs.
+ *
+ * @param strip Descriptor of LED strip
+ * @param start First LED index, 0-based
+ * @param len Number of LEDs
+ * @param data Pointer to RGB data
+ * @return `ESP_OK` on success
+ */
+esp_err_t led_strip_set_pixels(led_strip_t *strip, size_t start, size_t len, rgb_t *data);
+
+/**
+ * @brief Set multiple LEDs to the one color
+ *
+ * This function does not actually change colors of the LEDs.
+ * Call ::led_strip_flush() to send buffer to the LEDs.
+ *
+ * @param strip Descriptor of LED strip
+ * @param start First LED index, 0-based
+ * @param len Number of LEDs
+ * @param color RGB color
+ * @return `ESP_OK` on success
+ */
+esp_err_t led_strip_fill(led_strip_t *strip, size_t start, size_t len, rgb_t color);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __LED_STRIP_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/led_strip_spi/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,28 @@
+---
+components:
+  - name: led_strip_spi
+    description: SPI-based driver for SK9822/APA102 LED strips
+    group: led
+    groups: []
+    code_owners: trombik
+    depends:
+      # XXX conditional depends
+      - name: log
+      - name: color
+      - name: esp_idf_lib_helpers
+    thread_safe: N/A
+    targets:
+      - name: esp32
+      - name: esp32c3
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: MIT
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2020
+      - author:
+          name: trombik
+        year: 2021
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/led_strip_spi/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,12 @@
+if(${IDF_TARGET} STREQUAL esp8266)
+    set(req esp8266 log color esp_idf_lib_helpers)
+else()
+    set(req driver log color esp_idf_lib_helpers)
+endif()
+
+idf_component_register(
+    SRCS led_strip_spi.c led_strip_spi_sk9822.c
+    INCLUDE_DIRS .
+    REQUIRES ${req}
+)
+target_compile_options(${COMPONENT_LIB} PRIVATE -Werror=override-init)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/led_strip_spi/Kconfig	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,15 @@
+menu "LED strip SPI"
+    choice LED_STRIP_SPI_PROTOCOL_CHOOSE
+        prompt "Choose protocol for SPI"
+        default LED_STRIP_SPI_USING_SK9822
+        help
+            The protocol to use SPI communication.
+        config LED_STRIP_SPI_USING_SK9822
+            bool "SK9822 (and APA102)"
+    endchoice
+    config LED_STRIP_SPI_MUTEX_TIMEOUT_MS
+        int "Timeout in msec to obtain mutex"
+        default 1
+        help
+            Timeout when obtaining mutex with xSemaphoreTake.
+endmenu
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/led_strip_spi/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,22 @@
+MIT License
+
+Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+              2021 Tomoyuki Sakurai <y@rombik.org>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/led_strip_spi/README.md	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,8 @@
+# `led_strip_spi`
+
+A driver for SPI-based addressable LEDs.
+
+Supported LEDs are:
+
+- SK9822
+- APA102 (not tested)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/led_strip_spi/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = log color esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/led_strip_spi/led_strip_spi.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,370 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *               2021 Tomoyuki Sakurai <y@rombik.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file led_strip.c
+ *
+ * SPI-based ESP-IDF driver for SK9822 LED strips
+ *
+ */
+#include <string.h>
+#include <esp_err.h>
+#include <esp_log.h>
+#include <esp_attr.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/semphr.h>
+#include <esp_heap_caps.h>
+#include <esp_idf_lib_helpers.h>
+#include "led_strip_spi.h"
+
+#if defined(CONFIG_LED_STRIP_SPI_USING_SK9822)
+#include "led_strip_spi_sk9822.h"
+#endif
+
+#if HELPER_TARGET_IS_ESP32
+#include <driver/spi_master.h>
+#elif HELPER_TARGET_IS_ESP8266
+#include <driver/spi.h>
+#endif
+
+static const char *TAG = "led_strip_spi";
+static SemaphoreHandle_t mutex;
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+#define MUTEX_TIMEOUT   (CONFIG_LED_STRIP_SPI_MUTEX_TIMEOUT_MS / portTICK_PERIOD_MS)
+
+esp_err_t led_strip_spi_install()
+{
+    esp_err_t err;
+
+    mutex = xSemaphoreCreateMutex();
+    if (mutex == NULL) {
+        err = ESP_FAIL;
+        ESP_LOGE(TAG, "xSemaphoreCreateMutex(): failed");
+        goto fail;
+    }
+    err = ESP_OK;
+fail:
+    return err;
+}
+
+#if HELPER_TARGET_IS_ESP32
+static esp_err_t led_strip_spi_init_esp32(led_strip_spi_t *strip)
+{
+    CHECK_ARG(strip);
+
+    esp_err_t err = ESP_FAIL;
+    spi_bus_config_t bus_config = {
+        .mosi_io_num = strip->mosi_io_num,
+        .sclk_io_num = strip->sclk_io_num,
+        .miso_io_num = -1,
+        .quadhd_io_num = -1,
+        .quadwp_io_num = -1,
+#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)
+        .data4_io_num = -1,
+        .data5_io_num = -1,
+        .data6_io_num = -1,
+#endif
+        .flags = SPICOMMON_BUSFLAG_MASTER,
+        .max_transfer_sz = strip->max_transfer_sz,
+    };
+    spi_device_interface_config_t device_interface_config = {
+        .clock_speed_hz = strip->clock_speed_hz,
+        .mode = 3,
+        .spics_io_num = -1,
+        .queue_size = strip->queue_size,
+        .command_bits = 0,
+        .address_bits = 0,
+        .dummy_bits = 0,
+    };
+
+    if (xSemaphoreTake(mutex, MUTEX_TIMEOUT) != pdTRUE) {
+        err = ESP_FAIL;
+        ESP_LOGE(TAG, "xSemaphoreTake(): timeout");
+        goto fail_without_give;
+    }
+
+    strip->buf = heap_caps_malloc(LED_STRIP_SPI_BUFFER_SIZE(strip->length), MALLOC_CAP_DMA | MALLOC_CAP_32BIT);
+    if (strip->buf == NULL) {
+        ESP_LOGE(TAG, "heap_caps_malloc()");
+        err = ESP_ERR_NO_MEM;
+        goto fail;
+    }
+    memset(strip->buf, 0, LED_STRIP_SPI_BUFFER_SIZE(strip->length));
+
+    /* XXX length is in bit */
+    strip->transaction.length = LED_STRIP_SPI_BUFFER_SIZE(strip->length) * 8;
+
+    /* type-specific initialization  */
+#if CONFIG_LED_STRIP_SPI_USING_SK9822
+    err = led_strip_spi_sk9822_buf_init(strip);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "led_strip_spi_sk9822_buf_init(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+#endif
+    ESP_LOGD(TAG, "SPI buffer initialized");
+
+    err = spi_bus_initialize(strip->host_device, &bus_config, strip->dma_chan);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "spi_bus_initialize(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+    ESP_LOGD(TAG, "SPI bus initialized");
+
+    err = spi_bus_add_device(strip->host_device, &device_interface_config, &strip->device_handle);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "spi_bus_add_device(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+    ESP_LOGI(TAG, "LED strip initialized");
+fail:
+    if (xSemaphoreGive(mutex) != pdTRUE) {
+        ESP_LOGE(TAG, "xSemaphoreGive(): failed");
+    }
+fail_without_give:
+    return err;
+}
+#endif
+
+#if HELPER_TARGET_IS_ESP8266
+static esp_err_t led_strip_spi_init_esp8266(led_strip_spi_t *strip)
+{
+    esp_err_t err;
+    spi_config_t spi_config;
+    spi_interface_t interface_config = {
+
+        /* SPI mode 3, CPOL = 1, CPHA = 1 */
+        .cpol = 1,
+        .cpha = 1,
+        .bit_tx_order = 0,
+        .bit_rx_order = 0,
+        .byte_tx_order = 0,
+        .byte_rx_order = 0,
+        .mosi_en = 1,
+        .miso_en = 0,
+        .cs_en = 0,
+        .reserved9 = 23,
+    };
+
+    if (xSemaphoreTake(mutex, MUTEX_TIMEOUT) != pdTRUE) {
+        err = ESP_FAIL;
+        ESP_LOGE(TAG, "xSemaphoreTake(): timeout");
+        goto fail_without_give;
+    }
+
+    strip->buf = malloc(LED_STRIP_SPI_BUFFER_SIZE(strip->length));
+    if (strip->buf == NULL) {
+        ESP_LOGE(TAG, "malloc()");
+        err = ESP_ERR_NO_MEM;
+        goto fail;
+    }
+    memset(strip->buf, 0, LED_STRIP_SPI_BUFFER_SIZE(strip->length));
+
+#if CONFIG_LED_STRIP_SPI_USING_SK9822
+    err = led_strip_spi_sk9822_buf_init(strip);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "led_strip_spi_sk9822_buf_init(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+#endif
+    ESP_LOGI(TAG, "SPI buffer initialized");
+    spi_config.interface = interface_config;
+    spi_config.mode = SPI_MASTER_MODE;
+    spi_config.clk_div = strip->clk_div;
+    spi_config.event_cb = NULL;
+
+    err = spi_init(HSPI_HOST, &spi_config);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "spi_init(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+    ESP_LOGI(TAG, "SPI bus initialized");
+    ESP_LOGI(TAG, "LED strip initialized");
+fail:
+    if (xSemaphoreGive(mutex) != pdTRUE) {
+        ESP_LOGE(TAG, "xSemaphoreGive(): failed");
+    }
+fail_without_give:
+    return err;
+}
+#endif
+
+esp_err_t led_strip_spi_init(led_strip_spi_t *strip)
+{
+#if HELPER_TARGET_IS_ESP32
+    return led_strip_spi_init_esp32(strip);
+#elif HELPER_TARGET_IS_ESP8266
+    return led_strip_spi_init_esp8266(strip);
+#else
+#error "Unknown target"
+#endif
+}
+
+esp_err_t led_strip_spi_free(led_strip_spi_t *strip)
+{
+    CHECK_ARG(strip);
+
+    free(strip->buf);
+    return ESP_OK;
+}
+
+#if HELPER_TARGET_IS_ESP32
+static esp_err_t led_strip_spi_flush_esp32(led_strip_spi_t *strip)
+{
+    esp_err_t err = ESP_FAIL;
+    spi_transaction_t* t;
+
+    CHECK_ARG(strip);
+    if (!strip->transaction.tx_buffer) {
+        strip->transaction.tx_buffer = strip->buf;
+    }
+    strip->transaction.tx_buffer = strip->buf;
+    err = spi_device_queue_trans(strip->device_handle, &strip->transaction, portMAX_DELAY);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "spi_device_queue_trans(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+    err = spi_device_get_trans_result(strip->device_handle, &t, portMAX_DELAY);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "spi_device_get_trans_result(): %s", esp_err_to_name(err));
+        goto fail;
+    }
+    err = ESP_OK;
+fail:
+    return err;
+}
+#endif
+
+#if HELPER_TARGET_IS_ESP8266
+
+#define ESP8266_SPI_MAX_DATA_LENGTH 64 // in bytes
+
+static esp_err_t led_strip_spi_flush_esp8266(led_strip_spi_t *strip)
+{
+    esp_err_t err = ESP_FAIL;
+    spi_trans_t trans = {0};
+    int mosi_buffer_block_size, mosi_buffer_block_size_mod;
+
+    CHECK_ARG(strip);
+
+    /* XXX send ESP8266_SPI_MAX_DATA_LENGTH bytes data at a time. the
+     * documentation does not mention the limitation, but the SPI master
+     * driver complains:
+     * "spi: spi_master_trans(454): spi mosi must be shorter than 512 bits" */
+    mosi_buffer_block_size = LED_STRIP_SPI_BUFFER_SIZE(strip->length) / ESP8266_SPI_MAX_DATA_LENGTH;
+    mosi_buffer_block_size_mod = LED_STRIP_SPI_BUFFER_SIZE(strip->length) % ESP8266_SPI_MAX_DATA_LENGTH;
+
+    if (xSemaphoreTake(mutex, MUTEX_TIMEOUT) != pdTRUE) {
+        err = ESP_FAIL;
+        ESP_LOGE(TAG, "xSemaphoreTake(): timeout");
+        goto fail_without_give;
+    }
+
+    for (int i = 0; i < mosi_buffer_block_size; i++) {
+        trans.bits.mosi = ESP8266_SPI_MAX_DATA_LENGTH * 8; // bits, not bytes
+        trans.mosi = strip->buf + ESP8266_SPI_MAX_DATA_LENGTH * i;
+        err = spi_trans(HSPI_HOST, &trans);
+        if (err != ESP_OK) {
+            ESP_LOGE(TAG, "spi_trans(): %s", esp_err_to_name(err));
+            goto fail;
+        }
+    }
+    if (mosi_buffer_block_size_mod > 0) {
+        trans.bits.mosi = mosi_buffer_block_size_mod * 8; // bits, not bytes
+        trans.mosi = strip->buf + ESP8266_SPI_MAX_DATA_LENGTH * mosi_buffer_block_size;
+        err = spi_trans(HSPI_HOST, &trans);
+        if (err != ESP_OK) {
+            ESP_LOGE(TAG, "spi_trans(): %s", esp_err_to_name(err));
+            goto fail;
+        }
+    }
+fail:
+    if (xSemaphoreGive(mutex) != pdTRUE) {
+        ESP_LOGE(TAG, "xSemaphoreGive(): failed");
+    }
+fail_without_give:
+    return err;
+}
+#endif
+esp_err_t led_strip_spi_flush(led_strip_spi_t*strip)
+{
+#if HELPER_TARGET_IS_ESP32
+    return led_strip_spi_flush_esp32(strip);
+#elif HELPER_TARGET_IS_ESP8266
+    return led_strip_spi_flush_esp8266(strip);
+#else
+#error "Unknown target"
+#endif
+}
+
+esp_err_t led_strip_spi_set_pixel(led_strip_spi_t *strip, const int index, const rgb_t color)
+{
+    return led_strip_spi_set_pixel_brightness(strip, index, color, LED_STRIP_SPI_MAX_BRIGHTNESS);
+}
+
+esp_err_t led_strip_spi_set_pixels(led_strip_spi_t*strip, const int start, size_t len, const rgb_t data)
+{
+    return led_strip_spi_set_pixels_brightness(strip, start, len, data, LED_STRIP_SPI_MAX_BRIGHTNESS);
+}
+
+esp_err_t led_strip_spi_fill(led_strip_spi_t*strip, size_t start, size_t len, rgb_t color)
+{
+    return led_strip_spi_fill_brightness(strip, start, len, color, LED_STRIP_SPI_MAX_BRIGHTNESS);
+}
+
+esp_err_t led_strip_spi_set_pixel_brightness(led_strip_spi_t *strip, const int index, const rgb_t color, const uint8_t brightness)
+{
+#if CONFIG_LED_STRIP_SPI_USING_SK9822
+    return led_strip_spi_set_pixel_sk9822(strip, index, color, brightness);
+#endif
+    return ESP_ERR_NOT_SUPPORTED;
+}
+
+esp_err_t led_strip_spi_set_pixels_brightness(led_strip_spi_t*strip, const int start, size_t len, const rgb_t data, const uint8_t brightness)
+{
+    esp_err_t err = ESP_FAIL;
+
+    for (int i = 0; i < len; i++) {
+        err = led_strip_spi_set_pixel_brightness(strip, start + i, data, brightness);
+        if (err != ESP_OK) {
+            ESP_LOGE(TAG, "led_strip_spi_set_pixel(): %s", esp_err_to_name(err));
+            goto fail;
+        }
+    }
+fail:
+    return err;
+}
+
+esp_err_t led_strip_spi_fill_brightness(led_strip_spi_t*strip, size_t start, size_t len, rgb_t color, const uint8_t brightness)
+{
+    CHECK_ARG(strip && len && start + len <= strip->length);
+
+    for (size_t i = start; i < len; i++) {
+        CHECK(led_strip_spi_set_pixel_brightness(strip, i, color, brightness));
+    }
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/led_strip_spi/led_strip_spi.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,197 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *               2021 Tomoyuki Sakurai <y@rombik.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ *
+ * @file led_strip_spi.h
+ * @defgroup led_strip_spi led_strip_spi
+ * @{
+ *
+ */
+#ifndef __LED_STRIP_SPI_H__
+#define __LED_STRIP_SPI_H__
+
+#include <driver/gpio.h>
+#include <esp_err.h>
+#include <color.h>
+#include <esp_idf_lib_helpers.h>
+
+#if HELPER_TARGET_IS_ESP32
+#include <driver/spi_master.h>
+#include "led_strip_spi_esp32.h"
+#define LED_STRIP_SPI_DEFAULT()   LED_STRIP_SPI_DEFAULT_ESP32() ///< an alias of `LED_STRIP_SPI_DEFAULT_ESP32()` or `LED_STRIP_SPI_DEFAULT_ESP8266()`
+typedef led_strip_spi_esp32_t led_strip_spi_t;
+#endif
+
+#if HELPER_TARGET_IS_ESP8266
+#include <driver/spi.h>
+#include "led_strip_spi_esp8266.h"
+#define LED_STRIP_SPI_DEFAULT()   LED_STRIP_SPI_DEFAULT_ESP8266()
+typedef led_strip_spi_esp8266_t led_strip_spi_t;
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * * add LED_STRIP_SPI_USING_$NAME to Kconfig
+ * * define `LED_STRIP_SPI_BUFFER_SIZE(N_PIXEL)` that returns the required
+ *   size of buffer for the $NAME.
+ */
+
+#if defined(CONFIG_LED_STRIP_SPI_USING_SK9822)
+#include "led_strip_spi_sk9822.h"
+#else
+#error "unknown LED type"
+#endif
+
+/**
+ * Maximum brightness value for a pixel.
+ */
+#define LED_STRIP_SPI_MAX_BRIGHTNESS (100)
+
+/**
+ * @brief Setup the driver
+ *
+ * This method must be called before any other led_strip_spi methods
+ * @return `ESP_OK` on success
+ */
+esp_err_t led_strip_spi_install();
+
+/**
+ * @brief Initialize LED strip and allocate buffer memory
+ *
+ * @param strip Descriptor of LED strip
+ * @return `ESP_OK` on success
+ */
+esp_err_t led_strip_spi_init(led_strip_spi_t*strip);
+
+/**
+ * @brief Free LED strip
+ *
+ * @param strip Descriptor of LED strip
+ * @return `ESP_OK` on success
+ */
+esp_err_t led_strip_spi_free(led_strip_spi_t *strip);
+
+/**
+ * @brief Send strip buffer to LEDs
+ * @param strip Descriptor of LED strip
+ * @return `ESP_OK` on success
+ */
+esp_err_t led_strip_spi_flush(led_strip_spi_t*strip);
+
+/**
+ * @brief Set color of single LED in strip.
+ *
+ * This function does not actually change colors of the LEDs.
+ * Call ::led_strip_spi_flush() to send buffer to the LEDs.
+ *
+ * @param strip Descriptor of LED strip
+ * @param num LED number, [0:strip.length - 1].
+ * @param color RGB color
+ * @return `ESP_OK` on success
+ */
+esp_err_t led_strip_spi_set_pixel(led_strip_spi_t *strip, const int num, const rgb_t color);
+
+/**
+ * @brief Set color of single LED in strip.
+ *
+ * This function does not actually change colors of the LEDs.
+ * Call ::led_strip_spi_flush() to send buffer to the LEDs.
+ *
+ * @param strip Descriptor of LED strip
+ * @param num LED number, [0:strip.length - 1].
+ * @param color RGB color
+ * @param brightness Brightness of the LED, [0:100].
+ * @return `ESP_OK` on success
+ */
+esp_err_t led_strip_spi_set_pixel_brightness(led_strip_spi_t *strip, const int num, const rgb_t color, const uint8_t brightness);
+
+/**
+ * @brief Set colors of multiple LEDs
+ *
+ * This function does not actually change colors of the LEDs.
+ * Call ::led_strip_spi_flush() to send buffer to the LEDs.
+ *
+ * @param strip Descriptor of LED strip
+ * @param start First LED index, 0-based
+ * @param len Number of LEDs
+ * @param data Pointer to RGB data
+ * @return `ESP_OK` on success
+ */
+esp_err_t led_strip_spi_set_pixels(led_strip_spi_t*strip, const int start, size_t len, const rgb_t data);
+
+/**
+ * @brief Set colors of multiple LEDs
+ *
+ * This function does not actually change colors of the LEDs.
+ * Call ::led_strip_spi_flush() to send buffer to the LEDs.
+ *
+ * @param strip Descriptor of LED strip
+ * @param start First LED index, 0-based
+ * @param len Number of LEDs
+ * @param data Pointer to RGB data
+ * @param brightness Brightness of the LED, [0:100].
+ * @return `ESP_OK` on success
+ */
+esp_err_t led_strip_spi_set_pixels_brightness(led_strip_spi_t*strip, const int start, size_t len, const rgb_t data, const uint8_t brightness);
+
+/**
+ * @brief Set multiple LEDs to the one color.
+ *
+ * This function does not actually change colors of the LEDs.
+ * Call ::led_strip_spi_flush() to send buffer to the LEDs.
+ *
+ * @param strip Descriptor of LED strip
+ * @param start First LED index, 0-based
+ * @param len Number of LEDs
+ * @param color RGB color
+ * @return `ESP_OK` on success
+ */
+esp_err_t led_strip_spi_fill(led_strip_spi_t*strip, size_t start, size_t len, rgb_t color);
+
+/**
+ * @brief Set multiple LEDs to the one color.
+ *
+ * This function does not actually change colors of the LEDs.
+ * Call ::led_strip_spi_flush() to send buffer to the LEDs.
+ *
+ * @param strip Descriptor of LED strip
+ * @param start First LED index, 0-based
+ * @param len Number of LEDs
+ * @param color RGB color
+ * @param brightness Brightness of the LEDs, [0:100].
+ * @return `ESP_OK` on success
+ */
+esp_err_t led_strip_spi_fill_brightness(led_strip_spi_t*strip, size_t start, size_t len, rgb_t color, const uint8_t brightness);
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __LED_STRIP_SPI_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/led_strip_spi/led_strip_spi_esp32.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,109 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2021 Tomoyuki Sakurai <y@rombik.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file led_strip_spi_esp8266.h
+ * @defgroup led_strip_spi_esp32 led_strip_spi_esp32
+ * @{
+ */
+
+#if !defined(__LED_STRIP_SPI_ESP32__H__)
+#define __LED_STRIP_SPI_ESP32__H__
+
+#include <esp_idf_lib_helpers.h>
+#include <esp_idf_version.h>
+#include <driver/spi_master.h>
+
+#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 0, 0)
+#define LED_STRIP_SPI_DEFAULT_HOST_DEVICE  HSPI_HOST
+#else
+#define LED_STRIP_SPI_DEFAULT_HOST_DEVICE  SPI2_HOST ///< Default is `SPI2_HOST` (`HSPI_HOST` if `esp-idf` version is v3.x).
+#endif
+
+#if defined(CONFIG_IDF_TARGET_ESP32C3)
+#define LED_STRIP_SPI_DEFAULT_MOSI_IO_NUM   (7)
+#define LED_STRIP_SPI_DEFAULT_SCLK_IO_NUM   (6)
+#else
+#define LED_STRIP_SPI_DEFAULT_MOSI_IO_NUM   (13) ///< GPIO pin number of `LED_STRIP_SPI_DEFAULT_HOST_DEVICE`'s MOSI (default is 13 for ESP32, 7 for ESP32C3)
+#define LED_STRIP_SPI_DEFAULT_SCLK_IO_NUM   (14) ///< GPIO pin number of `LED_STRIP_SPI_DEFAULT_HOST_DEVICE`'s SCLK (default is 14 for ESP32, 6 for ESP32C3)
+#endif
+
+/**
+ * LED strip descriptor for ESP32-family.
+ */
+typedef struct
+{
+    void *buf;                          //< Pointer to the buffer
+    size_t length;                      //< Number of pixels
+    spi_host_device_t host_device;      //< SPI host device name, such as `SPI2_HOST`.
+    int mosi_io_num;                    ///< GPIO number of SPI MOSI.
+    int sclk_io_num;                    ///< GPIO number of SPI SCLK.
+    int max_transfer_sz;                ///< Maximum transfer size in bytes. Defaults to 4094 if 0.
+    int clock_speed_hz;                 ///< Clock speed in Hz.
+    int queue_size;                     ///< Queue size used by `spi_device_queue_trans()`.
+    spi_device_handle_t device_handle;  ///< Device handle assigned by the driver. The caller must provdie this.
+    int dma_chan;                       ///< DMA channed to use. Either 1 or 2.
+    spi_transaction_t transaction;      ///< SPI transaction used internally by the driver.
+} led_strip_spi_esp32_t;
+
+/**
+ * Default DMA channel to use. Default is `SPI_DMA_CH_AUTO` for ESP-IDF v4.3
+ * and newer, 1 for older versions.
+ */
+
+#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 3, 0)
+#define LED_STRIP_SPI_DEFAULT_DMA_CHAN  (1)
+#else
+#define LED_STRIP_SPI_DEFAULT_DMA_CHAN  SPI_DMA_CH_AUTO
+#endif
+/**
+ * Macro to initialize ::led_strip_spi_esp32_t
+ *
+ * `buf`: `NULL`,
+ * `length`: 1,
+ * `host_device`: `LED_STRIP_SPI_DEFAULT_HOST_DEVICE`,
+ * `mosi_io_num`: `LED_STRIP_SPI_DEFAULT_MOSI_IO_NUM`,
+ * `max_transfer_sz`: 0,
+ * `clock_speed_hz`: 1000000,
+ * `queue_size`: 1,
+ * `device_handle`: `NULL`,
+ * `dma_chan`: 1
+ */
+#define LED_STRIP_SPI_DEFAULT_ESP32() \
+{ \
+    .buf = NULL,                                      \
+    .length = 1,                                      \
+    .host_device = LED_STRIP_SPI_DEFAULT_HOST_DEVICE, \
+    .mosi_io_num = LED_STRIP_SPI_DEFAULT_MOSI_IO_NUM, \
+    .sclk_io_num = LED_STRIP_SPI_DEFAULT_SCLK_IO_NUM, \
+    .max_transfer_sz = 0,                             \
+    .clock_speed_hz = 1000000,                        \
+    .queue_size = 1,                                  \
+    .device_handle = NULL,                            \
+    .dma_chan = LED_STRIP_SPI_DEFAULT_DMA_CHAN,       \
+}
+
+/** @} */
+
+#endif // __LED_STRIP_SPI_ESP32__H__
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/led_strip_spi/led_strip_spi_esp8266.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,63 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2021 Tomoyuki Sakurai <y@rombik.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#if !defined(__LED_STRIP_SPI_ESP8266__H__)
+#define __LED_STRIP_SPI_ESP8266__H__
+
+/**
+ * @file led_strip_spi_esp8266.h
+ * @defgroup led_strip_spi_esp8266 led_strip_spi_esp8266
+ *
+ * @{
+ */
+
+#include <driver/spi.h>
+#include "led_strip_spi_esp8266.h"
+
+/**
+ * @struct led_strip_spi_esp8266_t
+ *
+ * LED strip descriptor for ESP8266.
+ */
+typedef struct
+{
+    void *buf;              ///< Pointer to the buffer.
+    size_t length;          ///< Number of pixels.
+    spi_clk_div_t clk_div;  ///< Value of `clk_div`, such as `SPI_2MHz_DIV`. See available values in `${IDF_PATH}/components/esp8266/include/driver/spi.h`.
+} led_strip_spi_esp8266_t;
+
+/**
+ * @brief A macro to initialize led_strip_spi_esp8266_t.
+ *
+ * `length`: 1 `clk_div`: SPI_2MHz_DIV
+ */
+#define LED_STRIP_SPI_DEFAULT_ESP8266() \
+{ \
+    .length = 1, \
+    .clk_div = SPI_2MHz_DIV, \
+}
+
+/** @} */
+
+#endif // __LED_STRIP_SPI_ESP8266__H__
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/led_strip_spi/led_strip_spi_sk9822.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,55 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2021 Tomoyuki Sakurai <y@rombik.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <esp_err.h>
+#include "led_strip_spi.h"
+#include "led_strip_spi_sk9822.h"
+
+esp_err_t led_strip_spi_set_pixel_sk9822(led_strip_spi_t *strip, size_t num, rgb_t color, uint8_t brightness)
+{
+    int index = (num + 1) * 4;
+    uint8_t cooked_brightness = 0;
+    /* Don't divided the range equal instead, the bottom 10 % is actually 0 brightness 
+       then every 3 percent after that increase the brightness level by 1 */
+    if (brightness >= 100) {
+        cooked_brightness = 31;
+    } else if (brightness > 7){
+        cooked_brightness = (brightness - 7) / 3;
+    }
+    ((uint8_t *)strip->buf)[index    ] = LED_STRIP_SPI_FRAME_SK9822_LED_MSB3 | 
+                                         (cooked_brightness & ((1 << LED_STRIP_SPI_FRAME_SK9822_LED_BRIGHTNESS_BITS) - 1));
+    ((uint8_t *)strip->buf)[index + 1] = color.b;
+    ((uint8_t *)strip->buf)[index + 2] = color.g;
+    ((uint8_t *)strip->buf)[index + 3] = color.r;
+    return ESP_OK;
+}
+
+esp_err_t led_strip_spi_sk9822_buf_init(led_strip_spi_t *strip)
+{
+    /* set mandatory bits in all LED frames */
+    for (int i = 1; i <= strip->length; i++) {
+        ((uint8_t *)strip->buf)[i * 4] = LED_STRIP_SPI_FRAME_SK9822_LED_MSB3;
+    }
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/led_strip_spi/led_strip_spi_sk9822.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,96 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *               2021 Tomoyuki Sakurai <y@rombik.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file led_strip_spi_sk9822.h
+ * @defgroup led_strip_spi_sk9822 led_strip_spi_sk9822
+ * @{
+ *
+ * Functions and macros for SK9822 LED strips.
+ *
+ * SPI data frame consists of:
+ *
+ * - A start frame of 32 zero bits (<0x00> <0x00> <0x00> <0x00>).
+ * - 32 bit LED frames for each LED in the string (<0xE0+brightness> &lt;blue&gt;
+ *   &lt;green&gt; &lt;red&gt;).
+ * - A SK9822 reset frame of 32 zero bits (<0x00> <0x00> <0x00> <0x00>).
+ * - An end frame consisting of at least (n/2) bits of 1, where n is the
+ *   number of LEDs in the string.
+ *
+ * For the details, see
+ * [SK9822 – a clone of the APA102?](https://cpldcpu.wordpress.com/2016/12/13/sk9822-a-clone-of-the-apa102/)
+ * and
+ * [Understanding the APA102 “Superled”](https://cpldcpu.wordpress.com/2014/11/30/understanding-the-apa102-superled/).
+ *
+ */
+#if !defined(__LED_STRIP_SPI_SK9822_H__)
+#define __LED_STRIP_SPI_SK9822_H__
+
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define LED_STRIP_SPI_FRAME_SK9822_START_SIZE  (4)      ///< The size in bytes of start frame.
+#define LED_STRIP_SPI_FRAME_SK9822_LED_SIZE    (4)      ///< The size in bytes of each LED frame.
+#define LED_STRIP_SPI_FRAME_SK9822_LEDS_SIZE(N_PIXEL) (LED_STRIP_SPI_FRAME_SK9822_LED_SIZE * N_PIXEL) ///< Total size in bytes of all LED frames in a strip. `N_PIXEL` is the number of pixels in the strip.
+#define LED_STRIP_SPI_FRAME_SK9822_RESET_SIZE  (4)      ///< The size in bytes of reset frame.
+#define LED_STRIP_SPI_FRAME_SK9822_END_SIZE(N_PIXEL) ((N_PIXEL / 16) + 1) ///< The size in bytes of the last frame. `N_PIXEL` is the number of pixels in the strip.
+
+#define LED_STRIP_SPI_FRAME_SK9822_LED_MSB3    (0xE0)   ///< A magic number of [31:29] in LED frames. The bits must be 1 (APA102, SK9822)
+
+#define LED_STRIP_SPI_FRAME_SK9822_LED_BRIGHTNESS_BITS (5) ///< Number of bits used to describe the brightness of the LED
+
+#define LED_STRIP_SPI_BUFFER_SIZE(N_PIXEL) (\
+        LED_STRIP_SPI_FRAME_SK9822_START_SIZE + \
+        LED_STRIP_SPI_FRAME_SK9822_LEDS_SIZE(N_PIXEL)  + \
+        LED_STRIP_SPI_FRAME_SK9822_RESET_SIZE + \
+        LED_STRIP_SPI_FRAME_SK9822_END_SIZE(N_PIXEL)) ///< A macro to caliculate required size of buffer. `N_PIXEL` is the number of pixels in the strip.
+
+/**
+ * @brief Initialize the buffer of SK9822 strip.
+ * @param[in] strip LED strip descriptor to initialize
+ * @return `ESP_OK` on success
+ */
+esp_err_t led_strip_spi_sk9822_buf_init(led_strip_spi_t *strip);
+
+/**
+ * @brief Set color of a pixel of SK9822 strip.
+ * @param[in] strip LED strip descriptor.
+ * @param[in] num Index of the LED pixel (zero-based).
+ * @param[in] color The color to set.
+ * @param[in] brightness The brightness to set, [0:100].
+ * @return `ESP_OK` on success.
+ */
+esp_err_t led_strip_spi_set_pixel_sk9822(led_strip_spi_t *strip, size_t num, rgb_t color, uint8_t brightness);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/lib8tion/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,20 @@
+---
+components:
+  - name: lib8tion
+    description: Math functions specifically designed for LED programming
+    group: common
+    groups: []
+    code_owners: UncleRus
+    depends: []
+    thread_safe: N/A
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: MIT
+    copyrights:
+      - author:
+          name: FastLED
+        year: 2013
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/lib8tion/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,11 @@
+if(${IDF_VERSION_MAJOR} STREQUAL 4 AND ${IDF_VERSION_MINOR} STREQUAL 1 AND ${IDF_VERSION_PATCH} STREQUAL 3)
+	set(req)	
+else()
+    set(req esp_timer)
+endif()
+
+idf_component_register(
+    INCLUDE_DIRS .
+    SRCS lib8tion.c
+    REQUIRES ${req}
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/lib8tion/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2013 FastLED
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/lib8tion/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,1 @@
+COMPONENT_ADD_INCLUDEDIRS = .
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/lib8tion/lib8tion.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013 FastLED
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "lib8tion.h"
+
+uint16_t rand16seed;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/lib8tion/lib8tion.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,772 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013 FastLED
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef __INC_LIB8TION_H
+#define __INC_LIB8TION_H
+
+/*
+
+ Fast, efficient 8-bit math functions specifically
+ designed for high-performance LED programming.
+
+ Included are:
+
+ - Saturating unsigned 8-bit add and subtract.
+ Instead of wrapping around if an overflow occurs,
+ these routines just 'clamp' the output at a maxumum
+ of 255, or a minimum of 0.  Useful for adding pixel
+ values.  E.g., qadd8(200, 100) = 255.
+
+ qadd8(i, j) == MIN((i + j), 0xFF)
+ qsub8(i, j) == MAX((i - j), 0)
+
+ - Saturating signed 8-bit ("7-bit") add.
+ qadd7(i, j) == MIN((i + j), 0x7F)
+
+
+ - Scaling (down) of unsigned 8- and 16- bit values.
+ Scaledown value is specified in 1/256ths.
+ scale8(i, sc) == (i * sc) / 256
+ scale16by8(i, sc) == (i * sc) / 256
+
+ Example: scaling a 0-255 value down into a
+ range from 0-99:
+ downscaled = scale8(originalnumber, 100);
+
+ A special version of scale8 is provided for scaling
+ LED brightness values, to make sure that they don't
+ accidentally scale down to total black at low
+ dimming levels, since that would look wrong:
+ scale8_video(i, sc) = ((i * sc) / 256) +? 1
+
+ Example: reducing an LED brightness by a
+ dimming factor:
+ new_bright = scale8_video(orig_bright, dimming);
+
+
+ - Fast 8- and 16- bit unsigned random numbers.
+ Significantly faster than Arduino random(), but
+ also somewhat less random.  You can add entropy.
+ random8()       == random from 0..255
+ random8(n)     == random from 0..(N-1)
+ random8(n, m)  == random from N..(M-1)
+
+ random16()      == random from 0..65535
+ random16(n)    == random from 0..(N-1)
+ random16(n, m) == random from N..(M-1)
+
+ random16_set_seed(k)    ==  seed = k
+ random16_add_entropy(k) ==  seed += k
+
+
+ - Absolute value of a signed 8-bit value.
+ abs8(i)     == abs(i)
+
+
+ - 8-bit math operations which return 8-bit values.
+ These are provided mostly for completeness,
+ not particularly for performance.
+ mul8(i, j)  == (i * j) & 0xFF
+ add8(i, j)  == (i + j) & 0xFF
+ sub8(i, j)  == (i - j) & 0xFF
+
+
+ - Fast 16-bit approximations of sin and cos.
+ Input angle is a uint16_t from 0-65535.
+ Output is a signed int16_t from -32767 to 32767.
+ sin16(x)  == sin((x/32768.0) * pi) * 32767
+ cos16(x)  == cos((x/32768.0) * pi) * 32767
+ Accurate to more than 99% in all cases.
+
+ - Fast 8-bit approximations of sin and cos.
+ Input angle is a uint8_t from 0-255.
+ Output is an UNsigned uint8_t from 0 to 255.
+ sin8(x)  == (sin((x/128.0) * pi) * 128) + 128
+ cos8(x)  == (cos((x/128.0) * pi) * 128) + 128
+ Accurate to within about 2%.
+
+
+ - Fast 8-bit "easing in/out" function.
+ ease8InOutCubic(x) == 3(x^i) - 2(x^3)
+ ease8InOutApprox(x) ==
+ faster, rougher, approximation of cubic easing
+ ease8InOutQuad(x) == quadratic (vs cubic) easing
+
+ - Cubic, Quadratic, and Triangle wave functions.
+ Input is a uint8_t representing phase withing the wave,
+ similar to how sin8 takes an angle 'theta'.
+ Output is a uint8_t representing the amplitude of
+ the wave at that point.
+ cubicwave8(x)
+ quadwave8(x)
+ triwave8(x)
+
+ - Square root for 16-bit integers.  About three times
+ faster and five times smaller than Arduino's built-in
+ generic 32-bit sqrt routine.
+ sqrt16(uint16_t x) == sqrt(x)
+
+ - Dimming and brightening functions for 8-bit
+ light values.
+ dim8_video(x)  == scale8_video(x, x)
+ dim8_raw(x)    == scale8(x, x)
+ dim8_lin(x)    == (x<128) ? ((x+1)/2) : scale8(x,x)
+ brighten8_video(x) == 255 - dim8_video(255 - x)
+ brighten8_raw(x) == 255 - dim8_raw(255 - x)
+ brighten8_lin(x) == 255 - dim8_lin(255 - x)
+ The dimming functions in particular are suitable
+ for making LED light output appear more 'linear'.
+
+
+ - Linear interpolation between two values, with the
+ fraction between them expressed as an 8- or 16-bit
+ fixed point fraction (fract8 or fract16).
+ lerp8by8(  fromU8, toU8, fract8)
+ lerp16by8( fromU16, toU16, fract8)
+ lerp15by8( fromS16, toS16, fract8)
+ == from + ((to - from) * fract8) / 256)
+ lerp16by16(fromU16, toU16, fract16)
+ == from + ((to - from) * fract16) / 65536)
+ map8(in, rangeStart, rangeEnd)
+ == map(in, 0, 255, rangeStart, rangeEnd);
+
+ - Beat generators which return sine or sawtooth
+ waves in a specified number of Beats Per Minute.
+ Sine wave beat generators can specify a low and
+ high range for the output.  Sawtooth wave beat
+ generators always range 0-255 or 0-65535.
+ beatsin8(BPM, low8, high8)
+ = (sine(beatphase) * (high8-low8)) + low8
+ beatsin16(BPM, low16, high16)
+ = (sine(beatphase) * (high16-low16)) + low16
+ beatsin88(BPM88, low16, high16)
+ = (sine(beatphase) * (high16-low16)) + low16
+ beat8(BPM)  = 8-bit repeating sawtooth wave
+ beat16(BPM) = 16-bit repeating sawtooth wave
+ beat88(BPM88) = 16-bit repeating sawtooth wave
+ BPM is beats per minute in either simple form
+ e.g. 120, or Q8.8 fixed-point form.
+ BPM88 is beats per minute in ONLY Q8.8 fixed-point
+ form.
+
+ Lib8tion is pronounced like 'libation': lie-BAY-shun
+
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include <esp_timer.h>
+
+#define LIB8STATIC               __attribute__ ((unused)) static inline
+#define LIB8STATIC_ALWAYS_INLINE __attribute__ ((always_inline)) static inline
+
+///@defgroup lib8tion Fast math functions
+///A variety of functions for working with numbers.
+///@{
+
+///////////////////////////////////////////////////////////////////////
+//
+// typdefs for fixed-point fractional types.
+//
+// sfract7 should be interpreted as signed 128ths.
+// fract8 should be interpreted as unsigned 256ths.
+// sfract15 should be interpreted as signed 32768ths.
+// fract16 should be interpreted as unsigned 65536ths.
+//
+// Example: if a fract8 has the value "64", that should be interpreted
+//          as 64/256ths, or one-quarter.
+//
+//
+//  fract8   range is 0 to 0.99609375
+//                 in steps of 0.00390625
+//
+//  sfract7  range is -0.9921875 to 0.9921875
+//                 in steps of 0.0078125
+//
+//  fract16  range is 0 to 0.99998474121
+//                 in steps of 0.00001525878
+//
+//  sfract15 range is -0.99996948242 to 0.99996948242
+//                 in steps of 0.00003051757
+//
+
+/// ANSI unsigned short _Fract.  range is 0 to 0.99609375
+///                 in steps of 0.00390625
+typedef uint8_t fract8;   ///< ANSI: unsigned short _Fract
+
+///  ANSI: signed short _Fract.  range is -0.9921875 to 0.9921875
+///                 in steps of 0.0078125
+typedef int8_t sfract7;  ///< ANSI: signed   short _Fract
+
+///  ANSI: unsigned _Fract.  range is 0 to 0.99998474121
+///                 in steps of 0.00001525878
+typedef uint16_t fract16;  ///< ANSI: unsigned       _Fract
+
+///  ANSI: signed _Fract.  range is -0.99996948242 to 0.99996948242
+///                 in steps of 0.00003051757
+typedef int16_t sfract15; ///< ANSI: signed         _Fract
+
+// accumXY types should be interpreted as X bits of integer,
+//         and Y bits of fraction.
+//         E.g., accum88 has 8 bits of int, 8 bits of fraction
+
+typedef uint16_t accum88;    ///< ANSI: unsigned short _Accum.  8 bits int, 8 bits fraction
+typedef int16_t  saccum78;   ///< ANSI: signed   short _Accum.  7 bits int, 8 bits fraction
+typedef uint32_t accum1616;  ///< ANSI: signed         _Accum. 16 bits int, 16 bits fraction
+typedef int32_t  saccum1516; ///< ANSI: signed         _Accum. 15 bits int, 16 bits fraction
+typedef uint16_t accum124;   ///< no direct ANSI counterpart. 12 bits int, 4 bits fraction
+typedef int32_t  saccum114;  ///< no direct ANSI counterpart. 1 bit int, 14 bits fraction
+
+/// typedef for IEEE754 "binary32" float type internals
+typedef union
+{
+    uint32_t i;
+    float f;
+    struct
+    {
+        uint32_t mantissa :23;
+        uint32_t exponent :8;
+        uint32_t signbit :1;
+    };
+    struct
+    {
+        uint32_t mant7 :7;
+        uint32_t mant16 :16;
+        uint32_t exp_ :8;
+        uint32_t sb_ :1;
+    };
+    struct
+    {
+        uint32_t mant_lo8 :8;
+        uint32_t mant_hi16_exp_lo1 :16;
+        uint32_t sb_exphi7 :8;
+    };
+} IEEE754binary32_t;
+
+#include "lib8tion/math8.h"
+#include "lib8tion/scale8.h"
+#include "lib8tion/random8.h"
+#include "lib8tion/trig8.h"
+
+///////////////////////////////////////////////////////////////////////
+//
+// float-to-fixed and fixed-to-float conversions
+//
+// Note that anything involving a 'float' on AVR will be slower.
+
+/// sfract15ToFloat: conversion from sfract15 fixed point to
+///                  IEEE754 32-bit float.
+LIB8STATIC float sfract15ToFloat(sfract15 y)
+{
+    return y / 32768.0;
+}
+
+/// conversion from IEEE754 float in the range (-1,1)
+///                  to 16-bit fixed point.  Note that the extremes of
+///                  one and negative one are NOT representable.  The
+///                  representable range is basically
+LIB8STATIC sfract15 floatToSfract15(float f)
+{
+    return f * 32768.0;
+}
+
+///////////////////////////////////////////////////////////////////////
+//
+// linear interpolation, such as could be used for Perlin noise, etc.
+//
+
+// A note on the structure of the lerp functions:
+// The cases for b>a and b<=a are handled separately for
+// speed: without knowing the relative order of a and b,
+// the value (a-b) might be overflow the width of a or b,
+// and have to be promoted to a wider, slower type.
+// To avoid that, we separate the two cases, and are able
+// to do all the math in the same width as the arguments,
+// which is much faster and smaller on AVR.
+
+/// linear interpolation between two unsigned 8-bit values,
+/// with 8-bit fraction
+LIB8STATIC uint8_t lerp8by8(uint8_t a, uint8_t b, fract8 frac)
+{
+    uint8_t result;
+    if (b > a)
+    {
+        uint8_t delta = b - a;
+        uint8_t scaled = scale8(delta, frac);
+        result = a + scaled;
+    }
+    else
+    {
+        uint8_t delta = a - b;
+        uint8_t scaled = scale8(delta, frac);
+        result = a - scaled;
+    }
+    return result;
+}
+
+/// linear interpolation between two unsigned 16-bit values,
+/// with 16-bit fraction
+LIB8STATIC uint16_t lerp16by16(uint16_t a, uint16_t b, fract16 frac)
+{
+    uint16_t result;
+    if (b > a)
+    {
+        uint16_t delta = b - a;
+        uint16_t scaled = scale16(delta, frac);
+        result = a + scaled;
+    }
+    else
+    {
+        uint16_t delta = a - b;
+        uint16_t scaled = scale16(delta, frac);
+        result = a - scaled;
+    }
+    return result;
+}
+
+/// linear interpolation between two unsigned 16-bit values,
+/// with 8-bit fraction
+LIB8STATIC uint16_t lerp16by8(uint16_t a, uint16_t b, fract8 frac)
+{
+    uint16_t result;
+    if (b > a)
+    {
+        uint16_t delta = b - a;
+        uint16_t scaled = scale16by8(delta, frac);
+        result = a + scaled;
+    }
+    else
+    {
+        uint16_t delta = a - b;
+        uint16_t scaled = scale16by8(delta, frac);
+        result = a - scaled;
+    }
+    return result;
+}
+
+/// linear interpolation between two signed 15-bit values,
+/// with 8-bit fraction
+LIB8STATIC int16_t lerp15by8(int16_t a, int16_t b, fract8 frac)
+{
+    int16_t result;
+    if (b > a)
+    {
+        uint16_t delta = b - a;
+        uint16_t scaled = scale16by8(delta, frac);
+        result = a + scaled;
+    }
+    else
+    {
+        uint16_t delta = a - b;
+        uint16_t scaled = scale16by8(delta, frac);
+        result = a - scaled;
+    }
+    return result;
+}
+
+/// linear interpolation between two signed 15-bit values,
+/// with 8-bit fraction
+LIB8STATIC int16_t lerp15by16(int16_t a, int16_t b, fract16 frac)
+{
+    int16_t result;
+    if (b > a)
+    {
+        uint16_t delta = b - a;
+        uint16_t scaled = scale16(delta, frac);
+        result = a + scaled;
+    }
+    else
+    {
+        uint16_t delta = a - b;
+        uint16_t scaled = scale16(delta, frac);
+        result = a - scaled;
+    }
+    return result;
+}
+
+///  map8: map from one full-range 8-bit value into a narrower
+/// range of 8-bit values, possibly a range of hues.
+///
+/// E.g. map myValue into a hue in the range blue..purple..pink..red
+/// hue = map8(myValue, HUE_BLUE, HUE_RED);
+///
+/// Combines nicely with the waveform functions (like sin8, etc)
+/// to produce continuous hue gradients back and forth:
+///
+///          hue = map8(sin8(myValue), HUE_BLUE, HUE_RED);
+///
+/// Mathematically simiar to lerp8by8, but arguments are more
+/// like Arduino's "map"; this function is similar to
+///
+///          map(in, 0, 255, rangeStart, rangeEnd)
+///
+/// but faster and specifically designed for 8-bit values.
+LIB8STATIC uint8_t map8(uint8_t in, uint8_t rangeStart, uint8_t rangeEnd)
+{
+    uint8_t rangeWidth = rangeEnd - rangeStart;
+    uint8_t out = scale8(in, rangeWidth);
+    out += rangeStart;
+    return out;
+}
+
+///////////////////////////////////////////////////////////////////////
+//
+// easing functions; see http://easings.net
+//
+
+/// ease8InOutQuad: 8-bit quadratic ease-in / ease-out function
+///                Takes around 13 cycles on AVR
+LIB8STATIC uint8_t ease8InOutQuad(uint8_t i)
+{
+    uint8_t j = i;
+    if (j & 0x80)
+    {
+        j = 255 - j;
+    }
+    uint8_t jj = scale8(j, j);
+    uint8_t jj2 = jj << 1;
+    if (i & 0x80)
+    {
+        jj2 = 255 - jj2;
+    }
+    return jj2;
+}
+
+/// ease16InOutQuad: 16-bit quadratic ease-in / ease-out function
+// C implementation at this point
+LIB8STATIC uint16_t ease16InOutQuad(uint16_t i)
+{
+    uint16_t j = i;
+    if (j & 0x8000)
+    {
+        j = 65535 - j;
+    }
+    uint16_t jj = scale16(j, j);
+    uint16_t jj2 = jj << 1;
+    if (i & 0x8000)
+    {
+        jj2 = 65535 - jj2;
+    }
+    return jj2;
+}
+
+/// ease8InOutCubic: 8-bit cubic ease-in / ease-out function
+///                 Takes around 18 cycles on AVR
+LIB8STATIC fract8 ease8InOutCubic(fract8 i)
+{
+    uint8_t ii = scale8(i, i);
+    uint8_t iii = scale8(ii, i);
+
+    uint16_t r1 = (3 * (uint16_t) (ii)) - (2 * (uint16_t) (iii));
+
+    /* the code generated for the above *'s automatically
+     cleans up R1, so there's no need to explicitily call
+     cleanup_R1(); */
+
+    uint8_t result = r1;
+
+    // if we got "256", return 255:
+    if (r1 & 0x100)
+    {
+        result = 255;
+    }
+    return result;
+}
+
+/// ease8InOutApprox: fast, rough 8-bit ease-in/ease-out function
+///                   shaped approximately like 'ease8InOutCubic',
+///                   it's never off by more than a couple of percent
+///                   from the actual cubic S-curve, and it executes
+///                   more than twice as fast.  Use when the cycles
+///                   are more important than visual smoothness.
+///                   Asm version takes around 7 cycles on AVR.
+LIB8STATIC fract8 ease8InOutApprox(fract8 i)
+{
+    if (i < 64)
+    {
+        // start with slope 0.5
+        i /= 2;
+    }
+    else if (i > (255 - 64))
+    {
+        // end with slope 0.5
+        i = 255 - i;
+        i /= 2;
+        i = 255 - i;
+    }
+    else
+    {
+        // in the middle, use slope 192/128 = 1.5
+        i -= 64;
+        i += (i / 2);
+        i += 32;
+    }
+
+    return i;
+}
+
+/// triwave8: triangle (sawtooth) wave generator.  Useful for
+///           turning a one-byte ever-increasing value into a
+///           one-byte value that oscillates up and down.
+///
+///           input         output
+///           0..127        0..254 (positive slope)
+///           128..255      254..0 (negative slope)
+///
+/// On AVR this function takes just three cycles.
+///
+LIB8STATIC uint8_t triwave8(uint8_t in)
+{
+    if (in & 0x80)
+        in = 255 - in;
+    return in << 1;
+}
+
+// quadwave8 and cubicwave8: S-shaped wave generators (like 'sine').
+//           Useful for turning a one-byte 'counter' value into a
+//           one-byte oscillating value that moves smoothly up and down,
+//           with an 'acceleration' and 'deceleration' curve.
+//
+//           These are even faster than 'sin8', and have
+//           slightly different curve shapes.
+//
+
+/// quadwave8: quadratic waveform generator.  Spends just a little more
+///            time at the limits than 'sine' does.
+LIB8STATIC uint8_t quadwave8(uint8_t in)
+{
+    return ease8InOutQuad(triwave8(in));
+}
+
+/// cubicwave8: cubic waveform generator.  Spends visibly more time
+///             at the limits than 'sine' does.
+LIB8STATIC uint8_t cubicwave8(uint8_t in)
+{
+    return ease8InOutCubic(triwave8(in));
+}
+
+/// squarewave8: square wave generator.  Useful for
+///           turning a one-byte ever-increasing value
+///           into a one-byte value that is either 0 or 255.
+///           The width of the output 'pulse' is
+///           determined by the pulsewidth argument:
+///
+///~~~
+///           If pulsewidth is 255, output is always 255.
+///           If pulsewidth < 255, then
+///             if input < pulsewidth  then output is 255
+///             if input >= pulsewidth then output is 0
+///~~~
+///
+/// the output looking like:
+///
+///~~~
+///     255   +--pulsewidth--+
+///      .    |              |
+///      0    0              +--------(256-pulsewidth)--------
+///~~~
+///
+/// @param in
+/// @param pulsewidth
+/// @returns square wave output
+LIB8STATIC uint8_t squarewave8(uint8_t in, uint8_t pulsewidth)
+{
+    return in < pulsewidth || pulsewidth == 255 ? 255 : 0;
+}
+
+// Beat generators - These functions produce waves at a given
+//                   number of 'beats per minute'.  Internally, they use
+//                   the Arduino function 'millis' to track elapsed time.
+//                   Accuracy is a bit better than one part in a thousand.
+//
+//       beat8(BPM) returns an 8-bit value that cycles 'BPM' times
+//                    per minute, rising from 0 to 255, resetting to zero,
+//                    rising up again, etc..  The output of this function
+//                    is suitable for feeding directly into sin8, and cos8,
+//                    triwave8, quadwave8, and cubicwave8.
+//       beat16(BPM) returns a 16-bit value that cycles 'BPM' times
+//                    per minute, rising from 0 to 65535, resetting to zero,
+//                    rising up again, etc.  The output of this function is
+//                    suitable for feeding directly into sin16 and cos16.
+//       beat88(BPM88) is the same as beat16, except that the BPM88 argument
+//                    MUST be in Q8.8 fixed point format, e.g. 120BPM must
+//                    be specified as 120*256 = 30720.
+//       beatsin8(BPM, uint8_t low, uint8_t high) returns an 8-bit value that
+//                    rises and falls in a sine wave, 'BPM' times per minute,
+//                    between the values of 'low' and 'high'.
+//       beatsin16(BPM, uint16_t low, uint16_t high) returns a 16-bit value
+//                    that rises and falls in a sine wave, 'BPM' times per
+//                    minute, between the values of 'low' and 'high'.
+//       beatsin88(BPM88, ...) is the same as beatsin16, except that the
+//                    BPM88 argument MUST be in Q8.8 fixed point format,
+//                    e.g. 120BPM must be specified as 120*256 = 30720.
+//
+//  BPM can be supplied two ways.  The simpler way of specifying BPM is as
+//  a simple 8-bit integer from 1-255, (e.g., "120").
+//  The more sophisticated way of specifying BPM allows for fractional
+//  "Q8.8" fixed point number (an 'accum88') with an 8-bit integer part and
+//  an 8-bit fractional part.  The easiest way to construct this is to multiply
+//  a floating point BPM value (e.g. 120.3) by 256, (e.g. resulting in 30796
+//  in this case), and pass that as the 16-bit BPM argument.
+//  "BPM88" MUST always be specified in Q8.8 format.
+//
+//  Originally designed to make an entire animation project pulse with brightness.
+//  For that effect, add this line just above your existing call to "FastLED.show()":
+//
+//     uint8_t bright = beatsin8(60 /*BPM*/, 192 /*dimmest*/, 255 /*brightest*/));
+//     FastLED.setBrightness(bright);
+//     FastLED.show();
+//
+//  The entire animation will now pulse between brightness 192 and 255 once per second.
+
+#define GET_MILLIS() (esp_timer_get_time() / 1000)
+
+/// beat16 generates a 16-bit 'sawtooth' wave at a given BPM,
+///        with BPM specified in Q8.8 fixed-point format; e.g.
+///        for this function, 120 BPM MUST BE specified as
+///        120*256 = 30720.
+///        If you just want to specify "120", use beat16 or beat8.
+LIB8STATIC uint16_t beat88(accum88 beats_per_minute_88, uint32_t timebase)
+{
+    // BPM is 'beats per minute', or 'beats per 60000ms'.
+    // To avoid using the (slower) division operator, we
+    // want to convert 'beats per 60000ms' to 'beats per 65536ms',
+    // and then use a simple, fast bit-shift to divide by 65536.
+    //
+    // The ratio 65536:60000 is 279.620266667:256; we'll call it 280:256.
+    // The conversion is accurate to about 0.05%, more or less,
+    // e.g. if you ask for "120 BPM", you'll get about "119.93".
+    return (((GET_MILLIS()) - timebase) * beats_per_minute_88 * 280) >> 16;
+}
+
+/// beat16 generates a 16-bit 'sawtooth' wave at a given BPM
+LIB8STATIC uint16_t beat16(accum88 beats_per_minute, uint32_t timebase)
+{
+    // Convert simple 8-bit BPM's to full Q8.8 accum88's if needed
+    if (beats_per_minute < 256)
+        beats_per_minute <<= 8;
+    return beat88(beats_per_minute, timebase);
+}
+
+/// beat8 generates an 8-bit 'sawtooth' wave at a given BPM
+LIB8STATIC uint8_t beat8(accum88 beats_per_minute, uint32_t timebase)
+{
+    return beat16(beats_per_minute, timebase) >> 8;
+}
+
+/// beatsin88 generates a 16-bit sine wave at a given BPM,
+///           that oscillates within a given range.
+///           For this function, BPM MUST BE SPECIFIED as
+///           a Q8.8 fixed-point value; e.g. 120BPM must be
+///           specified as 120*256 = 30720.
+///           If you just want to specify "120", use beatsin16 or beatsin8.
+LIB8STATIC uint16_t beatsin88(accum88 beats_per_minute_88, uint16_t lowest, uint16_t highest, uint32_t timebase, uint16_t phase_offset)
+{
+    uint16_t beat = beat88(beats_per_minute_88, timebase);
+    uint16_t beatsin = (sin16(beat + phase_offset) + 32768);
+    uint16_t rangewidth = highest - lowest;
+    uint16_t scaledbeat = scale16(beatsin, rangewidth);
+    uint16_t result = lowest + scaledbeat;
+    return result;
+}
+
+/// beatsin16 generates a 16-bit sine wave at a given BPM,
+///           that oscillates within a given range.
+LIB8STATIC uint16_t beatsin16(accum88 beats_per_minute, uint16_t lowest, uint16_t highest, uint32_t timebase, uint16_t phase_offset)
+{
+    uint16_t beat = beat16(beats_per_minute, timebase);
+    uint16_t beatsin = (sin16(beat + phase_offset) + 32768);
+    uint16_t rangewidth = highest - lowest;
+    uint16_t scaledbeat = scale16(beatsin, rangewidth);
+    uint16_t result = lowest + scaledbeat;
+    return result;
+}
+
+/// beatsin8 generates an 8-bit sine wave at a given BPM,
+///           that oscillates within a given range.
+LIB8STATIC uint8_t beatsin8(accum88 beats_per_minute, uint8_t lowest, uint8_t highest, uint32_t timebase, uint8_t phase_offset)
+{
+    uint8_t beat = beat8(beats_per_minute, timebase);
+    uint8_t beatsin = sin8(beat + phase_offset);
+    uint8_t rangewidth = highest - lowest;
+    uint8_t scaledbeat = scale8(beatsin, rangewidth);
+    uint8_t result = lowest + scaledbeat;
+    return result;
+}
+
+/// Return the current seconds since boot in a 16-bit value.  Used as part of the
+/// "every N time-periods" mechanism
+LIB8STATIC uint16_t seconds16()
+{
+    uint32_t ms = GET_MILLIS();
+    uint16_t s16;
+    s16 = ms / 1000;
+    return s16;
+}
+
+/// Return the current minutes since boot in a 16-bit value.  Used as part of the
+/// "every N time-periods" mechanism
+LIB8STATIC uint16_t minutes16()
+{
+    uint32_t ms = GET_MILLIS();
+    uint16_t m16;
+    m16 = (ms / (60000L)) & 0xFFFF;
+    return m16;
+}
+
+/// Return the current hours since boot in an 8-bit value.  Used as part of the
+/// "every N time-periods" mechanism
+LIB8STATIC uint8_t hours8()
+{
+    uint32_t ms = GET_MILLIS();
+    uint8_t h8;
+    h8 = (ms / (3600000L)) & 0xFF;
+    return h8;
+}
+
+/// Helper routine to divide a 32-bit value by 1024, returning
+/// only the low 16 bits. You'd think this would be just
+///   result = (in32 >> 10) & 0xFFFF;
+/// and on ARM, that's what you want and all is well.
+/// Used to convert millis to 'binary seconds' aka bseconds:
+/// one bsecond == 1024 millis.
+LIB8STATIC uint16_t div1024_32_16(uint32_t in32)
+{
+    uint16_t out16 = (in32 >> 10) & 0xFFFF;
+    return out16;
+}
+
+/// bseconds16 returns the current time-since-boot in
+/// "binary seconds", which are actually 1024/1000 of a
+/// second long.
+LIB8STATIC uint16_t bseconds16()
+{
+    uint32_t ms = GET_MILLIS();
+    uint16_t s16;
+    s16 = div1024_32_16(ms);
+    return s16;
+}
+
+///@}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/lib8tion/lib8tion/math8.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,291 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013 FastLED
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef __INC_LIB8TION_MATH_H
+#define __INC_LIB8TION_MATH_H
+
+///@ingroup lib8tion
+
+///@defgroup Math Basic math operations
+/// Fast, efficient 8-bit math functions specifically
+/// designed for high-performance LED programming.
+///@{
+
+/// add one byte to another, saturating at 0xFF
+/// @param i - first byte to add
+/// @param j - second byte to add
+/// @returns the sum of i & j, capped at 0xFF
+LIB8STATIC_ALWAYS_INLINE uint8_t qadd8(uint8_t i, uint8_t j)
+{
+    unsigned int t = i + j;
+    if (t > 255)
+        t = 255;
+    return t;
+}
+
+/// Add one byte to another, saturating at 0x7F and -0x80
+/// @param i - first byte to add
+/// @param j - second byte to add
+/// @returns the sum of i & j, capped at 0xFF
+LIB8STATIC_ALWAYS_INLINE int8_t qadd7(int8_t i, int8_t j)
+{
+    int16_t t = i + j;
+    if (t > 127)
+        t = 127;
+    else if (t < -128)
+        t = -127;
+    return t;
+}
+
+/// subtract one byte from another, saturating at 0x00
+/// @returns i - j with a floor of 0
+LIB8STATIC_ALWAYS_INLINE uint8_t qsub8(uint8_t i, uint8_t j)
+{
+    int t = i - j;
+    if (t < 0)
+        t = 0;
+    return t;
+}
+
+/// add one byte to another, with one byte result
+LIB8STATIC_ALWAYS_INLINE uint8_t add8(uint8_t i, uint8_t j)
+{
+    int t = i + j;
+    return t;
+}
+
+/// add one byte to two bytes, with two bytes result
+LIB8STATIC_ALWAYS_INLINE uint16_t add8to16(uint8_t i, uint16_t j)
+{
+    uint16_t t = i + j;
+    return t;
+}
+
+/// subtract one byte from another, 8-bit result
+LIB8STATIC_ALWAYS_INLINE uint8_t sub8(uint8_t i, uint8_t j)
+{
+    int t = i - j;
+    return t;
+}
+
+/// Calculate an integer average of two unsigned
+///       8-bit integer values (uint8_t).
+///       Fractional results are rounded down, e.g. avg8(20,41) = 30
+LIB8STATIC_ALWAYS_INLINE uint8_t avg8(uint8_t i, uint8_t j)
+{
+    return (i + j) >> 1;
+}
+
+/// Calculate an integer average of two unsigned
+///       16-bit integer values (uint16_t).
+///       Fractional results are rounded down, e.g. avg16(20,41) = 30
+LIB8STATIC_ALWAYS_INLINE uint16_t avg16(uint16_t i, uint16_t j)
+{
+    return (uint32_t)((uint32_t)(i) + (uint32_t)(j)) >> 1;
+}
+
+/// Calculate an integer average of two unsigned
+///       8-bit integer values (uint8_t).
+///       Fractional results are rounded up, e.g. avg8r(20,41) = 31
+LIB8STATIC_ALWAYS_INLINE uint8_t avg8r(uint8_t i, uint8_t j)
+{
+    return (i + j + 1) >> 1;
+}
+
+/// Calculate an integer average of two unsigned
+///       16-bit integer values (uint16_t).
+///       Fractional results are rounded up, e.g. avg16r(20,41) = 31
+LIB8STATIC_ALWAYS_INLINE uint16_t avg16r(uint16_t i, uint16_t j)
+{
+    return (uint32_t)((uint32_t)(i) + (uint32_t)(j) + 1) >> 1;
+}
+
+/// Calculate an integer average of two signed 7-bit
+///       integers (int8_t)
+///       If the first argument is even, result is rounded down.
+///       If the first argument is odd, result is rounded up.
+LIB8STATIC_ALWAYS_INLINE int8_t avg7(int8_t i, int8_t j)
+{
+    return (i >> 1) + (j >> 1) + (i & 0x1);
+}
+
+/// Calculate an integer average of two signed 15-bit
+///       integers (int16_t)
+///       If the first argument is even, result is rounded down.
+///       If the first argument is odd, result is rounded up.
+LIB8STATIC_ALWAYS_INLINE int16_t avg15(int16_t i, int16_t j)
+{
+    return (i >> 1) + (j >> 1) + (i & 0x1);
+}
+
+///       Calculate the remainder of one unsigned 8-bit
+///       value divided by anoter, aka A % M.
+///       Implemented by repeated subtraction, which is
+///       very compact, and very fast if A is 'probably'
+///       less than M.  If A is a large multiple of M,
+///       the loop has to execute multiple times.  However,
+///       even in that case, the loop is only two
+///       instructions long on AVR, i.e., quick.
+LIB8STATIC_ALWAYS_INLINE uint8_t mod8(uint8_t a, uint8_t m)
+{
+    while (a >= m)
+        a -= m;
+    return a;
+}
+
+///          Add two numbers, and calculate the modulo
+///          of the sum and a third number, M.
+///          In other words, it returns (A+B) % M.
+///          It is designed as a compact mechanism for
+///          incrementing a 'mode' switch and wrapping
+///          around back to 'mode 0' when the switch
+///          goes past the end of the available range.
+///          e.g. if you have seven modes, this switches
+///          to the next one and wraps around if needed:
+///            mode = addmod8( mode, 1, 7);
+///LIB8STATIC_ALWAYS_INLINESee 'mod8' for notes on performance.
+LIB8STATIC uint8_t addmod8(uint8_t a, uint8_t b, uint8_t m)
+{
+    a += b;
+    while (a >= m)
+        a -= m;
+    return a;
+}
+
+///          Subtract two numbers, and calculate the modulo
+///          of the difference and a third number, M.
+///          In other words, it returns (A-B) % M.
+///          It is designed as a compact mechanism for
+///          incrementing a 'mode' switch and wrapping
+///          around back to 'mode 0' when the switch
+///          goes past the end of the available range.
+///          e.g. if you have seven modes, this switches
+///          to the next one and wraps around if needed:
+///            mode = addmod8( mode, 1, 7);
+///LIB8STATIC_ALWAYS_INLINESee 'mod8' for notes on performance.
+LIB8STATIC uint8_t submod8(uint8_t a, uint8_t b, uint8_t m)
+{
+    a -= b;
+    while (a >= m)
+        a -= m;
+    return a;
+}
+
+/// 8x8 bit multiplication, with 8 bit result
+LIB8STATIC_ALWAYS_INLINE uint8_t mul8(uint8_t i, uint8_t j)
+{
+    return ((int)i * (int)(j)) & 0xFF;
+}
+
+/// saturating 8x8 bit multiplication, with 8 bit result
+/// @returns the product of i * j, capping at 0xFF
+LIB8STATIC_ALWAYS_INLINE uint8_t qmul8(uint8_t i, uint8_t j)
+{
+    unsigned p = (unsigned)i * (unsigned)j;
+    if (p > 255)
+        p = 255;
+    return p;
+}
+
+/// take abs() of a signed 8-bit uint8_t
+LIB8STATIC_ALWAYS_INLINE int8_t abs8(int8_t i)
+{
+    if (i < 0)
+        i = -i;
+    return i;
+}
+
+///         square root for 16-bit integers
+///         About three times faster and five times smaller
+///         than Arduino's general sqrt on AVR.
+LIB8STATIC uint8_t sqrt16(uint16_t x)
+{
+    if (x <= 1)
+        return x;
+
+    uint8_t low = 1; // lower bound
+    uint8_t hi, mid;
+
+    hi = x > 7904 ? 255 : (x >> 5) + 8; // initial estimate for upper bound
+
+    do
+    {
+        mid = (low + hi) >> 1;
+        if ((uint16_t)(mid * mid) > x)
+        {
+            hi = mid - 1;
+        }
+        else
+        {
+            if (mid == 255)
+                return 255;
+            low = mid + 1;
+        }
+    } while (hi >= low);
+
+    return low - 1;
+}
+
+/// blend a variable proportion(0-255) of one byte to another
+/// @param a - the starting byte value
+/// @param b - the byte value to blend toward
+/// @param amountOfB - the proportion (0-255) of b to blend
+/// @returns a byte value between a and b, inclusive
+LIB8STATIC uint8_t blend8(uint8_t a, uint8_t b, uint8_t amountOfB)
+{
+    // The BLEND_FIXED formula is
+    //
+    //   result = (  A*(amountOfA) + B*(amountOfB)              )/ 256
+    //
+    // …where amountOfA = 255-amountOfB.
+    //
+    // This formula will never return 255, which is why the BLEND_FIXED + SCALE8_FIXED version is
+    //
+    //   result = (  A*(amountOfA) + A + B*(amountOfB) + B      ) / 256
+    //
+    // We can rearrange this formula for some great optimisations.
+    //
+    //   result = (  A*(amountOfA) + A + B*(amountOfB) + B      ) / 256
+    //          = (  A*(255-amountOfB) + A + B*(amountOfB) + B  ) / 256
+    //          = (  A*(256-amountOfB) + B*(amountOfB) + B      ) / 256
+    //          = (  A*256 + B + B*(amountOfB) - A*(amountOfB)  ) / 256  // this is the version used in SCALE8_FIXED AVR below
+    //          = (  A*256 + B + (B-A)*(amountOfB)              ) / 256  // this is the version used in SCALE8_FIXED C below
+
+    uint16_t partial;
+    uint8_t result;
+
+    uint8_t amountOfA = 255 - amountOfB;
+
+    partial = (a * amountOfA);
+    partial += a;
+
+    partial += (b * amountOfB);
+    partial += b;
+
+    result = partial >> 8;
+
+    return result;
+}
+
+///@}
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/lib8tion/lib8tion/random8.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,118 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013 FastLED
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef __INC_LIB8TION_RANDOM_H
+#define __INC_LIB8TION_RANDOM_H
+///@ingroup lib8tion
+
+///@defgroup Random Fast random number generators
+/// Fast 8- and 16- bit unsigned random numbers.
+///  Significantly faster than Arduino random(), but
+///  also somewhat less random.  You can add entropy.
+///@{
+
+// X(n+1) = (2053 * X(n)) + 13849)
+#define FASTLED_RAND16_2053  ((uint16_t)(2053))
+#define FASTLED_RAND16_13849 ((uint16_t)(13849))
+
+#define APPLY_FASTLED_RAND16_2053(x) (x * FASTLED_RAND16_2053)
+
+/// random number seed
+extern uint16_t rand16seed; // = RAND16_SEED;
+
+/// Generate an 8-bit random number
+LIB8STATIC uint8_t random8()
+{
+    rand16seed = APPLY_FASTLED_RAND16_2053(rand16seed) + FASTLED_RAND16_13849;
+    // return the sum of the high and low bytes, for better
+    //  mixing and non-sequential correlation
+    return (uint8_t)(((uint8_t)(rand16seed & 0xFF)) + ((uint8_t)(rand16seed >> 8)));
+}
+
+/// Generate a 16 bit random number
+LIB8STATIC uint16_t random16()
+{
+    rand16seed = APPLY_FASTLED_RAND16_2053(rand16seed) + FASTLED_RAND16_13849;
+    return rand16seed;
+}
+
+/// Generate an 8-bit random number between 0 and lim
+/// @param lim the upper bound for the result
+LIB8STATIC uint8_t random8_to(uint8_t lim)
+{
+    uint8_t r = random8();
+    r = (r * lim) >> 8;
+    return r;
+}
+
+/// Generate an 8-bit random number in the given range
+/// @param min the lower bound for the random number
+/// @param lim the upper bound for the random number
+LIB8STATIC uint8_t random8_between(uint8_t min, uint8_t lim)
+{
+    uint8_t delta = lim - min;
+    uint8_t r = random8_to(delta) + min;
+    return r;
+}
+
+/// Generate an 16-bit random number between 0 and lim
+/// @param lim the upper bound for the result
+LIB8STATIC uint16_t random16_to(uint16_t lim)
+{
+    uint16_t r = random16();
+    uint32_t p = (uint32_t) lim * (uint32_t) r;
+    r = p >> 16;
+    return r;
+}
+
+/// Generate an 16-bit random number in the given range
+/// @param min the lower bound for the random number
+/// @param lim the upper bound for the random number
+LIB8STATIC uint16_t random16_between(uint16_t min, uint16_t lim)
+{
+    uint16_t delta = lim - min;
+    uint16_t r = random16_to(delta) + min;
+    return r;
+}
+
+/// Set the 16-bit seed used for the random number generator
+LIB8STATIC void random16_set_seed(uint16_t seed)
+{
+    rand16seed = seed;
+}
+
+/// Get the current seed value for the random number generator
+LIB8STATIC uint16_t random16_get_seed()
+{
+    return rand16seed;
+}
+
+/// Add entropy into the random number generator
+LIB8STATIC void random16_add_entropy(uint16_t entropy)
+{
+    rand16seed += entropy;
+}
+
+///@}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/lib8tion/lib8tion/scale8.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,201 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013 FastLED
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef __INC_LIB8TION_SCALE_H
+#define __INC_LIB8TION_SCALE_H
+
+///@ingroup lib8tion
+
+///@defgroup Scaling Scaling functions
+/// Fast, efficient 8-bit scaling functions specifically
+/// designed for high-performance LED programming.
+///
+/// Because of the AVR(Arduino) and ARM assembly language
+/// implementations provided, using these functions often
+/// results in smaller and faster code than the equivalent
+/// program using plain "C" arithmetic and logic.
+///@{
+
+///  scale one byte by a second one, which is treated as
+///  the numerator of a fraction whose denominator is 256
+///  In other words, it computes i * (scale / 256)
+///  4 clocks AVR with MUL, 2 clocks ARM
+LIB8STATIC_ALWAYS_INLINE uint8_t scale8(uint8_t i, fract8 scale)
+{
+    return (((uint16_t) i) * (1 + (uint16_t) (scale))) >> 8;
+}
+
+///  The "video" version of scale8 guarantees that the output will
+///  be only be zero if one or both of the inputs are zero.  If both
+///  inputs are non-zero, the output is guaranteed to be non-zero.
+///  This makes for better 'video'/LED dimming, at the cost of
+///  several additional cycles.
+LIB8STATIC_ALWAYS_INLINE uint8_t scale8_video(uint8_t i, fract8 scale)
+{
+    return (((int) i * (int) scale) >> 8) + ((i && scale) ? 1 : 0);
+}
+
+/// scale three one byte values by a fourth one, which is treated as
+///         the numerator of a fraction whose demominator is 256
+///         In other words, it computes r,g,b * (scale / 256)
+///
+///         THIS FUNCTION ALWAYS MODIFIES ITS ARGUMENTS IN PLACE
+LIB8STATIC void nscale8x3(uint8_t *r, uint8_t *g, uint8_t *b, fract8 scale)
+{
+    uint16_t scale_fixed = scale + 1;
+    *r = (((uint16_t) *r) * scale_fixed) >> 8;
+    *g = (((uint16_t) *g) * scale_fixed) >> 8;
+    *b = (((uint16_t) *b) * scale_fixed) >> 8;
+}
+
+/// scale three one byte values by a fourth one, which is treated as
+///         the numerator of a fraction whose demominator is 256
+///         In other words, it computes r,g,b * (scale / 256), ensuring
+/// that non-zero values passed in remain non zero, no matter how low the scale
+/// argument.
+///
+///         THIS FUNCTION ALWAYS MODIFIES ITS ARGUMENTS IN PLACE
+LIB8STATIC void nscale8x3_video(uint8_t *r, uint8_t *g, uint8_t *b, fract8 scale)
+{
+    uint8_t nonzeroscale = (scale != 0) ? 1 : 0;
+    *r = (*r == 0) ? 0 : (((int) *r * (int) (scale)) >> 8) + nonzeroscale;
+    *g = (*g == 0) ? 0 : (((int) *g * (int) (scale)) >> 8) + nonzeroscale;
+    *b = (*b == 0) ? 0 : (((int) *b * (int) (scale)) >> 8) + nonzeroscale;
+}
+
+///  scale two one byte values by a third one, which is treated as
+///         the numerator of a fraction whose demominator is 256
+///         In other words, it computes i,j * (scale / 256)
+///
+///         THIS FUNCTION ALWAYS MODIFIES ITS ARGUMENTS IN PLACE
+LIB8STATIC void nscale8x2(uint8_t *i, uint8_t *j, fract8 scale)
+{
+    uint16_t scale_fixed = scale + 1;
+    *i = (((uint16_t) *i) * scale_fixed) >> 8;
+    *j = (((uint16_t) *j) * scale_fixed) >> 8;
+}
+
+///  scale two one byte values by a third one, which is treated as
+///         the numerator of a fraction whose demominator is 256
+///         In other words, it computes i,j * (scale / 256), ensuring
+/// that non-zero values passed in remain non zero, no matter how low the scale
+/// argument.
+///
+///         THIS FUNCTION ALWAYS MODIFIES ITS ARGUMENTS IN PLACE
+LIB8STATIC void nscale8x2_video(uint8_t *i, uint8_t *j, fract8 scale)
+{
+    uint8_t nonzeroscale = (scale != 0) ? 1 : 0;
+    *i = (*i == 0) ? 0 : (((int) *i * (int) (scale)) >> 8) + nonzeroscale;
+    *j = (*j == 0) ? 0 : (((int) *j * (int) (scale)) >> 8) + nonzeroscale;
+}
+
+/// scale a 16-bit unsigned value by an 8-bit value,
+///         considered as numerator of a fraction whose denominator
+///         is 256. In other words, it computes i * (scale / 256)
+LIB8STATIC_ALWAYS_INLINE uint16_t scale16by8(uint16_t i, fract8 scale)
+{
+    return (i * (1 + ((uint16_t) scale))) >> 8;
+}
+
+/// scale a 16-bit unsigned value by a 16-bit value,
+///         considered as numerator of a fraction whose denominator
+///         is 65536. In other words, it computes i * (scale / 65536)
+
+LIB8STATIC uint16_t scale16(uint16_t i, fract16 scale)
+{
+    return ((uint32_t) (i) * (1 + (uint32_t) (scale))) / 65536;
+}
+///@}
+
+///@defgroup Dimming Dimming and brightening functions
+///
+/// Dimming and brightening functions
+///
+/// The eye does not respond in a linear way to light.
+/// High speed PWM'd LEDs at 50% duty cycle appear far
+/// brighter then the 'half as bright' you might expect.
+///
+/// If you want your midpoint brightness leve (128) to
+/// appear half as bright as 'full' brightness (255), you
+/// have to apply a 'dimming function'.
+///@{
+
+/// Adjust a scaling value for dimming
+LIB8STATIC uint8_t dim8_raw(uint8_t x)
+{
+    return scale8(x, x);
+}
+
+/// Adjust a scaling value for dimming for video (value will never go below 1)
+LIB8STATIC uint8_t dim8_video(uint8_t x)
+{
+    return scale8_video(x, x);
+}
+
+/// Linear version of the dimming function that halves for values < 128
+LIB8STATIC uint8_t dim8_lin(uint8_t x)
+{
+    if (x & 0x80)
+    {
+        x = scale8(x, x);
+    }
+    else
+    {
+        x += 1;
+        x /= 2;
+    }
+    return x;
+}
+
+/// inverse of the dimming function, brighten a value
+LIB8STATIC uint8_t brighten8_raw(uint8_t x)
+{
+    uint8_t ix = 255 - x;
+    return 255 - scale8(ix, ix);
+}
+
+/// inverse of the dimming function, brighten a value
+LIB8STATIC uint8_t brighten8_video(uint8_t x)
+{
+    uint8_t ix = 255 - x;
+    return 255 - scale8_video(ix, ix);
+}
+
+/// inverse of the dimming function, brighten a value
+LIB8STATIC uint8_t brighten8_lin(uint8_t x)
+{
+    uint8_t ix = 255 - x;
+    if (ix & 0x80)
+    {
+        ix = scale8(ix, ix);
+    }
+    else
+    {
+        ix += 1;
+        ix /= 2;
+    }
+    return 255 - ix;
+}
+
+///@}
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/lib8tion/lib8tion/trig8.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,154 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013 FastLED
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef __INC_LIB8TION_TRIG_H
+#define __INC_LIB8TION_TRIG_H
+
+///@ingroup lib8tion
+
+///@defgroup Trig Fast trig functions
+/// Fast 8 and 16-bit approximations of sin(x) and cos(x).
+///        Don't use these approximations for calculating the
+///        trajectory of a rocket to Mars, but they're great
+///        for art projects and LED displays.
+///
+///        On Arduino/AVR, the 16-bit approximation is more than
+///        10X faster than floating point sin(x) and cos(x), while
+/// the 8-bit approximation is more than 20X faster.
+///@{
+
+/// Fast 16-bit approximation of sin(x). This approximation never varies more than
+/// 0.69% from the floating point value you'd get by doing
+///
+///     float s = sin(x) * 32767.0;
+///
+/// @param theta input angle from 0-65535
+/// @returns sin of theta, value between -32767 to 32767.
+LIB8STATIC int16_t sin16(uint16_t theta)
+{
+    static const uint16_t base[] = { 0, 6393, 12539, 18204, 23170, 27245, 30273, 32137 };
+    static const uint8_t slope[] = { 49, 48, 44, 38, 31, 23, 14, 4 };
+
+    uint16_t offset = (theta & 0x3FFF) >> 3; // 0..2047
+    if (theta & 0x4000)
+        offset = 2047 - offset;
+
+    uint8_t section = offset / 256; // 0..7
+    uint16_t b = base[section];
+    uint8_t m = slope[section];
+
+    uint8_t secoffset8 = (uint8_t)(offset) / 2;
+
+    uint16_t mx = m * secoffset8;
+    int16_t y = mx + b;
+
+    if (theta & 0x8000)
+        y = -y;
+
+    return y;
+}
+
+/// Fast 16-bit approximation of cos(x). This approximation never varies more than
+/// 0.69% from the floating point value you'd get by doing
+///
+///     float s = cos(x) * 32767.0;
+///
+/// @param theta input angle from 0-65535
+/// @returns sin of theta, value between -32767 to 32767.
+LIB8STATIC int16_t cos16(uint16_t theta)
+{
+    return sin16(theta + 16384);
+}
+
+///////////////////////////////////////////////////////////////////////
+
+// sin8 & cos8
+//        Fast 8-bit approximations of sin(x) & cos(x).
+//        Input angle is an unsigned int from 0-255.
+//        Output is an unsigned int from 0 to 255.
+//
+//        This approximation can vary to to 2%
+//        from the floating point value you'd get by doing
+//          float s = (sin( x ) * 128.0) + 128;
+//
+//        Don't use this approximation for calculating the
+//        "real" trigonometric calculations, but it's great
+//        for art projects and LED displays.
+//
+//        On Arduino/AVR, this approximation is more than
+//        20X faster than floating point sin(x) and cos(x)
+//
+
+/// Fast 8-bit approximation of sin(x). This approximation never varies more than
+/// 2% from the floating point value you'd get by doing
+///
+///     float s = (sin(x) * 128.0) + 128;
+///
+/// @param theta input angle from 0-255
+/// @returns sin of theta, value between 0 and 255
+LIB8STATIC uint8_t sin8(uint8_t theta)
+{
+    static const uint8_t b_m16_interleave[] = { 0, 49, 49, 41, 90, 27, 117, 10 };
+
+    uint8_t offset = theta;
+    if (theta & 0x40)
+        offset = (uint8_t)255 - offset;
+    offset &= 0x3F; // 0..63
+
+    uint8_t secoffset = offset & 0x0F; // 0..15
+    if (theta & 0x40)
+        ++secoffset;
+
+    uint8_t section = offset >> 4; // 0..3
+    uint8_t s2 = section * 2;
+    const uint8_t *p = b_m16_interleave;
+    p += s2;
+    uint8_t b = *p;
+    ++p;
+    uint8_t m16 = *p;
+
+    uint8_t mx = (m16 * secoffset) >> 4;
+
+    int8_t y = mx + b;
+    if (theta & 0x80)
+        y = -y;
+
+    y += 128;
+
+    return y;
+}
+
+/// Fast 8-bit approximation of cos(x). This approximation never varies more than
+/// 2% from the floating point value you'd get by doing
+///
+///     float s = (cos(x) * 128.0) + 128;
+///
+/// @param theta input angle from 0-255
+/// @returns sin of theta, value between 0 and 255
+LIB8STATIC uint8_t cos8(uint8_t theta)
+{
+    return sin8(theta + 64);
+}
+
+///@}
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/lm75/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,24 @@
+---
+components:
+  - name: lm75
+    description: |
+      Driver for LM75, a digital temperature sensor and thermal watchdog
+    group: temperature
+    groups: []
+    code_owners:
+      - name: trombik
+    depends:
+      - name: i2cdev
+      - name: log
+      - name: esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - name: trombik
+        year: 2019
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/lm75/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS lm75.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/lm75/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,13 @@
+Copyright (c) 2019 Tomoyuki Sakurai <y@trombik.org>
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/lm75/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/lm75/lm75.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,307 @@
+/*
+ * Copyright (c) 2019 Tomoyuki Sakurai <y@trombik.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/**
+ * @file lm75.c
+ *
+ * ESP-IDF driver for LM75, a digital temperature sensor and thermal watchdog.
+ *
+ * The driver depends on i2cdev library in `esp-idf-lib`.
+ *
+ * The driver was written using LM75B.
+ */
+#include <esp_log.h>
+#include <esp_err.h>
+#include <esp_idf_lib_helpers.h>
+#include "lm75.h"
+
+#define I2C_FREQ_HZ 1000000 // 1 Mhz
+
+#define LM75_MASK_SHUTDOWN      (1 << 0)
+#define LM75_MASK_OS_COMP_INT   (1 << 1)
+#define LM75_MASK_OS_POL        (1 << 2)
+#define LM75_MASK_OS_F_QUE      ((1 << 4) | (1 << 3))
+
+#define LM75_REG_CONF  0x01
+#define LM75_REG_TEMP  0x00
+#define LM75_REG_TOS   0x03
+#define LM75_REG_THYST 0x02
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+/* use CHECK_LOGE after I2C_DEV_TAKE_MUTEX(). */
+#define CHECK_LOGE(dev, x, msg, ...) do { \
+        esp_err_t __; \
+        if ((__ = x) != ESP_OK) { \
+            I2C_DEV_GIVE_MUTEX(dev); \
+            ESP_LOGE(TAG, msg, ## __VA_ARGS__); \
+            return __; \
+        } \
+    } while (0)
+
+const static char *TAG = "lm75";
+
+/* read_register* and write_register* must be protected with I2C_DEV_TAKE_MUTEX */
+static esp_err_t read_register16(i2c_dev_t *dev, uint8_t reg, uint16_t *value)
+{
+    uint8_t data[] = { 0, 0 };
+    CHECK(i2c_dev_read_reg(dev, reg, data, 2));
+    *value = (data[0] << 8) | data[1];
+    ESP_LOGV(TAG, "read_register16() reg: 0x%x value: 0x%x", reg, *value);
+    return ESP_OK;
+}
+
+static esp_err_t read_register8(i2c_dev_t *dev, uint8_t reg, uint8_t *value)
+{
+    CHECK(i2c_dev_read_reg(dev, reg, value, 1));
+    ESP_LOGV(TAG, "read_register8() reg: 0x%x value: 0x%x", reg, *value);
+    return ESP_OK;
+}
+
+static esp_err_t write_register16(i2c_dev_t *dev, uint8_t reg, uint16_t value)
+{
+    ESP_LOGV(TAG, "write_register16(): reg: 0x%x, value: 0x%x", reg, value);
+    return i2c_dev_write(dev, &reg, 2, &value, 2);
+}
+
+static esp_err_t write_register8(i2c_dev_t *dev, uint8_t reg, uint8_t value)
+{
+    ESP_LOGV(TAG, "write_register8(): reg: 0x%x, value: 0x%x", reg, value);
+    return i2c_dev_write_reg(dev, reg, &value, 1);
+}
+
+esp_err_t lm75_read_temperature(i2c_dev_t *dev, float *value)
+{
+    CHECK_ARG(dev);
+    uint16_t raw_data;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    CHECK_LOGE(dev, read_register16(dev, LM75_REG_TEMP, &raw_data),
+            "lm75_read_temperature(): read_register16() failed: register: 0x%x", LM75_REG_TEMP);
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    *value = (raw_data >> 5) * 0.125;
+    return ESP_OK;
+}
+
+esp_err_t lm75_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+    if (addr < LM75_I2C_ADDRESS_DEFAULT || addr > LM75_I2C_ADDRESS_MAX) {
+        ESP_LOGE(TAG, "lm75_init_desc(): Invalid I2C address `0x%x`. address must not be less than 0x%x, not be more than 0x%x",
+                addr, LM75_I2C_ADDRESS_DEFAULT, LM75_I2C_ADDRESS_MAX);
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    dev->port = port;
+    dev->addr = addr;
+    dev->cfg.sda_io_num = sda_gpio;
+    dev->cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+    return i2c_dev_create_mutex(dev);
+}
+
+esp_err_t lm75_set_os_threshold(i2c_dev_t *dev, const float value)
+{
+    CHECK_ARG(dev);
+    uint16_t reg_value;
+
+    /*  two's complement format with the resolution of 0.5 C degree.
+     *  7 LSB of the LSByte are equal to zero and should be ignored.
+     */
+    if (value < 0) {
+        reg_value = ((uint16_t)(abs((int16_t)value) * 2) ^ 0xff) + 1;
+    } else {
+        reg_value = value * 2;
+    }
+    reg_value = reg_value << 7;
+    /* when the value is 25.0f:
+     * reg_value: 0x1900 9 bit reg_value: 0x32 value: 25.000000 */
+    ESP_LOGV(TAG, "lm75_set_os_threshold(): reg_value: 0x%x 9 bit reg_value: 0x%x value: %f",
+            reg_value, reg_value >> 7, value);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    CHECK_LOGE(dev, write_register16(dev, LM75_REG_TOS, reg_value),
+            "lm75_set_os_threshold(): write_register16() failed: register 0x%x",
+            LM75_REG_TOS);
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+
+esp_err_t lm75_get_os_threshold(i2c_dev_t *dev, float *value)
+{
+    CHECK_ARG(dev);
+    uint16_t reg_value;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    CHECK_LOGE(dev, read_register16(dev, LM75_REG_TOS, &reg_value),
+            "lm75_get_os_threshold(): read_register16() failed: register: 0x%x", LM75_REG_TOS);
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    ESP_LOGV(TAG, "lm75_get_os_threshold(): reg_value: 0x%x 9 bit reg_value: 0x%x", reg_value, reg_value >> 7);
+    reg_value = reg_value >> 7;
+    if (reg_value & (1 << 10)) {
+        *value = ((reg_value | (1 << 10)) ^ 0xff) + 1;
+        *value *= -1;
+        *value /= 2;
+    } else {
+        *value = reg_value / 2;
+    }
+
+    return ESP_OK;
+}
+
+esp_err_t lm75_free_desc(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(dev);
+}
+
+static
+esp_err_t lm75_set_bits_register8(i2c_dev_t *dev, uint8_t reg, uint8_t mask)
+{
+    CHECK_ARG(dev);
+    uint8_t value;
+    ESP_LOGV(TAG, "lm75_set_bits_register8(): reg: 0x%x, mask: 0x%x", reg, mask);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    CHECK_LOGE(dev, read_register8(dev, reg, &value),
+            "lm75_set_bits_register8(): read_register8() failed reg: 0x%x", reg);
+    ESP_LOGV(TAG, "lm75_set_bits_register8(): value in register: 0x%x", value);
+    if ((value & mask) != mask) {
+        value |= mask;
+        ESP_LOGV(TAG, "lm75_set_bits_register8(): updating register with value: 0x%x", value);
+        CHECK_LOGE(dev, write_register8(dev, reg, value),
+                "lm75_set_bits_register8(): write_register8() failed reg: 0x%x", reg);
+    } else {
+        ESP_LOGV(TAG, "lm75_set_bits_register8(): register unchanged");
+    }
+    I2C_DEV_GIVE_MUTEX(dev);
+    return ESP_OK;
+
+}
+
+static
+esp_err_t lm75_clear_bits_register8(i2c_dev_t *dev, uint8_t reg, uint8_t mask)
+{
+    CHECK_ARG(dev);
+    uint8_t value;
+    ESP_LOGV(TAG, "lm75_clear_bits_register8(): reg: 0x%x, mask: 0x%x", reg, mask);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    CHECK_LOGE(dev, read_register8(dev, reg, &value),
+            "read_register8() failed: register: 0x%x", reg);
+    if ((value & mask) == mask) {
+        value ^= mask;
+        ESP_LOGV(TAG, "lm75_clear_bits_register8(): updating register with value: 0x%x", value);
+        CHECK_LOGE(dev, write_register8(dev, reg, value),
+                "write_register8() failed: register 0x%x", reg);
+    } else {
+        ESP_LOGV(TAG, "lm75_clear_bits_register8(): register unchanged");
+    }
+    I2C_DEV_GIVE_MUTEX(dev);
+    return ESP_OK;
+
+}
+
+esp_err_t lm75_shutdown(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+    ESP_LOGV(TAG, "lm75_shutdown(): trying to set shutdown bit");
+
+    return lm75_set_bits_register8(dev, LM75_REG_CONF, LM75_MASK_SHUTDOWN);
+}
+
+esp_err_t lm75_wakeup(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+    ESP_LOGV(TAG, "lm75_wakeup(): trying to clear shutdown bit");
+
+    return lm75_clear_bits_register8(dev, LM75_REG_CONF, LM75_MASK_SHUTDOWN);
+}
+
+esp_err_t lm75_set_os_polarity(i2c_dev_t *dev, const lm75_os_polarity_t v)
+{
+    ESP_LOGV(TAG, "lm75_set_os_polarity(): v: %d", v);
+    if (v > 1) {
+        ESP_LOGE(TAG, "lm75_set_os_polarity(): second argument must be %d or %d",
+                LM75_OSP_LOW, LM75_OSP_HIGH);
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    if (v == LM75_OSP_HIGH) {
+        return lm75_set_bits_register8(dev, LM75_REG_CONF, LM75_MASK_OS_POL);
+    } else {
+        return lm75_clear_bits_register8(dev, LM75_REG_CONF, LM75_MASK_OS_POL);
+    }
+}
+esp_err_t lm75_get_os_polarity(i2c_dev_t *dev, uint8_t *v)
+{
+    CHECK_ARG(dev);
+    uint8_t reg_value;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    CHECK_LOGE(dev, read_register8(dev, LM75_REG_CONF, &reg_value),
+            "lm75_get_os_polarity(): read_register8() failed: reg: 0x%x", LM75_REG_CONF);
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    *v = (reg_value & LM75_MASK_OS_POL) == 0 ? 0 : 1;
+    return ESP_OK;
+}
+
+esp_err_t lm75_set_os_mode(i2c_dev_t *dev, lm75_os_mode_t v)
+{
+    ESP_LOGV(TAG, "lm75_set_os_mode(): v: %d", v);
+    if (v > 1) {
+        ESP_LOGE(TAG, "lm75_set_os_mode(): second argument must be %d or %d",
+                LM75_OS_MODE_COMP, LM75_OS_MODE_INT);
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    if (v == LM75_OS_MODE_INT) {
+        return lm75_set_bits_register8(dev, LM75_REG_CONF, LM75_MASK_OS_COMP_INT);
+    } else {
+        return lm75_clear_bits_register8(dev, LM75_REG_CONF, LM75_MASK_OS_COMP_INT);
+    }
+}
+/*
+ * Configuration register (0x00)
+ * | [7:5]    |   4    |  3   |   2    |     1       |    0     |
+ * | Reserved | OS_F_QUE[1:0] | PS_POL | OS_COMP_INT | SHUTDOWN |
+ */
+esp_err_t lm75_init(i2c_dev_t *dev, const lm75_config_t config)
+{
+    CHECK_ARG(dev);
+    uint8_t value = 0;
+
+    value = (config.mode           << 0) |
+            (config.os_mode        << 1) |
+            (config.os_mode        << 2) |
+            (config.os_fault_queue << 3);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    CHECK_LOGE(dev, write_register8(dev, LM75_REG_CONF, value),
+            "lm75_init(): write_register8() failed");
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/lm75/lm75.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 2019 Tomoyuki Sakurai <y@trombik.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/**
+ * @file lm75.h
+ * @defgroup lm75 lm75
+ * @{
+ *
+ * ESP-IDF driver for LM75, a digital temperature sensor and thermal watchdog.
+ *
+ * The driver depends on i2cdev library in `esp-idf-lib`.
+ *
+ * The driver was written using LM75B.
+ *
+ * Short usage instruction:
+ *
+ * 1. Include lm75.h
+ * 2. Initialize I2C descriptor by i2cdev_init()
+ * 3. Initialize LM75 descriptor by lm75_init_desc()
+ * 4. Initialize LM75 by lm75_init()
+ * 5. Read temperature by lm75_read_temperature()
+ *
+ */
+#ifndef __LM75_H__
+#define __LM75_H__
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define LM75_I2C_ADDRESS_DEFAULT (0x48) //!< Default I2C address (A0 == A1 == A2 == 0)
+#define LM75_I2C_ADDRESS_MAX (0x4f)     //!< I2C address (A0 == A1 == A2 == 1)
+
+/**
+ * Operation mode of LM75
+ */
+typedef enum {
+    LM75_MODE_NORMAL = 0,  //!< Normal operation mode
+    LM75_MODE_SHUTDOWN = 1 //!< Shutdown mode
+} lm75_mode_t;
+
+/**
+ * Overtemperature Shutdown Polarity
+ */
+typedef enum {
+    LM75_OSP_LOW  = 0, //!< Overtemperature Shutdown Polarity is active low
+    LM75_OSP_HIGH = 1  //!< OSP is active high
+} lm75_os_polarity_t;
+
+/**
+ * Overtemperature Shutdown output mode
+ */
+typedef enum {
+    LM75_OS_MODE_COMP = 0, //!< OS output mode is comparator
+    LM75_OS_MODE_INT  = 1  //!< OS output mode is interrupt
+} lm75_os_mode_t;
+
+/**
+ *  OS fault queue, the number of faults that must occur consecutively to
+ *  activate the OS output
+ */
+typedef enum {
+    LM75_FAULT_QUEUE1 = 0b00, //!< 1
+    LM75_FAULT_QUEUE2 = 0b01, //!< 2
+    LM75_FAULT_QUEUE4 = 0b10, //!< 4
+    LM75_FAULT_QUEUE6 = 0b11  //!< 6
+} lm75_fault_queue_t;
+
+/**
+ * Device configuration
+ */
+typedef struct {
+    lm75_mode_t mode;                     //!< Operation mode of the device
+    lm75_os_polarity_t os_pol;            //!< OS Polarity
+    lm75_os_mode_t os_mode;               //!< OS mode
+    lm75_fault_queue_t os_fault_queue;    //!< OS fault queue
+} lm75_config_t;
+
+/**
+ * @brief Initialize LM75 device descriptor
+ *
+ * i2cdev_init() must be called before this function.
+ *
+ * @param[out] dev pointer to LM75 device descriptor
+ * @param[in] addr I2C address of LM75
+ * @param[in] port I2C port
+ * @param[in] sda_gpio GPIO number of SDA
+ * @param[in] scl_gpio GPIO number of SCL
+ * @return `ESP_OK` on success
+ */
+esp_err_t lm75_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Initialize LM75
+ *
+ * lm75_init_desc() must be called before this function.
+ *
+ * @param[in] dev pointer to LM75 device descriptor
+ * @param[in] config configuration
+ */
+esp_err_t lm75_init(i2c_dev_t *dev, const lm75_config_t config);
+
+/**
+ * @brief free LM75 device descriptor
+ * @param dev Pointer to device descriptor
+ */
+esp_err_t lm75_free_desc(i2c_dev_t *dev);
+
+/**
+ * @brief Get the value of OS Polarity in the configuration register
+ * @param[in] dev pointer to LM75 device descriptor
+ * @param[out] v value of OS Polarity
+ * @return `ESP_OK` on success
+ */
+esp_err_t lm75_get_os_polarity(i2c_dev_t *dev, uint8_t *v);
+
+/**
+ * @brief Get the value of OS threshold in the configuration register
+ * @param[in] dev pointer to LM75 device descriptor
+ * @param[out] value value of OS threshold
+ * @return `ESP_OK` on success
+ */
+esp_err_t lm75_get_os_threshold(i2c_dev_t *dev, float *value);
+
+/**
+ * @brief Read the temperature
+ * @param[in] dev pointer to LM75 device descriptor
+ * @param[out] value temperature
+ * @return `ESP_OK` on success
+ */
+esp_err_t lm75_read_temperature(i2c_dev_t *dev, float *value);
+
+/**
+ * @brief Set OS mode
+ * @param[in] dev pointer to LM75 device descriptor
+ * @param[in] v OS mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t lm75_set_os_mode(i2c_dev_t *dev, const lm75_os_mode_t v);
+
+/**
+ * @brief Set the value of OS Polarity in the configuration register
+ *
+ * @param[in] dev pointer to LM75 device descriptor
+ * @param[in] v value of OS Polarity
+ * @return `ESP_OK` on success
+ */
+esp_err_t lm75_set_os_polarity(i2c_dev_t *dev, const lm75_os_polarity_t v);
+
+/**
+ * @brief Set the value of OS threshold in the configuration register
+ * @param[in] dev pointer to LM75 device descriptor
+ * @param[in] value value of OS threshold
+ * @return `ESP_OK` on success
+ */
+esp_err_t lm75_set_os_threshold(i2c_dev_t *dev, const float value);
+
+/**
+ * @brief Shutdown LM75
+ * @param[in] dev pointer to LM75 device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t lm75_shutdown(i2c_dev_t *dev);
+
+/**
+ * @brief Wake LM75 up
+ * @param[in] dev pointer to LM75 device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t lm75_wakeup(i2c_dev_t *dev);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ls7366r/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,20 @@
+---
+components:
+  - name: ls7366r
+    description: Driver for LS7366R Quadrature Encoder Counter
+    group: input
+    groups: []
+    code_owners: Jkallus
+    depends:
+      - name: driver
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: MIT
+    copyrights:
+      - author:
+          name: Jkallus
+        year: 2021
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ls7366r/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS ls7366r.c
+    INCLUDE_DIRS .
+    REQUIRES driver
+)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ls7366r/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 Joshua Kallus
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ls7366r/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = driver
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ls7366r/ls7366r.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,388 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2021 Joshua Kallus <joshk.kallus3@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file ls7366r.c
+ *
+ * ESP-IDF driver for LS7366R Quadrature Encoder Counter
+ *
+ * Datasheet: https://lsicsi.com/datasheets/LS7366R.pdf
+ *
+ * Copyright (c) 2021 Joshua Kallus <joshk.kallus3@gmail.com>
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+#include "ls7366r.h"
+#include <string.h>
+
+// Registers
+#define REG_MDR0 0x08
+#define REG_MDR1 0x10
+#define REG_DTR 0x18
+#define REG_CNTR 0x20
+#define REG_OTR 0x28
+#define REG_STR 0x30
+
+// Commands
+#define CMD_CLR 0x00
+#define CMD_WR 0x80
+#define CMD_LOAD 0xC0
+#define CMD_RD 0x40
+
+// Mode Bitmasks
+
+#define COUNTER_NON_QUAD              0x00    
+#define COUNTER_1X_QUAD               0x01    
+#define COUNTER_2X_QUAD               0x02    
+#define COUNTER_4X_QUAD               0x03    
+
+#define COUNTER_FREE_RUN              0x00    
+#define COUNTER_SINGLE_COUNT          0x04    
+#define COUNTER_RANGE_LIMIT           0x08    
+#define COUNTER_N_MODULE              0x0C   
+
+#define COUNTER_INDEX_DISABLED        0x00    
+#define COUNTER_INDEX_LOAD_CNTR       0x10     
+#define COUNTER_INDEX_RESET_CNTR      0x20     
+#define COUNTER_INDEX_LOAD_OTR        0x30     
+
+#define COUNTER_INDEX_ASYNC           0x00    
+#define COUNTER_INDEX_SYNC            0x40    
+
+#define COUNTER_FILTER_CLOCK_DIV1     0x00    
+#define COUNTER_FILTER_CLOCK_DIV2     0x80    
+
+#define COUNTER_MODE_32               0x00    
+#define COUNTER_MODE_24               0x01    
+#define COUNTER_MODE_16               0x02    
+#define COUNTER_MODE_8                0x03    
+
+#define COUNTER_ENABLE                0x00    
+#define COUNTER_DISABLE               0x04    
+
+#define COUNTER_FLAG_IDX              0x10    
+#define COUNTER_FLAG_CMP              0x20    
+#define COUNTER_FLAG_BW               0x40    
+#define COUNTER_FLAG_CY               0x80    
+
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+
+// Interface functions //////////////////////////////////////////////////////////////
+static esp_err_t write_command(spi_device_handle_t spi, uint8_t command)
+{
+	esp_err_t ret;
+	spi_transaction_t trans = {
+		.cmd = command,
+		.length = 0,
+		.tx_buffer = NULL,
+		.rx_buffer = NULL
+	};
+	
+	spi_device_acquire_bus(spi, portMAX_DELAY);
+	ret = spi_device_transmit(spi, &trans);
+	spi_device_release_bus(spi);
+	return ret;
+}
+
+static esp_err_t write_data(spi_device_handle_t spi, uint8_t command, uint8_t* data, uint8_t count)
+{
+	esp_err_t ret;
+	spi_transaction_t trans = { 
+		.cmd = command,
+		.length = count * 8,
+		.flags = SPI_TRANS_USE_TXDATA,
+		.rx_buffer = NULL,
+	};
+	
+	memcpy(trans.tx_data, data, count);
+	
+	spi_device_acquire_bus(spi, portMAX_DELAY);
+	ret = spi_device_transmit(spi, &trans);
+	spi_device_release_bus(spi);
+	return ret;
+}
+
+static esp_err_t read_register(spi_device_handle_t spi, uint8_t command, uint8_t *data)
+{
+	esp_err_t ret;
+	spi_transaction_t trans = {
+		.cmd = command,
+		.tx_buffer = NULL,
+		.rxlength = 1 * 8,
+		.flags = SPI_TRANS_USE_RXDATA,
+		.length = 8
+	};
+	
+	spi_device_acquire_bus(spi, portMAX_DELAY);
+	ret = spi_device_transmit(spi, &trans);
+	spi_device_release_bus(spi);
+	
+	*data = trans.rx_data[0];
+	return ret;
+}
+
+static esp_err_t read_data(spi_device_handle_t spi, uint8_t command, uint8_t* data, uint8_t count)
+{
+	esp_err_t ret;
+	spi_transaction_t trans = {
+		.cmd = command,
+		.flags = SPI_TRANS_USE_RXDATA,
+		.rxlength = count * 8,
+		.tx_buffer = NULL,
+		.length = 8 * count,
+	};
+	
+	spi_device_acquire_bus(spi, portMAX_DELAY);
+	ret = spi_device_transmit(spi, &trans);
+	spi_device_release_bus(spi);
+	
+	memcpy(data, trans.rx_data, count);
+	return ret;
+}
+//////////////////////////////////////////////////////////////////////////////////////
+
+///////////////// Control Functions ///////////////////////////////////////////////////
+static esp_err_t write_mdr0(spi_device_handle_t spi, uint8_t settings)
+{
+	return write_data(spi, CMD_WR | REG_MDR0, &settings, 1);
+}
+
+static esp_err_t write_mdr1(spi_device_handle_t spi, uint8_t settings)
+{
+	return write_data(spi, CMD_WR | REG_MDR1, &settings, 1);
+}
+
+static esp_err_t write_dtr(spi_device_handle_t spi, uint8_t* data, uint8_t length)
+{
+	return write_data(spi, CMD_WR | REG_DTR, data, length);
+}
+
+static esp_err_t read_mdr1(spi_device_handle_t spi, uint8_t *data)
+{
+	return read_register(spi, CMD_RD | REG_MDR1, data);
+}
+
+static esp_err_t read_cntr(spi_device_handle_t spi, int32_t* data)
+{
+	esp_err_t ret;
+	uint8_t data_buf[4];
+	uint32_t result;
+	ret = read_data(spi, CMD_RD | REG_CNTR, data_buf, 4);
+	result = data_buf[0];
+	
+	for (uint8_t cnt = 1; cnt < 4; cnt++)
+	{
+		result <<= 8;
+		result |= data_buf[cnt];
+	}
+	
+	*data = result;
+	return ret;
+}
+
+static esp_err_t clear_cntr(spi_device_handle_t spi)
+{
+	return write_command(spi, CMD_CLR | REG_CNTR);
+}
+
+static esp_err_t clear_str(spi_device_handle_t spi)
+{
+	return write_command(spi, CMD_CLR | REG_STR);
+}
+
+static esp_err_t counter_enable(spi_device_handle_t spi)
+{
+	esp_err_t ret;
+	uint8_t mdr1_current;
+	ret = read_mdr1(spi, &mdr1_current);
+	if (ret != ESP_OK)
+		return ret;
+	ret = write_mdr1(spi, mdr1_current & 0xFB); // bit 3 of MDR1 is 0 enable, 1 disable 0xFB = 0b11111011 masks all but the bit we need to set to 0					
+	return ret;	
+}
+
+static esp_err_t counter_disable(spi_device_handle_t spi)
+{
+	esp_err_t ret;
+	uint8_t mdr1_current;
+	ret = read_mdr1(spi, &mdr1_current);
+	if (ret != ESP_OK)
+		return ret;
+	ret = write_mdr1(spi, mdr1_current | 0x04); // bit 3 of MDR1 is 0 enable, 1 disable 0x04 = 0b00000100 masks all but the bit we need to set to 1	
+	return ret;
+	
+}
+///////////////////////////////////////////////////////////////////////////////////////////
+
+
+esp_err_t ls7366r_init_desc(ls7366r_t *dev, spi_host_device_t host, uint32_t clock_speed_hz, gpio_num_t cs_pin)
+{
+	CHECK_ARG(dev);
+	memset(&dev->spi_cfg, 0, sizeof(dev->spi_cfg));
+	dev->spi_cfg.spics_io_num = cs_pin;
+	dev->spi_cfg.clock_speed_hz = clock_speed_hz;
+	dev->spi_cfg.mode = 0;
+	dev->spi_cfg.queue_size = 1;
+	dev->spi_cfg.command_bits = 8;
+	return spi_bus_add_device(host, &dev->spi_cfg, &dev->spi_dev);
+}
+
+esp_err_t ls7366r_free_desc(ls7366r_t *dev)
+{
+	CHECK_ARG(dev);
+	return spi_bus_remove_device(dev->spi_dev);
+}
+
+esp_err_t ls7366r_set_config(ls7366r_t *dev, const ls7366r_config_t *config)
+{
+	esp_err_t ret;
+	
+	// Set MDR0
+	uint8_t mdr0_set = 0x00;
+	switch (config->filter_clock_divider)
+	{
+		case LS7366R_FILTER_CLK_1:
+			mdr0_set |= COUNTER_FILTER_CLOCK_DIV1;
+			break;
+		case LS7366R_FILTER_CLK_2:
+			mdr0_set |= COUNTER_FILTER_CLOCK_DIV2;
+			break;
+	}
+	
+	switch (config->index_sync)
+	{	
+		case LS7366R_INDEX_SYNCHRONOUS:
+			mdr0_set |= COUNTER_INDEX_SYNC;
+			break;
+		case LS7366R_INDEX_ASYNCHRONOUS:
+			mdr0_set |= COUNTER_INDEX_ASYNC;
+			break;
+	}
+	
+	switch (config->index_mode)
+	{
+		case LS7366R_INDEX_DISABLED:
+			mdr0_set |= COUNTER_INDEX_DISABLED;
+			break;
+		case LS7366R_INDEX_LOAD_CNTR:
+			mdr0_set |= COUNTER_INDEX_LOAD_CNTR;
+			break;
+		case LS7366R_INDEX_RESET_CNTR:
+			mdr0_set |= COUNTER_INDEX_RESET_CNTR;
+			break;
+		case LS7366R_INDEX_LOAD_OTR:
+			mdr0_set |= COUNTER_INDEX_LOAD_OTR;
+			break;
+	}
+	
+	switch (config->count_type)
+	{
+		case LS7366R_NON_QUAD:
+			mdr0_set |= COUNTER_NON_QUAD;
+			break;
+		case LS7366R_1x_QUAD:
+			mdr0_set |= COUNTER_1X_QUAD;
+			break;
+		case LS7366R_2x_QUAD:
+			mdr0_set |= COUNTER_2X_QUAD;
+			break;
+		case LS7366R_4X_QUAD:
+			mdr0_set |= COUNTER_4X_QUAD;
+			break;
+	}
+	
+	uint8_t mdr1_set = 0x00;
+	
+	uint8_t flag_borrow = config->flag_mode.borrow == LS7366R_FLAG_BORROW_ENABLE ? COUNTER_FLAG_BW : 0x00;
+	uint8_t flag_index = config->flag_mode.index == LS7366R_FLAG_INDEX_ENABLE ? COUNTER_FLAG_IDX : 0x00;
+	uint8_t flag_compare = config->flag_mode.compare == LS7366R_FLAG_COMPARE_ENABLE ? COUNTER_FLAG_CMP : 0x00;
+	uint8_t flag_carry = config->flag_mode.carry == LS7366R_FLAG_CARRY_ENABLE ? COUNTER_FLAG_CY : 0x00;
+	
+	mdr1_set = mdr1_set | flag_borrow | flag_carry | flag_compare | flag_index;
+	
+	switch (config->counter_enable)
+	{
+		case LS7366R_COUNTER_ENABLE:
+			mdr1_set |= COUNTER_ENABLE;
+			break;
+		case LS7366R_COUNTER_DISABLE:
+			mdr1_set |= COUNTER_DISABLE;
+			break;
+	}
+	
+	switch (config->counter_bits)
+	{
+		case LS7366R_8_BIT:
+			mdr1_set |= COUNTER_MODE_8;
+			break;
+		case LS7366R_16_BIT:
+			mdr1_set |= COUNTER_MODE_16;
+			break;
+		case LS7366R_24_BIT:
+			mdr1_set |= COUNTER_MODE_24;
+			break;
+		case LS7366R_32_BIT:
+			mdr1_set |= COUNTER_MODE_32;
+			break;
+	}
+	
+	ret = clear_cntr(dev->spi_dev);
+	if (ret != ESP_OK)
+		return ret;
+	ret = clear_str(dev->spi_dev);
+	if (ret != ESP_OK)
+		return ret;
+	ret = write_mdr0(dev->spi_dev, mdr0_set);
+	if (ret != ESP_OK)
+		return ret;
+	ret = write_mdr1(dev->spi_dev, mdr1_set);
+	return ret;
+}
+
+esp_err_t ls7366r_get_count(ls7366r_t *dev, int32_t *count)
+{
+	return read_cntr(dev->spi_dev, count);	
+}
+
+esp_err_t ls7366r_set_compare_val(ls7366r_t *dev, int32_t cmp)
+{
+	uint8_t cmp_data[4];
+	uint8_t* ptr = (uint8_t*)(&cmp);
+	cmp_data[3] = ptr[0];
+	cmp_data[2] = ptr[1];
+	cmp_data[1] = ptr[2];
+	cmp_data[0] = ptr[3];
+	
+	return write_dtr(dev->spi_dev, (uint8_t*)&cmp_data, 4);
+}
+
+esp_err_t ls7366r_clear_counter(ls7366r_t *dev)
+{
+	return clear_cntr(dev->spi_dev);
+}	
+
+esp_err_t ls7366r_counter_enable(ls7366r_t *dev, bool enable)
+{
+	return enable ? counter_enable(dev->spi_dev) : counter_disable(dev->spi_dev);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ls7366r/ls7366r.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,256 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2021 Joshua Kallus <joshk.kallus3@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file ls7366r.h
+ *
+ * @defgroup ls7366r ls7366r
+ * @{
+ *
+ * ESP-IDF driver for LS7366R Quadrature Encoder Counter
+ *
+ * Datasheet: https://lsicsi.com/datasheets/LS7366R.pdf
+ *
+ * Copyright (c) 2021 Joshua Kallus <joshk.kallus3@gmail.com>
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+#ifndef __LS7366R_H__
+#define __LS7366R_H__
+
+#include <driver/spi_master.h>
+#include <esp_err.h>
+#include <driver/gpio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define LS7366R_MAX_CLOCK_SPEED_HZ (2000000) // 2 MHz clock
+
+/**
+ * Counter type
+ */
+typedef enum
+{
+    LS7366R_NON_QUAD, // Up down counting with channel A being step and channel B being direction
+    LS7366R_1x_QUAD, // 1X quadrature
+    LS7366R_2x_QUAD, // 2X quadrature
+    LS7366R_4X_QUAD, // 4X quadrature (otherwise known as full quadrature)
+} ls7366r_count_type_t;
+
+/**
+ * Count mode
+ */
+typedef enum
+{
+    LS7366R_FREE_RUN, // Free running count mode
+    LS7366R_SINGLE_COUNT, // Single cycle count mode (counter disabled with carry or borrow, re-enabled with reset or load)
+    LS7366R_RANGE_LIMIT, // Up and down count-ranges are limited between DTR and zero
+    LS7366R_N_MODULO, // Input count clock frequency is divided by a factor of (n+1), where n = DTR, in both up and down directions
+} ls7366r_count_mode_t;
+
+/**
+ * Index mode
+ */
+typedef enum
+{
+    LS7366R_INDEX_DISABLED, // Disable index
+    LS7366R_INDEX_LOAD_CNTR, // Configure index as the "load CNTR" input (transfers DTR to CNTR)
+    LS7366R_INDEX_RESET_CNTR, // Configure index as the "reset CNTR" input (clears CNTR to 0)
+    LS7366R_INDEX_LOAD_OTR // Configure index as the "load OTR" input (transfers CNTR to OTR)
+} ls7366r_index_mode_t;
+
+/**
+ * Index sync or async
+ */
+typedef enum
+{
+    LS7366R_INDEX_SYNCHRONOUS, // Asynchronous index
+    LS7366R_INDEX_ASYNCHRONOUS // Synchronous index (overridden in non-quadrature mode)
+} ls7366r_index_sync_t;
+
+/**
+ * Counter bits
+ */
+typedef enum
+{
+    LS7366R_8_BIT, // 1 byte counter mode
+    LS7366R_16_BIT, // 2 byte counter mode
+    LS7366R_24_BIT, // 3 byte counter mode
+    LS7366R_32_BIT // 4 byte counter mode
+} ls7366r_counter_bits_t;
+
+/**
+ * Counter clock divider
+ */
+typedef enum
+{
+    LS7366R_FILTER_CLK_1, // Filter clock division factor 1
+    LS7366R_FILTER_CLK_2 // Filter clock division factor 2
+} ls7366r_filter_clock_divider_t;
+
+/**
+ * Counter enable
+ */
+typedef enum
+{
+    LS7366R_COUNTER_ENABLE, // Enable counting
+    LS7366R_COUNTER_DISABLE // Disable counting
+} ls7366r_counter_enable_t;
+
+/**
+ * Counter flag carry mode
+ */
+typedef enum
+{
+    LS7366R_FLAG_CARRY_ENABLE, // Enable flag on carry bit
+    LS7366R_FLAG_CARRY_DISABLE // Disable flag on carry bit
+} ls7366r_flag_carry_mode_t;
+
+/**
+ * Counter flag borrow mode
+ */
+typedef enum
+{
+    LS7366R_FLAG_BORROW_ENABLE, // Enable flag on borrow bit
+    LS7366R_FLAG_BORROW_DISABLE // Disable flag on borrow bit
+} ls7366r_flag_borrow_mode_t;
+
+/**
+ * Counter flag index mode
+ */
+typedef enum
+{
+    LS7366R_FLAG_INDEX_ENABLE, // Enable flag on index bit
+    LS7366R_FLAG_INDEX_DISABLE // Disable flag on index bit
+} ls7366r_flag_index_mode_t;
+
+/**
+ * Counter flag compare mode
+ */
+typedef enum
+{
+    LS7366R_FLAG_COMPARE_ENABLE, // Enable flag on compare bit
+    LS7366R_FLAG_COMPARE_DISABLE // Disable flag on compare bit
+} ls7366r_flag_compare_enable_t;
+
+typedef struct
+{
+    ls7366r_flag_borrow_mode_t borrow;
+    ls7366r_flag_carry_mode_t carry;
+    ls7366r_flag_compare_enable_t compare;
+    ls7366r_flag_index_mode_t index;
+} ls7366r_flag_mode_t; // LS7366R flag mode struct
+
+/**
+ * Device descriptor
+ */
+typedef struct
+{
+    spi_device_interface_config_t spi_cfg;
+    spi_device_handle_t spi_dev;
+} ls7366r_t; // LS7366R device struct
+
+/**
+ * Device configuration
+ */
+typedef struct
+{
+    ls7366r_flag_mode_t flag_mode; // MDR1
+    ls7366r_counter_enable_t counter_enable; // MDR1
+    ls7366r_counter_bits_t counter_bits; // MDR1
+    ls7366r_filter_clock_divider_t filter_clock_divider; // MDR0
+    ls7366r_index_sync_t index_sync; // MDR0
+    ls7366r_index_mode_t index_mode; // MDR0
+    ls7366r_count_type_t count_type; // MDR0
+} ls7366r_config_t; // LS7366R config struct
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev            Device descriptor
+ * @param host           SPI host
+ * @param clock_speed_hz SPI clock speed, Hz
+ * @param cs_pin         CS GPIO number
+ * @return `ESP_OK` on success
+ */
+esp_err_t ls7366r_init_desc(ls7366r_t *dev, spi_host_device_t host, uint32_t clock_speed_hz, gpio_num_t cs_pin);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t ls7366r_free_desc(ls7366r_t *dev);
+
+/**
+ * @brief Configure device
+ *
+ * @param dev    Device descriptor
+ * @param config Configuration
+ * @return `ESP_OK` on success
+ */
+esp_err_t ls7366r_set_config(ls7366r_t *dev, const ls7366r_config_t *config);
+
+/**
+ * @brief Get current count
+ *
+ * @param dev    Device descriptor
+ * @param count  Count variable
+ * @return `ESP_OK` on success
+ */
+esp_err_t ls7366r_get_count(ls7366r_t *dev, int32_t *count);
+
+/**
+ * @brief set value for compare
+ *
+ * @param dev    Device descriptor
+ * @param cmp    Compare value
+ * @return `ESP_OK` on success
+ */
+esp_err_t ls7366r_set_compare_val(ls7366r_t *dev, int32_t cmp);
+
+/**
+ * @brief clear counter value, set to 0
+ *
+ * @param dev    Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t ls7366r_clear_counter(ls7366r_t *dev);
+
+/**
+ * @brief enable or disable counter
+ *
+ * @param dev    Device descriptor
+ * @param enable Counter enabled or disabled
+ * @return `ESP_OK` on success
+ */
+esp_err_t ls7366r_counter_enable(ls7366r_t *dev, bool enable);
+	
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __LS7366R_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/max31725/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,23 @@
+---
+components:
+  - name: max31725
+    description: Driver for MAX31725/MAX31726 temperature sensors
+    group: temperature
+    groups: []
+    code_owners: UncleRus
+    depends:
+      - i2cdev
+      - log
+      - esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2020
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/max31725/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS max31725.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/max31725/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+Copyright (c) 2019 Ruslan V. Uss (https://github.com/UncleRus)
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/max31725/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/max31725/max31725.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,217 @@
+/*
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file max31725.c
+ *
+ * ESP-IDF driver for MAX31725/MAX31726 temperature sensors
+ *
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+
+#include <esp_log.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <esp_idf_lib_helpers.h>
+#include "max31725.h"
+
+static const char *TAG = "max31725";
+
+static const float t_lsb = 0.00390625;
+
+#define MAX31725_I2C_ADDR_MAX 0x5f
+
+#define I2C_FREQ_HZ 400000 // 400kHz
+
+#define REG_TEMP 0
+#define REG_CONF 1
+#define REG_HYST 2
+#define REG_OS   3
+
+#define BIT_SHUTDOWN 0
+#define BIT_COMP_INT 1
+#define BIT_OS_POL   2
+#define BIT_FAULT_Q0 3
+#define BIT_DATA_FMT 5
+#define BIT_TIMEOUT  6
+#define BIT_ONE_SHOT 7
+
+#define FMT_SHIFT 64.0
+#define CONV_TIME_MS 50
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+#define BV(x) (1 << (x))
+
+typedef union
+{
+    uint16_t udata;
+    int16_t sdata;
+} temp_data_t;
+
+static esp_err_t read_temp(i2c_dev_t *dev, uint8_t reg, float *temp, max31725_data_format_t fmt)
+{
+    CHECK_ARG(dev && temp);
+
+    temp_data_t buf;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, reg, &buf.udata, 2));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    buf.udata = (buf.udata << 8) | (buf.udata >> 8);
+    *temp = buf.sdata * t_lsb + (fmt == MAX31725_FMT_EXTENDED ? FMT_SHIFT : 0);
+
+    return ESP_OK;
+}
+
+static esp_err_t write_temp(i2c_dev_t *dev, uint8_t reg, float temp, max31725_data_format_t fmt)
+{
+    CHECK_ARG(dev);
+
+    temp_data_t buf;
+    buf.sdata = (temp - (fmt == MAX31725_FMT_EXTENDED ? FMT_SHIFT : 0)) / t_lsb;
+    buf.udata = (buf.udata << 8) | (buf.udata >> 8);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, reg, &buf.udata, 2));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t max31725_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+    if (addr < MAX31725_I2C_ADDR_BASE || addr > MAX31725_I2C_ADDR_MAX)
+    {
+        ESP_LOGE(TAG, "Invalid device address: 0x%02x", addr);
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    dev->port = port;
+    dev->addr = addr;
+    dev->cfg.sda_io_num = sda_gpio;
+    dev->cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(dev);
+}
+
+esp_err_t max31725_free_desc(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(dev);
+}
+
+esp_err_t max31725_get_config(i2c_dev_t *dev, max31725_mode_t *mode, max31725_data_format_t *fmt, max31725_fault_queue_t *fq,
+        max31725_os_polarity_t *op, max31725_os_mode_t *om)
+{
+    CHECK_ARG(dev && mode && fmt && fq && op && om);
+
+    uint8_t b;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, REG_CONF, &b, 1));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    *mode = (b >> BIT_SHUTDOWN) & 1;
+    *fmt = (b >> BIT_DATA_FMT) & 1;
+    *fq = (b >> BIT_FAULT_Q0) & 3;
+    *op = (b >> BIT_OS_POL) & 1;
+    *om = (b >> BIT_COMP_INT) & 1;
+
+    return ESP_OK;
+}
+
+esp_err_t max31725_set_config(i2c_dev_t *dev, max31725_mode_t mode, max31725_data_format_t fmt, max31725_fault_queue_t fq,
+        max31725_os_polarity_t op, max31725_os_mode_t om)
+{
+    CHECK_ARG(dev && fq <= MAX31725_FAULTS_6);
+
+    uint8_t b = (fmt != MAX31725_FMT_NORMAL ? BV(BIT_DATA_FMT) : 0) |
+                (fq << BIT_FAULT_Q0) |
+                (op != MAX31725_OS_LOW ? BV(BIT_OS_POL) : 0) |
+                (om != MAX31725_OS_COMPARATOR ? BV(BIT_COMP_INT) : 0) |
+                (mode != MAX31725_MODE_CONTINUOUS ? BV(BIT_SHUTDOWN) : 0);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, REG_CONF, &b, 1));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t max31725_one_shot(i2c_dev_t *dev, float *temp, max31725_data_format_t fmt)
+{
+    CHECK_ARG(dev);
+
+    uint8_t b;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, REG_CONF, &b, 1));
+    b |= BV(BIT_ONE_SHOT);
+    I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, REG_CONF, &b, 1));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    // wait 50 ms
+    vTaskDelay(pdMS_TO_TICKS(CONV_TIME_MS));
+
+    return read_temp(dev, REG_TEMP, temp, fmt);
+}
+
+esp_err_t max31725_get_temperature(i2c_dev_t *dev, float *temp, max31725_data_format_t fmt)
+{
+    return read_temp(dev, REG_TEMP, temp, fmt);
+}
+
+esp_err_t max31725_get_os_temp(i2c_dev_t *dev, float *temp, max31725_data_format_t fmt)
+{
+    return read_temp(dev, REG_OS, temp, fmt);
+}
+
+esp_err_t max31725_set_os_temp(i2c_dev_t *dev, float temp, max31725_data_format_t fmt)
+{
+    return write_temp(dev, REG_OS, temp, fmt);
+}
+
+esp_err_t max31725_get_hysteresis_temp(i2c_dev_t *dev, float *temp, max31725_data_format_t fmt)
+{
+    return read_temp(dev, REG_HYST, temp, fmt);
+}
+
+esp_err_t max31725_set_hysteresis_temp(i2c_dev_t *dev, float temp, max31725_data_format_t fmt)
+{
+    return write_temp(dev, REG_HYST, temp, fmt);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/max31725/max31725.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file max31725.h
+ * @defgroup max31725 max31725
+ * @{
+ *
+ * ESP-IDF driver for MAX31725/MAX31726 temperature sensors
+ *
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __MAX31725_H__
+#define __MAX31725_H__
+
+#include <stdbool.h>
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#define MAX31725_I2C_ADDR_BASE 0x40 //!< See full list in datasheet
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Temperature data range
+ */
+typedef enum {
+    MAX31725_FMT_NORMAL = 0, //!< -128 deg.C .. +127.99609375 deg.C (default)
+    MAX31725_FMT_EXTENDED    //!< -64 deg.C .. +191.99609375 deg.C (max 150 deg.C)
+} max31725_data_format_t;
+
+/**
+ * Fault queue size
+ */
+typedef enum {
+    MAX31725_FAULTS_1 = 0, //!< 1 fault to trigger OS (default)
+    MAX31725_FAULTS_2,     //!< 2 fault to trigger OS
+    MAX31725_FAULTS_4,     //!< 4 fault to trigger OS
+    MAX31725_FAULTS_6      //!< 6 fault to trigger OS
+} max31725_fault_queue_t;
+
+/**
+ * OS polarity
+ */
+typedef enum {
+    MAX31725_OS_LOW = 0, //!< OS active low (default)
+    MAX31725_OS_HIGH     //!< OS active high
+} max31725_os_polarity_t;
+
+/**
+ * Mode of OS operation
+ */
+typedef enum {
+    MAX31725_OS_COMPARATOR = 0, //!< OS comparator mode (default)
+    MAX31725_OS_INTERRUPT       //!< OS interrupt mode
+} max31725_os_mode_t;
+
+/**
+ * Device operating mode
+ */
+typedef enum {
+    MAX31725_MODE_CONTINUOUS = 0, //!< Continuous measurement mode (default)
+    MAX31725_MODE_SHUTDOWN        //!< Shutdown mode
+} max31725_mode_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev Device descriptor
+ * @param port I2C port number
+ * @param addr I2C address
+ * @param sda_gpio SDA GPIO
+ * @param scl_gpio SCL GPIO
+ * @return `ESP_OK` on success
+ */
+esp_err_t max31725_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t max31725_free_desc(i2c_dev_t *dev);
+
+/**
+ * @brief Read current device config
+ *
+ * @param dev Device descriptor
+ * @param[out] mode Operating mode
+ * @param[out] fmt Data format
+ * @param[out] fq Fault queue size
+ * @param[out] op OS polarity
+ * @param[out] om OS mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t max31725_get_config(i2c_dev_t *dev, max31725_mode_t *mode, max31725_data_format_t *fmt, max31725_fault_queue_t *fq,
+        max31725_os_polarity_t *op, max31725_os_mode_t *om);
+
+/**
+ * @brief Configure device
+ *
+ * @param dev Device descriptor
+ * @param mode Operating mode
+ * @param fmt Data format
+ * @param fq Fault queue size
+ * @param op OS polarity
+ * @param om OS mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t max31725_set_config(i2c_dev_t *dev, max31725_mode_t mode, max31725_data_format_t fmt, max31725_fault_queue_t fq,
+        max31725_os_polarity_t op, max31725_os_mode_t om);
+
+/**
+ * @brief Made a single-shot measurement
+ *
+ * Works only when device is in shutdown mode.
+ * Measurement time is ~50 ms.
+ *
+ * @param dev Device descriptor
+ * @param[out] temp Temperature, deg.C
+ * @param fmt Data format
+ * @return `ESP_OK` on success
+ */
+esp_err_t max31725_one_shot(i2c_dev_t *dev, float *temp, max31725_data_format_t fmt);
+
+/**
+ * @brief Read temperature register
+ *
+ * @param dev Device descriptor
+ * @param[out] temp Temperature, deg.C
+ * @param fmt Data format
+ * @return `ESP_OK` on success
+ */
+esp_err_t max31725_get_temperature(i2c_dev_t *dev, float *temp, max31725_data_format_t fmt);
+
+/**
+ * @brief Read OS threshold temperature
+ *
+ * @param dev Device descriptor
+ * @param[out] temp Temperature, deg.C
+ * @param fmt Data format
+ * @return `ESP_OK` on success
+ */
+esp_err_t max31725_get_os_temp(i2c_dev_t *dev, float *temp, max31725_data_format_t fmt);
+
+/**
+ * @brief Set OS threshold temperature
+ *
+ * @param dev Device descriptor
+ * @param temp Temperature, deg.C
+ * @param fmt Data format
+ * @return `ESP_OK` on success
+ */
+esp_err_t max31725_set_os_temp(i2c_dev_t *dev, float temp, max31725_data_format_t fmt);
+
+/**
+ * @brief Read OS hysteresis temperature
+ *
+ * @param dev Device descriptor
+ * @param[out] temp Temperature, deg.C
+ * @param fmt Data format
+ * @return `ESP_OK` on success
+ */
+esp_err_t max31725_get_hysteresis_temp(i2c_dev_t *dev, float *temp, max31725_data_format_t fmt);
+
+/**
+ * @brief Set OS hysteresis temperature
+ *
+ * @param dev Device descriptor
+ * @param temp Temperature, deg.C
+ * @param fmt Data format
+ * @return `ESP_OK` on success
+ */
+esp_err_t max31725_set_hysteresis_temp(i2c_dev_t *dev, float temp, max31725_data_format_t fmt);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __MAX31725_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/max31855/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,21 @@
+---
+components:
+  - name: max31855
+    description: Driver for MAX31855 cold-junction compensated thermocouple-to-digital converter
+    group: temperature
+    groups: []
+    code_owners: UncleRus
+    depends:
+      - driver
+      - log
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2022
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/max31855/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS max31855.c
+    INCLUDE_DIRS .
+    REQUIRES driver log
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/max31855/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+Copyright (c) 2022 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/max31855/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = driver log
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/max31855/max31855.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2022 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file max31855.c
+ *
+ * ESP-IDF driver for MAX31855 cold-junction compensated
+ * thermocouple-to-digital converter
+ *
+ * Copyright (c) 2022 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#include "max31855.h"
+#include <string.h>
+#include <math.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+#define BIT_TC_SIGN 31
+#define BIT_TC      18
+#define BIT_CJ_SIGN 15
+#define BIT_CJ      4
+#define BIT_SCV     2
+#define BIT_SCG     1
+#define BIT_OC      0
+
+#define MASK_CJ 0x0fff
+#define MASK_TC 0x3fff
+
+#define SIGN_CJ 0xf000
+#define SIGN_TC 0xc000
+
+#define LSB_TC 0.25f
+#define LSB_CJ 0.0625f
+
+static inline esp_err_t read_32(max31855_t *dev, uint32_t *val)
+{
+    spi_transaction_t t;
+    memset(&t, 0, sizeof(spi_transaction_t));
+
+    uint8_t rx[4];
+
+    t.tx_buffer = NULL;
+    t.rx_buffer = rx;
+    t.length = 32;
+    CHECK(spi_device_transmit(dev->spi_dev, &t));
+
+    *val = ((uint32_t)rx[0] << 24) | ((uint32_t)rx[1] << 16) | ((uint32_t)rx[2] << 8) | rx[3];
+
+    return ESP_OK;
+}
+
+static inline esp_err_t max31855_get_raw_data(max31855_t *dev, int16_t *tc_t, int16_t *cj_t, bool *scv, bool *scg, bool *oc)
+{
+    CHECK_ARG(dev && tc_t && cj_t && scv && scg && oc);
+
+    uint32_t v = 0;
+    CHECK(read_32(dev, &v));
+
+    // faults
+    *scv = v & BIT(BIT_SCV) ? true : false;
+    *scg = v & BIT(BIT_SCG) ? true : false;
+    *oc = v & BIT(BIT_OC) ? true : false;
+
+    // cold junction temperature
+    *cj_t = (int16_t)((v >> BIT_CJ) & MASK_CJ) | (v & BIT(BIT_CJ_SIGN) ? SIGN_CJ : 0);
+
+    // thermocouple temperature
+    *tc_t = (int16_t)((v >> BIT_TC) & MASK_TC) | (v & BIT(BIT_TC_SIGN) ? SIGN_TC : 0);
+
+    return ESP_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t max31855_init_desc(max31855_t *dev, spi_host_device_t host, uint32_t clock_speed_hz, gpio_num_t cs_pin)
+{
+    CHECK_ARG(dev);
+
+    memset(&dev->spi_cfg, 0, sizeof(dev->spi_cfg));
+    dev->spi_cfg.spics_io_num = cs_pin;
+    dev->spi_cfg.clock_speed_hz = clock_speed_hz;
+    dev->spi_cfg.mode = 0;
+    dev->spi_cfg.queue_size = 1;
+    dev->spi_cfg.cs_ena_pretrans = 1;
+
+    return spi_bus_add_device(host, &dev->spi_cfg, &dev->spi_dev);
+}
+
+esp_err_t max31855_free_desc(max31855_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return spi_bus_remove_device(dev->spi_dev);
+}
+
+esp_err_t max31855_get_temperature(max31855_t *dev, float *tc_t, float *cj_t, bool *scv, bool *scg, bool *oc)
+{
+    CHECK_ARG(tc_t);
+
+    int16_t raw_tc = 0, raw_cj = 0;
+    CHECK(max31855_get_raw_data(dev, &raw_tc, &raw_cj, scv, scg, oc));
+
+    *tc_t = (float)raw_tc * LSB_TC;
+    if (cj_t)
+        *cj_t = (float)raw_cj * LSB_CJ;
+
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/max31855/max31855.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2022 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file max31855.h
+ * @defgroup max31855 max31855
+ * @{
+ *
+ * ESP-IDF driver for MAX31855 cold-junction compensated
+ * thermocouple-to-digital converter
+ *
+ * Copyright (c) 2022 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __MAX31855_H__
+#define __MAX31855_H__
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <driver/spi_master.h>
+#include <driver/gpio.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define MAX31855_MAX_CLOCK_SPEED_HZ (5000000) // 5 MHz
+
+/**
+ * Device descriptor
+ */
+typedef struct
+{
+    spi_device_interface_config_t spi_cfg;  /**< SPI device configuration */
+    spi_device_handle_t spi_dev;            /**< SPI device handler */
+} max31855_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev            Device descriptor
+ * @param host           SPI host
+ * @param cs_pin         CS GPIO number
+ * @param clock_speed_hz SPI clock speed, Hz
+ * @return `ESP_OK` on success
+ */
+esp_err_t max31855_init_desc(max31855_t *dev, spi_host_device_t host, uint32_t clock_speed_hz, gpio_num_t cs_pin);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t max31855_free_desc(max31855_t *dev);
+
+/**
+ * @brief Read temperatures and sensor status
+ *
+ * @param dev       Device descriptor
+ * @param[out] tc_t Thermocouple temperature, degrees Celsius
+ * @param[out] cj_t Cold junction temperature, degrees Celsius (NULL-able)
+ * @param[out] scv  true when the thermocouple is short-circuited to VCC
+ * @param[out] scg  true when the thermocouple is short-circuited to GND
+ * @param[out] oc   true when the thermocouple is open (no connections)
+ * @return
+ */
+esp_err_t max31855_get_temperature(max31855_t *dev, float *tc_t, float *cj_t, bool *scv, bool *scg, bool *oc);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __MAX31855_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/max31865/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,21 @@
+---
+components:
+  - name: max31865
+    description: Driver for MAX31865 resistance converter for platinum RTDs
+    group: temperature
+    groups: []
+    code_owners: UncleRus
+    depends:
+      - driver
+      - log
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2021
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/max31865/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS max31865.c
+    INCLUDE_DIRS .
+    REQUIRES driver log
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/max31865/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+Copyright (c) 2017, 2018 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/max31865/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = driver log
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/max31865/max31865.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,314 @@
+/*
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file max31865.c
+ *
+ * ESP-IDF driver for MAX31865, resistance converter for platinum RTDs
+ *
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#include "max31865.h"
+#include <string.h>
+#include <esp_log.h>
+#include <math.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+
+static const char *TAG = "max31865";
+
+#define REG_CONFIG         (0x00)
+#define REG_RTD_MSB        (0x01)
+#define REG_HIGH_FAULT_MSB (0x03)
+#define REG_LOW_FAULT_MSB  (0x05)
+#define REG_FAULT_STATUS   (0x07)
+
+#define BIT_CONFIG_50HZ        BIT(0)
+#define BIT_CONFIG_FAULT_CLEAR BIT(1)
+#define BIT_CONFIG_FAULT_D2    BIT(2)
+#define BIT_CONFIG_FAULT_D3    BIT(3)
+#define BIT_CONFIG_3WIRE       BIT(4)
+#define BIT_CONFIG_1SHOT       BIT(5)
+#define BIT_CONFIG_AUTO        BIT(6)
+#define BIT_CONFIG_VBIAS       BIT(7)
+
+#define MASK_CONFIG_FAULT (BIT_CONFIG_FAULT_D2 | BIT_CONFIG_FAULT_D3)
+
+typedef struct
+{
+    float a, b;
+} rtd_coeff_t;
+
+static const rtd_coeff_t rtd_coeff[] = {
+     [MAX31865_ITS90]         = { .a = 3.9083e-3f, .b = -5.775e-7f },
+     [MAX31865_DIN43760]      = { .a = 3.9848e-3f, .b = -5.8019e-7f },
+     [MAX31865_US_INDUSTRIAL] = { .a = 3.9692e-3f, .b = -5.8495e-7f },
+};
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+static esp_err_t write_reg_8(max31865_t *dev, uint8_t reg, uint8_t val)
+{
+    spi_transaction_t t;
+    memset(&t, 0, sizeof(spi_transaction_t));
+
+    uint8_t tx[] = { reg | 0x80, val };
+
+    t.tx_buffer = tx;
+    t.length = sizeof(tx) * 8;
+
+    return spi_device_transmit(dev->spi_dev, &t);
+}
+
+static esp_err_t read_reg_8(max31865_t *dev, uint8_t reg, uint8_t *val)
+{
+    spi_transaction_t t;
+    memset(&t, 0, sizeof(spi_transaction_t));
+
+    uint8_t tx[] = { reg, 0 };
+    uint8_t rx[sizeof(tx)];
+
+    t.tx_buffer = tx;
+    t.rx_buffer = rx;
+    t.length = sizeof(tx) * 8;
+    CHECK(spi_device_transmit(dev->spi_dev, &t));
+
+    *val = rx[1];
+
+    return ESP_OK;
+}
+
+static esp_err_t read_reg_16(max31865_t *dev, uint8_t reg, uint16_t *val)
+{
+    spi_transaction_t t;
+    memset(&t, 0, sizeof(spi_transaction_t));
+
+    uint8_t tx[] = { reg, 0, 0 };
+    uint8_t rx[sizeof(tx)];
+
+    t.tx_buffer = tx;
+    t.rx_buffer = rx;
+    t.length = sizeof(tx) * 8;
+    CHECK(spi_device_transmit(dev->spi_dev, &t));
+
+    *val = (uint16_t)rx[1] << 8;
+    *val |= rx[2];
+
+    return ESP_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t max31865_init_desc(max31865_t *dev, spi_host_device_t host, uint32_t clock_speed_hz, gpio_num_t cs_pin)
+{
+    CHECK_ARG(dev);
+
+    memset(&dev->spi_cfg, 0, sizeof(dev->spi_cfg));
+    dev->spi_cfg.spics_io_num = cs_pin;
+    dev->spi_cfg.clock_speed_hz = clock_speed_hz;
+    dev->spi_cfg.mode = 1;
+    dev->spi_cfg.queue_size = 1;
+    dev->spi_cfg.cs_ena_pretrans = 1;
+    //dev->spi_cfg.flags = SPI_DEVICE_NO_DUMMY;
+
+    return spi_bus_add_device(host, &dev->spi_cfg, &dev->spi_dev);
+}
+
+esp_err_t max31865_free_desc(max31865_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return spi_bus_remove_device(dev->spi_dev);
+}
+
+esp_err_t max31865_set_config(max31865_t *dev, const max31865_config_t *config)
+{
+    CHECK_ARG(dev && config);
+
+    uint8_t val;
+    CHECK(read_reg_8(dev, REG_CONFIG, &val));
+
+    val &= ~(BIT_CONFIG_AUTO | BIT_CONFIG_3WIRE | BIT_CONFIG_VBIAS | BIT_CONFIG_50HZ);
+
+    val |= config->mode == MAX31865_MODE_AUTO ? BIT_CONFIG_AUTO : 0;
+    val |= config->connection == MAX31865_3WIRE ? BIT_CONFIG_3WIRE : 0;
+    val |= config->v_bias ? BIT_CONFIG_VBIAS : 0;
+    val |= config->filter == MAX31865_FILTER_50HZ ? BIT_CONFIG_50HZ : 0;
+
+    CHECK(write_reg_8(dev, REG_CONFIG, val));
+
+    return ESP_OK;
+}
+
+esp_err_t max31865_get_config(max31865_t *dev, max31865_config_t *config)
+{
+    CHECK_ARG(dev && config);
+
+    uint8_t val;
+    CHECK(read_reg_8(dev, REG_CONFIG, &val));
+
+    config->filter = val & BIT_CONFIG_50HZ ? MAX31865_FILTER_50HZ : MAX31865_FILTER_60HZ;
+    config->v_bias = val & BIT_CONFIG_VBIAS ? 1 : 0;
+    config->mode = val & BIT_CONFIG_AUTO ? MAX31865_MODE_AUTO : MAX31865_MODE_SINGLE;
+    config->connection = val & BIT_CONFIG_3WIRE ? MAX31865_3WIRE : MAX31865_2WIRE;
+
+    return ESP_OK;
+}
+
+esp_err_t max31865_start_measurement(max31865_t *dev)
+{
+    CHECK_ARG(dev);
+
+    uint8_t val;
+    CHECK(read_reg_8(dev, REG_CONFIG, &val));
+    val |= BIT_CONFIG_1SHOT;
+    CHECK(write_reg_8(dev, REG_CONFIG, val));
+
+    return ESP_OK;
+}
+
+esp_err_t max31865_read_raw(max31865_t *dev, uint16_t *raw, bool *fault)
+{
+    CHECK_ARG(dev && raw && fault);
+
+    CHECK(read_reg_16(dev, REG_RTD_MSB, raw));
+    *fault = *raw & 1;
+    *raw >>= 1;
+
+    return ESP_OK;
+}
+
+// conversion code taken from Adafruit library
+esp_err_t max31865_read_temperature(max31865_t *dev, float *temp)
+{
+    CHECK_ARG(dev && temp && dev->standard <= MAX31865_US_INDUSTRIAL);
+
+    uint16_t raw;
+    bool fault;
+    CHECK(max31865_read_raw(dev, &raw, &fault));
+    if (fault)
+    {
+        ESP_LOGE(TAG, "[CS %d] Fault detected", dev->spi_cfg.spics_io_num);
+        return ESP_FAIL;
+    }
+
+    float r_rtd = raw * dev->r_ref / 32768;
+
+    ESP_LOGD(TAG, "[CS %d] RTD resistance: %.8f", dev->spi_cfg.spics_io_num, r_rtd);
+
+    const rtd_coeff_t *c = rtd_coeff + dev->standard;
+
+    *temp = (sqrtf((c->a * c->a - (4 * c->b)) + (4 * c->b / dev->rtd_nominal * r_rtd)) - c->a) / (2 * c->b);
+
+    if (*temp >= 0)
+        return ESP_OK;
+
+    // below zero
+    r_rtd = r_rtd / dev->rtd_nominal * 100; // normalize to 100 Ohm
+
+    float rpoly = r_rtd;
+
+    *temp = -242.02;
+    *temp += 2.2228 * rpoly;
+    rpoly *= r_rtd; // square
+    *temp += 2.5859e-3 * rpoly;
+    rpoly *= r_rtd; // ^3
+    *temp -= 4.8260e-6 * rpoly;
+    rpoly *= r_rtd; // ^4
+    *temp -= 2.8183e-8 * rpoly;
+    rpoly *= r_rtd; // ^5
+    *temp += 1.5243e-10 * rpoly;
+
+    return ESP_OK;
+}
+
+esp_err_t max31865_measure(max31865_t *dev, float *temp)
+{
+    CHECK(max31865_start_measurement(dev));
+    vTaskDelay(pdMS_TO_TICKS(70));
+    return max31865_read_temperature(dev, temp);
+}
+
+esp_err_t max31865_detect_fault_auto(max31865_t *dev)
+{
+    CHECK_ARG(dev);
+
+    uint8_t conf;
+    CHECK(read_reg_8(dev, REG_CONFIG, &conf));
+
+    uint8_t fault_bits = conf & MASK_CONFIG_FAULT;
+    if (fault_bits == BIT_CONFIG_FAULT_D2)
+    {
+        ESP_LOGD(TAG, "[CS %d] Automatic fault detection still running", dev->spi_cfg.spics_io_num);
+        return ESP_ERR_INVALID_STATE;
+    }
+    if (fault_bits == BIT_CONFIG_FAULT_D3)
+    {
+        ESP_LOGD(TAG, "[CS %d] Manual cycle 1 still running, waiting for user to start cycle 2", dev->spi_cfg.spics_io_num);
+        return ESP_ERR_INVALID_STATE;
+    }
+    if (fault_bits == (BIT_CONFIG_FAULT_D2 | BIT_CONFIG_FAULT_D3))
+    {
+        ESP_LOGD(TAG, "[CS %d] Manual cycle 2 still running", dev->spi_cfg.spics_io_num);
+        return ESP_ERR_INVALID_STATE;
+    }
+
+    CHECK(write_reg_8(dev, REG_CONFIG, BIT_CONFIG_VBIAS | BIT_CONFIG_FAULT_D2));
+
+    uint8_t tmp;
+    do
+    {
+        // FIXME endless loop
+        CHECK(read_reg_8(dev, REG_CONFIG, &tmp));
+    }
+    while (tmp & MASK_CONFIG_FAULT);
+
+    return ESP_OK;
+}
+
+esp_err_t max31865_get_fault_status(max31865_t *dev, uint8_t *fault_status)
+{
+    CHECK_ARG(dev && fault_status);
+
+    return read_reg_8(dev, REG_FAULT_STATUS, fault_status);
+}
+
+esp_err_t max31865_clear_fault_status(max31865_t *dev)
+{
+    CHECK_ARG(dev);
+
+    uint8_t val;
+    CHECK(read_reg_8(dev, REG_CONFIG, &val));
+    val &= ~(BIT_CONFIG_1SHOT | BIT_CONFIG_FAULT_D2 | BIT_CONFIG_FAULT_D3);
+    val |= BIT_CONFIG_FAULT_CLEAR;
+    CHECK(write_reg_8(dev, REG_CONFIG, val));
+
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/max31865/max31865.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file max31865.h
+ * @defgroup max31865 max31865
+ * @{
+ *
+ * ESP-IDF driver for MAX31865, resistance converter for platinum RTDs
+ *
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __MAX31865_H__
+#define __MAX31865_H__
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <driver/spi_master.h>
+#include <driver/gpio.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define MAX31865_MAX_CLOCK_SPEED_HZ (1000000) // 1 MHz
+
+/**
+ * Conversion mode
+ */
+typedef enum {
+    MAX31865_MODE_SINGLE = 0, /**< Single consersion mode, default */
+    MAX31865_MODE_AUTO        /**< Automatic conversion mode at 50/60Hz rate */
+} max31865_mode_t;
+
+/**
+ * Notch frequencies for the noise rejection filter
+ */
+typedef enum {
+    MAX31865_FILTER_60HZ = 0, /**< 60Hz */
+    MAX31865_FILTER_50HZ      /**< 50Hz */
+} max31865_filter_t;
+
+/**
+ * Connection type
+ */
+typedef enum {
+    MAX31865_2WIRE = 0, /**< 2 wires */
+    MAX31865_3WIRE,     /**< 3 wires */
+    MAX31865_4WIRE      /**< 4 wires */
+} max31865_connection_type_t;
+
+/**
+ * Device configuration
+ */
+typedef struct
+{
+    max31865_mode_t mode;
+    max31865_connection_type_t connection;
+    bool v_bias;
+    max31865_filter_t filter;
+} max31865_config_t;
+
+/**
+ * Temperature scale standard
+ */
+typedef enum {
+    MAX31865_ITS90 = 0,    /**< ITS-90 */
+    MAX31865_DIN43760,     /**< DIN43760 */
+    MAX31865_US_INDUSTRIAL /**< US INDUSTRIAL */
+} max31865_standard_t;
+
+/**
+ * Device descriptor
+ */
+typedef struct
+{
+    spi_device_interface_config_t spi_cfg;  /**< SPI device configuration */
+    spi_device_handle_t spi_dev;            /**< SPI device handler */
+    max31865_standard_t standard;           /**< Temperature scale standard */
+    float r_ref;                            /**< Reference resistor value, Ohms */
+    float rtd_nominal;                      /**< RTD nominal resistance at 0 deg. C, Ohms (PT100 - 100 Ohms, PT1000 - 1000 Ohms) */
+} max31865_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev            Device descriptor
+ * @param host           SPI host
+ * @param clock_speed_hz SPI clock speed, Hz
+ * @param cs_pin         CS GPIO number
+ * @return `ESP_OK` on success
+ */
+esp_err_t max31865_init_desc(max31865_t *dev, spi_host_device_t host, uint32_t clock_speed_hz, gpio_num_t cs_pin);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t max31865_free_desc(max31865_t *dev);
+
+/**
+ * @brief Configure device
+ *
+ * @param dev    Device descriptor
+ * @param config Configuration
+ * @return `ESP_OK` on success
+ */
+esp_err_t max31865_set_config(max31865_t *dev, const max31865_config_t *config);
+
+/**
+ * @brief Read device configuration
+ *
+ * @param dev         Device descriptor
+ * @param[out] config Configuration
+ * @return `ESP_OK` on success
+ */
+esp_err_t max31865_get_config(max31865_t *dev, max31865_config_t *config);
+
+/**
+ * @brief Trigger single-shot measurement
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t max31865_start_measurement(max31865_t *dev);
+
+/**
+ * @brief Read raw RTD value
+ *
+ * @param dev        Device descritptor
+ * @param[out] raw   Raw 15 bits RTD value
+ * @param[out] fault true when fault is detected
+ * @return `ESP_OK` on success
+ */
+esp_err_t max31865_read_raw(max31865_t *dev, uint16_t *raw, bool *fault);
+
+/**
+ * @brief Read RTD value and convert it temperature
+ *
+ * @param dev       Device descritptor
+ * @param[out] temp Temperature, deg. Celsius
+ * @return `ESP_OK` on success
+ */
+esp_err_t max31865_read_temperature(max31865_t *dev, float *temp);
+
+/**
+ * @brief Measure temperature
+ *
+ * Run full cycle of single-shot measurement:
+ *  - trigger measurement
+ *  - wait for 70ms
+ *  - read and convert RTD value
+ *
+ * @param dev       Device descritptor
+ * @param[out] temp Temperature, deg. Celsius
+ * @return `ESP_OK` on success
+ */
+esp_err_t max31865_measure(max31865_t *dev, float *temp);
+
+/**
+ * @brief Run automatical fault detection cycle
+ *
+ * After calling this function, device must be reconfigured
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t max31865_detect_fault_auto(max31865_t *dev);
+
+/**
+ * @brief Get bits of current fault status
+ *
+ * See datasheet for fault status interpretation
+ *
+ * @param dev               Device descriptor
+ * @param[out] fault_status Fault status bits
+ * @return `ESP_OK` on success
+ */
+esp_err_t max31865_get_fault_status(max31865_t *dev, uint8_t *fault_status);
+
+/**
+ * @brief Clear current fault status
+ *
+ * After calling this function, device must be reconfigured
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t max31865_clear_fault_status(max31865_t *dev);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __MAX31865_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/max7219/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,21 @@
+---
+components:
+  - name: max7219
+    description: Driver for 8-Digit LED display drivers, MAX7219/MAX7221
+    group: led
+    groups: []
+    code_owners: UncleRus
+    depends:
+      - driver
+      - log
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2019
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/max7219/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS max7219.c
+    INCLUDE_DIRS .
+    REQUIRES driver log
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/max7219/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+Copyright (c) 2017, 2018 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/max7219/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = driver log
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/max7219/max7219.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,263 @@
+/*
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file max7219.c
+ *
+ * ESP-IDF driver for MAX7219/MAX7221
+ * Serially Interfaced, 8-Digit LED Display Drivers
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2017 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#include "max7219.h"
+#include <string.h>
+#include <esp_log.h>
+
+#include "max7219_priv.h"
+
+static const char *TAG = "max7219";
+
+#define ALL_CHIPS 0xff
+#define ALL_DIGITS 8
+
+#define REG_DIGIT_0      (1 << 8)
+#define REG_DECODE_MODE  (9 << 8)
+#define REG_INTENSITY    (10 << 8)
+#define REG_SCAN_LIMIT   (11 << 8)
+#define REG_SHUTDOWN     (12 << 8)
+#define REG_DISPLAY_TEST (15 << 8)
+
+#define VAL_CLEAR_BCD    0x0f
+#define VAL_CLEAR_NORMAL 0x00
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+static inline uint16_t shuffle(uint16_t val)
+{
+    return (val >> 8) | (val << 8);
+}
+
+static esp_err_t send(max7219_t *dev, uint8_t chip, uint16_t value)
+{
+    uint16_t buf[MAX7219_MAX_CASCADE_SIZE] = { 0 };
+    if (chip == ALL_CHIPS)
+    {
+        for (uint8_t i = 0; i < dev->cascade_size; i++)
+            buf[i] = shuffle(value);
+    }
+    else buf[chip] = shuffle(value);
+
+    spi_transaction_t t;
+    memset(&t, 0, sizeof(t));
+    t.length = dev->cascade_size * 16;
+    t.tx_buffer = buf;
+    return spi_device_transmit(dev->spi_dev, &t);
+}
+
+inline static uint8_t get_char(max7219_t *dev, char c)
+{
+    if (dev->bcd)
+    {
+        if (c >= '0' && c <= '9')
+            return c - '0';
+        switch (c)
+        {
+            case '-':
+                return 0x0a;
+            case 'E':
+            case 'e':
+                return 0x0b;
+            case 'H':
+            case 'h':
+                return 0x0c;
+            case 'L':
+            case 'l':
+                return 0x0d;
+            case 'P':
+            case 'p':
+                return 0x0e;
+        }
+        return VAL_CLEAR_BCD;
+    }
+
+    return font_7seg[(c - 0x20) & 0x7f];
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t max7219_init_desc(max7219_t *dev, spi_host_device_t host, uint32_t clock_speed_hz, gpio_num_t cs_pin)
+{
+    CHECK_ARG(dev);
+
+    memset(&dev->spi_cfg, 0, sizeof(dev->spi_cfg));
+    dev->spi_cfg.spics_io_num = cs_pin;
+    dev->spi_cfg.clock_speed_hz = clock_speed_hz;
+    dev->spi_cfg.mode = 0;
+    dev->spi_cfg.queue_size = 1;
+    dev->spi_cfg.flags = SPI_DEVICE_NO_DUMMY;
+
+    return spi_bus_add_device(host, &dev->spi_cfg, &dev->spi_dev);
+}
+
+esp_err_t max7219_free_desc(max7219_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return spi_bus_remove_device(dev->spi_dev);
+}
+
+esp_err_t max7219_init(max7219_t *dev)
+{
+    CHECK_ARG(dev);
+    if (!dev->cascade_size || dev->cascade_size > MAX7219_MAX_CASCADE_SIZE)
+    {
+        ESP_LOGE(TAG, "Invalid cascade size %d", dev->cascade_size);
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    uint8_t max_digits = dev->cascade_size * ALL_DIGITS;
+    if (dev->digits > max_digits)
+    {
+        ESP_LOGE(TAG, "Invalid digits count %d, max %d", dev->digits, max_digits);
+        return ESP_ERR_INVALID_ARG;
+    }
+    if (!dev->digits)
+        dev->digits = max_digits;
+
+    // Shutdown all chips
+    CHECK(max7219_set_shutdown_mode(dev, true));
+    // Disable test
+    CHECK(send(dev, ALL_CHIPS, REG_DISPLAY_TEST));
+    // Set max scan limit
+    CHECK(send(dev, ALL_CHIPS, REG_SCAN_LIMIT | (ALL_DIGITS - 1)));
+    // Set normal decode mode & clear display
+    CHECK(max7219_set_decode_mode(dev, false));
+    // Set minimal brightness
+    CHECK(max7219_set_brightness(dev, 0));
+    // Wake up
+    CHECK(max7219_set_shutdown_mode(dev, false));
+
+    return ESP_OK;
+}
+
+esp_err_t max7219_set_decode_mode(max7219_t *dev, bool bcd)
+{
+    CHECK_ARG(dev);
+
+    dev->bcd = bcd;
+    CHECK(send(dev, ALL_CHIPS, REG_DECODE_MODE | (bcd ? 0xff : 0)));
+    CHECK(max7219_clear(dev));
+
+    return ESP_OK;
+}
+
+esp_err_t max7219_set_brightness(max7219_t *dev, uint8_t value)
+{
+    CHECK_ARG(dev);
+    CHECK_ARG(value <= MAX7219_MAX_BRIGHTNESS);
+
+    CHECK(send(dev, ALL_CHIPS, REG_INTENSITY | value));
+
+    return ESP_OK;
+}
+
+esp_err_t max7219_set_shutdown_mode(max7219_t *dev, bool shutdown)
+{
+    CHECK_ARG(dev);
+
+    CHECK(send(dev, ALL_CHIPS, REG_SHUTDOWN | !shutdown));
+
+    return ESP_OK;
+}
+
+esp_err_t max7219_set_digit(max7219_t *dev, uint8_t digit, uint8_t val)
+{
+    CHECK_ARG(dev);
+    if (digit >= dev->digits)
+    {
+        ESP_LOGE(TAG, "Invalid digit: %d", digit);
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    if (dev->mirrored)
+        digit = dev->digits - digit - 1;
+
+    uint8_t c = digit / ALL_DIGITS;
+    uint8_t d = digit % ALL_DIGITS;
+
+    ESP_LOGV(TAG, "Chip %d, digit %d val 0x%02x", c, d, val);
+
+    CHECK(send(dev, c, (REG_DIGIT_0 + ((uint16_t)d << 8)) | val));
+
+    return ESP_OK;
+}
+
+esp_err_t max7219_clear(max7219_t *dev)
+{
+    CHECK_ARG(dev);
+
+    uint8_t val = dev->bcd ? VAL_CLEAR_BCD : VAL_CLEAR_NORMAL;
+    for (uint8_t i = 0; i < ALL_DIGITS; i++)
+        CHECK(send(dev, ALL_CHIPS, (REG_DIGIT_0 + ((uint16_t)i << 8)) | val));
+
+    return ESP_OK;
+}
+
+esp_err_t max7219_draw_text_7seg(max7219_t *dev, uint8_t pos, const char *s)
+{
+    CHECK_ARG(dev && s);
+
+    while (*s && pos < dev->digits)
+    {
+        uint8_t c = get_char(dev, *s);
+        if (*(s + 1) == '.')
+        {
+            c |= 0x80;
+            s++;
+        }
+        CHECK(max7219_set_digit(dev, pos, c));
+        pos++;
+        s++;
+    }
+
+    return ESP_OK;
+}
+
+esp_err_t max7219_draw_image_8x8(max7219_t *dev, uint8_t pos, const void *image)
+{
+    CHECK_ARG(dev && image);
+
+    for (uint8_t i = pos, offs = 0; i < dev->digits && offs < 8; i++, offs++)
+        max7219_set_digit(dev, i, *((uint8_t *)image + offs));
+
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/max7219/max7219.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file max7219.h
+ * @defgroup max7219 max7219
+ * @{
+ *
+ * ESP-IDF driver for MAX7219/MAX7221
+ * Serially Interfaced, 8-Digit LED Display Drivers
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2017 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __MAX7219_H__
+#define __MAX7219_H__
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <driver/spi_master.h>
+#include <driver/gpio.h> // add by nopnop2002
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define MAX7219_MAX_CLOCK_SPEED_HZ (10000000) // 10 MHz
+
+#define MAX7219_MAX_CASCADE_SIZE 8
+#define MAX7219_MAX_BRIGHTNESS   15
+
+/**
+ * Display descriptor
+ */
+typedef struct
+{
+    spi_device_interface_config_t spi_cfg;
+    spi_device_handle_t spi_dev;
+    uint8_t digits;              //!< Accessible digits in 7seg. Up to cascade_size * 8
+    uint8_t cascade_size;        //!< Up to `MAX7219_MAX_CASCADE_SIZE` MAX721xx cascaded
+    bool mirrored;               //!< true for horizontally mirrored displays
+    bool bcd;
+} max7219_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev Device descriptor
+ * @param host SPI host
+ * @param clock_speed_hz SPI clock speed, Hz
+ * @param cs_pin CS GPIO number
+ * @return `ESP_OK` on success
+ */
+esp_err_t max7219_init_desc(max7219_t *dev, spi_host_device_t host, uint32_t clock_speed_hz, gpio_num_t cs_pin);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t max7219_free_desc(max7219_t *dev);
+
+/**
+ * @brief Initialize display
+ *
+ * Switch it to normal operation from shutdown mode,
+ * set scan limit to the max and clear
+ *
+ * @param dev Display descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t max7219_init(max7219_t *dev);
+
+/**
+ * @brief Set decode mode and clear display
+ *
+ * @param dev Display descriptor
+ * @param bcd true to set BCD decode mode, false to normal
+ * @return `ESP_OK` on success
+ */
+esp_err_t max7219_set_decode_mode(max7219_t *dev, bool bcd);
+
+/**
+ * @brief Set display brightness
+ *
+ * @param dev Display descriptor
+ * @param value Brightness value, 0..MAX7219_MAX_BRIGHTNESS
+ * @return `ESP_OK` on success
+ */
+esp_err_t max7219_set_brightness(max7219_t *dev, uint8_t value);
+
+/**
+ * @brief Shutdown display or set it to normal mode
+ *
+ * @param dev Display descriptor
+ * @param shutdown Shutdown display if true
+ * @return `ESP_OK` on success
+ */
+esp_err_t max7219_set_shutdown_mode(max7219_t *dev, bool shutdown);
+
+/**
+ * @brief Write data to display digit
+ *
+ * @param dev Display descriptor
+ * @param digit Digit index, 0..dev->digits - 1
+ * @param val Data
+ * @return `ESP_OK` on success
+ */
+esp_err_t max7219_set_digit(max7219_t *dev, uint8_t digit, uint8_t val);
+
+/**
+ * @brief Clear display
+ *
+ * @param dev Display descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t max7219_clear(max7219_t *dev);
+
+/**
+ * @brief Draw text on 7-segment display
+ *
+ * @param dev Display descriptor
+ * @param pos Start digit
+ * @param s Text
+ * @return `ESP_OK` on success
+ */
+esp_err_t max7219_draw_text_7seg(max7219_t *dev, uint8_t pos, const char *s);
+
+/**
+ * @brief Draw 64-bit image on 8x8 matrix
+ *
+ * @param dev Display descriptor
+ * @param pos Start digit
+ * @param image 64-bit buffer with image data
+ * @return `ESP_OK` on success
+ */
+esp_err_t max7219_draw_image_8x8(max7219_t *dev, uint8_t pos, const void *image);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __MAX7219_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/max7219/max7219_priv.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file max7219_priv.h
+ *
+ * ESP-IDF driver for MAX7219/MAX7221
+ * Serially Interfaced, 8-Digit LED Display Drivers
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2017, 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __MAX7219_PRIV_H__
+#define __MAX7219_PRIV_H__
+
+static const uint8_t font_7seg[] = {
+    /*  ' '   !     "     #     $     %     &     '     (     )     */
+        0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x02, 0x4e, 0x78,
+    /*  *     +     ,     -     .     /     0     1     2     3     */
+        0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x7e, 0x30, 0x6d, 0x79,
+    /*  4     5     6     7     8     9     :     ;     <     =     */
+        0x33, 0x5b, 0x5f, 0x70, 0x7f, 0x7b, 0x00, 0x00, 0x0d, 0x09,
+    /*  >     ?     @     A     B     C     D     E     F     G     */
+        0x19, 0x65, 0x00, 0x77, 0x1f, 0x4e, 0x3d, 0x4f, 0x47, 0x5e,
+    /*  H     I     J     K     L     M     N     O     P     Q     */
+        0x37, 0x06, 0x38, 0x57, 0x0e, 0x76, 0x15, 0x1d, 0x67, 0x73,
+    /*  R     S     T     U     V     W     X     Y     Z     [     */
+        0x05, 0x5b, 0x0f, 0x1c, 0x3e, 0x2a, 0x49, 0x3b, 0x6d, 0x4e,
+    /*  \     ]     ^     _     `     a     b     c     d     e     */
+        0x00, 0x78, 0x00, 0x08, 0x02, 0x77, 0x1f, 0x4e, 0x3d, 0x4f,
+    /*  f     g     h     i     j     k     l     m     n     o     */
+        0x47, 0x5e, 0x37, 0x06, 0x38, 0x57, 0x0e, 0x76, 0x15, 0x1d,
+    /*  p     q     r     s     t     u     v     w     x     y     */
+        0x67, 0x73, 0x05, 0x5b, 0x0f, 0x1c, 0x3e, 0x2a, 0x49, 0x3b,
+    /*  z     {     |     }     ~     */
+        0x6d, 0x4e, 0x06, 0x78, 0x00
+};
+
+
+#endif /* __MAX7219_PRIV_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp23008/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,23 @@
+---
+components:
+  - name: mcp23008
+    description: Driver for 8-bit I2C GPIO expander MCP23008
+    group: gpio
+    groups: []
+    code_owners: UncleRus
+    depends:
+      - i2cdev
+      - log
+      - esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2018
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp23008/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS mcp23008.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp23008/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+Copyright (c) 2018 Ruslan V. Uss (https://github.com/UncleRus)
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp23008/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp23008/mcp23008.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,302 @@
+/*
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file mcp23008.c
+ *
+ * ESP-IDF driver for I2C 8 bit GPIO expander MCP23008
+ *
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+
+#include <esp_log.h>
+#include <esp_idf_lib_helpers.h>
+#include "mcp23008.h"
+
+#define I2C_FREQ_HZ 1000000 // Max 1MHz for esp-idf, but device supports up to 1.7Mhz
+
+#define REG_IODIR   0x00
+#define REG_IPOL    0x01
+#define REG_GPINTEN 0x02
+#define REG_DEFVAL  0x03
+#define REG_INTCON  0x04
+#define REG_IOCON   0x05
+#define REG_GPPU    0x06
+#define REG_INTF    0x07
+#define REG_INTCAP  0x08
+#define REG_GPIO    0x09
+#define REG_OLAT    0x0a
+
+#define BIT_IOCON_INTPOL 1
+#define BIT_IOCON_ODR    2
+//#define BIT_IOCON_HAEN   3
+#define BIT_IOCON_DISSLW 4
+#define BIT_IOCON_SREAD  5
+
+static const char *TAG = "mcp23008";
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+#define BV(x) (1 << (x))
+
+static esp_err_t read_reg(i2c_dev_t *dev, uint8_t reg, uint8_t *val)
+{
+    CHECK_ARG(dev && val);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, reg, val, 1));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+static esp_err_t write_reg(i2c_dev_t *dev, uint8_t reg, uint8_t val)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, reg, &val, 1));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+static esp_err_t read_reg_bit(i2c_dev_t *dev, uint8_t reg, bool *val, uint8_t bit)
+{
+    CHECK_ARG(dev && val);
+
+    uint8_t buf;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, reg, &buf, 1));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    *val = (buf & BV(bit)) >> bit;
+
+    return ESP_OK;
+}
+
+static esp_err_t write_reg_bit(i2c_dev_t *dev, uint8_t reg, bool val, uint8_t bit)
+{
+    CHECK_ARG(dev);
+
+    uint8_t buf;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, reg, &buf, 1));
+    buf = (buf & ~BV(bit)) | (val ? BV(bit) : 0);
+    I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, reg, &buf, 1));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t mcp23008_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+    if (addr < MCP23008_I2C_ADDR_BASE || addr > MCP23008_I2C_ADDR_BASE + 7)
+    {
+        ESP_LOGE(TAG, "Invalid device address: 0x%02x", addr);
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    dev->port = port;
+    dev->addr = addr;
+    dev->cfg.sda_io_num = sda_gpio;
+    dev->cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(dev);
+}
+
+esp_err_t mcp23008_free_desc(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(dev);
+}
+
+esp_err_t mcp23008_get_int_out_mode(i2c_dev_t *dev, mcp23008_int_out_mode_t *mode)
+{
+    CHECK_ARG(mode);
+
+    uint8_t r;
+    CHECK(read_reg(dev, REG_IOCON, &r));
+
+    if (r & BV(BIT_IOCON_ODR))
+    {
+        *mode = MCP23008_OPEN_DRAIN;
+        return ESP_OK;
+    }
+
+    *mode = r & BV(BIT_IOCON_INTPOL) ? MCP23008_ACTIVE_HIGH : MCP23008_ACTIVE_LOW;
+    return ESP_OK;
+}
+
+esp_err_t mcp23008_set_int_out_mode(i2c_dev_t *dev, mcp23008_int_out_mode_t mode)
+{
+    if (mode == MCP23008_OPEN_DRAIN)
+        return write_reg_bit(dev, REG_IOCON, true, BIT_IOCON_ODR);
+
+    return write_reg_bit(dev, REG_IOCON, mode == MCP23008_ACTIVE_HIGH, BIT_IOCON_INTPOL);
+}
+
+esp_err_t mcp23008_port_get_mode(i2c_dev_t *dev, uint8_t *val)
+{
+    return read_reg(dev, REG_IODIR, val);
+}
+
+esp_err_t mcp23008_port_set_mode(i2c_dev_t *dev, uint8_t val)
+{
+    return write_reg(dev, REG_IODIR, val);
+}
+
+esp_err_t mcp23008_port_get_pullup(i2c_dev_t *dev, uint8_t *val)
+{
+    return read_reg(dev, REG_GPPU, val);
+}
+
+esp_err_t mcp23008_port_set_pullup(i2c_dev_t *dev, uint8_t val)
+{
+    return write_reg(dev, REG_GPPU, val);
+}
+
+esp_err_t mcp23008_port_read(i2c_dev_t *dev, uint8_t *val)
+{
+    return read_reg(dev, REG_GPIO, val);
+}
+
+esp_err_t mcp23008_port_write(i2c_dev_t *dev, uint8_t val)
+{
+    return write_reg(dev, REG_GPIO, val);
+}
+
+esp_err_t mcp23008_get_mode(i2c_dev_t *dev, uint8_t pin, mcp23008_gpio_mode_t *mode)
+{
+    CHECK_ARG(mode && pin < 8);
+
+    bool buf;
+    CHECK(read_reg_bit(dev, REG_IODIR, &buf, pin));
+    *mode = buf ? MCP23008_GPIO_INPUT : MCP23008_GPIO_OUTPUT;
+
+    return ESP_OK;
+}
+
+esp_err_t mcp23008_set_mode(i2c_dev_t *dev, uint8_t pin, mcp23008_gpio_mode_t mode)
+{
+    CHECK_ARG(pin < 8);
+
+    return write_reg_bit(dev, REG_IODIR, mode, pin);
+}
+
+esp_err_t mcp23008_get_pullup(i2c_dev_t *dev, uint8_t pin, bool *enable)
+{
+    CHECK_ARG(pin < 8);
+
+    return read_reg_bit(dev, REG_GPPU, enable, pin);
+}
+
+esp_err_t mcp23008_set_pullup(i2c_dev_t *dev, uint8_t pin, bool enable)
+{
+    CHECK_ARG(pin < 8);
+
+    return write_reg_bit(dev, REG_GPPU, enable, pin);
+}
+
+esp_err_t mcp23008_get_level(i2c_dev_t *dev, uint8_t pin, uint32_t *val)
+{
+    CHECK_ARG(val && pin < 8);
+
+    bool buf;
+    CHECK(read_reg_bit(dev, REG_GPIO, &buf, pin));
+    *val = buf ? 1 : 0;
+
+    return ESP_OK;
+}
+
+esp_err_t mcp23008_set_level(i2c_dev_t *dev, uint8_t pin, uint32_t val)
+{
+    CHECK_ARG(pin < 8);
+
+    return write_reg_bit(dev, REG_GPIO, val, pin);
+}
+
+esp_err_t mcp23008_port_set_interrupt(i2c_dev_t *dev, uint8_t mask, mcp23008_gpio_intr_t intr)
+{
+    CHECK_ARG(dev);
+
+    uint8_t int_en;
+    CHECK(read_reg(dev, REG_GPINTEN, &int_en));
+
+    if (intr == MCP23008_INT_DISABLED)
+    {
+        // disable interrupts
+        int_en &= ~mask;
+        CHECK(write_reg(dev, REG_GPINTEN, int_en));
+
+        return ESP_OK;
+    }
+
+    uint8_t int_con;
+    CHECK(read_reg(dev, REG_INTCON, &int_con));
+
+    if (intr == MCP23008_INT_ANY_EDGE)
+        int_con &= ~mask;
+    else
+    {
+        int_con |= mask;
+
+        uint8_t int_def;
+        CHECK(read_reg(dev, REG_DEFVAL, &int_def));
+        if (intr == MCP23008_INT_LOW_EDGE)
+            int_def |= mask;
+        else
+            int_def &= ~mask;
+        CHECK(write_reg(dev, REG_DEFVAL, int_def));
+    }
+
+    CHECK(write_reg(dev, REG_INTCON, int_con));
+
+    // enable interrupts
+    int_en |= mask;
+    CHECK(write_reg(dev, REG_GPINTEN, int_en));
+
+    return ESP_OK;
+}
+
+esp_err_t mcp23008_set_interrupt(i2c_dev_t *dev, uint8_t pin, mcp23008_gpio_intr_t intr)
+{
+    CHECK_ARG(pin < 8);
+
+    return mcp23008_port_set_interrupt(dev, BV(pin), intr);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp23008/mcp23008.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,273 @@
+/*
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file mcp23008.h
+ * @defgroup mcp23008 mcp23008
+ * @{
+ *
+ * ESP-IDF driver for I2C 8 bit GPIO expander MCP23008
+ *
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __MCP23008_H__
+#define __MCP23008_H__
+
+#include <stdbool.h>
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#define MCP23008_I2C_ADDR_BASE 0x20
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * GPIO mode
+ */
+typedef enum
+{
+    MCP23008_GPIO_OUTPUT = 0,
+    MCP23008_GPIO_INPUT
+} mcp23008_gpio_mode_t;
+
+/**
+ * INTA/INTB pins mode
+ */
+typedef enum
+{
+    MCP23008_ACTIVE_LOW = 0, //!< Low level on interrupt
+    MCP23008_ACTIVE_HIGH,    //!< High level on interrupt
+    MCP23008_OPEN_DRAIN      //!< Open drain
+} mcp23008_int_out_mode_t;
+
+/**
+ * Interrupt mode
+ */
+typedef enum
+{
+    MCP23008_INT_DISABLED = 0, //!< No interrupt
+    MCP23008_INT_LOW_EDGE,     //!< Interrupt on low edge
+    MCP23008_INT_HIGH_EDGE,    //!< Interrupt on high edge
+    MCP23008_INT_ANY_EDGE      //!< Interrupt on any edge
+} mcp23008_gpio_intr_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * default SCL frequency is 1MHz
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @param port I2C port number
+ * @param addr I2C address,
+ * @param sda_gpio SDA GPIO
+ * @param scl_gpio SCL GPIO
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23008_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23008_free_desc(i2c_dev_t *dev);
+
+/**
+ * @brief Get INT pins mode
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @param[out] mode Buffer to store mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23008_get_int_out_mode(i2c_dev_t *dev, mcp23008_int_out_mode_t *mode);
+
+/**
+ * @brief Set INT pins mode
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @param mode INT pins mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23008_set_int_out_mode(i2c_dev_t *dev, mcp23008_int_out_mode_t mode);
+
+/**
+ * @brief Get GPIO pins mode
+ *
+ * 0 - output, 1 - input for each bit in `val`
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @param[out] val Buffer to store mode, 0 bit for GPIO0..7 bit for GPIO7
+ * @return
+ */
+esp_err_t mcp23008_port_get_mode(i2c_dev_t *dev, uint8_t *val);
+
+/**
+ * @brief Set GPIO pins mode
+ *
+ * 0 - output, 1 - input for each bit in `val`
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @param val Mode, 0 bit for GPIO0..7 bit for GPIO7
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23008_port_set_mode(i2c_dev_t *dev, uint8_t val);
+
+/**
+ * @brief Get GPIO pullups status
+ *
+ * 0 - pullup disabled, 1 - pullup enabled for each bit in `val`
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @param[out] val Pullup status, 0 bit for GPIO0..7 bit for GPIO7
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23008_port_get_pullup(i2c_dev_t *dev, uint8_t *val);
+
+/**
+ * @brief Set GPIO pullups status
+ *
+ * 0 - pullup disabled, 1 - pullup enabled for each bit in `val`
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @param val Pullup status, 0 bit for GPIO0..7 bit for GPIO7
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23008_port_set_pullup(i2c_dev_t *dev, uint8_t val);
+
+/**
+ * @brief Read GPIO port value
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @param[out] val 8-bit GPIO port value, 0 bit for GPIO0..7 bit for GPIO7
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23008_port_read(i2c_dev_t *dev, uint8_t *val);
+
+/**
+ * @brief Write value to GPIO port
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @param val GPIO port value, 0 bit for GPIO0..7 bit for GPIO7
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23008_port_write(i2c_dev_t *dev, uint8_t val);
+
+/**
+ * @brief Get GPIO pin mode
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @param pin Pin number, 0..7
+ * @param[out] mode GPIO pin mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23008_get_mode(i2c_dev_t *dev, uint8_t pin, mcp23008_gpio_mode_t *mode);
+
+/**
+ * @brief Set GPIO pin mode
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @param pin Pin number, 0..7
+ * @param mode GPIO pin mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23008_set_mode(i2c_dev_t *dev, uint8_t pin, mcp23008_gpio_mode_t mode);
+
+/**
+ * @brief Get pullup mode of GPIO pin
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @param pin Pin number, 0..7
+ * @param[out] enable pullup mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23008_get_pullup(i2c_dev_t *dev, uint8_t pin, bool *enable);
+
+/**
+ * @brief Set pullup mode of GPIO pin
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @param pin Pin number, 0..7
+ * @param enable `true` to enable pullup
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23008_set_pullup(i2c_dev_t *dev, uint8_t pin, bool enable);
+
+/**
+ * @brief Read GPIO pin level
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @param pin Pin number, 0..7
+ * @param[out] val `true` if pin currently in high state
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23008_get_level(i2c_dev_t *dev, uint8_t pin, uint32_t *val);
+
+/**
+ * @brief Set GPIO pin level
+ *
+ * Pin must be set up as output.
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @param pin Pin number, 0..7
+ * @param[out] val `true` if pin currently in high state
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23008_set_level(i2c_dev_t *dev, uint8_t pin, uint32_t val);
+
+/**
+ * @brief Setup interrupt for group of GPIO pins
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @param mask Pins to setup
+ * @param intr Interrupt mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23008_port_set_interrupt(i2c_dev_t *dev, uint8_t mask, mcp23008_gpio_intr_t intr);
+
+/**
+ * @brief Setup interrupt for GPIO pin
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @param pin Pin number, 0..7
+ * @param intr Interrupt mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23008_set_interrupt(i2c_dev_t *dev, uint8_t pin, mcp23008_gpio_intr_t intr);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __MCP23008_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp23x17/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,23 @@
+---
+components:
+  - name: mcp23x17
+    description: Driver for I2C/SPI 16 bit GPIO expanders MCP23017/MCP23S17
+    group: gpio
+    groups: []
+    code_owners: UncleRus
+    depends:
+      - driver
+      - i2cdev
+      - log
+      - esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2018
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp23x17/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS mcp23x17.c
+    INCLUDE_DIRS .
+    REQUIRES driver i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp23x17/Kconfig	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,11 @@
+menu "MCP23x17"
+
+	choice MCP23X17_IFACE
+		prompt "Chip type"
+		config MCP23X17_IFACE_I2C
+			bool "MCP23017 [I2C]"
+		config MCP23X17_IFACE_SPI
+			bool "MCP23S17 [SPI]"
+	endchoice
+
+endmenu
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp23x17/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+Copyright (c) 2018 Ruslan V. Uss (https://github.com/UncleRus)
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp23x17/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = driver i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp23x17/mcp23x17.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,483 @@
+/*
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file mcp23x17.c
+ *
+ * ESP-IDF driver for I2C/SPI 16 bit GPIO expanders MCP23017/MCP23S17
+ *
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+
+#include <esp_log.h>
+#include <string.h>
+#include <esp_idf_lib_helpers.h>
+#include "mcp23x17.h"
+
+static const char *TAG = "mcp23x17";
+
+#define I2C_FREQ_HZ 1000000  // Max 1MHz for esp-idf, but device supports up to 1.7Mhz
+
+#define REG_IODIRA   0x00
+#define REG_IODIRB   0x01
+#define REG_IPOLA    0x02
+#define REG_IPOLB    0x03
+#define REG_GPINTENA 0x04
+#define REG_GPINTENB 0x05
+#define REG_DEFVALA  0x06
+#define REG_DEFVALB  0x07
+#define REG_INTCONA  0x08
+#define REG_INTCONB  0x09
+#define REG_IOCON    0x0A
+#define REG_GPPUA    0x0C
+#define REG_GPPUB    0x0D
+#define REG_INTFA    0x0E
+#define REG_INTFB    0x0F
+#define REG_INTCAPA  0x10
+#define REG_INTCAPB  0x11
+#define REG_GPIOA    0x12
+#define REG_GPIOB    0x13
+#define REG_OLATA    0x14
+#define REG_OLATB    0x15
+
+#define BIT_IOCON_INTPOL 1
+#define BIT_IOCON_ODR    2
+#define BIT_IOCON_HAEN   3
+#define BIT_IOCON_DISSLW 4
+#define BIT_IOCON_SEQOP  5
+#define BIT_IOCON_MIRROR 6
+#define BIT_IOCON_BANK   7
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+#define BV(x) (1 << (x))
+
+#ifdef CONFIG_MCP23X17_IFACE_I2C
+
+static esp_err_t read_reg_16(mcp23x17_t *dev, uint8_t reg, uint16_t *val)
+{
+    CHECK_ARG(dev && val);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, reg, val, 2));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+static esp_err_t write_reg_16(mcp23x17_t *dev, uint8_t reg, uint16_t val)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, reg, &val, 2));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+static esp_err_t write_reg_bit_16(mcp23x17_t *dev, uint8_t reg, bool val, uint8_t bit)
+{
+    CHECK_ARG(dev);
+
+    uint16_t buf;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, reg, &buf, 2));
+    buf = (buf & ~BV(bit)) | (val ? BV(bit) : 0);
+    I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, reg, &buf, 2));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+static esp_err_t read_reg_bit_8(mcp23x17_t *dev, uint8_t reg, bool *val, uint8_t bit)
+{
+    CHECK_ARG(dev && val);
+
+    uint8_t buf;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, reg, &buf, 1));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    *val = (buf & BV(bit)) >> bit;
+
+    return ESP_OK;
+}
+
+static esp_err_t write_reg_bit_8(mcp23x17_t *dev, uint8_t reg, bool val, uint8_t bit)
+{
+    CHECK_ARG(dev);
+
+    uint8_t buf;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, reg, &buf, 1));
+    buf = (buf & ~BV(bit)) | (val ? BV(bit) : 0);
+    I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, reg, &buf, 1));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+#else
+
+static esp_err_t read_reg_16(mcp23x17_t *dev, uint8_t reg, uint16_t *val)
+{
+    CHECK_ARG(dev && val);
+
+    uint8_t rx[4] = { 0 };
+    uint8_t tx[4] = { (dev->addr << 1) | 0x01, reg, 0, 0 };
+
+    spi_transaction_t t;
+    memset(&t, 0, sizeof(spi_transaction_t));
+    t.rx_buffer = rx;
+    t.tx_buffer = tx;
+    t.length = 32;   // 32 bits
+
+    CHECK(spi_device_transmit(dev->spi_dev, &t));
+
+    *val = (rx[3] << 8) | rx[2];
+
+    return ESP_OK;
+}
+
+static esp_err_t write_reg_16(mcp23x17_t *dev, uint8_t reg, uint16_t val)
+{
+    CHECK_ARG(dev);
+
+    uint8_t tx[4] = { dev->addr << 1, reg, val, val >> 8 };
+
+    spi_transaction_t t;
+    memset(&t, 0, sizeof(spi_transaction_t));
+    t.tx_buffer = tx;
+    t.length = 32;   // 32 bits
+
+    CHECK(spi_device_transmit(dev->spi_dev, &t));
+
+    return ESP_OK;
+}
+
+static esp_err_t write_reg_bit_16(mcp23x17_t *dev, uint8_t reg, bool val, uint8_t bit)
+{
+    CHECK_ARG(dev);
+
+    uint16_t buf;
+
+    CHECK(read_reg_16(dev, reg, &buf));
+    return write_reg_16(dev, reg, (buf & ~BV(bit)) | (val ? BV(bit) : 0));
+}
+
+static esp_err_t read_reg_8(mcp23x17_t *dev, uint8_t reg, uint8_t *val)
+{
+    CHECK_ARG(dev && val);
+
+    uint8_t rx[3] = { 0 };
+    uint8_t tx[3] = { (dev->addr << 1) | 0x01, reg, 0 };
+
+    spi_transaction_t t;
+    memset(&t, 0, sizeof(spi_transaction_t));
+    t.rx_buffer = rx;
+    t.tx_buffer = tx;
+    t.length = 24;   // 24 bits
+
+    CHECK(spi_device_transmit(dev->spi_dev, &t));
+
+    *val = rx[2];
+
+    return ESP_OK;
+}
+
+static esp_err_t write_reg_8(mcp23x17_t *dev, uint8_t reg, uint8_t val)
+{
+    CHECK_ARG(dev);
+
+    uint8_t tx[3] = { dev->addr << 1, reg, val };
+
+    spi_transaction_t t;
+    memset(&t, 0, sizeof(spi_transaction_t));
+    t.tx_buffer = tx;
+    t.length = 24;   // 24 bits
+
+    CHECK(spi_device_transmit(dev->spi_dev, &t));
+
+    return ESP_OK;
+}
+
+
+static esp_err_t read_reg_bit_8(mcp23x17_t *dev, uint8_t reg, bool *val, uint8_t bit)
+{
+    CHECK_ARG(dev && val);
+
+    uint8_t buf;
+
+    CHECK(read_reg_8(dev, reg, &buf));
+    *val = (buf & BV(bit)) >> bit;
+
+    return ESP_OK;
+}
+
+static esp_err_t write_reg_bit_8(mcp23x17_t *dev, uint8_t reg, bool val, uint8_t bit)
+{
+    CHECK_ARG(dev);
+
+    uint8_t buf;
+
+    CHECK(read_reg_8(dev, reg, &buf));
+    return write_reg_8(dev, reg, (buf & ~BV(bit)) | (val ? BV(bit) : 0));
+}
+
+#endif
+
+static esp_err_t read_reg_bit_16(mcp23x17_t *dev, uint8_t reg, bool *val, uint8_t bit)
+{
+    uint16_t buf;
+
+    CHECK(read_reg_16(dev, reg, &buf));
+
+    *val = (buf & BV(bit)) >> bit;
+
+    return ESP_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef CONFIG_MCP23X17_IFACE_I2C
+
+esp_err_t mcp23x17_init_desc(mcp23x17_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+    if (addr < MCP23X17_ADDR_BASE || addr > MCP23X17_ADDR_BASE + 7)
+    {
+        ESP_LOGE(TAG, "Invalid device address: 0x%02x", addr);
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    dev->port = port;
+    dev->addr = addr;
+    dev->cfg.sda_io_num = sda_gpio;
+    dev->cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(dev);
+}
+
+esp_err_t mcp23x17_free_desc(mcp23x17_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(dev);
+}
+
+#else
+
+esp_err_t mcp23x17_init_desc_spi(mcp23x17_t *dev, spi_host_device_t host, uint32_t clock_speed_hz, uint8_t addr, gpio_num_t cs_pin)
+{
+    CHECK_ARG(dev);
+    if (addr < MCP23X17_ADDR_BASE || addr > MCP23X17_ADDR_BASE + 7)
+    {
+        ESP_LOGE(TAG, "Invalid device address: 0x%02x", addr);
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    dev->addr = addr;
+
+    memset(&dev->spi_cfg, 0, sizeof(dev->spi_cfg));
+    dev->spi_cfg.spics_io_num = cs_pin;
+    dev->spi_cfg.clock_speed_hz = clock_speed_hz;
+    dev->spi_cfg.mode = 0;
+    dev->spi_cfg.queue_size = 1;
+
+    return spi_bus_add_device(host, &dev->spi_cfg, &dev->spi_dev);
+}
+
+esp_err_t mcp23x17_free_desc(mcp23x17_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return spi_bus_remove_device(dev->spi_dev);
+}
+
+esp_err_t mcp23x17_setup_hw_addr(mcp23x17_t *dev, bool enable, uint8_t new_addr)
+{
+    CHECK(write_reg_bit_8(dev, REG_IOCON, enable, BIT_IOCON_HAEN));
+    dev->addr = enable ? new_addr : MCP23X17_ADDR_BASE;
+
+    return ESP_OK;
+}
+
+#endif
+
+esp_err_t mcp23x17_get_int_out_mode(mcp23x17_t *dev, mcp23x17_int_out_mode_t *mode)
+{
+    CHECK_ARG(mode);
+
+    bool buf;
+    CHECK(read_reg_bit_8(dev, REG_IOCON, &buf, BIT_IOCON_ODR));
+    if (buf)
+    {
+        *mode = MCP23X17_OPEN_DRAIN;
+        return ESP_OK;
+    }
+    CHECK(read_reg_bit_8(dev, REG_IOCON, &buf, BIT_IOCON_INTPOL));
+    *mode = buf ? MCP23X17_ACTIVE_HIGH : MCP23X17_ACTIVE_LOW;
+
+    return ESP_OK;
+}
+
+esp_err_t mcp23x17_set_int_out_mode(mcp23x17_t *dev, mcp23x17_int_out_mode_t mode)
+{
+    if (mode == MCP23X17_OPEN_DRAIN)
+        return write_reg_bit_8(dev, REG_IOCON, true, BIT_IOCON_ODR);
+
+    return write_reg_bit_8(dev, REG_IOCON, mode == MCP23X17_ACTIVE_HIGH, BIT_IOCON_INTPOL);
+}
+
+esp_err_t mcp23x17_port_get_mode(mcp23x17_t *dev, uint16_t *val)
+{
+    return read_reg_16(dev, REG_IODIRA, val);
+}
+
+esp_err_t mcp23x17_port_set_mode(mcp23x17_t *dev, uint16_t val)
+{
+    return write_reg_16(dev, REG_IODIRA, val);
+}
+
+esp_err_t mcp23x17_port_get_pullup(mcp23x17_t *dev, uint16_t *val)
+{
+    return read_reg_16(dev, REG_GPPUA, val);
+}
+
+esp_err_t mcp23x17_port_set_pullup(mcp23x17_t *dev, uint16_t val)
+{
+    return write_reg_16(dev, REG_GPPUA, val);
+}
+
+esp_err_t mcp23x17_port_read(mcp23x17_t *dev, uint16_t *val)
+{
+    return read_reg_16(dev, REG_GPIOA, val);
+}
+
+esp_err_t mcp23x17_port_write(mcp23x17_t *dev, uint16_t val)
+{
+    return write_reg_16(dev, REG_GPIOA, val);
+}
+
+esp_err_t mcp23x17_get_mode(mcp23x17_t *dev, uint8_t pin, mcp23x17_gpio_mode_t *mode)
+{
+    CHECK_ARG(mode);
+
+    bool buf;
+    CHECK(read_reg_bit_16(dev, REG_IODIRA, &buf, pin));
+    *mode = buf ? MCP23X17_GPIO_INPUT : MCP23X17_GPIO_OUTPUT;
+
+    return ESP_OK;
+}
+
+esp_err_t mcp23x17_set_mode(mcp23x17_t *dev, uint8_t pin, mcp23x17_gpio_mode_t mode)
+{
+    return write_reg_bit_16(dev, REG_IODIRA, mode, pin);
+}
+
+esp_err_t mcp23x17_get_pullup(mcp23x17_t *dev, uint8_t pin, bool *enable)
+{
+    return read_reg_bit_16(dev, REG_GPPUA, enable, pin);
+}
+
+esp_err_t mcp23x17_set_pullup(mcp23x17_t *dev, uint8_t pin, bool enable)
+{
+    return write_reg_bit_16(dev, REG_GPPUA, enable, pin);
+}
+
+esp_err_t mcp23x17_get_level(mcp23x17_t *dev, uint8_t pin, uint32_t *val)
+{
+    CHECK_ARG(val);
+
+    bool buf;
+    CHECK(read_reg_bit_16(dev, REG_GPIOA, &buf, pin));
+    *val = buf ? 1 : 0;
+
+    return ESP_OK;
+}
+
+esp_err_t mcp23x17_set_level(mcp23x17_t *dev, uint8_t pin, uint32_t val)
+{
+    return write_reg_bit_16(dev, REG_GPIOA, val, pin);
+}
+
+esp_err_t mcp23x17_port_set_interrupt(mcp23x17_t *dev, uint16_t mask, mcp23x17_gpio_intr_t intr)
+{
+    CHECK_ARG(dev);
+
+    uint16_t int_en;
+    CHECK(read_reg_16(dev, REG_GPINTENA, &int_en));
+
+    if (intr == MCP23X17_INT_DISABLED)
+    {
+        // disable interrupts
+        int_en &= ~mask;
+        CHECK(write_reg_16(dev, REG_GPINTENA, int_en));
+
+        return ESP_OK;
+    }
+
+    uint16_t int_con;
+    CHECK(read_reg_16(dev, REG_INTCONA, &int_con));
+
+    if (intr == MCP23X17_INT_ANY_EDGE)
+        int_con &= ~mask;
+    else
+    {
+        int_con |= mask;
+
+        uint16_t int_def;
+        CHECK(read_reg_16(dev, REG_DEFVALA, &int_def));
+        if (intr == MCP23X17_INT_LOW_EDGE)
+            int_def |= mask;
+        else
+            int_def &= ~mask;
+        CHECK(write_reg_16(dev, REG_DEFVALA, int_def));
+    }
+
+    CHECK(write_reg_16(dev, REG_INTCONA, int_con));
+
+    // enable interrupts
+    int_en |= mask;
+    CHECK(write_reg_16(dev, REG_GPINTENA, int_en));
+
+    return ESP_OK;
+}
+
+esp_err_t mcp23x17_set_interrupt(mcp23x17_t *dev, uint8_t pin, mcp23x17_gpio_intr_t intr)
+{
+    return mcp23x17_port_set_interrupt(dev, BV(pin), intr);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp23x17/mcp23x17.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,337 @@
+/*
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file mcp23x17.h
+ * @defgroup mcp23x17 mcp23x17
+ * @{
+ *
+ * ESP-IDF driver for I2C/SPI 16 bit GPIO expanders MCP23017/MCP23S17
+ *
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __MCP23X17_H__
+#define __MCP23X17_H__
+
+#include <stddef.h>
+#include <stdbool.h>
+#include <i2cdev.h>
+#include <driver/spi_master.h>
+#include <driver/gpio.h>
+#include <esp_err.h>
+
+#define MCP23X17_ADDR_BASE 0x20
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef CONFIG_MCP23X17_IFACE_I2C
+
+typedef i2c_dev_t mcp23x17_t;
+
+#else
+
+#define MCP23X17_MAX_SPI_FREQ SPI_MASTER_FREQ_10M // 10MHz
+
+typedef struct
+{
+    spi_device_interface_config_t spi_cfg;
+    spi_device_handle_t spi_dev;
+    uint8_t addr;
+} mcp23x17_t;
+
+#endif
+
+/**
+ * GPIO mode
+ */
+typedef enum
+{
+    MCP23X17_GPIO_OUTPUT = 0,
+    MCP23X17_GPIO_INPUT
+} mcp23x17_gpio_mode_t;
+
+/**
+ * INTA/INTB pins mode
+ */
+typedef enum
+{
+    MCP23X17_ACTIVE_LOW = 0, //!< Low level on interrupt
+    MCP23X17_ACTIVE_HIGH,    //!< High level on interrupt
+    MCP23X17_OPEN_DRAIN      //!< Open drain
+} mcp23x17_int_out_mode_t;
+
+/**
+ * Interrupt mode
+ */
+typedef enum
+{
+    MCP23X17_INT_DISABLED = 0, //!< No interrupt
+    MCP23X17_INT_LOW_EDGE,     //!< Interrupt on low edge
+    MCP23X17_INT_HIGH_EDGE,    //!< Interrupt on high edge
+    MCP23X17_INT_ANY_EDGE      //!< Interrupt on any edge
+} mcp23x17_gpio_intr_t;
+
+#ifdef CONFIG_MCP23X17_IFACE_I2C
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * Default SCL frequency is 1MHz.
+ *
+ * @param dev Pointer to device descriptor
+ * @param port I2C port number
+ * @param addr I2C address
+ * @param sda_gpio SDA GPIO
+ * @param scl_gpio SCL GPIO
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23x17_init_desc(mcp23x17_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Pointer to device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23x17_free_desc(mcp23x17_t *dev);
+
+#else
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev Pointer to device descriptor
+ * @param host SPI host
+ * @param clock_speed_hz SPI clock speed, Hz (max `MCP23X17_MAX_SPI_FREQ`)
+ * @param addr Device address
+ * @param cs_pin CS pin
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23x17_init_desc_spi(mcp23x17_t *dev, spi_host_device_t host, uint32_t clock_speed_hz, uint8_t addr, gpio_num_t cs_pin);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Pointer to device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23x17_free_desc_spi(mcp23x17_t *dev);
+
+#endif
+
+#ifdef CONFIG_MCP23X17_IFACE_SPI
+
+/**
+ * @brief Enable or disable hardware addressing (usage of pins A0..A2).
+ *
+ * Works only with MCP23S17.
+ * Warining! According to the datasheet, hardware addressing is disabled by default.
+ *
+ * @param dev Pointer to device descriptor
+ * @param enable `true` to enable hardware addressing
+ * @param new_addr New I2C address (`0b0100<A2><A1><A0>` after the addressing enabled).
+ *                 If `enable` is `false`, address will be set automatically
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23x17_setup_hw_addr(mcp23x17_t *dev, bool enable, uint8_t new_addr);
+
+#endif
+
+/**
+ * @brief Get INTA/INTB pins mode
+ *
+ * @param dev Pointer to device descriptor
+ * @param[out] mode Buffer to store mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23x17_get_int_out_mode(mcp23x17_t *dev, mcp23x17_int_out_mode_t *mode);
+
+/**
+ * @brief Set INTA/INTB pins mode
+ *
+ * @param dev Pointer to device descriptor
+ * @param mode INTA/INTB pins mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23x17_set_int_out_mode(mcp23x17_t *dev, mcp23x17_int_out_mode_t mode);
+
+/**
+ * @brief Get GPIO pins mode
+ *
+ * 0 - output, 1 - input for each bit in `val`
+ *
+ * @param dev Pointer to device descriptor
+ * @param[out] val Buffer to store mode, 0 bit for PORTA/GPIO0..15 bit for PORTB/GPIO7
+ * @return
+ */
+esp_err_t mcp23x17_port_get_mode(mcp23x17_t *dev, uint16_t *val);
+
+/**
+ * @brief Set GPIO pins mode
+ *
+ * 0 - output, 1 - input for each bit in `val`
+ *
+ * @param dev Pointer to device descriptor
+ * @param val Mode, 0 bit for PORTA/GPIO0..15 bit for PORTB/GPIO7
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23x17_port_set_mode(mcp23x17_t *dev, uint16_t val);
+
+/**
+ * @brief Get GPIO pullups status
+ *
+ * 0 - pullup disabled, 1 - pullup enabled for each bit in `val`
+ *
+ * @param dev Pointer to device descriptor
+ * @param[out] val Pullup status, 0 bit for PORTA/GPIO0..15 bit for PORTB/GPIO7
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23x17_port_get_pullup(mcp23x17_t *dev, uint16_t *val);
+
+/**
+ * @brief Set GPIO pullups status
+ *
+ * 0 - pullup disabled, 1 - pullup enabled for each bit in `val`
+ *
+ * @param dev Pointer to device descriptor
+ * @param val Pullup status, 0 bit for PORTA/GPIO0..15 bit for PORTB/GPIO7
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23x17_port_set_pullup(mcp23x17_t *dev, uint16_t val);
+
+/**
+ * @brief Read GPIO port value
+ *
+ * @param dev Pointer to device descriptor
+ * @param[out] val 16-bit GPIO port value, 0 bit for PORTA/GPIO0..15 bit for PORTB/GPIO7
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23x17_port_read(mcp23x17_t *dev, uint16_t *val);
+
+/**
+ * @brief Write value to GPIO port
+ *
+ * @param dev Pointer to device descriptor
+ * @param val GPIO port value, 0 bit for PORTA/GPIO0..15 bit for PORTB/GPIO7
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23x17_port_write(mcp23x17_t *dev, uint16_t val);
+
+/**
+ * @brief Get GPIO pin mode
+ *
+ * @param dev Pointer to device descriptor
+ * @param pin Pin number, 0 for PORTA/GPIO0..15 for PORTB/GPIO7
+ * @param[out] mode GPIO pin mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23x17_get_mode(mcp23x17_t *dev, uint8_t pin, mcp23x17_gpio_mode_t *mode);
+
+/**
+ * @brief Set GPIO pin mode
+ *
+ * @param dev Pointer to device descriptor
+ * @param pin Pin number, 0 for PORTA/GPIO0..15 for PORTB/GPIO7
+ * @param mode GPIO pin mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23x17_set_mode(mcp23x17_t *dev, uint8_t pin, mcp23x17_gpio_mode_t mode);
+
+/**
+ * @brief Get pullup mode of GPIO pin
+ *
+ * @param dev Pointer to device descriptor
+ * @param pin Pin number, 0 for PORTA/GPIO0..15 for PORTB/GPIO7
+ * @param[out] enable pullup mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23x17_get_pullup(mcp23x17_t *dev, uint8_t pin, bool *enable);
+
+/**
+ * @brief Set pullup mode of GPIO pin
+ *
+ * @param dev Pointer to device descriptor
+ * @param pin Pin number, 0 for PORTA/GPIO0..15 for PORTB/GPIO7
+ * @param enable `true` to enable pullup
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23x17_set_pullup(mcp23x17_t *dev, uint8_t pin, bool enable);
+
+/**
+ * @brief Read GPIO pin level
+ *
+ * @param dev Pointer to device descriptor
+ * @param pin Pin number, 0 for PORTA/GPIO0..15 for PORTB/GPIO7
+ * @param[out] val `true` if pin currently in high state
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23x17_get_level(mcp23x17_t *dev, uint8_t pin, uint32_t *val);
+
+/**
+ * @brief Set GPIO pin level
+ *
+ * Pin must be set up as output
+ *
+ * @param dev Pointer to device descriptor
+ * @param pin Pin number, 0 for PORTA/GPIO0..15 for PORTB/GPIO7
+ * @param[out] val `true` if pin currently in high state
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23x17_set_level(mcp23x17_t *dev, uint8_t pin, uint32_t val);
+
+/**
+ * @brief Setup interrupt for group of GPIO pins
+ *
+ * @param dev Pointer to device descriptor
+ * @param mask Pins to setup
+ * @param intr Interrupt mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23x17_port_set_interrupt(mcp23x17_t *dev, uint16_t mask, mcp23x17_gpio_intr_t intr);
+
+/**
+ * @brief Setup interrupt for GPIO pin
+ *
+ * @param dev Pointer to device descriptor
+ * @param pin Pin number, 0 for PORTA/GPIO0..15 for PORTB/GPIO7
+ * @param intr Interrupt mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp23x17_set_interrupt(mcp23x17_t *dev, uint8_t pin, mcp23x17_gpio_intr_t intr);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __MCP23X17_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp342x/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,23 @@
+---
+components:
+  - name: mcp342x
+    description: Driver for 18-Bit, delta-sigma ADC MCP3426/MCP3427/MCP3428
+    group: adc-dac
+    groups: []
+    code_owners: UncleRus
+    depends:
+      - i2cdev
+      - log
+      - esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2019
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp342x/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS mcp342x.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp342x/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp342x/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp342x/mcp342x.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,242 @@
+/*
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file mcp342x.c
+ *
+ * ESP-IDF driver for 18-Bit, multi-channel delta-sigma
+ * ADC MCP3426/MCP3427/MCP3428
+ *
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#include <esp_idf_lib_helpers.h>
+#include <esp_log.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include "mcp342x.h"
+
+#define I2C_FREQ_HZ 400000 // 400kHz
+
+static const char *TAG = "mcp342x";
+
+#define BV(x) (1 << (x))
+
+#define BIT_RDY   BV(7)
+#define BIT_MODE  BV(4)
+
+#define POS_CHAN 5
+#define POS_SR   2
+#define POS_GAIN 0
+
+#define MASK_VAL 3
+
+#define SIGN12 0xfffff000
+#define SIGN14 0xffffc000
+#define SIGN16 0xffff0000
+#define SIGN18 0xfffc0000
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+static const uint32_t sample_time[] = {
+    [MCP342X_RES_12] = 4167,
+    [MCP342X_RES_14] = 16667,
+    [MCP342X_RES_16] = 66667,
+    [MCP342X_RES_18] = 266667
+};
+
+static const float lsb[] = {
+    [MCP342X_RES_12] = 0.001,
+    [MCP342X_RES_14] = 0.00025,
+    [MCP342X_RES_16] = 0.0000625,
+    [MCP342X_RES_18] = 0.000015625
+};
+
+static const int gain_val[] = {
+    [MCP342X_GAIN1] = 1,
+    [MCP342X_GAIN2] = 2,
+    [MCP342X_GAIN4] = 4,
+    [MCP342X_GAIN8] = 8
+};
+
+static void get_cfg(mcp342x_t *dev, uint8_t reg)
+{
+    dev->mode = (reg & BIT_MODE) ? MCP342X_CONTINUOUS : MCP342X_ONESHOT;
+    dev->channel =    (reg >> POS_CHAN) & MASK_VAL;
+    dev->resolution = (reg >> POS_SR)   & MASK_VAL;
+    dev->gain =       (reg >> POS_GAIN) & MASK_VAL;
+}
+
+static inline uint8_t get_reg(mcp342x_t *dev)
+{
+    return (dev->mode == MCP342X_CONTINUOUS ? BIT_MODE : 0)
+        | ((dev->channel & MASK_VAL) << POS_CHAN)
+        | ((dev->resolution & MASK_VAL) << POS_SR)
+        | ((dev->gain & MASK_VAL) << POS_GAIN);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t mcp342x_init_desc(mcp342x_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev && addr >= MCP342X_ADDR_MIN && addr <= MCP342X_ADDR_MAX);
+
+    dev->i2c_dev.port = port;
+    dev->i2c_dev.addr = addr;
+    dev->i2c_dev.cfg.sda_io_num = sda_gpio;
+    dev->i2c_dev.cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(&dev->i2c_dev);
+}
+
+esp_err_t mcp342x_free_desc(mcp342x_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(&dev->i2c_dev);
+}
+
+esp_err_t mcp342x_get_data(mcp342x_t *dev, int32_t *data, bool *ready)
+{
+    CHECK_ARG(dev);
+
+    uint8_t buf[4] = { 0 };
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read(&dev->i2c_dev, NULL, 0, buf, sizeof(buf)));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    uint8_t reg = buf[(buf[3] & (MCP342X_RES_18 << POS_SR)) == (MCP342X_RES_18 << POS_SR) ? 3 : 2];
+    get_cfg(dev, reg);
+    if (ready) *ready = !(reg & BIT_RDY);
+
+    if (!data) return ESP_OK;
+
+    uint32_t r = 0;
+    switch (dev->resolution)
+    {
+        case MCP342X_RES_12:
+            r = (buf[0] << 8) | buf[1];
+            if (r & BV(11))
+                r |= SIGN12;
+            break;
+        case MCP342X_RES_14:
+            r = (buf[0] << 8) | buf[1];
+            if (r & BV(13))
+                r |= SIGN14;
+            break;
+        case MCP342X_RES_16:
+            r = (buf[0] << 8) | buf[1];
+            if (r & BV(15))
+                r |= SIGN16;
+            break;
+        case MCP342X_RES_18:
+            r = (buf[0] << 16) | (buf[1] << 8) | buf[2];
+            if (r & BV(17))
+                r |= SIGN18;
+            break;
+    }
+    *data = *((int32_t *)&r);
+
+    return ESP_OK;
+}
+
+esp_err_t mcp342x_get_voltage(mcp342x_t *dev, float *volts, bool *ready)
+{
+    CHECK_ARG(volts);
+
+    int32_t raw;
+    CHECK(mcp342x_get_data(dev, &raw, ready));
+    *volts = lsb[dev->resolution] * raw / gain_val[dev->gain];
+
+    return ESP_OK;
+}
+
+esp_err_t mcp342x_get_sample_time_us(mcp342x_t *dev, uint32_t *us)
+{
+    CHECK_ARG(dev && us && dev->resolution <= MCP342X_RES_18);
+
+    *us = sample_time[dev->resolution];
+    return ESP_OK;
+}
+
+esp_err_t mcp342x_set_config(mcp342x_t *dev)
+{
+    CHECK_ARG(dev);
+
+    uint8_t r = get_reg(dev);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_write(&dev->i2c_dev, NULL, 0, &r, 1));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t mcp342x_get_config(mcp342x_t *dev)
+{
+    return mcp342x_get_data(dev, NULL, NULL);
+}
+
+esp_err_t mcp342x_start_conversion(mcp342x_t *dev)
+{
+    CHECK_ARG(dev);
+
+    uint8_t r = get_reg(dev) | BIT_RDY;
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_write(&dev->i2c_dev, NULL, 0, &r, 1));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t mcp342x_oneshot_conversion(mcp342x_t *dev, int32_t *data)
+{
+    CHECK_ARG(dev && data);
+
+    dev->mode = MCP342X_ONESHOT;
+
+    uint32_t st;
+    CHECK(mcp342x_get_sample_time_us(dev, &st));
+    CHECK(mcp342x_start_conversion(dev));
+    vTaskDelay(pdMS_TO_TICKS(st / 1000 + 1));
+    bool ready;
+    CHECK(mcp342x_get_data(dev, data, &ready));
+    if (!ready)
+    {
+        ESP_LOGE(TAG, "Data not ready");
+        return ESP_FAIL;
+    }
+
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp342x/mcp342x.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file mcp342x.h
+ * @defgroup mcp342x mcp342x
+ * @{
+ *
+ * ESP-IDF driver for 18-Bit, multi-channel delta-sigma
+ * ADC MCP3426/MCP3427/MCP3428
+ *
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __MCP342X_H__
+#define __MCP342X_H__
+
+#include <stdbool.h>
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define MCP342X_ADDR_MIN 0x68
+#define MCP342X_ADDR_MAX 0x6f
+
+/**
+ * Device operation mode
+ */
+typedef enum {
+    MCP342X_ONESHOT = 0, //!< One-shot conversion mode
+    MCP342X_CONTINUOUS   //!< Continuous conversions mode, default
+} mcp342x_mode_t;
+
+/**
+ * Input channel
+ */
+typedef enum {
+    MCP342X_CHANNEL1 = 0, //!< Channel 1, default
+    MCP342X_CHANNEL2,     //!< Channel 2
+    MCP342X_CHANNEL3,     //!< Channel 3 (MCP3428 only, treated as channel 1 by the MCP3426/MCP3427)
+    MCP342X_CHANNEL4      //!< Channel 4 (MCP3428 only, treated as channel 2 by the MCP3426/MCP3427)
+} mcp342x_channel_t;
+
+/**
+ * Resolution
+ */
+typedef enum {
+    MCP342X_RES_12 = 0, //!< 12 bits, 240 samples per second
+    MCP342X_RES_14,     //!< 14 bits, 60 samples per second
+    MCP342X_RES_16,     //!< 16 bits, 15 samples per second
+    MCP342X_RES_18      //!< 18 bits, 3.75 samples per second
+} mcp342x_resolution_t;
+
+/**
+ * PGA gain
+ */
+typedef enum {
+    MCP342X_GAIN1 = 0,//!< x1, default
+    MCP342X_GAIN2,    //!< x2
+    MCP342X_GAIN4,    //!< x4
+    MCP342X_GAIN8     //!< x8
+} mcp342x_gain_t;
+
+/**
+ * Device descriptor
+ */
+typedef struct {
+    i2c_dev_t i2c_dev;               //!< I2C device descriptor
+    mcp342x_mode_t mode;             //!< Operational mode
+    mcp342x_channel_t channel;       //!< Input channel
+    mcp342x_resolution_t resolution; //!< Resolution
+    mcp342x_gain_t gain;             //!< PGA gain
+} mcp342x_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev Device descriptor
+ * @param port I2C port
+ * @param addr Device address
+ * @param sda_gpio SDA GPIO pin
+ * @param scl_gpio SCL GPIO pin
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp342x_init_desc(mcp342x_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp342x_free_desc(mcp342x_t *dev);
+
+/**
+ * @brief Configure device
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp342x_set_config(mcp342x_t *dev);
+
+/**
+ * @brief Read device configuration
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp342x_get_config(mcp342x_t *dev);
+
+/**
+ * @brief Get conversion time in microseconds
+ *
+ * @param dev Device descriptor
+ * @param[out] us Conversion time, us
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp342x_get_sample_time_us(mcp342x_t *dev, uint32_t *us);
+
+/**
+ * @brief Start conversion
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp342x_start_conversion(mcp342x_t *dev);
+
+/**
+ * @brief Get raw ADC value
+ *
+ * @param dev Device descriptor
+ * @param[out] data ADC value
+ * @param[out] ready Data validity flag
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp342x_get_data(mcp342x_t *dev, int32_t *data, bool *ready);
+
+/**
+ * @brief Get ADC voltage
+ *
+ * @param dev Device descriptor
+ * @param[out] volts ADC voltage, volts
+ * @param[out] ready Data validity flag
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp342x_get_voltage(mcp342x_t *dev, float *volts, bool *ready);
+
+/**
+ * @brief Do a single conversion
+ *
+ * - start conversion
+ * - wait conversion time
+ * - read conversion result
+ *
+ * @param dev Device descriptor
+ * @param[out] data ADC value
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp342x_oneshot_conversion(mcp342x_t *dev, int32_t *data);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __MCP342X_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp4725/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,23 @@
+---
+components:
+  - name: mcp4725
+    description: Driver for 12-bit DAC MCP4725
+    group: adc-dac
+    groups: []
+    code_owners: UncleRus
+    depends:
+      - i2cdev
+      - log
+      - esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2016
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp4725/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS mcp4725.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp4725/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+Copyright (c) 2016, 2019 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp4725/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp4725/mcp4725.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file mcp4725.c
+ *
+ * ESP-IDF Driver for 12-bit DAC MCP4725
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2016, 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+
+#include <esp_log.h>
+#include <esp_idf_lib_helpers.h>
+#include "mcp4725.h"
+
+static const char *TAG = "mcp4725";
+
+#define I2C_FREQ_HZ 1000000 // Max 1MHz for esp-idf, but device supports up to 3.4Mhz
+
+#define CMD_DAC    0x40
+#define CMD_EEPROM 0x60
+#define BIT_READY  0x80
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+static esp_err_t read_data(i2c_dev_t *dev, void *data, uint8_t size)
+{
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read(dev, NULL, 0, data, size));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t mcp4725_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+    if (addr < MCP4725A0_I2C_ADDR0 || addr > MCP4725A2_I2C_ADDR1)
+    {
+        ESP_LOGE(TAG, "Invalid device address: 0x%02x", addr);
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    dev->port = port;
+    dev->addr = addr;
+    dev->cfg.sda_io_num = sda_gpio;
+    dev->cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(dev);
+}
+
+esp_err_t mcp4725_free_desc(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(dev);
+}
+
+esp_err_t mcp4725_eeprom_busy(i2c_dev_t *dev, bool *busy)
+{
+    CHECK_ARG(dev && busy);
+
+    uint8_t res;
+    CHECK(read_data(dev, &res, 1));
+
+    *busy = !(res & BIT_READY);
+
+    return ESP_OK;
+}
+
+esp_err_t mcp4725_get_power_mode(i2c_dev_t *dev, bool eeprom, mcp4725_power_mode_t *mode)
+{
+    CHECK_ARG(dev && mode);
+
+    uint8_t buf[4];
+    CHECK(read_data(dev, buf, eeprom ? 4 : 1));
+
+    *mode = (eeprom ? buf[3] >> 5 : buf[0] >> 1) & 0x03;
+
+    return ESP_OK;
+}
+
+esp_err_t mcp4725_set_power_mode(i2c_dev_t *dev, bool eeprom, mcp4725_power_mode_t mode)
+{
+    CHECK_ARG(dev);
+
+    uint16_t value;
+    CHECK(mcp4725_get_raw_output(dev, eeprom, &value));
+
+    uint8_t data[] = {
+        (eeprom ? CMD_EEPROM : CMD_DAC) | (((uint8_t)mode & 3) << 1),
+        value >> 4,
+        value << 4
+    };
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_write(dev, NULL, 0, data, 3));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t mcp4725_get_raw_output(i2c_dev_t *dev, bool eeprom, uint16_t *value)
+{
+    CHECK_ARG(dev && value);
+
+    uint8_t buf[5];
+    CHECK(read_data(dev, buf, eeprom ? 5 : 3));
+
+    *value = eeprom
+        ? ((uint16_t)(buf[3] & 0x0f) << 8) | buf[4]
+        : ((uint16_t)buf[0] << 4) | (buf[1] >> 4);
+
+    return ESP_OK;
+}
+
+esp_err_t mcp4725_set_raw_output(i2c_dev_t *dev, uint16_t value, bool eeprom)
+{
+    CHECK_ARG(dev);
+
+    uint8_t data[] = {
+        (eeprom ? CMD_EEPROM : CMD_DAC),
+        value >> 4,
+        value << 4
+    };
+
+    ESP_LOGV(TAG, "Set output value to %u", value);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_write(dev, NULL, 0, data, 3));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t mcp4725_get_voltage(i2c_dev_t *dev, float vdd, bool eeprom, float *voltage)
+{
+    CHECK_ARG(voltage);
+
+    uint16_t value;
+    CHECK(mcp4725_get_raw_output(dev, eeprom, &value));
+
+    *voltage = vdd / MCP4725_MAX_VALUE * value;
+
+    return ESP_OK;
+}
+
+esp_err_t mcp4725_set_voltage(i2c_dev_t *dev, float vdd, float value, bool eeprom)
+{
+    return mcp4725_set_raw_output(dev, MCP4725_MAX_VALUE / vdd * value, eeprom);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp4725/mcp4725.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file mcp4725.h
+ * @defgroup mcp4725 mcp4725
+ * @{
+ *
+ * ESP-IDF Driver for 12-bit DAC MCP4725
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __MCP4725_H__
+#define __MCP4725_H__
+
+#include <stdbool.h>
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define MCP4725A0_I2C_ADDR0 0x60
+#define MCP4725A0_I2C_ADDR1 0x61
+#define MCP4725A1_I2C_ADDR0 0x62
+#define MCP4725A1_I2C_ADDR1 0x63
+#define MCP4725A2_I2C_ADDR0 0x64
+#define MCP4725A2_I2C_ADDR1 0x65
+
+#define MCP4725_MAX_VALUE 0x0fff
+
+/**
+ * Power mode, see datasheet
+ */
+typedef enum
+{
+    MCP4725_PM_NORMAL = 0,   //!< Normal mode
+    MCP4725_PM_PD_1K,        //!< Power down, 1kOhm resistor to ground
+    MCP4725_PM_PD_100K,      //!< Power down, 100kOhm resistor to ground
+    MCP4725_PM_PD_500K,      //!< Power down, 500kOhm resistor to ground
+} mcp4725_power_mode_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * Default SCL frequency is 1MHz
+ *
+ * @param dev I2C device descriptor
+ * @param port I2C port number
+ * @param addr I2C address,
+ * @param sda_gpio SDA GPIO
+ * @param scl_gpio SCL GPIO
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp4725_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev I2C device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp4725_free_desc(i2c_dev_t *dev);
+
+/**
+ * @brief Get device EEPROM status
+ *
+ * @param dev I2C device descriptor
+ * @param busy true when EEPROM is busy
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp4725_eeprom_busy(i2c_dev_t *dev, bool *busy);
+
+/**
+ * @brief Get power mode
+ *
+ * @param dev I2C device descriptor
+ * @param eeprom Read power mode from EEPROM if true
+ * @param[out] mode Power mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp4725_get_power_mode(i2c_dev_t *dev, bool eeprom, mcp4725_power_mode_t *mode);
+
+/**
+ * @brief Set power mode
+ *
+ * @param dev I2C device descriptor
+ * @param mode Power mode
+ * @param eeprom Store mode to device EEPROM if true
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp4725_set_power_mode(i2c_dev_t *dev, bool eeprom, mcp4725_power_mode_t mode);
+
+/**
+ * @brief Get current DAC value
+ *
+ * @param dev I2C device descriptor
+ * @param eeprom Read value from device EEPROM if true
+ * @param[out] value Raw output value, 0..4095
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp4725_get_raw_output(i2c_dev_t *dev, bool eeprom, uint16_t *value);
+
+/**
+ * @brief Set DAC output value
+ *
+ * @param dev I2C device descriptor
+ * @param value Raw output value, 0..4095
+ * @param eeprom Store value to device EEPROM if true
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp4725_set_raw_output(i2c_dev_t *dev, uint16_t value, bool eeprom);
+
+/**
+ * @brief Get current DAC output voltage
+ *
+ * @param dev I2C device descriptor
+ * @param vdd Device operating voltage, volts
+ * @param eeprom Read voltage from device EEPROM if true
+ * @param[out] voltage Current output voltage, volts
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp4725_get_voltage(i2c_dev_t *dev, float vdd, bool eeprom, float *voltage);
+
+/**
+ * @brief Set DAC output voltage
+ *
+ * @param dev I2C device descriptor
+ * @param vdd Device operating voltage, volts
+ * @param value Output value, volts
+ * @param eeprom Store value to device EEPROM if true
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp4725_set_voltage(i2c_dev_t *dev, float vdd, float value, bool eeprom);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __MCP4725_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp960x/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,23 @@
+---
+components:
+  - name: mcp960x
+    description: Driver for MCP9600/MCP9601, thermocouple EMF to temperature converter
+    group: temperature
+    groups: []
+    code_owners: UncleRus
+    depends:
+      - i2cdev
+      - log
+      - esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2020
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp960x/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS mcp960x.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp960x/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+Copyright 2020 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp960x/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp960x/mcp960x.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,430 @@
+/*
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file mcp960x.c
+ *
+ * ESP-IDF driver for MCP960X/L0X/RL0X
+ * Thermocouple EMF to Temperature Converter
+ *
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#include <esp_log.h>
+#include <esp_idf_lib_helpers.h>
+#include "mcp960x.h"
+
+#define I2C_FREQ_HZ 100000 // 100 KHz
+
+static const char *TAG = "mcp960x";
+
+#define REG_T_HOT      0x00
+#define REG_T_DELTA    0x01
+#define REG_T_COLD     0x02
+#define REG_RAW        0x03
+#define REG_STATUS     0x04
+#define REG_SENS_CONF  0x05
+#define REG_DEV_CONF   0x06
+#define REG_ALERT_CONF 0x08
+#define REG_ALERT_HYST 0x0c
+#define REG_ALERT_LIM  0x10
+#define REG_DEV_ID     0x20
+
+#define BIT_ST_ALERT1 0
+#define BIT_ST_ALERT2 1
+#define BIT_ST_ALERT3 2
+#define BIT_ST_ALERT4 3
+#define BIT_ST_OC     4
+#define BIT_ST_SC     5
+#define BIT_ST_TH     6
+#define BIT_ST_BURST  7
+
+#define MASK_ST_MODE    (3 << BIT_ST_OC)
+
+#define BIT_AC_OUT_EN   0
+#define BIT_AC_COMP_INT 1
+#define BIT_AC_ACT_LVL  2
+#define BIT_AC_T_DIR    3
+#define BIT_AC_T_SRC    4
+#define BIT_AC_INT_CLR  7
+
+#define BIT_SC_FILTER  0
+#define BIT_SC_TC_TYPE 4
+
+#define MASK_SC_FILTER  7
+#define MASK_SC_TC_TYPE (7 << BIT_SC_TC_TYPE)
+
+#define BIT_DC_MODE     0
+#define BIT_DC_SAMPLES  2
+#define BIT_DC_ADC_RES  5
+#define BIT_DC_SENS_RES 7
+
+#define MASK_DC_MODE    (3 << BIT_DC_MODE)
+#define MASK_DC_SAMPLES (7 << BIT_DC_SAMPLES)
+#define MASK_DC_ADC_RES (3 << BIT_DC_ADC_RES)
+
+#define BV(x) (1 << (x))
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+#define LSB 0.0625
+
+inline static uint16_t shuffle(uint16_t x)
+{
+    return (x >> 8) | (x << 8);
+}
+
+inline static esp_err_t write_reg_8(mcp960x_t *dev, uint8_t reg, uint8_t val)
+{
+    return i2c_dev_write_reg(&dev->i2c_dev, reg, &val, 1);
+}
+
+inline static esp_err_t read_reg_8(mcp960x_t *dev, uint8_t reg, uint8_t *val)
+{
+    return i2c_dev_read_reg(&dev->i2c_dev, reg, val, 1);
+}
+
+static esp_err_t read_reg_8_lock(mcp960x_t *dev, uint8_t reg, uint8_t *val)
+{
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, read_reg_8(dev, reg, val));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+static esp_err_t write_reg_8_lock(mcp960x_t *dev, uint8_t reg, uint8_t val)
+{
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, write_reg_8(dev, reg, val));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+static esp_err_t write_reg_16(mcp960x_t *dev, uint8_t reg, uint16_t val)
+{
+    uint16_t v = shuffle(val);
+    return i2c_dev_write_reg(&dev->i2c_dev, reg, &v, 2);
+}
+
+static esp_err_t read_reg_16(mcp960x_t *dev, uint8_t reg, uint16_t *val)
+{
+    CHECK(i2c_dev_read_reg(&dev->i2c_dev, reg, val, 2));
+    *val = shuffle(*val);
+    return ESP_OK;
+}
+
+inline static esp_err_t write_reg_16_float(mcp960x_t *dev, uint8_t reg, float val)
+{
+    return write_reg_16(dev, reg, (uint16_t)(val / LSB));
+}
+
+static esp_err_t read_reg_16_float(mcp960x_t *dev, uint8_t reg, float *val)
+{
+    uint16_t raw;
+    CHECK(read_reg_16(dev, reg, &raw));
+    *val = (float)raw * LSB;
+    return ESP_OK;
+}
+
+static esp_err_t read_reg_16_float_lock(mcp960x_t *dev, uint8_t reg, float *val)
+{
+    CHECK_ARG(dev && val);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, read_reg_16_float(dev, reg, val));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t mcp960x_init_desc(mcp960x_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+
+    if (addr < (MCP960X_ADDR_BASE) || addr > (MCP960X_ADDR_DEFAULT))
+    {
+        ESP_LOGE(TAG, "Invalid I2C address");
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    dev->i2c_dev.port = port;
+    dev->i2c_dev.addr = addr;
+    dev->i2c_dev.cfg.sda_io_num = sda_gpio;
+    dev->i2c_dev.cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    // Min. stretch time ~70us
+    dev->i2c_dev.timeout_ticks = I2CDEV_MAX_STRETCH_TIME;
+
+    return i2c_dev_create_mutex(&dev->i2c_dev);
+}
+
+esp_err_t mcp960x_free_desc(mcp960x_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(&dev->i2c_dev);
+}
+
+esp_err_t mcp960x_init(mcp960x_t *dev)
+{
+    CHECK_ARG(dev);
+
+    uint16_t r;
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, read_reg_16(dev, REG_DEV_ID, &r));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    dev->revision = r;
+    dev->id = r >> 8;
+
+    ESP_LOGD(TAG, "Device found, ID:Rev 0x%02x:0x%02x", dev->id, dev->revision);
+
+    return ESP_OK;
+}
+
+esp_err_t mcp960x_set_sensor_config(mcp960x_t *dev, mcp960x_thermocouple_t th, uint8_t filter)
+{
+    CHECK_ARG(dev && th <= MCP960X_TYPE_R && filter <= MCP960X_FILTER_MAX);
+
+    return write_reg_8_lock(dev, REG_SENS_CONF, (th << BIT_SC_TC_TYPE) | filter);
+}
+
+esp_err_t mcp960x_get_sensor_config(mcp960x_t *dev, mcp960x_thermocouple_t *th, uint8_t *filter)
+{
+    CHECK_ARG(dev && (th || filter));
+
+    uint8_t r;
+    CHECK(read_reg_8_lock(dev, REG_SENS_CONF, &r));
+
+    if (th)
+        *th = (r & MASK_SC_TC_TYPE) >> BIT_SC_TC_TYPE;
+    if (filter)
+        *filter = (r & MASK_SC_FILTER) >> BIT_SC_FILTER;
+
+    return ESP_OK;
+}
+
+esp_err_t mcp960x_set_device_config(mcp960x_t *dev, mcp960x_mode_t mode, mcp960x_burst_samples_t bs,
+        mcp960x_adc_resolution_t adc_res, mcp960x_tc_resolution_t tc_res)
+{
+    CHECK_ARG(dev
+            && mode <= MCP960X_MODE_BURST
+            && bs <= MCP960X_SAMPLES_128
+            && adc_res <= MCP960X_ADC_RES_12
+            && tc_res <= MCP960X_TC_RES_0_25);
+
+    return write_reg_8_lock(dev, REG_DEV_CONF,
+            (mode << BIT_DC_MODE) |
+            (bs << BIT_DC_SAMPLES) |
+            (adc_res << BIT_DC_ADC_RES) |
+            (tc_res << BIT_DC_SENS_RES));
+}
+
+esp_err_t mcp960x_get_device_config(mcp960x_t *dev, mcp960x_mode_t *mode, mcp960x_burst_samples_t *bs,
+        mcp960x_adc_resolution_t *adc_res, mcp960x_tc_resolution_t *tc_res)
+{
+    CHECK_ARG(dev && (mode || bs || adc_res || tc_res));
+
+    uint8_t r;
+    CHECK(read_reg_8_lock(dev, REG_SENS_CONF, &r));
+
+    if (mode)
+        *mode = (r & MASK_DC_MODE) >> BIT_DC_MODE;
+    if (bs)
+        *bs = (r & MASK_DC_SAMPLES) >> BIT_DC_SAMPLES;
+    if (adc_res)
+        *adc_res = (r & MASK_DC_ADC_RES) >> BIT_DC_ADC_RES;
+    if (tc_res)
+        *tc_res = (r >> BIT_DC_SENS_RES) & 1;
+
+    return ESP_OK;
+}
+
+esp_err_t mcp960x_set_mode(mcp960x_t *dev, mcp960x_mode_t mode)
+{
+    CHECK_ARG(dev && mode <= MCP960X_MODE_BURST);
+
+    uint8_t r;
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, read_reg_8(dev, REG_DEV_CONF, &r));
+    r = (r & ~MASK_DC_MODE) | (mode << BIT_DC_MODE);
+    I2C_DEV_CHECK(&dev->i2c_dev, write_reg_8(dev, REG_DEV_CONF, r));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t mcp960x_get_raw_adc_data(mcp960x_t *dev, int32_t *data)
+{
+    CHECK_ARG(dev && data);
+
+    uint8_t raw[3];
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, REG_RAW, raw, 3));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    uint32_t v = ((uint32_t)raw[0] << 16) | ((uint32_t)raw[1] << 8) | raw[2];
+    if (v & 0x800000) v |= 0xff000000;
+    *((uint32_t *)data) = v;
+
+    return ESP_OK;
+}
+
+esp_err_t mcp960x_get_thermocouple_temp(mcp960x_t *dev, float *t)
+{
+    return read_reg_16_float_lock(dev, REG_T_HOT, t);
+}
+
+esp_err_t mcp960x_get_delta_temp(mcp960x_t *dev, float *t)
+{
+    return read_reg_16_float_lock(dev, REG_T_DELTA, t);
+}
+
+esp_err_t mcp960x_get_ambient_temp(mcp960x_t *dev, float *t)
+{
+    return read_reg_16_float_lock(dev, REG_T_COLD, t);
+}
+
+esp_err_t mcp960x_get_status(mcp960x_t *dev, bool *temp_ready, bool *burst_ready, mcp960x_status_t *status,
+        bool *alert1, bool *alert2, bool *alert3, bool *alert4)
+{
+    CHECK_ARG(dev && (temp_ready || burst_ready || status || alert1 || alert2 || alert3 || alert4));
+
+    uint8_t r;
+    CHECK(read_reg_8_lock(dev, REG_STATUS, &r));
+
+    if (temp_ready)
+        *temp_ready = (r >> BIT_ST_TH) & 1;
+    if (burst_ready)
+        *burst_ready = (r >> BIT_ST_BURST) & 1;
+    if (status)
+       *status = (r & MASK_ST_MODE) >> BIT_ST_OC;
+    if (alert1)
+        *alert1 = (r >> BIT_ST_ALERT1) & 1;
+    if (alert2)
+        *alert2 = (r >> BIT_ST_ALERT2) & 1;
+    if (alert3)
+        *alert3 = (r >> BIT_ST_ALERT3) & 1;
+    if (alert4)
+        *alert4 = (r >> BIT_ST_ALERT4) & 1;
+
+    return ESP_OK;
+}
+
+esp_err_t mcp960x_set_alert_config(mcp960x_t *dev, mcp960x_alert_t alert, mcp960x_alert_mode_t mode,
+        mcp960x_alert_level_t active_lvl, mcp960x_alert_temp_dir_t temp_dir, mcp960x_alert_source_t src,
+        float limit, uint8_t hyst)
+{
+    CHECK_ARG(dev
+            && alert <= MCP960X_ALERT_4
+            && mode <= MCP960X_ALERT_INT
+            && active_lvl <= MCP960X_ACTIVE_HIGH
+            && temp_dir <= MCP960X_RISING
+            && src <= MCP960X_ALERT_SRC_TC);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, write_reg_8(dev, REG_ALERT_CONF + alert,
+            (mode > MCP960X_ALERT_DISABLED ? BV(BIT_AC_OUT_EN) : 0) |
+            (mode > MCP960X_ALERT_DISABLED ? (mode - 1) << BIT_AC_COMP_INT : 0) |
+            (active_lvl << BIT_AC_ACT_LVL) |
+            (temp_dir << BIT_AC_T_DIR) |
+            (src << BIT_AC_T_SRC)));
+    I2C_DEV_CHECK(&dev->i2c_dev, write_reg_8(dev, REG_ALERT_HYST + alert, hyst));
+    I2C_DEV_CHECK(&dev->i2c_dev, write_reg_16_float(dev, REG_ALERT_LIM + alert, limit));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t mcp960x_get_alert_config(mcp960x_t *dev, mcp960x_alert_t alert, mcp960x_alert_mode_t *mode,
+        mcp960x_alert_level_t *active_lvl, mcp960x_alert_temp_dir_t *temp_dir, mcp960x_alert_source_t *src,
+        float *limit, uint8_t *hyst)
+{
+    CHECK_ARG(dev && alert <= MCP960X_ALERT_4
+            && (mode || active_lvl || temp_dir || src || limit || hyst));
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    if (mode || active_lvl || temp_dir || src)
+    {
+        uint8_t v;
+        I2C_DEV_CHECK(&dev->i2c_dev, read_reg_8(dev, REG_ALERT_CONF + alert, &v));
+        if (mode)
+            *mode = v & BV(BIT_AC_OUT_EN) ? ((v >> BIT_AC_COMP_INT) & 1) + 1 : 0;
+        if (active_lvl)
+            *active_lvl = (v >> BIT_AC_ACT_LVL) & 1;
+        if (temp_dir)
+            *temp_dir = (v >> BIT_AC_T_DIR) & 1;
+        if (src)
+            *src = (v >> BIT_AC_T_SRC) & 1;
+    }
+    if (limit)
+        I2C_DEV_CHECK(&dev->i2c_dev, read_reg_16_float(dev, REG_ALERT_LIM + alert, limit));
+    if (hyst)
+        I2C_DEV_CHECK(&dev->i2c_dev, read_reg_8(dev, REG_ALERT_LIM + alert, hyst));
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t mcp960x_get_alert_status(mcp960x_t *dev, mcp960x_alert_t alert, bool *status)
+{
+    CHECK_ARG(dev && alert <= MCP960X_ALERT_4 && status);
+
+    uint8_t r;
+    CHECK(read_reg_8_lock(dev, REG_STATUS, &r));
+    *status = (r >> (BIT_ST_ALERT1 + alert)) & 1;
+
+    return ESP_OK;
+}
+
+esp_err_t mcp960x_clear_alert_int(mcp960x_t *dev, mcp960x_alert_t alert)
+{
+    CHECK_ARG(dev && alert <= MCP960X_ALERT_4);
+
+    uint8_t r;
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, read_reg_8(dev, REG_ALERT_CONF + alert, &r));
+    I2C_DEV_CHECK(&dev->i2c_dev, write_reg_8(dev, REG_ALERT_CONF + alert, r | BV(BIT_AC_INT_CLR)));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp960x/mcp960x.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,381 @@
+/*
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file mcp960x.h
+ * @defgroup mcp960x mcp960x
+ * @{
+ *
+ * ESP-IDF driver for MCP960X/L0X/RL0X
+ * Thermocouple EMF to Temperature Converter
+ *
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __MCP960X_H__
+#define __MCP960X_H__
+
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Basic I2C address. Device support 8 addresses, ranging from 0x60 to 0x67.
+ */
+#define MCP960X_ADDR_BASE     0x60
+/**
+ * Default I2C address (ADDR pin is connected to VDD or floating).
+ */
+#define MCP960X_ADDR_DEFAULT  0x67
+
+#define MCP960X_FILTER_OFF 0
+#define MCP960X_FILTER_MAX 7
+
+/**
+ * Thermocouple type
+ */
+typedef enum {
+    MCP960X_TYPE_K = 0, /**< default */
+    MCP960X_TYPE_J,
+    MCP960X_TYPE_T,
+    MCP960X_TYPE_N,
+    MCP960X_TYPE_S,
+    MCP960X_TYPE_E,
+    MCP960X_TYPE_B,
+    MCP960X_TYPE_R,
+} mcp960x_thermocouple_t;
+
+/**
+ * ADC measurement resolution
+ */
+typedef enum {
+    MCP960X_ADC_RES_18 = 0, /**< 18 bits / 2 uV, 320 ms, default */
+    MCP960X_ADC_RES_16,     /**< 16 bits / 8 uV, 80 ms */
+    MCP960X_ADC_RES_14,     /**< 14 bits / 32 uV, 20 ms */
+    MCP960X_ADC_RES_12,     /**< 12 bits / 128 uV, 5 ms */
+} mcp960x_adc_resolution_t;
+
+/**
+ * Number of temperature samples in burst mode
+ */
+typedef enum {
+    MCP960X_SAMPLES_1 = 0, /**< default */
+    MCP960X_SAMPLES_2,
+    MCP960X_SAMPLES_4,
+    MCP960X_SAMPLES_8,
+    MCP960X_SAMPLES_16,
+    MCP960X_SAMPLES_32,
+    MCP960X_SAMPLES_64,
+    MCP960X_SAMPLES_128,
+} mcp960x_burst_samples_t;
+
+/**
+ * Operational mode
+ */
+typedef enum {
+    MCP960X_MODE_NORMAL = 0, /**< default */
+    MCP960X_MODE_SHUTDOWN,
+    MCP960X_MODE_BURST,
+} mcp960x_mode_t;
+
+/**
+ * Cold-Junction/Ambient sensor resolution
+ */
+typedef enum {
+    MCP960X_TC_RES_0_0625 = 0, /**< 0.0625°C, conversion time: 250 ms */
+    MCP960X_TC_RES_0_25,       /**< 0.25°C, conversion time: 63 ms */
+} mcp960x_tc_resolution_t;
+
+/**
+ * Device status
+ */
+typedef enum {
+    MCP960X_OK = 0,        /**< Device is functioning normally */
+    MCP960X_OPEN_CIRCUIT,  /**< Open circuit detected on thermocouple input (MCP9601 only) */
+    MCP960X_SHORT_CIRCUIT, /**< Short circuit detected on thermocouple input (MCP9601 only) */
+} mcp960x_status_t;
+
+/**
+ * Alert
+ */
+typedef enum {
+    MCP960X_ALERT_1 = 0,
+    MCP960X_ALERT_2,
+    MCP960X_ALERT_3,
+    MCP960X_ALERT_4,
+} mcp960x_alert_t;
+
+/**
+ * Alert mode
+ */
+typedef enum {
+    MCP960X_ALERT_DISABLED, /**< Alert disabled */
+    MCP960X_ALERT_COMP,     /**< Comparator mode */
+    MCP960X_ALERT_INT,      /**< Interrupt mode */
+} mcp960x_alert_mode_t;
+
+/**
+ * Alert output active level
+ */
+typedef enum {
+    MCP960X_ACTIVE_LOW = 0, /**< Active-low */
+    MCP960X_ACTIVE_HIGH     /**< Active-high */
+} mcp960x_alert_level_t;
+
+/**
+ * Alert temperature direction
+ */
+typedef enum {
+    MCP960X_FALLING = 0, /**< Alert limit for falling or cooling temperatures */
+    MCP960X_RISING       /**< Alert limit for rising or heating temperatures */
+} mcp960x_alert_temp_dir_t;
+
+/**
+ * Alert monitoring source
+ */
+typedef enum {
+    MCP960X_ALERT_SRC_TH = 0, /**< Alert monitor for thermocouple temperature TH */
+    MCP960X_ALERT_SRC_TC      /**< Alert monitor for cold-junction sensor TC */
+} mcp960x_alert_source_t;
+
+/**
+ * Device descriptor
+ */
+typedef struct
+{
+    i2c_dev_t i2c_dev; /**< I2C device descriptor */
+    uint8_t id;        /**< Hardware ID */
+    uint8_t revision;  /**< Hardware revision */
+} mcp960x_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev Device descriptor
+ * @param addr Device I2C address
+ * @param port I2C port
+ * @param sda_gpio SDA GPIO
+ * @param scl_gpio SCL GPIO
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp960x_init_desc(mcp960x_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp960x_free_desc(mcp960x_t *dev);
+
+/**
+ * @brief Init device
+ *
+ * Read device HW ID and revision
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp960x_init(mcp960x_t *dev);
+
+/**
+ * @brief Setup thermocouple parameters
+ *
+ * @param dev Device descriptor
+ * @param th Thermocouple type
+ * @param filter Digital filtering level, 0..MCP960X_FILTER_MAX
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp960x_set_sensor_config(mcp960x_t *dev, mcp960x_thermocouple_t th, uint8_t filter);
+
+/**
+ * @brief Get thermocouple parameters
+ *
+ * @param dev Device descriptor
+ * @param[out] th Thermocouple type, NULL-able
+ * @param[out] filter Digital filtering level, 0..MCP960X_FILTER_MAX, NULL-able
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp960x_get_sensor_config(mcp960x_t *dev, mcp960x_thermocouple_t *th, uint8_t *filter);
+
+/**
+ * @brief Set device configuration
+ *
+ * @param dev Device descriptor
+ * @param mode Operation mode
+ * @param bs Number of temperature samples in burst mode
+ * @param adc_res ADC measurement resolution
+ * @param tc_res Cold-Junction/Ambient sensor resolution
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp960x_set_device_config(mcp960x_t *dev, mcp960x_mode_t mode, mcp960x_burst_samples_t bs,
+        mcp960x_adc_resolution_t adc_res, mcp960x_tc_resolution_t tc_res);
+
+/**
+ * @brief Get device configuration
+ *
+ * @param dev Device descriptor
+ * @param[out] mode Operation mode, NULL-able
+ * @param[out] bs Number of temperature samples in burst mode, NULL-able
+ * @param[out] adc_res ADC measurement resolution, NULL-able
+ * @param[out] tc_res Cold-Junction/Ambient sensor resolution, NULL-able
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp960x_get_device_config(mcp960x_t *dev, mcp960x_mode_t *mode, mcp960x_burst_samples_t *bs,
+        mcp960x_adc_resolution_t *adc_res, mcp960x_tc_resolution_t *tc_res);
+
+/**
+ * @brief Switch device operation mode
+ *
+ * @param dev Device descriptor
+ * @param mode Operation mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp960x_set_mode(mcp960x_t *dev, mcp960x_mode_t mode);
+
+/**
+ * @brief Get raw ADC data
+ *
+ * @param dev Device descriptor
+ * @param[out] data Raw ADC data
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp960x_get_raw_adc_data(mcp960x_t *dev, int32_t *data);
+
+/**
+ * @brief Get thermocouple temperature
+ *
+ * Returns cold-junction compensated and error-corrected
+ * thermocouple temperature
+ *
+ * @param dev Device descriptor
+ * @param[out] t Temperature, degrees Celsius
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp960x_get_thermocouple_temp(mcp960x_t *dev, float *t);
+
+/**
+ * @brief Get thermocouple junctions delta temperature
+ *
+ * Returns error corrected thermocouple hot-junction temperature without the
+ * cold-junction compensation
+ *
+ * @param dev Device descriptor
+ * @param[out] t Temperature, degrees Celsius
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp960x_get_delta_temp(mcp960x_t *dev, float *t);
+
+/**
+ * @brief Get cold-junction/ambient temperature
+ *
+ * @param dev Device descriptor
+ * @param[out] t Temperature, degrees Celsius
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp960x_get_ambient_temp(mcp960x_t *dev, float *t);
+
+/**
+ * @brief Get device status
+ *
+ * @param dev Device descriptor
+ * @param[out] temp_ready Temperature conversion complete if true, NULL-able
+ * @param[out] burst_ready Burst conversion complete if true, NULL-able (burst mode only)
+ * @param[out] status Device status, NULL-able
+ * @param[out] alert1 Alert 1 status, NULL-able
+ * @param[out] alert2 Alert 2 status, NULL-able
+ * @param[out] alert3 Alert 3 status, NULL-able
+ * @param[out] alert4 Alert 4 status, NULL-able
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp960x_get_status(mcp960x_t *dev, bool *temp_ready, bool *burst_ready, mcp960x_status_t *status,
+        bool *alert1, bool *alert2, bool *alert3, bool *alert4);
+
+/**
+ * @brief Setup temperature alert
+ *
+ * @param dev Device descriptor
+ * @param alert Alert index
+ * @param mode Alert mode
+ * @param active_lvl Alert output active level
+ * @param temp_dir Alert temperature direction
+ * @param src Alert temperature source
+ * @param limit Alert limit
+ * @param hyst Hysteresis
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp960x_set_alert_config(mcp960x_t *dev, mcp960x_alert_t alert, mcp960x_alert_mode_t mode,
+        mcp960x_alert_level_t active_lvl, mcp960x_alert_temp_dir_t temp_dir, mcp960x_alert_source_t src,
+        float limit, uint8_t hyst);
+
+/**
+ * @brief Get temperature alert configuration
+ *
+ * @param dev Device descriptor
+ * @param alert Alert index
+ * @param[out] mode Alert mode, NULL-able
+ * @param[out] active_lvl Alert output active level, NULL-able
+ * @param[out] temp_dir Alert temperature direction, NULL-able
+ * @param[out] src Alert temperature source, NULL-able
+ * @param[out] limit Alert limit, NULL-able
+ * @param[out] hyst Hysteresis, NULL-able
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp960x_get_alert_config(mcp960x_t *dev, mcp960x_alert_t alert, mcp960x_alert_mode_t *mode,
+        mcp960x_alert_level_t *active_lvl, mcp960x_alert_temp_dir_t *temp_dir, mcp960x_alert_source_t *src,
+        float *limit, uint8_t *hyst);
+
+/**
+ * @brief Get alert status
+ *
+ * @param dev Device descriptor
+ * @param alert Alert index
+ * @param[out] status Alert status
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp960x_get_alert_status(mcp960x_t *dev, mcp960x_alert_t alert, bool *status);
+
+/**
+ * @brief Clear alert interrupt
+ *
+ * @param dev Device descriptor
+ * @param alert Alert index
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp960x_clear_alert_int(mcp960x_t *dev, mcp960x_alert_t alert);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __MCP960X_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp9808/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,23 @@
+---
+components:
+  - name: mcp9808
+    description: Driver for MCP9808 Digital Temperature Sensor
+    group: temperature
+    groups: []
+    code_owners: UncleRus
+    depends:
+      - i2cdev
+      - log
+      - esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2020
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp9808/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS mcp9808.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp9808/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp9808/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp9808/mcp9808.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,331 @@
+/*
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file mcp9808.c
+ *
+ * ESP-IDF driver for MCP9808 Digital Temperature Sensor
+ *
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#include <esp_idf_lib_helpers.h>
+#include "mcp9808.h"
+
+#include <math.h>
+#include <esp_log.h>
+
+static const char *TAG = "mcp9808";
+
+#define I2C_FREQ_HZ 400000 // 400 kHz
+
+#define MANUFACTURER_ID 0x0054
+#define DEVICE_ID 0x04
+
+#define REG_CONFIG  1
+#define REG_T_UPPER 2
+#define REG_T_LOWER 3
+#define REG_T_CRIT  4
+#define REG_T_A     5
+#define REG_MANUF   6
+#define REG_ID      7
+#define REG_RES     8
+
+#define BIT_CONFIG_HYST       9
+#define BIT_CONFIG_SHDN       8
+#define BIT_CONFIG_CRIT_LOCK  7
+#define BIT_CONFIG_WIN_LOCK   6
+#define BIT_CONFIG_INT_CLR    5
+#define BIT_CONFIG_ALERT_STAT 4
+#define BIT_CONFIG_ALERT_CTRL 3
+#define BIT_CONFIG_ALERT_SEL  2
+#define BIT_CONFIG_ALERT_POL  1
+#define BIT_CONFIG_ALERT_MODE 0
+
+#define BIT_T_SIGN 12
+
+#define BIT_T_A_LOWER 13
+#define BIT_T_A_UPPER 14
+#define BIT_T_A_CRIT  15
+
+#define BV(x) (1 << (x))
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+static inline esp_err_t read_reg_16_nolock(i2c_dev_t *dev, uint8_t reg, uint16_t *val)
+{
+    CHECK(i2c_dev_read_reg(dev, reg, val, 2));
+    *val = (*val << 8) | (*val >> 8);
+    return ESP_OK;
+}
+
+static inline esp_err_t write_reg_16_nolock(i2c_dev_t *dev, uint8_t reg, uint16_t val)
+{
+    uint16_t buf = (val << 8) | (val >> 8);
+    return i2c_dev_write_reg(dev, reg, &buf, 2);
+}
+
+static esp_err_t read_reg_16(i2c_dev_t *dev, uint8_t reg, uint16_t *val)
+{
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, read_reg_16_nolock(dev, reg, val));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+static esp_err_t write_reg_16(i2c_dev_t *dev, uint8_t reg, uint16_t val)
+{
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, write_reg_16_nolock(dev, reg, val));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+static esp_err_t update_reg_16(i2c_dev_t *dev, uint8_t reg, uint16_t mask, uint16_t val)
+{
+    uint16_t old;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, read_reg_16_nolock(dev, reg, &old));
+    I2C_DEV_CHECK(dev, write_reg_16_nolock(dev, reg, (old & mask) | val));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+static esp_err_t write_temp(i2c_dev_t *dev, uint8_t reg, float t)
+{
+    uint16_t v;
+    if (t < 0)
+        v = (((1023 - ((uint16_t)(fabsf(t) * 4.0)) + 1) << 2) & 0x0fff) | BV(BIT_T_SIGN);
+    else
+        v = ((uint16_t)(t * 4.0) << 2) & 0x0fff;
+
+    return write_reg_16(dev, reg, v);
+}
+
+static esp_err_t read_temp(i2c_dev_t *dev, uint8_t reg, float *t, uint16_t *raw)
+{
+    uint16_t v;
+    CHECK(read_reg_16(dev, reg, &v));
+
+    *t = (v & 0x0fff) / 16.0;
+    if (v & BV(BIT_T_SIGN)) *t -= 256;
+
+    if (raw) *raw = v;
+
+    return ESP_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t mcp9808_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+
+    // I2C Address verification is not needed because the device may have a custom factory address
+
+    dev->port = port;
+    dev->addr = addr;
+    dev->cfg.sda_io_num = sda_gpio;
+    dev->cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(dev);
+}
+
+esp_err_t mcp9808_free_desc(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(dev);
+}
+
+esp_err_t mcp9808_init(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    uint16_t v;
+
+    CHECK(read_reg_16(dev, REG_MANUF, &v));
+    if (v != MANUFACTURER_ID)
+    {
+        ESP_LOGE(TAG, "Invalid manufacturer ID 0x%04x", v);
+        return ESP_ERR_INVALID_RESPONSE;
+    }
+
+    CHECK(read_reg_16(dev, REG_ID, &v));
+    if ((v >> 8) != DEVICE_ID)
+    {
+        ESP_LOGE(TAG, "Invalid device ID 0x%02x", v);
+        return ESP_ERR_INVALID_RESPONSE;
+    }
+    ESP_LOGD(TAG, "Device revision: 0x%02x", v & 0xff);
+
+    // clear all lock bits
+    return write_reg_16(dev, REG_CONFIG, 0);
+}
+
+esp_err_t mcp9808_set_mode(i2c_dev_t *dev, mcp9808_mode_t mode)
+{
+    CHECK_ARG(dev);
+
+    return update_reg_16(dev, REG_CONFIG, ~BV(BIT_CONFIG_SHDN),
+            mode == MCP9808_SHUTDOWN ? BV(BIT_CONFIG_SHDN) : 0);
+}
+
+esp_err_t mcp9808_get_mode(i2c_dev_t *dev, mcp9808_mode_t *mode)
+{
+    CHECK_ARG(dev && mode);
+
+    uint16_t v;
+    CHECK(read_reg_16(dev, REG_CONFIG, &v));
+    *mode = v & BV(BIT_CONFIG_SHDN) ? MCP9808_SHUTDOWN : MCP9808_CONTINUOUS;
+
+    return ESP_OK;
+}
+
+esp_err_t mcp9808_set_resolution(i2c_dev_t *dev, mcp9808_resolution_t res)
+{
+    CHECK_ARG(dev);
+
+    return write_reg_16(dev, REG_RES, res);
+}
+
+esp_err_t mcp9808_get_resolution(i2c_dev_t *dev, mcp9808_resolution_t *res)
+{
+    CHECK_ARG(dev && res);
+
+    return read_reg_16(dev, REG_RES, (uint16_t *)res);
+}
+
+esp_err_t mcp9808_set_alert_config(i2c_dev_t *dev, mcp9808_alert_mode_t mode,
+        mcp9808_alert_select_t sel, mcp9808_alert_polarity_t polarity, mcp9808_hysteresis_t hyst)
+{
+    CHECK_ARG(dev);
+
+    if (mode == MCP9808_ALERT_DISABLED)
+        return update_reg_16(dev, REG_CONFIG, ~BV(BIT_CONFIG_ALERT_CTRL), 0);
+
+    uint16_t mask = ~(BV(BIT_CONFIG_ALERT_CTRL) | BV(BIT_CONFIG_ALERT_SEL)
+            | BV(BIT_CONFIG_ALERT_MODE) | BV(BIT_CONFIG_ALERT_POL) | (3 << BIT_CONFIG_HYST));
+
+    return update_reg_16(dev, REG_CONFIG, mask,
+            BV(BIT_CONFIG_ALERT_CTRL)
+            | (mode == MCP9808_ALERT_COMPARATOR ? 0 : BV(BIT_CONFIG_ALERT_MODE))
+            | ((sel & 1) << BIT_CONFIG_ALERT_SEL)
+            | ((polarity & 1) << BIT_CONFIG_ALERT_POL)
+            | ((hyst & 3) << BIT_CONFIG_HYST));
+}
+
+esp_err_t mcp9808_get_alert_config(i2c_dev_t *dev, mcp9808_alert_mode_t *mode,
+        mcp9808_alert_select_t *sel, mcp9808_alert_polarity_t *polarity, mcp9808_hysteresis_t *hyst)
+{
+    CHECK_ARG(dev && mode && sel && polarity && hyst);
+
+    uint16_t v;
+    CHECK(read_reg_16(dev, REG_CONFIG, &v));
+
+    if (v & BV(BIT_CONFIG_ALERT_CTRL))
+        *mode = v & BV(BIT_CONFIG_ALERT_MODE) ? MCP9808_ALERT_INTERRUPT : MCP9808_ALERT_COMPARATOR;
+    else
+        *mode = MCP9808_ALERT_DISABLED;
+    *sel = v & BV(BIT_CONFIG_ALERT_SEL) ? MCP9808_ALERT_CRIT : MCP9808_ALERT_UP_LOW_CRIT;
+    *polarity = v & BV(BIT_CONFIG_ALERT_POL) ? MCP9808_ALERT_HIGH : MCP9808_ALERT_LOW;
+    *hyst = v >> BIT_CONFIG_HYST;
+
+    return ESP_OK;
+}
+
+esp_err_t mcp9808_set_limits(i2c_dev_t *dev, float t_upper, float t_lower, float t_crit)
+{
+    CHECK_ARG(dev);
+
+    CHECK(write_temp(dev, REG_T_UPPER, t_upper));
+    CHECK(write_temp(dev, REG_T_LOWER, t_lower));
+    CHECK(write_temp(dev, REG_T_CRIT, t_crit));
+
+    return ESP_OK;
+}
+
+esp_err_t mcp9808_get_limits(i2c_dev_t *dev, float *t_upper, float *t_lower, float *t_crit)
+{
+    CHECK_ARG(dev && t_upper && t_lower && t_crit);
+
+    CHECK(read_temp(dev, REG_T_UPPER, t_upper, NULL));
+    CHECK(read_temp(dev, REG_T_LOWER, t_lower, NULL));
+    CHECK(read_temp(dev, REG_T_CRIT, t_crit, NULL));
+
+    return ESP_OK;
+}
+
+esp_err_t mcp9808_set_alert_status(i2c_dev_t *dev, bool alert)
+{
+    CHECK_ARG(dev);
+
+    return update_reg_16(dev, REG_CONFIG, ~BV(BIT_CONFIG_ALERT_STAT),
+            alert ? BV(BIT_CONFIG_ALERT_STAT) : 0);
+}
+
+esp_err_t mcp9808_get_alert_status(i2c_dev_t *dev, bool *alert)
+{
+    CHECK_ARG(dev && alert);
+
+    uint16_t v;
+    CHECK(read_reg_16(dev, REG_CONFIG, &v));
+    *alert = v & BV(BIT_CONFIG_ALERT_STAT) ? true : false;
+
+    return ESP_OK;
+}
+
+esp_err_t mcp9808_clear_interrupt(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return update_reg_16(dev, REG_CONFIG, ~BV(BIT_CONFIG_INT_CLR), BV(BIT_CONFIG_INT_CLR));
+}
+
+esp_err_t mcp9808_get_temperature(i2c_dev_t *dev, float *t, bool *lower, bool *upper, bool *crit)
+{
+    CHECK_ARG(dev && t);
+
+    uint16_t v;
+
+    CHECK(read_temp(dev, REG_T_A, t, &v));
+    if (lower) *lower = v & BV(BIT_T_A_LOWER) ? true : false;
+    if (upper) *upper = v & BV(BIT_T_A_UPPER) ? true : false;
+    if (crit) *crit = v & BV(BIT_T_A_CRIT) ? true : false;
+
+    return ESP_OK;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mcp9808/mcp9808.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file mcp9808.h
+ * @defgroup mcp9808 mcp9808
+ * @{
+ *
+ * ESP-IDF driver for MCP9808 Digital Temperature Sensor
+ *
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __MCP9808_H__
+#define __MCP9808_H__
+
+#include <stdbool.h>
+#include <i2cdev.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define MCP9808_I2C_ADDR_000 0x18 //!< I2C address, pins: A0=0, A1=0, A2=0
+#define MCP9808_I2C_ADDR_001 0x19 //!< I2C address, pins: A0=1, A1=0, A2=0
+#define MCP9808_I2C_ADDR_010 0x1A //!< I2C address, pins: A0=0, A1=1, A2=0
+#define MCP9808_I2C_ADDR_011 0x1B //!< I2C address, pins: A0=1, A1=1, A2=0
+#define MCP9808_I2C_ADDR_100 0x1C //!< I2C address, pins: A0=0, A1=0, A2=1
+#define MCP9808_I2C_ADDR_101 0x1D //!< I2C address, pins: A0=1, A1=0, A2=1
+#define MCP9808_I2C_ADDR_110 0x1E //!< I2C address, pins: A0=0, A1=1, A2=1
+#define MCP9808_I2C_ADDR_111 0x1F //!< I2C address, pins: A0=1, A1=1, A2=1
+
+/**
+ * Device mode
+ */
+typedef enum {
+    MCP9808_CONTINUOUS = 0, //!< Continuous measurement mode, default
+    MCP9808_SHUTDOWN        //!< Shutdown mode
+} mcp9808_mode_t;
+
+/**
+ * T upper and T lower hysteresis
+ */
+typedef enum {
+    MCP9808_HYST_0 = 0, //!< 0.0 deg.C, default
+    MCP9808_HYST_1_5,   //!< 1.5 deg.C
+    MCP9808_HYST_3,     //!< 3.0 deg.C
+    MCP9808_HYST_6      //!< 6.0 deg.C
+} mcp9808_hysteresis_t;
+
+/**
+ * Alert output mode
+ */
+typedef enum {
+    MCP9808_ALERT_DISABLED = 0, //!< Alert output disabled, default
+    MCP9808_ALERT_COMPARATOR,   //!< Alert output in comparator mode
+    MCP9808_ALERT_INTERRUPT     //!< Alert output in interrupt mode
+} mcp9808_alert_mode_t;
+
+/**
+ * Alert output select
+ */
+typedef enum {
+    MCP9808_ALERT_UP_LOW_CRIT = 0, //!< Alert when T > T upper or T < T lower or T > T crit, default
+    MCP9808_ALERT_CRIT,            //!< Alert when T > T crit
+} mcp9808_alert_select_t;
+
+/**
+ * Alert output polarity
+ */
+typedef enum {
+    MCP9808_ALERT_LOW = 0, //!< Active-low, pull-up resistor required, default
+    MCP9808_ALERT_HIGH,    //!< Active-high
+} mcp9808_alert_polarity_t;
+
+/**
+ * Temperature resolution
+ */
+typedef enum {
+    MCP9808_RES_0_5 = 0, //!< Resolution = +0.5 deg.C, conversion time = 30 ms, typical sample rate = 33 Hz
+    MCP9808_RES_0_25,    //!< Resolution = +0.25 deg.C, conversion time = 65 ms, typical sample rate = 15 Hz
+    MCP9808_RES_0_125,   //!< Resolution = +0.125 deg.C, conversion time = 130 ms, typical sample rate = 7 Hz
+    MCP9808_RES_0_0625   //!< Resolution = +0.0625 deg.C, conversion time = 250 ms, typical sample rate = 4 Hz, default
+} mcp9808_resolution_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev Device descriptor
+ * @param addr Device I2C address
+ * @param port I2C port
+ * @param sda_gpio SDA GPIO
+ * @param scl_gpio SCL GPIO
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp9808_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp9808_free_desc(i2c_dev_t *dev);
+
+/**
+ * @brief Init device
+ *
+ * Set device configuration to default, clear lock bits
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp9808_init(i2c_dev_t *dev);
+
+/**
+ * @brief Set device mode
+ *
+ * @param dev Device descriptor
+ * @param mode Power mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp9808_set_mode(i2c_dev_t *dev, mcp9808_mode_t mode);
+
+/**
+ * @brief Get device mode
+ *
+ * @param dev Device descriptor
+ * @param[out] mode Current power mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp9808_get_mode(i2c_dev_t *dev, mcp9808_mode_t *mode);
+
+/**
+ * @brief Set temperature resolution
+ *
+ * @param dev Device descriptor
+ * @param res Resolution
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp9808_set_resolution(i2c_dev_t *dev, mcp9808_resolution_t res);
+
+/**
+ * @brief Get temperature resolution
+ *
+ * @param dev Device descriptor
+ * @param[out] res Resolution
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp9808_get_resolution(i2c_dev_t *dev, mcp9808_resolution_t *res);
+
+/**
+ * @brief Configure alert parameters
+ *
+ * @param dev Device descriptor
+ * @param mode Alert mode
+ * @param sel Alert window (see datasheet)
+ * @param polarity Alert output polarity
+ * @param hyst Alert limits hysteresis
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp9808_set_alert_config(i2c_dev_t *dev, mcp9808_alert_mode_t mode,
+        mcp9808_alert_select_t sel, mcp9808_alert_polarity_t polarity, mcp9808_hysteresis_t hyst);
+
+/**
+ * @brief Get alert configuration
+ *
+ * @param dev Device descriptor
+ * @param[out] mode Alert mode
+ * @param[out] sel Alert window (see datasheet)
+ * @param[out] polarity Alert output polarity
+ * @param[out] hyst Alert limits hysteresis
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp9808_get_alert_config(i2c_dev_t *dev, mcp9808_alert_mode_t *mode,
+        mcp9808_alert_select_t *sel, mcp9808_alert_polarity_t *polarity, mcp9808_hysteresis_t *hyst);
+
+/**
+ * @brief Set alert temperature limits
+ *
+ * @param dev Device descriptor
+ * @param t_upper Upper temperature
+ * @param t_lower Lower temperature
+ * @param t_crit  Critical temperature
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp9808_set_limits(i2c_dev_t *dev, float t_upper, float t_lower, float t_crit);
+
+/**
+ * @brief Get alert temperature limits
+ *
+ * @param dev Device descriptor
+ * @param[out] t_upper Upper temperature
+ * @param[out] t_lower Lower temperature
+ * @param[out] t_crit  Critical temperature
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp9808_get_limits(i2c_dev_t *dev, float *t_upper, float *t_lower, float *t_crit);
+
+/**
+ * @brief Set alert status
+ *
+ * @param dev Device descriptor
+ * @param alert True for alert
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp9808_set_alert_status(i2c_dev_t *dev, bool alert);
+
+/**
+ * @brief Get current alert status
+ *
+ * @param dev Device descriptor
+ * @param[out] alert Alert status
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp9808_get_alert_status(i2c_dev_t *dev, bool *alert);
+
+/**
+ * @brief Clear interrupt bit
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp9808_clear_interrupt(i2c_dev_t *dev);
+
+/**
+ * @brief Read temperature
+ *
+ * @param dev Device descriptor
+ * @param[out] t Ambient temperature
+ * @param[out] lower True if T a < T lower, can be NULL
+ * @param[out] upper True if T a > T upper, can be NULL
+ * @param[out] crit True if T a >= T critical, can be NULL
+ * @return `ESP_OK` on success
+ */
+esp_err_t mcp9808_get_temperature(i2c_dev_t *dev, float *t, bool *lower, bool *upper, bool *crit);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __MCP9808_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mhz19b/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+---
+components:
+  - name: mhz19b
+    description: Driver for MH-Z19B NDIR CO₂ sensor
+    group: air-quality
+    groups:
+      - gas
+    code_owners: UncleRus
+    depends:
+      - log
+      - esp_idf_lib_helpers
+    thread_safe: no
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - author:
+          name: Erriez
+        year: 2020
+      - author:
+          name: DavidD
+        year: 2021
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mhz19b/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,9 @@
+if(${IDF_VERSION_MAJOR} STREQUAL 4 AND ${IDF_VERSION_MINOR} STREQUAL 1 AND ${IDF_VERSION_PATCH} STREQUAL 3)
+	set(req log esp_idf_lib_helpers driver)
+else()
+	set(req log esp_idf_lib_helpers driver esp_timer)
+endif()
+
+idf_component_register(SRCS "mhz19b.c"
+                       INCLUDE_DIRS "."
+                       REQUIRES ${req})
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mhz19b/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+Copyright (c) 2021 David Douard <david.douard@sdfa3.org>
+
+Redistribution and use in source and binary forms, with or woithout
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mhz19b/README.md	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,61 @@
+# Driver for the MH-Z19B NDIR CO2 sensor
+
+This driver is heavily inspired from [Erriez MH-Z19B CO2 sensor library for
+Arduino](https://github.com/Erriez/ErriezMHZ19B).
+
+It uses an UART serial port to communicate with the sensor. Since the UART
+must configured a specific way (9600 8N1), the `mhz19b_init()` takes care of
+configuring it.  It will however not start detecting/reading from the sensor.
+This must be done. Typical usage would be:
+
+```C
+[...]
+#include "esp_log.h"
+#include "mhz19b.h"
+
+#define MHZ19B_TX 12
+#define MHZ19B_RX 13
+
+void app_main(void)
+{
+    int16_t co2;
+    mhz19b_dev_t dev;
+    char version[6];
+    uint16_t range;
+    bool autocal;
+
+    mhz19b_init(&dev, UART_NUM_1, MHZ19B_TX, MHZ19B_RX);
+
+    while (!mhz19b_detect(&dev))
+    {
+        ESP_LOGI(TAG, "MHZ-19B not detected, waiting...");
+        vTaskDelay(1000 / portTICK_PERIOD_MS);
+    }
+
+    mhz19b_get_version(&dev, version, 5);
+    ESP_LOGI(TAG, "MHZ-19B firmware version: %s", version);
+    ESP_LOGI(TAG, "MHZ-19B set range and autocal");
+
+    mhz19b_set_range(&dev, MHZ19B_RANGE_5000);
+    mhz19b_set_auto_calibration(&dev, false);
+
+    mhz19b_get_range(&dev, &range);
+    ESP_LOGI(TAG, "  range: %d", range);
+
+    mhz19b_get_auto_calibration(&dev, &autocal);
+    ESP_LOGI(TAG, "  autocal: %s", autocal ? "ON" : "OFF");
+
+    while (mhz19b_is_warming_up(&dev))
+    {
+        ESP_LOGI(TAG, "MHZ-19B is warming up");
+        vTaskDelay(1000 / portTICK_PERIOD_MS);
+    }
+
+    while (1) {
+        mhz19b_read_CO2(&dev, &co2);
+        ESP_LOGI(TAG, "CO2: %d", co2);
+        vTaskDelay(5000 / portTICK_PERIOD_MS);
+    }
+}
+
+```
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mhz19b/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = log
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mhz19b/mhz19b.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,295 @@
+/*
+ * Copyright (c) 2020 Erriez <https://github.com/Erriez>
+ * Copyright (c) 2021 David Douard <david.douard@sdfa3.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file mhz19b.c
+ *
+ * ESP-IDF driver for MH-Z19B NDIR CO2 sensor connected to UART
+ *
+ * Inspired from https://github.com/Erriez/ErriezMHZ19B
+ *
+ * Copyright (c) 2020 Erriez <https://github.com/Erriez>
+ * Copyright (c) 2021 David Douard <david.douard@sdfa3.org>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#include <string.h>
+#include <esp_idf_lib_helpers.h>
+#include <esp_log.h>
+#include <esp_timer.h>
+
+#include "mhz19b.h"
+
+static const char *TAG = "mhz19b";
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+esp_err_t mhz19b_init(mhz19b_dev_t *dev, uart_port_t uart_port, gpio_num_t tx_gpio, gpio_num_t rx_gpio)
+{
+    CHECK_ARG(dev);
+
+    uart_config_t uart_config = {
+        .baud_rate = 9600,
+        .data_bits = UART_DATA_8_BITS,
+        .parity    = UART_PARITY_DISABLE,
+        .stop_bits = UART_STOP_BITS_1,
+        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
+#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0)
+        .source_clk = UART_SCLK_APB,
+#endif
+    };
+    CHECK(uart_driver_install(uart_port, MHZ19B_SERIAL_BUF_LEN * 2, 0, 0, NULL, 0));
+    CHECK(uart_param_config(uart_port, &uart_config));
+#if HELPER_TARGET_IS_ESP32
+    CHECK(uart_set_pin(uart_port, tx_gpio, rx_gpio, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
+#endif
+
+    dev->uart_port = uart_port;
+    // buffer for the incoming data
+    dev->buf = malloc(MHZ19B_SERIAL_BUF_LEN);
+    if (!dev->buf)
+        return ESP_ERR_NO_MEM;
+    dev->last_value = -1;
+    dev->last_ts = esp_timer_get_time();
+    return ESP_OK;
+}
+
+esp_err_t mhz19b_free(mhz19b_dev_t *dev)
+{
+    CHECK_ARG(dev && dev->buf);
+
+    free(dev->buf);
+    dev->buf = NULL;
+    return ESP_OK;
+}
+
+bool mhz19b_detect(mhz19b_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    uint16_t range;
+    // Check valid PPM range
+    if ((mhz19b_get_range(dev, &range) == ESP_OK) && (range > 0))
+        return true;
+
+    // Sensor not detected, or invalid range returned
+    // Try recover by calling setRange(MHZ19B_RANGE_5000);
+    return false;
+}
+
+bool mhz19b_is_warming_up(mhz19b_dev_t *dev, bool smart_warming_up)
+{
+    CHECK_ARG(dev);
+
+    // Wait at least 3 minutes after power-on
+    if (esp_timer_get_time() < MHZ19B_WARMING_UP_TIME_US)
+    {
+        if (smart_warming_up)
+        {
+            ESP_LOGD(TAG, "Using smart warming up detection");
+
+            int16_t co2, last_co2;
+            last_co2 = dev->last_value;
+            // Sensor returns valid data after CPU reset and keep sensor powered
+            if (mhz19b_read_co2(dev, &co2) != ESP_OK)
+                return false;
+            if ((last_co2 != -1) && (last_co2 != co2))
+                // CO2 value changed since last read, no longer warming-up
+                return false;
+        }
+        // Warming-up
+        return true;
+    }
+
+    // Not warming-up
+    return false;
+}
+
+bool mhz19b_is_ready(mhz19b_dev_t *dev)
+{
+    if (!dev) return false;
+
+    // Minimum CO2 read interval (Built-in LED flashes)
+    if ((esp_timer_get_time() - dev->last_ts) > MHZ19B_READ_INTERVAL_MS) {
+        return true;
+    }
+
+    return false;
+}
+
+esp_err_t mhz19b_read_co2(mhz19b_dev_t *dev, int16_t *co2)
+{
+    CHECK_ARG(dev && co2);
+
+    // Send command "Read CO2 concentration"
+    CHECK(mhz19b_send_command(dev, MHZ19B_CMD_READ_CO2, 0, 0, 0, 0, 0));
+
+    // 16-bit CO2 value in response Bytes 2 and 3
+    *co2 = (dev->buf[2] << 8) | dev->buf[3];
+    dev->last_ts = esp_timer_get_time();
+    dev->last_value = *co2;
+
+    return ESP_OK;
+}
+
+esp_err_t mhz19b_get_version(mhz19b_dev_t *dev, char *version)
+{
+    CHECK_ARG(dev && version);
+
+    // Clear version
+    memset(version, 0, 5);
+
+    // Send command "Read firmware version" (NOT DOCUMENTED)
+    CHECK(mhz19b_send_command(dev, MHZ19B_CMD_GET_VERSION, 0, 0, 0, 0, 0));
+
+    // Copy 4 ASCII characters to version array like "0443"
+    for (uint8_t i = 0; i < 4; i++) {
+        // Version in response Bytes 2..5
+        version[i] = dev->buf[i + 2];
+    }
+
+    return ESP_OK;
+}
+
+esp_err_t mhz19b_set_range(mhz19b_dev_t *dev, mhz19b_range_t range)
+{
+    CHECK_ARG(dev);
+
+    // Send "Set range" command
+    return mhz19b_send_command(dev, MHZ19B_CMD_SET_RANGE,
+                               0x00, 0x00, 0x00, (range >> 8), (range & 0xff));
+}
+
+esp_err_t mhz19b_get_range(mhz19b_dev_t *dev, uint16_t *range)
+{
+    CHECK_ARG(dev && range);
+
+    // Send command "Read range" (NOT DOCUMENTED)
+    CHECK(mhz19b_send_command(dev, MHZ19B_CMD_GET_RANGE, 0, 0, 0, 0, 0));
+
+    // Range is in Bytes 4 and 5
+    *range = (dev->buf[4] << 8) | dev->buf[5];
+
+    // Check range according to documented specification
+    if ((*range != MHZ19B_RANGE_2000) && (*range != MHZ19B_RANGE_5000))
+        return ESP_ERR_INVALID_RESPONSE;
+
+    return ESP_OK;
+}
+
+esp_err_t mhz19b_set_auto_calibration(mhz19b_dev_t *dev, bool calibration_on)
+{
+    CHECK_ARG(dev);
+
+    // Send command "Set Automatic Baseline Correction (ABC logic function)"
+    return mhz19b_send_command(dev, MHZ19B_CMD_SET_AUTO_CAL, (calibration_on ? 0xA0 : 0x00), 0, 0, 0, 0);
+}
+
+esp_err_t mhz19b_get_auto_calibration(mhz19b_dev_t *dev, bool *calibration_on)
+{
+    CHECK_ARG(dev && calibration_on);
+
+    // Send command "Get Automatic Baseline Correction (ABC logic function)" (NOT DOCUMENTED)
+    CHECK(mhz19b_send_command(dev, MHZ19B_CMD_GET_AUTO_CAL, 0, 0, 0, 0, 0));
+
+    // Response is located in Byte 7: 0 = off, 1 = on
+    *calibration_on = dev->buf[7] & 0x01;
+
+    return ESP_OK;
+}
+
+esp_err_t mhz19b_start_calibration(mhz19b_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    // Send command "Zero Point Calibration"
+    return mhz19b_send_command(dev, MHZ19B_CMD_CAL_ZERO_POINT, 0, 0, 0, 0, 0);
+}
+
+esp_err_t mhz19b_send_command(mhz19b_dev_t *dev, uint8_t cmd, uint8_t b3, uint8_t b4, uint8_t b5, uint8_t b6, uint8_t b7)
+{
+    CHECK_ARG(dev && dev->buf);
+
+    uint8_t txBuffer[MHZ19B_SERIAL_RX_BYTES] = { 0xFF, 0x01, cmd, b3, b4, b5, b6, b7, 0x00 };
+
+    // Check initialized
+#if HELPER_TARGET_IS_ESP32 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0)
+    if (!uart_is_driver_installed(dev->uart_port))
+        return ESP_ERR_INVALID_STATE;
+#endif
+
+    // Add CRC Byte
+    txBuffer[8] = mhz19b_calc_crc(txBuffer);
+
+    // Clear receive buffer
+    uart_flush(dev->uart_port);
+
+    // Write serial data
+    uart_write_bytes(dev->uart_port, (char *) txBuffer, sizeof(txBuffer));
+
+    // Clear receive buffer
+    memset(dev->buf, 0, MHZ19B_SERIAL_BUF_LEN);
+
+    // Read response from serial buffer
+    int len = uart_read_bytes(dev->uart_port, dev->buf,
+                              MHZ19B_SERIAL_RX_BYTES,
+                              MHZ19B_SERIAL_RX_TIMEOUT_MS / portTICK_PERIOD_MS);
+    if (len < 9)
+        return ESP_ERR_TIMEOUT;
+
+    // Check received Byte[0] == 0xFF and Byte[1] == transmit command
+    if ((dev->buf[0] != 0xFF) || (dev->buf[1] != cmd))
+        return ESP_ERR_INVALID_RESPONSE;
+
+    // Check received Byte[8] CRC
+    if (dev->buf[8] != mhz19b_calc_crc(dev->buf))
+        return ESP_ERR_INVALID_CRC;
+
+    // Return result
+    return ESP_OK;
+}
+
+// ----------------------------------------------------------------------------
+// Private functions
+// ----------------------------------------------------------------------------
+
+uint8_t mhz19b_calc_crc(uint8_t *data)
+{
+    uint8_t crc = 0;
+
+    // Calculate CRC on 8 data Bytes
+    for (uint8_t i = 1; i < 8; i++)
+        crc += data[i];
+
+    crc = 0xFF - crc;
+    crc++;
+
+    // Return calculated CRC
+    return crc;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/mhz19b/mhz19b.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,279 @@
+/*
+ * Copyright (c) 2020 Erriez <https://github.com/Erriez>
+ * Copyright (c) 2021 David Douard <david.douard@sdfa3.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file mhz19b.h
+ * @defgroup mhz19b mhz19b
+ * @{
+ *
+ * ESP-IDF driver for MH-Z19B NDIR CO2 sensor connected to UART
+ *
+ * Inspired from https://github.com/Erriez/ErriezMHZ19B
+ *
+ * Copyright (c) 2020 Erriez <https://github.com/Erriez>\n
+ * Copyright (c) 2021 David Douard <david.douard@sdfa3.org>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __MHZ19B_H__
+#define __MHZ19B_H__
+
+#include <stdbool.h>
+#include <driver/uart.h>
+#include <driver/gpio.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief MHZ19B sensor device data structure
+ */
+typedef struct
+{
+    uart_port_t uart_port;  ///< UART port used to communicate
+    uint8_t *buf;           ///< read buffer attached to this device
+    int16_t last_value;     ///< last read value
+    int64_t last_ts;        ///< timestamp of the last sensor co2 level reading
+} mhz19b_dev_t;
+
+//! 3 minutes warming-up time after power-on before valid data returned
+#define MHZ19B_WARMING_UP_TIME_MS       (3UL * 60000UL)
+#define MHZ19B_WARMING_UP_TIME_US       (3UL * 60000UL * 1000UL)
+
+//! Minimum response time between CO2 reads (EXPERIMENTALLY DEFINED)
+#define MHZ19B_READ_INTERVAL_MS         (5UL * 1000UL)
+
+//! Fixed 9 Bytes response
+#define MHZ19B_SERIAL_RX_BYTES          9
+//! 128 is the minimal value the UART driver will accept (at least on esp32)
+#define MHZ19B_SERIAL_BUF_LEN           128
+
+//! Response timeout between 15..120 ms at 9600 baud works reliable for all commands
+#define MHZ19B_SERIAL_RX_TIMEOUT_MS     120
+
+// Documented commands
+#define MHZ19B_CMD_SET_AUTO_CAL         0x79 ///< set auto calibration on/off
+#define MHZ19B_CMD_READ_CO2             0x86 ///< read CO2 concentration
+#define MHZ19B_CMD_CAL_ZERO_POINT       0x87 ///< calibrate zero point at 400ppm
+#define MHZ19B_CMD_CAL_SPAN_PIONT       0x88 ///< calibrate span point (NOT IMPLEMENTED)
+#define MHZ19B_CMD_SET_RANGE            0x99 ///< set detection range
+
+// Not documented commands
+#define MHZ19B_CMD_GET_AUTO_CAL         0x7D ///< get auto calibration status (NOT DOCUMENTED)
+#define MHZ19B_CMD_GET_RANGE            0x9B ///< get range detection (NOT DOCUMENTED)
+#define MHZ19B_CMD_GET_VERSION          0xA0 ///< get firmware version (NOT DOCUMENTED)
+
+/**
+ * @brief PPM range
+ */
+typedef enum {
+    MHZ19B_RANGE_2000 = 2000,            ///< 2000 ppm
+    MHZ19B_RANGE_5000 = 5000,            ///< 5000 ppm (Default)
+} mhz19b_range_t;
+
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev Pointer to the sensor device data structure
+ * @param uart_port UART poert number
+ * @param tx_gpio GPIO pin number for TX
+ * @param rx_gpio GPIO pin number for RX
+ *
+ * @return ESP_OK on success
+ */
+esp_err_t mhz19b_init(mhz19b_dev_t *dev, uart_port_t uart_port, gpio_num_t tx_gpio, gpio_num_t rx_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Pointer to the sensor device data structure
+ *
+ * @return ESP_OK on success
+ */
+esp_err_t mhz19b_free(mhz19b_dev_t *dev);
+
+/**
+ * @brief Detect sensor by checking range response
+ *
+ * @param dev Pointer to the sensor device data structure
+ *
+ * @return true if sensor is detected
+ */
+bool mhz19b_detect(mhz19b_dev_t *dev);
+
+/**
+ * @brief Check if sensor is warming up
+ *
+ * @param dev Pointer to the sensor device data structure
+ * @param smart_warming_up Smart check
+ *
+ * @return true if sensor is warming up
+ */
+bool mhz19b_is_warming_up(mhz19b_dev_t *dev, bool smart_warming_up);
+
+/**
+ * @brief Check minimum interval between CO2 reads
+ *
+ * Not described in the datasheet, but it is the same frequency as the built-in LED blink.
+ *
+ * @param dev Pointer to the sensor device data structure
+ *
+ * @return true if ready to call ::mhz19b_read_co2()
+ */
+bool mhz19b_is_ready(mhz19b_dev_t *dev);
+
+/**
+ * @brief Read CO2 from sensor
+ *
+ * @param dev Pointer to the sensor device data structure
+ * @param[out] co2 CO2 level
+ *      - < 0: MH-Z19B response error codes.
+ *      - 0..399 ppm: Incorrect values. Minimum value starts at 400ppm outdoor fresh air.
+ *      - 400..1000 ppm: Concentrations typical of occupied indoor spaces with good air exchange.
+ *      - 1000..2000 ppm: Complaints of drowsiness and poor air quality. Ventilation is required.
+ *      - 2000..5000 ppm: Headaches, sleepiness and stagnant, stale, stuffy air. Poor concentration, loss of
+ *        attention, increased heart rate and slight nausea may also be present.
+ *      - Higher values are extremely dangerous and cannot be measured.
+ *
+ * @return ESP_OK on success
+ */
+esp_err_t mhz19b_read_co2(mhz19b_dev_t *dev, int16_t *co2);
+
+/**
+ * @brief Get firmware version (NOT DOCUMENTED)
+ *
+ * @details
+ *      This is an undocumented command, but most sensors returns ASCII "0430 or "0443".
+ *
+ * @param dev Pointer to the sensor device data structure
+ * @param[out] version
+ *      Returned character pointer to version (must be at least 5 Bytes)\n
+ *      Only valid when return is set to ESP_OK.
+ *
+ * @return ESP_OK on success
+ */
+esp_err_t mhz19b_get_version(mhz19b_dev_t *dev, char *version);
+
+/**
+ * @brief Set CO2 range
+ *
+ * @param dev Pointer to the sensor device data structure
+ * @param range Range of the sensor (2000 or 5000, in ppm)
+ *
+ * @return ESP_OK on success
+ */
+esp_err_t mhz19b_set_range(mhz19b_dev_t *dev, mhz19b_range_t range);
+
+/**
+ * @brief Get CO2 range in PPM (NOT DOCUMENTED)
+ *
+ * @details
+ *      This function verifies valid read ranges of 2000 or 5000 ppm.\n
+ *      Note: Other ranges may be returned, but are undocumented and marked as invalid.
+ *
+ * @param dev Pointer to the sensor device data structure
+ * @param range Current value of the range of the sensor (output)
+ *
+ * @return ESP_OK on success
+ */
+esp_err_t mhz19b_get_range(mhz19b_dev_t *dev, uint16_t *range);
+
+/**
+ * @brief Enable or disable automatic calibration
+ *
+ * @param dev Pointer to the sensor device data structure
+ * @param calibration_on
+ *      - true: Automatic calibration on.
+ *      - false: Automatic calibration off.
+ *
+ * @return ESP_OK on success
+ */
+esp_err_t mhz19b_set_auto_calibration(mhz19b_dev_t *dev, bool calibration_on);
+
+/**
+ * @brief Get status of automatic calibration (NOT DOCUMENTED)
+ *
+ * @param dev Pointer to the sensor device data structure
+ * @param[out] calibration_on Automatic calibration status
+ *
+ * @return ESP_OK on success
+ */
+esp_err_t mhz19b_get_auto_calibration(
+	mhz19b_dev_t *dev, bool *calibration_on); // (NOT DOCUMENTED)
+
+/**
+ * @brief Start Zero Point Calibration manually at 400ppm
+ *
+ * @details
+ *      The sensor must be powered-up for at least 20 minutes in fresh air at 400ppm room
+ *      temperature. Then call this function once to execute self calibration.\n
+ *      Recommended to use this function when auto calibration is off.
+ *
+ * @param dev Pointer to the sensor device data structure
+ *
+ * @return ESP_OK on success
+ */
+esp_err_t mhz19b_start_calibration(mhz19b_dev_t *dev);
+
+/**
+ * @brief Send serial command to sensor and read response
+ *
+ * @details
+ *      Send command to sensor and read response, protected with a receive timeout.\n
+ *      Result is available in the device descriptor buffer.
+ *
+ * @param dev Pointer to the sensor device data structure
+ * @param cmd Command Byte
+ * @param b3 Byte 3 (default 0)
+ * @param b4 Byte 4 (default 0)
+ * @param b5 Byte 5 (default 0)
+ * @param b6 Byte 6 (default 0)
+ * @param b7 Byte 7 (default 0)
+ */
+esp_err_t mhz19b_send_command(mhz19b_dev_t *dev, uint8_t cmd,
+							  uint8_t b3, uint8_t b4, uint8_t b5,
+							  uint8_t b6, uint8_t b7);
+
+/**
+ * @brief Calculate CRC on 8 data Bytes buffer
+ *
+ * @param data Buffer pointer to calculate CRC.
+ * @return Calculated 8-bit CRC.
+ */
+uint8_t mhz19b_calc_crc(uint8_t *data);
+
+#ifdef __cplusplus
+}
+#endif /* End of CPP guard */
+
+/**@}*/
+
+#endif /* __MHZ19B_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ms5611/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,27 @@
+---
+components:
+  - name: ms5611
+    description: Driver for barometic pressure sensor MS5611-01BA03
+    group: pressure
+    groups:
+      - name: temperature
+    code_owners: UncleRus
+    depends:
+      - i2cdev
+      - log
+      - esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2018
+      - author:
+          name: BernhardG
+        year: 2016
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ms5611/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS ms5611.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ms5611/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,27 @@
+Copyright (c) 2016 Bernhard Guillon <Bernhard.Guillon@begu.org>
+Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ms5611/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ms5611/ms5611.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,261 @@
+/*
+ * Copyright (c) 2016 Bernhard Guillon <Bernhard.Guillon@begu.org>
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file ms5611.c
+ *
+ * ESP-IDF driver for barometric pressure sensor MS5611-01BA03
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2016 Bernhard Guillon <Bernhard.Guillon@begu.org>\n
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+
+#include <esp_idf_lib_helpers.h>
+#include "ms5611.h"
+
+#include <stddef.h>
+#include <esp_system.h>
+#include <esp_log.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <ets_sys.h>
+
+#define I2C_FREQ_HZ 400000 // 400 kHz
+
+#define CMD_CONVERT_D1 0x40
+#define CMD_CONVERT_D2 0x50
+#define CMD_ADC_READ   0x00
+#define CMD_RESET      0x1E
+
+#define PROM_ADDR_SENS     0xa2
+#define PROM_ADDR_OFF      0xa4
+#define PROM_ADDR_TCS      0xa6
+#define PROM_ADDR_TCO      0xa8
+#define PROM_ADDR_T_REF    0xaa
+#define PROM_ADDR_TEMPSENS 0xac
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+static const char *TAG = "ms5611";
+
+static inline esp_err_t send_command(ms5611_t *dev, uint8_t cmd)
+{
+    return i2c_dev_write(&dev->i2c_dev, NULL, 0, &cmd, 1);
+}
+
+static inline uint16_t shuffle(uint16_t val)
+{
+    return ((val & 0xff00) >> 8) | ((val & 0xff) << 8);
+}
+
+static inline esp_err_t read_prom(ms5611_t *dev)
+{
+    uint16_t tmp;
+
+    // FIXME calculate CRC (AN502)
+
+    CHECK(i2c_dev_read_reg(&dev->i2c_dev, PROM_ADDR_SENS, &tmp, 2));
+    dev->config_data.sens = shuffle(tmp);
+    CHECK(i2c_dev_read_reg(&dev->i2c_dev, PROM_ADDR_OFF, &tmp, 2));
+    dev->config_data.off = shuffle(tmp);
+    CHECK(i2c_dev_read_reg(&dev->i2c_dev, PROM_ADDR_TCS, &tmp, 2));
+    dev->config_data.tcs = shuffle(tmp);
+    CHECK(i2c_dev_read_reg(&dev->i2c_dev, PROM_ADDR_TCO, &tmp, 2));
+    dev->config_data.tco = shuffle(tmp);
+    CHECK(i2c_dev_read_reg(&dev->i2c_dev, PROM_ADDR_T_REF, &tmp, 2));
+    dev->config_data.t_ref = shuffle(tmp);
+    CHECK(i2c_dev_read_reg(&dev->i2c_dev, PROM_ADDR_TEMPSENS, &tmp, 2));
+    dev->config_data.tempsens = shuffle(tmp);
+
+    return ESP_OK;
+}
+
+static esp_err_t read_adc(ms5611_t *dev, uint32_t *result)
+{
+    uint8_t tmp[3];
+
+    CHECK(i2c_dev_read_reg(&dev->i2c_dev, 0, tmp, 3));
+    *result = (tmp[0] << 16) | (tmp[1] << 8) | tmp[2];
+
+    // If we are to fast the ADC will return 0 instead of the actual result
+    return *result == 0 ? ESP_ERR_INVALID_RESPONSE : ESP_OK;
+}
+
+static void wait_conversion(ms5611_t *dev)
+{
+    uint32_t us = 8220;
+    switch (dev->osr)
+    {
+        case MS5611_OSR_256: us = 500; break;   // 0.5ms
+        case MS5611_OSR_512: us = 1100; break;  // 1.1ms
+        case MS5611_OSR_1024: us = 2100; break; // 2.1ms
+        case MS5611_OSR_2048: us = 4100; break; // 4.1ms
+        case MS5611_OSR_4096: us = 8220; break; // 8.22ms
+    }
+    ets_delay_us(us);
+}
+
+static inline esp_err_t get_raw_temperature(ms5611_t *dev, uint32_t *result)
+{
+    CHECK(send_command(dev, CMD_CONVERT_D2 + dev->osr));
+    wait_conversion(dev);
+    CHECK(read_adc(dev, result));
+
+    return ESP_OK;
+}
+
+static inline esp_err_t get_raw_pressure(ms5611_t *dev, uint32_t *result)
+{
+    CHECK(send_command(dev, CMD_CONVERT_D1 + dev->osr));
+    wait_conversion(dev);
+    CHECK(read_adc(dev, result));
+
+    return ESP_OK;
+}
+
+static esp_err_t ms5611_reset(ms5611_t *dev)
+{
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, send_command(dev, CMD_RESET));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+/////////////////////////Public//////////////////////////////////////
+
+esp_err_t ms5611_init_desc(ms5611_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+
+    if (addr != MS5611_ADDR_CSB_HIGH && addr != MS5611_ADDR_CSB_LOW)
+    {
+        ESP_LOGE(TAG, "Invalid I2C address 0x%02x", addr);
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    dev->i2c_dev.port = port;
+    dev->i2c_dev.addr = addr;
+    dev->i2c_dev.cfg.sda_io_num = sda_gpio;
+    dev->i2c_dev.cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(&dev->i2c_dev);
+}
+
+esp_err_t ms5611_free_desc(ms5611_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(&dev->i2c_dev);
+}
+
+esp_err_t ms5611_init(ms5611_t *dev, ms5611_osr_t osr)
+{
+    CHECK_ARG(dev);
+
+    dev->osr = osr;
+
+    // First of all we need to reset the chip
+    CHECK(ms5611_reset(dev));
+    // Wait a bit for the device to reset
+    vTaskDelay(pdMS_TO_TICKS(10));
+    // Get the config
+    CHECK(read_prom(dev));
+
+    return ESP_OK;
+}
+
+esp_err_t ms5611_get_sensor_data(ms5611_t *dev, int32_t *pressure, float *temperature)
+{
+    CHECK_ARG(dev && pressure && temperature);
+
+    // Second order temperature compensation see datasheet p8
+    uint32_t raw_pressure = 0;
+    uint32_t raw_temperature = 0;
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, get_raw_pressure(dev, &raw_pressure));
+    I2C_DEV_CHECK(&dev->i2c_dev, get_raw_temperature(dev, &raw_temperature));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    // dT = D2 - T_ref = D2 - C5 * 2^8
+    int32_t dt = raw_temperature - ((int32_t)dev->config_data.t_ref << 8);
+    // Actual temperature (-40...85C with 0.01 resolution)
+    // TEMP = 20C +dT * TEMPSENSE =2000 + dT * C6 / 2^23
+    int64_t temp = (2000 + (int64_t)dt * dev->config_data.tempsens / 8388608);
+    // Offset at actual temperature
+    // OFF=OFF_t1 + TCO * dT = OFF_t1(C2) * 2^16 + (C4*dT)/2^7
+    int64_t off = (int64_t)((int64_t)dev->config_data.off * 65536)
+        + (((int64_t)dev->config_data.tco * dt) / 128);
+    // Sensitivity at actual temperature
+    // SENS=SENS_t1 + TCS *dT = SENS_t1(C1) *2^15 + (TCS(C3) *dT)/2^8
+    int64_t sens = (int64_t)(((int64_t)dev->config_data.sens) * 32768)
+        + (((int64_t)dev->config_data.tcs * dt) / 256);
+
+    // Set defaults for temp >= 2000
+    int64_t t_2 = 0;
+    int64_t off_2 = 0;
+    int64_t sens_2 = 0;
+    int64_t help = 0;
+    if (temp < 2000)
+    {
+        // Low temperature
+        t_2 = ((dt * dt) >> 31); // T2 = dT^2/2^31
+        help = (temp - 2000);
+        help = 5 * help * help;
+        off_2 = help >> 1;       // OFF_2  = 5 * (TEMP - 2000)^2/2^1
+        sens_2 = help >> 2;      // SENS_2 = 5 * (TEMP - 2000)^2/2^2
+        if (temp < -1500)
+        {
+            // Very low temperature
+            help = (temp + 1500);
+            help = help * help;
+            off_2 = off_2 + 7 * help;             // OFF_2  = OFF_2 + 7 * (TEMP + 1500)^2
+            sens_2 = sens_2 + ((11 * help) >> 1); // SENS_2 = SENS_2 + 7 * (TEMP + 1500)^2/2^1
+        }
+    }
+
+    temp = temp - t_2;
+    off = off - off_2;
+    sens = sens - sens_2;
+
+    // Temperature compensated pressure (10...1200mbar with 0.01mbar resolution
+    // P = digital pressure value  * SENS - OFF = (D1 * SENS/2^21 -OFF)/2^15
+    *pressure = (int32_t)(((int64_t)raw_pressure * (sens / 0x200000) - off) / 32768);
+    *temperature = (float)temp / 100.0;
+
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ms5611/ms5611.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2016 Bernhard Guillon <Bernhard.Guillon@begu.org>
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file ms5611.h
+ * @defgroup ms5611 ms5611
+ * @{
+ *
+ * ESP-IDF driver for barometric pressure sensor MS5611-01BA03
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2016 Bernhard Guillon <Bernhard.Guillon@begu.org>\n
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __MS5611_H__
+#define __MS5611_H__
+
+#include <stdint.h>
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define MS5611_ADDR_CSB_HIGH    0x76
+#define MS5611_ADDR_CSB_LOW     0x77
+
+/**
+ * Oversampling ratio
+ */
+typedef enum
+{
+    MS5611_OSR_256  = 0x00, //!< 256 samples per measurement
+    MS5611_OSR_512  = 0x02, //!< 512 samples per measurement
+    MS5611_OSR_1024 = 0x04, //!< 1024 samples per measurement
+    MS5611_OSR_2048 = 0x06, //!< 2048 samples per measurement
+    MS5611_OSR_4096 = 0x08  //!< 4096 samples per measurement
+} ms5611_osr_t;
+
+/**
+ * Configuration data
+ */
+typedef struct
+{
+    uint16_t sens;       //!< C1 Pressure sensitivity                             | SENS_t1
+    uint16_t off;        //!< C2 Pressure offset                                  | OFF_t1
+    uint16_t tcs;        //!< C3 Temperature coefficient of pressure sensitivity  | TCS
+    uint16_t tco;        //!< C4 Temperature coefficient of pressure offset       | TCO
+    uint16_t t_ref;      //!< C5 Reference temperature                            | T_ref
+    uint16_t tempsens;   //!< C6 Temperature coefficient of the temperature       | TEMPSENSE
+} ms5611_config_data_t;
+
+/**
+ * Device descriptor
+ */
+typedef struct
+{
+    i2c_dev_t i2c_dev;                //!< I2C device settings
+    ms5611_osr_t osr;                 //!< Oversampling setting
+    ms5611_config_data_t config_data; //!< Device configuration, filled upon initialize
+} ms5611_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev Device descriptor
+ * @param addr I2C address, `MS5611_ADDR_CSB_HIGH` or `MS5611_ADDR_CSB_LOW`
+ * @param port I2C port
+ * @param sda_gpio GPIO pin for SDA
+ * @param scl_gpio GPIO pin for SCL
+ * @return `ESP_OK` on success
+ */
+esp_err_t ms5611_init_desc(ms5611_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t ms5611_free_desc(ms5611_t *dev);
+
+/**
+ * @brief Init MS5611-01BA03
+ *
+ * Reset device and read calibration data
+ *
+ * @param dev Device descriptor
+ * @param osr Oversampling ratio
+ * @return `ESP_OK` on success
+ */
+esp_err_t ms5611_init(ms5611_t *dev, ms5611_osr_t osr);
+
+/**
+ * @brief Measure pressure and temperature
+ *
+ * @param dev Device descriptor
+ * @param[out] pressure Pressure, Pa
+ * @param[out] temperature Temperature, degrees Celsius
+ * @return `ESP_OK` on success
+ */
+esp_err_t ms5611_get_sensor_data(ms5611_t *dev, int32_t *pressure, float *temperature);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __MS5611_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/noise/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,21 @@
+---
+components:
+  - name: noise
+    description: Noise generation functions
+    group: common
+    groups: []
+    code_owners: UncleRus
+    depends:
+      - lib8tion
+    thread_safe: N/A
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: MIT
+    copyrights:
+      - author:
+          name: FastLED
+        year: 2013
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/noise/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS noise.c
+    INCLUDE_DIRS .
+    REQUIRES lib8tion
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/noise/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2013 FastLED
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/noise/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = lib8tion
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/noise/noise.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,491 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013 FastLED
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "noise.h"
+
+#define ALWAYS_INLINE static inline __attribute__((always_inline))
+
+const static uint8_t _p[] = {
+    151, 160, 137,  91,  90,  15, 131,  13, 201,  95,  96,  53, 194, 233,   7, 225,
+    140,  36, 103,  30,  69, 142,   8,  99,  37, 240,  21,  10,  23, 190,   6, 148,
+    247, 120, 234,  75,   0,  26, 197,  62,  94, 252, 219, 203, 117,  35,  11,  32,
+     57, 177,  33,  88, 237, 149,  56,  87, 174,  20, 125, 136, 171, 168,  68, 175,
+     74, 165,  71, 134, 139,  48,  27, 166,  77, 146, 158, 231,  83, 111, 229, 122,
+     60, 211, 133, 230, 220, 105,  92,  41,  55,  46, 245,  40, 244, 102, 143,  54,
+     65,  25,  63, 161,   1, 216,  80,  73, 209,  76, 132, 187, 208,  89,  18, 169,
+    200, 196, 135, 130, 116, 188, 159,  86, 164, 100, 109, 198, 173, 186,   3,  64,
+     52, 217, 226, 250, 124, 123,   5, 202,  38, 147, 118, 126, 255,  82,  85, 212,
+    207, 206,  59, 227,  47,  16,  58,  17, 182, 189,  28,  42, 223, 183, 170, 213,
+    119, 248, 152,   2,  44, 154, 163,  70, 221, 153, 101, 155, 167,  43, 172,   9,
+    129,  22,  39, 253,  19,  98, 108, 110,  79, 113, 224, 232, 178, 185, 112, 104,
+    218, 246,  97, 228, 251,  34, 242, 193, 238, 210, 144,  12, 191, 179, 162, 241,
+     81,  51, 145, 235, 249,  14, 239, 107,  49, 192, 214,  31, 181, 199, 106, 157,
+    184,  84, 204, 176, 115, 121,  50,  45, 127,   4, 150, 254, 138, 236, 205,  93,
+    222, 114,  67,  29,  24,  72, 243, 141, 128, 195,  78,  66, 215,  61, 156, 180,
+    151
+};
+
+#define P(x) _p[(x)]
+
+#define EASE8(x)  (ease8InOutQuad(x))
+#define EASE16(x) (ease16InOutQuad(x))
+
+#define FADE(x)     scale16(x,x)
+#define LERP(a,b,u) lerp15by16(a,b,u)
+
+ALWAYS_INLINE int16_t grad16_3d(uint8_t hash, int16_t x, int16_t y, int16_t z)
+{
+    hash = hash & 15;
+    int16_t u = hash < 8 ? x : y;
+    int16_t v = hash < 4 ? y : hash == 12 || hash == 14 ? x : z;
+    if (hash & 1)
+        u = -u;
+    if (hash & 2)
+        v = -v;
+
+    return avg15(u, v);
+}
+
+ALWAYS_INLINE int16_t grad16_2d(uint8_t hash, int16_t x, int16_t y)
+{
+    hash = hash & 7;
+    int16_t u, v;
+    if (hash < 4)
+    {
+        u = x;
+        v = y;
+    }
+    else
+    {
+        u = y;
+        v = x;
+    }
+    if (hash & 1)
+        u = -u;
+    if (hash & 2)
+        v = -v;
+
+    return avg15(u, v);
+}
+
+ALWAYS_INLINE int16_t grad16_1d(uint8_t hash, int16_t x)
+{
+    hash = hash & 15;
+    int16_t u, v;
+    if (hash > 8)
+    {
+        u = x;
+        v = x;
+    }
+    else if (hash < 4)
+    {
+        u = x;
+        v = 1;
+    }
+    else
+    {
+        u = 1;
+        v = x;
+    }
+    if (hash & 1)
+        u = -u;
+    if (hash & 2)
+        v = -v;
+
+    return avg15(u, v);
+}
+
+ALWAYS_INLINE int8_t selectBasedOnHashBit(uint8_t hash, uint8_t bitnumber, int8_t a, int8_t b)
+{
+    int8_t result;
+    result = (hash & (1 << bitnumber)) ? a : b;
+    return result;
+}
+
+ALWAYS_INLINE int8_t grad8_3d(uint8_t hash, int8_t x, int8_t y, int8_t z)
+{
+    hash &= 0x0f;
+
+    int8_t u, v;
+    u = selectBasedOnHashBit(hash, 3, y, x);
+
+    v = hash < 4 ? y : hash == 12 || hash == 14 ? x : z;
+
+    if (hash & 1)
+        u = -u;
+    if (hash & 2)
+        v = -v;
+
+    return avg7(u, v);
+}
+
+ALWAYS_INLINE int8_t grad8_2d(uint8_t hash, int8_t x, int8_t y)
+{
+    // since the tests below can be done bit-wise on the bottom
+    // three bits, there's no need to mask off the higher bits
+    //  hash = hash & 7;
+    int8_t u, v;
+    if (hash & 4)
+    {
+        u = y;
+        v = x;
+    }
+    else
+    {
+        u = x;
+        v = y;
+    }
+
+    if (hash & 1)
+        u = -u;
+    if (hash & 2)
+        v = -v;
+
+    return avg7(u, v);
+}
+
+ALWAYS_INLINE int8_t grad8_1d(uint8_t hash, int8_t x)
+{
+    // since the tests below can be done bit-wise on the bottom
+    // four bits, there's no need to mask off the higher bits
+    //  hash = hash & 15;
+    int8_t u, v;
+    if (hash & 8)
+    {
+        u = x;
+        v = x;
+    }
+    else
+    {
+        if (hash & 4)
+        {
+            u = 1;
+            v = x;
+        }
+        else
+        {
+            u = x;
+            v = 1;
+        }
+    }
+
+    if (hash & 1)
+        u = -u;
+    if (hash & 2)
+        v = -v;
+
+    return avg7(u, v);
+}
+
+ALWAYS_INLINE int8_t lerp7by8(int8_t a, int8_t b, fract8 frac)
+{
+    int8_t result;
+    if (b > a)
+    {
+        uint8_t delta = b - a;
+        uint8_t scaled = scale8(delta, frac);
+        result = a + scaled;
+    }
+    else
+    {
+        uint8_t delta = a - b;
+        uint8_t scaled = scale8(delta, frac);
+        result = a - scaled;
+    }
+    return result;
+}
+
+int16_t inoise16_3d_raw(uint32_t x, uint32_t y, uint32_t z)
+{
+    // Find the unit cube containing the point
+    uint8_t X = (x >> 16) & 0xFF;
+    uint8_t Y = (y >> 16) & 0xFF;
+    uint8_t Z = (z >> 16) & 0xFF;
+
+    // Hash cube corner coordinates
+    uint8_t A  = P(X) + Y;
+    uint8_t AA = P(A) + Z;
+    uint8_t AB = P(A + 1) + Z;
+    uint8_t B  = P(X + 1) + Y;
+    uint8_t BA = P(B) + Z;
+    uint8_t BB = P(B + 1) + Z;
+
+    // Get the relative position of the point in the cube
+    uint16_t u = x & 0xFFFF;
+    uint16_t v = y & 0xFFFF;
+    uint16_t w = z & 0xFFFF;
+
+    // Get a signed version of the above for the grad function
+    int16_t xx = (u >> 1) & 0x7FFF;
+    int16_t yy = (v >> 1) & 0x7FFF;
+    int16_t zz = (w >> 1) & 0x7FFF;
+    uint16_t N = 0x8000L;
+
+    u = ease16InOutQuad(u);
+    v = ease16InOutQuad(v);
+    w = ease16InOutQuad(w);
+
+    // skip the log fade adjustment for the moment, otherwise here we would
+    // adjust fade values for u,v,w
+    int16_t X1 = lerp15by16(grad16_3d(P(AA), xx, yy, zz), grad16_3d(P(BA), xx - N, yy, zz), u);
+    int16_t X2 = lerp15by16(grad16_3d(P(AB), xx, yy - N, zz), grad16_3d(P(BB), xx - N, yy - N, zz), u);
+    int16_t X3 = lerp15by16(grad16_3d(P(AA + 1), xx, yy, zz - N), grad16_3d(P(BA + 1), xx - N, yy, zz - N), u);
+    int16_t X4 = lerp15by16(grad16_3d(P(AB + 1), xx, yy - N, zz - N), grad16_3d(P(BB + 1), xx - N, yy - N, zz - N), u);
+
+    int16_t Y1 = lerp15by16(X1, X2, v);
+    int16_t Y2 = lerp15by16(X3, X4, v);
+
+    return lerp15by16(Y1, Y2, w);
+}
+
+uint16_t inoise16_3d(uint32_t x, uint32_t y, uint32_t z)
+{
+    int32_t ans = inoise16_3d_raw(x, y, z);
+    ans = ans + 19052L;
+    uint32_t pan = ans;
+    pan *= 440L;
+    return pan >> 8;
+}
+
+int16_t inoise16_2d_raw(uint32_t x, uint32_t y)
+{
+    // Find the unit cube containing the point
+    uint8_t X = x >> 16;
+    uint8_t Y = y >> 16;
+
+    // Hash cube corner coordinates
+    uint8_t A  = P(X) + Y;
+    uint8_t AA = P(A);
+    uint8_t AB = P(A + 1);
+    uint8_t B  = P(X + 1) + Y;
+    uint8_t BA = P(B);
+    uint8_t BB = P(B + 1);
+
+    // Get the relative position of the point in the cube
+    uint16_t u = x & 0xFFFF;
+    uint16_t v = y & 0xFFFF;
+
+    // Get a signed version of the above for the grad function
+    int16_t xx = (u >> 1) & 0x7FFF;
+    int16_t yy = (v >> 1) & 0x7FFF;
+    uint16_t N = 0x8000L;
+
+    u = ease16InOutQuad(u);
+    v = ease16InOutQuad(v);
+
+    int16_t X1 = lerp15by16(grad16_2d(P(AA), xx, yy), grad16_2d(P(BA), xx - N, yy), u);
+    int16_t X2 = lerp15by16(grad16_2d(P(AB), xx, yy - N), grad16_2d(P(BB), xx - N, yy - N), u);
+
+    return lerp15by16(X1,X2,v);
+}
+
+uint16_t inoise16_2d(uint32_t x, uint32_t y)
+{
+    int32_t ans = inoise16_2d_raw(x, y);
+    ans = ans + 17308L;
+    uint32_t pan = ans;
+    pan *= 484L;
+    return (pan >> 8);
+}
+
+int16_t inoise16_1d_raw(uint32_t x)
+{
+    // Find the unit cube containing the point
+    uint8_t X = x >> 16;
+
+    // Hash cube corner coordinates
+    uint8_t A  = P(X);
+    uint8_t AA = P(A);
+    uint8_t B  = P(X + 1);
+    uint8_t BA = P(B);
+
+    // Get the relative position of the point in the cube
+    uint16_t u = x & 0xFFFF;
+
+    // Get a signed version of the above for the grad function
+    int16_t xx = (u >> 1) & 0x7FFF;
+    uint16_t N = 0x8000L;
+
+    u = ease16InOutQuad(u);
+
+    return lerp15by16(grad16_1d(P(AA), xx), grad16_1d(P(BA), xx - N), u);
+}
+
+uint16_t inoise16_1d(uint32_t x)
+{
+    return ((uint32_t)((int32_t)inoise16_1d_raw(x) + 17308L)) << 1;
+}
+
+int8_t inoise8_3d_raw(uint16_t x, uint16_t y, uint16_t z)
+{
+    // Find the unit cube containing the point
+    uint8_t X = x >> 8;
+    uint8_t Y = y >> 8;
+    uint8_t Z = z >> 8;
+
+    // Hash cube corner coordinates
+    uint8_t A  = P(X) + Y;
+    uint8_t AA = P(A) + Z;
+    uint8_t AB = P(A + 1) + Z;
+    uint8_t B  = P(X + 1) +Y;
+    uint8_t BA = P(B) + Z;
+    uint8_t BB = P(B + 1) +Z;
+
+    // Get the relative position of the point in the cube
+    uint8_t u = x;
+    uint8_t v = y;
+    uint8_t w = z;
+
+    // Get a signed version of the above for the grad function
+    int8_t xx = ((uint8_t)x >> 1) & 0x7F;
+    int8_t yy = ((uint8_t)y >> 1) & 0x7F;
+    int8_t zz = ((uint8_t)z >> 1) & 0x7F;
+    uint8_t N = 0x80;
+
+    u = ease8InOutQuad(u);
+    v = ease8InOutQuad(v);
+    w = ease8InOutQuad(w);
+
+    int8_t X1 = lerp7by8(grad8_3d(P(AA), xx, yy, zz), grad8_3d(P(BA), xx - N, yy, zz), u);
+    int8_t X2 = lerp7by8(grad8_3d(P(AB), xx, yy - N, zz), grad8_3d(P(BB), xx - N, yy - N, zz), u);
+    int8_t X3 = lerp7by8(grad8_3d(P(AA + 1), xx, yy, zz - N), grad8_3d(P(BA + 1), xx - N, yy, zz - N), u);
+    int8_t X4 = lerp7by8(grad8_3d(P(AB + 1), xx, yy - N, zz - N), grad8_3d(P(BB + 1), xx - N, yy - N, zz - N), u);
+
+    int8_t Y1 = lerp7by8(X1, X2, v);
+    int8_t Y2 = lerp7by8(X3, X4, v);
+
+    return lerp7by8(Y1, Y2, w);
+}
+
+uint8_t inoise8_3d(uint16_t x, uint16_t y, uint16_t z)
+{
+    int8_t n = inoise8_3d_raw(x, y, z);  // -64..+64
+    n += 64;                             //   0..128
+    return qadd8(n, n);                  //   0..255
+}
+
+int8_t inoise8_2d_raw(uint16_t x, uint16_t y)
+{
+    // Find the unit cube containing the point
+    uint8_t X = x >> 8;
+    uint8_t Y = y >> 8;
+
+    // Hash cube corner coordinates
+    uint8_t A  = P(X)+Y;
+    uint8_t AA = P(A);
+    uint8_t AB = P(A + 1);
+    uint8_t B  = P(X+1)+Y;
+    uint8_t BA = P(B);
+    uint8_t BB = P(B + 1);
+
+    // Get the relative position of the point in the cube
+    uint8_t u = x;
+    uint8_t v = y;
+
+    // Get a signed version of the above for the grad function
+    int8_t xx = ((uint8_t)x >> 1) & 0x7F;
+    int8_t yy = ((uint8_t)y >> 1) & 0x7F;
+    uint8_t N = 0x80;
+
+    u = ease8InOutQuad(u);
+    v = ease8InOutQuad(v);
+
+    int8_t X1 = lerp7by8(grad8_2d(P(AA), xx, yy), grad8_2d(P(BA), xx - N, yy), u);
+    int8_t X2 = lerp7by8(grad8_2d(P(AB), xx, yy - N), grad8_2d(P(BB), xx - N, yy - N), u);
+
+    return lerp7by8(X1, X2, v);
+}
+
+uint8_t inoise8_2d(uint16_t x, uint16_t y)
+{
+    int8_t n = inoise8_2d_raw(x, y);  // -64..+64
+    n += 64;                          //   0..128
+    return qadd8(n, n);               //   0..255
+}
+
+// output range = -64 .. +64
+int8_t inoise8_1d_raw(uint16_t x)
+{
+    // Find the unit cube containing the point
+    uint8_t X = x >> 8;
+
+    // Hash cube corner coordinates
+    uint8_t A  = P(X);
+    uint8_t AA = P(A);
+    uint8_t B  = P(X + 1);
+    uint8_t BA = P(B);
+
+    // Get the relative position of the point in the cube
+    uint8_t u = x;
+
+    // Get a signed version of the above for the grad function
+    int8_t xx = ((uint8_t)x >> 1) & 0x7F;
+    uint8_t N = 0x80;
+
+    u = ease8InOutQuad(u);
+
+    return lerp7by8(grad8_1d(P(AA), xx), grad8_1d(P(BA), xx - N), u);
+}
+
+uint8_t inoise8_1d(uint16_t x)
+{
+    int8_t n = inoise8_1d_raw(x);  // -64..+64
+    n += 64;                       //  0..128
+    return qadd8(n, n);            //  0..255
+}
+
+void fill_raw_noise8(uint8_t *pData, uint8_t num_points, uint8_t octaves, uint16_t x, int scale, uint16_t time)
+{
+    uint32_t _xx = x;
+    uint32_t scx = scale;
+    for (int o = 0; o < octaves; ++o)
+    {
+        for (int i = 0, xx = _xx; i < num_points; ++i, xx += scx)
+            pData[i] = qadd8(pData[i], inoise8_2d(xx, time) >> o);
+
+        _xx <<= 1;
+        scx <<= 1;
+    }
+}
+
+void fill_raw_noise16into8(uint8_t *pData, uint8_t num_points, uint8_t octaves, uint32_t x, int scale, uint32_t time)
+{
+    uint32_t _xx = x;
+    uint32_t scx = scale;
+    for (int o = 0; o < octaves; ++o)
+    {
+        for (int i = 0, xx = _xx; i < num_points; ++i, xx += scx)
+        {
+            uint32_t accum = (inoise16_2d(xx, time)) >> o;
+            accum += (pData[i] << 8);
+            if (accum > 65535)
+            {
+                accum = 65535;
+            }
+            pData[i] = accum >> 8;
+        }
+
+        _xx <<= 1;
+        scx <<= 1;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/noise/noise.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,100 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013 FastLED
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef __NOISE_H__
+#define __NOISE_H__
+
+#include <lib8tion.h>
+
+///@file noise.h
+/// Noise functions provided by the library.
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+///@defgroup Noise Noise functions
+///Perlin noise function definitions
+///@{
+/// @name scaled 16 bit noise functions
+///@{
+/// 16 bit, fixed point implementation of Perlin's Noise.  Coordinates are
+/// 16.16 fixed point values, 32 bit integers with integral coordinates in the high 16
+/// bits and fractional in the low 16 bits, and the function takes 1d, 2d, and 3d coordinate
+/// values.  These functions are scaled to return 0-65535
+uint16_t inoise16_3d(uint32_t x, uint32_t y, uint32_t z);
+uint16_t inoise16_2d(uint32_t x, uint32_t y);
+uint16_t inoise16_1d(uint32_t x);
+///@}
+
+/// @name raw 16 bit noise functions
+///@{
+/// 16 bit raw versions of the noise functions.  These values are not scaled/altered and have
+/// output values roughly in the range (-18k,18k)
+int16_t inoise16_3d_raw(uint32_t x, uint32_t y, uint32_t z);
+int16_t inoise16_2d_raw(uint32_t x, uint32_t y);
+int16_t inoise16_1d_raw(uint32_t x);
+///@}
+
+/// @name 8 bit scaled noise functions
+///@{
+/// 8 bit, fixed point implementation of perlin's Simplex Noise.  Coordinates are
+/// 8.8 fixed point values, 16 bit integers with integral coordinates in the high 8
+/// bits and fractional in the low 8 bits, and the function takes 1d, 2d, and 3d coordinate
+/// values.  These functions are scaled to return 0-255
+uint8_t inoise8_3d(uint16_t x, uint16_t y, uint16_t z);
+uint8_t inoise8_2d(uint16_t x, uint16_t y);
+uint8_t inoise8_1d(uint16_t x);
+///@}
+
+/// @name 8 bit raw noise functions
+///@{
+/// 8 bit raw versions of the noise functions.  These values are not scaled/altered and have
+/// output values roughly in the range (-70,70)
+int8_t inoise8_3d_raw(uint16_t x, uint16_t y, uint16_t z);
+int8_t inoise8_2d_raw(uint16_t x, uint16_t y);
+int8_t inoise8_1d_raw(uint16_t x);
+///@}
+
+///@name raw fill functions
+///@{
+/// Raw noise fill functions - fill into a 1d or 2d array of 8-bit values using either 8-bit noise or 16-bit noise
+/// functions.
+///@param pData the array of data to write into
+///@param num_points the number of points of noise to compute
+///@param octaves the number of octaves to use for noise
+///@param x the x position in the noise field
+///@param y the y position in the noise field for 2d functions
+///@param scalex the scale (distance) between x points when filling in noise
+///@param scaley the scale (distance) between y points when filling in noise
+///@param time the time position for the noise field
+void fill_raw_noise8(uint8_t *pData, uint8_t num_points, uint8_t octaves, uint16_t x, int scale, uint16_t time);
+void fill_raw_noise16into8(uint8_t *pData, uint8_t num_points, uint8_t octaves, uint32_t x, int scale, uint32_t time);
+///@}
+///@}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __NOISE_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/onewire/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,29 @@
+---
+components:
+  - name: onewire
+    description: Bit-banging 1-Wire driver
+    group: common
+    groups: []
+    code_owners: UncleRus
+    depends:
+      # XXX conditional depends
+      - driver
+      - freertos
+      - log
+      - esp_idf_lib_helpers
+    thread_safe: no
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      # XXX what `MIT *` means?
+      - name: MIT
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2016
+      - author:
+          name: zeroday
+        year: 2016
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/onewire/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,11 @@
+if(${IDF_TARGET} STREQUAL esp8266)
+    set(req esp8266 freertos esp_idf_lib_helpers)
+else()
+    set(req driver freertos esp_idf_lib_helpers)
+endif()
+
+idf_component_register(
+    SRCS onewire.c
+    INCLUDE_DIRS .
+    REQUIRES ${req}
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/onewire/Kconfig	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,9 @@
+menu "OneWire"
+
+config ONEWIRE_CRC8_TABLE
+    bool "Fast CRC-8 algorithm"
+    default "y"
+    help
+        Compute a Dallas Semiconductor 8 bit CRC using a CRC table located in flash
+
+endmenu
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/onewire/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,31 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 zeroday nodemcu.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-------------------------------------------------------------------------------
+
+Portions copyright (C) 2000 Dallas Semiconductor Corporation, under the
+following additional terms:
+
+Except as contained in this notice, the name of Dallas Semiconductor
+shall not be used except as stated in the Dallas Semiconductor
+Branding Policy.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/onewire/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,7 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+
+ifdef CONFIG_IDF_TARGET_ESP8266
+COMPONENT_DEPENDS = esp8266 freertos esp_idf_lib_helpers
+else
+COMPONENT_DEPENDS = driver freertos esp_idf_lib_helpers
+endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/onewire/onewire.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,548 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014 zeroday nodemcu.com
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ * -------------------------------------------------------------------------------
+ * Portions copyright (C) 2000 Dallas Semiconductor Corporation, under the
+ * following additional terms:
+ *
+ * Except as contained in this notice, the name of Dallas Semiconductor
+ * shall not be used except as stated in the Dallas Semiconductor
+ * Branding Policy.
+ */
+
+/**
+ * @file onewire.c
+ *
+ * Routines to access devices using the Dallas Semiconductor 1-Wire(tm)
+ * protocol.
+ *
+ * This is a port of a bit-banging one wire driver based on the implementation
+ * from NodeMCU.
+ *
+ * This, in turn, appears to have been based on the PJRC Teensy driver
+ * (https://www.pjrc.com/teensy/td_libs_OneWire.html), by Jim Studt, Paul
+ * Stoffregen, and a host of others.
+ *
+ * The original code is licensed under the MIT license.  The CRC code was taken
+ * (at least partially) from Dallas Semiconductor sample code, which was licensed
+ * under an MIT license with an additional clause (prohibiting inappropriate use
+ * of the Dallas Semiconductor name).  See the accompanying LICENSE file for
+ * details.
+ */
+
+#include <string.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <ets_sys.h>
+#include <esp_idf_lib_helpers.h>
+#include "onewire.h"
+
+#define ONEWIRE_SELECT_ROM 0x55
+#define ONEWIRE_SKIP_ROM   0xcc
+#define ONEWIRE_SEARCH     0xf0
+
+#if HELPER_TARGET_IS_ESP8266
+#define PORT_ENTER_CRITICAL portENTER_CRITICAL()
+#define PORT_EXIT_CRITICAL portEXIT_CRITICAL()
+#define OPEN_DRAIN_MODE GPIO_MODE_OUTPUT_OD
+
+#elif HELPER_TARGET_IS_ESP32
+static portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
+#define PORT_ENTER_CRITICAL portENTER_CRITICAL(&mux)
+#define PORT_EXIT_CRITICAL portEXIT_CRITICAL(&mux)
+#define OPEN_DRAIN_MODE GPIO_MODE_INPUT_OUTPUT_OD
+#else
+#error BUG: Unknown target
+#endif
+
+// Waits up to `max_wait` microseconds for the specified pin to go high.
+// Returns true if successful, false if the bus never comes high (likely
+// shorted).
+static inline bool _onewire_wait_for_bus(gpio_num_t pin, int max_wait)
+{
+    bool state;
+    for (int i = 0; i < ((max_wait + 4) / 5); i++)
+    {
+        if (gpio_get_level(pin))
+            break;
+        ets_delay_us(5);
+    }
+    state = gpio_get_level(pin);
+    // Wait an extra 1us to make sure the devices have an adequate recovery
+    // time before we drive things low again.
+    ets_delay_us(1);
+    return state;
+}
+
+static void setup_pin(gpio_num_t pin, bool open_drain)
+{
+    gpio_set_direction(pin, open_drain ? OPEN_DRAIN_MODE : GPIO_MODE_OUTPUT);
+    gpio_set_pull_mode(pin, GPIO_PULLUP_ONLY);
+}
+
+// Perform the onewire reset function.  We will wait up to 250uS for
+// the bus to come high, if it doesn't then it is broken or shorted
+// and we return false;
+//
+// Returns true if a device asserted a presence pulse, false otherwise.
+//
+bool onewire_reset(gpio_num_t pin)
+{
+    setup_pin(pin, true);
+
+    gpio_set_level(pin, 1);
+    // wait until the wire is high... just in case
+    if (!_onewire_wait_for_bus(pin, 250))
+        return false;
+
+    gpio_set_level(pin, 0);
+    ets_delay_us(480);
+
+    PORT_ENTER_CRITICAL;
+    gpio_set_level(pin, 1); // allow it to float
+    ets_delay_us(70);
+    bool r = !gpio_get_level(pin);
+    PORT_EXIT_CRITICAL;
+
+    // Wait for all devices to finish pulling the bus low before returning
+    if (!_onewire_wait_for_bus(pin, 410))
+        return false;
+
+    return r;
+}
+
+static bool _onewire_write_bit(gpio_num_t pin, bool v)
+{
+    if (!_onewire_wait_for_bus(pin, 10))
+        return false;
+    PORT_ENTER_CRITICAL;
+    if (v)
+    {
+        gpio_set_level(pin, 0);  // drive output low
+        ets_delay_us(10);
+        gpio_set_level(pin, 1);  // allow output high
+        ets_delay_us(55);
+    }
+    else
+    {
+        gpio_set_level(pin, 0);  // drive output low
+        ets_delay_us(65);
+        gpio_set_level(pin, 1); // allow output high
+    }
+    ets_delay_us(1);
+    PORT_EXIT_CRITICAL;
+
+    return true;
+}
+
+static int _onewire_read_bit(gpio_num_t pin)
+{
+    if (!_onewire_wait_for_bus(pin, 10))
+        return -1;
+
+    PORT_ENTER_CRITICAL;
+    gpio_set_level(pin, 0);
+    ets_delay_us(2);
+    gpio_set_level(pin, 1);  // let pin float, pull up will raise
+    ets_delay_us(11);
+    int r = gpio_get_level(pin);  // Must sample within 15us of start
+    ets_delay_us(48);
+    PORT_EXIT_CRITICAL;
+
+    return r;
+}
+
+// Write a byte. The writing code uses open-drain mode and expects the pullup
+// resistor to pull the line high when not driven low.  If you need strong
+// power after the write (e.g. DS18B20 in parasite power mode) then call
+// onewire_power() after this is complete to actively drive the line high.
+//
+bool onewire_write(gpio_num_t pin, uint8_t v)
+{
+    for (uint8_t bitMask = 0x01; bitMask; bitMask <<= 1)
+        if (!_onewire_write_bit(pin, (bitMask & v)))
+            return false;
+
+    return true;
+}
+
+bool onewire_write_bytes(gpio_num_t pin, const uint8_t *buf, size_t count)
+{
+    for (size_t i = 0; i < count; i++)
+        if (!onewire_write(pin, buf[i]))
+            return false;
+
+    return true;
+}
+
+// Read a byte
+//
+int onewire_read(gpio_num_t pin)
+{
+    int r = 0;
+
+    for (uint8_t bitMask = 0x01; bitMask; bitMask <<= 1)
+    {
+        int bit = _onewire_read_bit(pin);
+        if (bit < 0)
+            return -1;
+        else if (bit)
+            r |= bitMask;
+    }
+    return r;
+}
+
+bool onewire_read_bytes(gpio_num_t pin, uint8_t *buf, size_t count)
+{
+    size_t i;
+    int b;
+
+    for (i = 0; i < count; i++)
+    {
+        b = onewire_read(pin);
+        if (b < 0)
+            return false;
+        buf[i] = b;
+    }
+    return true;
+}
+
+bool onewire_select(gpio_num_t pin, onewire_addr_t addr)
+{
+    uint8_t i;
+
+    if (!onewire_write(pin, ONEWIRE_SELECT_ROM))
+        return false;
+
+    for (i = 0; i < 8; i++)
+    {
+        if (!onewire_write(pin, addr & 0xff))
+            return false;
+        addr >>= 8;
+    }
+
+    return true;
+}
+
+bool onewire_skip_rom(gpio_num_t pin)
+{
+    return onewire_write(pin, ONEWIRE_SKIP_ROM);
+}
+
+bool onewire_power(gpio_num_t pin)
+{
+    // Make sure the bus is not being held low before driving it high, or we
+    // may end up shorting ourselves out.
+    if (!_onewire_wait_for_bus(pin, 10))
+        return false;
+
+    setup_pin(pin, false);
+    gpio_set_level(pin, 1);
+
+    return true;
+}
+
+void onewire_depower(gpio_num_t pin)
+{
+    setup_pin(pin, true);
+}
+
+void onewire_search_start(onewire_search_t *search)
+{
+    // reset the search state
+    memset(search, 0, sizeof(*search));
+}
+
+void onewire_search_prefix(onewire_search_t *search, uint8_t family_code)
+{
+    uint8_t i;
+
+    search->rom_no[0] = family_code;
+    for (i = 1; i < 8; i++)
+    {
+        search->rom_no[i] = 0;
+    }
+    search->last_discrepancy = 64;
+    search->last_device_found = false;
+}
+
+// Perform a search. If the next device has been successfully enumerated, its
+// ROM address will be returned.  If there are no devices, no further
+// devices, or something horrible happens in the middle of the
+// enumeration then ONEWIRE_NONE is returned.  Use OneWire::reset_search() to
+// start over.
+//
+// --- Replaced by the one from the Dallas Semiconductor web site ---
+//--------------------------------------------------------------------------
+// Perform the 1-Wire Search Algorithm on the 1-Wire bus using the existing
+// search state.
+// Return 1 : device found, ROM number in ROM_NO buffer
+//        0 : device not found, end of search
+//
+onewire_addr_t onewire_search_next(onewire_search_t *search, gpio_num_t pin)
+{
+    //TODO: add more checking for read/write errors
+    uint8_t id_bit_number;
+    uint8_t last_zero, search_result;
+    int rom_byte_number;
+    int8_t id_bit, cmp_id_bit;
+    onewire_addr_t addr;
+    unsigned char rom_byte_mask;
+    bool search_direction;
+
+    // initialize for search
+    id_bit_number = 1;
+    last_zero = 0;
+    rom_byte_number = 0;
+    rom_byte_mask = 1;
+    search_result = 0;
+
+    // if the last call was not the last one
+    if (!search->last_device_found)
+    {
+        // 1-Wire reset
+        if (!onewire_reset(pin))
+        {
+            // reset the search
+            search->last_discrepancy = 0;
+            search->last_device_found = false;
+            return ONEWIRE_NONE;
+        }
+
+        // issue the search command
+        onewire_write(pin, ONEWIRE_SEARCH);
+
+        // loop to do the search
+        do
+        {
+            // read a bit and its complement
+            id_bit = _onewire_read_bit(pin);
+            cmp_id_bit = _onewire_read_bit(pin);
+
+            if ((id_bit == 1) && (cmp_id_bit == 1))
+                break;
+            else
+            {
+                // all devices coupled have 0 or 1
+                if (id_bit != cmp_id_bit)
+                    search_direction = id_bit;  // bit write value for search
+                else
+                {
+                    // if this discrepancy if before the Last Discrepancy
+                    // on a previous next then pick the same as last time
+                    if (id_bit_number < search->last_discrepancy)
+                        search_direction = ((search->rom_no[rom_byte_number] & rom_byte_mask) > 0);
+                    else
+                        // if equal to last pick 1, if not then pick 0
+                        search_direction = (id_bit_number == search->last_discrepancy);
+
+                    // if 0 was picked then record its position in LastZero
+                    if (!search_direction)
+                        last_zero = id_bit_number;
+                }
+
+                // set or clear the bit in the ROM byte rom_byte_number
+                // with mask rom_byte_mask
+                if (search_direction)
+                    search->rom_no[rom_byte_number] |= rom_byte_mask;
+                else
+                    search->rom_no[rom_byte_number] &= ~rom_byte_mask;
+
+                // serial number search direction write bit
+                _onewire_write_bit(pin, search_direction);
+
+                // increment the byte counter id_bit_number
+                // and shift the mask rom_byte_mask
+                id_bit_number++;
+                rom_byte_mask <<= 1;
+
+                // if the mask is 0 then go to new SerialNum byte rom_byte_number and reset mask
+                if (rom_byte_mask == 0)
+                {
+                    rom_byte_number++;
+                    rom_byte_mask = 1;
+                }
+            }
+        } while (rom_byte_number < 8);  // loop until through all ROM bytes 0-7
+
+        // if the search was successful then
+        if (!(id_bit_number < 65))
+        {
+            // search successful so set last_discrepancy,last_device_found,search_result
+            search->last_discrepancy = last_zero;
+
+            // check for last device
+            if (search->last_discrepancy == 0)
+                search->last_device_found = true;
+
+            search_result = 1;
+        }
+    }
+
+    // if no device found then reset counters so next 'search' will be like a first
+    if (!search_result || !search->rom_no[0])
+    {
+        search->last_discrepancy = 0;
+        search->last_device_found = false;
+        return ONEWIRE_NONE;
+    }
+    else
+    {
+        addr = 0;
+        for (rom_byte_number = 7; rom_byte_number >= 0; rom_byte_number--)
+        {
+            addr = (addr << 8) | search->rom_no[rom_byte_number];
+        }
+        //printf("Ok I found something at %08x%08x...\n", (uint32_t)(addr >> 32), (uint32_t)addr);
+    }
+    return addr;
+}
+
+// The 1-Wire CRC scheme is described in Maxim Application Note 27:
+// "Understanding and Using Cyclic Redundancy Checks with Maxim iButton Products"
+//
+
+#ifdef CONFIG_ONEWIRE_CRC8_TABLE
+// This table comes from Dallas sample code where it is freely reusable,
+// though Copyright (c) 2000 Dallas Semiconductor Corporation
+static const uint8_t dscrc_table[] = {
+    0, 94, 188, 226, 97, 63, 221, 131, 194, 156, 126, 32, 163, 253, 31, 65,
+    157, 195, 33, 127, 252, 162, 64, 30, 95, 1, 227, 189, 62, 96, 130, 220,
+    35, 125, 159, 193, 66, 28, 254, 160, 225, 191, 93, 3, 128, 222, 60, 98,
+    190, 224, 2, 92, 223, 129, 99, 61, 124, 34, 192, 158, 29, 67, 161, 255,
+    70, 24, 250, 164, 39, 121, 155, 197, 132, 218, 56, 102, 229, 187, 89, 7,
+    219, 133, 103, 57, 186, 228, 6, 88, 25, 71, 165, 251, 120, 38, 196, 154,
+    101, 59, 217, 135, 4, 90, 184, 230, 167, 249, 27, 69, 198, 152, 122, 36,
+    248, 166, 68, 26, 153, 199, 37, 123, 58, 100, 134, 216, 91, 5, 231, 185,
+    140, 210, 48, 110, 237, 179, 81, 15, 78, 16, 242, 172, 47, 113, 147, 205,
+    17, 79, 173, 243, 112, 46, 204, 146, 211, 141, 111, 49, 178, 236, 14, 80,
+    175, 241, 19, 77, 206, 144, 114, 44, 109, 51, 209, 143, 12, 82, 176, 238,
+    50, 108, 142, 208, 83, 13, 239, 177, 240, 174, 76, 18, 145, 207, 45, 115,
+    202, 148, 118, 40, 171, 245, 23, 73, 8, 86, 180, 234, 105, 55, 213, 139,
+    87, 9, 235, 181, 54, 104, 138, 212, 149, 203, 41, 119, 244, 170, 72, 22,
+    233, 183, 85, 11, 136, 214, 52, 106, 43, 117, 151, 201, 74, 20, 246, 168,
+    116, 42, 200, 150, 21, 75, 169, 247, 182, 232, 10, 84, 215, 137, 107, 53
+};
+
+//
+// Compute a Dallas Semiconductor 8 bit CRC. These show up in the ROM
+// and the registers.  (note: this might better be done without to
+// table, it would probably be smaller and certainly fast enough
+// compared to all those delayMicrosecond() calls.  But I got
+// confused, so I use this table from the examples.)
+//
+uint8_t onewire_crc8(const uint8_t *data, uint8_t len)
+{
+    uint8_t crc = 0;
+
+    while (len--)
+        crc = dscrc_table[crc ^ *data++];
+
+    return crc;
+}
+#else
+//
+// Compute a Dallas Semiconductor 8 bit CRC directly.
+// this is much slower, but much smaller, than the lookup table.
+//
+uint8_t onewire_crc8(const uint8_t *data, uint8_t len)
+{
+    uint8_t crc = 0;
+
+    while (len--)
+    {
+        uint8_t inbyte = *data++;
+        for (int i = 8; i; i--)
+        {
+            uint8_t mix = (crc ^ inbyte) & 0x01;
+            crc >>= 1;
+            if (mix)
+                crc ^= 0x8C;
+            inbyte >>= 1;
+        }
+    }
+    return crc;
+}
+#endif
+
+// Compute the 1-Wire CRC16 and compare it against the received CRC.
+// Example usage (reading a DS2408):
+//    // Put everything in a buffer so we can compute the CRC easily.
+//    uint8_t buf[13];
+//    buf[0] = 0xF0;    // Read PIO Registers
+//    buf[1] = 0x88;    // LSB address
+//    buf[2] = 0x00;    // MSB address
+//    WriteBytes(net, buf, 3);    // Write 3 cmd bytes
+//    ReadBytes(net, buf+3, 10);  // Read 6 data bytes, 2 0xFF, 2 CRC16
+//    if (!CheckCRC16(buf, 11, &buf[11])) {
+//        // Handle error.
+//    }     
+//          
+// @param input - Array of bytes to checksum.
+// @param len - How many bytes to use.
+// @param inverted_crc - The two CRC16 bytes in the received data.
+//                       This should just point into the received data,
+//                       *not* at a 16-bit integer.
+// @param crc - The crc starting value (optional)
+// @return 1, iff the CRC matches.
+bool onewire_check_crc16(const uint8_t* input, size_t len, const uint8_t* inverted_crc, uint16_t crc_iv)
+{
+    uint16_t crc = ~onewire_crc16(input, len, crc_iv);
+    return (crc & 0xFF) == inverted_crc[0] && (crc >> 8) == inverted_crc[1];
+}
+
+// Compute a Dallas Semiconductor 16 bit CRC.  This is required to check
+// the integrity of data received from many 1-Wire devices.  Note that the
+// CRC computed here is *not* what you'll get from the 1-Wire network,
+// for two reasons:
+//   1) The CRC is transmitted bitwise inverted.
+//   2) Depending on the endian-ness of your processor, the binary
+//      representation of the two-byte return value may have a different
+//      byte order than the two bytes you get from 1-Wire.
+// @param input - Array of bytes to checksum.
+// @param len - How many bytes to use.
+// @param crc - The crc starting value (optional)
+// @return The CRC16, as defined by Dallas Semiconductor.
+uint16_t onewire_crc16(const uint8_t* input, size_t len, uint16_t crc_iv)
+{
+    uint16_t crc = crc_iv;
+    static const uint8_t oddparity[16] = { 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 };
+
+    uint16_t i;
+    for (i = 0; i < len; i++)
+    {
+        // Even though we're just copying a byte from the input,
+        // we'll be doing 16-bit computation with it.
+        uint16_t cdata = input[i];
+        cdata = (cdata ^ crc) & 0xff;
+        crc >>= 8;
+
+        if (oddparity[cdata & 0x0F] ^ oddparity[cdata >> 4])
+            crc ^= 0xC001;
+
+        cdata <<= 6;
+        crc ^= cdata;
+        cdata <<= 1;
+        crc ^= cdata;
+    }
+    return crc;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/onewire/onewire.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,307 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014 zeroday nodemcu.com
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ * -------------------------------------------------------------------------------
+ * Portions copyright (C) 2000 Dallas Semiconductor Corporation, under the
+ * following additional terms:
+ *
+ * Except as contained in this notice, the name of Dallas Semiconductor
+ * shall not be used except as stated in the Dallas Semiconductor
+ * Branding Policy.
+ */
+
+/**
+ * @file onewire.h
+ * @defgroup onewire onewire
+ * @{
+ *
+ * @brief Routines to access devices using the Dallas Semiconductor 1-Wire(tm)
+ *        protocol.
+ *
+ * This is a port of a bit-banging one wire driver based on the implementation
+ * from NodeMCU.
+ *
+ * This, in turn, appears to have been based on the PJRC Teensy driver
+ * (https://www.pjrc.com/teensy/td_libs_OneWire.html), by Jim Studt, Paul
+ * Stoffregen, and a host of others.
+ *
+ * The original code is licensed under the MIT license.  The CRC code was taken
+ * (at least partially) from Dallas Semiconductor sample code, which was licensed
+ * under an MIT license with an additional clause (prohibiting inappropriate use
+ * of the Dallas Semiconductor name).  See the accompanying LICENSE file for
+ * details.
+ */
+#ifndef __ONEWIRE_H__
+#define __ONEWIRE_H__
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <driver/gpio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Type used to hold all 1-Wire device ROM addresses (64-bit)
+ */
+typedef uint64_t onewire_addr_t;
+
+/**
+ * Structure to contain the current state for onewire_search_next(), etc
+ */
+typedef struct
+{
+    uint8_t rom_no[8];
+    uint8_t last_discrepancy;
+    bool last_device_found;
+} onewire_search_t;
+
+/**
+ * ::ONEWIRE_NONE is an invalid ROM address that will never occur in a device
+ * (CRC mismatch), and so can be useful as an indicator for "no-such-device",
+ * etc.
+ */
+#define ONEWIRE_NONE ((onewire_addr_t)(0xffffffffffffffffLL))
+
+/**
+ * @brief Perform a 1-Wire reset cycle.
+ *
+ * @param pin  The GPIO pin connected to the 1-Wire bus.
+ *
+ * @return `true` if at least one device responds with a presence pulse,
+ *         `false` if no devices were detected (or the bus is shorted, etc)
+ */
+bool onewire_reset(gpio_num_t pin);
+
+/**
+ * @brief Issue a 1-Wire "ROM select" command to select a particular device.
+ *
+ * It is necessary to call ::onewire_reset() before calling this function.
+ *
+ * @param pin   The GPIO pin connected to the 1-Wire bus.
+ * @param addr  The ROM address of the device to select
+ *
+ * @return `true` if the "ROM select" command could be successfully issued,
+ *         `false` if there was an error.
+ */
+bool onewire_select(gpio_num_t pin, const onewire_addr_t addr);
+
+/**
+ * @brief Issue a 1-Wire "skip ROM" command to select *all* devices on the bus.
+ *
+ * It is necessary to call ::onewire_reset() before calling this function.
+ *
+ * @param pin   The GPIO pin connected to the 1-Wire bus.
+ *
+ * @return `true` if the "skip ROM" command could be successfully issued,
+ *         `false` if there was an error.
+ */
+bool onewire_skip_rom(gpio_num_t pin);
+
+/**
+ * @brief Write a byte on the onewire bus.
+ *
+ * The writing code uses open-drain mode and expects the pullup resistor to
+ * pull the line high when not driven low. If you need strong power after the
+ * write (e.g. DS18B20 in parasite power mode) then call ::onewire_power()
+ * after this is complete to actively drive the line high.
+ *
+ * @param pin   The GPIO pin connected to the 1-Wire bus.
+ * @param v     The byte value to write
+ *
+ * @return `true` if successful, `false` on error.
+ */
+bool onewire_write(gpio_num_t pin, uint8_t v);
+
+/**
+ * @brief Write multiple bytes on the 1-Wire bus.
+ *
+ * See ::onewire_write() for more info.
+ *
+ * @param pin    The GPIO pin connected to the 1-Wire bus.
+ * @param buf    A pointer to the buffer of bytes to be written
+ * @param count  Number of bytes to write
+ *
+ * @return `true` if all bytes written successfully, `false` on error.
+ */
+bool onewire_write_bytes(gpio_num_t pin, const uint8_t *buf, size_t count);
+
+/**
+ * @brief Read a byte from a 1-Wire device.
+ *
+ * @param pin    The GPIO pin connected to the 1-Wire bus.
+ *
+ * @return the read byte on success, negative value on error.
+ */
+int onewire_read(gpio_num_t pin);
+
+/**
+ * @brief Read multiple bytes from a 1-Wire device.
+ *
+ * @param pin    The GPIO pin connected to the 1-Wire bus.
+ * @param[out] buf   A pointer to the buffer to contain the read bytes
+ * @param count  Number of bytes to read
+ *
+ * @return `true` on success, `false` on error.
+ */
+bool onewire_read_bytes(gpio_num_t pin, uint8_t *buf, size_t count);
+
+/**
+ * @brief Actively drive the bus high to provide extra power for certain
+ *        operations of parasitically-powered devices.
+ *
+ * For parasitically-powered devices which need more power than can be
+ * provided via the normal pull-up resistor, it may be necessary for some
+ * operations to drive the bus actively high.  This function can be used to
+ * perform that operation.
+ *
+ * The bus can be depowered once it is no longer needed by calling
+ * ::onewire_depower(), or it will be depowered automatically the next time
+ * ::onewire_reset() is called to start another command.
+ *
+ * @note Make sure the device(s) you are powering will not pull more current
+ *       than the ESP32/ESP8266 is able to supply via its GPIO pins (this is
+ *       especially important when multiple devices are on the same bus and
+ *       they are all performing a power-intensive operation at the same time
+ *       (i.e. multiple DS18B20 sensors, which have all been given a
+ *       "convert T" operation by using ::onewire_skip_rom())).
+ *
+ * @note This routine will check to make sure that the bus is already high
+ *       before driving it, to make sure it doesn't attempt to drive it high
+ *       while something else is pulling it low (which could cause a reset or
+ *       damage the ESP32/ESP8266).
+ *
+ * @param pin    The GPIO pin connected to the 1-Wire bus.
+ *
+ * @return `true` on success, `false` on error.
+ */
+bool onewire_power(gpio_num_t pin);
+
+/**
+ * @brief Stop forcing power onto the bus.
+ *
+ * You only need to do this if you previously called ::onewire_power() to drive
+ * the bus high and now want to allow it to float instead.  Note that
+ * onewire_reset() will also automatically depower the bus first, so you do
+ * not need to call this first if you just want to start a new operation.
+ *
+ * @param pin    The GPIO pin connected to the 1-Wire bus.
+ */
+void onewire_depower(gpio_num_t pin);
+
+/**
+ * @brief Clear the search state so that it will start from the beginning on
+ *        the next call to ::onewire_search_next().
+ *
+ * @param[out] search  The onewire_search_t structure to reset.
+ */
+void onewire_search_start(onewire_search_t *search);
+
+/**
+ * @brief Setup the search to search for devices with the specified
+ *        "family code".
+ *
+ * @param[out] search       The onewire_search_t structure to update.
+ * @param family_code   The "family code" to search for.
+ */
+void onewire_search_prefix(onewire_search_t *search, uint8_t family_code);
+
+/**
+ * @brief Search for the next device on the bus.
+ *
+ * The order of returned device addresses is deterministic. You will always
+ * get the same devices in the same order.
+ *
+ * @note It might be a good idea to check the CRC to make sure you didn't get
+ *       garbage.
+ *
+ * @return the address of the next device on the bus, or ::ONEWIRE_NONE if
+ *         there is no next address. ::ONEWIRE_NONE might also mean that
+ *         the bus is shorted, there are no devices, or you have already
+ *         retrieved all of them.
+ */
+onewire_addr_t onewire_search_next(onewire_search_t *search, gpio_num_t pin);
+
+/**
+ * @brief Compute a Dallas Semiconductor 8 bit CRC.
+ *
+ * These are used in the ROM address and scratchpad registers to verify the
+ * transmitted data is correct.
+ */
+uint8_t onewire_crc8(const uint8_t *data, uint8_t len);
+
+/**
+ * @brief Compute the 1-Wire CRC16 and compare it against the received CRC.
+ *
+ * Example usage (reading a DS2408):
+ * @code{.c}
+ *     // Put everything in a buffer so we can compute the CRC easily.
+ *     uint8_t buf[13];
+ *     buf[0] = 0xF0;    // Read PIO Registers
+ *     buf[1] = 0x88;    // LSB address
+ *     buf[2] = 0x00;    // MSB address
+ *     onewire_write_bytes(pin, buf, 3);    // Write 3 cmd bytes
+ *     onewire_read_bytes(pin, buf+3, 10);  // Read 6 data bytes, 2 0xFF, 2 CRC16
+ *     if (!onewire_check_crc16(buf, 11, &buf[11])) {
+ *         // TODO: Handle error.
+ *     }
+ * @endcode
+ *
+ * @param input         Array of bytes to checksum.
+ * @param len           Number of bytes in `input`
+ * @param inverted_crc  The two CRC16 bytes in the received data.
+ *                      This should just point into the received data,
+ *                      *not* at a 16-bit integer.
+ * @param crc_iv        The crc starting value (optional)
+ *
+ * @return `true` if the CRC matches, `false` otherwise.
+ */
+bool onewire_check_crc16(const uint8_t* input, size_t len, const uint8_t* inverted_crc, uint16_t crc_iv);
+
+/**
+ * @brief Compute a Dallas Semiconductor 16 bit CRC.
+ *
+ * This is required to check the integrity of data received from many 1-Wire
+ * devices.  Note that the CRC computed here is *not* what you'll get from the
+ * 1-Wire network, for two reasons:
+ *
+ *   1. The CRC is transmitted bitwise inverted.
+ *   2. Depending on the endian-ness of your processor, the binary
+ *      representation of the two-byte return value may have a different
+ *      byte order than the two bytes you get from 1-Wire.
+ *
+ * @param input   Array of bytes to checksum.
+ * @param len     How many bytes are in `input`.
+ * @param crc_iv  The crc starting value (optional)
+ *
+ * @return the CRC16, as defined by Dallas Semiconductor.
+ */
+uint16_t onewire_crc16(const uint8_t* input, size_t len, uint16_t crc_iv);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif  /* __ONEWIRE_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pca9557/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,23 @@
+---
+components:
+  - name: pca9557
+    description: Driver for PCA9537/PCA9557/TCA9534 remote 4/8-bit I/O expanders for I2C-bus
+    group: gpio
+    groups: []
+    code_owners: UncleRus
+    depends:
+      - i2cdev
+      - log
+      - esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2021
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pca9557/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS pca9557.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pca9557/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pca9557/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pca9557/pca9557.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file pca9557.c
+ *
+ * ESP-IDF driver for PCA9537/PCA9557/TCA9534 remote 4/8-bit I/O expanders for I2C-bus
+ *
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+
+#include <esp_idf_lib_helpers.h>
+#include "pca9557.h"
+
+#define I2C_FREQ_HZ 400000
+
+#define REG_IN   0x00
+#define REG_OUT  0x01
+#define REG_POL  0x02
+#define REG_CONF 0x03
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+static esp_err_t read_reg_8(i2c_dev_t *dev, uint8_t reg, uint8_t *val)
+{
+    CHECK_ARG(dev && val);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, reg, val, 1));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+static esp_err_t write_reg_8(i2c_dev_t *dev, uint8_t reg, uint8_t val)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, reg, &val, 1));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+static esp_err_t read_bit(i2c_dev_t *dev, uint8_t reg, uint8_t bit, uint32_t *val)
+{
+    CHECK_ARG(dev && val);
+
+    uint8_t v;
+    CHECK(read_reg_8(dev, reg, &v));
+    *val = v & BIT(bit) ? 1 : 0;
+
+    return ESP_OK;
+}
+
+static esp_err_t write_bit(i2c_dev_t *dev, uint8_t reg, uint8_t bit, uint32_t val)
+{
+    CHECK_ARG(dev);
+
+    uint8_t v;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, reg, &v, 1));
+    v = (v & ~BIT(bit)) | (val ? BIT(bit) : 0);
+    I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, reg, &v, 1));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+esp_err_t pca9557_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev && (
+            (addr & PCA9557_I2C_ADDR_BASE) == PCA9557_I2C_ADDR_BASE ||
+            (addr & TCA9534_I2C_ADDR_BASE) == TCA9534_I2C_ADDR_BASE ||
+            addr == PCA9537_I2C_ADDR));
+
+    dev->port = port;
+    dev->addr = addr;
+    dev->cfg.sda_io_num = sda_gpio;
+    dev->cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(dev);
+}
+
+esp_err_t pca9557_free_desc(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(dev);
+}
+
+esp_err_t pca9557_port_get_mode(i2c_dev_t *dev, uint8_t *mode)
+{
+    return read_reg_8(dev, REG_CONF, mode);
+}
+
+esp_err_t pca9557_port_set_mode(i2c_dev_t *dev, uint8_t mode)
+{
+    return write_reg_8(dev, REG_CONF, mode);
+}
+
+esp_err_t pca9557_port_get_polarity(i2c_dev_t *dev, uint8_t *pol)
+{
+    return read_reg_8(dev, REG_POL, pol);
+}
+
+esp_err_t pca9557_port_set_polarity(i2c_dev_t *dev, uint8_t pol)
+{
+    return write_reg_8(dev, REG_POL, pol);
+}
+
+esp_err_t pca9557_port_read(i2c_dev_t *dev, uint8_t *val)
+{
+    return read_reg_8(dev, REG_IN, val);
+}
+
+esp_err_t pca9557_port_write(i2c_dev_t *dev, uint8_t val)
+{
+    return write_reg_8(dev, REG_OUT, val);
+}
+
+esp_err_t pca9557_get_mode(i2c_dev_t *dev, uint8_t pin, pca9557_mode_t *mode)
+{
+    return read_bit(dev, REG_CONF, pin, (uint32_t *)mode);
+}
+
+esp_err_t pca9557_set_mode(i2c_dev_t *dev, uint8_t pin, pca9557_mode_t mode)
+{
+    return write_bit(dev, REG_CONF, pin, mode);
+}
+
+esp_err_t pca9557_get_level(i2c_dev_t *dev, uint8_t pin, uint32_t *val)
+{
+    return read_bit(dev, REG_IN, pin, val);
+}
+
+esp_err_t pca9557_set_level(i2c_dev_t *dev, uint8_t pin, uint32_t val)
+{
+    return write_bit(dev, REG_OUT, pin, val);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pca9557/pca9557.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file pca9557.h
+ * @defgroup pca9557 pca9557
+ * @{
+ *
+ * ESP-IDF driver for PCA9537/PCA9557/TCA9534 remote 4/8-bit I/O expanders for I2C-bus
+ *
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __PCA9557_H__
+#define __PCA9557_H__
+
+#include <stddef.h>
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define PCA9537_I2C_ADDR      0x49 ///< I2C address for PCA9537
+#define PCA9557_I2C_ADDR_BASE 0x18 ///< Base I2C address for PCA9557
+#define TCA9534_I2C_ADDR_BASE 0x20 ///< Base I2C address for TCA9534
+
+/**
+ * Pin modes (directions)
+ */
+typedef enum {
+    PCA9557_MODE_OUTPUT = 0,
+    PCA9557_MODE_INPUT,
+} pca9557_mode_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * Default SCL frequency is 400kHz
+ *
+ * @param dev       Pointer to I2C device descriptor
+ * @param port      I2C port number
+ * @param addr      I2C address (`0b0011<A2><A1><A0>` for PCA9557/`PCA9537_I2C_ADDR` for PCA9537, `TCA9534_I2C_ADDR_BASE` for TCA9534)
+ * @param sda_gpio  SDA GPIO
+ * @param scl_gpio  SCL GPIO
+ * @return `ESP_OK` on success
+ */
+esp_err_t pca9557_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev       Pointer to I2C device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t pca9557_free_desc(i2c_dev_t *dev);
+
+/**
+ * @brief Get directions of I/O pins
+ *
+ * 0 - output, 1 - input for each bit in `mode`
+ *
+ * @param dev       Pointer to device descriptor
+ * @param[out] mode I/O directions
+ * @return `ESP_OK` on success
+ */
+esp_err_t pca9557_port_get_mode(i2c_dev_t *dev, uint8_t *mode);
+
+/**
+ * @brief Set directions of I/O pins
+ *
+ * 0 - output, 1 - input for each bit in `mode`
+ *
+ * @param dev       Pointer to device descriptor
+ * @param mode      I/O directions
+ * @return `ESP_OK` on success
+ */
+esp_err_t pca9557_port_set_mode(i2c_dev_t *dev, uint8_t mode);
+
+/**
+ * @brief Get input polarity settings
+ *
+ * 0 - normal input polarity, 1 - inverted for each bit in `pol`
+ *
+ * @param dev       Pointer to device descriptor
+ * @param[out] pol  Input polarity settings
+ * @return `ESP_OK` on success
+ */
+esp_err_t pca9557_port_get_polarity(i2c_dev_t *dev, uint8_t *pol);
+
+/**
+ * @brief Set input polarity settings
+ *
+ * 0 - normal input polarity, 1 - inverted for each bit in `pol`
+ *
+ * @param dev       Pointer to device descriptor
+ * @param pol       Input polarity settings
+ * @return `ESP_OK` on success
+ */
+esp_err_t pca9557_port_set_polarity(i2c_dev_t *dev, uint8_t pol);
+
+/**
+ * @brief Read I/O port value
+ *
+ * @param dev       Pointer to I2C device descriptor
+ * @param[out] val  8-bit GPIO port value for PCA9557 or 4-bit port value for PCA9537
+ * @return `ESP_OK` on success
+ */
+esp_err_t pca9557_port_read(i2c_dev_t *dev, uint8_t *val);
+
+/**
+ * @brief Write value to I/O port
+ *
+ * @param dev     Pointer to I2C device descriptor
+ * @param val     8-bit GPIO port value for PCA9557 or 4-bit port value for PCA9537
+ * @return ESP_OK on success
+ */
+esp_err_t pca9557_port_write(i2c_dev_t *dev, uint8_t val);
+
+/**
+ * @brief Read I/O pin mode
+ *
+ * @param dev       Pointer to device descriptor
+ * @param pin       Pin number, 0..7 for PCA9557, 0..3 for PC9537
+ * @param[out] mode Pin mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t pca9557_get_mode(i2c_dev_t *dev, uint8_t pin, pca9557_mode_t *mode);
+
+/**
+ * @brief Set I/O pin mode
+ *
+ * @param dev      Pointer to device descriptor
+ * @param pin      Pin number, 0..7 for PCA9557, 0..3 for PC9537
+ * @param mode     Pin mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t pca9557_set_mode(i2c_dev_t *dev, uint8_t pin, pca9557_mode_t mode);
+
+/**
+ * @brief Read I/O pin level
+ *
+ * @param dev      Pointer to device descriptor
+ * @param pin      Pin number, 0..7 for PCA9557, 0..3 for PC9537
+ * @param[out] val 1 if pin currently in high state, 0 otherwise
+ * @return `ESP_OK` on success
+ */
+esp_err_t pca9557_get_level(i2c_dev_t *dev, uint8_t pin, uint32_t *val);
+
+/**
+ * @brief Set I/O pin level
+ *
+ * Pin must be set up as output
+ *
+ * @param dev      Pointer to device descriptor
+ * @param pin      Pin number, 0..7 for PCA9557, 0..3 for PC9537
+ * @param val      Pin level. 1 - high, 0 - low
+ * @return `ESP_OK` on success
+ */
+esp_err_t pca9557_set_level(i2c_dev_t *dev, uint8_t pin, uint32_t val);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __PCA9557_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pca9685/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,23 @@
+---
+components:
+  - name: pca9685
+    description: Driver for 16-channel, 12-bit PWM PCA9685
+    group: misc
+    groups: []
+    code_owners: UncleRus
+    depends:
+      - i2cdev
+      - log
+      - esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2016
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pca9685/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS pca9685.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pca9685/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+Copyright 2016, 2018 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pca9685/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pca9685/pca9685.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,372 @@
+/*
+ * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file pca9685.c
+ *
+ * ESP-IDF driver for 16-channel, 12-bit PWM PCA9685
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+
+#include "pca9685.h"
+#include <esp_idf_lib_helpers.h>
+#include <inttypes.h>
+#include <esp_system.h>
+#include <esp_log.h>
+#include <ets_sys.h>
+
+#define I2C_FREQ_HZ 1000000 // 1 Mhz
+
+#define REG_MODE1      0x00
+#define REG_MODE2      0x01
+#define REG_SUBADR1    0x02
+#define REG_SUBADR2    0x03
+#define REG_SUBADR3    0x04
+#define REG_ALLCALLADR 0x05
+#define REG_LEDX       0x06
+#define REG_ALL_LED    0xfa
+#define REG_PRE_SCALE  0xfe
+
+#define MODE1_RESTART (1 << 7)
+#define MODE1_EXTCLK  (1 << 6)
+#define MODE1_AI      (1 << 5)
+#define MODE1_SLEEP   (1 << 4)
+
+#define MODE1_SUB_BIT 3
+
+#define MODE2_INVRT   (1 << 4)
+#define MODE2_OUTDRV  (1 << 2)
+
+#define LED_FULL_ON_OFF (1 << 4)
+
+#define REG_LED_N(x)  (REG_LEDX + (x) * 4)
+#define OFFS_REG_LED_ON  1
+#define OFFS_REG_LED_OFF 3
+
+#define INTERNAL_FREQ 25000000
+
+#define MIN_PRESCALER 0x03
+#define MAX_PRESCALER 0xff
+#define MAX_SUBADDR   2
+
+#define WAKEUP_DELAY_US 500
+
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+#define CHECK_ARG_LOGE(VAL, msg, ...) do { if (!(VAL)) { ESP_LOGE(TAG, msg, ## __VA_ARGS__); return ESP_ERR_INVALID_ARG; } } while (0)
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+
+static const char *TAG = "pca9685";
+
+inline static uint32_t round_div(uint32_t x, uint32_t y)
+{
+    return (x + y / 2) / y;
+}
+
+inline static esp_err_t write_reg(i2c_dev_t *dev, uint8_t reg, uint8_t val)
+{
+    return i2c_dev_write_reg(dev, reg, &val, 1);
+}
+
+inline static esp_err_t read_reg(i2c_dev_t *dev, uint8_t reg, uint8_t *val)
+{
+    return i2c_dev_read_reg(dev, reg, val, 1);
+}
+
+static esp_err_t update_reg(i2c_dev_t *dev, uint8_t reg, uint8_t mask, uint8_t val)
+{
+    uint8_t v;
+
+    CHECK(read_reg(dev, reg, &v));
+    v = (v & ~mask) | val;
+    CHECK(write_reg(dev, reg, v));
+
+    return ESP_OK;
+}
+
+static esp_err_t dev_sleep(i2c_dev_t *dev, bool sleep)
+{
+    CHECK(update_reg(dev, REG_MODE1, MODE1_SLEEP, sleep ? MODE1_SLEEP : 0));
+    if (!sleep)
+        ets_delay_us(WAKEUP_DELAY_US);
+
+    return ESP_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/// Public
+
+esp_err_t pca9685_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+
+    dev->port = port;
+    dev->addr = addr;
+    dev->cfg.sda_io_num = sda_gpio;
+    dev->cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(dev);
+}
+
+esp_err_t pca9685_free_desc(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(dev);
+}
+
+esp_err_t pca9685_init(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    // Enable autoincrement
+    I2C_DEV_CHECK(dev, update_reg(dev, REG_MODE1, MODE1_AI, MODE1_AI));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t pca9685_set_subaddr(i2c_dev_t *dev, uint8_t num, uint8_t subaddr, bool enable)
+{
+    CHECK_ARG(dev);
+    CHECK_ARG_LOGE(num <= MAX_SUBADDR, "Invalid subadress number (%d), must be in (0..2)", num);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, write_reg(dev, REG_SUBADR1 + num, subaddr << 1));
+
+    uint8_t mask = 1 << (MODE1_SUB_BIT - num);
+    I2C_DEV_CHECK(dev, update_reg(dev, REG_MODE1, mask, enable ? mask : 0));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t pca9685_restart(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    uint8_t mode;
+    I2C_DEV_CHECK(dev, read_reg(dev, REG_MODE1, &mode));
+    if (mode & MODE1_RESTART)
+    {
+        I2C_DEV_CHECK(dev, write_reg(dev, REG_MODE1, mode & ~MODE1_SLEEP));
+        ets_delay_us(WAKEUP_DELAY_US);
+    }
+    I2C_DEV_CHECK(dev, write_reg(dev, REG_MODE1, (mode & ~MODE1_SLEEP) | MODE1_RESTART));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t pca9685_is_sleeping(i2c_dev_t *dev, bool *sleeping)
+{
+    CHECK_ARG(dev && sleeping);
+
+    uint8_t v;
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, read_reg(dev, REG_MODE1, &v));
+    I2C_DEV_GIVE_MUTEX(dev);
+    *sleeping = v & MODE1_SLEEP;
+
+    return ESP_OK;
+}
+
+esp_err_t pca9685_sleep(i2c_dev_t *dev, bool sleep)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, dev_sleep(dev, sleep));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t pca9685_is_output_inverted(i2c_dev_t *dev, bool *inv)
+{
+    CHECK_ARG(dev && inv);
+
+    uint8_t v;
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, read_reg(dev, REG_MODE2, &v));
+    I2C_DEV_GIVE_MUTEX(dev);
+    *inv = v & MODE2_INVRT;
+
+    return ESP_OK;
+}
+
+esp_err_t pca9685_set_output_inverted(i2c_dev_t *dev, bool inverted)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, update_reg(dev, REG_MODE2, MODE2_INVRT, inverted ? MODE2_INVRT : 0));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t pca9685_get_output_open_drain(i2c_dev_t *dev, bool *od)
+{
+    CHECK_ARG(dev && od);
+
+    uint8_t v;
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, read_reg(dev, REG_MODE2, &v));
+    I2C_DEV_GIVE_MUTEX(dev);
+    *od = v & MODE2_OUTDRV;
+
+    return ESP_OK;
+}
+
+esp_err_t pca9685_set_output_open_drain(i2c_dev_t *dev, bool od)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, update_reg(dev, REG_MODE2, MODE2_OUTDRV, od ? 0 : MODE2_OUTDRV));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t pca9685_get_prescaler(i2c_dev_t *dev, uint8_t *prescaler)
+{
+    CHECK_ARG(dev && prescaler);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, read_reg(dev, REG_PRE_SCALE, prescaler));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t pca9685_set_prescaler(i2c_dev_t *dev, uint8_t prescaler)
+{
+    CHECK_ARG(dev);
+    CHECK_ARG_LOGE(prescaler >= MIN_PRESCALER,
+            "Invalid prescaler value: (%" PRIu8 "), must be >= 3", prescaler);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, dev_sleep(dev, true));
+    I2C_DEV_CHECK(dev, write_reg(dev, REG_PRE_SCALE, prescaler));
+    I2C_DEV_CHECK(dev, dev_sleep(dev, false));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t pca9685_get_pwm_frequency(i2c_dev_t *dev, uint16_t *freq)
+{
+    CHECK_ARG(dev);
+    CHECK_ARG(freq);
+
+    uint8_t prescale;
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, read_reg(dev, REG_PRE_SCALE, &prescale));
+    I2C_DEV_GIVE_MUTEX(dev);
+    *freq = INTERNAL_FREQ / ((uint32_t)PCA9685_MAX_PWM_VALUE * (prescale + 1));
+
+    return ESP_OK;
+}
+
+esp_err_t pca9685_set_pwm_frequency(i2c_dev_t *dev, uint16_t freq)
+{
+    uint32_t prescaler = round_div(INTERNAL_FREQ, (uint32_t)PCA9685_MAX_PWM_VALUE * freq) - 1;
+    CHECK_ARG_LOGE(prescaler >= MIN_PRESCALER && prescaler <= MAX_PRESCALER,
+            "Invalid prescaler value (%" PRIu32 "), must be in (%d..%d)", prescaler,
+            MIN_PRESCALER, MAX_PRESCALER);
+    return pca9685_set_prescaler(dev, prescaler);
+}
+
+esp_err_t pca9685_set_pwm_value(i2c_dev_t *dev, uint8_t channel, uint16_t val)
+{
+    CHECK_ARG(dev);
+    CHECK_ARG_LOGE(channel <= PCA9685_CHANNEL_ALL,
+            "Invalid channel %d, must be in (0..%d)", channel, PCA9685_CHANNEL_ALL);
+    CHECK_ARG_LOGE(val <= PCA9685_MAX_PWM_VALUE,
+            "Invalid PWM value %d, must be in (0..PCA9685_MAX_PWM_VALUE)", val);
+
+    uint8_t reg = channel == PCA9685_CHANNEL_ALL ? REG_ALL_LED : REG_LED_N(channel);
+
+    bool full_on = val >= PCA9685_MAX_PWM_VALUE;
+    bool full_off = val == 0;
+
+    uint16_t raw = full_on ? 4095 : val;
+
+    uint8_t buf[4] = {
+        0,
+        full_on ? LED_FULL_ON_OFF : 0,
+        raw,
+        full_off ? LED_FULL_ON_OFF | (raw >> 8) : raw >> 8
+    };
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, reg, buf, 4));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t pca9685_set_pwm_values(i2c_dev_t *dev, uint8_t first_ch, uint8_t channels,
+        const uint16_t *values)
+{
+    CHECK_ARG(values);
+    CHECK_ARG_LOGE(channels > 0 && first_ch + channels - 1 < PCA9685_CHANNEL_ALL,
+            "Invalid first_ch or channels: (%d, %d)", first_ch, channels);
+
+
+    size_t size = channels * 4;
+    uint8_t buf[size];
+    for (uint8_t ch = first_ch; ch < first_ch + channels; ch++)
+    {
+        bool full_on = values[ch] >= PCA9685_MAX_PWM_VALUE;
+        bool full_off = values[ch] == 0;
+
+        uint16_t val = full_on ? 4095 : values[ch];
+
+        buf[ch * 4] = 0;
+        buf[ch * 4 + 1] = full_on ? LED_FULL_ON_OFF : 0;
+        buf[ch * 4 + 2] = val;
+        buf[ch * 4 + 3] = full_off ? LED_FULL_ON_OFF | (val >> 8) : val >> 8;
+    }
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, REG_LED_N(first_ch), buf, size));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pca9685/pca9685.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,251 @@
+/*
+ * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file pca9685.h
+ * @defgroup pca9685 pca9685
+ * @{
+ *
+ * ESP-IDF driver for 16-channel, 12-bit PWM PCA9685
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __PCA9685_H__
+#define __PCA9685_H__
+
+#include <stdbool.h>
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define PCA9685_ADDR_BASE 0x40 //!< Base I2C device address
+
+#define PCA9685_MAX_PWM_VALUE 4096
+
+/**
+ * PWM channel
+ */
+typedef enum
+{
+    PCA9685_CHANNEL_0 = 0,//!< PCA9685_CHANNEL_0
+    PCA9685_CHANNEL_1,    //!< PCA9685_CHANNEL_1
+    PCA9685_CHANNEL_2,    //!< PCA9685_CHANNEL_2
+    PCA9685_CHANNEL_3,    //!< PCA9685_CHANNEL_3
+    PCA9685_CHANNEL_4,    //!< PCA9685_CHANNEL_4
+    PCA9685_CHANNEL_5,    //!< PCA9685_CHANNEL_5
+    PCA9685_CHANNEL_6,    //!< PCA9685_CHANNEL_6
+    PCA9685_CHANNEL_7,    //!< PCA9685_CHANNEL_7
+    PCA9685_CHANNEL_8,    //!< PCA9685_CHANNEL_8
+    PCA9685_CHANNEL_9,    //!< PCA9685_CHANNEL_9
+    PCA9685_CHANNEL_10,   //!< PCA9685_CHANNEL_10
+    PCA9685_CHANNEL_11,   //!< PCA9685_CHANNEL_11
+    PCA9685_CHANNEL_12,   //!< PCA9685_CHANNEL_12
+    PCA9685_CHANNEL_13,   //!< PCA9685_CHANNEL_13
+    PCA9685_CHANNEL_14,   //!< PCA9685_CHANNEL_14
+    PCA9685_CHANNEL_15,   //!< PCA9685_CHANNEL_15
+    PCA9685_CHANNEL_ALL   //!< All channels
+} pca9685_channel_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @param addr PCA9685 address
+ * @param port I2C port number
+ * @param sda_gpio GPIO pin number for SDA
+ * @param scl_gpio GPIO pin number for SCL
+ * @return `ESP_OK` on success
+ */
+esp_err_t pca9685_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t pca9685_free_desc(i2c_dev_t *dev);
+
+/**
+ * @brief Init device
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t pca9685_init(i2c_dev_t *dev);
+
+/**
+ * @brief Setup device subaddress
+ *
+ * See section 7.3.6 if the datasheet
+ *
+ * @param dev Device descriptor
+ * @param num Subaddress number, 0..2
+ * @param subaddr Subaddress, 7 bit
+ * @param enable True to enable subaddress, false to disable
+ * @return `ESP_OK` on success
+ */
+esp_err_t pca9685_set_subaddr(i2c_dev_t *dev, uint8_t num, uint8_t subaddr, bool enable);
+
+/**
+ * @brief Restart device
+ *
+ * See section 7.3.1.1 of the datasheet
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t pca9685_restart(i2c_dev_t *dev);
+
+/**
+ * @brief Check if device is in sleep mode
+ *
+ * @param dev Device descriptor
+ * @param[out] sleeping True if device is sleeping
+ * @return `ESP_OK` on success
+ */
+esp_err_t pca9685_is_sleeping(i2c_dev_t *dev, bool *sleeping);
+
+/**
+ * @brief Switch device to low-power mode or wake it up
+ *
+ * @param dev Device descriptor
+ * @param sleep True for sleep mode, false for wake up
+ * @return `ESP_OK` on success
+ */
+esp_err_t pca9685_sleep(i2c_dev_t *dev, bool sleep);
+
+/**
+ * @brief Get logic inversion of the outputs
+ *
+ * @param dev Device descriptor
+ * @param[out] inv True if outputs are inverted, false otherwise
+ * @return `ESP_OK` on success
+ */
+esp_err_t pca9685_is_output_inverted(i2c_dev_t *dev, bool *inv);
+
+/**
+ * @brief Logically invert outputs
+ *
+ * See section 7.7 of the datasheet
+ *
+ * @param dev Device descriptor
+ * @param inverted True for inverted outputs
+ * @return `ESP_OK` on success
+ */
+esp_err_t pca9685_set_output_inverted(i2c_dev_t *dev, bool inverted);
+
+/**
+ * @brief Get outputs mode
+ *
+ * @param dev Device descriptor
+ * @param[out] od True if outputs are in open drain mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t pca9685_get_output_open_drain(i2c_dev_t *dev, bool *od);
+
+/**
+ * @brief Set outputs mode
+ *
+ * @param dev Device descriptor
+ * @param od True to set open drain mode, false to normal mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t pca9685_set_output_open_drain(i2c_dev_t *dev, bool od);
+
+/**
+ * @brief Get PWM frequency prescaler
+ *
+ * @param dev Device descriptor
+ * @param[out] prescaler Frequency prescaler
+ * @return `ESP_OK` on success
+ */
+esp_err_t pca9685_get_prescaler(i2c_dev_t *dev, uint8_t *prescaler);
+
+/**
+ * @brief Set PWM frequency prescaler
+ *
+ * @param dev Device descriptor
+ * @param prescaler Prescaler value
+ * @return `ESP_OK` on success
+ */
+esp_err_t pca9685_set_prescaler(i2c_dev_t *dev, uint8_t prescaler);
+
+/**
+ * @brief Get PWM frequency
+ *
+ * @param dev Device descriptor
+ * @param[out] freq PWM frequency, Hz
+ * @return `ESP_OK` on success
+ */
+esp_err_t pca9685_get_pwm_frequency(i2c_dev_t *dev, uint16_t *freq);
+
+/**
+ * @brief Set PWM frequency
+ *
+ * @param dev Device descriptor
+ * @param freq PWM frequency, Hz
+ * @return `ESP_OK` on success
+ */
+esp_err_t pca9685_set_pwm_frequency(i2c_dev_t *dev, uint16_t freq);
+
+/**
+ * @brief Set PWM value on output channel
+ *
+ * @param dev Device descriptor
+ * @param channel Channel number, 0..15 or >15 for all channels
+ * @param val PWM value, 0..4096
+ * @return `ESP_OK` on success
+ */
+esp_err_t pca9685_set_pwm_value(i2c_dev_t *dev, uint8_t channel, uint16_t val);
+
+/**
+ * @brief Set PWM values on multiple output channels
+ *
+ * @param dev Device descriptor
+ * @param first_ch First channel, 0..15
+ * @param channels Number of channels to update
+ * @param values Array of the channel values, each 0..4096
+ * @return `ESP_OK` on success
+ */
+esp_err_t pca9685_set_pwm_values(i2c_dev_t *dev, uint8_t first_ch, uint8_t channels,
+        const uint16_t *values);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __PCA9685_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pcf8563/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,23 @@
+---
+components:
+  - name: pcf8563
+    description: Driver for PCF8563 real-time clock/calendar
+    group: rtc
+    groups: []
+    code_owners: UncleRus
+    depends:
+      - i2cdev
+      - log
+      - esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2020
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pcf8563/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS pcf8563.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pcf8563/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pcf8563/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pcf8563/pcf8563.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,370 @@
+/*
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file pcf8563.c
+ *
+ * ESP-IDF driver for PCF8563 real-time clock/calendar
+ *
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#include "pcf8563.h"
+
+#define I2C_FREQ_HZ 400000
+
+#define REG_CTRL_STATUS1 0x00
+#define REG_CTRL_STATUS2 0x01
+#define REG_VL_SECONDS   0x02
+#define REG_MINUTES      0x03
+#define REG_HOURS        0x04
+#define REG_DAYS         0x05
+#define REG_WEEKDAYS     0x06
+#define REG_CENT_MONTHS  0x07
+#define REG_YEARS        0x08
+#define REG_ALARM_MIN    0x09
+#define REG_ALARM_HOUR   0x0a
+#define REG_ALARM_DAY    0x0b
+#define REG_ALARM_WDAY   0x0c
+#define REG_CLKOUT       0x0d
+#define REG_TIMER_CTRL   0x0e
+#define REG_TIMER        0x0f
+
+#define BIT_YEAR_CENTURY 7
+#define BIT_VL 7
+#define BIT_AE 7
+#define BIT_CLKOUT_FD 0
+#define BIT_CLKOUT_FE 7
+
+#define BIT_CTRL_STATUS2_TIE 0
+#define BIT_CTRL_STATUS2_AIE 1
+#define BIT_CTRL_STATUS2_TF  3
+#define BIT_CTRL_STATUS2_AF  4
+
+#define BIT_TIMER_CTRL_TE 7
+
+#define MASK_TIMER_CTRL_TD 0x03
+#define MASK_ALARM 0x7f
+
+#define MASK_MIN  0x7f
+#define MASK_HOUR 0x3f
+#define MASK_MDAY 0x3f
+#define MASK_WDAY 0x07
+#define MASK_MON  0x1f
+
+#define BV(x) ((uint8_t)(1 << (x)))
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(ARG) do { if (!(ARG)) return ESP_ERR_INVALID_ARG; } while (0)
+
+static uint8_t bcd2dec(uint8_t val)
+{
+    return (val >> 4) * 10 + (val & 0x0f);
+}
+
+static uint8_t dec2bcd(uint8_t val)
+{
+    return ((val / 10) << 4) + (val % 10);
+}
+
+static inline esp_err_t read_reg_nolock(i2c_dev_t *dev, uint8_t reg, uint8_t *val)
+{
+    return i2c_dev_read_reg(dev, reg, val, 1);
+}
+
+static inline esp_err_t write_reg_nolock(i2c_dev_t *dev, uint8_t reg, uint8_t val)
+{
+    return i2c_dev_write_reg(dev, reg, &val, 1);
+}
+
+static esp_err_t update_reg_nolock(i2c_dev_t *dev, uint8_t reg, uint8_t mask, uint8_t val)
+{
+    uint8_t v;
+    CHECK(read_reg_nolock(dev, reg, &v));
+    CHECK(write_reg_nolock(dev, reg, (v & ~mask) | val));
+    return ESP_OK;
+}
+
+static esp_err_t read_reg(i2c_dev_t *dev, uint8_t reg, uint8_t *val)
+{
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, read_reg_nolock(dev, reg, val));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+static esp_err_t write_reg(i2c_dev_t *dev, uint8_t reg, uint8_t val)
+{
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, write_reg_nolock(dev, reg, val));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+static esp_err_t update_reg(i2c_dev_t *dev, uint8_t reg, uint8_t mask, uint8_t val)
+{
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, update_reg_nolock(dev, reg, mask, val));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t pcf8563_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+
+    dev->port = port;
+    dev->addr = PCF8563_I2C_ADDR;
+    dev->cfg.sda_io_num = sda_gpio;
+    dev->cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+    return i2c_dev_create_mutex(dev);
+}
+
+esp_err_t pcf8563_free_desc(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(dev);
+}
+
+esp_err_t pcf8563_set_time(i2c_dev_t *dev, struct tm *time)
+{
+    CHECK_ARG(dev && time);
+
+    bool ovf = time->tm_year >= 200;
+
+    uint8_t data[7] = {
+        dec2bcd(time->tm_sec),
+        dec2bcd(time->tm_min),
+        dec2bcd(time->tm_hour),
+        dec2bcd(time->tm_mday),
+        dec2bcd(time->tm_wday),
+        dec2bcd(time->tm_mon + 1) | (ovf ? BV(BIT_YEAR_CENTURY) : 0),
+        dec2bcd(time->tm_year - (ovf ? 200 : 100))
+    };
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, REG_VL_SECONDS, data, 7));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t pcf8563_get_time(i2c_dev_t *dev, struct tm *time, bool *valid)
+{
+    CHECK_ARG(dev && time && valid);
+
+    uint8_t data[7];
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, REG_VL_SECONDS, data, 7));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    *valid = data[0] & BV(BIT_VL) ? false : true;
+    time->tm_sec  = bcd2dec(data[0] & ~BV(BIT_VL));
+    time->tm_min  = bcd2dec(data[1] & MASK_MIN);
+    time->tm_hour = bcd2dec(data[2] & MASK_HOUR);
+    time->tm_mday = bcd2dec(data[3] & MASK_MDAY);
+    time->tm_wday = bcd2dec(data[4] & MASK_WDAY);
+    time->tm_mon  = bcd2dec(data[5] & MASK_MON) - 1;
+    time->tm_year = bcd2dec(data[6]) + (data[5] & BV(BIT_YEAR_CENTURY) ? 200 : 100);
+
+    return ESP_OK;
+}
+
+esp_err_t pcf8563_set_clkout(i2c_dev_t *dev, pcf8563_clkout_freq_t freq)
+{
+    CHECK_ARG(dev);
+
+    return write_reg(dev, REG_CLKOUT,
+            freq == PCF8563_DISABLED
+                ? 0
+                : (BV(BIT_CLKOUT_FE) | ((freq - 1) & 3))
+           );
+}
+
+esp_err_t pcf8563_get_clkout(i2c_dev_t *dev, pcf8563_clkout_freq_t *freq)
+{
+    CHECK_ARG(dev && freq);
+
+    uint8_t v;
+    CHECK(read_reg(dev, REG_CLKOUT, &v));
+    *freq = v & BV(BIT_CLKOUT_FE) ? (v & 3) + 1 : PCF8563_DISABLED;
+
+    return ESP_OK;
+}
+
+esp_err_t pcf8563_set_timer_settings(i2c_dev_t *dev, bool int_enable, pcf8563_timer_clock_t clock)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, update_reg_nolock(dev, REG_CTRL_STATUS2,
+            BV(BIT_CTRL_STATUS2_TIE), int_enable ? BV(BIT_CTRL_STATUS2_TIE) : 0));
+    I2C_DEV_CHECK(dev, update_reg_nolock(dev, REG_TIMER_CTRL, MASK_TIMER_CTRL_TD, clock));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t pcf8563_get_timer_settings(i2c_dev_t *dev, bool *int_enabled, pcf8563_timer_clock_t *clock)
+{
+    CHECK_ARG(dev && int_enabled && clock);
+
+    uint8_t s, t;
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, read_reg_nolock(dev, REG_CTRL_STATUS2, &s));
+    I2C_DEV_CHECK(dev, read_reg_nolock(dev, REG_TIMER_CTRL, &t));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    *int_enabled = s & BV(BIT_CTRL_STATUS2_TIE) ? true : false;
+    *clock = t & MASK_TIMER_CTRL_TD;
+
+    return ESP_OK;
+}
+
+esp_err_t pcf8563_set_timer_value(i2c_dev_t *dev, uint8_t value)
+{
+    CHECK_ARG(dev);
+
+    return write_reg(dev, REG_TIMER, value);
+}
+
+esp_err_t pcf8563_get_timer_value(i2c_dev_t *dev, uint8_t *value)
+{
+    CHECK_ARG(dev && value);
+
+    return read_reg(dev, REG_TIMER, value);
+}
+
+esp_err_t pcf8563_start_timer(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return update_reg(dev, REG_TIMER_CTRL, BV(BIT_TIMER_CTRL_TE), BV(BIT_TIMER_CTRL_TE));
+}
+
+esp_err_t pcf8563_stop_timer(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return update_reg(dev, REG_TIMER_CTRL, BV(BIT_TIMER_CTRL_TE), 0);
+}
+
+esp_err_t pcf8563_get_timer_flag(i2c_dev_t *dev, bool *timer)
+{
+    CHECK_ARG(dev && timer);
+
+    uint8_t v;
+    CHECK(read_reg(dev, REG_CTRL_STATUS2, &v));
+    *timer = v & BIT_CTRL_STATUS2_TF ? true : false;
+
+    return ESP_OK;
+}
+
+esp_err_t pcf8563_clear_timer_flag(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return update_reg(dev, REG_CTRL_STATUS2, BV(BIT_CTRL_STATUS2_TF), 0);
+}
+
+esp_err_t pcf8563_set_alarm(i2c_dev_t *dev, bool int_enable, uint32_t flags, struct tm *time)
+{
+    CHECK_ARG(dev && time);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, update_reg_nolock(dev, REG_CTRL_STATUS2,
+            BV(BIT_CTRL_STATUS2_AIE), int_enable ? BV(BIT_CTRL_STATUS2_AIE) : 0));
+    uint8_t data[4] = {
+        dec2bcd(time->tm_min) | (flags & PCF8563_ALARM_MATCH_MIN ? 0 : BV(BIT_AE)),
+        dec2bcd(time->tm_hour) | (flags & PCF8563_ALARM_MATCH_HOUR ? 0 : BV(BIT_AE)),
+        dec2bcd(time->tm_mday) | (flags & PCF8563_ALARM_MATCH_DAY ? 0 : BV(BIT_AE)),
+        dec2bcd(time->tm_wday) | (flags & PCF8563_ALARM_MATCH_WEEKDAY ? 0 : BV(BIT_AE)),
+    };
+    I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, REG_ALARM_MIN, data, 4));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t pcf8563_get_alarm(i2c_dev_t *dev, bool *int_enabled, uint32_t *flags, struct tm *time)
+{
+    CHECK_ARG(dev && int_enabled && flags && time);
+
+    uint8_t data[4], s;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, read_reg_nolock(dev, REG_CTRL_STATUS2, &s));
+    I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, REG_ALARM_MIN, data, 4));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    *int_enabled = s & BV(BIT_CTRL_STATUS2_AIE) ? true : false;
+    *flags = 0;
+    if (!(data[0] & BV(BIT_AE)))
+        *flags |= PCF8563_ALARM_MATCH_MIN;
+    if (!(data[1] & BV(BIT_AE)))
+        *flags |= PCF8563_ALARM_MATCH_HOUR;
+    if (!(data[2] & BV(BIT_AE)))
+        *flags |= PCF8563_ALARM_MATCH_DAY;
+    if (!(data[3] & BV(BIT_AE)))
+        *flags |= PCF8563_ALARM_MATCH_WEEKDAY;
+
+    time->tm_min = bcd2dec(data[0] & MASK_ALARM);
+    time->tm_hour = bcd2dec(data[1] & MASK_ALARM);
+    time->tm_mday = bcd2dec(data[2] & MASK_ALARM);
+    time->tm_wday = bcd2dec(data[3] & MASK_ALARM);
+
+    return ESP_OK;
+}
+
+esp_err_t pcf8563_get_alarm_flag(i2c_dev_t *dev, bool *alarm)
+{
+    CHECK_ARG(dev && alarm);
+
+    uint8_t v;
+    CHECK(read_reg(dev, REG_CTRL_STATUS2, &v));
+    *alarm = v & BIT_CTRL_STATUS2_AF ? true : false;
+
+    return ESP_OK;
+}
+
+esp_err_t pcf8563_clear_alarm_flag(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return update_reg(dev, REG_CTRL_STATUS2, BV(BIT_CTRL_STATUS2_AF), 0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pcf8563/pcf8563.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,256 @@
+/*
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file pcf8563.h
+ * @defgroup pcf8563 pcf8563
+ * @{
+ *
+ * ESP-IDF driver for PCF8563 real-time clock/calendar
+ *
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __PCF8563_H__
+#define __PCF8563_H__
+
+#include <i2cdev.h>
+#include <stdbool.h>
+#include <time.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define PCF8563_I2C_ADDR 0x51
+
+/**
+ * Frequency output at pin CLKOUT
+ */
+typedef enum {
+    PCF8563_DISABLED = 0, //!< CLKOUT output is inhibited and set high-impedance
+    PCF8563_32768HZ,      //!< 32768 Hz
+    PCF8563_1024HZ,       //!< 1024 Hz
+    PCF8563_32HZ,         //!< 32 Hz
+    PCF8563_1HZ,          //!< 1 Hz
+} pcf8563_clkout_freq_t;
+
+/**
+ * Timer clock
+ */
+typedef enum {
+    PCF8563_TIMER_4096HZ = 0, //!< 4096 Hz
+    PCF8563_TIMER_64HZ,       //!< 64 Hz
+    PCF8563_TIMER_1HZ,        //!< 1 Hz
+    PCF8563_TIMER_1_60HZ      //!< 1/60 Hz
+} pcf8563_timer_clock_t;
+
+/**
+ * Flags to setup alarm
+ */
+typedef enum {
+    PCF8563_ALARM_MATCH_MIN     = 0x01, //!< Alarm when minute matched
+    PCF8563_ALARM_MATCH_HOUR    = 0x02, //!< Alarm when hour matched
+    PCF8563_ALARM_MATCH_DAY     = 0x04, //!< Alarm when day matched
+    PCF8563_ALARM_MATCH_WEEKDAY = 0x08  //!< Alarm when weekday matched
+} pcf8563_alarm_flags_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev I2C device descriptor
+ * @param port I2C port
+ * @param sda_gpio SDA GPIO
+ * @param scl_gpio SCL GPIO
+ * @return `ESP_OK` on success
+ */
+esp_err_t pcf8563_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev I2C device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t pcf8563_free_desc(i2c_dev_t *dev);
+
+/**
+ * @brief Set the time on the RTC
+ *
+ * @param dev I2C device descriptor
+ * @param time Pointer to time struct
+ * @return `ESP_OK` on success
+ */
+esp_err_t pcf8563_set_time(i2c_dev_t *dev, struct tm *time);
+
+/**
+ * @brief Get the time from the RTC
+ *
+ * @param dev I2C device descriptor
+ * @param[out] time Pointer to time struct
+ * @param[out] valid Time validity, false when RTC had power failures
+ * @return `ESP_OK` on success
+ */
+esp_err_t pcf8563_get_time(i2c_dev_t *dev, struct tm *time, bool *valid);
+
+/**
+ * @brief Set output frequency on CLKOUT pin
+ *
+ * @param dev I2C device descriptor
+ * @param freq Frequency
+ * @return `ESP_OK` on success
+ */
+esp_err_t pcf8563_set_clkout(i2c_dev_t *dev, pcf8563_clkout_freq_t freq);
+
+/**
+ * @brief Get current frequency on CLKOUT pin
+ *
+ * @param dev I2C device descriptor
+ * @param[out] freq Frequency
+ * @return `ESP_OK` on success
+ */
+esp_err_t pcf8563_get_clkout(i2c_dev_t *dev, pcf8563_clkout_freq_t *freq);
+
+/**
+ * @brief Setup timer
+ *
+ * @param dev I2C device descriptor
+ * @param int_enable true for enable interrupt on timer
+ * @param clock Timer frequency
+ * @return `ESP_OK` on success
+ */
+esp_err_t pcf8563_set_timer_settings(i2c_dev_t *dev, bool int_enable, pcf8563_timer_clock_t clock);
+
+/**
+ * @brief Get timer settings
+ *
+ * @param dev I2C device descriptor
+ * @param[out] int_enabled true if timer interrupt is enabled
+ * @param[out] clock Timer frequency
+ * @return `ESP_OK` on success
+ */
+esp_err_t pcf8563_get_timer_settings(i2c_dev_t *dev, bool *int_enabled, pcf8563_timer_clock_t *clock);
+
+/**
+ * @brief Set timer register value
+ *
+ * @param dev I2C device descriptor
+ * @param value Value to set int timer register
+ * @return `ESP_OK` on success
+ */
+esp_err_t pcf8563_set_timer_value(i2c_dev_t *dev, uint8_t value);
+
+/**
+ * @brief Get timer register value
+ *
+ * @param dev I2C device descriptor
+ * @param[out] value Timer value
+ * @return `ESP_OK` on success
+ */
+esp_err_t pcf8563_get_timer_value(i2c_dev_t *dev, uint8_t *value);
+
+/**
+ * @brief Start timer
+ *
+ * @param dev I2C device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t pcf8563_start_timer(i2c_dev_t *dev);
+
+/**
+ * @brief Stop timer
+ *
+ * @param dev I2C device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t pcf8563_stop_timer(i2c_dev_t *dev);
+
+/**
+ * @brief Get state of the timer flag
+ *
+ * @param dev I2C device descriptor
+ * @param[out] timer true when flag is set
+ * @return `ESP_OK` on success
+ */
+esp_err_t pcf8563_get_timer_flag(i2c_dev_t *dev, bool *timer);
+
+/**
+ * @brief Clear timer flag
+ *
+ * @param dev I2C device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t pcf8563_clear_timer_flag(i2c_dev_t *dev);
+
+/**
+ * @brief Setup alarm
+ *
+ * @param dev I2C device descriptor
+ * @param int_enable true to enable alarm interrupt
+ * @param flags Alarm types, combination of pcf8563_alarm_flags_t values
+ * @param time Alarm time. Only tm_min, tm_hour, tm_mday and tm_wday are used
+ * @return `ESP_OK` on success
+ */
+esp_err_t pcf8563_set_alarm(i2c_dev_t *dev, bool int_enable, uint32_t flags, struct tm *time);
+
+/**
+ * @brief Get alarm settings
+ *
+ * @param dev I2C device descriptor
+ * @param[out] int_enabled true if alarm interrupt is enabled
+ * @param[out] flags Selected alarm types, combination of pcf8563_alarm_flags_t values
+ * @param[out] time Alarm time. Only tm_min, tm_hour, tm_mday and tm_wday are used
+ * @return `ESP_OK` on success
+ */
+esp_err_t pcf8563_get_alarm(i2c_dev_t *dev, bool *int_enabled, uint32_t *flags, struct tm *time);
+
+/**
+ * @brief Get alarm flag
+ *
+ * @param dev I2C device descriptor
+ * @param[out] alarm true if alarm occurred
+ * @return `ESP_OK` on success
+ */
+esp_err_t pcf8563_get_alarm_flag(i2c_dev_t *dev, bool *alarm);
+
+/**
+ * @brief Clear alarm flag
+ *
+ * @param dev I2C device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t pcf8563_clear_alarm_flag(i2c_dev_t *dev);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __PCF8563_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pcf8574/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,23 @@
+---
+components:
+  - name: pcf8574
+    description: Driver for PCF8574 remote 8-bit I/O expander for I2C-bus
+    group: gpio
+    groups: []
+    code_owners: UncleRus
+    depends:
+      - i2cdev
+      - log
+      - esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: MIT
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2018
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pcf8574/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS pcf8574.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pcf8574/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2018 Ruslan V. Uss (https://github.com/UncleRus)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pcf8574/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pcf8574/pcf8574.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,99 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file pcf8574.c
+ *
+ * ESP-IDF driver for PCF8574 compatible remote 8-bit I/O expanders for I2C-bus
+ *
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+
+#include <esp_err.h>
+#include <esp_idf_lib_helpers.h>
+#include "pcf8574.h"
+
+#define I2C_FREQ_HZ 100000
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+#define BV(x) (1 << (x))
+
+static esp_err_t read_port(i2c_dev_t *dev, uint8_t *val)
+{
+    CHECK_ARG(dev && val);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read(dev, NULL, 0, val, 1));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+static esp_err_t write_port(i2c_dev_t *dev, uint8_t val)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_write(dev, NULL, 0, &val, 1));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t pcf8574_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+    CHECK_ARG(addr & 0x20);
+
+    dev->port = port;
+    dev->addr = addr;
+    dev->cfg.sda_io_num = sda_gpio;
+    dev->cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(dev);
+}
+
+esp_err_t pcf8574_free_desc(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(dev);
+}
+
+esp_err_t pcf8574_port_read(i2c_dev_t *dev, uint8_t *val)
+{
+    return read_port(dev, val);
+}
+
+esp_err_t pcf8574_port_write(i2c_dev_t *dev, uint8_t val)
+{
+    return write_port(dev, val);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pcf8574/pcf8574.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,92 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file pcf8574.h
+ * @defgroup pcf8574 pcf8574
+ * @{
+ *
+ * ESP-IDF driver for PCF8574 compatible remote 8-bit I/O expanders for I2C-bus
+ *
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+#ifndef __PCF8574_H__
+#define __PCF8574_H__
+
+#include <stddef.h>
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * Default SCL frequency is 100kHz
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @param port I2C port number
+ * @param addr I2C address (0b0100[A2][A1][A0] for PCF8574, 0b0111[A2][A1][A0] for PCF8574A)
+ * @param sda_gpio SDA GPIO
+ * @param scl_gpio SCL GPIO
+ * @return `ESP_OK` on success
+ */
+esp_err_t pcf8574_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t pcf8574_free_desc(i2c_dev_t *dev);
+
+/**
+ * @brief Read GPIO port value
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @param val 8-bit GPIO port value
+ * @return `ESP_OK` on success
+ */
+esp_err_t pcf8574_port_read(i2c_dev_t *dev, uint8_t *val);
+
+/**
+ * @brief Write value to GPIO port
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @param value GPIO port value
+ * @return ESP_OK on success
+ */
+esp_err_t pcf8574_port_write(i2c_dev_t *dev, uint8_t value);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __PCF8574_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pcf8575/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,23 @@
+---
+components:
+  - name: pcf8575
+    description: Driver for PCF8575 remote 16-bit I/O expander for I2C-bus
+    group: gpio
+    groups: []
+    code_owners: UncleRus
+    depends:
+      - i2cdev
+      - log
+      - esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: MIT
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2019
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pcf8575/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS pcf8575.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pcf8575/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2018 Ruslan V. Uss (https://github.com/UncleRus)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pcf8575/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pcf8575/pcf8575.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,98 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file pcf8575.c
+ *
+ * ESP-IDF driver for PCF8575 remote 16-bit I/O expander for I2C-bus
+ *
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+#include <esp_err.h>
+#include <esp_idf_lib_helpers.h>
+#include "pcf8575.h"
+
+#define I2C_FREQ_HZ 400000
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+#define BV(x) (1 << (x))
+
+static esp_err_t read_port(i2c_dev_t *dev, uint16_t *val)
+{
+    CHECK_ARG(dev && val);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read(dev, NULL, 0, val, 2));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+static esp_err_t write_port(i2c_dev_t *dev, uint16_t val)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_write(dev, NULL, 0, &val, 2));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t pcf8575_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+    CHECK_ARG(addr & 0x20);
+
+    dev->port = port;
+    dev->addr = addr;
+    dev->cfg.sda_io_num = sda_gpio;
+    dev->cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(dev);
+}
+
+esp_err_t pcf8575_free_desc(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(dev);
+}
+
+esp_err_t pcf8575_port_read(i2c_dev_t *dev, uint16_t *val)
+{
+    return read_port(dev, val);
+}
+
+esp_err_t pcf8575_port_write(i2c_dev_t *dev, uint16_t val)
+{
+    return write_port(dev, val);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pcf8575/pcf8575.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,91 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file pcf8575.h
+ * @defgroup pcf8575 pcf8575
+ * @{
+ *
+ * ESP-IDF driver for PCF8575 remote 16-bit I/O expander for I2C-bus
+ *
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+#ifndef __PCF8575_H__
+#define __PCF8575_H__
+
+#include <stddef.h>
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define PCF8575_I2C_ADDR_BASE 0x20
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * Default SCL frequency is 400kHz
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @param port I2C port number
+ * @param addr I2C address (`0b0100<A2><A1><A0>` for PCF8575)
+ * @param sda_gpio SDA GPIO
+ * @param scl_gpio SCL GPIO
+ * @return `ESP_OK` on success
+ */
+esp_err_t pcf8575_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ * @param dev Pointer to I2C device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t pcf8575_free_desc(i2c_dev_t *dev);
+
+/**
+ * @brief Read GPIO port value
+ * @param dev Pointer to I2C device descriptor
+ * @param val 8-bit GPIO port value
+ * @return `ESP_OK` on success
+ */
+esp_err_t pcf8575_port_read(i2c_dev_t *dev, uint16_t *val);
+
+/**
+ * @brief Write value to GPIO port
+ * @param dev Pointer to I2C device descriptor
+ * @param value GPIO port value
+ * @return ESP_OK on success
+ */
+esp_err_t pcf8575_port_write(i2c_dev_t *dev, uint16_t value);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __PCF8575_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pcf8591/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+---
+components:
+  - name: pcf8591
+    description: Driver for 8-bit ADC and an 8-bit DAC PCF8591
+    group: adc-dac
+    groups: []
+    code_owners: UncleRus
+    depends:
+      - i2cdev
+      - log
+      - esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2017
+      - author:
+          name: PhamNgocT
+        year: 2017
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pcf8591/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS pcf8591.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pcf8591/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,27 @@
+Copyright (c) 2017 Pham Ngoc Thanh <pnt239@gmail.com>
+Copyright (c) 2017, 2018 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pcf8591/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pcf8591/pcf8591.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2017 Pham Ngoc Thanh <pnt239@gmail.com>
+ * Copyright (c) 2017 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file pcf8591.c
+ *
+ * ESP-IDF driver for 8-bit analog-to-digital conversion and
+ * an 8-bit digital-to-analog conversion PCF8591
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2017 Pham Ngoc Thanh <pnt239@gmail.com>\n
+ * Copyright (c) 2017, 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#include <stddef.h>
+#include <esp_log.h>
+#include <esp_idf_lib_helpers.h>
+#include "pcf8591.h"
+
+static const char *TAG = "pcf8591";
+
+#define I2C_FREQ_HZ 100000
+
+#define BV(x) (1 << (x))
+
+#define CTRL_AD_CH_MASK 0x03
+
+#define CTRL_AD_IN_PRG 4
+#define CTRL_AD_IN_PRG_MASK (0x03 << CTRL_AD_IN_PRG)
+
+#define CTRL_DA_OUT_EN 6
+
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+esp_err_t pcf8591_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+    if ((addr & PCF8591_DEFAULT_ADDRESS) != PCF8591_DEFAULT_ADDRESS)
+    {
+        ESP_LOGE(TAG, "Invalid I2C address 0x%02x", addr);
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    dev->port = port;
+    dev->addr = addr;
+    dev->cfg.sda_io_num = sda_gpio;
+    dev->cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+    i2c_dev_create_mutex(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t pcf8591_free_desc(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(dev);
+}
+
+esp_err_t pcf8591_read(i2c_dev_t *dev, pcf8591_input_conf_t conf, uint8_t channel, uint8_t *value)
+{
+    CHECK_ARG(dev && value);
+    if (channel >= 4)
+    {
+        ESP_LOGE(TAG, "Invalid channel number %d", channel);
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    uint8_t control_reg =
+            ((conf << CTRL_AD_IN_PRG) & CTRL_AD_IN_PRG_MASK) |
+            (channel & CTRL_AD_CH_MASK) |
+            BV(CTRL_DA_OUT_EN);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, control_reg, value, 1));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t pcf8591_write(i2c_dev_t *dev, uint8_t value)
+{
+    CHECK_ARG(dev);
+
+    uint8_t buf[2] = { BV(CTRL_DA_OUT_EN), value };
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_write(dev, NULL, 0, buf, sizeof(buf)));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/pcf8591/pcf8591.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2017 Pham Ngoc Thanh <pnt239@gmail.com>
+ * Copyright (c) 2017 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file pcf8591.h
+ * @defgroup pcf8591 pcf8591
+ * @{
+ *
+ * ESP-IDF driver for 8-bit analog-to-digital conversion and
+ * an 8-bit digital-to-analog conversion PCF8591
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2017 Pham Ngoc Thanh <pnt239@gmail.com>\n
+ * Copyright (c) 2017 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __PCF8591_H__
+#define __PCF8591_H__
+
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define PCF8591_DEFAULT_ADDRESS 0x48
+
+/**
+ * Analog inputs configuration, see datasheet
+ */
+typedef enum {
+    PCF8591_IC_4_SINGLES = 0,   //!< Four single-ended inputs
+    PCF8591_IC_DIFF,            //!< Three differential inputs
+    PCF8591_IC_2_SINGLES_DIFF,  //!< Two single-ended and differential mixed
+    PCF8591_IC_2_DIFFS          //!< Two differential inputs
+} pcf8591_input_conf_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev Device descriptor
+ * @param addr I2C device address
+ * @param port I2C port number
+ * @param sda_gpio GPIO pin for SDA
+ * @param scl_gpio GPIO pin for SCL
+ * @return `ESP_OK` on success
+ */
+esp_err_t pcf8591_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t pcf8591_free_desc(i2c_dev_t *dev);
+
+/**
+ * @brief Read input value of an analog pin
+ *
+ * @param dev Device descriptor
+ * @param conf Analog inputs configuration
+ * @param channel Analog channel
+ * @param[out] value Analog value
+ * @return `ESP_OK` on success
+ */
+esp_err_t pcf8591_read(i2c_dev_t *dev, pcf8591_input_conf_t conf, uint8_t channel, uint8_t *value);
+
+/**
+ * @brief Write value to analog output
+ *
+ * @param dev Device descriptor
+ * @param value DAC value
+ * @return `ESP_OK` on success
+ */
+esp_err_t pcf8591_write(i2c_dev_t *dev, uint8_t value);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __PCF8591_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/qmc5883l/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,23 @@
+---
+components:
+  - name: qmc5883l
+    description: Driver for QMC5883L 3-axis magnetic sensor
+    group: magnetic
+    groups: []
+    code_owners: UncleRus
+    depends:
+      - i2cdev
+      - log
+      - esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2019
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/qmc5883l/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS qmc5883l.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/qmc5883l/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+Copyright 2019 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/qmc5883l/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/qmc5883l/qmc5883l.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,257 @@
+/*
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file qmc5883l.c
+ *
+ * ESP-IDF Driver for 3-axis magnetic sensor QMC5883L
+ *
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+
+#include <esp_log.h>
+#include <esp_idf_lib_helpers.h>
+#include "qmc5883l.h"
+
+#define I2C_FREQ_HZ 400000 // 400kHz
+
+#define REG_XOUT_L 0x00
+#define REG_XOUT_H 0x01
+#define REG_YOUT_L 0x02
+#define REG_YOUT_H 0x03
+#define REG_ZOUT_L 0x04
+#define REG_ZOUT_H 0x05
+#define REG_STATE  0x06
+#define REG_TOUT_L 0x07
+#define REG_TOUT_H 0x08
+#define REG_CTRL1  0x09
+#define REG_CTRL2  0x0a
+#define REG_FBR    0x0b
+#define REG_ID     0x0d
+
+#define MASK_MODE  0xfe
+#define MASK_ODR   0xf3
+
+static const char *TAG = "qmc5883l";
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+inline static esp_err_t write_reg_nolock(qmc5883l_t *dev, uint8_t reg, uint8_t val)
+{
+    return i2c_dev_write_reg(&dev->i2c_dev, reg, &val, 1);
+}
+
+inline static esp_err_t read_reg_nolock(qmc5883l_t *dev, uint8_t reg, uint8_t *val)
+{
+    return i2c_dev_read_reg(&dev->i2c_dev, reg, val, 1);
+}
+
+static esp_err_t write_reg(qmc5883l_t *dev, uint8_t reg, uint8_t val)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, write_reg_nolock(dev, reg, val));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+static esp_err_t read_reg(qmc5883l_t *dev, uint8_t reg, uint8_t *val)
+{
+    CHECK_ARG(dev && val);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, read_reg_nolock(dev, reg, val));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t qmc5883l_init_desc(qmc5883l_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+
+    dev->i2c_dev.port = port;
+    dev->i2c_dev.addr = addr;
+    dev->i2c_dev.cfg.sda_io_num = sda_gpio;
+    dev->i2c_dev.cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+    return i2c_dev_create_mutex(&dev->i2c_dev);
+}
+
+esp_err_t qmc5883l_free_desc(qmc5883l_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(&dev->i2c_dev);
+}
+
+esp_err_t qmc5883l_reset(qmc5883l_t *dev)
+{
+    CHECK(write_reg(dev, REG_CTRL2, 0x80));
+    dev->range = QMC5883L_RNG_2;
+
+    return ESP_OK;
+}
+
+esp_err_t qmc5883l_get_chip_id(qmc5883l_t *dev, uint8_t *id)
+{
+    return read_reg(dev, REG_ID, id);
+}
+
+esp_err_t qmc5883l_set_mode(qmc5883l_t *dev, qmc5883l_mode_t mode)
+{
+    CHECK_ARG(dev && mode <= QMC5883L_MODE_CONTINUOUS);
+
+    uint8_t v;
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, read_reg_nolock(dev, REG_CTRL1, &v));
+    I2C_DEV_CHECK(&dev->i2c_dev, write_reg_nolock(dev, REG_CTRL1, (v & 0xfe) | mode));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t qmc5883l_get_mode(qmc5883l_t *dev, qmc5883l_mode_t *mode)
+{
+    CHECK_ARG(mode);
+
+    uint8_t v;
+    CHECK(read_reg(dev, REG_CTRL1, &v));
+    *mode = v & 1;
+
+    return ESP_OK;
+}
+
+esp_err_t qmc5883l_set_config(qmc5883l_t *dev, qmc5883l_odr_t odr, qmc5883l_osr_t osr, qmc5883l_range_t rng)
+{
+    CHECK_ARG(dev && odr <= QMC5883L_DR_200 && osr <= QMC5883L_OSR_512 && rng <= QMC5883L_RNG_8);
+
+    uint8_t v;
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, read_reg_nolock(dev, REG_CTRL1, &v));
+    dev->range = rng;
+    I2C_DEV_CHECK(&dev->i2c_dev, write_reg_nolock(dev, REG_FBR, 1)); // Define set/reset period
+    I2C_DEV_CHECK(&dev->i2c_dev, write_reg_nolock(dev, REG_CTRL1,
+            (v & 0x03) | ((odr & 3) << 2) | ((rng & 1) << 4) | ((osr & 3) << 6)));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t qmc5883l_get_config(qmc5883l_t *dev, qmc5883l_odr_t *odr, qmc5883l_osr_t *osr, qmc5883l_range_t *rng)
+{
+    CHECK_ARG(odr && osr && rng);
+
+    uint8_t v;
+    CHECK(read_reg(dev, REG_CTRL1, &v));
+    *odr = (v >> 2) & 3;
+    *osr = (v >> 6) & 3;
+    *rng = (v >> 4) & 1;
+
+    return ESP_OK;
+}
+
+esp_err_t qmc5883l_set_int(qmc5883l_t *dev, bool enable)
+{
+    return write_reg(dev, REG_CTRL2, enable ? 1 : 0);
+}
+
+esp_err_t qmc5883l_get_int(qmc5883l_t *dev, bool *enable)
+{
+    CHECK_ARG(enable);
+
+    uint8_t v;
+    CHECK(read_reg(dev, REG_CTRL2, &v));
+    *enable = v & 1;
+
+    return ESP_OK;
+}
+
+esp_err_t qmc5883l_data_ready(qmc5883l_t *dev, bool *ready)
+{
+    CHECK_ARG(ready);
+
+    uint8_t v;
+    CHECK(read_reg(dev, REG_STATE, &v));
+    *ready = v & 1;
+
+    return ESP_OK;
+}
+
+esp_err_t qmc5883l_get_raw_data(qmc5883l_t *dev, qmc5883l_raw_data_t *raw)
+{
+    CHECK_ARG(dev && raw);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    esp_err_t ret = i2c_dev_read_reg(&dev->i2c_dev, REG_XOUT_L, raw, 6);
+    if (ret != ESP_OK)
+        ESP_LOGE(TAG, "Could not read data register, err = %d", ret);
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+    return ret;
+}
+
+esp_err_t qmc5883l_raw_to_mg(qmc5883l_t *dev, qmc5883l_raw_data_t *raw, qmc5883l_data_t *data)
+{
+    CHECK_ARG(dev && raw && data);
+
+    float f = (dev->range == QMC5883L_RNG_2 ? 2000.0 : 8000.0) / 32768;
+
+    data->x = raw->x * f;
+    data->y = raw->y * f;
+    data->z = raw->z * f;
+
+    return ESP_OK;
+}
+
+esp_err_t qmc5883l_get_data(qmc5883l_t *dev, qmc5883l_data_t *data)
+{
+    qmc5883l_raw_data_t raw;
+    CHECK(qmc5883l_get_raw_data(dev, &raw));
+    return qmc5883l_raw_to_mg(dev, &raw, data);
+}
+
+esp_err_t qmc5883l_get_raw_temp(qmc5883l_t *dev, int16_t *temp)
+{
+    CHECK_ARG(dev && temp);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    esp_err_t ret = i2c_dev_read_reg(&dev->i2c_dev, REG_TOUT_L, temp, 2);
+    if (ret != ESP_OK)
+        ESP_LOGE(TAG, "Could not read TOUT register, err = %d", ret);
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+    return ret;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/qmc5883l/qmc5883l.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,268 @@
+/*
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file qmc5883l.h
+ * @defgroup qmc5883l qmc5883l
+ * @{
+ *
+ * ESP-IDF Driver for 3-axis magnetic sensor QMC5883L
+ *
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __QMC5883L_H__
+#define __QMC5883L_H__
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Default I2C address
+ */
+#define QMC5883L_I2C_ADDR_DEF 0x0d
+
+/**
+ * Output data rate
+ */
+typedef enum {
+    QMC5883L_DR_10 = 0, //!< 10Hz
+    QMC5883L_DR_50,     //!< 50Hz
+    QMC5883L_DR_100,    //!< 100Hz
+    QMC5883L_DR_200,    //!< 200Hz
+} qmc5883l_odr_t;
+
+/**
+ * Oversampling rate
+ */
+typedef enum {
+    QMC5883L_OSR_64 = 0, //!< 64 samples
+    QMC5883L_OSR_128,    //!< 128 samples
+    QMC5883L_OSR_256,    //!< 256 samples
+    QMC5883L_OSR_512,    //!< 512 samples
+} qmc5883l_osr_t;
+
+/**
+ * Field range
+ */
+typedef enum {
+    QMC5883L_RNG_2 = 0,//!< -2G..+2G
+    QMC5883L_RNG_8     //!< -8G..+8G
+} qmc5883l_range_t;
+
+/**
+ * Mode
+ */
+typedef enum {
+    QMC5883L_MODE_STANDBY = 0, //!< Standby low power mode, no measurements
+    QMC5883L_MODE_CONTINUOUS   //!< Continuous measurements
+} qmc5883l_mode_t;
+
+
+/**
+ * Raw measurement result
+ */
+typedef struct
+{
+    int16_t x;
+    int16_t y;
+    int16_t z;
+} qmc5883l_raw_data_t;
+
+/**
+ * Measurement result, milligauss
+ */
+typedef struct
+{
+    float x;
+    float y;
+    float z;
+} qmc5883l_data_t;
+
+/**
+ * Device descriptor
+ */
+typedef struct {
+    i2c_dev_t i2c_dev;
+    qmc5883l_range_t range;
+} qmc5883l_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev Device descriptor
+ * @param port I2C port number
+ * @param addr I2C address
+ * @param sda_gpio SDA GPIO
+ * @param scl_gpio SCL GPIO
+ * @return `ESP_OK` on success
+ */
+esp_err_t qmc5883l_init_desc(qmc5883l_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t qmc5883l_free_desc(qmc5883l_t *dev);
+
+/**
+ * @brief Reset device
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t qmc5883l_reset(qmc5883l_t *dev);
+
+/**
+ * @brief Read chip ID
+ *
+ * @param dev Device descriptor
+ * @param[out] id Chip ID
+ * @return `ESP_OK` on success
+ */
+esp_err_t qmc5883l_get_chip_id(qmc5883l_t *dev, uint8_t *id);
+
+/**
+ * @brief Set device mode
+ *
+ * @param dev Device descriptor
+ * @param mode Mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t qmc5883l_set_mode(qmc5883l_t *dev, qmc5883l_mode_t mode);
+
+/**
+ * @brief Read current device mode
+ *
+ * @param dev Device descriptor
+ * @param[out] mode Mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t qmc5883l_get_mode(qmc5883l_t *dev, qmc5883l_mode_t *mode);
+
+/**
+ * @brief Set device configuration
+ *
+ * @param dev Device descriptor
+ * @param odr Output data rate
+ * @param osr Oversampling
+ * @param rng Field range
+ * @return `ESP_OK` on success
+ */
+esp_err_t qmc5883l_set_config(qmc5883l_t *dev, qmc5883l_odr_t odr, qmc5883l_osr_t osr, qmc5883l_range_t rng);
+
+/**
+ * @brief Read current device configuration
+ *
+ * @param dev Device descriptor
+ * @param[out] odr Output data rate
+ * @param[out] osr Oversampling
+ * @param[out] rng Field range
+ * @return `ESP_OK` on success
+ */
+esp_err_t qmc5883l_get_config(qmc5883l_t *dev, qmc5883l_odr_t *odr, qmc5883l_osr_t *osr, qmc5883l_range_t *rng);
+
+/**
+ * @brief Enable/disable interrupt pin
+ *
+ * @param dev Device descriptor
+ * @param enable Enable interrupt if true
+ * @return `ESP_OK` on success
+ */
+esp_err_t qmc5883l_set_int(qmc5883l_t *dev, bool enable);
+
+/**
+ * @brief Get interrupt pin state
+ *
+ * @param dev Device descriptor
+ * @param[out] enable Interrupt pin enabled if true
+ * @return `ESP_OK` on success
+ */
+esp_err_t qmc5883l_get_int(qmc5883l_t *dev, bool *enable);
+
+/**
+ * @brief Get magnetic data state
+ *
+ * @param dev Device descriptor
+ * @param[out] ready Magnetic data ready to read if true
+ * @return `ESP_OK` on success
+ */
+esp_err_t qmc5883l_data_ready(qmc5883l_t *dev, bool *ready);
+
+/**
+ * @brief Read raw magnetic data
+ *
+ * @param dev Device descriptor
+ * @param[out] raw Raw magnetic data
+ * @return `ESP_OK` on success
+ */
+esp_err_t qmc5883l_get_raw_data(qmc5883l_t *dev, qmc5883l_raw_data_t *raw);
+
+/**
+ * @brief Convert raw magnetic data to milligauss
+ *
+ * @param dev Device descriptor
+ * @param raw Raw magnetic data
+ * @param[out] data Magnetic data in mG
+ * @return `ESP_OK` on success
+ */
+esp_err_t qmc5883l_raw_to_mg(qmc5883l_t *dev, qmc5883l_raw_data_t *raw, qmc5883l_data_t *data);
+
+/**
+ * @brief Read magnetic data in milligauss
+ *
+ * @param dev Device descriptor
+ * @param[out] data Magnetic data in mG
+ * @return `ESP_OK` on success
+ */
+esp_err_t qmc5883l_get_data(qmc5883l_t *dev, qmc5883l_data_t *data);
+
+/**
+ * @brief Read raw temperature data (see datasheet)
+ *
+ * @param dev Device descriptor
+ * @param[out] temp Raw temperature data
+ * @return `ESP_OK` on success
+ */
+esp_err_t qmc5883l_get_raw_temp(qmc5883l_t *dev, int16_t *temp);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __QMC5883L_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/rda5807m/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,23 @@
+---
+components:
+  - name: rda5807m
+    description: Driver for single-chip broadcast FM radio tuner RDA5807M
+    group: misc
+    groups: []
+    code_owners: UncleRus
+    depends:
+      - i2cdev
+      - log
+      - esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2018
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/rda5807m/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS rda5807m.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/rda5807m/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/rda5807m/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/rda5807m/rda5807m.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,539 @@
+/*
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file rda5807m.c
+ *
+ * ESP-IDF driver for single-chip broadcast FM radio tuner RDA5807M
+ *
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#include "rda5807m.h"
+#include <inttypes.h>
+#include <esp_idf_lib_helpers.h>
+#include <esp_log.h>
+#include <string.h>
+#include <esp_err.h>
+
+#define I2C_FREQ_HZ 100000 // 100kHz
+
+#define I2C_ADDR_SEQ 0x10
+#define I2C_ADDR_IDX 0x11
+
+// Registers
+#define REG_CHIP_ID 0x00
+#define REG_CTRL    0x02
+#define REG_CHAN    0x03
+#define REG_R4      0x04
+#define REG_VOL     0x05
+#define REG_R7      0x07
+#define REG_RA      0x0a
+#define REG_RB      0x0b
+#define REG_RDSA    0x0c
+#define REG_RDSB    0x0d
+#define REG_RDSC    0x0c
+#define REG_RDSD    0x0d
+
+// Bits
+#define BIT_CTRL_ENABLE      0
+#define BIT_CTRL_SOFT_RESET  1
+#define BIT_CTRL_NEW_METHOD  2
+#define BIT_CTRL_RDS_EN      3
+#define BIT_CTRL_CLK_MODE    4
+#define BIT_CTRL_SKMODE      7
+#define BIT_CTRL_SEEK        8
+#define BIT_CTRL_SEEKUP      9
+#define BIT_CTRL_RCLK_DIRECT 10
+#define BIT_CTRL_RCLK_NC     11
+#define BIT_CTRL_BASS        12
+#define BIT_CTRL_MONO        13
+#define BIT_CTRL_DMUTE       14
+#define BIT_CTRL_DHIZ        15
+
+#define BIT_CHAN_SPACE 0
+#define BIT_CHAN_BAND  2
+#define BIT_CHAN_TUNE  4
+#define BIT_CHAN_CHAN  6
+
+#define BIT_R4_AFCD        8
+#define BIT_R4_SOFTMUTE_EN 9
+#define BIT_R4_DE          11
+
+#define BIT_VOL_VOLUME   0
+#define BIT_VOL_SEEKTH   8
+#define BIT_VOL_INT_MODE 15
+
+#define BIT_R7_SOFTBLEND_EN 1
+#define BIT_R7_50M          9
+#define BIT_R7_TH_SOFTBLEND 10
+
+#define BIT_RA_READCHAN 0
+#define BIT_RA_ST       10
+#define BIT_RA_BLK_E    11
+#define BIT_RA_RDSS     12
+#define BIT_RA_SF       13
+#define BIT_RA_STC      14
+#define BIT_RA_RDSR     15
+
+#define BIT_RB_BLERB    0
+#define BIT_RB_BLERA    2
+#define BIT_RB_ABCD_E   4
+#define BIT_RB_FM_READY 7
+#define BIT_RB_FM_ST    8
+#define BIT_RB_RSSI     9
+
+// Masks
+#define MASK_CHAN_SPACE 0x0003
+#define MASK_CHAN_BAND  0x000c
+#define MASK_CHAN_CHAN  0xffc0  // high 10 bits
+
+#define MASK_VOL_VOLUME  0x000f
+#define MASK_VOL_SEEKTH  0x0f00
+
+#define MASK_RA_READCHAN 0x3ff
+
+#define MAX_CHAN 0x3ff
+
+#define BV(x) (1 << (x))
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+static const uint8_t spacings[] = {
+    [RDA5807M_CHAN_SPACE_100] = 100,
+    [RDA5807M_CHAN_SPACE_200] = 200,
+    [RDA5807M_CHAN_SPACE_50]  = 50,
+    [RDA5807M_CHAN_SPACE_25]  = 25
+};
+
+typedef struct
+{
+    uint32_t lower;
+    uint32_t upper;
+} band_limit_t;
+
+static const band_limit_t band_limits[] = {
+    [RDA5807M_BAND_87_108] = {87000, 108000},
+    [RDA5807M_BAND_76_91]  = {76000, 91000},
+    [RDA5807M_BAND_76_108] = {76000, 108000},
+    [RDA5807M_BAND_65_76]  = {65000, 76000},
+    [RDA5807M_BAND_50_76]  = {50000, 76000}
+};
+
+static const char *TAG = "rda5807m";
+
+static inline esp_err_t read_register_nolock(rda5807m_t *dev, uint8_t reg, uint16_t *val)
+{
+    dev->i2c_dev.addr = I2C_ADDR_IDX;
+    CHECK(i2c_dev_read_reg(&dev->i2c_dev, reg, val, 2));
+
+    *val = (*val >> 8) | (*val << 8);
+
+    return ESP_OK;
+}
+
+static inline esp_err_t write_register_nolock(rda5807m_t *dev, uint8_t reg, uint16_t val)
+{
+    uint16_t v = (val >> 8) | (val << 8);
+
+    dev->i2c_dev.addr = I2C_ADDR_IDX;
+    return i2c_dev_write_reg(&dev->i2c_dev, reg, &v, 2);
+}
+
+static esp_err_t read_register(rda5807m_t *dev, uint8_t reg, uint16_t *val)
+{
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, read_register_nolock(dev, reg, val));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+static esp_err_t update_register_nolock(rda5807m_t *dev, uint8_t reg, uint16_t mask, uint16_t val)
+{
+    uint16_t old;
+
+    CHECK(read_register_nolock(dev, reg, &old));
+    CHECK(write_register_nolock(dev, reg, (old & ~mask) | val));
+
+    return ESP_OK;
+}
+
+static esp_err_t update_register(rda5807m_t *dev, uint8_t reg, uint16_t mask, uint16_t val)
+{
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, update_register_nolock(dev, reg, mask, val));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+static esp_err_t read_registers_bulk(rda5807m_t *dev, uint16_t *val, uint8_t count)
+{
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    dev->i2c_dev.addr = I2C_ADDR_SEQ;
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read(&dev->i2c_dev, NULL, 0, val, count * 2));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    for (uint8_t i = 0; i < count; i++)
+        val[i] = (val[i] >> 8) | (val[i] << 8);
+
+    return ESP_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t rda5807m_init_desc(rda5807m_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+
+    dev->i2c_dev.port = port;
+    dev->i2c_dev.addr = I2C_ADDR_IDX;
+    dev->i2c_dev.cfg.sda_io_num = sda_gpio;
+    dev->i2c_dev.cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+    return i2c_dev_create_mutex(&dev->i2c_dev);
+}
+
+esp_err_t rda5807m_free_desc(rda5807m_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(&dev->i2c_dev);
+}
+
+esp_err_t rda5807m_init(rda5807m_t *dev, rda5807m_clock_freq_t clock_freq)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    // reset
+    I2C_DEV_CHECK(&dev->i2c_dev, write_register_nolock(dev, REG_CTRL, BV(BIT_CTRL_SOFT_RESET) | BV(BIT_CTRL_ENABLE)));
+
+    I2C_DEV_CHECK(&dev->i2c_dev, write_register_nolock(dev, REG_CTRL,
+            ((clock_freq & 7) << BIT_CTRL_CLK_MODE) | // Set clock mode
+            BV(BIT_CTRL_DHIZ) |                       // Enable audio output
+            BV(BIT_CTRL_DMUTE) |                      // Disable mute
+            BV(BIT_CTRL_RDS_EN) |                     // Enable RDS
+            BV(BIT_CTRL_ENABLE)                       // Enable chip
+    ));
+    // De-emphasis = 50us
+    I2C_DEV_CHECK(&dev->i2c_dev, write_register_nolock(dev, REG_R4, BV(BIT_R4_DE)));
+    // Set volume = 0, Seek threshold = ~32 dB SNR, INT_MODE = 1 (?)
+    I2C_DEV_CHECK(&dev->i2c_dev, write_register_nolock(dev, REG_VOL, BV(BIT_VOL_INT_MODE) | (8 << BIT_VOL_SEEKTH)));
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    dev->band = RDA5807M_BAND_87_108;
+    dev->spacing = RDA5807M_CHAN_SPACE_100;
+
+    ESP_LOGD(TAG, "Device initialized");
+
+    return ESP_OK;
+}
+
+esp_err_t rda5807m_get_state(rda5807m_t *dev, rda5807m_state_t *state)
+{
+    CHECK_ARG(dev && state);
+
+    uint16_t r[6];
+    uint16_t ctrl;
+    CHECK(read_registers_bulk(dev, r, 6));
+    CHECK(read_register(dev, REG_CTRL, &ctrl));
+
+    if (r[0] & BIT_RA_SF) state->seek_status = RDA5807M_SEEK_FAILED;
+    else if (r[0] & BIT_RA_STC) state->seek_status = RDA5807M_SEEK_COMPLETE;
+    else if (ctrl & BIT_CTRL_SEEK) state->seek_status = RDA5807M_SEEK_STARTED;
+    else state->seek_status = RDA5807M_SEEK_NONE;
+
+    state->frequency = (r[0] & MASK_RA_READCHAN) * spacings[dev->spacing] + band_limits[dev->band].lower;
+    state->stereo = (r[0] & BIT_RA_ST) != 0;
+    state->station = (r[1] & BIT_RB_FM_ST) != 0;
+    state->rds_ready = (r[0] & BIT_RA_RDSR) != 0;
+    state->rssi = r[1] >> BIT_RB_RSSI;
+
+    memcpy(state->rds, &r[2], 8);
+
+    return ESP_OK;
+}
+
+esp_err_t rda5807m_get_volume(rda5807m_t *dev, uint8_t *vol)
+{
+    CHECK_ARG(dev && vol);
+
+    uint16_t v;
+    CHECK(read_register(dev, REG_VOL, &v));
+    *vol = v & MASK_VOL_VOLUME;
+
+    return ESP_OK;
+}
+
+esp_err_t rda5807m_set_volume(rda5807m_t *dev, uint8_t vol)
+{
+    CHECK_ARG(dev && vol <= RDA5807M_VOL_MAX);
+
+    CHECK(update_register(dev, REG_VOL, MASK_VOL_VOLUME, vol));
+
+    ESP_LOGD(TAG, "Volume set to %d", vol);
+
+    return ESP_OK;
+}
+
+esp_err_t rda5807m_get_mute(rda5807m_t *dev, bool *mute)
+{
+    CHECK_ARG(dev && mute);
+
+    uint16_t v;
+    CHECK(read_register(dev, REG_CTRL, &v));
+    *mute = !(v & BV(BIT_CTRL_DMUTE));
+
+    return ESP_OK;
+}
+
+esp_err_t rda5807m_set_mute(rda5807m_t *dev, bool mute)
+{
+    CHECK_ARG(dev);
+
+    CHECK(update_register(dev, REG_CTRL, BV(BIT_CTRL_DMUTE), mute ? 0 : BV(BIT_CTRL_DMUTE)));
+
+    ESP_LOGD(TAG, "Mute %s", mute ? "enabled" : "disabled");
+
+    return ESP_OK;
+}
+
+esp_err_t rda5807m_get_softmute(rda5807m_t *dev, bool *softmute)
+{
+    CHECK_ARG(dev && softmute);
+
+    uint16_t v;
+    CHECK(read_register(dev, REG_R4, &v));
+    *softmute = v & BV(BIT_R4_SOFTMUTE_EN);
+
+    return ESP_OK;
+}
+
+esp_err_t rda5807m_set_softmute(rda5807m_t *dev, bool softmute)
+{
+    CHECK_ARG(dev);
+
+    CHECK(update_register(dev, REG_R4, BV(BIT_R4_SOFTMUTE_EN), softmute ? BV(BIT_R4_SOFTMUTE_EN) : 0));
+
+    ESP_LOGD(TAG, "Softmute %s", softmute ? "enabled" : "disabled");
+
+    return ESP_OK;
+}
+
+esp_err_t rda5807m_get_bass_boost(rda5807m_t *dev, bool *bass_boost)
+{
+    CHECK_ARG(dev && bass_boost);
+
+    uint16_t v;
+    CHECK(read_register(dev, REG_CTRL, &v));
+    *bass_boost = v & BV(BIT_CTRL_BASS);
+
+    return ESP_OK;
+}
+
+esp_err_t rda5807m_set_bass_boost(rda5807m_t *dev, bool bass_boost)
+{
+    CHECK_ARG(dev);
+
+    CHECK(update_register(dev, REG_CTRL, BV(BIT_CTRL_BASS), bass_boost ? BV(BIT_CTRL_BASS) : 0));
+
+    ESP_LOGD(TAG, "Bass-boost %s", bass_boost ? "enabled" : "disabled");
+
+    return ESP_OK;
+}
+
+esp_err_t rda5807m_get_mono(rda5807m_t *dev, bool *mono)
+{
+    CHECK_ARG(dev && mono);
+
+    uint16_t v;
+    CHECK(read_register(dev, REG_CTRL, &v));
+    *mono = v & BV(BIT_CTRL_MONO);
+
+    return ESP_OK;
+}
+
+esp_err_t rda5807m_set_mono(rda5807m_t *dev, bool mono)
+{
+    CHECK_ARG(dev);
+
+    CHECK(update_register(dev, REG_CTRL, BV(BIT_CTRL_MONO), mono ? BV(BIT_CTRL_MONO) : 0));
+
+    ESP_LOGD(TAG, "Mono %s", mono ? "enabled" : "disabled");
+
+    return ESP_OK;
+}
+
+esp_err_t rda5807m_get_band(rda5807m_t *dev, rda5807m_band_t *band)
+{
+    CHECK_ARG(dev && band);
+
+    uint16_t v;
+    CHECK(read_register(dev, REG_CHAN, &v));
+    v = (v & MASK_CHAN_BAND) >> BIT_CHAN_BAND;
+    if (v == 3)
+    {
+        uint16_t r7;
+        CHECK(read_register(dev, REG_R7, &r7));
+        *band = (r7 >> BIT_R7_50M) & 1 ? RDA5807M_BAND_65_76 : RDA5807M_BAND_50_76;
+    }
+    else *band = v;
+
+    dev->band = *band;
+
+    return ESP_OK;
+}
+
+esp_err_t rda5807m_set_band(rda5807m_t *dev, rda5807m_band_t band)
+{
+    CHECK_ARG(dev && band <= RDA5807M_BAND_50_76);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, update_register_nolock(dev, REG_CHAN, MASK_CHAN_BAND,
+            (band <= RDA5807M_BAND_65_76 ? band : RDA5807M_BAND_65_76) << BIT_CHAN_BAND));
+    if (band >= RDA5807M_BAND_65_76)
+        I2C_DEV_CHECK(&dev->i2c_dev, update_register_nolock(dev, REG_R7, BV(BIT_R7_50M),
+                band == RDA5807M_BAND_65_76 ? BV(BIT_R7_50M) : 0));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    dev->band = band;
+
+    ESP_LOGD(TAG, "Band: %" PRIu32 "..%" PRIu32 " kHz", band_limits[band].lower, band_limits[band].upper);
+
+    return ESP_OK;
+}
+
+esp_err_t rda5807m_get_channel_spacing(rda5807m_t *dev, rda5807m_channel_spacing_t *spacing)
+{
+    CHECK_ARG(dev && spacing);
+
+    uint16_t v;
+    CHECK(read_register(dev, REG_CHAN, &v));
+    dev->spacing = *spacing = v & MASK_CHAN_SPACE;
+
+    return ESP_OK;
+}
+
+esp_err_t rda5807m_set_channel_spacing(rda5807m_t *dev, rda5807m_channel_spacing_t spacing)
+{
+    CHECK_ARG(dev && spacing <= RDA5807M_CHAN_SPACE_25);
+
+    CHECK(update_register(dev, REG_CTRL, MASK_CHAN_SPACE, spacing));
+    dev->spacing = spacing;
+
+    return ESP_OK;
+}
+
+esp_err_t rda5807m_get_frequency_khz(rda5807m_t *dev, uint32_t *frequency)
+{
+    CHECK_ARG(dev && frequency);
+
+    uint16_t chan;
+    CHECK(read_registers_bulk(dev, &chan, 1));
+    chan &= MASK_RA_READCHAN;
+
+    *frequency = chan * spacings[dev->spacing] + band_limits[dev->band].lower;
+
+    return ESP_OK;
+}
+
+esp_err_t rda5807m_set_frequency_khz(rda5807m_t *dev, uint32_t frequency)
+{
+    CHECK_ARG(dev);
+
+    if (frequency < band_limits[dev->band].lower || frequency > band_limits[dev->band].upper)
+    {
+        ESP_LOGE(TAG, "Could not set frequency: %" PRIu32 " kHz is out of bounds (%" PRIu32 "..%" PRIu32 ")",
+                frequency, band_limits[dev->band].lower, band_limits[dev->band].upper);
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    uint16_t chan = (frequency - band_limits[dev->band].lower) / spacings[dev->spacing];
+    if (chan > MAX_CHAN)
+    {
+        ESP_LOGE(TAG, "Could not set frequency to %" PRIu32 " kHz with current band/spacing settings", frequency);
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    CHECK(update_register(dev, REG_CHAN, MASK_CHAN_CHAN | BV(BIT_CHAN_TUNE),
+            (chan << BIT_CHAN_CHAN) | BV(BIT_CHAN_TUNE)));
+
+    ESP_LOGD(TAG, "Frequency: %" PRIu32 " kHz", chan * spacings[dev->spacing] + band_limits[dev->band].lower);
+
+    return ESP_OK;
+}
+
+esp_err_t rda5807m_get_afc(rda5807m_t *dev, bool *afc)
+{
+    CHECK_ARG(dev && afc);
+
+    uint16_t v;
+    CHECK(read_register(dev, REG_R4, &v));
+    *afc = !(v & BV(BIT_R4_AFCD));
+
+    return ESP_OK;
+}
+
+esp_err_t rda5807m_set_afc(rda5807m_t *dev, bool afc)
+{
+    CHECK_ARG(dev);
+
+    return update_register(dev, REG_R4, BV(BIT_R4_AFCD), afc ? 0 : BV(BIT_R4_AFCD));
+}
+
+esp_err_t rda5807m_seek_start(rda5807m_t *dev, bool up, bool wrap, uint8_t threshold)
+{
+    CHECK_ARG(dev && threshold <= RDA5807M_SEEK_TH_MAX);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, update_register_nolock(dev, REG_VOL, MASK_VOL_SEEKTH,
+            threshold << BIT_VOL_SEEKTH));
+    I2C_DEV_CHECK(&dev->i2c_dev, update_register_nolock(dev, REG_CTRL,
+            BV(BIT_CTRL_SEEK) | BV(BIT_CTRL_SEEKUP) | BV(BIT_CTRL_SKMODE),
+            BV(BIT_CTRL_SEEK) | (up ? BV(BIT_CTRL_SEEKUP) : 0) | (wrap ? 0 : BV(BIT_CTRL_SKMODE))));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    ESP_LOGD(TAG, "Seek started: %s, %s at bound, SNR threshold: %d", up ? "up" : "down", wrap ? "wrap" : "stop", threshold);
+
+    return ESP_OK;
+}
+
+esp_err_t rda5807m_seek_stop(rda5807m_t *dev)
+{
+    CHECK_ARG(dev);
+    CHECK(update_register(dev, REG_CTRL, BV(BIT_CTRL_SEEK), 0));
+
+    ESP_LOGD(TAG, "Seek stopped");
+
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/rda5807m/rda5807m.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,353 @@
+/*
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file rda5807m.h
+ * @defgroup rda5807m rda5807m
+ * @{
+ *
+ * ESP-IDF driver for single-chip broadcast FM radio tuner RDA5807M
+ *
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __RDA5807M_H__
+#define __RDA5807M_H__
+
+#include <i2cdev.h>
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define RDA5807M_RSSI_MAX    0x3f
+#define RDA5807M_SEEK_TH_DEF 0x08
+#define RDA5807M_SEEK_TH_MAX 0x0f
+#define RDA5807M_VOL_MAX     0x0f
+
+/**
+ * Clock mode
+ */
+typedef enum
+{
+    RDA5807M_CLK_32768HZ = 0, //!< 32768 Hz, default
+    RDA5807M_CLK_12MHZ   = 1, //!< 12 MHz
+    RDA5807M_CLK_13MHZ   = 2, //!< 13 MHz
+    RDA5807M_CLK_19_2MHZ = 3, //!< 19.2 MHz
+    RDA5807M_CLK_24MHZ   = 5, //!< 24 MHz
+    RDA5807M_CLK_26MHZ   = 6, //!< 26 MHz
+    RDA5807M_CLK_38_4MHZ = 7, //!< 38.4 MHz
+} rda5807m_clock_freq_t;
+
+/**
+ * Channel spacing (frequency step)
+ */
+typedef enum
+{
+    RDA5807M_CHAN_SPACE_100 = 0, //!< 100 KHz, default
+    RDA5807M_CHAN_SPACE_200,     //!< 200 KHz
+    RDA5807M_CHAN_SPACE_50,      //!< 50 KHz
+    RDA5807M_CHAN_SPACE_25       //!< 25 KHz
+} rda5807m_channel_spacing_t;
+
+/**
+ * FM Band
+ */
+typedef enum {
+    RDA5807M_BAND_87_108 = 0, //!< 87..108 MHz (US/Europe), default
+    RDA5807M_BAND_76_91,      //!< 76..91 MHz (Japan)
+    RDA5807M_BAND_76_108,     //!< 76..108 MHz (Worldwide)
+    RDA5807M_BAND_65_76,      //!< 65..76 MHz (Eastern Europe)
+    RDA5807M_BAND_50_76       //!< 50..76 MHz (Eastern Europe wide)
+} rda5807m_band_t;
+
+/**
+ * Seek status
+ */
+typedef enum {
+    RDA5807M_SEEK_NONE = 0, //!< There is currently no station seek
+    RDA5807M_SEEK_STARTED,  //!< Seeking is in progress
+    RDA5807M_SEEK_COMPLETE, //!< Seeking is complete
+    RDA5807M_SEEK_FAILED    //!< Seeking is failed - no stations with RSSI > threshold found
+} rda5807m_seek_status_t;
+
+/**
+ * Overall device status
+ */
+typedef struct
+{
+    rda5807m_seek_status_t seek_status; //!< Seek status
+    bool station;                       //!< True if tuned to a station
+    bool stereo;                        //!< True if stereo is available
+    bool rds_ready;                     //!< True if RDS data is ready
+    uint8_t rssi;                       //!< RSSI, 0..RDA5807M_RSSI_MAX (logarithmic scale)
+    uint32_t frequency;                 //!< Current frequency, kHz
+    uint16_t rds[4];                    //!< RDS data
+} rda5807m_state_t;
+
+/**
+ * Device descriptor
+ */
+typedef struct
+{
+    i2c_dev_t i2c_dev;                  //!< I2C device descriptor
+    rda5807m_band_t band;               //!< Current band
+    rda5807m_channel_spacing_t spacing; //!< Current spacing
+} rda5807m_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev Device descriptor
+ * @param port I2C port
+ * @param sda_gpio SDA GPIO
+ * @param scl_gpio SCL GPIO
+ * @return `ESP_OK` on success
+ */
+esp_err_t rda5807m_init_desc(rda5807m_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t rda5807m_free_desc(rda5807m_t *dev);
+
+/**
+ * @brief Initialize device
+ *
+ * @param dev Device descriptor
+ * @param clock_freq RCLK frequency, usually `RDA5807M_CLK_32768HZ`
+ * @return `ESP_OK` on success
+ */
+esp_err_t rda5807m_init(rda5807m_t *dev, rda5807m_clock_freq_t clock_freq);
+
+/**
+ * @brief Get current device status
+ *
+ * @param dev Device descriptor
+ * @param[out] state Device status descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t rda5807m_get_state(rda5807m_t *dev, rda5807m_state_t *state);
+
+/**
+ * @brief Get volume level (DAC gain)
+ *
+ * @param dev Device descriptor
+ * @param[out] vol Volume level, 0..RDA5807M_VOL_MAX
+ * @return `ESP_OK` on success
+ */
+esp_err_t rda5807m_get_volume(rda5807m_t *dev, uint8_t *vol);
+
+/**
+ * @brief Set volume level (DAC gain)
+ *
+ * Volume scale is logarithmic. When 0, device is muted and output impedance
+ * is very large.
+ *
+ * @param dev Device descriptor
+ * @param vol Volume level, 0..RDA5807M_VOL_MAX
+ * @return `ESP_OK` on success
+ */
+esp_err_t rda5807m_set_volume(rda5807m_t *dev, uint8_t vol);
+
+/**
+ * @brief Get current mute state
+ *
+ * @param dev Device descriptor
+ * @param[out] mute Mute state
+ * @return `ESP_OK` on success
+ */
+esp_err_t rda5807m_get_mute(rda5807m_t *dev, bool *mute);
+
+/**
+ * @brief Mute/unmute device
+ *
+ * @param dev Device descriptor
+ * @param mute Mute if true
+ * @return `ESP_OK` on success
+ */
+esp_err_t rda5807m_set_mute(rda5807m_t *dev, bool mute);
+
+/**
+ * @brief Get current soft mute state
+ *
+ * @param dev Device descriptor
+ * @param[out] softmute Soft mute state
+ * @return `ESP_OK` on success
+ */
+esp_err_t rda5807m_get_softmute(rda5807m_t *dev, bool *softmute);
+
+/**
+ * @brief Enable/disable soft mute
+ *
+ * @param dev Device descriptor
+ * @param softmute If true, enable soft mute
+ * @return `ESP_OK` on success
+ */
+esp_err_t rda5807m_set_softmute(rda5807m_t *dev, bool softmute);
+
+/**
+ * @brief Get current state of the bass boost feature
+ *
+ * @param dev Device descriptor
+ * @param[out] bass_boost Bass boost state
+ * @return `ESP_OK` on success
+ */
+esp_err_t rda5807m_get_bass_boost(rda5807m_t *dev, bool *bass_boost);
+
+/**
+ * @brief Enable/disable bass boost feature
+ *
+ * @param dev Device descriptor
+ * @param bass_boost If true, enable bass boost
+ * @return `ESP_OK` on success
+ */
+esp_err_t rda5807m_set_bass_boost(rda5807m_t *dev, bool bass_boost);
+
+/**
+ * @brief Get forced mono state
+ *
+ * @param dev Device descriptor
+ * @param[out] mono Forced mono state
+ * @return `ESP_OK` on success
+ */
+esp_err_t rda5807m_get_mono(rda5807m_t *dev, bool *mono);
+
+/**
+ * @brief Enable/disable forced mono
+ *
+ * @param dev Device descriptor
+ * @param mono If true, audio will be in mono even if stereo is available
+ * @return `ESP_OK` on success
+ */
+esp_err_t rda5807m_set_mono(rda5807m_t *dev, bool mono);
+
+/**
+ * @brief Get current band
+ *
+ * @param dev Device descriptor
+ * @param[out] band Current band
+ * @return `ESP_OK` on success
+ */
+esp_err_t rda5807m_get_band(rda5807m_t *dev, rda5807m_band_t *band);
+
+/**
+ * @brief Switch device to band
+ *
+ * @param dev Device descriptor
+ * @param band New band
+ * @return `ESP_OK` on success
+ */
+esp_err_t rda5807m_set_band(rda5807m_t *dev, rda5807m_band_t band);
+
+/**
+ * @brief Get current channel spacing (frequency step)
+ *
+ * @param dev Device descriptor
+ * @param[out] spacing Current channel spacing
+ * @return `ESP_OK` on success
+ */
+esp_err_t rda5807m_get_channel_spacing(rda5807m_t *dev, rda5807m_channel_spacing_t *spacing);
+
+/**
+ * @brief Set channel spacing (frequency step)
+ *
+ * @param dev Device descriptor
+ * @param spacing Channel spacing, usually `RDA5807M_CHAN_SPACE_100`
+ * @return `ESP_OK` on success
+ */
+esp_err_t rda5807m_set_channel_spacing(rda5807m_t *dev, rda5807m_channel_spacing_t spacing);
+
+/**
+ * @brief Get frequency the device is tuned to
+ *
+ * @param dev Device descriptor
+ * @param[out] frequency Frequency, kHz
+ * @return `ESP_OK` on success
+ */
+esp_err_t rda5807m_get_frequency_khz(rda5807m_t *dev, uint32_t *frequency);
+
+/**
+ * @brief Tune device to a frequency
+ *
+ * @param dev Device descriptor
+ * @param frequency Frequency, kHz
+ * @return `ESP_OK` on success
+ */
+esp_err_t rda5807m_set_frequency_khz(rda5807m_t *dev, uint32_t frequency);
+
+/**
+ * @brief Get current state of the automatic frequency control
+ *
+ * @param dev Device descriptor
+ * @param[out] afc AFC state
+ * @return `ESP_OK` on success
+ */
+esp_err_t rda5807m_get_afc(rda5807m_t *dev, bool *afc);
+
+/**
+ * @brief Enable/disable automatic frequency control
+ *
+ * @param dev Device descriptor
+ * @param afc AFC state
+ * @return `ESP_OK` on success
+ */
+esp_err_t rda5807m_set_afc(rda5807m_t *dev, bool afc);
+
+/**
+ * @brief Start seeking stations
+ *
+ * @param dev Device descriptor
+ * @param up Seeking direction: true - up, false - down
+ * @param wrap If true, wrap at the upper or lower band limit and
+ *             continue seeking, else stop seeking at bounds
+ * @param threshold Seeking SNR threshold, 0..`RDA5807M_SEEK_TH_MAX`.
+ *                  Usually it's `RDA5807M_SEEK_TH_DEF`
+ * @return `ESP_OK` on success
+ */
+esp_err_t rda5807m_seek_start(rda5807m_t *dev, bool up, bool wrap, uint8_t threshold);
+
+/**
+ * @brief Stop seeking stations
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t rda5807m_seek_stop(rda5807m_t *dev);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __RDA5807M_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/scd30/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,30 @@
+---
+components:
+  - name: scd30
+    description: Driver for SCD30 CO₂ sensor
+    group: air-quality
+    groups:
+      - name: gas
+    code_owners: UncleRus
+    depends:
+      - i2cdev
+      - log
+      - esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2021
+      - author:
+          name: Sensirion
+        year: 2021
+      - author:
+          name: nated0g
+        year: 2021
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/scd30/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS scd30.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/scd30/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,28 @@
+Copyright (c) 2021, Sensirion AG
+Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+Copyright (c) 2021 Nate Usher <n.usher87@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/scd30/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/scd30/scd30.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,343 @@
+/*
+ * Copyright (c) 2021, Sensirion AG
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ * Copyright (c) 2021 Nate Usher <n.usher87@gmail.com>
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file scd30.c
+ *
+ * ESP-IDF driver for Sensirion SCD30 CO2 sensor.
+ *
+ * Adapted from https://github.com/UncleRus/esp-idf-lib/tree/master/components/scd4x
+ *
+ * Copyright (c) 2021, Sensirion AG
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ * Copyright (c) 2021 Nate Usher <n.usher87@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <esp_log.h>
+#if CONFIG_IDF_TARGET_ESP32
+#include <esp32/rom/ets_sys.h>
+#elif CONFIG_IDF_TARGET_ESP32S2
+#include <esp32s2/rom/ets_sys.h>
+#elif CONFIG_IDF_TARGET_ESP32S3
+#include <esp32s3/rom/ets_sys.h>
+#elif CONFIG_IDF_TARGET_ESP32C3
+#include <esp32c3/rom/ets_sys.h>
+#elif CONFIG_IDF_TARGET_ESP32H2
+#include <esp32h2/rom/ets_sys.h>
+#elif CONFIG_IDF_TARGET_ESP32C2
+#include <esp32c2/rom/ets_sys.h>
+#endif
+#include <esp_idf_lib_helpers.h>
+#include "scd30.h"
+
+#define I2C_FREQ_HZ 100000 // 100kHz
+
+static const char *TAG = "scd30";
+
+#define CMD_TRIGGER_CONTINUOUS_MEASUREMENT (0x0010)
+#define CMD_STOP_CONTINUOUS_MEASUREMENT (0x0104)
+#define CMD_SET_MEASUREMENT_INTERVAL (0x4600)
+#define CMD_GET_DATA_READY_STATUS (0x0202)
+#define CMD_READ_MEASUREMENT (0x0300)
+#define CMD_ACTIVATE_AUTOMATIC_SELF_CALIBRATION (0x5306)
+#define CMD_SET_FORCED_RECALIBRATION_VALUE (0x5204)
+#define CMD_SET_TEMPERATURE_OFFSET (0x5403)
+#define CMD_ALTITUDE_COMPENSATION (0x5102)
+#define CMD_READ_FIRMWARE_VERSION (0xD100)
+#define CMD_SOFT_RESET (0XD304)
+
+#define CHECK(x)                \
+    do                          \
+    {                           \
+        esp_err_t __;           \
+        if ((__ = x) != ESP_OK) \
+            return __;          \
+    } while (0)
+#define CHECK_ARG(VAL)                  \
+    do                                  \
+    {                                   \
+        if (!(VAL))                     \
+            return ESP_ERR_INVALID_ARG; \
+    } while (0)
+
+static uint8_t crc8(const uint8_t *data, size_t count)
+{
+    uint8_t res = 0xff;
+
+    for (size_t i = 0; i < count; ++i)
+    {
+        res ^= data[i];
+        for (uint8_t bit = 8; bit > 0; --bit)
+        {
+            if (res & 0x80)
+                res = (res << 1) ^ 0x31;
+            else
+                res = (res << 1);
+        }
+    }
+    return res;
+}
+
+static inline uint16_t swap(uint16_t v)
+{
+    return (v << 8) | (v >> 8);
+}
+
+static esp_err_t send_cmd(i2c_dev_t *dev, uint16_t cmd, uint16_t *data, size_t words)
+{
+    uint8_t buf[2 + words * 3];
+    // add command
+    *(uint16_t *)buf = swap(cmd);
+    if (data && words)
+        // add arguments
+        for (size_t i = 0; i < words; i++)
+        {
+            uint8_t *p = buf + 2 + i * 3;
+            *(uint16_t *)p = swap(data[i]);
+            *(p + 2) = crc8(p, 2);
+        }
+
+    ESP_LOGV(TAG, "Sending buffer:");
+    ESP_LOG_BUFFER_HEX_LEVEL(TAG, buf, sizeof(buf), ESP_LOG_VERBOSE);
+
+    return i2c_dev_write(dev, NULL, 0, buf, sizeof(buf));
+}
+
+static esp_err_t read_resp(i2c_dev_t *dev, uint16_t *data, size_t words)
+{
+    uint8_t buf[words * 3];
+    CHECK(i2c_dev_read(dev, NULL, 0, buf, sizeof(buf)));
+
+    ESP_LOGV(TAG, "Received buffer:");
+    ESP_LOG_BUFFER_HEX_LEVEL(TAG, buf, sizeof(buf), ESP_LOG_VERBOSE);
+
+    for (size_t i = 0; i < words; i++)
+    {
+        uint8_t *p = buf + i * 3;
+        uint8_t crc = crc8(p, 2);
+        if (crc != *(p + 2))
+        {
+            ESP_LOGE(TAG, "Invalid CRC 0x%02x, expected 0x%02x", crc, *(p + 2));
+            return ESP_ERR_INVALID_CRC;
+        }
+        data[i] = swap(*(uint16_t *)p);
+    }
+    return ESP_OK;
+}
+
+static esp_err_t execute_cmd(i2c_dev_t *dev, uint16_t cmd, uint32_t timeout_ms,
+                             uint16_t *out_data, size_t out_words, uint16_t *in_data, size_t in_words)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, send_cmd(dev, cmd, out_data, out_words));
+    if (timeout_ms)
+    {
+        if (timeout_ms > 10)
+            vTaskDelay(pdMS_TO_TICKS(timeout_ms));
+        else
+            ets_delay_us(timeout_ms * 1000);
+    }
+    if (in_data && in_words)
+        I2C_DEV_CHECK(dev, read_resp(dev, in_data, in_words));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+//////////////////////////////////////////////////////////////////
+
+esp_err_t scd30_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+
+    dev->port = port;
+    dev->addr = SCD30_I2C_ADDR;
+    dev->cfg.sda_io_num = sda_gpio;
+    dev->cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(dev);
+}
+
+esp_err_t scd30_free_desc(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(dev);
+}
+
+esp_err_t scd30_trigger_continuous_measurement(i2c_dev_t *dev, uint16_t p_comp)
+{
+    CHECK_ARG(p_comp == 0 || (p_comp >= 700 && p_comp <= 1400));
+
+    return execute_cmd(dev, CMD_TRIGGER_CONTINUOUS_MEASUREMENT, 0, &p_comp, 1, NULL, 0);
+}
+
+esp_err_t scd30_stop_continuous_measurement(i2c_dev_t *dev)
+{
+    return execute_cmd(dev, CMD_STOP_CONTINUOUS_MEASUREMENT, 1, NULL, 0, NULL, 0);
+}
+
+esp_err_t scd30_get_measurement_interval(i2c_dev_t *dev, uint16_t *interval_seconds)
+{
+    CHECK_ARG(interval_seconds);
+    return execute_cmd(dev, CMD_SET_MEASUREMENT_INTERVAL, 1, NULL, 0, interval_seconds, 1);
+}
+
+esp_err_t scd30_set_measurement_interval(i2c_dev_t *dev, uint16_t interval_seconds)
+{
+    CHECK_ARG(interval_seconds > 2 && interval_seconds < 1800);
+    return execute_cmd(dev, CMD_SET_MEASUREMENT_INTERVAL, 1, &interval_seconds, 1, NULL, 0);
+}
+
+esp_err_t scd30_get_data_ready_status(i2c_dev_t *dev, bool *data_ready)
+{
+    CHECK_ARG(data_ready);
+
+    uint16_t status;
+    CHECK(execute_cmd(dev, CMD_GET_DATA_READY_STATUS, 1, NULL, 0, &status, 1));
+    *data_ready = status != 0;
+
+    return ESP_OK;
+}
+
+esp_err_t scd30_read_measurement(i2c_dev_t *dev, float *co2, float *temperature, float *humidity)
+{
+    CHECK_ARG(co2 || temperature || humidity);
+
+    union {
+        uint32_t u32;
+        float f;
+    } tmp;
+    uint16_t buf[6];
+    CHECK(execute_cmd(dev, CMD_READ_MEASUREMENT, 3, NULL, 0, buf, 6));
+    if (co2)
+    {
+        tmp.u32 = ((uint32_t)buf[0] << 16) | buf[1];
+        *co2 = tmp.f;
+    }
+    if (temperature)
+    {
+        tmp.u32 = ((uint32_t)buf[2] << 16) | buf[3];
+        *temperature = tmp.f;
+    }
+    if (humidity)
+    {
+        tmp.u32 = ((uint32_t)buf[4] << 16) | buf[5];
+        *humidity = tmp.f;
+    }
+    return ESP_OK;
+}
+
+esp_err_t scd30_get_automatic_self_calibration(i2c_dev_t *dev, bool *enabled)
+{
+    CHECK_ARG(enabled);
+
+    return execute_cmd(dev, CMD_ACTIVATE_AUTOMATIC_SELF_CALIBRATION, 1, NULL, 0, (uint16_t *)enabled, 1);
+}
+
+esp_err_t scd30_set_automatic_self_calibration(i2c_dev_t *dev, bool enabled)
+{
+    return execute_cmd(dev, CMD_ACTIVATE_AUTOMATIC_SELF_CALIBRATION, 1, (uint16_t *)&enabled, 1, NULL, 0);
+}
+
+esp_err_t scd30_get_forced_recalibration_value(i2c_dev_t *dev, uint16_t *correction_value)
+{
+    CHECK_ARG(correction_value);
+
+    return execute_cmd(dev, CMD_SET_FORCED_RECALIBRATION_VALUE, 1,
+                       NULL, 0, correction_value, 1);
+}
+
+esp_err_t scd30_set_forced_recalibration_value(i2c_dev_t *dev, uint16_t target_co2_concentration)
+{
+    CHECK_ARG(target_co2_concentration);
+
+    return execute_cmd(dev, CMD_SET_FORCED_RECALIBRATION_VALUE, 1,
+                       &target_co2_concentration, 1, NULL, 0);
+}
+
+esp_err_t scd30_get_temperature_offset_ticks(i2c_dev_t *dev, uint16_t *t_offset)
+{
+    CHECK_ARG(t_offset);
+
+    return execute_cmd(dev, CMD_SET_TEMPERATURE_OFFSET, 1, NULL, 0, t_offset, 1);
+}
+
+esp_err_t scd30_get_temperature_offset(i2c_dev_t *dev, float *t_offset)
+{
+    CHECK_ARG(t_offset);
+    uint16_t raw;
+
+    CHECK(scd30_get_temperature_offset_ticks(dev, &raw));
+
+    *t_offset = (float)raw / 100;
+    return ESP_OK;
+}
+
+esp_err_t scd30_set_temperature_offset_ticks(i2c_dev_t *dev, uint16_t t_offset)
+{
+    return execute_cmd(dev, CMD_SET_TEMPERATURE_OFFSET, 1, &t_offset, 1, NULL, 0);
+}
+
+esp_err_t scd30_set_temperature_offset(i2c_dev_t *dev, float t_offset)
+{
+    uint16_t raw = (uint16_t)(t_offset * 100);
+    return scd30_set_temperature_offset_ticks(dev, raw);
+}
+
+esp_err_t scd30_get_sensor_altitude(i2c_dev_t *dev, uint16_t *altitude)
+{
+    CHECK_ARG(altitude);
+
+    return execute_cmd(dev, CMD_ALTITUDE_COMPENSATION, 1, NULL, 0, altitude, 1);
+}
+
+esp_err_t scd30_set_sensor_altitude(i2c_dev_t *dev, uint16_t altitude)
+{
+    return execute_cmd(dev, CMD_ALTITUDE_COMPENSATION, 1, &altitude, 1, NULL, 0);
+}
+
+esp_err_t scd30_read_firmware_version(i2c_dev_t *dev, uint16_t *firmware_version)
+{
+    CHECK_ARG(firmware_version);
+    return execute_cmd(dev, CMD_READ_FIRMWARE_VERSION, 1, NULL, 0, firmware_version, 1);
+}
+
+esp_err_t scd30_soft_reset(i2c_dev_t *dev)
+{
+    return execute_cmd(dev, CMD_SOFT_RESET, 0, NULL, 0, NULL, 0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/scd30/scd30.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,310 @@
+/*
+ * Copyright (c) 2021, Sensirion AG
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ * Copyright (c) 2021 Nate Usher <n.usher87@gmail.com>
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file scd30.h
+ * @defgroup scd30 scd30
+ * @{
+ *
+ * ESP-IDF driver for Sensirion SCD30 CO2 sensor.
+ *
+ * Adapted from https://github.com/UncleRus/esp-idf-lib/tree/master/components/scd4x
+ * 
+ * Copyright (c) 2021, Sensirion AG
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ * Copyright (c) 2021 Nate Usher <n.usher87@gmail.com>
+ * 
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __SCD30_H__
+#define __SCD30_H__
+
+#include <i2cdev.h>
+#include <esp_err.h>
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define SCD30_I2C_ADDR 0x61
+
+/**
+ * @brief Initialize device descriptor.
+ *
+ * @param dev      Device descriptor
+ * @param port     I2C port
+ * @param sda_gpio SDA GPIO
+ * @param scl_gpio SCL GPIO
+ * @return         `ESP_OK` on success
+ */
+esp_err_t scd30_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor.
+ *
+ * @param dev Device descriptor
+ * @return    `ESP_OK` on success
+ */
+esp_err_t scd30_free_desc(i2c_dev_t *dev);
+
+/**
+ * @brief Trigger continuous  measurement.
+ *
+ * Signal update interval default is 2 seconds.
+ *
+ * @param dev           Device descriptor
+ * @param p_comp        Optional ambient pressure compensation in mBar, 0 to deactivate 
+ * @return              `ESP_OK` on success
+ */
+esp_err_t scd30_trigger_continuous_measurement(i2c_dev_t *dev, uint16_t p_comp);
+
+/**
+ * @brief Stop continuous measurement.
+ *
+ * Stops the continuous measurement of the SCD30.
+ *
+ * @param dev Device descriptor
+ * @return    `ESP_OK` on success
+ */
+esp_err_t scd30_stop_continuous_measurement(i2c_dev_t *dev);
+
+/**
+ * @brief Get measurement interval
+ *
+ * Gets the interval used bythe SCD30 sensor to measure in continuous measurement mode.
+ * Saved in non-volatile memory.
+ *
+ * @param dev                   Device descriptor
+ * @param interval_seconds      Measurement interval in seconds 
+ * 
+ * @return         `ESP_OK` on success
+ */
+esp_err_t scd30_get_measurement_interval(i2c_dev_t *dev, uint16_t *interval_seconds);
+
+/**
+ * @brief Set measurement interval
+ *
+ * Sets the interval used bythe SCD30 sensor to measure in continuous measurement mode.
+ * Saved in non-volatile memory.
+ *
+ * @param dev                   Device descriptor
+ * @param interval_seconds      Measurement interval in seconds 
+ * 
+ * @return         `ESP_OK` on success
+ */
+esp_err_t scd30_set_measurement_interval(i2c_dev_t *dev, uint16_t interval_seconds);
+
+/**
+ * @brief Check whether new measurement data is available for read-out.
+ *
+ * @param dev        Device descriptor
+ * @param data_ready true if data is ready, false otherwise
+ * @return           `ESP_OK` on success
+ */
+esp_err_t scd30_get_data_ready_status(i2c_dev_t *dev, bool *data_ready);
+
+/**
+ * @brief Read sensor output and convert.
+ *
+ * When new measurement data is available it can be read out with the following command.
+ * Make sure that the measurement is completed by calling scd30_get_data_ready_status()
+ *
+ * @param dev         Device descriptor
+ * @param co2         CO₂ concentration in ppm
+ * @param temperature Temperature in degrees Celsius (°C)
+ * @param humidity    Relative humidity in percent RH
+ * @return            `ESP_OK` on success
+ */
+esp_err_t scd30_read_measurement(i2c_dev_t *dev, float *co2, float *temperature, float *humidity);
+
+/**
+ * @brief Get automatic self calibration (ASC) state.
+ *
+ * By default, the ASC is disabled.
+ *
+ * @param dev     Device descriptor.
+ * @param enabled true if ASC is enabled, false otherwise
+ * @return        `ESP_OK` on success
+ */
+esp_err_t scd30_get_automatic_self_calibration(i2c_dev_t *dev, bool *enabled);
+
+/**
+ * @brief Enable or disable automatic self calibration (ASC).
+ *
+ * By default, the ASC is disabled.
+ *
+ * @param dev     Device descriptor.
+ * @param enabled true to enable ASC, false to disable ASC
+ * @return        `ESP_OK` on success
+ */
+esp_err_t scd30_set_automatic_self_calibration(i2c_dev_t *dev, bool enabled);
+
+/**
+ * @brief Get Forced Recalibration Value
+ * 
+ * See scd_30_set_forced_recalibration_value.
+ * 
+ * The most recently used reference value is retained in volatile memory and 
+ * can be read out with the command sequence given below. After repowering 
+ * the sensor, the command will return the standard reference value of 400 ppm.
+ * 
+ * @param dev                      Device descriptor.
+ * @param correction_value         FRC correction value in CO₂ ppm 
+ * 
+ * @return                         `ESP_OK` on success
+ */
+esp_err_t scd30_get_forced_recalibration_value(i2c_dev_t *dev,
+        uint16_t *correction_value);
+
+/**
+ * @brief Set Forced Recalibration Value.
+ *
+ * Forced recalibration (FRC) is used to compensate for sensor drifts when a reference
+ * value of the CO2 concentration in close proximity to the SCD30 is available.
+ * For best results,the sensor has to be run in a stable environment in continuous
+ * mode at a measurement rateof 2s for at least two minutes before applying the FRC
+ * command and sending the reference value. Setting a reference CO2 concentration 
+ * by the method described here will always supersede corrections from the ASC
+ * 
+ * Imposes a permanent update to the calibration curve which persists after repowering
+ * the sensor.
+ * @param dev                      Device descriptor.
+ * @param target_co2_concentration Target CO₂ concentration in ppm. (400 <= val <= 2000)
+ *                                 
+ * @return                         `ESP_OK` on success
+ */
+esp_err_t scd30_set_forced_recalibration_value(i2c_dev_t *dev,
+        uint16_t target_co2_concentration);
+
+/**
+ * @brief Get temperature offset in ticks.
+ *
+ * Get the current temperature offset value saved in non-volatile memory.  
+ *
+ * @note Only available in idle mode.
+ *
+ * @param dev      Device descriptor
+ * @param t_offset Temperature offset.
+ *                 Convert value to °C by: value * 100; 
+ * @return         `ESP_OK` on success
+ */
+esp_err_t scd30_get_temperature_offset_ticks(i2c_dev_t *dev, uint16_t *t_offset);
+
+/**
+ * @brief Get temperature offset in °C.
+ *
+ * See ::scd30_get_temperature_offset_ticks() for more details.
+ *
+ * @param dev      Device descriptor
+ * @param t_offset Temperature offset in degrees Celsius (°C)
+ * @return         `ESP_OK` on success
+ */
+esp_err_t scd30_get_temperature_offset(i2c_dev_t *dev, float *t_offset);
+
+/**
+ * @brief Set temperature offset in ticks.
+ *
+ * Set the temperature offset value to be saved in non-volatile memory.
+ * The last set value will be used for temperature offset compensation after repowering.
+ *
+ * @param dev      Device descriptor
+ * @param t_offset Temperature offset.
+ *                 Convert °C to value by: T / 100;
+ * @return         `ESP_OK` on success
+ */
+esp_err_t scd30_set_temperature_offset_ticks(i2c_dev_t *dev, uint16_t t_offset);
+
+/**
+ * @brief Set temperature offset in °C.
+ *
+ * See ::scd30_set_temperature_offset_ticks() for more details.
+ *
+ * @param dev      Device descriptor
+ * @param t_offset Temperature offset in degrees Celsius (°C)
+ * @return         `ESP_OK` on success
+ */
+esp_err_t scd30_set_temperature_offset(i2c_dev_t *dev, float t_offset);
+
+/**
+ * @brief Get configured sensor altitude.
+ *
+ * Get configured sensor altitude in meters above sea level. Per default, the
+ * sensor altitude is set to 0 meter above sea-level.
+ *
+ * @note Only available in idle mode.
+ *
+ * @param dev      Device descriptor.
+ * @param altitude Sensor altitude in meters.
+ * @return         `ESP_OK` on success
+ */
+esp_err_t scd30_get_sensor_altitude(i2c_dev_t *dev, uint16_t *altitude);
+
+/**
+ * @brief Set sensor altitude in meters above sea level.
+ *
+ * Note that setting a sensor altitude to the sensor overrides any pressure
+ * compensation based on a previously set ambient pressure.
+ *
+ * @param dev      Device descriptor.
+ * @param altitude Sensor altitude in meters.
+ * @return         `ESP_OK` on success
+ */
+esp_err_t scd30_set_sensor_altitude(i2c_dev_t *dev, uint16_t altitude);
+
+/**
+ * @brief Get firmware version.
+ *
+ * Following command can be used to read out the firmware version of SCD30 module
+ * The MSB is the major firmware version, the LSB is the minor firmware version 
+ * @param dev                   Device descriptor
+ * @param firmware_version      Firmware version 
+ * 
+ * @return        `ESP_OK` on success
+ */
+esp_err_t scd30_read_firmware_version(i2c_dev_t *dev, uint16_t *firmware_version);
+
+/**
+ * @brief Reset the sensor
+ *
+ * Soft reset mechanism that forces the sensor into the same state as after powering up
+ * After soft reset the sensor will reload all calibrated data. 
+ * 
+ * @param dev Device descriptor
+ * @return    `ESP_OK` on success
+ */
+esp_err_t scd30_soft_reset(i2c_dev_t *dev);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __SCD3O_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/scd4x/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,27 @@
+---
+components:
+  - name: scd4x
+    description: Driver for SCD40/SCD41 miniature CO₂ sensor
+    group: air-quality
+    groups:
+      - name: gas
+    code_owners: UncleRus
+    depends:
+      - i2cdev
+      - log
+      - esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2021
+      - author:
+          name: Sensirion
+        year: 2021
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/scd4x/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS scd4x.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/scd4x/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,27 @@
+Copyright (c) 2021, Sensirion AG
+Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/scd4x/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/scd4x/scd4x.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,365 @@
+/*
+ * Copyright (c) 2021, Sensirion AG
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file scd4x.c
+ *
+ * ESP-IDF driver for SCD4x CO2 sensor.
+ *
+ * Ported from https://github.com/Sensirion/raspberry-pi-i2c-scd4x
+ *
+ * Copyright (c) 2021, Sensirion AG
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <esp_log.h>
+#include <ets_sys.h>
+#include <esp_idf_lib_helpers.h>
+#include "scd4x.h"
+
+#define I2C_FREQ_HZ 100000 // 100kHz
+
+static const char *TAG = "scd4x";
+
+#define CMD_START_PERIODIC_MEASUREMENT             (0x21B1)
+#define CMD_READ_MEASUREMENT                       (0xEC05)
+#define CMD_STOP_PERIODIC_MEASUREMENT              (0x3F86)
+#define CMD_SET_TEMPERATURE_OFFSET                 (0x241D)
+#define CMD_GET_TEMPERATURE_OFFSET                 (0x2318)
+#define CMD_SET_SENSOR_ALTITUDE                    (0x2427)
+#define CMD_GET_SENSOR_ALTITUDE                    (0x2322)
+#define CMD_SET_AMBIENT_PRESSURE                   (0xE000)
+#define CMD_PERFORM_FORCED_RECALIBRATION           (0x362F)
+#define CMD_SET_AUTOMATIC_SELF_CALIBRATION_ENABLED (0x2416)
+#define CMD_GET_AUTOMATIC_SELF_CALIBRATION_ENABLED (0x2313)
+#define CMD_START_LOW_POWER_PERIODIC_MEASUREMENT   (0x21AC)
+#define CMD_GET_DATA_READY_STATUS                  (0xE4B8)
+#define CMD_PERSIST_SETTINGS                       (0x3615)
+#define CMD_GET_SERIAL_NUMBER                      (0x3682)
+#define CMD_PERFORM_SELF_TEST                      (0x3639)
+#define CMD_PERFORM_FACTORY_RESET                  (0x3632)
+#define CMD_REINIT                                 (0x3646)
+#define CMD_MEASURE_SINGLE_SHOT                    (0x219D)
+#define CMD_MEASURE_SINGLE_SHOT_RHT_ONLY           (0x2196)
+#define CMD_POWER_DOWN                             (0x36E0)
+#define CMD_WAKE_UP                                (0x36F6)
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+static uint8_t crc8(const uint8_t *data, size_t count)
+{
+    uint8_t res = 0xff;
+
+    for (size_t i = 0; i < count; ++i)
+    {
+        res ^= data[i];
+        for (uint8_t bit = 8; bit > 0; --bit)
+        {
+            if (res & 0x80)
+                res = (res << 1) ^ 0x31;
+            else
+                res = (res << 1);
+        }
+    }
+    return res;
+}
+
+static inline uint16_t swap(uint16_t v)
+{
+    return (v << 8) | (v >> 8);
+}
+
+static esp_err_t send_cmd(i2c_dev_t *dev, uint16_t cmd, uint16_t *data, size_t words)
+{
+    uint8_t buf[2 + words * 3];
+    // add command
+    *(uint16_t *)buf = swap(cmd);
+    if (data && words)
+        // add arguments
+        for (size_t i = 0; i < words; i++)
+        {
+            uint8_t *p = buf + 2 + i * 3;
+            *(uint16_t *)p = swap(data[i]);
+            *(p + 2) = crc8(p, 2);
+        }
+
+    ESP_LOGV(TAG, "Sending buffer:");
+    ESP_LOG_BUFFER_HEX_LEVEL(TAG, buf, sizeof(buf), ESP_LOG_VERBOSE);
+
+    return i2c_dev_write(dev, NULL, 0, buf, sizeof(buf));
+}
+
+static esp_err_t read_resp(i2c_dev_t *dev, uint16_t *data, size_t words)
+{
+    uint8_t buf[words * 3];
+    CHECK(i2c_dev_read(dev, NULL, 0, buf, sizeof(buf)));
+
+    ESP_LOGV(TAG, "Received buffer:");
+    ESP_LOG_BUFFER_HEX_LEVEL(TAG, buf, sizeof(buf), ESP_LOG_VERBOSE);
+
+    for (size_t i = 0; i < words; i++)
+    {
+        uint8_t *p = buf + i * 3;
+        uint8_t crc = crc8(p, 2);
+        if (crc != *(p + 2))
+        {
+            ESP_LOGE(TAG, "Invalid CRC 0x%02x, expected 0x%02x", crc, *(p + 2));
+            return ESP_ERR_INVALID_CRC;
+        }
+        data[i] = swap(*(uint16_t *)p);
+    }
+    return ESP_OK;
+}
+
+static esp_err_t execute_cmd(i2c_dev_t *dev, uint16_t cmd, uint32_t timeout_ms,
+        uint16_t *out_data, size_t out_words, uint16_t *in_data, size_t in_words)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, send_cmd(dev, cmd, out_data, out_words));
+    if (timeout_ms)
+    {
+        if (timeout_ms > 10)
+            vTaskDelay(pdMS_TO_TICKS(timeout_ms));
+        else
+            ets_delay_us(timeout_ms * 1000);
+    }
+    if (in_data && in_words)
+        I2C_DEV_CHECK(dev, read_resp(dev, in_data, in_words));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t scd4x_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+
+    dev->port = port;
+    dev->addr = SCD4X_I2C_ADDR;
+    dev->cfg.sda_io_num = sda_gpio;
+    dev->cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(dev);
+}
+
+esp_err_t scd4x_free_desc(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(dev);
+}
+
+esp_err_t scd4x_start_periodic_measurement(i2c_dev_t *dev)
+{
+    return execute_cmd(dev, CMD_START_PERIODIC_MEASUREMENT, 1, NULL, 0, NULL, 0);
+}
+
+esp_err_t scd4x_read_measurement_ticks(i2c_dev_t *dev, uint16_t *co2, uint16_t *temperature, uint16_t *humidity)
+{
+    CHECK_ARG(co2 || temperature || humidity);
+
+    uint16_t buf[3];
+    CHECK(execute_cmd(dev, CMD_READ_MEASUREMENT, 1, NULL, 0, buf, 3));
+    if (co2)
+        *co2 = buf[0];
+    if (temperature)
+        *temperature = buf[1];
+    if (humidity)
+        *humidity = buf[2];
+
+    return ESP_OK;
+}
+
+esp_err_t scd4x_read_measurement(i2c_dev_t *dev, uint16_t *co2, float *temperature, float *humidity)
+{
+    CHECK_ARG(co2 || temperature || humidity);
+    uint16_t t_raw, h_raw;
+
+    CHECK(scd4x_read_measurement_ticks(dev, co2, &t_raw, &h_raw));
+    if (temperature)
+        *temperature = (float)t_raw * 175.0f / 65536.0f - 45.0f;
+    if (humidity)
+        *humidity = (float)h_raw * 100.0f / 65536.0f;
+
+    return ESP_OK;
+}
+
+esp_err_t scd4x_stop_periodic_measurement(i2c_dev_t *dev)
+{
+    return execute_cmd(dev, CMD_STOP_PERIODIC_MEASUREMENT, 500, NULL, 0, NULL, 0);
+}
+
+esp_err_t scd4x_get_temperature_offset_ticks(i2c_dev_t *dev, uint16_t *t_offset)
+{
+    CHECK_ARG(t_offset);
+
+    return execute_cmd(dev, CMD_GET_TEMPERATURE_OFFSET, 1, NULL, 0, t_offset, 1);
+}
+
+esp_err_t scd4x_get_temperature_offset(i2c_dev_t *dev, float *t_offset)
+{
+    CHECK_ARG(t_offset);
+    uint16_t raw;
+
+    CHECK(scd4x_get_temperature_offset_ticks(dev, &raw));
+
+    *t_offset = (float)raw * 175.0f / 65536.0f;
+
+    return ESP_OK;
+}
+
+esp_err_t scd4x_set_temperature_offset_ticks(i2c_dev_t *dev, uint16_t t_offset)
+{
+    return execute_cmd(dev, CMD_SET_TEMPERATURE_OFFSET, 1, &t_offset, 1, NULL, 0);
+}
+
+esp_err_t scd4x_set_temperature_offset(i2c_dev_t *dev, float t_offset)
+{
+    uint16_t raw = (uint16_t)(t_offset * 65536.0f / 175.0f + 0.5f);
+    return scd4x_set_temperature_offset_ticks(dev, raw);
+}
+
+esp_err_t scd4x_get_sensor_altitude(i2c_dev_t *dev, uint16_t *altitude)
+{
+    CHECK_ARG(altitude);
+
+    return execute_cmd(dev, CMD_GET_SENSOR_ALTITUDE, 1, NULL, 0, altitude, 1);
+}
+
+esp_err_t scd4x_set_sensor_altitude(i2c_dev_t *dev, uint16_t altitude)
+{
+    return execute_cmd(dev, CMD_SET_SENSOR_ALTITUDE, 1, &altitude, 1, NULL, 0);
+}
+
+esp_err_t scd4x_set_ambient_ressure(i2c_dev_t *dev, uint16_t pressure)
+{
+    return execute_cmd(dev, CMD_SET_AMBIENT_PRESSURE, 1, &pressure, 1, NULL, 0);
+}
+
+esp_err_t scd4x_perform_forced_recalibration(i2c_dev_t *dev, uint16_t target_co2_concentration,
+        uint16_t *frc_correction)
+{
+    CHECK_ARG(frc_correction);
+
+    return execute_cmd(dev, CMD_PERFORM_FORCED_RECALIBRATION, 400,
+            &target_co2_concentration, 1, frc_correction, 1);
+}
+
+esp_err_t scd4x_get_automatic_self_calibration(i2c_dev_t *dev, bool *enabled)
+{
+    CHECK_ARG(enabled);
+
+    return execute_cmd(dev, CMD_GET_AUTOMATIC_SELF_CALIBRATION_ENABLED, 1, NULL, 0, (uint16_t *)enabled, 1);
+}
+
+esp_err_t scd4x_set_automatic_self_calibration(i2c_dev_t *dev, bool enabled)
+{
+    return execute_cmd(dev, CMD_SET_AUTOMATIC_SELF_CALIBRATION_ENABLED, 1, (uint16_t *)&enabled, 1, NULL, 0);
+}
+
+esp_err_t scd4x_start_low_power_periodic_measurement(i2c_dev_t *dev)
+{
+    return execute_cmd(dev, CMD_START_LOW_POWER_PERIODIC_MEASUREMENT, 0, NULL, 0, NULL, 0);
+}
+
+esp_err_t scd4x_get_data_ready_status(i2c_dev_t *dev, bool *data_ready)
+{
+    CHECK_ARG(data_ready);
+
+    uint16_t status;
+    CHECK(execute_cmd(dev, CMD_GET_DATA_READY_STATUS, 1, NULL, 0, &status, 1));
+    *data_ready = (status & 0x7ff) != 0;
+
+    return ESP_OK;
+}
+
+esp_err_t scd4x_persist_settings(i2c_dev_t *dev)
+{
+    return execute_cmd(dev, CMD_PERSIST_SETTINGS, 800, NULL, 0, NULL, 0);
+}
+
+esp_err_t scd4x_get_serial_number(i2c_dev_t *dev, uint16_t *serial0, uint16_t *serial1, uint16_t *serial2)
+{
+    CHECK_ARG(serial0 && serial1 && serial2);
+
+    uint16_t buf[3];
+    CHECK(execute_cmd(dev, CMD_GET_SERIAL_NUMBER, 1, NULL, 0, buf, 3));
+    *serial0 = buf[0];
+    *serial1 = buf[1];
+    *serial2 = buf[2];
+
+    return ESP_OK;
+}
+
+esp_err_t scd4x_perform_self_test(i2c_dev_t *dev, bool *malfunction)
+{
+    CHECK_ARG(malfunction);
+
+    return execute_cmd(dev, CMD_PERFORM_SELF_TEST, 10000, NULL, 0, (uint16_t *)malfunction, 1);
+}
+
+esp_err_t scd4x_perform_factory_reset(i2c_dev_t *dev)
+{
+    return execute_cmd(dev, CMD_PERFORM_FACTORY_RESET, 800, NULL, 0, NULL, 0);
+}
+
+esp_err_t scd4x_reinit(i2c_dev_t *dev)
+{
+    return execute_cmd(dev, CMD_REINIT, 20, NULL, 0, NULL, 0);
+}
+
+esp_err_t scd4x_measure_single_shot(i2c_dev_t *dev)
+{
+    return execute_cmd(dev, CMD_MEASURE_SINGLE_SHOT, 5000, NULL, 0, NULL, 0);
+}
+
+esp_err_t scd4x_measure_single_shot_rht_only(i2c_dev_t *dev)
+{
+    return execute_cmd(dev, CMD_MEASURE_SINGLE_SHOT_RHT_ONLY, 50, NULL, 0, NULL, 0);
+}
+
+esp_err_t scd4x_power_down(i2c_dev_t *dev)
+{
+    return execute_cmd(dev, CMD_POWER_DOWN, 1, NULL, 0, NULL, 0);
+}
+
+esp_err_t scd4x_wake_up(i2c_dev_t *dev)
+{
+    return execute_cmd(dev, CMD_WAKE_UP, 20, NULL, 0, NULL, 0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/scd4x/scd4x.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,428 @@
+/*
+ * Copyright (c) 2021, Sensirion AG
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file scd4x.h
+ * @defgroup scd4x scd4x
+ * @{
+ *
+ * ESP-IDF driver for SCD4x CO2 sensor.
+ *
+ * Ported from https://github.com/Sensirion/raspberry-pi-i2c-scd4x
+ *
+ * Copyright (c) 2021, Sensirion AG
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __SCD4X_H__
+#define __SCD4X_H__
+
+#include <i2cdev.h>
+#include <esp_err.h>
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define SCD4X_I2C_ADDR 0x62
+
+/**
+ * @brief Initialize device descriptor.
+ *
+ * @param dev      Device descriptor
+ * @param port     I2C port
+ * @param sda_gpio SDA GPIO
+ * @param scl_gpio SCL GPIO
+ * @return         `ESP_OK` on success
+ */
+esp_err_t scd4x_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor.
+ *
+ * @param dev Device descriptor
+ * @return    `ESP_OK` on success
+ */
+esp_err_t scd4x_free_desc(i2c_dev_t *dev);
+
+/**
+ * @brief Start periodic measurement.
+ *
+ * Signal update interval is 5 seconds.
+ *
+ * @note This command is only available in idle mode.
+ *
+ * @param dev Device descriptor
+ * @return    `ESP_OK` on success
+ */
+esp_err_t scd4x_start_periodic_measurement(i2c_dev_t *dev);
+
+/**
+ * @brief Read sensor output.
+ *
+ * The measurement data can only be read out once per signal update interval
+ * as the buffer is emptied upon read-out. If no data is available in the
+ * buffer, the sensor returns a NACK. To avoid a NACK response the
+ * ::scd4x_get_data_ready_status() can be called to check data status.
+ *
+ * @note This command is only available in measurement mode. The firmware
+ * updates the measurement values depending on the measurement mode.
+ *
+ * @param dev         Device descriptor
+ * @param co2         CO₂ concentration in ppm
+ * @param temperature Convert value to °C by: -45 °C + 175 °C * value/2^16
+ * @param humidity    Convert value to %RH by: 100%RH * value/2^16
+ * @return            `ESP_OK` on success
+ */
+esp_err_t scd4x_read_measurement_ticks(i2c_dev_t *dev, uint16_t *co2, uint16_t *temperature, uint16_t *humidity);
+
+/**
+ * @brief Read sensor output and convert.
+ *
+ * See ::scd4x_read_measurement_ticks() for more details.
+ *
+ * @note This command is only available in measurement mode. The firmware
+ * updates the measurement values depending on the measurement mode.
+ *
+ * @param dev         Device descriptor
+ * @param co2         CO₂ concentration in ppm
+ * @param temperature Temperature in degrees Celsius (°C)
+ * @param humidity    Relative humidity in percent RH
+ * @return            `ESP_OK` on success
+ */
+esp_err_t scd4x_read_measurement(i2c_dev_t *dev, uint16_t *co2, float *temperature, float *humidity);
+
+/**
+ * @brief Stop periodic measurement.
+ *
+ * Stop periodic measurement and return to idle mode for sensor configuration
+ * or to safe energy.
+ *
+ * @note This command is only available in measurement mode.
+ *
+ * @param dev Device descriptor
+ * @return    `ESP_OK` on success
+ */
+esp_err_t scd4x_stop_periodic_measurement(i2c_dev_t *dev);
+
+/**
+ * @brief Get temperature offset in ticks.
+ *
+ * The temperature offset represents the difference between the measured
+ * temperature by the SCD4x and the actual ambient temperature. Per default,
+ * the temperature offset is set to 4°C.
+ *
+ * @note Only available in idle mode.
+ *
+ * @param dev      Device descriptor
+ * @param t_offset Temperature offset.
+ *                 Convert value to °C by: 175 * value / 2^16
+ * @return         `ESP_OK` on success
+ */
+esp_err_t scd4x_get_temperature_offset_ticks(i2c_dev_t *dev, uint16_t *t_offset);
+
+/**
+ * @brief Get temperature offset in °C.
+ *
+ * See ::scd4x_get_temperature_offset_ticks() for more details.
+ *
+ * @note Only available in idle mode.
+ *
+ * @param dev      Device descriptor
+ * @param t_offset Temperature offset in degrees Celsius (°C)
+ * @return         `ESP_OK` on success
+ */
+esp_err_t scd4x_get_temperature_offset(i2c_dev_t *dev, float *t_offset);
+
+/**
+ * @brief Set temperature offset in ticks.
+ *
+ * Setting the temperature offset of the SCD4x inside the customer device
+ * correctly allows the user to leverage the RH and T output signal. Note
+ * that the temperature offset can depend on various factors such as the
+ * SCD4x measurement mode, self-heating of close components, the ambient
+ * temperature and air flow. Thus, the SCD4x temperature offset should be
+ * determined inside the customer device under its typical operation and in
+ * thermal equilibrium.
+ *
+ * @note Only available in idle mode.
+ *
+ * @param dev      Device descriptor
+ * @param t_offset Temperature offset.
+ *                 Convert °C to value by: T * 2^16 / 175
+ * @return         `ESP_OK` on success
+ */
+esp_err_t scd4x_set_temperature_offset_ticks(i2c_dev_t *dev, uint16_t t_offset);
+
+/**
+ * @brief Set temperature offset in °C.
+ *
+ * See ::scd4x_set_temperature_offset_ticks() for more details.
+ *
+ * @note Only available in idle mode.
+ *
+ * @param dev      Device descriptor
+ * @param t_offset Temperature offset in degrees Celsius (°C)
+ * @return         `ESP_OK` on success
+ */
+esp_err_t scd4x_set_temperature_offset(i2c_dev_t *dev, float t_offset);
+
+/**
+ * @brief Get configured sensor altitude.
+ *
+ * Get configured sensor altitude in meters above sea level. Per default, the
+ * sensor altitude is set to 0 meter above sea-level.
+ *
+ * @note Only available in idle mode.
+ *
+ * @param dev      Device descriptor.
+ * @param altitude Sensor altitude in meters.
+ * @return         `ESP_OK` on success
+ */
+esp_err_t scd4x_get_sensor_altitude(i2c_dev_t *dev, uint16_t *altitude);
+
+/**
+ * @brief Set sensor altitude in meters above sea level.
+ *
+ * Note that setting a sensor altitude to the sensor overrides any pressure
+ * compensation based on a previously set ambient pressure.
+ *
+ * @note Only available in idle mode.
+ *
+ * @param dev      Device descriptor.
+ * @param altitude Sensor altitude in meters.
+ * @return         `ESP_OK` on success
+ */
+esp_err_t scd4x_set_sensor_altitude(i2c_dev_t *dev, uint16_t altitude);
+
+/**
+ * @brief Set ambient pressure.
+ *
+ * The set_ambient_pressure command can be sent during periodic measurements
+ * to enable continuous pressure compensation. Note that setting an ambient
+ * pressure to the sensor overrides any pressure compensation based on a
+ * previously set sensor altitude.
+ *
+ * @note Available during measurements.
+ *
+ * @param dev      Device descriptor.
+ * @param pressure Ambient pressure in hPa.
+ *                 Convert value to Pa by: value * 100
+ * @return         `ESP_OK` on success
+ */
+esp_err_t scd4x_set_ambient_ressure(i2c_dev_t *dev, uint16_t pressure);
+
+/**
+ * @brief Perform forced recalibration.
+ *
+ * To successfully conduct an accurate forced recalibration, the following
+ * steps need to be carried out:
+ * - Operate the SCD4x in a periodic measurement mode for > 3 minutes in an
+ *   environment with homogenous and constant CO₂ concentration.
+ * - Stop periodic measurement. Wait 500 ms.
+ * - Subsequently call scd4x_perform_forced_recalibration() and optionally
+ *   read out the baseline correction. A return value of 0xffff indicates
+ *   that the forced recalibration failed.
+ *
+ * @param dev                      Device descriptor.
+ * @param target_co2_concentration Target CO₂ concentration in ppm.
+ * @param frc_correction           FRC correction value in CO₂ ppm or 0xFFFF
+ *                                 if the command failed.
+ * @return                         `ESP_OK` on success
+ */
+esp_err_t scd4x_perform_forced_recalibration(i2c_dev_t *dev,
+        uint16_t target_co2_concentration, uint16_t *frc_correction);
+
+/**
+ * @brief Get automatic self calibration (ASC) state.
+ *
+ * By default, the ASC is enabled.
+ *
+ * @param dev     Device descriptor.
+ * @param enabled true if ASC is enabled, false otherwise
+ * @return        `ESP_OK` on success
+ */
+esp_err_t scd4x_get_automatic_self_calibration(i2c_dev_t *dev, bool *enabled);
+
+/**
+ * @brief Enable or disable automatic self calibration (ASC).
+ *
+ * By default, the ASC is enabled.
+ *
+ * @param dev     Device descriptor.
+ * @param enabled true to enable ASC, false to disable ASC
+ * @return        `ESP_OK` on success
+ */
+esp_err_t scd4x_set_automatic_self_calibration(i2c_dev_t *dev, bool enabled);
+
+/**
+ * @brief Start low power periodic measurement.
+ *
+ * Signal update interval is 30 seconds.
+ *
+ * @note This command is only available in idle mode.
+ *
+ * @param dev Device descriptor
+ * @return    `ESP_OK` on success
+ */
+esp_err_t scd4x_start_low_power_periodic_measurement(i2c_dev_t *dev);
+
+/**
+ * @brief Check whether new measurement data is available for read-out.
+ *
+ * @param dev        Device descriptor
+ * @param data_ready true if data is ready, false otherwise
+ * @return           `ESP_OK` on success
+ */
+esp_err_t scd4x_get_data_ready_status(i2c_dev_t *dev, bool *data_ready);
+
+/**
+ * @brief Store current configuration in EEPROM.
+ *
+ * Configuration settings such as the temperature offset, sensor altitude and
+ * the ASC enabled/disabled parameter are by default stored in the volatile
+ * memory (RAM) only and will be lost after a power-cycle. This funciton
+ * stores the current configuration in the EEPROM of the SCD4x, making them
+ * resistant to power-cycling. To avoid unnecessary wear of the EEPROM,
+ * function should only be called when persistence is required and if actual
+ * changes to the configuration have been made. Note that field calibration
+ * history (i.e. FRC and ASC) is stored in the EEPROM automatically.
+ *
+ * @param dev Device descriptor
+ * @return    `ESP_OK` on success
+ */
+esp_err_t scd4x_persist_settings(i2c_dev_t *dev);
+
+/**
+ * @brief Read serial number.
+ *
+ * Reading out the serial number can be used to identify the chip and to verify
+ * the presence of the sensor. Function returns 3 words. Together, the 3 words
+ * constitute a unique serial number with a length of 48 bits (big endian format).
+ *
+ * @param dev     Device descriptor
+ * @param serial0 First word of the 48 bit serial number
+ * @param serial1 Second word of the 48 bit serial number
+ * @param serial2 Third word of the 48 bit serial number
+ * @return        `ESP_OK` on success
+ */
+esp_err_t scd4x_get_serial_number(i2c_dev_t *dev, uint16_t *serial0, uint16_t *serial1, uint16_t *serial2);
+
+/**
+ * @brief Perform self-test.
+ *
+ * This function can be used as an  end-of-line test to confirm sensor
+ * functionality.
+ *
+ * @param dev         Device descriptor
+ * @param malfunction true if malfunction detected, false if device is OK
+ * @return            `ESP_OK` on success
+ */
+esp_err_t scd4x_perform_self_test(i2c_dev_t *dev, bool *malfunction);
+
+/**
+ * @brief Factory reset sensor.
+ *
+ * Initiates the reset of all configurations stored in the EEPROM and erases the
+ * FRC and ASC algorithm history.
+ *
+ * @param dev Device descriptor
+ * @return    `ESP_OK` on success
+ */
+esp_err_t scd4x_perform_factory_reset(i2c_dev_t *dev);
+
+/**
+ * @brief Reinitialize sensor.
+ *
+ * The reinit command reinitializes the sensor by reloading user settings from
+ * EEPROM. Before sending the reinit command, the stop measurement command must
+ * be issued. If reinit command does not trigger the desired re-initialization,
+ * a power-cycle should be applied to the SCD4x.
+ *
+ * @note Only available in idle mode.
+ *
+ * @param dev Device descriptor
+ * @return    `ESP_OK` on success
+ */
+esp_err_t scd4x_reinit(i2c_dev_t *dev);
+
+/**
+ * @brief Perform single measurement.
+ *
+ * On-demand measurement of CO₂ concentration, relative humidity and temperature.
+ * The sensor output is read with the ::scd4x_read_measurement() function.
+ *
+ * @note Only available in idle mode.
+ *
+ * @param dev Device descriptor
+ * @return    `ESP_OK` on success
+ */
+esp_err_t scd4x_measure_single_shot(i2c_dev_t *dev);
+
+/**
+ * @brief Perform single measurement of of relative humidity and temperature
+ *        only.
+ *
+ * @note Only available in idle mode.
+ *
+ * @param dev Device descriptor
+ * @return    `ESP_OK` on success
+ */
+esp_err_t scd4x_measure_single_shot_rht_only(i2c_dev_t *dev);
+
+/**
+ * @brief Put the sensor from idle to sleep mode.
+ *
+ * Call this function to reduce current consumption.
+ *
+ * @note Only available in idle mode.
+ *
+ * @param dev Device descriptor
+ * @return    `ESP_OK` on success
+ */
+esp_err_t scd4x_power_down(i2c_dev_t *dev);
+
+/**
+ * @brief Wake up sensor from sleep mode to idle mode.
+ *
+ * @note Only available in sleep mode.
+ *
+ * @param dev Device descriptor
+ * @return    `ESP_OK` on success
+ */
+esp_err_t scd4x_wake_up(i2c_dev_t *dev);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __SCD4X_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/sgp40/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,23 @@
+---
+components:
+  - name: sgp40
+    description: Driver for SGP40 Indoor Air Quality Sensor for VOC Measurements
+    group: air-quality
+    groups: []
+    code_owners: UncleRus
+    depends:
+      - i2cdev
+      - log
+      - esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2020
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/sgp40/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS sgp40.c sensirion_voc_algorithm.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/sgp40/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,27 @@
+Copyright 2020 Sensirion AG
+Copyright 2020 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/sgp40/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/sgp40/sensirion_voc_algorithm.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,804 @@
+/*
+ * Copyright (c) 2020, Sensirion AG
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Sensirion AG nor the names of its
+ *   contributors may be used to endorse or promote products derived from
+ *   this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "sensirion_voc_algorithm.h"
+
+/* The fixed point arithmetic parts of this code were originally created by
+ * https://github.com/PetteriAimonen/libfixmath
+ */
+
+/*!< the maximum value of fix16_t */
+#define FIX16_MAXIMUM 0x7FFFFFFF
+/*!< the minimum value of fix16_t */
+#define FIX16_MINIMUM 0x80000000
+/*!< the value used to indicate overflows when FIXMATH_NO_OVERFLOW is not
+ * specified */
+#define FIX16_OVERFLOW 0x80000000
+/*!< fix16_t value of 1 */
+#define FIX16_ONE 0x00010000
+
+inline fix16_t fix16_from_int(int32_t a) {
+    return a * FIX16_ONE;
+}
+
+inline int32_t fix16_cast_to_int(fix16_t a) {
+    return (a >> 16);
+}
+
+/*! Multiplies the two given fix16_t's and returns the result. */
+static fix16_t fix16_mul(fix16_t inArg0, fix16_t inArg1);
+
+/*! Divides the first given fix16_t by the second and returns the result. */
+static fix16_t fix16_div(fix16_t inArg0, fix16_t inArg1);
+
+/*! Returns the square root of the given fix16_t. */
+static fix16_t fix16_sqrt(fix16_t inValue);
+
+/*! Returns the exponent (e^) of the given fix16_t. */
+static fix16_t fix16_exp(fix16_t inValue);
+
+static fix16_t fix16_mul(fix16_t inArg0, fix16_t inArg1) {
+    // Each argument is divided to 16-bit parts.
+    //					AB
+    //			*	 CD
+    // -----------
+    //					BD	16 * 16 -> 32 bit products
+    //				 CB
+    //				 AD
+    //				AC
+    //			 |----| 64 bit product
+    int32_t A = (inArg0 >> 16), C = (inArg1 >> 16);
+    uint32_t B = (inArg0 & 0xFFFF), D = (inArg1 & 0xFFFF);
+
+    int32_t AC = A * C;
+    int32_t AD_CB = A * D + C * B;
+    uint32_t BD = B * D;
+
+    int32_t product_hi = AC + (AD_CB >> 16);
+
+    // Handle carry from lower 32 bits to upper part of result.
+    uint32_t ad_cb_temp = AD_CB << 16;
+    uint32_t product_lo = BD + ad_cb_temp;
+    if (product_lo < BD)
+        product_hi++;
+
+#ifndef FIXMATH_NO_OVERFLOW
+    // The upper 17 bits should all be the same (the sign).
+    if (product_hi >> 31 != product_hi >> 15)
+        return FIX16_OVERFLOW;
+#endif
+
+#ifdef FIXMATH_NO_ROUNDING
+    return (product_hi << 16) | (product_lo >> 16);
+#else
+    // Subtracting 0x8000 (= 0.5) and then using signed right shift
+    // achieves proper rounding to result-1, except in the corner
+    // case of negative numbers and lowest word = 0x8000.
+    // To handle that, we also have to subtract 1 for negative numbers.
+    uint32_t product_lo_tmp = product_lo;
+    product_lo -= 0x8000;
+    product_lo -= (uint32_t)product_hi >> 31;
+    if (product_lo > product_lo_tmp)
+        product_hi--;
+
+    // Discard the lowest 16 bits. Note that this is not exactly the same
+    // as dividing by 0x10000. For example if product = -1, result will
+    // also be -1 and not 0. This is compensated by adding +1 to the result
+    // and compensating this in turn in the rounding above.
+    fix16_t result = (product_hi << 16) | (product_lo >> 16);
+    result += 1;
+    return result;
+#endif
+}
+
+static fix16_t fix16_div(fix16_t a, fix16_t b) {
+    // This uses the basic binary restoring division algorithm.
+    // It appears to be faster to do the whole division manually than
+    // trying to compose a 64-bit divide out of 32-bit divisions on
+    // platforms without hardware divide.
+
+    if (b == 0)
+        return FIX16_MINIMUM;
+
+    uint32_t remainder = (a >= 0) ? a : (-a);
+    uint32_t divider = (b >= 0) ? b : (-b);
+
+    uint32_t quotient = 0;
+    uint32_t bit = 0x10000;
+
+    /* The algorithm requires D >= R */
+    while (divider < remainder) {
+        divider <<= 1;
+        bit <<= 1;
+    }
+
+#ifndef FIXMATH_NO_OVERFLOW
+    if (!bit)
+        return FIX16_OVERFLOW;
+#endif
+
+    if (divider & 0x80000000) {
+        // Perform one step manually to avoid overflows later.
+        // We know that divider's bottom bit is 0 here.
+        if (remainder >= divider) {
+            quotient |= bit;
+            remainder -= divider;
+        }
+        divider >>= 1;
+        bit >>= 1;
+    }
+
+    /* Main division loop */
+    while (bit && remainder) {
+        if (remainder >= divider) {
+            quotient |= bit;
+            remainder -= divider;
+        }
+
+        remainder <<= 1;
+        bit >>= 1;
+    }
+
+#ifndef FIXMATH_NO_ROUNDING
+    if (remainder >= divider) {
+        quotient++;
+    }
+#endif
+
+    fix16_t result = quotient;
+
+    /* Figure out the sign of result */
+    if ((a ^ b) & 0x80000000) {
+#ifndef FIXMATH_NO_OVERFLOW
+        if (result == FIX16_MINIMUM)
+            return FIX16_OVERFLOW;
+#endif
+
+        result = -result;
+    }
+
+    return result;
+}
+
+static fix16_t fix16_sqrt(fix16_t x) {
+    // It is assumed that x is not negative
+
+    uint32_t num = x;
+    uint32_t result = 0;
+    uint32_t bit;
+    uint8_t n;
+
+    bit = (uint32_t)1 << 30;
+    while (bit > num)
+        bit >>= 2;
+
+    // The main part is executed twice, in order to avoid
+    // using 64 bit values in computations.
+    for (n = 0; n < 2; n++) {
+        // First we get the top 24 bits of the answer.
+        while (bit) {
+            if (num >= result + bit) {
+                num -= result + bit;
+                result = (result >> 1) + bit;
+            } else {
+                result = (result >> 1);
+            }
+            bit >>= 2;
+        }
+
+        if (n == 0) {
+            // Then process it again to get the lowest 8 bits.
+            if (num > 65535) {
+                // The remainder 'num' is too large to be shifted left
+                // by 16, so we have to add 1 to result manually and
+                // adjust 'num' accordingly.
+                // num = a - (result + 0.5)^2
+                //	 = num + result^2 - (result + 0.5)^2
+                //	 = num - result - 0.5
+                num -= result;
+                num = (num << 16) - 0x8000;
+                result = (result << 16) + 0x8000;
+            } else {
+                num <<= 16;
+                result <<= 16;
+            }
+
+            bit = 1 << 14;
+        }
+    }
+
+#ifndef FIXMATH_NO_ROUNDING
+    // Finally, if next bit would have been 1, round the result upwards.
+    if (num > result) {
+        result++;
+    }
+#endif
+
+    return (fix16_t)result;
+}
+
+static fix16_t fix16_exp(fix16_t x) {
+// Function to approximate exp(); optimized more for code size than speed
+
+// exp(x) for x = +/- {1, 1/8, 1/64, 1/512}
+#define NUM_EXP_VALUES 4
+    static const fix16_t exp_pos_values[NUM_EXP_VALUES] = {
+        F16(2.7182818), F16(1.1331485), F16(1.0157477), F16(1.0019550)};
+    static const fix16_t exp_neg_values[NUM_EXP_VALUES] = {
+        F16(0.3678794), F16(0.8824969), F16(0.9844964), F16(0.9980488)};
+    const fix16_t* exp_values;
+
+    fix16_t res, arg;
+    uint16_t i;
+
+    if (x >= F16(10.3972))
+        return FIX16_MAXIMUM;
+    if (x <= F16(-11.7835))
+        return 0;
+
+    if (x < 0) {
+        x = -x;
+        exp_values = exp_neg_values;
+    } else {
+        exp_values = exp_pos_values;
+    }
+
+    res = FIX16_ONE;
+    arg = FIX16_ONE;
+    for (i = 0; i < NUM_EXP_VALUES; i++) {
+        while (x >= arg) {
+            res = fix16_mul(res, exp_values[i]);
+            x -= arg;
+        }
+        arg >>= 3;
+    }
+    return res;
+}
+
+static void VocAlgorithm__init_instances(VocAlgorithmParams* params);
+static void
+VocAlgorithm__mean_variance_estimator__init(VocAlgorithmParams* params);
+static void VocAlgorithm__mean_variance_estimator___init_instances(
+    VocAlgorithmParams* params);
+static void VocAlgorithm__mean_variance_estimator__set_parameters(
+    VocAlgorithmParams* params, fix16_t std_initial,
+    fix16_t tau_mean_variance_hours, fix16_t gating_max_duration_minutes);
+static void
+VocAlgorithm__mean_variance_estimator__set_states(VocAlgorithmParams* params,
+                                                  fix16_t mean, fix16_t std,
+                                                  fix16_t uptime_gamma);
+static fix16_t
+VocAlgorithm__mean_variance_estimator__get_std(VocAlgorithmParams* params);
+static fix16_t
+VocAlgorithm__mean_variance_estimator__get_mean(VocAlgorithmParams* params);
+static void VocAlgorithm__mean_variance_estimator___calculate_gamma(
+    VocAlgorithmParams* params, fix16_t voc_index_from_prior);
+static void VocAlgorithm__mean_variance_estimator__process(
+    VocAlgorithmParams* params, fix16_t sraw, fix16_t voc_index_from_prior);
+static void VocAlgorithm__mean_variance_estimator___sigmoid__init(
+    VocAlgorithmParams* params);
+static void VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
+    VocAlgorithmParams* params, fix16_t L, fix16_t X0, fix16_t K);
+static fix16_t VocAlgorithm__mean_variance_estimator___sigmoid__process(
+    VocAlgorithmParams* params, fix16_t sample);
+static void VocAlgorithm__mox_model__init(VocAlgorithmParams* params);
+static void VocAlgorithm__mox_model__set_parameters(VocAlgorithmParams* params,
+                                                    fix16_t SRAW_STD,
+                                                    fix16_t SRAW_MEAN);
+static fix16_t VocAlgorithm__mox_model__process(VocAlgorithmParams* params,
+                                                fix16_t sraw);
+static void VocAlgorithm__sigmoid_scaled__init(VocAlgorithmParams* params);
+static void
+VocAlgorithm__sigmoid_scaled__set_parameters(VocAlgorithmParams* params,
+                                             fix16_t offset);
+static fix16_t VocAlgorithm__sigmoid_scaled__process(VocAlgorithmParams* params,
+                                                     fix16_t sample);
+static void VocAlgorithm__adaptive_lowpass__init(VocAlgorithmParams* params);
+static void
+VocAlgorithm__adaptive_lowpass__set_parameters(VocAlgorithmParams* params);
+static fix16_t
+VocAlgorithm__adaptive_lowpass__process(VocAlgorithmParams* params,
+                                        fix16_t sample);
+
+void VocAlgorithm_init(VocAlgorithmParams* params) {
+
+    params->mVoc_Index_Offset = F16(VocAlgorithm_VOC_INDEX_OFFSET_DEFAULT);
+    params->mTau_Mean_Variance_Hours =
+        F16(VocAlgorithm_TAU_MEAN_VARIANCE_HOURS);
+    params->mGating_Max_Duration_Minutes =
+        F16(VocAlgorithm_GATING_MAX_DURATION_MINUTES);
+    params->mSraw_Std_Initial = F16(VocAlgorithm_SRAW_STD_INITIAL);
+    params->mUptime = F16(0.);
+    params->mSraw = F16(0.);
+    params->mVoc_Index = 0;
+    VocAlgorithm__init_instances(params);
+}
+
+static void VocAlgorithm__init_instances(VocAlgorithmParams* params) {
+
+    VocAlgorithm__mean_variance_estimator__init(params);
+    VocAlgorithm__mean_variance_estimator__set_parameters(
+        params, params->mSraw_Std_Initial, params->mTau_Mean_Variance_Hours,
+        params->mGating_Max_Duration_Minutes);
+    VocAlgorithm__mox_model__init(params);
+    VocAlgorithm__mox_model__set_parameters(
+        params, VocAlgorithm__mean_variance_estimator__get_std(params),
+        VocAlgorithm__mean_variance_estimator__get_mean(params));
+    VocAlgorithm__sigmoid_scaled__init(params);
+    VocAlgorithm__sigmoid_scaled__set_parameters(params,
+                                                 params->mVoc_Index_Offset);
+    VocAlgorithm__adaptive_lowpass__init(params);
+    VocAlgorithm__adaptive_lowpass__set_parameters(params);
+}
+
+void VocAlgorithm_get_states(VocAlgorithmParams* params, int32_t* state0,
+                             int32_t* state1) {
+
+    *state0 = VocAlgorithm__mean_variance_estimator__get_mean(params);
+    *state1 = VocAlgorithm__mean_variance_estimator__get_std(params);
+    return;
+}
+
+void VocAlgorithm_set_states(VocAlgorithmParams* params, int32_t state0,
+                             int32_t state1) {
+
+    VocAlgorithm__mean_variance_estimator__set_states(
+        params, state0, state1, F16(VocAlgorithm_PERSISTENCE_UPTIME_GAMMA));
+    params->mSraw = state0;
+}
+
+void VocAlgorithm_set_tuning_parameters(VocAlgorithmParams* params,
+                                        int32_t voc_index_offset,
+                                        int32_t learning_time_hours,
+                                        int32_t gating_max_duration_minutes,
+                                        int32_t std_initial) {
+
+    params->mVoc_Index_Offset = (fix16_from_int(voc_index_offset));
+    params->mTau_Mean_Variance_Hours = (fix16_from_int(learning_time_hours));
+    params->mGating_Max_Duration_Minutes =
+        (fix16_from_int(gating_max_duration_minutes));
+    params->mSraw_Std_Initial = (fix16_from_int(std_initial));
+    VocAlgorithm__init_instances(params);
+}
+
+void VocAlgorithm_process(VocAlgorithmParams* params, int32_t sraw,
+                          int32_t* voc_index) {
+
+    if ((params->mUptime <= F16(VocAlgorithm_INITIAL_BLACKOUT))) {
+        params->mUptime =
+            (params->mUptime + F16(VocAlgorithm_SAMPLING_INTERVAL));
+    } else {
+        if (((sraw > 0) && (sraw < 65000))) {
+            if ((sraw < 20001)) {
+                sraw = 20001;
+            } else if ((sraw > 52767)) {
+                sraw = 52767;
+            }
+            params->mSraw = (fix16_from_int((sraw - 20000)));
+        }
+        params->mVoc_Index =
+            VocAlgorithm__mox_model__process(params, params->mSraw);
+        params->mVoc_Index =
+            VocAlgorithm__sigmoid_scaled__process(params, params->mVoc_Index);
+        params->mVoc_Index =
+            VocAlgorithm__adaptive_lowpass__process(params, params->mVoc_Index);
+        if ((params->mVoc_Index < F16(0.5))) {
+            params->mVoc_Index = F16(0.5);
+        }
+        if ((params->mSraw > F16(0.))) {
+            VocAlgorithm__mean_variance_estimator__process(
+                params, params->mSraw, params->mVoc_Index);
+            VocAlgorithm__mox_model__set_parameters(
+                params, VocAlgorithm__mean_variance_estimator__get_std(params),
+                VocAlgorithm__mean_variance_estimator__get_mean(params));
+        }
+    }
+    *voc_index = (fix16_cast_to_int((params->mVoc_Index + F16(0.5))));
+    return;
+}
+
+static void
+VocAlgorithm__mean_variance_estimator__init(VocAlgorithmParams* params) {
+
+    VocAlgorithm__mean_variance_estimator__set_parameters(params, F16(0.),
+                                                          F16(0.), F16(0.));
+    VocAlgorithm__mean_variance_estimator___init_instances(params);
+}
+
+static void VocAlgorithm__mean_variance_estimator___init_instances(
+    VocAlgorithmParams* params) {
+
+    VocAlgorithm__mean_variance_estimator___sigmoid__init(params);
+}
+
+static void VocAlgorithm__mean_variance_estimator__set_parameters(
+    VocAlgorithmParams* params, fix16_t std_initial,
+    fix16_t tau_mean_variance_hours, fix16_t gating_max_duration_minutes) {
+
+    params->m_Mean_Variance_Estimator__Gating_Max_Duration_Minutes =
+        gating_max_duration_minutes;
+    params->m_Mean_Variance_Estimator___Initialized = false;
+    params->m_Mean_Variance_Estimator___Mean = F16(0.);
+    params->m_Mean_Variance_Estimator___Sraw_Offset = F16(0.);
+    params->m_Mean_Variance_Estimator___Std = std_initial;
+    params->m_Mean_Variance_Estimator___Gamma =
+        (fix16_div(F16((VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING *
+                        (VocAlgorithm_SAMPLING_INTERVAL / 3600.))),
+                   (tau_mean_variance_hours +
+                    F16((VocAlgorithm_SAMPLING_INTERVAL / 3600.)))));
+    params->m_Mean_Variance_Estimator___Gamma_Initial_Mean =
+        F16(((VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING *
+              VocAlgorithm_SAMPLING_INTERVAL) /
+             (VocAlgorithm_TAU_INITIAL_MEAN + VocAlgorithm_SAMPLING_INTERVAL)));
+    params->m_Mean_Variance_Estimator___Gamma_Initial_Variance = F16(
+        ((VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING *
+          VocAlgorithm_SAMPLING_INTERVAL) /
+         (VocAlgorithm_TAU_INITIAL_VARIANCE + VocAlgorithm_SAMPLING_INTERVAL)));
+    params->m_Mean_Variance_Estimator__Gamma_Mean = F16(0.);
+    params->m_Mean_Variance_Estimator__Gamma_Variance = F16(0.);
+    params->m_Mean_Variance_Estimator___Uptime_Gamma = F16(0.);
+    params->m_Mean_Variance_Estimator___Uptime_Gating = F16(0.);
+    params->m_Mean_Variance_Estimator___Gating_Duration_Minutes = F16(0.);
+}
+
+static void
+VocAlgorithm__mean_variance_estimator__set_states(VocAlgorithmParams* params,
+                                                  fix16_t mean, fix16_t std,
+                                                  fix16_t uptime_gamma) {
+
+    params->m_Mean_Variance_Estimator___Mean = mean;
+    params->m_Mean_Variance_Estimator___Std = std;
+    params->m_Mean_Variance_Estimator___Uptime_Gamma = uptime_gamma;
+    params->m_Mean_Variance_Estimator___Initialized = true;
+}
+
+static fix16_t
+VocAlgorithm__mean_variance_estimator__get_std(VocAlgorithmParams* params) {
+
+    return params->m_Mean_Variance_Estimator___Std;
+}
+
+static fix16_t
+VocAlgorithm__mean_variance_estimator__get_mean(VocAlgorithmParams* params) {
+
+    return (params->m_Mean_Variance_Estimator___Mean +
+            params->m_Mean_Variance_Estimator___Sraw_Offset);
+}
+
+static void VocAlgorithm__mean_variance_estimator___calculate_gamma(
+    VocAlgorithmParams* params, fix16_t voc_index_from_prior) {
+
+    fix16_t uptime_limit;
+    fix16_t sigmoid_gamma_mean;
+    fix16_t gamma_mean;
+    fix16_t gating_threshold_mean;
+    fix16_t sigmoid_gating_mean;
+    fix16_t sigmoid_gamma_variance;
+    fix16_t gamma_variance;
+    fix16_t gating_threshold_variance;
+    fix16_t sigmoid_gating_variance;
+
+    uptime_limit = F16((VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__FIX16_MAX -
+                        VocAlgorithm_SAMPLING_INTERVAL));
+    if ((params->m_Mean_Variance_Estimator___Uptime_Gamma < uptime_limit)) {
+        params->m_Mean_Variance_Estimator___Uptime_Gamma =
+            (params->m_Mean_Variance_Estimator___Uptime_Gamma +
+             F16(VocAlgorithm_SAMPLING_INTERVAL));
+    }
+    if ((params->m_Mean_Variance_Estimator___Uptime_Gating < uptime_limit)) {
+        params->m_Mean_Variance_Estimator___Uptime_Gating =
+            (params->m_Mean_Variance_Estimator___Uptime_Gating +
+             F16(VocAlgorithm_SAMPLING_INTERVAL));
+    }
+    VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
+        params, F16(1.), F16(VocAlgorithm_INIT_DURATION_MEAN),
+        F16(VocAlgorithm_INIT_TRANSITION_MEAN));
+    sigmoid_gamma_mean =
+        VocAlgorithm__mean_variance_estimator___sigmoid__process(
+            params, params->m_Mean_Variance_Estimator___Uptime_Gamma);
+    gamma_mean =
+        (params->m_Mean_Variance_Estimator___Gamma +
+         (fix16_mul((params->m_Mean_Variance_Estimator___Gamma_Initial_Mean -
+                     params->m_Mean_Variance_Estimator___Gamma),
+                    sigmoid_gamma_mean)));
+    gating_threshold_mean =
+        (F16(VocAlgorithm_GATING_THRESHOLD) +
+         (fix16_mul(
+             F16((VocAlgorithm_GATING_THRESHOLD_INITIAL -
+                  VocAlgorithm_GATING_THRESHOLD)),
+             VocAlgorithm__mean_variance_estimator___sigmoid__process(
+                 params, params->m_Mean_Variance_Estimator___Uptime_Gating))));
+    VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
+        params, F16(1.), gating_threshold_mean,
+        F16(VocAlgorithm_GATING_THRESHOLD_TRANSITION));
+    sigmoid_gating_mean =
+        VocAlgorithm__mean_variance_estimator___sigmoid__process(
+            params, voc_index_from_prior);
+    params->m_Mean_Variance_Estimator__Gamma_Mean =
+        (fix16_mul(sigmoid_gating_mean, gamma_mean));
+    VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
+        params, F16(1.), F16(VocAlgorithm_INIT_DURATION_VARIANCE),
+        F16(VocAlgorithm_INIT_TRANSITION_VARIANCE));
+    sigmoid_gamma_variance =
+        VocAlgorithm__mean_variance_estimator___sigmoid__process(
+            params, params->m_Mean_Variance_Estimator___Uptime_Gamma);
+    gamma_variance =
+        (params->m_Mean_Variance_Estimator___Gamma +
+         (fix16_mul(
+             (params->m_Mean_Variance_Estimator___Gamma_Initial_Variance -
+              params->m_Mean_Variance_Estimator___Gamma),
+             (sigmoid_gamma_variance - sigmoid_gamma_mean))));
+    gating_threshold_variance =
+        (F16(VocAlgorithm_GATING_THRESHOLD) +
+         (fix16_mul(
+             F16((VocAlgorithm_GATING_THRESHOLD_INITIAL -
+                  VocAlgorithm_GATING_THRESHOLD)),
+             VocAlgorithm__mean_variance_estimator___sigmoid__process(
+                 params, params->m_Mean_Variance_Estimator___Uptime_Gating))));
+    VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
+        params, F16(1.), gating_threshold_variance,
+        F16(VocAlgorithm_GATING_THRESHOLD_TRANSITION));
+    sigmoid_gating_variance =
+        VocAlgorithm__mean_variance_estimator___sigmoid__process(
+            params, voc_index_from_prior);
+    params->m_Mean_Variance_Estimator__Gamma_Variance =
+        (fix16_mul(sigmoid_gating_variance, gamma_variance));
+    params->m_Mean_Variance_Estimator___Gating_Duration_Minutes =
+        (params->m_Mean_Variance_Estimator___Gating_Duration_Minutes +
+         (fix16_mul(F16((VocAlgorithm_SAMPLING_INTERVAL / 60.)),
+                    ((fix16_mul((F16(1.) - sigmoid_gating_mean),
+                                F16((1. + VocAlgorithm_GATING_MAX_RATIO)))) -
+                     F16(VocAlgorithm_GATING_MAX_RATIO)))));
+    if ((params->m_Mean_Variance_Estimator___Gating_Duration_Minutes <
+         F16(0.))) {
+        params->m_Mean_Variance_Estimator___Gating_Duration_Minutes = F16(0.);
+    }
+    if ((params->m_Mean_Variance_Estimator___Gating_Duration_Minutes >
+         params->m_Mean_Variance_Estimator__Gating_Max_Duration_Minutes)) {
+        params->m_Mean_Variance_Estimator___Uptime_Gating = F16(0.);
+    }
+}
+
+static void VocAlgorithm__mean_variance_estimator__process(
+    VocAlgorithmParams* params, fix16_t sraw, fix16_t voc_index_from_prior) {
+
+    fix16_t delta_sgp;
+    fix16_t c;
+    fix16_t additional_scaling;
+
+    if ((params->m_Mean_Variance_Estimator___Initialized == false)) {
+        params->m_Mean_Variance_Estimator___Initialized = true;
+        params->m_Mean_Variance_Estimator___Sraw_Offset = sraw;
+        params->m_Mean_Variance_Estimator___Mean = F16(0.);
+    } else {
+        if (((params->m_Mean_Variance_Estimator___Mean >= F16(100.)) ||
+             (params->m_Mean_Variance_Estimator___Mean <= F16(-100.)))) {
+            params->m_Mean_Variance_Estimator___Sraw_Offset =
+                (params->m_Mean_Variance_Estimator___Sraw_Offset +
+                 params->m_Mean_Variance_Estimator___Mean);
+            params->m_Mean_Variance_Estimator___Mean = F16(0.);
+        }
+        sraw = (sraw - params->m_Mean_Variance_Estimator___Sraw_Offset);
+        VocAlgorithm__mean_variance_estimator___calculate_gamma(
+            params, voc_index_from_prior);
+        delta_sgp = (fix16_div(
+            (sraw - params->m_Mean_Variance_Estimator___Mean),
+            F16(VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING)));
+        if ((delta_sgp < F16(0.))) {
+            c = (params->m_Mean_Variance_Estimator___Std - delta_sgp);
+        } else {
+            c = (params->m_Mean_Variance_Estimator___Std + delta_sgp);
+        }
+        additional_scaling = F16(1.);
+        if ((c > F16(1440.))) {
+            additional_scaling = F16(4.);
+        }
+        params->m_Mean_Variance_Estimator___Std = (fix16_mul(
+            fix16_sqrt((fix16_mul(
+                additional_scaling,
+                (F16(VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING) -
+                 params->m_Mean_Variance_Estimator__Gamma_Variance)))),
+            fix16_sqrt((
+                (fix16_mul(
+                    params->m_Mean_Variance_Estimator___Std,
+                    (fix16_div(
+                        params->m_Mean_Variance_Estimator___Std,
+                        (fix16_mul(
+                            F16(VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING),
+                            additional_scaling)))))) +
+                (fix16_mul(
+                    (fix16_div(
+                        (fix16_mul(
+                            params->m_Mean_Variance_Estimator__Gamma_Variance,
+                            delta_sgp)),
+                        additional_scaling)),
+                    delta_sgp))))));
+        params->m_Mean_Variance_Estimator___Mean =
+            (params->m_Mean_Variance_Estimator___Mean +
+             (fix16_mul(params->m_Mean_Variance_Estimator__Gamma_Mean,
+                        delta_sgp)));
+    }
+}
+
+static void VocAlgorithm__mean_variance_estimator___sigmoid__init(
+    VocAlgorithmParams* params) {
+
+    VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
+        params, F16(0.), F16(0.), F16(0.));
+}
+
+static void VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
+    VocAlgorithmParams* params, fix16_t L, fix16_t X0, fix16_t K) {
+
+    params->m_Mean_Variance_Estimator___Sigmoid__L = L;
+    params->m_Mean_Variance_Estimator___Sigmoid__K = K;
+    params->m_Mean_Variance_Estimator___Sigmoid__X0 = X0;
+}
+
+static fix16_t VocAlgorithm__mean_variance_estimator___sigmoid__process(
+    VocAlgorithmParams* params, fix16_t sample) {
+
+    fix16_t x;
+
+    x = (fix16_mul(params->m_Mean_Variance_Estimator___Sigmoid__K,
+                   (sample - params->m_Mean_Variance_Estimator___Sigmoid__X0)));
+    if ((x < F16(-50.))) {
+        return params->m_Mean_Variance_Estimator___Sigmoid__L;
+    } else if ((x > F16(50.))) {
+        return F16(0.);
+    } else {
+        return (fix16_div(params->m_Mean_Variance_Estimator___Sigmoid__L,
+                          (F16(1.) + fix16_exp(x))));
+    }
+}
+
+static void VocAlgorithm__mox_model__init(VocAlgorithmParams* params) {
+
+    VocAlgorithm__mox_model__set_parameters(params, F16(1.), F16(0.));
+}
+
+static void VocAlgorithm__mox_model__set_parameters(VocAlgorithmParams* params,
+                                                    fix16_t SRAW_STD,
+                                                    fix16_t SRAW_MEAN) {
+
+    params->m_Mox_Model__Sraw_Std = SRAW_STD;
+    params->m_Mox_Model__Sraw_Mean = SRAW_MEAN;
+}
+
+static fix16_t VocAlgorithm__mox_model__process(VocAlgorithmParams* params,
+                                                fix16_t sraw) {
+
+    return (fix16_mul((fix16_div((sraw - params->m_Mox_Model__Sraw_Mean),
+                                 (-(params->m_Mox_Model__Sraw_Std +
+                                    F16(VocAlgorithm_SRAW_STD_BONUS))))),
+                      F16(VocAlgorithm_VOC_INDEX_GAIN)));
+}
+
+static void VocAlgorithm__sigmoid_scaled__init(VocAlgorithmParams* params) {
+
+    VocAlgorithm__sigmoid_scaled__set_parameters(params, F16(0.));
+}
+
+static void
+VocAlgorithm__sigmoid_scaled__set_parameters(VocAlgorithmParams* params,
+                                             fix16_t offset) {
+
+    params->m_Sigmoid_Scaled__Offset = offset;
+}
+
+static fix16_t VocAlgorithm__sigmoid_scaled__process(VocAlgorithmParams* params,
+                                                     fix16_t sample) {
+
+    fix16_t x;
+    fix16_t shift;
+
+    x = (fix16_mul(F16(VocAlgorithm_SIGMOID_K),
+                   (sample - F16(VocAlgorithm_SIGMOID_X0))));
+    if ((x < F16(-50.))) {
+        return F16(VocAlgorithm_SIGMOID_L);
+    } else if ((x > F16(50.))) {
+        return F16(0.);
+    } else {
+        if ((sample >= F16(0.))) {
+            shift = (fix16_div(
+                (F16(VocAlgorithm_SIGMOID_L) -
+                 (fix16_mul(F16(5.), params->m_Sigmoid_Scaled__Offset))),
+                F16(4.)));
+            return ((fix16_div((F16(VocAlgorithm_SIGMOID_L) + shift),
+                               (F16(1.) + fix16_exp(x)))) -
+                    shift);
+        } else {
+            return (fix16_mul(
+                (fix16_div(params->m_Sigmoid_Scaled__Offset,
+                           F16(VocAlgorithm_VOC_INDEX_OFFSET_DEFAULT))),
+                (fix16_div(F16(VocAlgorithm_SIGMOID_L),
+                           (F16(1.) + fix16_exp(x))))));
+        }
+    }
+}
+
+static void VocAlgorithm__adaptive_lowpass__init(VocAlgorithmParams* params) {
+
+    VocAlgorithm__adaptive_lowpass__set_parameters(params);
+}
+
+static void
+VocAlgorithm__adaptive_lowpass__set_parameters(VocAlgorithmParams* params) {
+
+    params->m_Adaptive_Lowpass__A1 =
+        F16((VocAlgorithm_SAMPLING_INTERVAL /
+             (VocAlgorithm_LP_TAU_FAST + VocAlgorithm_SAMPLING_INTERVAL)));
+    params->m_Adaptive_Lowpass__A2 =
+        F16((VocAlgorithm_SAMPLING_INTERVAL /
+             (VocAlgorithm_LP_TAU_SLOW + VocAlgorithm_SAMPLING_INTERVAL)));
+    params->m_Adaptive_Lowpass___Initialized = false;
+}
+
+static fix16_t
+VocAlgorithm__adaptive_lowpass__process(VocAlgorithmParams* params,
+                                        fix16_t sample) {
+
+    fix16_t abs_delta;
+    fix16_t F1;
+    fix16_t tau_a;
+    fix16_t a3;
+
+    if ((params->m_Adaptive_Lowpass___Initialized == false)) {
+        params->m_Adaptive_Lowpass___X1 = sample;
+        params->m_Adaptive_Lowpass___X2 = sample;
+        params->m_Adaptive_Lowpass___X3 = sample;
+        params->m_Adaptive_Lowpass___Initialized = true;
+    }
+    params->m_Adaptive_Lowpass___X1 =
+        ((fix16_mul((F16(1.) - params->m_Adaptive_Lowpass__A1),
+                    params->m_Adaptive_Lowpass___X1)) +
+         (fix16_mul(params->m_Adaptive_Lowpass__A1, sample)));
+    params->m_Adaptive_Lowpass___X2 =
+        ((fix16_mul((F16(1.) - params->m_Adaptive_Lowpass__A2),
+                    params->m_Adaptive_Lowpass___X2)) +
+         (fix16_mul(params->m_Adaptive_Lowpass__A2, sample)));
+    abs_delta =
+        (params->m_Adaptive_Lowpass___X1 - params->m_Adaptive_Lowpass___X2);
+    if ((abs_delta < F16(0.))) {
+        abs_delta = (-abs_delta);
+    }
+    F1 = fix16_exp((fix16_mul(F16(VocAlgorithm_LP_ALPHA), abs_delta)));
+    tau_a =
+        ((fix16_mul(F16((VocAlgorithm_LP_TAU_SLOW - VocAlgorithm_LP_TAU_FAST)),
+                    F1)) +
+         F16(VocAlgorithm_LP_TAU_FAST));
+    a3 = (fix16_div(F16(VocAlgorithm_SAMPLING_INTERVAL),
+                    (F16(VocAlgorithm_SAMPLING_INTERVAL) + tau_a)));
+    params->m_Adaptive_Lowpass___X3 =
+        ((fix16_mul((F16(1.) - a3), params->m_Adaptive_Lowpass___X3)) +
+         (fix16_mul(a3, sample)));
+    return params->m_Adaptive_Lowpass___X3;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/sgp40/sensirion_voc_algorithm.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 2020, Sensirion AG
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Sensirion AG nor the names of its
+ *   contributors may be used to endorse or promote products derived from
+ *   this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file sensirion_voc_algorithm.h
+ * @defgroup sgp40 sgp40
+ * @{
+ *
+ * Library for calculating concentration of volatile organic compounds
+ */
+
+#ifndef VOCALGORITHM_H_
+#define VOCALGORITHM_H_
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* The fixed point arithmetic parts of this code were originally created by
+ * https://github.com/PetteriAimonen/libfixmath
+ */
+
+typedef int32_t fix16_t;
+
+#define F16(x)                                                                 \
+  ((fix16_t)(((x) >= 0) ? ((x)*65536.0 + 0.5) : ((x)*65536.0 - 0.5)))
+
+#define VocAlgorithm_SAMPLING_INTERVAL (1.)
+#define VocAlgorithm_INITIAL_BLACKOUT (45.)
+#define VocAlgorithm_VOC_INDEX_GAIN (230.)
+#define VocAlgorithm_SRAW_STD_INITIAL (50.)
+#define VocAlgorithm_SRAW_STD_BONUS (220.)
+#define VocAlgorithm_TAU_MEAN_VARIANCE_HOURS (12.)
+#define VocAlgorithm_TAU_INITIAL_MEAN (20.)
+#define VocAlgorithm_INIT_DURATION_MEAN ((3600. * 0.75))
+#define VocAlgorithm_INIT_TRANSITION_MEAN (0.01)
+#define VocAlgorithm_TAU_INITIAL_VARIANCE (2500.)
+#define VocAlgorithm_INIT_DURATION_VARIANCE ((3600. * 1.45))
+#define VocAlgorithm_INIT_TRANSITION_VARIANCE (0.01)
+#define VocAlgorithm_GATING_THRESHOLD (340.)
+#define VocAlgorithm_GATING_THRESHOLD_INITIAL (510.)
+#define VocAlgorithm_GATING_THRESHOLD_TRANSITION (0.09)
+#define VocAlgorithm_GATING_MAX_DURATION_MINUTES ((60. * 3.))
+#define VocAlgorithm_GATING_MAX_RATIO (0.3)
+#define VocAlgorithm_SIGMOID_L (500.)
+#define VocAlgorithm_SIGMOID_K (-0.0065)
+#define VocAlgorithm_SIGMOID_X0 (213.)
+#define VocAlgorithm_VOC_INDEX_OFFSET_DEFAULT (100.)
+#define VocAlgorithm_LP_TAU_FAST (20.0)
+#define VocAlgorithm_LP_TAU_SLOW (500.0)
+#define VocAlgorithm_LP_ALPHA (-0.2)
+#define VocAlgorithm_PERSISTENCE_UPTIME_GAMMA ((3. * 3600.))
+#define VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING (64.)
+#define VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__FIX16_MAX (32767.)
+
+/**
+ * Struct to hold all the states of the VOC algorithm.
+ */
+typedef struct {
+  fix16_t mVoc_Index_Offset;
+  fix16_t mTau_Mean_Variance_Hours;
+  fix16_t mGating_Max_Duration_Minutes;
+  fix16_t mSraw_Std_Initial;
+  fix16_t mUptime;
+  fix16_t mSraw;
+  fix16_t mVoc_Index;
+  fix16_t m_Mean_Variance_Estimator__Gating_Max_Duration_Minutes;
+  bool m_Mean_Variance_Estimator___Initialized;
+  fix16_t m_Mean_Variance_Estimator___Mean;
+  fix16_t m_Mean_Variance_Estimator___Sraw_Offset;
+  fix16_t m_Mean_Variance_Estimator___Std;
+  fix16_t m_Mean_Variance_Estimator___Gamma;
+  fix16_t m_Mean_Variance_Estimator___Gamma_Initial_Mean;
+  fix16_t m_Mean_Variance_Estimator___Gamma_Initial_Variance;
+  fix16_t m_Mean_Variance_Estimator__Gamma_Mean;
+  fix16_t m_Mean_Variance_Estimator__Gamma_Variance;
+  fix16_t m_Mean_Variance_Estimator___Uptime_Gamma;
+  fix16_t m_Mean_Variance_Estimator___Uptime_Gating;
+  fix16_t m_Mean_Variance_Estimator___Gating_Duration_Minutes;
+  fix16_t m_Mean_Variance_Estimator___Sigmoid__L;
+  fix16_t m_Mean_Variance_Estimator___Sigmoid__K;
+  fix16_t m_Mean_Variance_Estimator___Sigmoid__X0;
+  fix16_t m_Mox_Model__Sraw_Std;
+  fix16_t m_Mox_Model__Sraw_Mean;
+  fix16_t m_Sigmoid_Scaled__Offset;
+  fix16_t m_Adaptive_Lowpass__A1;
+  fix16_t m_Adaptive_Lowpass__A2;
+  bool m_Adaptive_Lowpass___Initialized;
+  fix16_t m_Adaptive_Lowpass___X1;
+  fix16_t m_Adaptive_Lowpass___X2;
+  fix16_t m_Adaptive_Lowpass___X3;
+} VocAlgorithmParams;
+
+/**
+ * Initialize the VOC algorithm parameters. Call this once at the beginning or
+ * whenever the sensor stopped measurements.
+ * @param params    Pointer to the VocAlgorithmParams struct
+ */
+void VocAlgorithm_init(VocAlgorithmParams *params);
+
+/**
+ * Get current algorithm states. Retrieved values can be used in
+ * VocAlgorithm_set_states() to resume operation after a short interruption,
+ * skipping initial learning phase. This feature can only be used after at least
+ * 3 hours of continuous operation.
+ * @param params    Pointer to the VocAlgorithmParams struct
+ * @param state0    State0 to be stored
+ * @param state1    State1 to be stored
+ */
+void VocAlgorithm_get_states(VocAlgorithmParams *params, int32_t *state0,
+                             int32_t *state1);
+
+/**
+ * Set previously retrieved algorithm states to resume operation after a short
+ * interruption, skipping initial learning phase. This feature should not be
+ * used after inerruptions of more than 10 minutes. Call this once after
+ * VocAlgorithm_init() and the optional VocAlgorithm_set_tuning_parameters(), if
+ * desired. Otherwise, the algorithm will start with initial learning phase.
+ * @param params    Pointer to the VocAlgorithmParams struct
+ * @param state0    State0 to be restored
+ * @param state1    State1 to be restored
+ */
+void VocAlgorithm_set_states(VocAlgorithmParams *params, int32_t state0,
+                             int32_t state1);
+
+/**
+ * Set parameters to customize the VOC algorithm. Call this once after
+ * VocAlgorithm_init(), if desired. Otherwise, the default values will be used.
+ *
+ * @param params                      Pointer to the VocAlgorithmParams struct
+ * @param voc_index_offset            VOC index representing typical (average)
+ *                                    conditions. Range 1..250, default 100
+ * @param learning_time_hours         Time constant of long-term estimator.
+ *                                    Past events will be forgotten after about
+ *                                    twice the learning time.
+ *                                    Range 1..72 [hours], default 12 [hours]
+ * @param gating_max_duration_minutes Maximum duration of gating (freeze of
+ *                                    estimator during high VOC index signal).
+ *                                    0 (no gating) or range 1..720 [minutes],
+ *                                    default 180 [minutes]
+ * @param std_initial                 Initial estimate for standard deviation.
+ *                                    Lower value boosts events during initial
+ *                                    learning period, but may result in larger
+ *                                    device-to-device variations.
+ *                                    Range 10..500, default 50
+ */
+void VocAlgorithm_set_tuning_parameters(VocAlgorithmParams *params,
+                                        int32_t voc_index_offset,
+                                        int32_t learning_time_hours,
+                                        int32_t gating_max_duration_minutes,
+                                        int32_t std_initial);
+
+/**
+ * Calculate the VOC index value from the raw sensor value.
+ *
+ * @param params    Pointer to the VocAlgorithmParams struct
+ * @param sraw      Raw value from the SGP40 sensor
+ * @param voc_index Calculated VOC index value from the raw sensor value. Zero
+ *                  during initial blackout period and 1..500 afterwards
+ */
+void VocAlgorithm_process(VocAlgorithmParams *params, int32_t sraw,
+                          int32_t *voc_index);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* VOCALGORITHM_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/sgp40/sgp40.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,229 @@
+/**
+ * @file sgp40.c
+ *
+ * ESP-IDF driver for SGP40 Indoor Air Quality Sensor for VOC Measurements
+ *
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#include <esp_err.h>
+#include <esp_idf_lib_helpers.h>
+#include <esp_log.h>
+#include "sgp40.h"
+#include <math.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <ets_sys.h>
+
+#define I2C_FREQ_HZ 400000
+
+static const char *TAG = "sgp40";
+
+#define CMD_SOFT_RESET  0x0006
+#define CMD_FEATURESET  0x202f
+#define CMD_MEASURE_RAW 0x260f
+#define CMD_SELF_TEST   0x280e
+#define CMD_SERIAL      0x3682
+#define CMD_HEATER_OFF  0x3615
+
+#define TIME_SOFT_RESET  10
+#define TIME_FEATURESET  10
+#define TIME_MEASURE_RAW 30
+#define TIME_SELF_TEST   250
+#define TIME_HEATER_OFF  10
+#define TIME_SERIAL      10
+
+#define SELF_TEST_OK 0xd400
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(ARG) do { if (!(ARG)) return ESP_ERR_INVALID_ARG; } while (0)
+
+static uint8_t crc8(const uint8_t *data, size_t count)
+{
+    uint8_t res = 0xff;
+
+    for (size_t i = 0; i < count; ++i)
+    {
+        res ^= data[i];
+        for (uint8_t bit = 8; bit > 0; --bit)
+        {
+            if (res & 0x80)
+                res = (res << 1) ^ 0x31;
+            else
+                res = (res << 1);
+        }
+    }
+    return res;
+}
+
+static inline uint16_t swap(uint16_t v)
+{
+    return (v << 8) | (v >> 8);
+}
+
+static esp_err_t send_cmd(i2c_dev_t *dev, uint16_t cmd, uint16_t *data, size_t words)
+{
+    uint8_t buf[2 + words * 3];
+    // add command
+    *(uint16_t *)buf = swap(cmd);
+    if (data && words)
+        // add arguments
+        for (size_t i = 0; i < words; i++)
+        {
+            uint8_t *p = buf + 2 + i * 3;
+            *(uint16_t *)p = swap(data[i]);
+            *(p + 2) = crc8(p, 2);
+        }
+
+    ESP_LOGV(TAG, "Sending buffer:");
+    ESP_LOG_BUFFER_HEX_LEVEL(TAG, buf, sizeof(buf), ESP_LOG_VERBOSE);
+
+    return i2c_dev_write(dev, NULL, 0, buf, sizeof(buf));
+}
+
+static esp_err_t read_resp(i2c_dev_t *dev, uint16_t *data, size_t words)
+{
+    uint8_t buf[words * 3];
+    CHECK(i2c_dev_read(dev, NULL, 0, buf, sizeof(buf)));
+
+    ESP_LOGV(TAG, "Received buffer:");
+    ESP_LOG_BUFFER_HEX_LEVEL(TAG, buf, sizeof(buf), ESP_LOG_VERBOSE);
+
+    for (size_t i = 0; i < words; i++)
+    {
+        uint8_t *p = buf + i * 3;
+        uint8_t crc = crc8(p, 2);
+        if (crc != *(p + 2))
+        {
+            ESP_LOGE(TAG, "Invalid CRC 0x%02x, expected 0x%02x", crc, *(p + 2));
+            return ESP_ERR_INVALID_CRC;
+        }
+        data[i] = swap(*(uint16_t *)p);
+    }
+    return ESP_OK;
+}
+
+static esp_err_t execute_cmd(sgp40_t *dev, uint16_t cmd, uint32_t timeout_ms,
+        uint16_t *out_data, size_t out_words, uint16_t *in_data, size_t in_words)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, send_cmd(&dev->i2c_dev, cmd, out_data, out_words));
+    if (timeout_ms)
+    {
+        if (timeout_ms > 10)
+            vTaskDelay(pdMS_TO_TICKS(timeout_ms));
+        else
+            ets_delay_us(timeout_ms * 1000);
+    }
+    if (in_data && in_words)
+        I2C_DEV_CHECK(&dev->i2c_dev, read_resp(&dev->i2c_dev, in_data, in_words));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+esp_err_t sgp40_init_desc(sgp40_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+
+    dev->i2c_dev.port = port;
+    dev->i2c_dev.addr = SGP40_ADDR;
+    dev->i2c_dev.cfg.sda_io_num = sda_gpio;
+    dev->i2c_dev.cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+    return i2c_dev_create_mutex(&dev->i2c_dev);
+}
+
+esp_err_t sgp40_free_desc(sgp40_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(&dev->i2c_dev);
+}
+
+esp_err_t sgp40_init(sgp40_t *dev)
+{
+    CHECK_ARG(dev);
+
+    CHECK(execute_cmd(dev, CMD_SERIAL, TIME_SERIAL, NULL, 0, dev->serial, 3));
+    CHECK(execute_cmd(dev, CMD_FEATURESET, TIME_FEATURESET, NULL, 0, &dev->featureset, 1));
+
+    ESP_LOGD(TAG, "Device found. S/N: 0x%04x%04x%04x, featureset 0x%04x",
+            dev->serial[0], dev->serial[1], dev->serial[2], dev->featureset);
+
+    VocAlgorithm_init(&dev->voc);
+
+    return ESP_OK;
+}
+
+esp_err_t sgp40_soft_reset(sgp40_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return execute_cmd(dev, CMD_SOFT_RESET, TIME_SOFT_RESET, NULL, 0, NULL, 0);
+}
+
+esp_err_t sgp40_self_test(sgp40_t *dev)
+{
+    CHECK_ARG(dev);
+
+    uint16_t res;
+    CHECK(execute_cmd(dev, CMD_SELF_TEST, TIME_SELF_TEST, NULL, 0, &res, 1));
+
+    return res == SELF_TEST_OK ? ESP_OK : ESP_FAIL;
+}
+
+esp_err_t sgp40_heater_off(sgp40_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return execute_cmd(dev, CMD_HEATER_OFF, TIME_HEATER_OFF, NULL, 0, NULL, 0);
+}
+
+esp_err_t sgp40_measure_raw(sgp40_t *dev, float humidity, float temperature, uint16_t *raw)
+{
+    CHECK_ARG(dev && raw);
+
+    uint16_t params[2];
+    if (isnan(humidity) || isnan(temperature))
+    {
+        params[0] = 0x8000;
+        params[1] = 0x6666;
+        ESP_LOGW(TAG, "Uncompensated measurement");
+    }
+    else
+    {
+        if (humidity < 0)
+            humidity = 0;
+        else if (humidity > 100)
+            humidity = 100;
+
+        if (temperature < -45)
+            temperature = -45;
+        else if (temperature > 129.76)
+            temperature = 129.76;
+
+        params[0] = (uint16_t)(humidity / 100.0 * 65536);
+        params[1] = (uint16_t)((temperature + 45) / 175.0 * 65535);
+    }
+
+    return execute_cmd(dev, CMD_MEASURE_RAW, TIME_MEASURE_RAW, params, 2, raw, 1);
+}
+
+esp_err_t sgp40_measure_voc(sgp40_t *dev, float humidity, float temperature, int32_t *voc_index)
+{
+    CHECK_ARG(dev && voc_index);
+
+    uint16_t raw;
+    CHECK(sgp40_measure_raw(dev, humidity, temperature, &raw));
+    VocAlgorithm_process(&dev->voc, raw, voc_index);
+
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/sgp40/sgp40.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,122 @@
+/**
+ * @file sgp40.h
+ * @defgroup sgp40 sgp40
+ * @{
+ *
+ * ESP-IDF driver for SGP40 Indoor Air Quality Sensor for VOC Measurements
+ *
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __SGP40_H__
+#define __SGP40_H__
+
+#include <stdbool.h>
+#include <time.h>
+#include <i2cdev.h>
+#include <esp_err.h>
+#include "sensirion_voc_algorithm.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define SGP40_ADDR 0x59 //!< I2C address
+
+/**
+ * Device descriptor
+ */
+typedef struct
+{
+    i2c_dev_t i2c_dev;
+    uint16_t serial[3];
+    uint16_t featureset;
+    VocAlgorithmParams voc;
+} sgp40_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev Device descriptor
+ * @param port I2C port
+ * @param sda_gpio SDA GPIO
+ * @param scl_gpio SCL GPIO
+ * @return `ESP_OK` on success
+ */
+esp_err_t sgp40_init_desc(sgp40_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t sgp40_free_desc(sgp40_t *dev);
+
+/**
+ * @brief Read device information, initialize the VOC algorithm
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t sgp40_init(sgp40_t *dev);
+
+/**
+ * @brief Reset device, than put it to idle mode
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t sgp40_soft_reset(sgp40_t *dev);
+
+/**
+ * @brief Perform a self-test
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t sgp40_self_test(sgp40_t *dev);
+
+/**
+ * @brief Turn hotplate off, stop measurement and put device to idle mode
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t sgp40_heater_off(sgp40_t *dev);
+
+/**
+ * @brief Perform a measurement
+ *
+ * @param dev Device descriptor
+ * @param humidity Relative humidity, percents. Use NaN if
+ *                 you want uncompensated measurement
+ * @param temperature Temperature, degrees Celsius. Use NaN if
+ *                    you want uncompensated measurement
+ * @param[out] raw Raw value, proportional to the logarithm
+ *                 of the resistance of the sensing element
+ * @return `ESP_OK` on success
+ */
+esp_err_t sgp40_measure_raw(sgp40_t *dev, float humidity, float temperature, uint16_t *raw);
+
+/**
+ * @brief Perform a measurement and update VOC index
+ *
+ * @param dev Device descriptor
+ * @param humidity Relative humidity, percents. Use NaN if
+ *                 you want uncompensated measurement
+ * @param temperature Temperature, degrees Celsius. Use NaN if
+ *                    you want uncompensated measurement
+ * @param[out] voc_index Calculated VOC index
+ * @return
+ */
+esp_err_t sgp40_measure_voc(sgp40_t *dev, float humidity, float temperature, int32_t *voc_index);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __SGP40_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/sht3x/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,27 @@
+---
+components:
+  - name: sht3x
+    description: Driver for Sensirion SHT30/SHT31/SHT35 digital temperature and humidity sensor
+    group: temperature
+    groups:
+      - name: humidity
+    code_owners: UncleRus
+    depends:
+      - i2cdev
+      - log
+      - esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2016
+      - author:
+          name: gschorcht
+        year: 2017
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/sht3x/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,11 @@
+if(${IDF_VERSION_MAJOR} STREQUAL 4 AND ${IDF_VERSION_MINOR} STREQUAL 1 AND ${IDF_VERSION_PATCH} STREQUAL 3)
+	set(req i2cdev log esp_idf_lib_helpers)
+else()
+	set(req i2cdev log esp_idf_lib_helpers esp_timer)
+endif()
+
+idf_component_register(
+    SRCS sht3x.c
+    INCLUDE_DIRS .
+    REQUIRES ${req} 
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/sht3x/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,27 @@
+Copyright (c) 2017 Gunar Schorcht (https://github.com/gschorcht)
+Copyright (c) 2019 Ruslan V. Uss (https://github.com/UncleRus)
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/sht3x/README.md	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,4 @@
+# Driver for SHT3x digital temperature and humidity sensor
+
+This driver is fork of [original SHT3x driver](https://github.com/gschorcht/sht3x-esp-idf)
+by Gunar Schorcht (@gschorcht), made to be compatible with the i2cdev library.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/sht3x/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/sht3x/sht3x.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,324 @@
+/*
+ * Copyright (c) 2017 Gunar Schorcht <https://github.com/gschorcht>
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file sht3x.c
+ *
+ * ESP-IDF driver for Sensirion SHT3x digital temperature and humidity sensor
+ *
+ * Forked from <https://github.com/gschorcht/sht3x-esp-idf>
+ *
+ * Copyright (c) 2017 Gunar Schorcht <https://github.com/gschorcht>\n
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+
+#include <esp_log.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <esp_timer.h>
+#include <esp_idf_lib_helpers.h>
+#include "sht3x.h"
+
+#define I2C_FREQ_HZ 1000000 // 1MHz
+
+const char *TAG = "sht3x";
+
+#define SHT3X_STATUS_CMD               0xF32D
+#define SHT3X_CLEAR_STATUS_CMD         0x3041
+#define SHT3X_RESET_CMD                0x30A2
+#define SHT3X_FETCH_DATA_CMD           0xE000
+#define SHT3X_STOP_PERIODIC_MEAS_CMD   0x3093
+#define SHT3X_HEATER_ON_CMD            0x306D
+#define SHT3X_HEATER_OFF_CMD           0x3066
+
+static const uint16_t SHT3X_MEASURE_CMD[6][3] = {
+        {0x2400, 0x240b, 0x2416}, // [SINGLE_SHOT][H,M,L] without clock stretching
+        {0x2032, 0x2024, 0x202f}, // [PERIODIC_05][H,M,L]
+        {0x2130, 0x2126, 0x212d}, // [PERIODIC_1 ][H,M,L]
+        {0x2236, 0x2220, 0x222b}, // [PERIODIC_2 ][H,M,L]
+        {0x2334, 0x2322, 0x2329}, // [PERIODIC_4 ][H,M,L]
+        {0x2737, 0x2721, 0x272a}  // [PERIODIC_10][H,M,L]
+};
+
+// due to the fact that ticks can be smaller than portTICK_PERIOD_MS, one and
+// a half tick period added to the duration to be sure that waiting time for
+// the results is long enough
+#define TIME_TO_TICKS(ms) (1 + ((ms) + (portTICK_PERIOD_MS-1) + portTICK_PERIOD_MS/2 ) / portTICK_PERIOD_MS)
+
+#define SHT3X_MEAS_DURATION_REP_HIGH   15
+#define SHT3X_MEAS_DURATION_REP_MEDIUM 6
+#define SHT3X_MEAS_DURATION_REP_LOW    4
+
+// measurement durations in us
+static const uint16_t SHT3X_MEAS_DURATION_US[3] = {
+        SHT3X_MEAS_DURATION_REP_HIGH   * 1000,
+        SHT3X_MEAS_DURATION_REP_MEDIUM * 1000,
+        SHT3X_MEAS_DURATION_REP_LOW    * 1000
+};
+
+// measurement durations in RTOS ticks
+static const uint8_t SHT3X_MEAS_DURATION_TICKS[3] = {
+        TIME_TO_TICKS(SHT3X_MEAS_DURATION_REP_HIGH),
+        TIME_TO_TICKS(SHT3X_MEAS_DURATION_REP_MEDIUM),
+        TIME_TO_TICKS(SHT3X_MEAS_DURATION_REP_LOW)
+};
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+#define G_POLYNOM 0x31
+
+static inline uint16_t shuffle(uint16_t val)
+{
+    return (val >> 8) | (val << 8);
+}
+
+static uint8_t crc8(uint8_t data[], int len)
+{
+    // initialization value
+    uint8_t crc = 0xff;
+
+    // iterate over all bytes
+    for (int i = 0; i < len; i++)
+    {
+        crc ^= data[i];
+        for (int i = 0; i < 8; i++)
+        {
+            bool xor = crc & 0x80;
+            crc = crc << 1;
+            crc = xor ? crc ^ G_POLYNOM : crc;
+        }
+    }
+    return crc;
+}
+
+static esp_err_t send_cmd_nolock(sht3x_t *dev, uint16_t cmd)
+{
+    cmd = shuffle(cmd);
+
+    return i2c_dev_write(&dev->i2c_dev, NULL, 0, &cmd, 2);
+}
+
+static esp_err_t send_cmd(sht3x_t *dev, uint16_t cmd)
+{
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, send_cmd_nolock(dev, cmd));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+static esp_err_t start_nolock(sht3x_t *dev, sht3x_mode_t mode, sht3x_repeat_t repeat)
+{
+    dev->mode = mode;
+    dev->repeatability = repeat;
+    CHECK(send_cmd_nolock(dev, SHT3X_MEASURE_CMD[mode][repeat]));
+    dev->meas_start_time = esp_timer_get_time();
+    dev->meas_started = true;
+    dev->meas_first = true;
+
+    return ESP_OK;
+}
+
+static inline bool is_measuring(sht3x_t *dev)
+{
+    // not running if measurement is not started at all or
+    // it is not the first measurement in periodic mode
+    if (!dev->meas_started || !dev->meas_first)
+      return false;
+
+    // not running if time elapsed is greater than duration
+    uint64_t elapsed = esp_timer_get_time() - dev->meas_start_time;
+
+    return elapsed < SHT3X_MEAS_DURATION_US[dev->repeatability];
+}
+
+static esp_err_t get_raw_data_nolock(sht3x_t *dev, sht3x_raw_data_t raw_data)
+{
+    if (!dev->meas_started)
+    {
+        ESP_LOGE(TAG, "Measurement is not started");
+        return ESP_ERR_INVALID_STATE;
+    }
+    if (is_measuring(dev))
+    {
+        ESP_LOGE(TAG, "Measurement is still running");
+        return ESP_ERR_INVALID_STATE;
+    }
+
+    // read raw data
+    uint16_t cmd = shuffle(SHT3X_FETCH_DATA_CMD);
+    CHECK(i2c_dev_read(&dev->i2c_dev, &cmd, 2, raw_data, sizeof(sht3x_raw_data_t)));
+
+    // reset first measurement flag
+    dev->meas_first = false;
+
+    // reset measurement started flag in single shot mode
+    if (dev->mode == SHT3X_SINGLE_SHOT)
+        dev->meas_started = false;
+
+    // check temperature crc
+    if (crc8(raw_data, 2) != raw_data[2])
+    {
+        ESP_LOGE(TAG, "CRC check for temperature data failed");
+        return ESP_ERR_INVALID_CRC;
+    }
+
+    // check humidity crc
+    if (crc8(raw_data + 3, 2) != raw_data[5])
+    {
+        ESP_LOGE(TAG, "CRC check for humidity data failed");
+        return ESP_ERR_INVALID_CRC;
+    }
+
+    return ESP_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t sht3x_init_desc(sht3x_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+
+    dev->i2c_dev.port = port;
+    dev->i2c_dev.addr = addr;
+    dev->i2c_dev.cfg.sda_io_num = sda_gpio;
+    dev->i2c_dev.cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(&dev->i2c_dev);
+}
+
+esp_err_t sht3x_free_desc(sht3x_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(&dev->i2c_dev);
+}
+
+esp_err_t sht3x_init(sht3x_t *dev)
+{
+    CHECK_ARG(dev);
+
+    dev->mode = SHT3X_SINGLE_SHOT;
+    dev->meas_start_time = 0;
+    dev->meas_started = false;
+    dev->meas_first = false;
+
+    return send_cmd(dev, SHT3X_CLEAR_STATUS_CMD);
+}
+
+esp_err_t sht3x_set_heater(sht3x_t *dev, bool enable)
+{
+    CHECK_ARG(dev);
+
+    return send_cmd(dev, enable ? SHT3X_HEATER_ON_CMD : SHT3X_HEATER_OFF_CMD);
+}
+
+esp_err_t sht3x_compute_values(sht3x_raw_data_t raw_data, float *temperature, float *humidity)
+{
+    CHECK_ARG(raw_data && (temperature || humidity));
+
+    if (temperature)
+        *temperature = ((((raw_data[0] * 256.0) + raw_data[1]) * 175) / 65535.0) - 45;
+
+    if (humidity)
+        *humidity = ((((raw_data[3] * 256.0) + raw_data[4]) * 100) / 65535.0);
+
+    return ESP_OK;
+}
+
+esp_err_t sht3x_measure(sht3x_t *dev, float *temperature, float *humidity)
+{
+    CHECK_ARG(dev && (temperature || humidity));
+
+    sht3x_raw_data_t raw_data;
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, start_nolock(dev, SHT3X_SINGLE_SHOT, SHT3X_HIGH));
+    vTaskDelay(SHT3X_MEAS_DURATION_TICKS[SHT3X_HIGH]);
+    I2C_DEV_CHECK(&dev->i2c_dev, get_raw_data_nolock(dev, raw_data));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return sht3x_compute_values(raw_data, temperature, humidity);
+}
+
+uint8_t sht3x_get_measurement_duration(sht3x_repeat_t repeat)
+{
+    return SHT3X_MEAS_DURATION_TICKS[repeat];  // in RTOS ticks
+}
+
+esp_err_t sht3x_start_measurement(sht3x_t *dev, sht3x_mode_t mode, sht3x_repeat_t repeat)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, start_nolock(dev, mode, repeat));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t sht3x_stop_periodic_measurement(sht3x_t *dev)
+{
+    CHECK_ARG(dev);
+
+    CHECK(send_cmd(dev, SHT3X_STOP_PERIODIC_MEAS_CMD));
+    dev->mode = SHT3X_SINGLE_SHOT;
+    dev->meas_start_time = 0;
+    dev->meas_started = false;
+    dev->meas_first = false;
+
+    return ESP_OK;
+}
+
+esp_err_t sht3x_get_raw_data(sht3x_t *dev, sht3x_raw_data_t raw_data)
+{
+    CHECK_ARG(dev && raw_data);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, get_raw_data_nolock(dev, raw_data));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t sht3x_get_results(sht3x_t *dev, float *temperature, float *humidity)
+{
+    CHECK_ARG(dev && (temperature || humidity));
+
+    sht3x_raw_data_t raw_data;
+
+    CHECK(sht3x_get_raw_data(dev, raw_data));
+
+    return sht3x_compute_values(raw_data, temperature, humidity);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/sht3x/sht3x.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,268 @@
+/*
+ * Copyright (c) 2017 Gunar Schorcht <https://github.com/gschorcht>
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file sht3x.h
+ * @defgroup sht3x sht3x
+ * @{
+ *
+ * ESP-IDF driver for Sensirion SHT3x digital temperature and humidity sensor
+ *
+ * Forked from <https://github.com/gschorcht/sht3x-esp-idf>
+ *
+ * Copyright (c) 2017 Gunar Schorcht <https://github.com/gschorcht>\n
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __SHT3X_H__
+#define __SHT3X_H__
+
+#include <stdbool.h>
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define SHT3X_I2C_ADDR_GND 0x44
+#define SHT3X_I2C_ADDR_VDD 0x45
+
+#define SHT3X_RAW_DATA_SIZE 6
+
+typedef uint8_t sht3x_raw_data_t[SHT3X_RAW_DATA_SIZE];
+
+/**
+ * Possible measurement modes
+ */
+typedef enum
+{
+    SHT3X_SINGLE_SHOT = 0,  //!< one single measurement
+    SHT3X_PERIODIC_05MPS,   //!< periodic with 0.5 measurements per second (mps)
+    SHT3X_PERIODIC_1MPS,    //!< periodic with   1 measurements per second (mps)
+    SHT3X_PERIODIC_2MPS,    //!< periodic with   2 measurements per second (mps)
+    SHT3X_PERIODIC_4MPS,    //!< periodic with   4 measurements per second (mps)
+    SHT3X_PERIODIC_10MPS    //!< periodic with  10 measurements per second (mps)
+} sht3x_mode_t;
+
+/**
+ * Possible repeatability modes
+ */
+typedef enum
+{
+    SHT3X_HIGH = 0,
+    SHT3X_MEDIUM,
+    SHT3X_LOW
+} sht3x_repeat_t;
+
+/**
+ * Device descriptor
+ */
+typedef struct
+{
+    i2c_dev_t i2c_dev;            //!< I2C device descriptor
+
+    sht3x_mode_t mode;            //!< used measurement mode
+    sht3x_repeat_t repeatability; //!< used repeatability
+
+    bool meas_started;            //!< indicates whether measurement started
+    uint64_t meas_start_time;     //!< measurement start time in us
+    bool meas_first;              //!< first measurement in periodic mode
+} sht3x_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev       Device descriptor
+ * @param port      I2C port
+ * @param addr      Device address
+ * @param sda_gpio  SDA GPIO
+ * @param scl_gpio  SCL GPIO
+ * @return          `ESP_OK` on success
+ */
+esp_err_t sht3x_init_desc(sht3x_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev       Device descriptor
+ * @return          `ESP_OK` on success
+ */
+esp_err_t sht3x_free_desc(sht3x_t *dev);
+
+/**
+ * @brief Initialize sensor
+ *
+ * @param dev       Device descriptor
+ * @return          `ESP_OK` on success
+ */
+esp_err_t sht3x_init(sht3x_t *dev);
+
+/**
+ * @brief Enable/disable heater
+ *
+ * @param dev       Device descriptor
+ * @param enable    True to enable, false to disable
+ * @return          `ESP_OK` on success
+ */
+esp_err_t sht3x_set_heater(sht3x_t *dev, bool enable);
+
+/**
+ * @brief High level measurement function
+ *
+ * For convenience this function comprises all three steps to perform
+ * one measurement in only one function:
+ *
+ * 1. Starts a measurement in single shot mode with high reliability
+ * 2. Waits using `vTaskDelay()` until measurement results are available
+ * 3. Returns the results in kind of floating point sensor values
+ *
+ * This function is the easiest way to use the sensor. It is most suitable
+ * for users that don't want to have the control on sensor details.
+ *
+ * @note The function delays the calling task up to 30 ms to wait for
+ *       the measurement results. This might lead to problems when function
+ *       is called from a software timer callback function.
+ *
+ * @param dev         Device descriptor
+ * @param temperature Temperature in degree Celsius
+ * @param humidity    Humidity in percent
+ * @return            `ESP_OK` on success
+ */
+esp_err_t sht3x_measure(sht3x_t *dev, float *temperature, float *humidity);
+
+/**
+ * @brief Get the duration of a measurement in RTOS ticks.
+ *
+ * The function returns the duration in RTOS ticks required by the sensor to
+ * perform a measurement for the given repeatability. Once a measurement is
+ * started with function ::sht3x_start_measurement() the user task can use this
+ * duration in RTOS ticks directly to wait with function `vTaskDelay()` until
+ * the measurement results can be fetched.
+ *
+ * @note The duration only depends on repeatability level. Therefore,
+ *       it can be considered as constant for a repeatability.
+ *
+ * @param repeat    Repeatability, see type ::sht3x_repeat_t
+ * @return          Measurement duration given in RTOS ticks
+ */
+uint8_t sht3x_get_measurement_duration(sht3x_repeat_t repeat);
+
+/**
+ * @brief Start the measurement in single shot or periodic mode
+ *
+ * The function starts the measurement either in *single shot mode*
+ * (exactly one measurement) or *periodic mode* (periodic measurements)
+ * with given repeatability.
+ *
+ * In the *single shot mode*, this function has to be called for each
+ * measurement. The measurement duration has to be waited every time
+ * before the results can be fetched.
+ *
+ * In the *periodic mode*, this function has to be called only once. Also
+ * the measurement duration has to be waited only once until the first
+ * results are available. After this first measurement, the sensor then
+ * automatically performs all subsequent measurements. The rate of periodic
+ * measurements can be 10, 4, 2, 1 or 0.5 measurements per second (mps).
+ *
+ * @note Due to inaccuracies in timing of the sensor, the user task
+ *       should fetch the results at a lower rate. The rate of the periodic
+ *       measurements is defined by the parameter \p mode.
+ *
+ * @param dev       Device descriptor
+ * @param mode      Measurement mode, see type ::sht3x_mode_t
+ * @param repeat    Repeatability, see type ::sht3x_repeat_t
+ * @return          `ESP_OK` on success
+ */
+esp_err_t sht3x_start_measurement(sht3x_t *dev, sht3x_mode_t mode, sht3x_repeat_t repeat);
+
+/**
+ * @brief Stop the periodic mode measurements
+ *
+ * The function stops the measurements  in *periodic mode*
+ * (periodic measurements) and the sensor returns in *single shot mode*
+ *
+ * @param dev       Device descriptor
+ * @return          `ESP_OK` on success
+ */
+esp_err_t sht3x_stop_periodic_measurement(sht3x_t *dev);
+
+/**
+ * @brief Read measurement results from sensor as raw data
+ *
+ * The function read measurement results from the sensor, checks the CRC
+ * checksum and stores them in the byte array as following.
+ *
+ *      data[0] = Temperature MSB
+ *      data[1] = Temperature LSB
+ *      data[2] = Temperature CRC
+ *      data[3] = Humidity MSB
+ *      data[4] = Humidity LSB
+ *      data[2] = Humidity CRC
+ *
+ * In case that there are no new data that can be read, the function fails.
+ *
+ * @param dev       Device descriptor
+ * @param raw_data  Byte array in which raw data are stored
+ * @return          `ESP_OK` on success
+ */
+esp_err_t sht3x_get_raw_data(sht3x_t *dev, sht3x_raw_data_t raw_data);
+
+/**
+ * @brief Computes sensor values from raw data
+ *
+ * @param raw_data    Byte array that contains raw data
+ * @param temperature Temperature in degree Celsius
+ * @param humidity    Humidity in percent
+ * @return            `ESP_OK` on success
+ */
+esp_err_t sht3x_compute_values(sht3x_raw_data_t raw_data, float *temperature, float *humidity);
+
+/**
+ * @brief Get measurement results in form of sensor values
+ *
+ * The function combines function ::sht3x_get_raw_data() and function
+ * ::sht3x_compute_values() to get the measurement results.
+ *
+ * In case that there are no results that can be read, the function fails.
+ *
+ * @param dev         Device descriptor
+ * @param temperature Temperature in degree Celsius
+ * @param humidity    Humidity in percent
+ * @return            `ESP_OK` on success
+ */
+esp_err_t sht3x_get_results(sht3x_t *dev, float *temperature, float *humidity);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __SHT3X_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/sht4x/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,25 @@
+---
+components:
+  - name: sht4x
+    description: |
+      Driver for Sensirion SHT40/SHT41/SHT45 digital temperature and humidity sensor
+    group: temperature
+    groups:
+      - name: humidity
+    code_owners: UncleRus
+    depends:
+      - i2cdev
+      - log
+      - esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2021
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/sht4x/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,11 @@
+if(${IDF_VERSION_MAJOR} STREQUAL 4 AND ${IDF_VERSION_MINOR} STREQUAL 1 AND ${IDF_VERSION_PATCH} STREQUAL 3)
+	set(req i2cdev log esp_idf_lib_helpers)
+else()
+	set(req i2cdev log esp_idf_lib_helpers esp_timer)
+endif()
+
+idf_component_register(
+    SRCS sht4x.c
+    INCLUDE_DIRS .
+    REQUIRES ${req}
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/sht4x/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+Copyright (c) 2021 Ruslan V. Uss (https://github.com/UncleRus)
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/sht4x/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/sht4x/sht4x.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,313 @@
+/*
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file sht4x.c
+ *
+ * ESP-IDF driver for Sensirion SHT40/SHT41 digital temperature and humidity sensor
+ *
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+
+#include <esp_log.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <esp_idf_lib_helpers.h>
+#include <esp_timer.h>
+#include "sht4x.h"
+
+#define I2C_FREQ_HZ 1000000 // 1MHz
+
+static const char *TAG = "sht4x";
+
+#define CMD_RESET             0x94
+#define CMD_SERIAL            0x89
+#define CMD_MEAS_HIGH         0xfd
+#define CMD_MEAS_MED          0xf6
+#define CMD_MEAS_LOW          0xe0
+#define CMD_MEAS_H_HIGH_LONG  0x39
+#define CMD_MEAS_H_HIGH_SHORT 0x32
+#define CMD_MEAS_H_MED_LONG   0x2f
+#define CMD_MEAS_H_MED_SHORT  0x24
+#define CMD_MEAS_H_LOW_LONG   0x1e
+#define CMD_MEAS_H_LOW_SHORT  0x15
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+#define G_POLYNOM 0x31
+
+static uint8_t crc8(uint8_t data[], size_t len)
+{
+    uint8_t crc = 0xff;
+
+    for (size_t i = 0; i < len; i++)
+    {
+        crc ^= data[i];
+        for (size_t i = 0; i < 8; i++)
+            crc = crc & 0x80 ? (crc << 1) ^ G_POLYNOM : crc << 1;
+    }
+    return crc;
+}
+
+static inline size_t get_duration_ms(sht4x_t *dev)
+{
+    switch (dev->heater)
+    {
+        case SHT4X_HEATER_HIGH_LONG:
+        case SHT4X_HEATER_MEDIUM_LONG:
+        case SHT4X_HEATER_LOW_LONG:
+            return 1100;
+        case SHT4X_HEATER_HIGH_SHORT:
+        case SHT4X_HEATER_MEDIUM_SHORT:
+        case SHT4X_HEATER_LOW_SHORT:
+            return 110;
+        default:
+            switch (dev->repeatability)
+            {
+                case SHT4X_HIGH:
+                    return 10;
+                case SHT4X_MEDIUM:
+                    return 5;
+                default:
+                    return 2;
+            }
+    }
+}
+
+static inline uint8_t get_meas_cmd(sht4x_t *dev)
+{
+    switch (dev->heater)
+    {
+        case SHT4X_HEATER_HIGH_LONG:
+            return CMD_MEAS_H_HIGH_LONG;
+        case SHT4X_HEATER_HIGH_SHORT:
+            return CMD_MEAS_H_HIGH_SHORT;
+        case SHT4X_HEATER_MEDIUM_LONG:
+            return CMD_MEAS_H_MED_LONG;
+        case SHT4X_HEATER_MEDIUM_SHORT:
+            return CMD_MEAS_H_MED_SHORT;
+        case SHT4X_HEATER_LOW_LONG:
+            return CMD_MEAS_H_LOW_LONG;
+        case SHT4X_HEATER_LOW_SHORT:
+            return CMD_MEAS_H_LOW_SHORT;
+        default:
+            switch (dev->repeatability)
+            {
+                case SHT4X_HIGH:
+                    return CMD_MEAS_HIGH;
+                case SHT4X_MEDIUM:
+                    return CMD_MEAS_MED;
+                default:
+                    return CMD_MEAS_LOW;
+            }
+    }
+}
+
+static inline esp_err_t send_cmd_nolock(sht4x_t *dev, uint8_t cmd)
+{
+    ESP_LOGD(TAG, "Sending cmd %02x...", cmd);
+    return i2c_dev_write(&dev->i2c_dev, NULL, 0, &cmd, 1);
+}
+
+static inline esp_err_t read_res_nolock(sht4x_t *dev, sht4x_raw_data_t res)
+{
+    CHECK(i2c_dev_read(&dev->i2c_dev, NULL, 0, res, SHT4X_RAW_DATA_SIZE));
+
+    ESP_LOGD(TAG, "Got response %02x %02x %02x %02x %02x %02x",
+            res[0], res[1], res[2], res[3], res[4], res[5]);
+
+    if (res[2] != crc8(res, 2) || res[5] != crc8(res + 3, 2))
+    {
+        ESP_LOGE(TAG, "Invalid CRC");
+        return ESP_ERR_INVALID_CRC;
+    }
+
+    return ESP_OK;
+}
+
+static esp_err_t send_cmd(sht4x_t *dev, uint8_t cmd)
+{
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, send_cmd_nolock(dev, cmd));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+static esp_err_t read_res(sht4x_t *dev, sht4x_raw_data_t res)
+{
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, read_res_nolock(dev, res));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+static esp_err_t exec_cmd(sht4x_t *dev, uint8_t cmd, size_t delay_ticks, sht4x_raw_data_t res)
+{
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, send_cmd_nolock(dev, cmd));
+    if (delay_ticks)
+        vTaskDelay(delay_ticks);
+    I2C_DEV_CHECK(&dev->i2c_dev, read_res_nolock(dev, res));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+static inline bool is_measuring(sht4x_t *dev)
+{
+    // not running if measurement is not started
+    if (!dev->meas_started)
+      return false;
+
+    // not running if time elapsed is greater than duration
+    uint64_t elapsed = esp_timer_get_time() - dev->meas_start_time;
+    return elapsed < get_duration_ms(dev) * 1000;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t sht4x_init_desc(sht4x_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+
+    dev->i2c_dev.port = port;
+    dev->i2c_dev.addr = SHT4X_I2C_ADDRESS;
+    dev->i2c_dev.cfg.sda_io_num = sda_gpio;
+    dev->i2c_dev.cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(&dev->i2c_dev);
+}
+
+esp_err_t sht4x_free_desc(sht4x_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(&dev->i2c_dev);
+}
+
+esp_err_t sht4x_init(sht4x_t *dev)
+{
+    CHECK_ARG(dev);
+
+    dev->repeatability = SHT4X_HIGH;
+    dev->heater = SHT4X_HEATER_OFF;
+
+    sht4x_raw_data_t s;
+    CHECK(exec_cmd(dev, CMD_SERIAL, pdMS_TO_TICKS(10), s));
+    dev->serial = ((uint32_t)s[0] << 24) | ((uint32_t)s[1] << 16) | ((uint32_t)s[3] << 8) | s[4];
+
+    return sht4x_reset(dev);
+}
+
+esp_err_t sht4x_reset(sht4x_t *dev)
+{
+    dev->meas_start_time = 0;
+    dev->meas_started = false;
+
+    CHECK(send_cmd(dev, CMD_RESET));
+    vTaskDelay(1);
+
+    return ESP_OK;
+}
+
+esp_err_t sht4x_measure(sht4x_t *dev, float *temperature, float *humidity)
+{
+    CHECK_ARG(dev && (temperature || humidity));
+
+    sht4x_raw_data_t raw;
+    CHECK(exec_cmd(dev, get_meas_cmd(dev), sht4x_get_measurement_duration(dev), raw));
+
+    return sht4x_compute_values(raw, temperature, humidity);
+}
+
+esp_err_t sht4x_start_measurement(sht4x_t *dev)
+{
+    CHECK_ARG(dev);
+
+    if (is_measuring(dev))
+    {
+        ESP_LOGE(TAG, "Measurement is still running");
+        return ESP_ERR_INVALID_STATE;
+    }
+
+    dev->meas_start_time = esp_timer_get_time();
+    CHECK(send_cmd(dev, get_meas_cmd(dev)));
+    dev->meas_started = true;
+
+    return ESP_OK;
+}
+
+size_t sht4x_get_measurement_duration(sht4x_t *dev)
+{
+    if (!dev) return 0;
+
+    size_t res = pdMS_TO_TICKS(get_duration_ms(dev));
+    return res == 0 ? 1 : res;
+}
+
+esp_err_t sht4x_get_raw_data(sht4x_t *dev, sht4x_raw_data_t raw)
+{
+    CHECK_ARG(dev);
+
+    if (is_measuring(dev))
+    {
+        ESP_LOGE(TAG, "Measurement is still running");
+        return ESP_ERR_INVALID_STATE;
+    }
+
+    dev->meas_started = false;
+    return read_res(dev, raw);
+}
+
+esp_err_t sht4x_compute_values(sht4x_raw_data_t raw_data, float *temperature, float *humidity)
+{
+    CHECK_ARG(raw_data && (temperature || humidity));
+
+    if (temperature)
+        *temperature = ((uint16_t)raw_data[0] << 8 | raw_data[1]) * 175.0 / 65535.0 - 45.0;
+
+    if (humidity)
+        *humidity = ((uint16_t)raw_data[3] << 8 | raw_data[4]) * 125.0 / 65535.0 - 6.0;
+
+    return ESP_OK;
+}
+
+esp_err_t sht4x_get_results(sht4x_t *dev, float *temperature, float *humidity)
+{
+    sht4x_raw_data_t raw;
+    CHECK(sht4x_get_raw_data(dev, raw));
+
+    return sht4x_compute_values(raw, temperature, humidity);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/sht4x/sht4x.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,229 @@
+/*
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file sht4x.h
+ * @defgroup sht4x sht4x
+ * @{
+ *
+ * ESP-IDF driver for Sensirion SHT40/SHT41 digital temperature and humidity sensor
+ *
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __SHT4X_H__
+#define __SHT4X_H__
+
+#include <stdbool.h>
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define SHT4X_I2C_ADDRESS 0x44
+
+#define SHT4X_RAW_DATA_SIZE 6
+
+typedef uint8_t sht4x_raw_data_t[SHT4X_RAW_DATA_SIZE];
+
+/**
+ * Possible heater modes
+ */
+typedef enum
+{
+    SHT4X_HEATER_OFF = 0,      /**< Heater is off, default */
+    SHT4X_HEATER_HIGH_LONG,    /**< High power (~200mW), 1 second pulse */
+    SHT4X_HEATER_HIGH_SHORT,   /**< High power (~200mW), 0.1 second pulse */
+    SHT4X_HEATER_MEDIUM_LONG,  /**< Medium power (~110mW), 1 second pulse */
+    SHT4X_HEATER_MEDIUM_SHORT, /**< Medium power (~110mW), 0.1 second pulse */
+    SHT4X_HEATER_LOW_LONG,     /**< Low power (~20mW), 1 second pulse */
+    SHT4X_HEATER_LOW_SHORT,    /**< Low power (~20mW), 0.1 second pulse */
+} sht4x_heater_t;
+
+/**
+ * Possible repeatability modes
+ */
+typedef enum
+{
+    SHT4X_HIGH = 0,
+    SHT4X_MEDIUM,
+    SHT4X_LOW
+} sht4x_repeat_t;
+
+/**
+ * Device descriptor
+ */
+typedef struct
+{
+    i2c_dev_t i2c_dev;            //!< I2C device descriptor
+
+    uint32_t serial;              //!< device serial number
+
+    sht4x_heater_t heater;        //!< used measurement mode
+    sht4x_repeat_t repeatability; //!< used repeatability
+
+    bool meas_started;            //!< indicates whether measurement started
+    uint64_t meas_start_time;     //!< measurement start time in us
+} sht4x_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev       Device descriptor
+ * @param port      I2C port
+ * @param sda_gpio  SDA GPIO
+ * @param scl_gpio  SCL GPIO
+ * @return          `ESP_OK` on success
+ */
+esp_err_t sht4x_init_desc(sht4x_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev       Device descriptor
+ * @return          `ESP_OK` on success
+ */
+esp_err_t sht4x_free_desc(sht4x_t *dev);
+
+/**
+ * @brief Initialize sensor
+ *
+ * @param dev       Device descriptor
+ * @return          `ESP_OK` on success
+ */
+esp_err_t sht4x_init(sht4x_t *dev);
+
+/**
+ * @brief Soft-reset sensor
+ *
+ * @param dev       Device descriptor
+ * @return          `ESP_OK` on success
+ */
+esp_err_t sht4x_reset(sht4x_t *dev);
+
+/**
+ * @brief High level measurement function
+ *
+ * For convenience this function comprises all three steps to perform
+ * one measurement in only one function:
+ *
+ * 1. Starts a measurement
+ * 2. Waits using `vTaskDelay()` until measurement results are available
+ * 3. Returns the results in kind of floating point sensor values
+ *
+ * This function is the easiest way to use the sensor. It is most suitable
+ * for users that don't want to have the control on sensor details.
+ *
+ * @note The function delays the calling task up to 1.1 s to wait for
+ *       the measurement results. This might lead to problems when the function
+ *       is called from a software timer callback function.
+ *
+ * @param dev         Device descriptor
+ * @param temperature Temperature in degree Celsius
+ * @param humidity    Humidity in percent
+ * @return            `ESP_OK` on success
+ */
+esp_err_t sht4x_measure(sht4x_t *dev, float *temperature, float *humidity);
+
+/**
+ * @brief Start the measurement.
+ *
+ * @param dev       Device descriptor
+ * @return          `ESP_OK` on success
+ */
+esp_err_t sht4x_start_measurement(sht4x_t *dev);
+
+/**
+ * @brief Get the duration of a measurement in RTOS ticks.
+ *
+ * The function returns the duration in RTOS ticks required by the sensor to
+ * perform a measurement for the given repeatability and heater setting.
+ * Once a measurement is started with function ::sht4x_start_measurement()
+ * the user task can use this duration in RTOS ticks directly to wait
+ * with function `vTaskDelay()` until the measurement results can be fetched.
+ *
+ * @param dev       Device descriptor
+ * @return          Measurement duration given in RTOS ticks
+ */
+size_t sht4x_get_measurement_duration(sht4x_t *dev);
+
+/**
+ * @brief Read measurement results from sensor as raw data
+ *
+ * The function read measurement results from the sensor, checks the CRC
+ * checksum and stores them in the byte array as following.
+ *
+ *      data[0] = Temperature MSB
+ *      data[1] = Temperature LSB
+ *      data[2] = Temperature CRC
+ *      data[3] = Humidity MSB
+ *      data[4] = Humidity LSB
+ *      data[5] = Humidity CRC
+ *
+ * In case that there are no new data that can be read, the function fails.
+ *
+ * @param dev      Device descriptor
+ * @param[out] raw Byte array in which raw data are stored
+ * @return         `ESP_OK` on success
+ */
+esp_err_t sht4x_get_raw_data(sht4x_t *dev, sht4x_raw_data_t raw);
+
+/**
+ * @brief Computes sensor values from raw data
+ *
+ * @param raw_data         Byte array that contains raw data
+ * @param[out] temperature Temperature in degree Celsius
+ * @param[out] humidity    Humidity in percent
+ * @return                 `ESP_OK` on success
+ */
+esp_err_t sht4x_compute_values(sht4x_raw_data_t raw_data, float *temperature, float *humidity);
+
+/**
+ * @brief Get measurement results in form of sensor values
+ *
+ * The function combines function ::sht4x_get_raw_data() and function
+ * ::sht4x_compute_values() to get the measurement results.
+ *
+ * In case that there are no results that can be read, the function fails.
+ *
+ * @param dev              Device descriptor
+ * @param[out] temperature Temperature in degree Celsius
+ * @param[out] humidity    Humidity in percent
+ * @return                 `ESP_OK` on success
+ */
+esp_err_t sht4x_get_results(sht4x_t *dev, float *temperature, float *humidity);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __SHT4X_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/si7021/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,27 @@
+---
+components:
+  - name: si7021
+    description: |
+      Driver for Si7013/Si7020/Si7021/HTU2xD/SHT2x and compatible temperature
+      and humidity sensors
+    group: temperature
+    groups:
+      - name: humidity
+    code_owners: UncleRus
+    depends:
+      - i2cdev
+      - log
+      - esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp32c3
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2019
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/si7021/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS si7021.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/si7021/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/si7021/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/si7021/si7021.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,318 @@
+/*
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file si7021.c
+ *
+ * ESP-IDF driver for Si7013/Si7020/Si7021/HTU2xD/SHT2x and
+ * compatible temperature and humidity sensors
+ *
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#include <esp_log.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <esp_idf_lib_helpers.h>
+#include "si7021.h"
+
+#define I2C_FREQ_HZ 400000 // 400kHz
+
+static const char *TAG = "si7021";
+
+#define DELAY_MS 100  // fixed delay (100 ms for SHT20)
+
+#define CMD_MEAS_RH_HOLD     0xe5 // not used, can't stretch clock
+#define CMD_MEAS_RH_NOHOLD   0xf5
+#define CMD_MEAS_T_HOLD      0xe3 // not used, can't stretch clock
+#define CMD_MEAS_T_NOHOLD    0xf3
+#define CMD_READ_T           0xe0 // not used
+#define CMD_RESET            0xfe
+#define CMD_WRITE_USER_REG   0xe6
+#define CMD_READ_USER_REG    0xe7
+#define CMD_WRITE_HEATER_REG 0x51
+#define CMD_READ_HEATER_REG  0x11
+#define CMD_READ_ID_1        0xfa0f
+#define CMD_READ_ID_2        0xfcc9
+#define CMD_READ_FW_REV_1    0x84b8
+
+#define BIT_USER_REG_RES0 0
+#define BIT_USER_REG_HTRE 2
+#define BIT_USER_REG_RES1 7
+
+#define HEATER_MASK 0x0f
+
+#define BV(x) (1 << (x))
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+static bool check_crc(uint16_t value, uint8_t crc)
+{
+    uint32_t row = (uint32_t)value << 8;
+    row |= crc;
+
+    uint32_t divisor = 0x988000;
+
+    for (int i = 0; i < 16; i++)
+    {
+        if (row & (uint32_t)1 << (23 - i))
+            row ^= divisor;
+        divisor >>= 1;
+    }
+
+    return !row;
+}
+
+static esp_err_t measure(i2c_dev_t *dev, uint8_t cmd, uint16_t *raw)
+{
+    I2C_DEV_TAKE_MUTEX(dev);
+    // write command
+    I2C_DEV_CHECK(dev, i2c_dev_write(dev, NULL, 0, &cmd, 1));
+
+    // wait
+    vTaskDelay(DELAY_MS / portTICK_PERIOD_MS);
+
+    // read data
+    uint8_t buf[3];
+    I2C_DEV_CHECK(dev, i2c_dev_read(dev, NULL, 0, buf, 3));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    *raw = ((uint16_t)buf[0] << 8) | buf[1];
+
+    if (!check_crc(*raw, buf[2]))
+    {
+        ESP_LOGE(TAG, "Invalid CRC");
+        return ESP_ERR_INVALID_RESPONSE;
+    }
+
+    return ESP_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t si7021_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+
+    dev->port = port;
+    dev->addr = SI7021_I2C_ADDR;
+    dev->cfg.sda_io_num = sda_gpio;
+    dev->cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(dev);
+}
+
+esp_err_t si7021_free_desc(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(dev);
+}
+
+esp_err_t si7021_reset(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    uint8_t cmd = CMD_RESET;
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_write(dev, NULL, 0, &cmd, 1));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    vTaskDelay(pdMS_TO_TICKS(DELAY_MS));
+
+    return ESP_OK;
+}
+
+esp_err_t si7021_get_heater(i2c_dev_t *dev, bool *on)
+{
+    CHECK_ARG(dev && on);
+
+    uint8_t u;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, CMD_READ_USER_REG, &u, 1));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    *on = u & BV(BIT_USER_REG_HTRE);
+
+    return ESP_OK;
+}
+
+esp_err_t si7021_set_heater(i2c_dev_t *dev, bool on)
+{
+    CHECK_ARG(dev);
+
+    uint8_t u;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, CMD_READ_USER_REG, &u, 1));
+    u = (u & ~BV(BIT_USER_REG_HTRE)) | (on ? 1 : 0);
+    I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, CMD_WRITE_USER_REG, &u, 1));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t si7021_get_heater_current(i2c_dev_t *dev, uint8_t *level)
+{
+    CHECK_ARG(dev && level);
+
+    uint8_t h;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, CMD_READ_HEATER_REG, &h, 1));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    *level = h & HEATER_MASK;
+
+    return ESP_OK;
+}
+
+esp_err_t si7021_set_heater_current(i2c_dev_t *dev, uint8_t level)
+{
+    CHECK_ARG(dev && level <= SI7021_MAX_HEATER_CURRENT);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, CMD_WRITE_HEATER_REG, &level, 1));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t si7021_get_resolution(i2c_dev_t *dev, si7021_resolution_t *r)
+{
+    CHECK_ARG(dev && r);
+
+    uint8_t u;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, CMD_READ_USER_REG, &u, 1));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    *r = ((u >> BIT_USER_REG_RES1) << 1) | (u & BV(BIT_USER_REG_RES0));
+
+    return ESP_OK;
+}
+
+esp_err_t si7021_set_resolution(i2c_dev_t *dev, si7021_resolution_t r)
+{
+    CHECK_ARG(dev && r <= SI7021_RES_RH11_T11);
+
+    uint8_t u;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, CMD_READ_USER_REG, &u, 1));
+    u = (u & ~(BV(BIT_USER_REG_RES0) | BV(BIT_USER_REG_RES1))) | ((r & 2) << (BIT_USER_REG_RES1 - 1)) | (r & 1);
+    I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, CMD_WRITE_USER_REG, &u, 1));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t si7021_measure_temperature(i2c_dev_t *dev, float *t)
+{
+    CHECK_ARG(dev && t);
+
+    uint16_t raw;
+    CHECK(measure(dev, CMD_MEAS_T_NOHOLD, &raw));
+    *t = raw * 175.72 / 65536 - 46.85;
+
+    return ESP_OK;
+}
+
+esp_err_t si7021_measure_humidity(i2c_dev_t *dev, float *rh)
+{
+    CHECK_ARG(dev && rh);
+
+    uint16_t raw;
+    CHECK(measure(dev, CMD_MEAS_RH_NOHOLD, &raw));
+    *rh = raw * 125.0 / 65536 - 6;
+
+    return ESP_OK;
+}
+
+esp_err_t si7021_get_serial(i2c_dev_t *dev, uint64_t *serial, bool sht2x_mode)
+{
+    CHECK_ARG(dev && serial);
+
+    uint8_t buf[8];
+    uint16_t cmd;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+
+    // read SNA, ignore CRC
+    cmd = CMD_READ_ID_1;
+    I2C_DEV_CHECK(dev, i2c_dev_read(dev, &cmd, 2, buf, 8));
+    *serial = sht2x_mode
+        ? ((uint64_t)buf[0] << 40) | ((uint64_t)buf[2] << 32) | ((uint64_t)buf[4] << 24) | ((uint64_t)buf[6] << 16)
+        : ((uint64_t)buf[0] << 56) | ((uint64_t)buf[2] << 48) | ((uint64_t)buf[4] << 40) | ((uint64_t)buf[6] << 32);
+
+    // read SNB, ignore CRC
+    cmd = CMD_READ_ID_2;
+    I2C_DEV_CHECK(dev, i2c_dev_read(dev, &cmd, 2, buf, 6));
+    *serial |= sht2x_mode
+        ? ((uint32_t)buf[0] << 8) | ((uint64_t)buf[3] << 56) | ((uint64_t)buf[4] << 48) | buf[1]
+        : ((uint32_t)buf[0] << 24) | ((uint32_t)buf[1] << 16) | ((uint32_t)buf[3] << 8) | buf[4];
+
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+esp_err_t si7021_get_device_id(i2c_dev_t *dev, si7021_device_id_t *id)
+{
+    CHECK_ARG(id);
+
+    uint64_t serial;
+    CHECK(si7021_get_serial(dev, &serial, false));
+
+    switch ((serial >> 24) & 0xff)
+    {
+        case 0x0d:
+            *id = SI_MODEL_SI7013;
+            break;
+        case 0x14:
+            *id = SI_MODEL_SI7020;
+            break;
+        case 0x15:
+            *id = SI_MODEL_SI7021;
+            break;
+        case 0x00:
+        case 0xff:
+            *id = SI_MODEL_SAMPLE;
+            break;
+        default:
+            *id = SI_MODEL_UNKNOWN;
+    }
+
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/si7021/si7021.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,238 @@
+/*
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file si7021.h
+ * @defgroup si7021 si7021
+ * @{
+ *
+ * ESP-IDF driver for Si7013/Si7020/Si7021/HTU2xD/SHT2x and
+ * compatible temperature and humidity sensors
+ *
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __SI7021_H__
+#define __SI7021_H__
+
+#include <stdbool.h>
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define SI7021_I2C_ADDR 0x40 //!< I2C address
+
+#define SI7021_MAX_HEATER_CURRENT 0x0f //!< Maximum current of the heater for Si70xx
+
+/**
+ * Device model for Si70xx
+ */
+typedef enum {
+    SI_MODEL_SI7013 = 0, //!< Si7013
+    SI_MODEL_SI7020,     //!< Si7020
+    SI_MODEL_SI7021,     //!< Si7021
+    SI_MODEL_SAMPLE,     //!< Engineering sample
+    SI_MODEL_UNKNOWN     //!< Unknown model
+} si7021_device_id_t;
+
+/**
+ * Measurement resolution
+ */
+typedef enum {
+    SI7021_RES_RH12_T14 = 0, //!< Relative humidity: 12 bits, temperature: 14 bits
+    SI7021_RES_RH08_T12,     //!< Relative humidity: 8 bits, temperature: 12 bits
+    SI7021_RES_RH10_T13,     //!< Relative humidity: 10 bits, temperature: 13 bits
+    SI7021_RES_RH11_T11      //!< Relative humidity: 11 bits, temperature: 11 bits
+} si7021_resolution_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev       Device descriptor
+ * @param port      I2C port
+ * @param sda_gpio  SDA GPIO pin
+ * @param scl_gpio  SCL GPIO pin
+ * @return `ESP_OK` on success
+ */
+esp_err_t si7021_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t si7021_free_desc(i2c_dev_t *dev);
+
+/**
+ * @brief Reset device
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t si7021_reset(i2c_dev_t *dev);
+
+/**
+ * @brief Get heater state
+ *
+ * This function is supported by:
+ *
+ *  - SI7013
+ *  - SI7020
+ *  - SI7021
+ *  - SHT2x
+ *
+ * @param dev      Device descriptor
+ * @param[out] on  `true` if heater enabled
+ * @return `ESP_OK` on success
+ */
+esp_err_t si7021_get_heater(i2c_dev_t *dev, bool *on);
+
+/**
+ * @brief Switch heater on/off
+ *
+ * This function is supported by:
+ *
+ *  - SI7013
+ *  - SI7020
+ *  - SI7021
+ *  - SHT2x
+ *
+ * @param dev   Device descriptor
+ * @param on    if `true`, heater will be enabled
+ * @return `ESP_OK` on success
+ */
+esp_err_t si7021_set_heater(i2c_dev_t *dev, bool on);
+
+/**
+ * @brief Get heater current
+ *
+ * This function is supported by:
+ *
+ *  - SI7013
+ *  - SI7020
+ *  - SI7021
+ *
+ * @param dev         Device descriptor
+ * @param[out] level  Heater current, see datasheet
+ * @return `ESP_OK` on success
+ */
+esp_err_t si7021_get_heater_current(i2c_dev_t *dev, uint8_t *level);
+
+/**
+ * @brief Set heater current
+ *
+ * This function is supported by:
+ *
+ *  - SI7013
+ *  - SI7020
+ *  - SI7021
+ *
+ * @param dev       Device descriptor
+ * @param level     Heater current, see datasheet
+ * @return `ESP_OK` on success
+ */
+esp_err_t si7021_set_heater_current(i2c_dev_t *dev, uint8_t level);
+
+/**
+ * @brief Get measurement resolution
+ *
+ * @param dev       Device descriptor
+ * @param[out] r    Resolution
+ * @return `ESP_OK` on success
+ */
+esp_err_t si7021_get_resolution(i2c_dev_t *dev, si7021_resolution_t *r);
+
+/**
+ * @brief Set measurement resolution
+ *
+ * @param dev   Device descriptor
+ * @param r     Resolution
+ * @return `ESP_OK` on success
+ */
+esp_err_t si7021_set_resolution(i2c_dev_t *dev, si7021_resolution_t r);
+
+/**
+ * @brief Measure temperature
+ *
+ * @param dev     Device descriptor
+ * @param[out] t  Temperature, deg. Celsius
+ * @return `ESP_OK` on success
+ */
+esp_err_t si7021_measure_temperature(i2c_dev_t *dev, float *t);
+
+/**
+ * @brief Measure relative humidity
+ *
+ * @param dev       Device descriptor
+ * @param[out] rh   Relative humidity, %
+ * @return `ESP_OK` on success
+ */
+esp_err_t si7021_measure_humidity(i2c_dev_t *dev, float *rh);
+
+/**
+ * @brief Get serial number of device
+ *
+ * This function is supported by:
+ *
+ *  - SI7013
+ *  - SI7020
+ *  - SI7021
+ *  - SHT2x
+ *
+ * @param dev          Device descriptor
+ * @param[out] serial  Serial no.
+ * @param sht2x_mode   `true` for SHT2x devices
+ * @return `ESP_OK` on success
+ */
+esp_err_t si7021_get_serial(i2c_dev_t *dev, uint64_t *serial, bool sht2x_mode);
+
+/**
+ * @brief Get device model
+ *
+ * This function is supported by:
+ *
+ *  - SI7013
+ *  - SI7020
+ *  - SI7021
+ *
+ * @param dev       Device descriptor
+ * @param[out] id   Device model
+ * @return `ESP_OK` on success
+ */
+esp_err_t si7021_get_device_id(i2c_dev_t *dev, si7021_device_id_t *id);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __SI7021_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/sts21/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,24 @@
+---
+components:
+  - name: sts21
+    description: |
+      Driver for STS21 temperature sensor
+    group: temperature
+    groups: []
+    code_owners:
+      - name: UncleRus
+    depends:
+      - name: i2cdev
+      - name: log
+      - name: esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - name: UncleRus
+        year: 2022
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/sts21/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS sts21.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/sts21/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+Copyright 2022 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/sts21/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/sts21/sts21.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,261 @@
+/*
+ * Copyright (c) 2022 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file sts21.c
+ *
+ * ESP-IDF driver for humidty/temperature sensors sts2110/sts2115/sts2120
+ *
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#include "sts21.h"
+#include <ets_sys.h>
+#include <esp_log.h>
+
+static const char *TAG = "sts21";
+
+#define I2C_FREQ_HZ 400000 // 400kHz
+#define I2C_ADDRESS 0x4a
+
+#define CMD_TRIGGER_NO_HOLD 0xf3
+#define CMD_WRITE_USER_REG  0xe6
+#define CMD_READ_USER_REG   0xe7
+#define CMD_SOFT_RESET      0xfe
+
+#define BIT_RES_H        7
+#define BIT_BATTERY_FAIL 6
+#define BIT_HEATER       2
+#define BIT_RES_L        0
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+static const uint32_t measurement_time_us[] = {
+    [STS21_RESOLUTION_14] = 85000,
+    [STS21_RESOLUTION_13] = 43000,
+    [STS21_RESOLUTION_12] = 22000,
+    [STS21_RESOLUTION_11] = 11000,
+};
+
+static bool check_crc(uint16_t value, uint8_t crc)
+{
+    uint32_t row = (uint32_t)value << 8;
+    row |= crc;
+
+    uint32_t divisor = 0x988000;
+
+    for (int i = 0; i < 16; i++)
+    {
+        if (row & (uint32_t)1 << (23 - i))
+            row ^= divisor;
+        divisor >>= 1;
+    }
+
+    return !row;
+}
+
+static esp_err_t update_user_reg(sts21_t *dev, uint8_t value, uint8_t mask)
+{
+    uint8_t r;
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, CMD_READ_USER_REG, &r, 1));
+    r = (r & ~mask) | value;
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_write_reg(&dev->i2c_dev, CMD_WRITE_USER_REG, &r, 1));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+static esp_err_t read_user_reg(sts21_t *dev, uint8_t *value)
+{
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, CMD_READ_USER_REG, value, 1));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+static esp_err_t write_cmd(sts21_t *dev, uint8_t cmd)
+{
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_write(&dev->i2c_dev, NULL, 0, &cmd, 1));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t sts21_init_desc(sts21_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+
+    dev->i2c_dev.port = port;
+    dev->i2c_dev.addr = I2C_ADDRESS;
+    dev->i2c_dev.cfg.sda_io_num = sda_gpio;
+    dev->i2c_dev.cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(&dev->i2c_dev);
+}
+
+esp_err_t sts21_free_desc(sts21_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(&dev->i2c_dev);
+}
+
+esp_err_t sts21_init(sts21_t *dev)
+{
+    CHECK_ARG(dev);
+
+    // soft reset
+    CHECK(write_cmd(dev, CMD_SOFT_RESET));
+    dev->resolution = STS21_RESOLUTION_14;
+
+    return sts21_set_heater_state(dev, false);
+}
+
+esp_err_t sts21_get_resolution(sts21_t *dev, sts21_resolution_t *res)
+{
+    CHECK_ARG(dev && res);
+
+    uint8_t r;
+    CHECK(read_user_reg(dev, &r));
+    dev->resolution = *res = ((r >> BIT_RES_H) << 1) | (r & BIT(BIT_RES_L));
+
+    return ESP_OK;
+}
+
+esp_err_t sts21_set_resolution(sts21_t *dev, sts21_resolution_t res)
+{
+    CHECK_ARG(dev);
+
+    CHECK(update_user_reg(dev, (((res >> 1) & 1) << BIT_RES_H) | (res & 1), BIT(BIT_RES_H) | BIT(BIT_RES_L)));
+    dev->resolution = res;
+
+    return ESP_OK;
+}
+
+esp_err_t sts21_get_heater_state(sts21_t *dev, bool *on)
+{
+    CHECK_ARG(dev && on);
+
+    uint8_t r;
+    CHECK(read_user_reg(dev, &r));
+    *on = r & BIT(BIT_HEATER) ? true : false;
+
+    return ESP_OK;
+}
+
+esp_err_t sts21_set_heater_state(sts21_t *dev, bool on)
+{
+    CHECK_ARG(dev);
+
+    return update_user_reg(dev, on ? BIT(BIT_HEATER) : 0, BIT(BIT_HEATER));
+}
+
+esp_err_t sts21_get_power_alert(sts21_t *dev, bool *alert)
+{
+    CHECK_ARG(dev && alert);
+
+    uint8_t r;
+    CHECK(read_user_reg(dev, &r));
+    *alert = r & BIT(BIT_BATTERY_FAIL) ? true : false;
+
+    return ESP_OK;
+}
+
+esp_err_t sts21_is_busy(sts21_t *dev, bool *busy)
+{
+    CHECK_ARG(dev && busy);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    esp_err_t res = i2c_dev_probe(&dev->i2c_dev, I2C_DEV_READ);
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    if (res == ESP_FAIL)
+    {
+        // sensor NACKs I2C read operation when measurement is in progress
+        *busy = true;
+        return ESP_OK;
+    }
+
+    if (res == ESP_OK)
+    {
+        *busy = false;
+        return ESP_OK;
+    }
+
+    return res;
+}
+
+esp_err_t sts21_trigger_measurement(sts21_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return write_cmd(dev, CMD_TRIGGER_NO_HOLD);
+}
+
+esp_err_t sts21_read_temperature(sts21_t *dev, float *t)
+{
+    CHECK_ARG(dev && t);
+
+    uint8_t buf[3];
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read(&dev->i2c_dev, NULL, 0, buf, sizeof(buf)));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    uint16_t raw = ((uint16_t)buf[0] << 8) | buf[1];
+
+    // calc crc
+    if (!check_crc(raw, buf[2]))
+    {
+        ESP_LOGE(TAG, "Invalid CRC. Raw data: 0x%04x, CRC: 0x%02x", raw, buf[2]);
+        return ESP_ERR_INVALID_CRC;
+    }
+
+    raw &= 0xffff << (dev->resolution + 2);
+    // calc temperature
+    *t = (-46.85f + (175.72f * raw / 65536.0f)) * 1.8f + 32;
+
+    return ESP_OK;
+}
+
+esp_err_t sts21_measure(sts21_t *dev, float *t)
+{
+    CHECK(sts21_trigger_measurement(dev));
+    ets_delay_us(measurement_time_us[dev->resolution]);
+    return sts21_read_temperature(dev, t);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/sts21/sts21.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2022 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file sts21.h
+ * @defgroup sts21 sts21
+ * @{
+ *
+ * ESP-IDF driver for humidty/temperature sensors sts2110/sts2115/sts2120
+ *
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __sts21_H__
+#define __sts21_H__
+
+#include <i2cdev.h>
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Measurement resolutions
+ */
+typedef enum {
+    STS21_RESOLUTION_14 = 0, /**< 14 bits, <= 85 ms, default */
+    STS21_RESOLUTION_13,     /**< 13 bits, <= 43 ms */
+    STS21_RESOLUTION_12,     /**< 12 bits, <= 22 ms */
+    STS21_RESOLUTION_11,     /**< 11 bits, <= 11 ms */
+} sts21_resolution_t;
+
+/**
+ * Device descriptor
+ */
+typedef struct
+{
+    i2c_dev_t i2c_dev;
+    sts21_resolution_t resolution;
+} sts21_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev      Device descriptor
+ * @param port     I2C port
+ * @param sda_gpio SDA GPIO
+ * @param scl_gpio SCL GPIO
+ * @return `ESP_OK` on success
+ */
+esp_err_t sts21_init_desc(sts21_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t sts21_free_desc(sts21_t *dev);
+
+/**
+ * @brief Init device
+ *
+ * Perform soft-reset, set resolution to 14 bits, switch off the heater.
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t sts21_init(sts21_t *dev);
+
+/**
+ * @brief Get measurement resolution
+ *
+ * @param dev      Device descriptor
+ * @param[out] res Measurement resolution
+ * @return `ESP_OK` on success
+ */
+esp_err_t sts21_get_resolution(sts21_t *dev, sts21_resolution_t *res);
+
+/**
+ * @brief Set measurement resolution
+ *
+ * @param dev Device descriptor
+ * @param res Measurement resolution
+ * @return `ESP_OK` on success
+ */
+esp_err_t sts21_set_resolution(sts21_t *dev, sts21_resolution_t res);
+
+/**
+ * @brief Get heater state
+ *
+ * @param dev     Device descriptor
+ * @param[out] on true when heater is on
+ * @return `ESP_OK` on success
+ */
+esp_err_t sts21_get_heater_state(sts21_t *dev, bool *on);
+
+/**
+ * @brief Switch heater on/off
+ *
+ * @param dev Device descriptor
+ * @param on  true to switch heater on
+ * @return `ESP_OK` on success
+ */
+esp_err_t sts21_set_heater_state(sts21_t *dev, bool on);
+
+/**
+ * @brief Get power state
+ *
+ * @param dev        Device descriptor
+ * @param[out] alert true when power voltage < 2.25V
+ * @return `ESP_OK` on success
+ */
+esp_err_t sts21_get_power_alert(sts21_t *dev, bool *alert);
+
+/**
+ * @brief Get measurement status
+ *
+ * @param dev       Device descriptor
+ * @param[out] busy true when device performs measurement
+ * @return `ESP_OK` on success
+ */
+esp_err_t sts21_is_busy(sts21_t *dev, bool *busy);
+
+/**
+ * @brief Trigger measurement
+ *
+ * Before reading the measurement result, you must wait a time depending
+ * on the resolution or use ::sts21_is_busy() function
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t sts21_trigger_measurement(sts21_t *dev);
+
+/**
+ * @brief Read measurement result
+ *
+ * @param dev    Device descriptor
+ * @param[out] t Temperature in degrees Celsius
+ * @return `ESP_OK` on success
+ */
+esp_err_t sts21_read_temperature(sts21_t *dev, float *t);
+
+/**
+ * @brief Measure temperature and read result
+ *
+ * The function combines function ::sts21_trigger_measurement() and function
+ * ::sts21_read_temperature() to get the measurement results.
+ *
+ * @param dev    Device descriptor
+ * @param[out] t Temperature in degrees Celsius
+ * @return `ESP_OK` on success
+ */
+esp_err_t sts21_measure(sts21_t *dev, float *t);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __sts21_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tca9548/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,24 @@
+---
+components:
+  - name: tca9548
+    description: |
+      Driver for TCA9548A/PCA9548A low-voltage 8-channel I2C switch
+    group: misc
+    groups: []
+    code_owners: UncleRus
+    depends:
+      - i2cdev
+      - log
+      - esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2020
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tca9548/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS tca9548.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tca9548/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tca9548/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tca9548/tca9548.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file tca9548.c
+ *
+ * ESP-IDF driver for low-voltage 8-channel I2C switch TCA9548/PCA9548
+ *
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#include <esp_idf_lib_helpers.h>
+#include <esp_log.h>
+#include "tca9548.h"
+
+#define I2C_FREQ_HZ 100000 // 100kHz
+
+static const char *TAG = "tca9548";
+
+#define BYTE_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c"
+#define BYTE_TO_BINARY(byte)  \
+  (byte & BV(7) ? '1' : '0'), \
+  (byte & BV(6) ? '1' : '0'), \
+  (byte & BV(5) ? '1' : '0'), \
+  (byte & BV(4) ? '1' : '0'), \
+  (byte & BV(3) ? '1' : '0'), \
+  (byte & BV(2) ? '1' : '0'), \
+  (byte & BV(1) ? '1' : '0'), \
+  (byte & BV(0) ? '1' : '0')
+
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+esp_err_t tca9548_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev && addr >= TCA9548_ADDR_0 && addr <= TCA9548_ADDR_7);
+
+    dev->port = port;
+    dev->addr = addr;
+    dev->cfg.sda_io_num = sda_gpio;
+    dev->cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(dev);
+}
+
+esp_err_t tca9548_free_desc(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(dev);
+}
+
+esp_err_t tca9548_set_channels(i2c_dev_t *dev, uint8_t channels)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_write(dev, NULL, 0, &channels, 1));
+    I2C_DEV_GIVE_MUTEX(dev);
+    ESP_LOGD(TAG, "[0x%02x at %d] Channels set to 0x%02x (0b" BYTE_TO_BINARY_PATTERN ")",
+            dev->addr, dev->port, channels, BYTE_TO_BINARY(channels));
+
+    return ESP_OK;
+}
+
+esp_err_t tca9548_get_channels(i2c_dev_t *dev, uint8_t *channels)
+{
+    CHECK_ARG(dev && channels);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read(dev, NULL, 0, channels, 1));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tca9548/tca9548.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file tca9548.h
+ * @defgroup tca9548 tca9548
+ * @{
+ *
+ * ESP-IDF driver for low-voltage 8-channel I2C switch TCA9548/PCA9548
+ *
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __TCA9548_H__
+#define __TCA9548_H__
+
+#include <stdbool.h>
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define TCA9548_ADDR_0 0x70
+#define TCA9548_ADDR_1 0x71
+#define TCA9548_ADDR_2 0x72
+#define TCA9548_ADDR_3 0x73
+#define TCA9548_ADDR_4 0x74
+#define TCA9548_ADDR_5 0x75
+#define TCA9548_ADDR_6 0x76
+#define TCA9548_ADDR_7 0x77
+
+#ifndef BV
+#define BV(x) (1 << (x))
+#endif
+
+#define TCA9548_CHANNEL0 BV(0)
+#define TCA9548_CHANNEL1 BV(1)
+#define TCA9548_CHANNEL2 BV(2)
+#define TCA9548_CHANNEL3 BV(3)
+#define TCA9548_CHANNEL4 BV(4)
+#define TCA9548_CHANNEL5 BV(5)
+#define TCA9548_CHANNEL6 BV(6)
+#define TCA9548_CHANNEL7 BV(7)
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev Device descriptor
+ * @param port I2C port
+ * @param addr Device address
+ * @param sda_gpio SDA GPIO pin
+ * @param scl_gpio SCL GPIO pin
+ * @return `ESP_OK` on success
+ */
+esp_err_t tca9548_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t tca9548_free_desc(i2c_dev_t *dev);
+
+/**
+ * @brief Switch channels
+ *
+ * @param dev Device descriptor
+ * @param channels Channel flags, combination of TCA9548_CHANNELn
+ * @return `ESP_OK` on success
+ */
+esp_err_t tca9548_set_channels(i2c_dev_t *dev, uint8_t channels);
+
+/**
+ * @brief Read current channels configuration
+ *
+ * @param dev Device descriptor
+ * @param[out] channels Channel flags, combination of TCA9548_CHANNELn
+ * @return `ESP_OK` on success
+ */
+esp_err_t tca9548_get_channels(i2c_dev_t *dev, uint8_t *channels);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __TCA9548_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tca95x5/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,24 @@
+---
+components:
+  - name: tca95x5
+    description: |
+      Driver for TCA9535/TCA9555 remote 16-bit I/O expanders for I2C-bus
+    group: gpio
+    groups: []
+    code_owners: UncleRus
+    depends:
+      - i2cdev
+      - log
+      - esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2019
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tca95x5/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS tca95x5.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tca95x5/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tca95x5/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tca95x5/tca95x5.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file tca95x5.c
+ *
+ * ESP-IDF driver for TCA9535/TCA9555 remote 16-bit I/O expanders for I2C-bus
+ *
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+
+#include <esp_idf_lib_helpers.h>
+#include "tca95x5.h"
+
+#define I2C_FREQ_HZ 400000
+
+#define REG_IN0   0x00
+#define REG_OUT0  0x02
+#define REG_CONF0 0x06
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+#define BV(x) (1 << (x))
+
+static esp_err_t read_reg_16(i2c_dev_t *dev, uint8_t reg, uint16_t *val)
+{
+    CHECK_ARG(dev && val);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, reg, val, 2));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+static esp_err_t write_reg_16(i2c_dev_t *dev, uint8_t reg, uint16_t val)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, reg, &val, 2));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t tca95x5_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev && (addr & TCA95X5_I2C_ADDR_BASE));
+
+    dev->port = port;
+    dev->addr = addr;
+    dev->cfg.sda_io_num = sda_gpio;
+    dev->cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(dev);
+}
+
+esp_err_t tca95x5_free_desc(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(dev);
+}
+
+esp_err_t tca95x5_port_get_mode(i2c_dev_t *dev, uint16_t *mode)
+{
+    return read_reg_16(dev, REG_CONF0, mode);
+}
+
+esp_err_t tca95x5_port_set_mode(i2c_dev_t *dev, uint16_t mode)
+{
+    return write_reg_16(dev, REG_CONF0, mode);
+}
+
+esp_err_t tca95x5_port_read(i2c_dev_t *dev, uint16_t *val)
+{
+    return read_reg_16(dev, REG_IN0, val);
+}
+
+esp_err_t tca95x5_port_write(i2c_dev_t *dev, uint16_t val)
+{
+    return write_reg_16(dev, REG_OUT0, val);
+}
+
+esp_err_t tca95x5_get_level(i2c_dev_t *dev, uint8_t pin, uint32_t *val)
+{
+    uint16_t v;
+    CHECK(read_reg_16(dev, REG_IN0, &v));
+    *val = v & BV(pin) ? 1 : 0;
+
+    return ESP_OK;
+}
+
+esp_err_t tca95x5_set_level(i2c_dev_t *dev, uint8_t pin, uint32_t val)
+{
+    uint16_t v;
+
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, REG_OUT0, &v, 2));
+    v = (v & ~BV(pin)) | (val ? BV(pin) : 0);
+    I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, REG_OUT0, &v, 2));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    return ESP_OK;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tca95x5/tca95x5.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file tca95x5.h
+ * @defgroup tca95x5 tca95x5
+ * @{
+ *
+ * ESP-IDF driver for TCA9535/TCA9555 remote 16-bit I/O expanders for I2C-bus
+ *
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __TCA95X5_H__
+#define __TCA95X5_H__
+
+#include <stddef.h>
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define TCA95X5_I2C_ADDR_BASE 0x20
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * Default SCL frequency is 400kHz
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @param addr I2C address (`0b0100<A2><A1><A0>`)
+ * @param port I2C port number
+ * @param sda_gpio SDA GPIO
+ * @param scl_gpio SCL GPIO
+ * @return `ESP_OK` on success
+ */
+esp_err_t tca95x5_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ * @param dev Pointer to I2C device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t tca95x5_free_desc(i2c_dev_t *dev);
+
+/**
+ * @brief Get GPIO pins mode
+ *
+ * 0 - output, 1 - input for each bit in `val`
+ *
+ * @param dev Pointer to device descriptor
+ * @param[out] mode Buffer to store mode, 0 bit for P0.0 .. 15 bit for P1.7
+ * @return `ESP_OK` on success
+ */
+esp_err_t tca95x5_port_get_mode(i2c_dev_t *dev, uint16_t *mode);
+
+/**
+ * @brief Set GPIO pins mode
+ *
+ * 0 - output, 1 - input for each bit in `val`
+ *
+ * @param dev Pointer to device descriptor
+ * @param mode Mode, 0 bit for P0.0 .. 15 bit for P1.7
+ * @return `ESP_OK` on success
+ */
+esp_err_t tca95x5_port_set_mode(i2c_dev_t *dev, uint16_t mode);
+
+/**
+ * @brief Read GPIO port value
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @param val 16-bit GPIO port value, 0 bit for P0.0 .. 15 bit for P1.7
+ * @return `ESP_OK` on success
+ */
+esp_err_t tca95x5_port_read(i2c_dev_t *dev, uint16_t *val);
+
+/**
+ * @brief Write value to GPIO port
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @param val GPIO port value, 0 bit for P0.0 .. 15 bit for P1.7
+ * @return ESP_OK on success
+ */
+esp_err_t tca95x5_port_write(i2c_dev_t *dev, uint16_t val);
+
+/**
+ * @brief Read GPIO pin level
+ *
+ * @param dev Pointer to device descriptor
+ * @param pin Pin number, 0 for P0.0 .. 15 for P1.7
+ * @param[out] val `true` if pin currently in high state
+ * @return `ESP_OK` on success
+ */
+esp_err_t tca95x5_get_level(i2c_dev_t *dev, uint8_t pin, uint32_t *val);
+
+/**
+ * @brief Set GPIO pin level
+ *
+ * Pin must be set up as output
+ *
+ * @param dev Pointer to device descriptor
+ * @param pin Pin number, 0 for P0.0 .. 15 for P1.7
+ * @param[out] val `true` if pin currently in high state
+ * @return `ESP_OK` on success
+ */
+esp_err_t tca95x5_set_level(i2c_dev_t *dev, uint8_t pin, uint32_t val);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __TCA95X5_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tda74xx/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,23 @@
+---
+components:
+  - name: tda74xx
+    description: Driver for TDA7439/TDA7439DS/TDA7440D audioprocessors
+    group: misc
+    groups: []
+    code_owners: UncleRus
+    depends:
+      - i2cdev
+      - log
+      - esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: MIT
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2018
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tda74xx/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS tda74xx.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tda74xx/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Ruslan V. Uss
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tda74xx/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tda74xx/tda74xx.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,201 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file tda74xx.c
+ *
+ * ESP-IDF driver for TDA7439/TDA7439DS/TDA7440 audioprocessors
+ *
+ * Copyright (c) 2018, 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+
+#include <esp_log.h>
+#include <esp_idf_lib_helpers.h>
+#include "tda74xx.h"
+
+#define I2C_FREQ_HZ 100000 // 100kHz
+
+static const char *TAG = "tda74xx";
+
+#define REG_INPUT_SELECTOR 0x00
+#define REG_INPUT_GAIN     0x01
+#define REG_VOLUME         0x02
+#define REG_BASS_GAIN      0x03
+#define REG_MID_GAIN       0x04
+#define REG_TREBLE_GAIN    0x05
+#define REG_ATTEN_R        0x06
+#define REG_ATTEN_L        0x07
+
+#define MUTE_VALUE 0x38
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+static esp_err_t write_reg(i2c_dev_t *dev, uint8_t reg, uint8_t val)
+{
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, reg, &val, 1));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    ESP_LOGD(TAG, "%02x -> %02x", val, reg);
+
+    return ESP_OK;
+}
+
+static esp_err_t read_reg(i2c_dev_t *dev, uint8_t reg, uint8_t *val)
+{
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, reg, val, 1));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    ESP_LOGD(TAG, "%02x <- %02x", *val, reg);
+
+    return ESP_OK;
+}
+
+esp_err_t tda74xx_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+
+    dev->port = port;
+    dev->addr = TDA74XX_ADDR;
+    dev->cfg.sda_io_num = sda_gpio;
+    dev->cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(dev);
+}
+
+esp_err_t tda74xx_free_desc(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(dev);
+}
+
+esp_err_t tda74xx_set_input(i2c_dev_t *dev, uint8_t input)
+{
+    CHECK_ARG(dev && input <= TDA74XX_MAX_INPUT);
+
+    return write_reg(dev, REG_INPUT_SELECTOR, input);
+}
+
+esp_err_t tda74xx_get_input(i2c_dev_t *dev, uint8_t *input)
+{
+    CHECK_ARG(dev && input);
+
+    return read_reg(dev, REG_INPUT_SELECTOR, input);
+}
+
+esp_err_t tda74xx_set_input_gain(i2c_dev_t *dev, uint8_t gain_db)
+{
+    CHECK_ARG(dev && gain_db <= TDA74XX_MAX_INPUT_GAIN);
+
+    return write_reg(dev, REG_INPUT_GAIN, gain_db / 2);
+}
+
+esp_err_t tda74xx_get_input_gain(i2c_dev_t *dev, uint8_t *gain_db)
+{
+    CHECK_ARG(dev && gain_db);
+
+    CHECK(read_reg(dev, REG_INPUT_GAIN, gain_db));
+    *gain_db *= 2;
+
+    return ESP_OK;
+}
+
+esp_err_t tda74xx_set_volume(i2c_dev_t *dev, int8_t volume_db)
+{
+    CHECK_ARG(dev && volume_db <= TDA74XX_MAX_VOLUME && volume_db >= TDA74XX_MIN_VOLUME);
+
+    return write_reg(dev, REG_VOLUME, volume_db == TDA74XX_MIN_VOLUME ? MUTE_VALUE : -volume_db);
+}
+
+esp_err_t tda74xx_get_volume(i2c_dev_t *dev, int8_t *volume_db)
+{
+    CHECK_ARG(dev && volume_db);
+
+    CHECK(read_reg(dev, REG_VOLUME, (uint8_t *)volume_db));
+    *volume_db = -*volume_db;
+    return ESP_OK;
+}
+
+esp_err_t tda74xx_set_equalizer_gain(i2c_dev_t *dev, tda74xx_band_t band, int8_t gain_db)
+{
+    CHECK_ARG(dev && gain_db >= TDA74XX_MIN_EQ_GAIN && gain_db <= TDA74XX_MAX_EQ_GAIN);
+
+    uint8_t reg;
+    switch(band)
+    {
+        case TDA74XX_BAND_BASS:
+            reg = REG_BASS_GAIN;
+            break;
+        case TDA74XX_BAND_MIDDLE:
+            reg = REG_MID_GAIN;
+            break;
+        default:
+            reg = REG_TREBLE_GAIN;
+    }
+
+    return write_reg(dev, reg, (gain_db + 14) / 2);
+}
+
+esp_err_t tda74xx_get_equalizer_gain(i2c_dev_t *dev, tda74xx_band_t band, int8_t *gain_db)
+{
+    CHECK_ARG(dev && gain_db);
+
+    uint8_t reg;
+    switch(band)
+    {
+        case TDA74XX_BAND_BASS:
+            reg = REG_BASS_GAIN;
+            break;
+        case TDA74XX_BAND_MIDDLE:
+            reg = REG_MID_GAIN;
+            break;
+        default:
+            reg = REG_TREBLE_GAIN;
+    }
+
+    CHECK(read_reg(dev, reg, (uint8_t *)gain_db));
+    *gain_db = *gain_db * 2 - 14;
+    return ESP_OK;
+}
+
+esp_err_t tda74xx_set_speaker_attenuation(i2c_dev_t *dev, tda74xx_channel_t channel, uint8_t atten_db)
+{
+    CHECK_ARG(dev && atten_db <= TDA74XX_MAX_ATTEN);
+
+    return write_reg(dev, channel == TDA74XX_CHANNEL_LEFT ? REG_ATTEN_L : REG_ATTEN_R, atten_db);
+}
+
+esp_err_t tda74xx_get_speaker_attenuation(i2c_dev_t *dev, tda74xx_channel_t channel, uint8_t *atten_db)
+{
+    CHECK_ARG(dev && atten_db);
+
+    return read_reg(dev, channel == TDA74XX_CHANNEL_LEFT ? REG_ATTEN_L : REG_ATTEN_R, atten_db);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tda74xx/tda74xx.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,196 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file tda74xx.h
+ * @defgroup tda74xx tda74xx
+ * @{
+ *
+ * ESP-IDF driver for TDA7439/TDA7439DS/TDA7440 audioprocessors
+ *
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+#ifndef __TDA74XX_H__
+#define __TDA74XX_H__
+
+#include <stdbool.h>
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define TDA74XX_ADDR 0x44 //!< I2C address
+
+#define TDA74XX_MAX_INPUT      3    //!< Maximum input number
+
+#define TDA74XX_MIN_VOLUME     -48  //!< Mute volume level, dB
+#define TDA74XX_MAX_VOLUME     0    //!< Maximum colume level, dB
+
+#define TDA74XX_MAX_INPUT_GAIN 30   //!< Maximum input gain, dB
+
+#define TDA74XX_MIN_EQ_GAIN    -14  //!< Minimum equalizer gain, dB
+#define TDA74XX_MAX_EQ_GAIN    14   //!< Maximum equalizer gain, dB
+
+#define TDA74XX_MAX_ATTEN      56   //!< Maximum speaker attenuation level, dB
+
+/**
+ * Audio channel
+ */
+typedef enum {
+    TDA74XX_CHANNEL_LEFT = 0,
+    TDA74XX_CHANNEL_RIGHT
+} tda74xx_channel_t;
+
+/**
+ * Equalizer band
+ */
+typedef enum {
+    TDA74XX_BAND_BASS = 0,
+    TDA74XX_BAND_MIDDLE,    //!< Not supported on TDA7440
+    TDA74XX_BAND_TREBLE,
+} tda74xx_band_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev Device descriptor
+ * @param port I2C port number
+ * @param sda_gpio GPIO pin number for SDA
+ * @param scl_gpio GPIO pin number for SCL
+ * @return `ESP_OK` on success
+ */
+esp_err_t tda74xx_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t tda74xx_free_desc(i2c_dev_t *dev);
+
+/**
+ * @brief Switch input
+ *
+ * @param dev Device descriptor
+ * @param input Input #, 0..3
+ * @return `ESP_OK` on success
+ */
+esp_err_t tda74xx_set_input(i2c_dev_t *dev, uint8_t input);
+
+/**
+ * @brief Get current input
+ *
+ * @param dev Device descriptor
+ * @param[out] input Input #, 0..3
+ * @return `ESP_OK` on success
+ */
+esp_err_t tda74xx_get_input(i2c_dev_t *dev, uint8_t *input);
+
+/**
+ * @brief Set input gain, dB
+ *
+ * @param dev Device descriptor
+ * @param gain_db Gain, 0..30 dB
+ * @return `ESP_OK` on success
+ */
+esp_err_t tda74xx_set_input_gain(i2c_dev_t *dev, uint8_t gain_db);
+
+/**
+ * @brief Get input gain
+ *
+ * @param dev Device descriptor
+ * @param[out] gain_db Gain, 0..30 dB
+ * @return `ESP_OK` on success
+ */
+esp_err_t tda74xx_get_input_gain(i2c_dev_t *dev, uint8_t *gain_db);
+
+/**
+ * @brief Set master volume
+ *
+ * @param dev Device descriptor
+ * @param volume_db Volume, -48..0 dB
+ * @return `ESP_OK` on success
+ */
+esp_err_t tda74xx_set_volume(i2c_dev_t *dev, int8_t volume_db);
+
+/**
+ * @brief Get master volume
+ *
+ * @param dev Device descriptor
+ * @param[out] volume_db Volume, -48..0 dB
+ * @return `ESP_OK` on success
+ */
+esp_err_t tda74xx_get_volume(i2c_dev_t *dev, int8_t *volume_db);
+
+/**
+ * @brief Set equalizer gain
+ *
+ * @param dev Device descriptor
+ * @param band Band
+ * @param gain_db Gain, -14..14 dB in 2 dB step
+ * @return `ESP_OK` on success
+ */
+esp_err_t tda74xx_set_equalizer_gain(i2c_dev_t *dev, tda74xx_band_t band, int8_t gain_db);
+
+/**
+ * @brief Get equalizer gain
+ *
+ * @param dev Device descriptor
+ * @param band Band
+ * @param[out] gain_db Gain, -14..14 dB in 2 dB step
+ * @return `ESP_OK` on success
+ */
+esp_err_t tda74xx_get_equalizer_gain(i2c_dev_t *dev, tda74xx_band_t band, int8_t *gain_db);
+
+/**
+ * @brief Attenuate speaker
+ *
+ * @param dev Device descriptor
+ * @param channel Audio channel
+ * @param atten_db Attenuation, 0..56 dB
+ * @return `ESP_OK` on success
+ */
+esp_err_t tda74xx_set_speaker_attenuation(i2c_dev_t *dev, tda74xx_channel_t channel, uint8_t atten_db);
+
+/**
+ * @brief Get speaker attenuation
+ *
+ * @param dev Device descriptor
+ * @param channel Audio channel
+ * @param[out] atten_db Attenuation, 0..56 dB
+ * @return `ESP_OK` on success
+ */
+esp_err_t tda74xx_get_speaker_attenuation(i2c_dev_t *dev, tda74xx_channel_t channel, uint8_t *atten_db);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __TDA74XX_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tsl2561/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+---
+components:
+  - name: tsl2561
+    description: Driver for light-to-digital converter TSL2561
+    group: light
+    groups: []
+    code_owners: UncleRus
+    depends:
+      - i2cdev
+      - log
+      - esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2018
+      - author:
+          name: bschwind
+        year: 2016
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tsl2561/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS tsl2561.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tsl2561/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,27 @@
+Copyright (c) 2016 Brian Schwind (https://github.com/bschwind)
+Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tsl2561/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tsl2561/tsl2561.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,444 @@
+/*
+ * Copyright (c) 2016 Brian Schwind <https://github.com/bschwind>
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file tsl2561.c
+ *
+ * ESP-IDF driver for TSL2561 light-to-digital converter
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2016 Brian Schwind <https://github.com/bschwind>\n
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <esp_log.h>
+#include <esp_idf_lib_helpers.h>
+#include "tsl2561.h"
+
+#define I2C_FREQ_HZ 400000 // 400kHz
+
+static const char *TAG = "tsl2561";
+
+// Registers
+#define TSL2561_REG_COMMAND          0x80
+#define TSL2561_REG_CONTROL          0x00
+#define TSL2561_REG_TIMING           0x01
+#define TSL2561_REG_THRESHOLD_LOW_0  0x02
+#define TSL2561_REG_THRESHOLD_LOW_1  0x03
+#define TSL2561_REG_THRESHOLD_HIGH_0 0x04
+#define TSL2561_REG_THRESHOLD_HIGH_1 0x05
+#define TSL2561_REG_INTERRUPT        0x06
+#define TSL2561_REG_PART_ID          0x0A
+#define TSL2561_REG_CHANNEL_0_LOW    0x0C
+#define TSL2561_REG_CHANNEL_0_HIGH   0x0D
+#define TSL2561_REG_CHANNEL_1_LOW    0x0E
+#define TSL2561_REG_CHANNEL_1_HIGH   0x0F
+
+// TSL2561 Misc Values
+#define TSL2561_ON        0x03
+#define TSL2561_OFF       0x00
+#define TSL2561_READ_WORD 0x20
+
+// Integration times in useconds
+#define TSL2561_INTEGRATION_TIME_13MS  20
+#define TSL2561_INTEGRATION_TIME_101MS 110
+#define TSL2561_INTEGRATION_TIME_402MS 420 // Default
+
+// Calculation constants
+#define LUX_SCALE     14
+#define RATIO_SCALE   9
+#define CH_SCALE      10
+#define CHSCALE_TINT0 0x7517
+#define CHSCALE_TINT1 0x0fe7
+
+// Constants from the TSL2561 data sheet
+#define K1T 0x0040 // 0.125 * 2^RATIO_SCALE
+#define B1T 0x01f2 // 0.0304 * 2^LUX_SCALE
+#define M1T 0x01be // 0.0272 * 2^LUX_SCALE
+#define K2T 0x0080 // 0.250 * 2^RATIO_SCALE
+#define B2T 0x0214 // 0.0325 * 2^LUX_SCALE
+#define M2T 0x02d1 // 0.0440 * 2^LUX_SCALE
+#define K3T 0x00c0 // 0.375 * 2^RATIO_SCALE
+#define B3T 0x023f // 0.0351 * 2^LUX_SCALE
+#define M3T 0x037b // 0.0544 * 2^LUX_SCALE
+#define K4T 0x0100 // 0.50 * 2^RATIO_SCALE
+#define B4T 0x0270 // 0.0381 * 2^LUX_SCALE
+#define M4T 0x03fe // 0.0624 * 2^LUX_SCALE
+#define K5T 0x0138 // 0.61 * 2^RATIO_SCALE
+#define B5T 0x016f // 0.0224 * 2^LUX_SCALE
+#define M5T 0x01fc // 0.0310 * 2^LUX_SCALE
+#define K6T 0x019a // 0.80 * 2^RATIO_SCALE
+#define B6T 0x00d2 // 0.0128 * 2^LUX_SCALE
+#define M6T 0x00fb // 0.0153 * 2^LUX_SCALE
+#define K7T 0x029a // 1.3 * 2^RATIO_SCALE
+#define B7T 0x0018 // 0.00146 * 2^LUX_SCALE
+#define M7T 0x0012 // 0.00112 * 2^LUX_SCALE
+#define K8T 0x029a // 1.3 * 2^RATIO_SCALE
+#define B8T 0x0000 // 0.000 * 2^LUX_SCALE
+#define M8T 0x0000 // 0.000 * 2^LUX_SCALE
+#define K1C 0x0043 // 0.130 * 2^RATIO_SCALE
+#define B1C 0x0204 // 0.0315 * 2^LUX_SCALE
+#define M1C 0x01ad // 0.0262 * 2^LUX_SCALE
+#define K2C 0x0085 // 0.260 * 2^RATIO_SCALE
+#define B2C 0x0228 // 0.0337 * 2^LUX_SCALE
+#define M2C 0x02c1 // 0.0430 * 2^LUX_SCALE
+#define K3C 0x00c8 // 0.390 * 2^RATIO_SCALE
+#define B3C 0x0253 // 0.0363 * 2^LUX_SCALE
+#define M3C 0x0363 // 0.0529 * 2^LUX_SCALE
+#define K4C 0x010a // 0.520 * 2^RATIO_SCALE
+#define B4C 0x0282 // 0.0392 * 2^LUX_SCALE
+#define M4C 0x03df // 0.0605 * 2^LUX_SCALE
+#define K5C 0x014d // 0.65 * 2^RATIO_SCALE
+#define B5C 0x0177 // 0.0229 * 2^LUX_SCALE
+#define M5C 0x01dd // 0.0291 * 2^LUX_SCALE
+#define K6C 0x019a // 0.80 * 2^RATIO_SCALE
+#define B6C 0x0101 // 0.0157 * 2^LUX_SCALE
+#define M6C 0x0127 // 0.0180 * 2^LUX_SCALE
+#define K7C 0x029a // 1.3 * 2^RATIO_SCALE
+#define B7C 0x0037 // 0.00338 * 2^LUX_SCALE
+#define M7C 0x002b // 0.00260 * 2^LUX_SCALE
+#define K8C 0x029a // 1.3 * 2^RATIO_SCALE
+#define B8C 0x0000 // 0.000 * 2^LUX_SCALE
+#define M8C 0x0000 // 0.000 * 2^LUX_SCALE
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+#define SLEEP_MS(x) do { vTaskDelay(pdMS_TO_TICKS(x)); } while (0)
+
+static inline esp_err_t write_register(tsl2561_t *dev, uint8_t reg, uint8_t value)
+{
+    return i2c_dev_write_reg(&dev->i2c_dev, TSL2561_REG_COMMAND | reg, &value, 1);
+}
+
+static inline esp_err_t read_register(tsl2561_t *dev, uint8_t reg, uint8_t *value)
+{
+    return i2c_dev_read_reg(&dev->i2c_dev, TSL2561_REG_COMMAND | reg, value, 1);
+}
+
+static inline esp_err_t read_register_16(tsl2561_t *dev, uint8_t low_register_addr, uint16_t *value)
+{
+    uint8_t buf[2];
+
+    CHECK(i2c_dev_read_reg(&dev->i2c_dev, TSL2561_REG_COMMAND | TSL2561_READ_WORD | low_register_addr, buf, 2));
+    *value = ((uint16_t)buf[1] << 8) | buf[0];
+
+    return ESP_OK;
+}
+
+static inline esp_err_t enable(tsl2561_t *dev)
+{
+    return write_register(dev, TSL2561_REG_CONTROL, TSL2561_ON);
+}
+
+static inline esp_err_t disable(tsl2561_t *dev)
+{
+    return write_register(dev, TSL2561_REG_CONTROL, TSL2561_OFF);
+}
+
+static inline esp_err_t get_channel_data(tsl2561_t *dev, uint16_t *channel0, uint16_t *channel1)
+{
+    int sleep_ms;
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, enable(dev));
+
+    // Since we just enabled the chip, we need to sleep
+    // for the chip's integration time so it can gather a reading
+    switch (dev->integration_time)
+    {
+        case TSL2561_INTEGRATION_13MS:
+            sleep_ms = TSL2561_INTEGRATION_TIME_13MS;
+            break;
+        case TSL2561_INTEGRATION_101MS:
+            sleep_ms = TSL2561_INTEGRATION_TIME_101MS;
+            break;
+        default:
+            sleep_ms = TSL2561_INTEGRATION_TIME_402MS;
+            break;
+    }
+    SLEEP_MS(sleep_ms);
+
+    I2C_DEV_CHECK(&dev->i2c_dev, read_register_16(dev, TSL2561_REG_CHANNEL_0_LOW, channel0));
+    I2C_DEV_CHECK(&dev->i2c_dev, read_register_16(dev, TSL2561_REG_CHANNEL_1_LOW, channel1));
+
+    I2C_DEV_CHECK(&dev->i2c_dev, disable(dev));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+    ESP_LOGD(TAG, "integration time: %d ms channel0: 0x%x channel1: 0x%x", sleep_ms, *channel0, *channel1);
+
+    return ESP_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t tsl2561_init_desc(tsl2561_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+
+    if (addr != TSL2561_I2C_ADDR_GND && addr != TSL2561_I2C_ADDR_FLOAT && addr != TSL2561_I2C_ADDR_VCC)
+    {
+        ESP_LOGE(TAG, "Invalid I2C address `0x%x`: must be one of 0x%x, 0x%x, 0x%x",
+                addr, TSL2561_I2C_ADDR_GND, TSL2561_I2C_ADDR_FLOAT, TSL2561_I2C_ADDR_VCC);
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    dev->i2c_dev.port = port;
+    dev->i2c_dev.addr = addr;
+    dev->i2c_dev.cfg.sda_io_num = sda_gpio;
+    dev->i2c_dev.cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(&dev->i2c_dev);
+}
+
+esp_err_t tsl2561_free_desc(tsl2561_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(&dev->i2c_dev);
+}
+
+esp_err_t tsl2561_init(tsl2561_t *dev)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, enable(dev));
+    uint8_t control_reg;
+    I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, TSL2561_REG_CONTROL, &control_reg));
+    if ((control_reg & TSL2561_ON)!= TSL2561_ON)
+    {
+        ESP_LOGE(TAG, "Error initializing tsl2561, control register wasn't set to ON");
+        I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+        return ESP_ERR_INVALID_RESPONSE;
+    }
+
+    // Fetch the package type
+    uint8_t part_reg;
+    I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, TSL2561_REG_PART_ID, &part_reg));
+    dev->package_type = part_reg >> 6;
+
+    // Fetch the gain and integration time
+    uint8_t timing_reg;
+    I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, TSL2561_REG_TIMING, &timing_reg));
+    dev->gain = timing_reg & 0x10;
+    dev->integration_time = timing_reg & 0x03;
+
+    I2C_DEV_CHECK(&dev->i2c_dev, disable(dev));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t tsl2561_set_integration_time(tsl2561_t *dev, tsl2561_integration_time_t integration_time)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, enable(dev));
+
+    I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, TSL2561_REG_TIMING, integration_time | dev->gain));
+    dev->integration_time = integration_time;
+
+    I2C_DEV_CHECK(&dev->i2c_dev, disable(dev));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t tsl2561_set_gain(tsl2561_t *dev, tsl2561_gain_t gain)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, enable(dev));
+
+    I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, TSL2561_REG_TIMING, gain | dev->integration_time));
+    dev->gain = gain;
+
+    I2C_DEV_CHECK(&dev->i2c_dev, disable(dev));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t tsl2561_read_lux(tsl2561_t *dev, uint32_t *lux)
+{
+    CHECK_ARG(dev && lux);
+
+    uint32_t ch_scale, channel1, channel0;
+
+    switch (dev->integration_time)
+    {
+        case TSL2561_INTEGRATION_13MS:
+            ch_scale = CHSCALE_TINT0;
+            break;
+        case TSL2561_INTEGRATION_101MS:
+            ch_scale = CHSCALE_TINT1;
+            break;
+        default:
+            ch_scale = (1 << CH_SCALE);
+            break;
+    }
+
+    // Scale if gain is 1x
+    if (dev->gain == TSL2561_GAIN_1X)
+        // 16x is nominal, so if the gain is set to 1x then
+        // we need to scale by 16
+        ch_scale = ch_scale << 4;
+
+    uint16_t ch0 = 0;
+    uint16_t ch1 = 0;
+
+    CHECK(get_channel_data(dev, &ch0, &ch1));
+
+    // Scale the channel values
+    channel0 = (ch0 * ch_scale) >> CH_SCALE;
+    channel1 = (ch1 * ch_scale) >> CH_SCALE;
+
+    // Find the ratio of the channel values (channel1 / channel0)
+    // Protect against divide by zero
+    uint32_t ratio1 = 0;
+    if (channel0 != 0)
+        ratio1 = (channel1 << (RATIO_SCALE+1)) / channel0;
+
+    // Round the ratio value
+    uint32_t ratio = (ratio1 + 1) >> 1;
+
+    uint32_t b = 0;
+    uint32_t m = 0;
+    switch (dev->package_type)
+    {
+        case TSL2561_PACKAGE_CS:
+            if (ratio <= K1C)
+            {
+                b = B1C;
+                m = M1C;
+            }
+            else if (ratio <= K2C)
+            {
+                b = B2C;
+                m = M2C;
+            }
+            else if (ratio <= K3C)
+            {
+                b = B3C;
+                m = M3C;
+            }
+            else if (ratio <= K4C)
+            {
+                b = B4C;
+                m = M4C;
+            }
+            else if (ratio <= K5C)
+            {
+                b = B5C;
+                m = M5C;
+            }
+            else if (ratio <= K6C)
+            {
+                b = B6C;
+                m = M6C;
+            }
+            else if (ratio <= K7C)
+            {
+                b = B7C;
+                m = M7C;
+            }
+            else if (ratio > K8C)
+            {
+                b = B8C;
+                m = M8C;
+            }
+
+            break;
+        case TSL2561_PACKAGE_T_FN_CL:
+            if (ratio <= K1T)
+            {
+                b = B1T;
+                m = M1T;
+            }
+            else if (ratio <= K2T)
+            {
+                b = B2T;
+                m = M2T;
+            }
+            else if (ratio <= K3T)
+            {
+                b = B3T;
+                m = M3T;
+            }
+            else if (ratio <= K4T)
+            {
+                b = B4T;
+                m = M4T;
+            }
+            else if (ratio <= K5T)
+            {
+                b = B5T;
+                m = M5T;
+            }
+            else if (ratio <= K6T)
+            {
+                b = B6T;
+                m = M6T;
+            }
+            else if (ratio <= K7T)
+            {
+                b = B7T;
+                m = M7T;
+            }
+            else if (ratio > K8T)
+            {
+                b = B8T;
+                m = M8T;
+            }
+
+            break;
+        default:
+            ESP_LOGE(TAG, "Invalid package type in CalculateLux");
+            return ESP_ERR_NOT_SUPPORTED;
+    }
+
+    int32_t temp;
+    temp = ((channel0 * b) - (channel1 * m));
+
+    // Round lsb (2^(LUX_SCALE−1))
+    temp += (1 << (LUX_SCALE - 1));
+
+    // Strip off fractional portion
+    *lux = temp >> LUX_SCALE;
+
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tsl2561/tsl2561.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2016 Brian Schwind <https://github.com/bschwind>
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file tsl2561.h
+ * @defgroup tsl2561 tsl2561
+ * @{
+ *
+ * ESP-IDF driver for TSL2561 light-to-digital converter
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2016 Brian Schwind <https://github.com/bschwind>\n
+ * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+
+#ifndef __TSL2561_H__
+#define __TSL2561_H__
+
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define TSL2561_I2C_ADDR_GND   0x29
+#define TSL2561_I2C_ADDR_FLOAT 0x39 //!< Default I2C address
+#define TSL2561_I2C_ADDR_VCC   0x49
+
+/**
+ * Integration time
+ */
+typedef enum
+{
+    TSL2561_INTEGRATION_13MS = 0, //!< 13ms
+    TSL2561_INTEGRATION_101MS,    //!< 101ms
+    TSL2561_INTEGRATION_402MS     //!< 402ms, default
+} tsl2561_integration_time_t;
+
+/**
+ * Gain
+ */
+typedef enum
+{
+    TSL2561_GAIN_1X = 0x00, //!< Default
+    TSL2561_GAIN_16X = 0x10
+} tsl2561_gain_t;
+
+typedef enum
+{
+    TSL2561_PACKAGE_CS = 0,
+    TSL2561_PACKAGE_T_FN_CL
+} tsl2561_package_t;
+
+/**
+ * Device descriptor
+ */
+typedef struct
+{
+    i2c_dev_t i2c_dev;
+    tsl2561_integration_time_t integration_time;
+    tsl2561_gain_t gain;
+    tsl2561_package_t package_type;
+} tsl2561_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev Device descriptor
+ * @param addr I2C device address, `TSL2561_I2C_ADDR_...` const
+ * @param port I2C port
+ * @param sda_gpio SDA GPIO pin
+ * @param scl_gpio SCL GPIO pin
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl2561_init_desc(tsl2561_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl2561_free_desc(tsl2561_t *dev);
+
+/**
+ * @brief Initialize device
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl2561_init(tsl2561_t *dev);
+
+/**
+ * @brief Set device integration time
+ *
+ * @param dev Device descriptor
+ * @param integration_time Integration time
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl2561_set_integration_time(tsl2561_t *dev, tsl2561_integration_time_t integration_time);
+
+/**
+ * @brief Set device gain
+ *
+ * @param dev Device descriptor
+ * @param gain Gain
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl2561_set_gain(tsl2561_t *dev, tsl2561_gain_t gain);
+
+/**
+ * @brief Read light intensity from device
+ *
+ * @param dev Device descriptor
+ * @param[out] lux Light intensity, lux
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl2561_read_lux(tsl2561_t *dev, uint32_t *lux);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif  // __TSL2561_H__
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tsl2591/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,23 @@
+---
+components:
+  - name: tsl2591
+    description: Driver for light-to-digital converter TSL2591
+    group: light
+    groups: []
+    code_owners: UncleRus
+    depends:
+      - i2cdev
+      - log
+      - esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: MIT
+    copyrights:
+      - author:
+          name: juliandoerner
+        year: 2020
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tsl2591/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS tsl2591.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tsl2591/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 Julian Doerner (https://github.com/juliandoerner)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tsl2591/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tsl2591/tsl2591.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,660 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2020 Julian Doerner <https://github.com/juliandoerner>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file tsl2591.c
+ *
+ * ESP-IDF driver for TSL2591 light-to-digital.
+ *
+ * Copyright (c) 2020 Julian Doerner <https://github.com/juliandoerner>
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <esp_log.h>
+#include <esp_idf_lib_helpers.h>
+#include "tsl2591.h"
+
+#define I2C_FREQ_HZ 400000 // 400kHz
+
+static const char *TAG = "tsl2591";
+
+// Registers
+#define TSL2591_REG_COMMAND 0x80
+#define TSL2591_REG_ENABLE  0x00
+#define TSL2591_REG_CONTROL 0x01
+#define TSL2591_REG_AILTL   0x04    // ALS interrupt low threshold low byte
+#define TSL2591_REG_AILTH   0x05    // ALS interrupt low threshold high byte
+#define TSL2591_REG_AIHTL   0x06    // ALS interrupt high threshold low byte
+#define TSL2591_REG_AIHTH   0x07    // ALS interrupt high threshold high byte
+#define TSL2591_REG_NPAILTL 0x08    // No ALS persist interrupt low threshold low byte
+#define TSL2591_REG_NPAILTH 0x09    // No ALS persist interrupt low threshold high byte
+#define TSL2591_REG_NPAIHTL 0x0A    // No ALS persist interrupt high threshold low byte
+#define TSL2591_REG_NPAIHTH 0x0B    // No ALS persist interrupt high threshold high byte
+#define TSL2591_REG_PERSIST 0x0C    // Interrupt persistence filter
+#define TSL2591_REG_C0DATAL 0x14
+#define TSL2591_REG_C0DATAH 0x15
+#define TSL2591_REG_C1DATAL 0x16
+#define TSL2591_REG_C1DATAH 0x17
+#define TSL2591_REG_STATUS  0x13
+
+// TSL2591 command register transaction mode.
+#define TSL2591_TRANSACTION_NORMAL  0x20    // Normal transaction for addressing registers
+#define TSL2591_TRANSACTION_SPECIAL 0x60    // Special transactions for interrupt clearing
+
+// TSL2591 command register special functions.
+#define TSL2591_SPECIAL_SET_INTR        0x04    // Forces interrupt
+#define TSL2591_SPECIAL_CLEAR_INTR      0x06    // Clear ALS interrupt
+#define TSL2591_SPECIAL_CLEAR_BOTH      0x07    // Clear ALS and no persist ALS interrupt
+#define TSL2591_SPECIAL_CLEAR_NP_INTR   0x0A    // Clear no persist ALS interrupt
+
+// TSL2591 integration times in useconds.
+#define TSL2591_INTEGRATION_TIME_100MS  110
+#define TSL2591_INTEGRATION_TIME_200MS  210
+#define TSL2591_INTEGRATION_TIME_300MS  310
+#define TSL2591_INTEGRATION_TIME_400MS  410
+#define TSL2591_INTEGRATION_TIME_500MS  510
+#define TSL2591_INTEGRATION_TIME_600MS  610
+
+// TSL2591 status flags.
+#define TSL2591_STATUS_ALS_INTR     0x10
+#define TSL2591_STATUS_ALS_NP_INTR  0x20
+#define TSL2591_STATUS_ALS_VALID    0x01  
+
+// Calculation constants.
+#define TSL2591_LUX_DF 408.0F
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+#define SLEEP_MS(x) do { vTaskDelay(pdMS_TO_TICKS(x)); } while (0)
+
+// Read/write to registers.
+static inline esp_err_t write_register(tsl2591_t *dev, uint8_t reg, uint8_t value)
+{
+    ESP_LOGD(TAG, "Writing register: 0x%x; Data: 0x%x.", reg, value);
+    return i2c_dev_write_reg(&dev->i2c_dev, 
+        TSL2591_REG_COMMAND | TSL2591_TRANSACTION_NORMAL | reg, &value, 1);
+}
+
+static inline esp_err_t read_register(tsl2591_t *dev, uint8_t reg, uint8_t *value)
+{
+    esp_err_t err;
+    err = i2c_dev_read_reg(&dev->i2c_dev, 
+        TSL2591_REG_COMMAND | TSL2591_TRANSACTION_NORMAL | reg, value, 1);
+    ESP_LOGD(TAG, "Red register: 0x%x; Data: 0x%x.", reg, *value);
+    return err;
+}
+
+// Write special function to command register.
+static inline esp_err_t write_special_function(tsl2591_t *dev, uint8_t special_function)
+{
+    uint8_t function = TSL2591_REG_COMMAND | TSL2591_TRANSACTION_SPECIAL | special_function;
+    ESP_LOGD(TAG, "Calling special function: 0x%x with 0x%x.", special_function, function);
+    return i2c_dev_write(&dev->i2c_dev, NULL, 0, &function, 8);
+}
+
+// Read/write enable register.
+static inline esp_err_t write_enable_register(tsl2591_t *dev, uint8_t value)
+{
+    return write_register(dev, TSL2591_REG_ENABLE, value);
+}
+
+static inline esp_err_t read_enable_register(tsl2591_t *dev, uint8_t *value)
+{
+    return read_register(dev, TSL2591_REG_ENABLE, value);
+}
+
+// Read/write control register.
+static inline esp_err_t write_control_register(tsl2591_t *dev, uint8_t value)
+{
+    return write_register(dev, TSL2591_REG_CONTROL, value);
+}
+
+static inline esp_err_t read_control_register(tsl2591_t *dev, uint8_t *value)
+{
+    return read_register(dev, TSL2591_REG_CONTROL, value);
+}
+
+// Read 16 bit from two consecutive registers.
+// Note that the sensor will shadow for example C0DATAH if C0DATAL is read. 
+static inline esp_err_t read_register16(tsl2591_t *dev, uint8_t low_register, uint16_t *value)
+{
+    uint8_t buf[2];
+    CHECK(i2c_dev_read_reg(&dev->i2c_dev, 
+        TSL2591_REG_COMMAND | TSL2591_TRANSACTION_NORMAL | low_register, buf, 2));
+    *value = (uint16_t)buf[1] << 8 | buf[0];
+
+    return ESP_OK;
+
+}
+
+
+// Initialization.
+esp_err_t tsl2591_init_desc(tsl2591_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+    ESP_LOGD(TAG, "Initialize descriptor");
+
+    dev->i2c_dev.port = port;
+    dev->i2c_dev.addr = TSL2591_I2C_ADDR; // tsl2591 has only one i2c address.
+    dev->i2c_dev.cfg.sda_io_num = sda_gpio;
+    dev->i2c_dev.cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(&dev->i2c_dev);
+}
+
+esp_err_t tsl2591_free_desc(tsl2591_t *dev)
+{
+    CHECK_ARG(dev);
+    ESP_LOGD(TAG, "Free descriptor.");
+
+    return i2c_dev_delete_mutex(&dev->i2c_dev);
+}
+
+esp_err_t tsl2591_init(tsl2591_t *dev)
+{
+    CHECK_ARG(dev);
+    ESP_LOGD(TAG, "Initialize sensor.");
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    uint8_t tmp_reg = 0;
+    I2C_DEV_CHECK(&dev->i2c_dev, read_enable_register(dev, &tmp_reg));
+    dev->settings.enable_reg = tmp_reg;
+    ESP_LOGD(TAG, "Initial enable register: %x.", tmp_reg);
+
+    I2C_DEV_CHECK(&dev->i2c_dev, read_control_register(dev, &tmp_reg));
+    dev->settings.control_reg = tmp_reg;
+    ESP_LOGD(TAG, "Initial control register: %x.", tmp_reg);
+
+    I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, TSL2591_REG_PERSIST, &tmp_reg));
+    dev->settings.persistence_reg = tmp_reg;
+    ESP_LOGD(TAG, "Initial persistence filter: %x.", tmp_reg);
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    // Wait until the first integration cycle is completed.
+    tsl2591_integration_time_t integration_time;
+    ESP_ERROR_CHECK(tsl2591_get_integration_time(dev, &integration_time));
+    switch (integration_time)
+    {
+    case TSL2591_INTEGRATION_100MS:
+        SLEEP_MS(110);
+        break;
+    case TSL2591_INTEGRATION_200MS:
+        SLEEP_MS(210);
+        break;
+    case TSL2591_INTEGRATION_300MS:
+        SLEEP_MS(310);
+        break;
+    case TSL2591_INTEGRATION_400MS:
+        SLEEP_MS(410);
+        break;
+    case TSL2591_INTEGRATION_500MS:
+        SLEEP_MS(510);
+        break;
+    case TSL2591_INTEGRATION_600MS:
+        SLEEP_MS(610);
+        break;
+    }
+
+    return ESP_OK;
+}
+
+
+// Measure.
+esp_err_t tsl2591_get_channel_data(tsl2591_t *dev, uint16_t *channel0, uint16_t *channel1)
+{
+    CHECK_ARG(dev && channel0 &&  channel1);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    I2C_DEV_CHECK(&dev->i2c_dev, read_register16(dev, TSL2591_REG_C0DATAL, channel0));
+    I2C_DEV_CHECK(&dev->i2c_dev, read_register16(dev, TSL2591_REG_C1DATAL, channel1));
+    
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+    ESP_LOGD(TAG, "channel0: 0x%x channel1: 0x%x.", *channel0, *channel1);
+
+    return ESP_OK;
+}
+
+esp_err_t tsl2591_calculate_lux(tsl2591_t *dev, uint16_t channel0, uint16_t channel1, float *lux)
+{
+    CHECK_ARG(dev && lux);
+
+    float atime, again;
+    switch (dev->settings.control_reg & 0x07)
+    {
+    case TSL2591_INTEGRATION_100MS:
+        atime = 100;
+        break;
+    case TSL2591_INTEGRATION_200MS:
+        atime = 200;
+        break;
+    case TSL2591_INTEGRATION_300MS:
+        atime = 300;
+        break;
+    case TSL2591_INTEGRATION_400MS:
+        atime = 400;
+        break;
+    case TSL2591_INTEGRATION_500MS:
+        atime = 500;
+        break;
+    case TSL2591_INTEGRATION_600MS:
+        atime = 600;
+        break;
+    default:
+        atime = 100;
+    }
+
+    switch (dev->settings.control_reg & TSL2591_GAIN_MAX)
+    {
+    case TSL2591_GAIN_LOW:
+        again = 1;
+        break;
+    case TSL2591_GAIN_MEDIUM:
+        again = 25;
+        break;
+    case TSL2591_GAIN_HIGH:
+        again = 428;
+        break;
+    case TSL2591_GAIN_MAX:
+        again = 9876;
+        break;
+    default:
+        again = 1;
+    }
+
+    // See Adafruit Arduino driver.
+    float cpl = (atime * again) / TSL2591_LUX_DF;
+    *lux = (((float)channel0 - (float)channel1)) * 
+        (1.0F - ((float)channel1 / (float)channel0)) / cpl;
+
+    return ESP_OK;
+}
+
+esp_err_t tsl2591_get_lux(tsl2591_t *dev, float *lux)
+{
+    CHECK_ARG(dev && lux);
+
+    uint16_t channel0, channel1;
+    CHECK(tsl2591_get_channel_data(dev, &channel0, &channel1));
+    
+    return tsl2591_calculate_lux(dev, channel0, channel1, lux);
+}
+
+// Setters and getters enable register.
+esp_err_t tsl2591_set_power_status(tsl2591_t *dev, tsl2591_power_status_t power_status)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    I2C_DEV_CHECK(&dev->i2c_dev, 
+        write_enable_register(dev, (dev->settings.enable_reg & ~TSL2591_POWER_ON) | power_status));
+    dev->settings.enable_reg = (dev->settings.enable_reg & ~TSL2591_POWER_ON) | power_status;
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t tsl2591_get_power_status(tsl2591_t *dev, tsl2591_power_status_t *power_status)
+{
+    CHECK_ARG(dev && power_status);
+
+    *power_status = dev->settings.enable_reg & TSL2591_POWER_ON;
+
+    return ESP_OK;
+}
+
+esp_err_t tsl2591_set_als_status(tsl2591_t *dev, tsl2591_als_status_t als_status)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    I2C_DEV_CHECK(&dev->i2c_dev, 
+        write_enable_register(dev, (dev->settings.enable_reg & ~TSL2591_ALS_ON) | als_status));
+    dev->settings.enable_reg = (dev->settings.enable_reg & ~TSL2591_ALS_ON) | als_status;
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t tsl2591_get_als_status(tsl2591_t *dev, tsl2591_als_status_t *als_status)
+{
+    CHECK_ARG(dev && als_status);
+
+    *als_status = dev->settings.enable_reg & TSL2591_ALS_ON;
+
+    return ESP_OK;
+}
+
+esp_err_t tsl2591_set_interrupt(tsl2591_t *dev, tsl2591_interrupt_t interrupt)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    I2C_DEV_CHECK(&dev->i2c_dev,
+        write_enable_register(dev, (dev->settings.enable_reg & ~TSL2591_ALS_INTR_BOTH_ON) | interrupt));
+    dev->settings.enable_reg = (dev->settings.enable_reg & ~TSL2591_ALS_INTR_BOTH_ON) | interrupt; 
+
+    uint8_t tmp = 0;
+    I2C_DEV_CHECK(&dev->i2c_dev,
+        read_enable_register(dev, &tmp));
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t tsl2591_get_interrupt(tsl2591_t *dev, tsl2591_interrupt_t *interrupt)
+{
+    CHECK_ARG(dev && interrupt);
+
+    *interrupt = dev->settings.enable_reg & TSL2591_ALS_INTR_BOTH_ON;
+
+    return ESP_OK;
+}
+
+esp_err_t tsl2591_set_sleep_after_intr(tsl2591_t *dev, tsl2591_sleep_after_intr_t sleep_after_intr)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    I2C_DEV_CHECK(&dev->i2c_dev,
+        write_enable_register(dev, (dev->settings.enable_reg & ~TSL2591_SLEEP_AFTER_ON) | sleep_after_intr));
+    dev->settings.enable_reg = (dev->settings.enable_reg & ~TSL2591_SLEEP_AFTER_ON) | sleep_after_intr; 
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t tsl2591_get_sleep_after_intr(tsl2591_t *dev, tsl2591_sleep_after_intr_t *sleep_after_intr)
+{
+    CHECK_ARG(dev && sleep_after_intr);
+
+    *sleep_after_intr = dev->settings.enable_reg & TSL2591_SLEEP_AFTER_ON;
+
+    return ESP_OK;
+}
+
+
+// Setters and getters control register.
+esp_err_t tsl2591_set_integration_time(tsl2591_t *dev, tsl2591_integration_time_t integration_time)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    // Last 3 bits represent the integration time.
+    I2C_DEV_CHECK(&dev->i2c_dev, 
+        write_control_register(dev, (dev->settings.control_reg & ~0x07) | integration_time));
+    dev->settings.control_reg = (dev->settings.control_reg & ~0x07) | integration_time;
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t tsl2591_get_integration_time(tsl2591_t *dev, tsl2591_integration_time_t *integration_time)
+{
+    CHECK_ARG(dev && integration_time);
+
+    // Last 3 bits represent the integration time.
+    *integration_time = dev->settings.control_reg & 0x07;
+
+    return ESP_OK;
+}
+
+esp_err_t tsl2591_set_gain(tsl2591_t *dev, tsl2591_gain_t gain)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    I2C_DEV_CHECK(&dev->i2c_dev, 
+        write_control_register(dev, (dev->settings.control_reg & ~TSL2591_GAIN_MAX) | gain));
+    dev->settings.control_reg = (dev->settings.control_reg & ~TSL2591_GAIN_MAX) | gain;
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t tsl2591_get_gain(tsl2591_t *dev, tsl2591_gain_t *gain)
+{
+    CHECK_ARG(dev && gain);
+
+    *gain = dev->settings.control_reg & TSL2591_GAIN_MAX;
+
+    return ESP_OK;
+}
+
+
+// Setter and getter persistence filter.
+esp_err_t tsl2591_set_persistence_filter(tsl2591_t *dev, tsl2591_persistence_filter_t filter)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    I2C_DEV_CHECK(&dev->i2c_dev, 
+        write_register(dev, TSL2591_REG_PERSIST, (dev->settings.persistence_reg & ~TSL2591_60_CYCLES) | filter));
+    dev->settings.persistence_reg = (dev->settings.persistence_reg & ~TSL2591_60_CYCLES) | filter;
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t tsl2591_get_persistence_filter(tsl2591_t *dev, tsl2591_persistence_filter_t *filter)
+{
+    CHECK_ARG(dev && filter);
+
+    *filter = dev->settings.persistence_reg & TSL2591_60_CYCLES;
+
+    return ESP_OK;
+}
+
+
+// Setters thresholds.
+esp_err_t tsl2591_als_set_low_threshold(tsl2591_t *dev, uint16_t low_threshold)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, TSL2591_REG_AILTL, low_threshold));
+    I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, TSL2591_REG_AILTH, low_threshold >> 8));
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t tsl2591_als_set_high_threshold(tsl2591_t *dev, uint16_t high_threshold)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, TSL2591_REG_AIHTL, high_threshold));
+    I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, TSL2591_REG_AIHTH, high_threshold >> 8));
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t tsl2591_no_persist_set_low_threshold(tsl2591_t *dev, uint16_t low_threshold)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, TSL2591_REG_NPAILTL, low_threshold));
+    I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, TSL2591_REG_NPAILTH, low_threshold >> 8));
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t tsl2591_no_persist_set_high_threshold(tsl2591_t *dev, uint16_t high_threshold)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, TSL2591_REG_NPAIHTL, high_threshold));
+    I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, TSL2591_REG_NPAIHTH, high_threshold >> 8));
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+
+// Special functions.
+esp_err_t tsl2591_set_test_intr(tsl2591_t *dev)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    I2C_DEV_CHECK(&dev->i2c_dev,
+        write_special_function(dev, TSL2591_SPECIAL_SET_INTR));
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t tsl2591_clear_als_intr(tsl2591_t *dev)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    I2C_DEV_CHECK(&dev->i2c_dev,
+        write_special_function(dev, TSL2591_SPECIAL_CLEAR_INTR));
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t tsl2591_clear_als_np_intr(tsl2591_t *dev)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    I2C_DEV_CHECK(&dev->i2c_dev,
+        write_special_function(dev, TSL2591_SPECIAL_CLEAR_NP_INTR));
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t tsl2591_clear_both_intr(tsl2591_t *dev)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    I2C_DEV_CHECK(&dev->i2c_dev,
+        write_special_function(dev, TSL2591_SPECIAL_CLEAR_BOTH));
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+
+// Getters status flags.
+esp_err_t tsl2591_get_np_intr_flag(tsl2591_t *dev, bool *flag)
+{
+    CHECK_ARG(dev && flag);
+    
+    uint8_t tmp;
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    I2C_DEV_CHECK(&dev->i2c_dev,
+        read_register(dev, TSL2591_REG_STATUS, &tmp));
+    
+    *flag = tmp & TSL2591_STATUS_ALS_NP_INTR ? true : false;
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t tsl2591_get_als_intr_flag(tsl2591_t *dev, bool *flag)
+{
+    CHECK_ARG(dev && flag);
+    
+    uint8_t tmp;
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    I2C_DEV_CHECK(&dev->i2c_dev,
+        read_register(dev, TSL2591_REG_STATUS, &tmp));
+    
+    *flag = tmp & TSL2591_STATUS_ALS_INTR ? true : false;
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t tsl2591_get_als_valid_flag(tsl2591_t *dev, bool *flag)
+{
+    CHECK_ARG(dev && flag);
+    
+    uint8_t tmp;
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+
+    I2C_DEV_CHECK(&dev->i2c_dev,
+        read_register(dev, TSL2591_REG_STATUS, &tmp));
+    
+    *flag = tmp & TSL2591_STATUS_ALS_VALID? true : false;
+
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tsl2591/tsl2591.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,443 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2020 Julian Doerner <https://github.com/juliandoerner>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file tsl2591.h
+ * @defgroup tsl2591 tsl2591
+ * @{
+ *
+ * ESP-IDF driver for TSL2591 light-to-digital. 
+ *
+ * Copyright (c) 2020 Julian Doerner <https://github.com/juliandoerner>
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+
+#ifndef __TSL2591_H__
+#define __TSL2591_H__
+
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define TSL2591_I2C_ADDR   0x29 // TSL2591 has only one i2c address.
+
+/**
+ * Power status. The sensor measures only if ALS an Power is on.
+ */
+typedef enum
+{
+    TSL2591_POWER_OFF = 0x00,
+    TSL2591_POWER_ON = 0x01 //!< Default
+} tsl2591_power_status_t;
+
+/**
+ * ALS status. The sensor measures only if ALS and Power is on.
+ */
+typedef enum
+{
+    TSL2591_ALS_OFF = 0x00,
+    TSL2591_ALS_ON = 0x02   //!< Default
+} tsl2591_als_status_t;
+
+/**
+ * Interrupts. TSL2591 has two interrupt sources. 
+ * Check the datasheet for details.
+ */
+typedef enum
+{
+    TSL2591_INTR_OFF = 0x00, //!< Default
+    TSL2591_ALS_INTR_ON = 0x10,
+    TSL2591_ALS_INTR_NP_ON = 0x80,
+    TSL2591_ALS_INTR_BOTH_ON = 0x90
+} tsl2591_interrupt_t;
+
+/**
+ * Interrupt sleep setting. 
+ */
+typedef enum
+{
+    TSL2591_SLEEP_AFTER_OFF = 0x00, //!< Default
+    TSL2591_SLEEP_AFTER_ON = 0x40
+} tsl2591_sleep_after_intr_t;
+
+/**
+ * Integration time.
+ */
+typedef enum
+{
+    TSL2591_INTEGRATION_100MS = 0, //!< Default 
+    TSL2591_INTEGRATION_200MS,      
+    TSL2591_INTEGRATION_300MS,      
+    TSL2591_INTEGRATION_400MS,      
+    TSL2591_INTEGRATION_500MS,      
+    TSL2591_INTEGRATION_600MS       
+} tsl2591_integration_time_t;
+
+/**
+ * Gain.
+ */
+typedef enum
+{
+    TSL2591_GAIN_LOW = 0x00, //!< Default
+    TSL2591_GAIN_MEDIUM = 0x10,
+    TSL2591_GAIN_HIGH = 0x20,
+    TSL2591_GAIN_MAX = 0x30
+} tsl2591_gain_t;
+
+/**
+ * Persistence filter.
+ */
+typedef enum
+{
+    TSL2591_EVERY_CYCLE = 0, //!< Default
+    TSL2591_NO_PERSIST,
+    TSL2591_2_CYCLES,
+    TSL2591_3_CYCLES,
+    TSL2591_5_CYCLES,
+    TSL2591_10_CYCLES,
+    TSL2591_15_CYCLES,
+    TSL2591_20_CYCLES,
+    TSL2591_25_CYCLES,
+    TSL2591_30_CYCLES,
+    TSL2591_35_CYCLES,
+    TSL2591_40_CYCLES,
+    TSL2591_45_CYCLES,
+    TSL2591_50_CYCLES,
+    TSL2591_55_CYCLES,
+    TSL2591_60_CYCLES
+} tsl2591_persistence_filter_t;
+
+/**
+ * Device settings.
+ */
+typedef struct 
+{
+    uint8_t enable_reg;
+    uint8_t control_reg;
+    uint8_t persistence_reg;
+} tsl2591_settings_t;
+
+/**
+ * Device descriptor.
+ */
+typedef struct
+{
+    i2c_dev_t i2c_dev;
+    tsl2591_settings_t settings;
+
+} tsl2591_t;
+
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev Device descriptor
+ * @param port I2C port
+ * @param sda_gpio SDA GPIO pin
+ * @param scl_gpio SCL GPIO pin
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl2591_init_desc(tsl2591_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl2591_free_desc(tsl2591_t *dev);
+
+/**
+ * @brief Initialize device
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl2591_init(tsl2591_t *dev);
+
+/**
+ * @brief Read channel data
+ *
+ * @param dev Device descriptor
+ * @param[out] channel0 Channel 0 data
+ * @param[out] channel1 Channel 1 data
+ */
+esp_err_t tsl2591_get_channel_data(tsl2591_t *dev, uint16_t *channel0, uint16_t *channel1);
+
+/**
+ * @brief Calculate light intensity from channels
+ *
+ * @param dev Device descriptor
+ * @param channel0 Channel0 data
+ * @param channel1 Channel1 data
+ * @param[out] lux Light intensity
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl2591_calculate_lux(tsl2591_t *dev, uint16_t channel0, uint16_t channel1, float *lux);
+
+/**
+ * @brief Get and calculate light intensity
+ *
+ * @param dev Device descriptor
+ * @param[out] lux Light intensity
+ * @return `ESP_OK`
+ */
+esp_err_t tsl2591_get_lux(tsl2591_t *dev, float *lux);
+
+/**
+ * @brief Set device power status
+ *
+ * @param dev Device descriptor
+ * @param power_status Power status
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl2591_set_power_status(tsl2591_t *dev, tsl2591_power_status_t power_status);
+
+/**
+ * @brief Get device power status
+ *
+ * @param dev Device descriptor
+ * @param[out] power_status Power status
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl2591_get_power_status(tsl2591_t *dev, tsl2591_power_status_t *power_status);
+
+/**
+ * @brief Set device ALS status
+ *
+ * @param dev Device descriptor
+ * @param als_status Als status
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl2591_set_als_status(tsl2591_t *dev, tsl2591_als_status_t als_status);
+
+/**
+ * @brief Get device ALS status
+ *
+ * @param dev Device descriptor
+ * @param[out] als_status Als status
+ * @return `ESP_OK`
+ */
+esp_err_t tsl2591_get_als_status(tsl2591_t *dev, tsl2591_als_status_t *als_status);
+
+/**
+ * @brief Set device interrupt mode
+ *
+ * @param dev Device descriptor
+ * @param interrupt interrupt mode
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl2591_set_interrupt(tsl2591_t *dev, tsl2591_interrupt_t interrupt);
+
+/**
+ * @brief Get device interrupt mode
+ *
+ * @param dev Device descriptor
+ * @param[out] interrupt interrupt mode
+ * @return `ESP_OK`
+ */
+esp_err_t tsl2591_get_interrupt(tsl2591_t *dev, tsl2591_interrupt_t *interrupt);
+
+/**
+ * @brief Set sleeping after interrupt
+ *
+ * @param dev Device descriptor
+ * @param sleep_after_intr Sleeping after interrupt
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl2591_set_sleep_after_intr(tsl2591_t *dev, tsl2591_sleep_after_intr_t sleep_after_intr);
+
+/**
+ * @brief Get sleeping after interrupt setting
+ *
+ * @param dev Device descriptor
+ * @param[out] sleep_after_intr Sleeping after interrupt
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl2591_get_sleep_after_intr(tsl2591_t *dev, tsl2591_sleep_after_intr_t *sleep_after_intr);
+
+/**
+ * @brief Set device integration time
+ *
+ * @param dev Device descriptor
+ * @param integration_time Integration time
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl2591_set_integration_time(tsl2591_t *dev, tsl2591_integration_time_t integration_time);
+
+/**
+ * @brief Get device integration time
+ *
+ * @param dev Device descriptor
+ * @param[out] integration_time Integration time
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl2591_get_integration_time(tsl2591_t *dev, tsl2591_integration_time_t *integration_time);
+
+/**
+ * @brief Set device gain
+ *
+ * @param dev Device descriptor
+ * @param gain Gain
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl2591_set_gain(tsl2591_t *dev, tsl2591_gain_t gain);
+
+/**
+ * @brief Get device gain
+ *
+ * @param dev Device descriptor
+ * @param[out] gain Gain
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl2591_get_gain(tsl2591_t *dev, tsl2591_gain_t *gain);
+
+/**
+ * @brief Set device persistence filter
+ *
+ * @param dev Device descriptor
+ * @param filter Persistence filter 
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl2591_set_persistence_filter(tsl2591_t *dev, tsl2591_persistence_filter_t filter);
+
+/**
+ * @brief Get device persistence filter
+ *
+ * @param dev Device descriptor
+ * @param[out] filter Persistence filter
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl2591_get_persistence_filter(tsl2591_t *dev, tsl2591_persistence_filter_t *filter);
+
+/**
+ * @brief Set ALS interrupt low threshold
+ *
+ * @param dev Device descriptor
+ * @param low_threshold Low threshold
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl2591_als_set_low_threshold(tsl2591_t *dev, uint16_t low_threshold);
+
+/**
+ * @brief Set ALS interrupt high threshold
+ *
+ * @param dev Device descriptor
+ * @param high_threshold High threshold
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl2591_als_set_high_threshold(tsl2591_t *dev, uint16_t high_threshold);
+
+/**
+ * @brief Set no persist ALS interrupt low threshold
+ *
+ * @param dev Device descriptor
+ * @param low_threshold Low threshold
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl2591_no_persist_set_low_threshold(tsl2591_t *dev, uint16_t low_threshold);
+
+/**
+ * @brief Set no persist ALS interrupt high threshold
+ *
+ * @param dev Device descriptor
+ * @param high_threshold High threshold
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl2591_no_persist_set_high_threshold(tsl2591_t *dev, uint16_t high_threshold);
+
+/**
+ * @brief Set interrupt
+ *
+ * At least on interrupt must be enabled.
+ *
+ * @param dev Device descriptor
+ * @return `ESP_PK` on success
+ */
+esp_err_t tsl2591_set_test_intr(tsl2591_t *dev);
+
+/**
+ * @brief Clear ALS interrupt
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl2591_clear_als_intr(tsl2591_t *dev);
+
+/**
+ * @brief Clear ALS no persist interrupt
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl2591_clear_als_np_intr(tsl2591_t *dev);
+
+/**
+ * @brief Clear both interrupts
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl2591_clear_both_intr(tsl2591_t *dev);
+
+/**
+ * @brief Get ALS no persist interrupt flag
+ *
+ * @param dev Device descriptor
+ * @param[out] flag Interrupt flag
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl2591_get_np_intr_flag(tsl2591_t *dev, bool *flag);
+
+/**
+ * @brief Get ALS interrupt flag
+ *
+ * @param dev Device descriptor
+ * @param[out] flag Interrupt flag
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl2591_get_als_intr_flag(tsl2591_t *dev, bool *flag);
+
+/**
+ * @brief Get ALS validity flag
+ *
+ * This flag is set when integration cycle is completed
+ * after enabling ALS.
+ *
+ * @param dev Device descriptor
+ * @param[out] flag Validity flag
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl2591_get_als_valid_flag(tsl2591_t *dev, bool *flag);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif  // __TSL2591_H__
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tsl4531/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+---
+components:
+  - name: tsl4531
+    description: Driver for digital ambient light sensor TSL4531
+    group: light
+    groups: []
+    code_owners: UncleRus
+    depends:
+      - i2cdev
+      - log
+      - esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2019
+      - author:
+          name: bschwind
+        year: 2017
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tsl4531/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS tsl4531.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tsl4531/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,27 @@
+Copyright (c) 2017 Brian Schwind (https://github.com/bschwind)
+Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tsl4531/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tsl4531/tsl4531.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2017 Brian Schwind <https://github.com/bschwind>
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file tsl4531.c
+ *
+ * ESP-IDF driver for digital ambient light sensor TSL4531
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2017 Brian Schwind <https://github.com/bschwind>\n
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+
+#include <esp_log.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <esp_idf_lib_helpers.h>
+#include "tsl4531.h"
+
+#define I2C_FREQ_HZ 400000
+
+static const char *TAG = "tsl4531";
+
+// Registers
+#define TSL4531_REG_COMMAND   0x80
+#define TSL4531_REG_CONTROL   0x00
+#define TSL4531_REG_CONFIG    0x01
+#define TSL4531_REG_DATA_LOW  0x04
+#define TSL4531_REG_DATA_HIGH 0x05
+#define TSL4531_REG_DEVICE_ID 0x0A
+
+// TSL4531 Misc Values
+#define TSL4531_ON  0x03
+#define TSL4531_OFF 0x00
+
+// Integration times in milliseconds
+#define TSL4531_INTEGRATION_TIME_100MS 120
+#define TSL4531_INTEGRATION_TIME_200MS 240
+#define TSL4531_INTEGRATION_TIME_400MS 480 // Default
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+inline static esp_err_t write_register(tsl4531_t *dev, uint8_t reg, uint8_t val)
+{
+    return i2c_dev_write_reg(&dev->i2c_dev, TSL4531_REG_COMMAND | reg, &val, 1);
+}
+
+inline static esp_err_t read_register(tsl4531_t *dev, uint8_t reg, uint8_t *val)
+{
+    return i2c_dev_read_reg(&dev->i2c_dev, TSL4531_REG_COMMAND | reg, val, 1);
+}
+
+inline static esp_err_t read_register_16(tsl4531_t *dev, uint8_t reg, uint16_t *val)
+{
+    return i2c_dev_read_reg(&dev->i2c_dev, TSL4531_REG_COMMAND | reg, val, 2);
+}
+
+inline static esp_err_t enable(tsl4531_t *dev)
+{
+    return write_register(dev, TSL4531_REG_CONTROL, TSL4531_ON);
+}
+
+inline static esp_err_t disable(tsl4531_t *dev)
+{
+    return write_register(dev, TSL4531_REG_CONTROL, TSL4531_OFF);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t tsl4531_init_desc(tsl4531_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+
+    dev->i2c_dev.port = port;
+    dev->i2c_dev.addr = TSL4531_I2C_ADDR;
+    dev->i2c_dev.cfg.sda_io_num = sda_gpio;
+    dev->i2c_dev.cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(&dev->i2c_dev);
+}
+
+esp_err_t tsl4531_free_desc(tsl4531_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(&dev->i2c_dev);
+}
+
+esp_err_t tsl4531_init(tsl4531_t *dev)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, enable(dev));
+
+    uint8_t control_reg;
+    I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, TSL4531_REG_CONTROL, &control_reg));
+    if (control_reg != TSL4531_ON)
+    {
+        I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+        ESP_LOGE(TAG, "Error initializing TSL4531, control register wasn't set to ON");
+        return ESP_ERR_INVALID_RESPONSE;
+    }
+
+    uint8_t id;
+    I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, TSL4531_REG_DEVICE_ID, &id));
+    id >>= 4;
+    switch (id)
+    {
+        case TSL4531_PART_TSL45317:
+        case TSL4531_PART_TSL45313:
+        case TSL4531_PART_TSL45315:
+        case TSL4531_PART_TSL45311:
+            dev->part_id = id;
+            break;
+        default:
+            ESP_LOGW(TAG, "Unknown part id for TSL4531 sensor: %u", id);
+    }
+
+    I2C_DEV_CHECK(&dev->i2c_dev, disable(dev));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t tsl4531_config(tsl4531_t *dev, tsl4531_integration_time_t integration_time, bool skip_power_save)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, enable(dev));
+    I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, TSL4531_REG_CONFIG,
+            (skip_power_save ? 0x08 : 0x00) | (0x03 & integration_time)));
+    I2C_DEV_CHECK(&dev->i2c_dev, disable(dev));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    dev->integration_time = integration_time;
+    dev->skip_power_save = skip_power_save;
+
+    return ESP_OK;
+}
+
+esp_err_t tsl4531_read_lux(tsl4531_t *dev, uint16_t *lux)
+{
+    CHECK_ARG(dev && lux);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, enable(dev));
+
+    uint16_t multiplier;
+    switch (dev->integration_time)
+    {
+        case TSL4531_INTEGRATION_100MS:
+            multiplier = 4;
+            vTaskDelay(pdMS_TO_TICKS(TSL4531_INTEGRATION_TIME_100MS));
+            break;
+        case TSL4531_INTEGRATION_200MS:
+            multiplier = 2;
+            vTaskDelay(pdMS_TO_TICKS(TSL4531_INTEGRATION_TIME_200MS));
+            break;
+        default:
+            multiplier = 1;
+            vTaskDelay(pdMS_TO_TICKS(TSL4531_INTEGRATION_TIME_400MS));
+    }
+
+    uint16_t lux_data;
+    I2C_DEV_CHECK(&dev->i2c_dev, read_register_16(dev, TSL4531_REG_DATA_LOW, &lux_data));
+    I2C_DEV_CHECK(&dev->i2c_dev, disable(dev));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    *lux = multiplier * lux_data;
+
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tsl4531/tsl4531.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2017 Brian Schwind <https://github.com/bschwind>
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file tsl4531.h
+ * @defgroup tsl4531 tsl4531
+ * @{
+ *
+ * ESP-IDF driver for digital ambient light sensor TSL4531
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2017 Brian Schwind <https://github.com/bschwind>\n
+ * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __TSL4531_H__
+#define __TSL4531_H__
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define TSL4531_I2C_ADDR 0x29
+
+/**
+ * Integration time
+ */
+typedef enum
+{
+    TSL4531_INTEGRATION_400MS = 0x00, //!< Default
+    TSL4531_INTEGRATION_200MS = 0x01,
+    TSL4531_INTEGRATION_100MS = 0x02,
+} tsl4531_integration_time_t;
+
+/**
+ * Part IDs
+ */
+typedef enum
+{
+    TSL4531_PART_TSL45317 = 0x08,
+    TSL4531_PART_TSL45313 = 0x09,
+    TSL4531_PART_TSL45315 = 0x0A,
+    TSL4531_PART_TSL45311 = 0x0B
+} tsl4531_part_id_t;
+
+/**
+ * Device descriptor
+ */
+typedef struct {
+    i2c_dev_t i2c_dev;
+    tsl4531_integration_time_t integration_time;
+    bool skip_power_save;
+    tsl4531_part_id_t part_id;
+} tsl4531_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * @param dev Device descriptor
+ * @param port I2C port
+ * @param sda_gpio SDA GPIO pin
+ * @param scl_gpio SCL GPIO pin
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl4531_init_desc(tsl4531_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl4531_free_desc(tsl4531_t *dev);
+
+/**
+ * @brief Initialize device
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl4531_init(tsl4531_t *dev);
+
+/**
+ * @brief Configure device
+ *
+ * @param dev Device descriptor
+ * @param integration_time Integration time
+ * @param skip_power_save PowerSave Mode. When true, the power save states are
+ *        skipped following a light integration cycle for shorter sampling rates
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl4531_config(tsl4531_t *dev, tsl4531_integration_time_t integration_time, bool skip_power_save);
+
+/**
+ * @brief Read conversion results in lux
+ *
+ * @param dev Device descriptor
+ * @param[out] lux Conversion result in lux
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsl4531_read_lux(tsl4531_t *dev, uint16_t *lux);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif  // __TSL4531_H__
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tsys01/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,24 @@
+---
+components:
+  - name: tsys01
+    description: |
+      Driver for precision digital temperature sensor TSYS01
+    group: temperature
+    groups: []
+    code_owners: UncleRus
+    depends:
+      - i2cdev
+      - log
+      - esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2020
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tsys01/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS tsys01.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tsys01/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+Copyright 2020 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tsys01/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tsys01/tsys01.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file tsys01.c
+ *
+ * ESP-IDF driver for digital temperature sensor TSYS01
+ *
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#include <esp_log.h>
+#include <esp_idf_lib_helpers.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include "tsys01.h"
+
+#define I2C_FREQ_HZ 1000000 // 1MHz
+
+static const char *TAG = "tsys01";
+
+#define CMD_RESET  0x1e
+#define CMD_START  0x48
+#define CMD_READ   0x00
+#define CMD_PROM   0xa0
+#define CMD_SERIAL 0xac
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+inline static esp_err_t send_cmd_nolock(tsys01_t *dev, uint8_t cmd)
+{
+    return i2c_dev_write(&dev->i2c_dev, NULL, 0, &cmd, 1);
+}
+
+static esp_err_t send_cmd(tsys01_t *dev, uint8_t cmd)
+{
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, send_cmd_nolock(dev, cmd));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+inline static float calc_temp(tsys01_t *dev, uint16_t raw)
+{
+    return -2.0f * dev->cal[1] / 1000000000000000000000.0f * raw * raw * raw * raw +
+            4.0f * dev->cal[2] / 10000000000000000.0f * raw * raw * raw +
+           -2.0f * dev->cal[3] / 100000000000.0f * raw * raw +
+            1.0f * dev->cal[4] / 1000000.0f * raw +
+           -1.5f * dev->cal[5] / 100.0f;
+}
+
+static esp_err_t get_temp_nolock(tsys01_t *dev, uint32_t *raw, float *t)
+{
+    uint8_t r[3];
+    CHECK(i2c_dev_read_reg(&dev->i2c_dev, CMD_READ, r, 3));
+
+    if (raw)
+        *raw = ((uint32_t)r[0] << 16) | ((uint32_t)r[1] << 8) | r[2];
+    if (t)
+        *t = calc_temp(dev, ((uint16_t)r[0] << 8) | r[1]);
+
+    return ESP_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t tsys01_init_desc(tsys01_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+
+    if (addr != TSYS01_I2C_ADDR1 && addr != TSYS01_I2C_ADDR2)
+    {
+        ESP_LOGE(TAG, "Invalid I2C address");
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    dev->i2c_dev.port = port;
+    dev->i2c_dev.addr = addr;
+    dev->i2c_dev.cfg.sda_io_num = sda_gpio;
+    dev->i2c_dev.cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(&dev->i2c_dev);
+}
+
+esp_err_t tsys01_free_desc(tsys01_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(&dev->i2c_dev);
+}
+
+esp_err_t tsys01_init(tsys01_t *dev)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    for (size_t i = 0; i < 8; i++)
+    {
+        I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, CMD_PROM + i * 2, dev->cal + i, 2));
+        dev->cal[i] = (dev->cal[i] >> 8) | (dev->cal[i] << 8);
+    }
+    uint8_t r[4];
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, CMD_SERIAL, r, 2));
+    I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, CMD_SERIAL + 2, r + 2, 2));
+    dev->serial = ((uint32_t)r[0] << 16) | ((uint32_t)r[1] << 8) | r[2];
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t tsys01_reset(tsys01_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return send_cmd(dev, CMD_RESET);
+}
+
+esp_err_t tsys01_start(tsys01_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return send_cmd(dev, CMD_START);
+}
+
+esp_err_t tsys01_get_temp(tsys01_t *dev, uint32_t *raw, float *t)
+{
+    CHECK_ARG(dev && (raw || t));
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, get_temp_nolock(dev, raw, t));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
+
+esp_err_t tsys01_measure(tsys01_t *dev, float *t)
+{
+    CHECK_ARG(dev && t);
+
+    I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
+    I2C_DEV_CHECK(&dev->i2c_dev, send_cmd_nolock(dev, CMD_START));
+    vTaskDelay(pdMS_TO_TICKS(10));
+    I2C_DEV_CHECK(&dev->i2c_dev, get_temp_nolock(dev, NULL, t));
+    I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
+
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/tsys01/tsys01.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file tsys01.h
+ * @defgroup tsys01 tsys01
+ * @{
+ *
+ * ESP-IDF driver for digital temperature sensor TSYS01
+ *
+ * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __TSYS01_H__
+#define __TSYS01_H__
+
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define TSYS01_I2C_ADDR1 0x76
+#define TSYS01_I2C_ADDR2 0x77
+
+/**
+ * Device descriptor
+ */
+typedef struct
+{
+    i2c_dev_t i2c_dev; //!< I2C device descriptor
+    uint16_t cal[8];   //!< Calibration values
+    uint32_t serial;   //!< Serial number
+} tsys01_t;
+
+/**
+ * @brief Initialize device descriptor.
+ *
+ * @param dev Device descriptor
+ * @param addr Device I2C address
+ * @param port I2C port
+ * @param sda_gpio SDA GPIO
+ * @param scl_gpio SCL GPIO
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsys01_init_desc(tsys01_t *dev, uint8_t addr, i2c_port_t port,
+                           gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor.
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsys01_free_desc(tsys01_t *dev);
+
+/**
+ * @brief Initialize device.
+ *
+ * Reads sensor configuration.
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsys01_init(tsys01_t *dev);
+
+/**
+ * @brief Reset sensor.
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsys01_reset(tsys01_t *dev);
+
+/**
+ * @brief Start temperature conversion.
+ *
+ * @param dev Device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsys01_start(tsys01_t *dev);
+
+/**
+ * @brief Read converted temperature from sensor.
+ *
+ * @param dev Device descriptor
+ * @param[out] raw Raw ADC value, NULL-able
+ * @param[out] t Temperature, degrees Celsius, NULL-able
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsys01_get_temp(tsys01_t *dev, uint32_t *raw, float *t);
+
+/**
+ * @brief Perform temperature conversion
+ *
+ * This function starts temperature conversion,
+ * waits 10 ms and reads result.
+ *
+ * @param dev Device descriptor
+ * @param[out] t Temperature, degrees Celsius
+ * @return `ESP_OK` on success
+ */
+esp_err_t tsys01_measure(tsys01_t *dev, float *t);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __TSYS01_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ultrasonic/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,24 @@
+---
+components:
+  - name: ultrasonic
+    description: Driver for ultrasonic range meters, e.g. HC-SR04, HY-SRF05
+    group: misc
+    groups: []
+    code_owners: UncleRus
+    depends:
+      # XXX conditional depends
+      - driver
+      - freertos
+      - esp_idf_lib_helpers
+    thread_safe: no
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2016
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ultrasonic/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,13 @@
+if(${IDF_TARGET} STREQUAL esp8266)
+    set(req esp8266 freertos esp_idf_lib_helpers esp_timer)
+elseif(${IDF_VERSION_MAJOR} STREQUAL 4 AND ${IDF_VERSION_MINOR} STREQUAL 1 AND ${IDF_VERSION_PATCH} STREQUAL 3)
+    set(req driver freertos esp_idf_lib_helpers)
+else()
+    set(req driver freertos esp_idf_lib_helpers esp_timer)
+endif()
+
+idf_component_register(
+    SRCS ultrasonic.c
+    INCLUDE_DIRS .
+    REQUIRES ${req}
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ultrasonic/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+Copyright (c) 2016, 2018 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ultrasonic/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,7 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+
+ifdef CONFIG_IDF_TARGET_ESP8266
+COMPONENT_DEPENDS = esp8266 freertos esp_idf_lib_helpers
+else
+COMPONENT_DEPENDS = driver freertos esp_idf_lib_helpers
+endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ultrasonic/ultrasonic.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file ultrasonic.c
+ *
+ * ESP-IDF driver for ultrasonic range meters, e.g. HC-SR04, HY-SRF05 and the like
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#include <esp_idf_lib_helpers.h>
+#include "ultrasonic.h"
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <esp_timer.h>
+#include <ets_sys.h>
+
+#define TRIGGER_LOW_DELAY 4
+#define TRIGGER_HIGH_DELAY 10
+#define PING_TIMEOUT 6000
+#define ROUNDTRIP_M 5800.0f
+#define ROUNDTRIP_CM 58
+
+#if HELPER_TARGET_IS_ESP32
+static portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
+#define PORT_ENTER_CRITICAL portENTER_CRITICAL(&mux)
+#define PORT_EXIT_CRITICAL portEXIT_CRITICAL(&mux)
+
+#elif HELPER_TARGET_IS_ESP8266
+#define PORT_ENTER_CRITICAL portENTER_CRITICAL()
+#define PORT_EXIT_CRITICAL portEXIT_CRITICAL()
+
+#else
+#error cannot identify the target
+#endif
+
+#define timeout_expired(start, len) ((esp_timer_get_time() - (start)) >= (len))
+
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define RETURN_CRITICAL(RES) do { PORT_EXIT_CRITICAL; return RES; } while(0)
+
+esp_err_t ultrasonic_init(const ultrasonic_sensor_t *dev)
+{
+    CHECK_ARG(dev);
+
+    CHECK(gpio_set_direction(dev->trigger_pin, GPIO_MODE_OUTPUT));
+    CHECK(gpio_set_direction(dev->echo_pin, GPIO_MODE_INPUT));
+
+    return gpio_set_level(dev->trigger_pin, 0);
+}
+
+
+esp_err_t ultrasonic_measure_raw(const ultrasonic_sensor_t *dev, uint32_t max_time_us, uint32_t *time_us)
+{
+    CHECK_ARG(dev && time_us);
+
+    PORT_ENTER_CRITICAL;
+
+    // Ping: Low for 2..4 us, then high 10 us
+    CHECK(gpio_set_level(dev->trigger_pin, 0));
+    ets_delay_us(TRIGGER_LOW_DELAY);
+    CHECK(gpio_set_level(dev->trigger_pin, 1));
+    ets_delay_us(TRIGGER_HIGH_DELAY);
+    CHECK(gpio_set_level(dev->trigger_pin, 0));
+
+    // Previous ping isn't ended
+    if (gpio_get_level(dev->echo_pin))
+        RETURN_CRITICAL(ESP_ERR_ULTRASONIC_PING);
+
+    // Wait for echo
+    int64_t start = esp_timer_get_time();
+    while (!gpio_get_level(dev->echo_pin))
+    {
+        if (timeout_expired(start, PING_TIMEOUT))
+            RETURN_CRITICAL(ESP_ERR_ULTRASONIC_PING_TIMEOUT);
+    }
+
+    // got echo, measuring
+    int64_t echo_start = esp_timer_get_time();
+    int64_t time = echo_start;
+    while (gpio_get_level(dev->echo_pin))
+    {
+        time = esp_timer_get_time();
+        if (timeout_expired(echo_start, max_time_us))
+            RETURN_CRITICAL(ESP_ERR_ULTRASONIC_ECHO_TIMEOUT);
+    }
+    PORT_EXIT_CRITICAL;
+
+    *time_us = time - echo_start;
+
+    return ESP_OK;
+}
+
+esp_err_t ultrasonic_measure(const ultrasonic_sensor_t *dev, float max_distance, float *distance)
+{
+    CHECK_ARG(dev && distance);
+
+    uint32_t time_us;
+    CHECK(ultrasonic_measure_raw(dev, max_distance * ROUNDTRIP_M, &time_us));
+    *distance = time_us / ROUNDTRIP_M;
+
+    return ESP_OK;
+}
+
+esp_err_t ultrasonic_measure_cm(const ultrasonic_sensor_t *dev, uint32_t max_distance, uint32_t *distance)
+{
+    CHECK_ARG(dev && distance);
+
+    uint32_t time_us;
+    CHECK(ultrasonic_measure_raw(dev, max_distance * ROUNDTRIP_CM, &time_us));
+    *distance = time_us / ROUNDTRIP_CM;
+
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/ultrasonic/ultrasonic.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file ultrasonic.h
+ * @defgroup ultrasonic ultrasonic
+ * @{
+ *
+ * ESP-IDF driver for ultrasonic range meters, e.g. HC-SR04, HY-SRF05 and so on
+ *
+ * Ported from esp-open-rtos
+ *
+ * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __ULTRASONIC_H__
+#define __ULTRASONIC_H__
+
+#include <driver/gpio.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define ESP_ERR_ULTRASONIC_PING         0x200
+#define ESP_ERR_ULTRASONIC_PING_TIMEOUT 0x201
+#define ESP_ERR_ULTRASONIC_ECHO_TIMEOUT 0x202
+
+/**
+ * Device descriptor
+ */
+typedef struct
+{
+    gpio_num_t trigger_pin; //!< GPIO output pin for trigger
+    gpio_num_t echo_pin;    //!< GPIO input pin for echo
+} ultrasonic_sensor_t;
+
+/**
+ * @brief Init ranging module
+ *
+ * @param dev Pointer to the device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t ultrasonic_init(const ultrasonic_sensor_t *dev);
+
+/**
+ * @brief Measure time between ping and echo
+ *
+ * @param dev Pointer to the device descriptor
+ * @param max_time_us Maximal time to wait for echo
+ * @param[out] time_us Time, us
+ * @return `ESP_OK` on success, otherwise:
+ *         - ::ESP_ERR_ULTRASONIC_PING         - Invalid state (previous ping is not ended)
+ *         - ::ESP_ERR_ULTRASONIC_PING_TIMEOUT - Device is not responding
+ *         - ::ESP_ERR_ULTRASONIC_ECHO_TIMEOUT - Distance is too big or wave is scattered
+ */
+esp_err_t ultrasonic_measure_raw(const ultrasonic_sensor_t *dev, uint32_t max_time_us, uint32_t *time_us);
+
+/**
+ * @brief Measure distance in meters
+ *
+ * @param dev Pointer to the device descriptor
+ * @param max_distance Maximal distance to measure, meters
+ * @param[out] distance Distance in meters
+ * @return `ESP_OK` on success, otherwise:
+ *         - ::ESP_ERR_ULTRASONIC_PING         - Invalid state (previous ping is not ended)
+ *         - ::ESP_ERR_ULTRASONIC_PING_TIMEOUT - Device is not responding
+ *         - ::ESP_ERR_ULTRASONIC_ECHO_TIMEOUT - Distance is too big or wave is scattered
+ */
+esp_err_t ultrasonic_measure(const ultrasonic_sensor_t *dev, float max_distance, float *distance);
+
+/**
+ * @brief Measure distance in centimeters
+ *
+ * @param dev Pointer to the device descriptor
+ * @param max_distance Maximal distance to measure, centimeters
+ * @param[out] distance Distance in centimeters
+ * @return `ESP_OK` on success, otherwise:
+ *         - ::ESP_ERR_ULTRASONIC_PING         - Invalid state (previous ping is not ended)
+ *         - ::ESP_ERR_ULTRASONIC_PING_TIMEOUT - Device is not responding
+ *         - ::ESP_ERR_ULTRASONIC_ECHO_TIMEOUT - Distance is too big or wave is scattered
+ */
+esp_err_t ultrasonic_measure_cm(const ultrasonic_sensor_t *dev, uint32_t max_distance, uint32_t *distance);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __ULTRASONIC_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/veml7700/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,23 @@
+---
+components:
+  - name: veml7700
+    description: Driver for VEML7700 ambient light sensor
+    group: light
+    groups: []
+    code_owners: Th3Link
+    depends:
+      - i2cdev
+      - log
+      - esp_idf_lib_helpers
+    thread_safe: yes
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: ISC
+    copyrights:
+      - author:
+          name: Th3Link
+        year: 2019
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/veml7700/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS veml7700.c
+    INCLUDE_DIRS .
+    REQUIRES i2cdev log esp_idf_lib_helpers
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/veml7700/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,17 @@
+ISC License
+
+SPDX-License-Identifier: ISC
+
+Copyright (c) 2022 Marc Luehr <marcluehr@gmail.com>
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/veml7700/README.md	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,104 @@
+# Driver for the VEML7700 ambient light sensor
+
+This is the driver for the VEML7700 ambiernt light sensor from Vishay. It is not the
+cheapest solution but very easy to integrate, with a minimum of further components.
+The sensor can be SMD soldered by hand on a custom PCB, but also several breakout boards 
+are available.
+
+## About the sensor
+
+The VEML7700 is a high-accuracy ambient light sensor. All measurements and filtering
+is done on the chip which makes it easy to integrate. The sensitivity can be adjusted
+by a gain setting and by changing the integration time. The output of the sensor is a
+count value which must be converted using the resolution value in the driver. 
+
+The sensor has power saving features which reduces the repetition of measurements.
+It has also a power off feature to save even more power.
+
+The ambient light sensor value is filterd very clone to the caracteristic of the
+human eye. Besides, als the white channel with a wider wavelength spectrum. In most
+applications, the ambient light value will do just fine.
+
+## Power consumption
+
+- 0.5 μA in shut-down mode (@3.3V)
+- down to 2 μA in power save mode 4 (@3.3V)
+- 45 μA on 100ms integration time (@3.3V)
+
+## Communication interface
+
+I2C is used as communication interface without any further interrupt pins. It has six
+command codes for the settings and the output values. Read the datasheet or application
+note for further information.
+
+To reduce interactions with the integrated microcontroller, the interrupt feature can
+be used. Therefore, one must configure the low and high threshold and enable the interrupt.
+
+## Interrupt application examples
+
+If values below a certain threshold is of interest, i.e. to activate lights when its 
+getting dark outside, the low threshold should be adjusted and setting the high threshold
+to maximum (65535). 
+
+Another application could be an automated rollershutter, then both thresholds sould be
+set to trigger the up and down movement of rollershutters.
+ 
+## Measurement process
+
+The measurement takes time and the sensor should not be read out faster than the
+measurement time. Therefore the application should be adjusted to the sensor configuration
+regarting integration time and power save modes. Alternatively, the interrupt feature
+can be used by repeatetive reading of the interrupt status.
+
+## Usage
+
+This driver uses i2cdev which must be initialized first. Then initialize the device
+decriptor, one discriptor per device. The sensor can be used without configuration,
+but has a high chance of over-saturation on sunlight. Therefore, change gain and
+integration time to your needs and configure the device.
+
+Then, the veml7700_ambient_light and veml7700_white_channel functions can be used
+to read out the brightness. The driver converts the counts from the device to lx using
+the configuration.
+
+### Hardware configurations
+
+The driver supports multiple VEML7700 sensors at the same time that are
+connected to I2C. Following figure show some possible hardware
+configurations.
+
+First figure shows the configuration with one sensor at I2C bus 0.
+
+```text
+ +------------------+   +----------+
+ | ESP8266 / ESP32  |   | VEML7700 |
+ |                  |   |          |
+ |   GPIO 14 (SCL)  ----> SCL      |
+ |   GPIO 13 (SDA)  <---> SDA      |
+ +------------------+   +----------+
+```
+
+Next figure shows a possible configuration with two I2C buses.
+
+```text
+ +------------------+   +----------+
+ | ESP8266 / ESP32  |   | VEML7700 |
+ |                  |   |          |
+ |   GPIO 14 (SCL)  ----> SCL      |
+ |   GPIO 13 (SDA)  <---> SDA      |
+ |                  |   +----------+
+ |                  |   | VEML7700 |
+ |                  |   |          |
+ |   GPIO 5  (SCL)  ----> SCL      |
+ |   GPIO 4  (SDA)  <---> SDA      |
+ +------------------+   +----------+
+```
+
+Only one sensor per I2C bus is possible, since it uses a unconfigurable I2C slave address. 
+However, one could also use GPIO controller bus buffer to connect the bus to different 
+devices.
+
+## References
+[Datasheet](https://www.vishay.com/docs/84286/veml7700.pdf)
+
+[Application Note](https://www.vishay.com/docs/84323/designingveml7700.pdf)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/veml7700/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,2 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/veml7700/veml7700.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,276 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2022 Marc Luehr <marcluehr@gmail.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define VEML7700_COMMAND_CODE_ALS_CONF_0   (0)
+#define VEML7700_COMMAND_CODE_ALS_WH       (1)
+#define VEML7700_COMMAND_CODE_ALS_WL       (2)
+#define VEML7700_COMMAND_CODE_POWER_SAVING (3)
+#define VEML7700_COMMAND_CODE_ALS          (4)
+#define VEML7700_COMMAND_CODE_WHITE        (5)
+#define VEML7700_COMMAND_CODE_ALS_INT      (6)
+
+#define VEML7700_GAIN_MASK  (0x1800)
+#define VEML7700_GAIN_SHIFT (11)
+
+#define VEML7700_INTEGRATION_TIME_MASK  (0x03C0)
+#define VEML7700_INTEGRATION_TIME_SHIFT (6)
+
+#define VEML7700_PERSISTENCE_PROTECTION_MASK  (0x0030)
+#define VEML7700_PERSISTENCE_PROTECTION_SHIFT (4)
+
+#define VEML7700_INTERRUPT_ENABLE_MASK  (0x0002)
+#define VEML7700_INTERRUPT_ENABLE_SHIFT (1)
+
+#define VEML7700_SHUTDOWN_MASK  (0x0001)
+#define VEML7700_SHUTDOWN_SHIFT (0)
+
+#define VEML7700_POWER_SAVING_MODE_MASK  (0x0060)
+#define VEML7700_POWER_SAVING_MODE_SHIFT (1)
+
+#define VEML7700_POWER_SAVING_MODE_ENABLE_MASK  (0x0001)
+#define VEML7700_POWER_SAVING_MODE_ENABLE_SHIFT (0)
+
+#define VEML7700_INTERRUPT_STATUS_LOW_MASK   (0x8000)
+#define VEML7700_INTERRUPT_STATUS_LOW_SHIFT  (15)
+#define VEML7700_INTERRUPT_STATUS_HIGH_MASK  (0x4000)
+#define VEML7700_INTERRUPT_STATUS_HIGH_SHIFT (14)
+
+#define VEML7700_RESOLUTION_800MS_IT_GAIN_2     (36)
+#define VEML7700_RESOLUTION_800MS_IT_GAIN_2_DIV (1000)
+
+/**
+ * @file veml7700.c
+ *
+ * ESP-IDF driver for VEML7700 brightness sensors for I2C-bus
+ *
+ * Copyright (c) 2022 Marc Luehr <marcluehr@gmail.com>
+ *
+ * MIT Licensed as described in the file LICENSE
+ */
+
+#include "veml7700.h"
+
+#define I2C_FREQ_HZ (100000)
+
+#define CHECK(x)                                                                                                       \
+    do                                                                                                                 \
+    {                                                                                                                  \
+        esp_err_t __;                                                                                                  \
+        if ((__ = x) != ESP_OK)                                                                                        \
+            return __;                                                                                                 \
+    }                                                                                                                  \
+    while (0)
+#define CHECK_ARG(VAL)                                                                                                 \
+    do                                                                                                                 \
+    {                                                                                                                  \
+        if (!(VAL))                                                                                                    \
+            return ESP_ERR_INVALID_ARG;                                                                                \
+    }                                                                                                                  \
+    while (0)
+
+static esp_err_t read_port(i2c_dev_t *dev, uint8_t command_code, uint16_t *data)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_CHECK(dev, i2c_dev_read(dev, &command_code, 1, data, 2));
+
+    return ESP_OK;
+}
+
+static esp_err_t write_port(i2c_dev_t *dev, uint8_t command_code, uint16_t data)
+{
+    CHECK_ARG(dev);
+
+    I2C_DEV_CHECK(dev, i2c_dev_write(dev, &command_code, 1, &data, 2));
+
+    return ESP_OK;
+}
+
+static uint32_t resolution(veml7700_config_t *config)
+{
+    CHECK_ARG(config);
+
+    uint32_t resolution = VEML7700_RESOLUTION_800MS_IT_GAIN_2;
+    switch (config->gain)
+    {
+        case VEML7700_GAIN_1:
+            resolution = resolution * 2;
+            break;
+        case VEML7700_GAIN_DIV_4:
+            resolution = resolution * 8;
+            break;
+        case VEML7700_GAIN_DIV_8:
+            resolution = resolution * 16;
+            break;
+    }
+    switch (config->integration_time)
+    {
+        case VEML7700_INTEGRATION_TIME_400MS:
+            resolution = resolution * 2;
+            break;
+        case VEML7700_INTEGRATION_TIME_200MS:
+            resolution = resolution * 4;
+            break;
+        case VEML7700_INTEGRATION_TIME_100MS:
+            resolution = resolution * 8;
+            break;
+        case VEML7700_INTEGRATION_TIME_50MS:
+            resolution = resolution * 16;
+            break;
+        case VEML7700_INTEGRATION_TIME_25MS:
+            resolution = resolution * 32;
+            break;
+    }
+    return resolution;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+esp_err_t veml7700_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
+{
+    CHECK_ARG(dev);
+
+    dev->port = port;
+    dev->addr = VEML7700_I2C_ADDR;
+    dev->cfg.sda_io_num = sda_gpio;
+    dev->cfg.scl_io_num = scl_gpio;
+#if HELPER_TARGET_IS_ESP32
+    dev->cfg.master.clk_speed = I2C_FREQ_HZ;
+#endif
+
+    return i2c_dev_create_mutex(dev);
+}
+
+esp_err_t veml7700_free_desc(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    return i2c_dev_delete_mutex(dev);
+}
+
+esp_err_t veml7700_probe(i2c_dev_t *dev)
+{
+    CHECK_ARG(dev);
+
+    /* use write request since read request causes a timeout;
+     * just doing a read is not intended to use by the chip,
+     * it is waiting for a command code
+     */
+    I2C_DEV_TAKE_MUTEX(dev);
+    esp_err_t err = i2c_dev_probe(dev, I2C_DEV_WRITE);
+    I2C_DEV_GIVE_MUTEX(dev);
+    return err;
+}
+
+esp_err_t veml7700_set_config(i2c_dev_t *dev, veml7700_config_t *config)
+{
+    CHECK_ARG(dev);
+    CHECK_ARG(config);
+
+    uint16_t config_data = 0;
+    config_data |= config->gain << VEML7700_GAIN_SHIFT;
+    config_data |= config->integration_time << VEML7700_INTEGRATION_TIME_SHIFT;
+    config_data |= config->persistence_protect << VEML7700_PERSISTENCE_PROTECTION_SHIFT;
+    config_data |= config->interrupt_enable << VEML7700_INTERRUPT_ENABLE_SHIFT;
+    config_data |= config->shutdown << VEML7700_SHUTDOWN_SHIFT;
+
+    uint16_t power_saving_data = 0;
+    power_saving_data |= config->power_saving_mode << VEML7700_POWER_SAVING_MODE_SHIFT;
+    power_saving_data |= config->power_saving_enable << VEML7700_POWER_SAVING_MODE_ENABLE_SHIFT;
+    
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, write_port(dev, VEML7700_COMMAND_CODE_ALS_CONF_0, config_data));
+    I2C_DEV_CHECK(dev, write_port(dev, VEML7700_COMMAND_CODE_ALS_WH, config->threshold_high));
+    I2C_DEV_CHECK(dev, write_port(dev, VEML7700_COMMAND_CODE_ALS_WL, config->threshold_low));
+    I2C_DEV_CHECK(dev, write_port(dev, VEML7700_COMMAND_CODE_POWER_SAVING, power_saving_data));
+    I2C_DEV_GIVE_MUTEX(dev);
+    return ESP_OK;
+}
+
+esp_err_t veml7700_get_config(i2c_dev_t *dev, veml7700_config_t *config)
+{
+    CHECK_ARG(dev);
+    CHECK_ARG(config);
+
+    uint16_t config_data = 0;
+    uint16_t power_saving_data = 0;
+    
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, read_port(dev, VEML7700_COMMAND_CODE_ALS_CONF_0, &config_data));
+    I2C_DEV_CHECK(dev, read_port(dev, VEML7700_COMMAND_CODE_ALS_WH, &(config->threshold_high)));
+    I2C_DEV_CHECK(dev, read_port(dev, VEML7700_COMMAND_CODE_ALS_WL, &(config->threshold_low)));
+    I2C_DEV_CHECK(dev, read_port(dev, VEML7700_COMMAND_CODE_POWER_SAVING, &power_saving_data));
+    I2C_DEV_GIVE_MUTEX(dev);
+    
+    config->gain = (config_data & VEML7700_GAIN_MASK) >> VEML7700_GAIN_SHIFT;
+    config->integration_time = (config_data & VEML7700_INTEGRATION_TIME_MASK) 
+        >> VEML7700_INTEGRATION_TIME_SHIFT;
+    config->integration_time = (config_data & VEML7700_PERSISTENCE_PROTECTION_MASK) 
+        >> VEML7700_PERSISTENCE_PROTECTION_SHIFT;
+    config->integration_time = (config_data & VEML7700_INTERRUPT_ENABLE_MASK) 
+        >> VEML7700_INTERRUPT_ENABLE_SHIFT;
+    config->integration_time = (config_data & VEML7700_SHUTDOWN_MASK) 
+        >> VEML7700_SHUTDOWN_SHIFT;
+
+    config->power_saving_mode = (power_saving_data & VEML7700_POWER_SAVING_MODE_MASK)
+        >> VEML7700_POWER_SAVING_MODE_SHIFT;
+    config->power_saving_enable = (power_saving_data & VEML7700_POWER_SAVING_MODE_ENABLE_MASK)
+        >> VEML7700_POWER_SAVING_MODE_ENABLE_SHIFT;
+
+    return ESP_OK;
+}
+
+esp_err_t veml7700_get_ambient_light(i2c_dev_t *dev, veml7700_config_t *config, uint32_t *value)
+{
+    CHECK_ARG(dev);
+    CHECK_ARG(config);
+
+    uint16_t raw_value = 0;
+    CHECK(read_port(dev, VEML7700_COMMAND_CODE_ALS, &raw_value));
+
+    *value = (raw_value * resolution(config)) / VEML7700_RESOLUTION_800MS_IT_GAIN_2_DIV;
+    return ESP_OK;
+}
+
+esp_err_t veml7700_get_white_channel(i2c_dev_t *dev, veml7700_config_t *config, uint32_t *value)
+{
+    CHECK_ARG(dev);
+    CHECK_ARG(config);
+
+    uint16_t raw_value = 0;
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, read_port(dev, VEML7700_COMMAND_CODE_WHITE, &raw_value));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    *value = (raw_value * resolution(config)) / VEML7700_RESOLUTION_800MS_IT_GAIN_2_DIV;
+    return ESP_OK;
+}
+
+esp_err_t veml7700_get_interrupt_status(i2c_dev_t *dev, bool *low_threshold, bool *high_threshold)
+{
+    CHECK_ARG(dev);
+
+    uint16_t interrupt_status = 0;
+    I2C_DEV_TAKE_MUTEX(dev);
+    I2C_DEV_CHECK(dev, read_port(dev, VEML7700_COMMAND_CODE_ALS_INT, &interrupt_status));
+    I2C_DEV_GIVE_MUTEX(dev);
+
+    *high_threshold = interrupt_status & VEML7700_INTERRUPT_STATUS_HIGH_MASK;
+    *low_threshold = interrupt_status & VEML7700_INTERRUPT_STATUS_LOW_MASK;
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/veml7700/veml7700.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,164 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2022 Marc Luehr <marcluehr@gmail.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/**
+ * @file veml7700.h
+ * @defgroup veml7700 veml7700
+ * @{
+ *
+ * ESP-IDF driver for VEML7700 brightness sensors for I2C-bus
+ *
+ * Copyright (c) 2022 Marc Luehr <marcluehr@gmail.com>
+ *
+ * ISC Licensed as described in the file LICENSE
+ */
+#ifndef __VEML7700_H__
+#define __VEML7700_H__
+
+#include <stddef.h>
+#include <i2cdev.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define VEML7700_I2C_ADDR (0x10)
+
+#define VEML7700_INTEGRATION_TIME_25MS  (0b1100)
+#define VEML7700_INTEGRATION_TIME_50MS  (0b1000)
+#define VEML7700_INTEGRATION_TIME_100MS (0b0000)
+#define VEML7700_INTEGRATION_TIME_200MS (0b0001)
+#define VEML7700_INTEGRATION_TIME_400MS (0b0010)
+#define VEML7700_INTEGRATION_TIME_800MS (0b0011)
+
+#define VEML7700_GAIN_1     (0b00)
+#define VEML7700_GAIN_2     (0b01)
+#define VEML7700_GAIN_DIV_8 (0b10)
+#define VEML7700_GAIN_DIV_4 (0b11)
+
+#define VEML7700_POWER_SAVING_MODE_500MS  (0b00)
+#define VEML7700_POWER_SAVING_MODE_1000MS (0b01)
+#define VEML7700_POWER_SAVING_MODE_2000MS (0b10)
+#define VEML7700_POWER_SAVING_MODE_4000MS (0b11)
+
+#define VEML7700_PERSISTENCE_PROTECTION_1 (0b00)
+#define VEML7700_PERSISTENCE_PROTECTION_2 (0b01)
+#define VEML7700_PERSISTENCE_PROTECTION_4 (0b10)
+#define VEML7700_PERSISTENCE_PROTECTION_8 (0b11)
+
+/**
+ * VEML configuration descriptor
+ */
+typedef struct
+{
+    uint16_t gain : 2;                //!< control the sensitivity
+    uint16_t integration_time : 4;    //!< time to measure
+    uint16_t persistence_protect : 2; //!< sample count before the interrupt triggers
+    uint16_t interrupt_enable : 1;    //!< enable threshold interrupt
+    uint16_t shutdown : 1;            //!< set to 1 to shutdown the device, set to 0 to wakeup
+    uint16_t threshold_high;          //!< high threshold for the interrupt
+    uint16_t threshold_low;           //!< low threshold for the interrupt
+    uint16_t power_saving_mode : 2;   //!< power saving mode
+    uint16_t power_saving_enable : 1; //!< enable the pover saving mode
+} veml7700_config_t;
+
+/**
+ * @brief Initialize device descriptor
+ *
+ * Default SCL frequency is 100kHz. The I2C address is fix.
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @param port I2C port number
+ * @param sda_gpio SDA GPIO
+ * @param scl_gpio SCL GPIO
+ * @return `ESP_OK` on success
+ */
+esp_err_t veml7700_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
+
+/**
+ * @brief Free device descriptor
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t veml7700_free_desc(i2c_dev_t *dev);
+
+/**
+ * @brief Probe if the device exist on the bus
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t veml7700_probe(i2c_dev_t *dev);
+
+/**
+ * @brief Write the config to the device
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @param config Pointer to the config descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t veml7700_set_config(i2c_dev_t *dev, veml7700_config_t *config);
+
+/**
+ * @brief Read the config to the device
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @param config Pointer to the config descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t veml7700_get_config(i2c_dev_t *dev, veml7700_config_t *config);
+
+/**
+ * @brief Read ambient light sensor value from the device
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @param config Pointer to the config descriptor
+ * @param value_lux Pointer as return value in lux
+ * @return `ESP_OK` on success
+ */
+esp_err_t veml7700_get_ambient_light(i2c_dev_t *dev, veml7700_config_t *config, uint32_t *value_lux);
+
+/**
+ * @brief Read white channel value from the device
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @param config Pointer to the config descriptor
+ * @param value_lux Pointer as return value in lux
+ * @return `ESP_OK` on success
+ */
+esp_err_t veml7700_get_white_channel(i2c_dev_t *dev, veml7700_config_t *config, uint32_t *value_lux);
+
+/**
+ * @brief Read the interrupt status from the device
+ *
+ * @param dev Pointer to I2C device descriptor
+ * @param low_threshold Pointer to return the low threshold passed indicator
+ * @param high_threshold Pointer to return the high threshold passed indicator
+ * @return `ESP_OK` on success
+ */
+esp_err_t veml7700_get_interrupt_status(i2c_dev_t *dev, bool *low_threshold, bool *high_threshold);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __VEML7700_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/wiegand/.eil.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,24 @@
+---
+components:
+  - name: wiegand
+    description: Wiegand protocol receiver
+    group: misc
+    groups: []
+    code_owners: UncleRus
+    depends:
+      # XXX conditional depends
+      - driver
+      - log
+      - esp_idf_lib_helpers
+    thread_safe: no
+    targets:
+      - name: esp32
+      - name: esp8266
+      - name: esp32s2
+      - name: esp32c3
+    licenses:
+      - name: BSD-3
+    copyrights:
+      - author:
+          name: UncleRus
+        year: 2021
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/wiegand/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,13 @@
+if(${IDF_TARGET} STREQUAL esp8266)
+    set(req esp8266 log esp_idf_lib_helpers esp_timer)
+elseif(${IDF_VERSION_MAJOR} STREQUAL 4 AND ${IDF_VERSION_MINOR} STREQUAL 1 AND ${IDF_VERSION_PATCH} STREQUAL 3)
+    set(req driver log esp_idf_lib_helpers)
+else()
+    set(req driver log esp_idf_lib_helpers esp_timer)
+endif()
+
+idf_component_register(
+    SRCS wiegand.c
+    INCLUDE_DIRS .
+    REQUIRES ${req}
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/wiegand/LICENSE	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,26 @@
+Copyright 2021 Ruslan V. Uss <unclerus@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of itscontributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/wiegand/component.mk	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,7 @@
+COMPONENT_ADD_INCLUDEDIRS = .
+
+ifdef CONFIG_IDF_TARGET_ESP8266
+COMPONENT_DEPENDS = esp8266 log esp_idf_lib_helpers
+else
+COMPONENT_DEPENDS = driver log esp_idf_lib_helpers
+endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/wiegand/wiegand.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file wiegand.c
+ *
+ * ESP-IDF Wiegand protocol receiver
+ *
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#include <esp_log.h>
+#include <string.h>
+#include <stdlib.h>
+#include <esp_idf_lib_helpers.h>
+#include "wiegand.h"
+
+static const char *TAG = "wiegand";
+
+#define TIMER_INTERVAL_US 50000 // 50ms
+
+#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
+#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
+
+static void isr_disable(wiegand_reader_t *reader)
+{
+    gpio_set_intr_type(reader->gpio_d0, GPIO_INTR_DISABLE);
+    gpio_set_intr_type(reader->gpio_d1, GPIO_INTR_DISABLE);
+}
+
+static void isr_enable(wiegand_reader_t *reader)
+{
+    gpio_set_intr_type(reader->gpio_d0, GPIO_INTR_NEGEDGE);
+    gpio_set_intr_type(reader->gpio_d1, GPIO_INTR_NEGEDGE);
+}
+
+#if HELPER_TARGET_IS_ESP32
+static void IRAM_ATTR isr_handler(void *arg)
+#else
+static void isr_handler(void *arg)
+#endif
+{
+    wiegand_reader_t *reader = (wiegand_reader_t *)arg;
+    if (!reader->enabled)
+        return;
+
+    int d0 = gpio_get_level(reader->gpio_d0);
+    int d1 = gpio_get_level(reader->gpio_d1);
+
+    // ignore equal
+    if (d0 == d1)
+        return;
+    // overflow
+    if (reader->bits >= reader->size * 8)
+        return;
+
+    esp_timer_stop(reader->timer);
+
+    uint8_t value;
+    if (reader->bit_order == WIEGAND_MSB_FIRST)
+        value = (d0 ? 0x80 : 0) >> (reader->bits % 8);
+    else
+        value = (d0 ? 1 : 0) << (reader->bits % 8);
+
+    if (reader->byte_order == WIEGAND_MSB_FIRST)
+        reader->buf[reader->size - reader->bits / 8 - 1] |= value;
+    else
+        reader->buf[reader->bits / 8] |= value;
+
+    reader->bits++;
+
+    esp_timer_start_once(reader->timer, TIMER_INTERVAL_US);
+}
+
+static void timer_handler(void *arg)
+{
+    wiegand_reader_t *reader = (wiegand_reader_t *)arg;
+
+    ESP_LOGD(TAG, "Got %d bits of data", reader->bits);
+
+    wiegand_reader_disable(reader);
+
+    if (reader->callback)
+        reader->callback(reader);
+
+    wiegand_reader_enable(reader);
+
+    isr_enable(reader);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+esp_err_t wiegand_reader_init(wiegand_reader_t *reader, gpio_num_t gpio_d0, gpio_num_t gpio_d1,
+        bool internal_pullups, size_t buf_size, wiegand_callback_t callback, wiegand_order_t bit_order,
+        wiegand_order_t byte_order)
+{
+    CHECK_ARG(reader && buf_size && callback);
+
+    esp_err_t res = gpio_install_isr_service(0);
+    if (res != ESP_OK && res != ESP_ERR_INVALID_STATE)
+        return res;
+
+    memset(reader, 0, sizeof(wiegand_reader_t));
+    reader->gpio_d0 = gpio_d0;
+    reader->gpio_d1 = gpio_d1;
+    reader->size = buf_size;
+    reader->buf = calloc(buf_size, 1);
+    reader->bit_order = bit_order;
+    reader->byte_order = byte_order;
+    reader->callback = callback;
+
+    esp_timer_create_args_t timer_args = {
+        .name = TAG,
+        .arg = reader,
+        .callback = timer_handler,
+        .dispatch_method = ESP_TIMER_TASK
+    };
+    CHECK(esp_timer_create(&timer_args, &reader->timer));
+
+    CHECK(gpio_set_direction(gpio_d0, GPIO_MODE_INPUT));
+    CHECK(gpio_set_direction(gpio_d1, GPIO_MODE_INPUT));
+    CHECK(gpio_set_pull_mode(gpio_d0, internal_pullups ? GPIO_PULLUP_ONLY : GPIO_FLOATING));
+    CHECK(gpio_set_pull_mode(gpio_d1, internal_pullups ? GPIO_PULLUP_ONLY : GPIO_FLOATING));
+    isr_disable(reader);
+    CHECK(gpio_isr_handler_add(gpio_d0, isr_handler, reader));
+    CHECK(gpio_isr_handler_add(gpio_d1, isr_handler, reader));
+    isr_enable(reader);
+    reader->enabled = true;
+
+    ESP_LOGD(TAG, "Reader initialized on D0=%d, D1=%d", gpio_d0, gpio_d1);
+
+    return ESP_OK;
+}
+
+esp_err_t wiegand_reader_disable(wiegand_reader_t *reader)
+{
+    CHECK_ARG(reader);
+
+    isr_disable(reader);
+    esp_timer_stop(reader->timer);
+    reader->enabled = false;
+
+    ESP_LOGD(TAG, "Reader on D0=%d, D1=%d disabled", reader->gpio_d0, reader->gpio_d1);
+
+    return ESP_OK;
+}
+
+esp_err_t wiegand_reader_enable(wiegand_reader_t *reader)
+{
+    CHECK_ARG(reader);
+
+    reader->bits = 0;
+    memset(reader->buf, 0, reader->size);
+
+    isr_enable(reader);
+    reader->enabled = true;
+
+    ESP_LOGD(TAG, "Reader on D0=%d, D1=%d enabled", reader->gpio_d0, reader->gpio_d1);
+
+    return ESP_OK;
+}
+
+esp_err_t wiegand_reader_done(wiegand_reader_t *reader)
+{
+    CHECK_ARG(reader && reader->buf);
+
+    isr_disable(reader);
+    CHECK(gpio_isr_handler_remove(reader->gpio_d0));
+    CHECK(gpio_isr_handler_remove(reader->gpio_d1));
+    esp_timer_stop(reader->timer);
+    CHECK(esp_timer_delete(reader->timer));
+    free(reader->buf);
+
+    ESP_LOGD(TAG, "Reader removed");
+
+    return ESP_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/components/wiegand/wiegand.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of itscontributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file wiegand.h
+ * @defgroup wiegand wiegand
+ * @{
+ *
+ * ESP-IDF Wiegand protocol receiver
+ *
+ * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com>
+ *
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef __WIEGAND_H__
+#define __WIEGAND_H__
+
+#include <driver/gpio.h>
+#include <esp_err.h>
+#include <esp_timer.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct wiegand_reader wiegand_reader_t;
+
+typedef void (*wiegand_callback_t)(wiegand_reader_t *reader);
+
+/**
+ * Bit and byte order of data
+ */
+typedef enum {
+    WIEGAND_MSB_FIRST = 0,
+    WIEGAND_LSB_FIRST
+} wiegand_order_t;
+
+/**
+ * Wiegand reader descriptor
+ */
+struct wiegand_reader
+{
+    gpio_num_t gpio_d0, gpio_d1;
+    wiegand_callback_t callback;
+    wiegand_order_t bit_order;
+    wiegand_order_t byte_order;
+
+    uint8_t *buf;
+    size_t size;
+    size_t bits;
+    esp_timer_handle_t timer;
+    bool start_parity;
+    bool enabled;
+};
+
+/**
+ * @brief Create and initialize reader instance.
+ *
+ * @param reader           Reader descriptor
+ * @param gpio_d0          GPIO pin for D0
+ * @param gpio_d1          GPIO pin for D0
+ * @param internal_pullups Enable internal pull-up resistors for D0 and D1 GPIO
+ * @param buf_size         Reader buffer size in bytes, must be large enough to
+ *                         contain entire Wiegand key
+ * @param callback         Callback function for processing received codes
+ * @param bit_order        Bit order of data
+ * @param byte_order       Byte order of data
+ * @return `ESP_OK` on success
+ */
+esp_err_t wiegand_reader_init(wiegand_reader_t *reader, gpio_num_t gpio_d0, gpio_num_t gpio_d1,
+        bool internal_pullups, size_t buf_size, wiegand_callback_t callback, wiegand_order_t bit_order,
+        wiegand_order_t byte_order);
+
+/**
+ * @brief Disable reader
+ *
+ * While reader is disabled, it will not receive new data
+ *
+ * @param reader Reader descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t wiegand_reader_disable(wiegand_reader_t *reader);
+
+/**
+ * @brief Enable reader
+ *
+ * @param reader Reader descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t wiegand_reader_enable(wiegand_reader_t *reader);
+
+/**
+ * @brief Delete reader instance.
+ *
+ * @param reader Reader descriptor
+ * @return `ESP_OK` on success
+ */
+esp_err_t wiegand_reader_done(wiegand_reader_t *reader);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif /* __WIEGAND_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/devtools/.rubocop.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,134 @@
+---
+# inherit_from: .rubocop_todo.yml
+
+AllCops:
+  Exclude:
+    - "vendor/**/*"
+  # enable detailed explanations available in cops
+  # the default output is not enough to understand what is wrong
+  DisplayCopNames: true
+  ExtraDetails: true
+  DisplayStyleGuide: true
+
+  # the default CacheRootDirectory is no longer `/tmp`, but a directory under
+  # `$HOME` and some Unix platforms use symlink to that path
+  AllowSymlinksInCacheRootDirectory: true
+
+Style/StringLiterals:
+  EnforcedStyle: double_quotes
+
+Style/SymbolArray:
+  # perefer brackets for `grep-ability`
+  EnforcedStyle: brackets
+
+Metrics/BlockLength:
+  Exclude:
+    - Guardfile
+  IgnoredMethods:
+    - describe
+    - context
+    - namespace
+
+Layout/LineLength:
+  Exclude:
+    # Gemfile is not application code
+    - "Gemfile"
+  # ignore heredoc for readability
+  AllowHeredoc: true
+  # URLs are almost always long
+  AllowURI: true
+  URISchemes:
+    - http
+    - https
+    - git
+    - ftp
+  IgnoreCopDirectives: true
+
+Gemspec/DateAssignment: # new in 1.10
+  Enabled: true
+Layout/LineEndStringConcatenationIndentation: # new in 1.18
+  Enabled: true
+Layout/SpaceBeforeBrackets: # new in 1.7
+  Enabled: true
+Lint/AmbiguousAssignment: # new in 1.7
+  Enabled: true
+Lint/AmbiguousOperatorPrecedence: # new in 1.21
+  Enabled: true
+Lint/AmbiguousRange: # new in 1.19
+  Enabled: true
+Lint/DeprecatedConstants: # new in 1.8
+  Enabled: true
+Lint/DuplicateBranch: # new in 1.3
+  Enabled: true
+Lint/DuplicateRegexpCharacterClassElement: # new in 1.1
+  Enabled: true
+Lint/EmptyBlock: # new in 1.1
+  Enabled: true
+Lint/EmptyClass: # new in 1.3
+  Enabled: true
+Lint/EmptyInPattern: # new in 1.16
+  Enabled: true
+Lint/IncompatibleIoSelectWithFiberScheduler: # new in 1.21
+  Enabled: true
+Lint/LambdaWithoutLiteralBlock: # new in 1.8
+  Enabled: true
+Lint/NoReturnInBeginEndBlocks: # new in 1.2
+  Enabled: true
+Lint/NumberedParameterAssignment: # new in 1.9
+  Enabled: true
+Lint/OrAssignmentToConstant: # new in 1.9
+  Enabled: true
+Lint/RedundantDirGlobSort: # new in 1.8
+  Enabled: true
+Lint/RequireRelativeSelfPath: # new in 1.22
+  Enabled: true
+Lint/SymbolConversion: # new in 1.9
+  Enabled: true
+Lint/ToEnumArguments: # new in 1.1
+  Enabled: true
+Lint/TripleQuotes: # new in 1.9
+  Enabled: true
+Lint/UnexpectedBlockArity: # new in 1.5
+  Enabled: true
+Lint/UnmodifiedReduceAccumulator: # new in 1.1
+  Enabled: true
+Security/IoMethods: # new in 1.22
+  Enabled: true
+Style/ArgumentsForwarding: # new in 1.1
+  Enabled: true
+Style/CollectionCompact: # new in 1.2
+  Enabled: true
+Style/DocumentDynamicEvalDefinition: # new in 1.1
+  Enabled: true
+Style/EndlessMethod: # new in 1.8
+  Enabled: true
+Style/HashConversion: # new in 1.10
+  Enabled: true
+Style/HashExcept: # new in 1.7
+  Enabled: true
+Style/IfWithBooleanLiteralBranches: # new in 1.9
+  Enabled: true
+Style/InPatternThen: # new in 1.16
+  Enabled: true
+Style/MultilineInPatternThen: # new in 1.16
+  Enabled: true
+Style/NegatedIfElseCondition: # new in 1.2
+  Enabled: true
+Style/NilLambda: # new in 1.3
+  Enabled: true
+Style/NumberedParameters: # new in 1.22
+  Enabled: true
+Style/NumberedParametersLimit: # new in 1.22
+  Enabled: true
+Style/QuotedSymbols: # new in 1.16
+  Enabled: true
+Style/RedundantArgument: # new in 1.4
+  Enabled: true
+Style/RedundantSelfAssignmentBranch: # new in 1.19
+  Enabled: true
+Style/SelectByRegexp: # new in 1.22
+  Enabled: true
+Style/StringChars: # new in 1.12
+  Enabled: true
+Style/SwapValues: # new in 1.1
+  Enabled: true
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/devtools/README.md.erb	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,150 @@
+# ESP-IDF Components library
+
+[![Build Status](https://github.com/UncleRus/esp-idf-lib/workflows/Build%20examples/badge.svg)](https://github.com/UncleRus/esp-idf-lib/actions?query=workflow%3A%22Build+examples%22)
+[![Build the documentation](https://github.com/UncleRus/esp-idf-lib/workflows/Build%20the%20documentation/badge.svg)](https://github.com/UncleRus/esp-idf-lib/actions?query=workflow%3A%22Build+the+documentation%22)
+[![Docs Status](https://readthedocs.org/projects/esp-idf-lib/badge/?version=latest&style=flat)](https://esp-idf-lib.readthedocs.io/en/latest/)
+
+Components for Espressif ESP32 [ESP-IDF framework](https://github.com/espressif/esp-idf)
+and [ESP8266 RTOS SDK](https://github.com/espressif/ESP8266_RTOS_SDK).
+
+Part of them ported from [esp-open-rtos](https://github.com/SuperHouse/esp-open-rtos).
+
+## Supported versions of frameworks and devices
+
+| Chip           | Framework          | Versions
+|----------------|--------------------|-----------------------
+| ESP32          | ESP-IDF            | All officially supported versions (see [Support Period Policy](https://github.com/espressif/esp-idf/blob/master/SUPPORT_POLICY.md)) and `master`
+| ESP32-S2 *[1]* | ESP-IDF            | All officially supported versions and `master`
+| ESP32-C3 *[1]* | ESP-IDF            | All officially supported versions and `master`
+| ESP8266  *[2]* | ESP8266 RTOS SDK   | `master`, v3.4
+
+[1] *Use "`idf.py set-target esp32s2`" or "`idf.py set-target esp32c3`" before "`idf.py menuconfig`" to change
+the chip type.*
+
+[2] *Due to the incompatibility of ESP8266 drivers and hardware, some
+libraries are not* *supported on ESP8266 (see "ESP8266" column in the tables).*
+
+## How to use
+
+### ESP32
+
+Clone this repository somewhere, e.g.:
+
+```Shell
+cd ~/myprojects/esp
+git clone https://github.com/UncleRus/esp-idf-lib.git
+```
+
+Add path to components in your [project makefile](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/build-system-legacy.html),
+e.g:
+
+```Makefile
+PROJECT_NAME := my-esp-project
+EXTRA_COMPONENT_DIRS := /home/user/myprojects/esp/esp-idf-lib/components
+include $(IDF_PATH)/make/project.mk
+```
+
+or in [CMakeLists.txt](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/build-system.html):
+
+```CMake
+cmake_minimum_required(VERSION 3.5)
+set(EXTRA_COMPONENT_DIRS /home/user/myprojects/esp/esp-idf-lib/components)
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(my-esp-project)
+```
+
+or with CMake [FetchContent](https://cmake.org/cmake/help/latest/module/FetchContent.html)
+
+```CMake
+cmake_minimum_required(VERSION 3.11)
+include(FetchContent)
+FetchContent_Declare(
+  espidflib
+  GIT_REPOSITORY https://github.com/UncleRus/esp-idf-lib.git
+)
+FetchContent_MakeAvailable(espidflib)
+set(EXTRA_COMPONENT_DIRS ${espidflib_SOURCE_DIR}/components)
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(my-esp-project)
+```
+
+### ESP8266 RTOS SDK
+
+Clone this repository somewhere, e.g.:
+
+```Shell
+cd ~/myprojects/esp
+git clone https://github.com/UncleRus/esp-idf-lib.git
+```
+
+Add path to components in your [project makefile](https://docs.espressif.com/projects/esp8266-rtos-sdk/en/latest/api-guides/build-system.html),
+e.g:
+
+```Makefile
+PROJECT_NAME := my-esp-project
+EXTRA_COMPONENT_DIRS := /home/user/myprojects/esp/esp-idf-lib/components
+EXCLUDE_COMPONENTS := max7219 mcp23x17 led_strip max31865 ls7366r max31855
+include $(IDF_PATH)/make/project.mk
+```
+
+See [GitHub examples](https://github.com/UncleRus/esp-idf-lib/tree/master/examples)
+or [GitLab examples](https://gitlab.com/UncleRus/esp-idf-lib/tree/master/examples).
+
+## Documentation
+
+- [Documentation](https://esp-idf-lib.readthedocs.io/en/latest/)
+- [Frequently asked questions](FAQ.md)
+
+## Components
+<% groups.sort_by!(&:description).each do |g| %>
+### <%= g.description %>
+
+| Component                | Description                                                                      | License | Supported on       | Thread safety
+|--------------------------|----------------------------------------------------------------------------------|---------|--------------------|--------------
+<% components = all_components.select { |c| c.group_of?(g.name) }.sort_by!(&:name).each do |c|
+     name = format("%-24s", "**#{c.name}**")
+     description = format("%-80s", c.description)
+     licenses = format("%-7s", c.licenses.map(&:name).join(", "))
+     supported_on = format("%-18s", c.targets.map { |t| "`#{t.name}`" }.join(", ")) -%>
+| <%= name %> | <%= description %> | <%= licenses %> | <%= supported_on %> | <%= c.thread_safe ? "Yes" : "No" %>
+<% end -%>
+<% end -%>
+
+## Library maintainers
+
+- [Ruslan V. Uss](https://github.com/UncleRus)
+- [Tomoyuki Sakurai](https://github.com/trombik)
+
+## Credits
+
+- [Tomoyuki Sakurai](https://github.com/trombik), developer of the LM75 and
+  SK9822/APA102 drivers, author of the RTOS SDK ESP82666 support, master CI
+- [Gunar Schorcht](https://github.com/gschorcht), developer of SHT3x, BME680
+  and CCS811 drivers
+- [Brian Schwind](https://github.com/bschwind), developer of TS2561 and
+  TSL4531 drivers
+- [Andrej Krutak](https://github.com/andree182), developer of BH1750 driver
+- Frank Bargstedt, developer of BMP180 driver
+- [sheinz](https://github.com/sheinz), developer of BMP280 driver
+- [Jonathan Hartsuiker](https://github.com/jsuiker), developer of DHT driver
+- [Grzegorz Hetman](https://github.com/hetii), developer of DS18B20 driver
+- [Alex Stewart](https://github.com/astewart-consensus), developer of DS18B20 driver
+- [Richard A Burton](mailto:richardaburton@gmail.com), developer of DS3231 driver
+- [Bhuvanchandra DV](https://github.com/bhuvanchandra), developer of DS3231 driver
+- [Zaltora](https://github.com/Zaltora), developer of INA3231 driver
+- [Bernhard Guillon](https://gitlab.com/mrnice), developer of MS5611-01BA03 driver
+- [Pham Ngoc Thanh](https://github.com/panoti), developer of PCF8591 driver
+- [Lucio Tarantino](https://github.com/dianlight), developer of ADS111x driver
+- [Julian Dörner](https://github.com/juliandoerner), developer of TSL2591 driver
+- [FastLED community](https://github.com/FastLED), developers of `lib8tion`,
+  `color` and `noise` libraries
+- [Erriez](https://github.com/Erriez), developer of MH-Z19B driver
+- [David Douard](https://github.com/douardda), developer of MH-Z19B driver
+- [Nate Usher](https://github.com/nated0g), developer of SCD30 driver
+- [Josh Kallus](https://github.com/Jkallus), developer of LS7366R driver
+- [saasaa](https://github.com/saasaa), developer of HTS221 driver
+- [Timofei Korostelev](https://github.com/chudsaviet), developer of HT16K33 driver
+- [Jose Manuel Perez](https://github.com/jmpmscorp), developer of LC709203F driver
+- [Weslley Duarte](https://github.com/weslleymfd), developer of ADS130E08 driver
+- [Jan Veeh](https://github.com/janveeh), developer of ICM42670 driver
+- [Marc Luehr](https://github.com/Th3Link), developer of VEML7700 driver
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/devtools/Rakefile	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+task default: [:test]
+
+desc "Run all tests"
+task test: [:rubocop, :rspec]
+
+desc "Run rubocop"
+task :rubocop do
+  sh "rubocop"
+end
+
+desc "Run rspec"
+task :rspec do
+  sh "rspec --format d"
+end
+
+desc "Update README.md"
+task :readme do
+  require "erb"
+  require_relative "spec/group_list"
+  require_relative "spec/component"
+
+  template = File.read("README.md.erb")
+  groups = GroupList.new("groups.yml").all
+
+  # * select if it is a directory
+  # * make path to metadata file
+  # * read it
+  # * parse it as YAML
+  # * take all components under "components" key
+  # * flatten the list of components
+  # * create a Component from the item
+  all_components = Dir.children("../components")
+                      .select { |f| File.directory?(File.join("../components", f)) }
+                      .map { |c| File.join("../components", c, ".eil.yml") }
+                      .map { |f| File.read(f) }
+                      .map { |f| YAML.safe_load(f) }
+                      .map { |y| y["components"] }
+                      .flatten
+                      .map { |c| Component.new(c) }
+  markdown = ERB.new(template, trim_mode: "%-").result(binding)
+  puts markdown
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/devtools/cmake-get-requires/.gitignore	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,4 @@
+CMakeCache.txt
+CMakeFiles
+cmake_install.cmake
+Makefile
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/devtools/cmake-get-requires/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,23 @@
+# a script to parse CMakeLists.txt of a component, print REQUIRES.
+#
+# usage:
+# cmake -DCOMPONENT_NAME:STRING=framebuffer .  >/dev/null
+#
+# output in stderr:
+# REQUIRES:log;color
+
+cmake_minimum_required(VERSION 3.5)
+
+# mock idf_component_register()
+function(idf_component_register)
+    set(multiValueArgs REQUIRES)
+    cmake_parse_arguments(MY "" "" "${multiValueArgs}" ${ARGN})
+
+    # print REQUIRES argument to stderr. MY_REQUIRES is a semicolon separated
+    # string, such as `foo;bar`
+    message(NOTICE "REQUIRES:${MY_REQUIRES}")
+endfunction()
+
+include(${CMAKE_CURRENT_SOURCE_DIR}/../../components/${COMPONENT_NAME}/CMakeLists.txt)
+
+project(ProjectName)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/devtools/groups.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,49 @@
+---
+
+- name: adc-dac
+  description: ADC/DAC libraries
+
+- name: common
+  description: Common libraries
+
+- name: rtc
+  description: Real-time clocks
+
+- name: humidity
+  description: Humidity sensors
+
+- name: temperature
+  description: Temperature sensors
+
+- name: pressure
+  description: Pressure sensors
+
+- name: air-quality
+  description: Air quality sensors
+
+- name: gas
+  description: Gas sensors
+
+- name: current
+  description: Current and power sensors
+
+- name: magnetic
+  description: Magnetic sensors
+
+- name: light
+  description: Light sensors
+
+- name: gpio
+  description: GPIO expanders
+
+- name: led
+  description: LED drivers
+
+- name: input
+  description: Input device drivers
+
+- name: misc
+  description: Other misc libraries
+
+- name: imu
+  description: Inertial measurement units
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/devtools/persons.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,117 @@
+---
+- name: UncleRus
+  email: unclerus@gmail.com
+  full_name: Ruslan V. Uss
+  gh_id: UncleRus
+
+- name: trombik
+  email: y@trombik.org
+  full_name: Tomoyuki Sakurai
+  gh_id: trombik
+
+- name: dianlight
+  full_name: Lucio Tarantino
+  gh_id: dianlight
+
+- name: Andrej
+  full_name: Andrej Krutak
+  email: dev@andree.sk
+
+- name: gschorcht
+  full_name: Gunar Schorcht
+  gh_id: gschorcht
+
+- name: FrankB
+  full_name: Frank Bargstedt
+
+- name: sheinz
+  gh_id: sheinz
+
+- name: FastLED
+  gh_id: FastLED
+  full_name: FastLED project
+
+- name: jsuiker
+  gh_id: jsuiker
+
+- name: PavelM
+  email: merzlyakovpavel@gmail.com
+
+- name: AlexS
+  email: foogod@gmail.com
+
+- name: GrzegorzH
+  email: ghetman@gmail.com
+
+- name: BhuvanchandraD
+  email: bhuvanchandra.dv@gmail.com
+
+- name: RichardA
+  email: richardaburton@gmail.com
+
+- name: Zaltora
+  gh_id: Zaltora
+
+- name: BernhardG
+  email: Bernhard.Guillon@begu.org
+
+- name: zeroday
+  # GH account does not exist
+  email: zeroday@nodemcu.com
+
+- name: PhamNgocT
+  # GH account exists, but not sure that is the correct one
+  email: pnt239@gmail.com
+
+- name: Sensirion
+  gh_id: Sensirion
+
+- name: nated0g
+  gh_id: nated0g
+  full_name: Nate Usher
+
+- name: bschwind
+  gh_id: bschwind
+  full_name: Brian Schwind
+
+- name: juliandoerner
+  gh_id: juliandoerner
+  full_name: Julian Doerner
+
+- name: Erriez
+  gh_id: Erriez
+
+- name: DavidD
+  full_name: David Douard
+  email: david.douard@sdfa3.org
+
+- name: Jkallus
+  full_name: Joshua Kallus
+  email: joshk.kallus3@gmail.com
+  gh_id: Jkallus
+
+- name: saasaa
+  full_name: Alexander Bodenseher
+  gh_id: saasaa
+
+- name: chudsaviet
+  full_name: Timofei Korostelev
+  email: timofei_public@dranik.dev
+  gh_id: chudsaviet
+
+- name: jmpmscorp
+  full_name: Jose Manuel Perez
+  gh_id: jmpmscorp
+
+- name: weslleymfd
+  full_name: Weslley Duarte
+  gh_id: weslleymfd
+
+- name: janveeh
+  full_name: Jan Veeh
+  gh_id: janveeh
+
+- name: Th3Link
+  full_name: Marc Luehr
+  gh_id: th3link
+  email: marcluehr@gmail.com
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/devtools/spec/component.rb	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,117 @@
+# frozen_string_literal: true
+
+require_relative "copyright"
+require_relative "target"
+require_relative "person"
+require_relative "license"
+require_relative "group"
+
+class Component
+  # Component represents a component in metadata. it is not a `component` in
+  # esp-idf. a metadata may contain multiple Components. usually, an esp-idf
+  # component has one `component` and its metadata usually contains one
+  # Component. but a project, such as `esp-idf-lib` repository, may have
+  # multiple `components`.
+
+  # a list of valid keys. some values of keys are simply String or Integer
+  # object. others are objects, or resources, defined in the specification.
+  VALID_KEYS = %w[
+    name
+    description
+    group
+    groups
+    code_owners
+    depends
+    thread_safe
+    targets
+    licenses
+    copyrights
+  ].freeze
+
+  def initialize(hash)
+    validate_keys(hash)
+    @metadata = hash
+    @name = name
+  end
+
+  attr_reader :metadata
+
+  def validate_keys(hash)
+    # validate basic constraints only. the classes are for tests in spec
+    # files, providing readable tests and results. the actual specification is
+    # in the spec files, not in classes. rspec is more readable and maintainable
+    # than ruby code.
+    #
+    # maybe, if these classes are found to be useful, create a gem of the
+    # classes and specification tests in the gem. until then, keep the classes
+    # simple so that others can maintain the specification.
+    raise ArgumentError, "missing name" unless hash.key?("name")
+    raise ArgumentError, "empty name" if hash["name"].empty?
+
+    hash.each_key do |k|
+      raise ArgumentError, "unknown key: `#{k}`" unless VALID_KEYS.include?(k)
+    end
+  end
+
+  def to_s
+    metadata["name"]
+  end
+
+  # special keys that return instances of classes.
+  def group
+    Group.new(metadata["group"])
+  end
+
+  def groups
+    metadata["groups"].map { |g| Group.new(g) }
+  end
+
+  def code_owners
+    metadata["code_owners"].map { |p| Person.new(p) }
+  end
+
+  def targets
+    metadata["targets"].map { |t| Target.new(t) }
+  end
+
+  def licenses
+    metadata["licenses"].map { |l| License.new(l) }
+  end
+
+  def copyrights
+    metadata["copyrights"].map { |c| Copyright.new(c) }
+  end
+
+  def valid_key_with_question?(name)
+    name.to_s.end_with?("?") && VALID_KEYS.include?(name.to_s.chop)
+  end
+
+  def valid_key?(name)
+    VALID_KEYS.include?(name.to_s)
+  end
+
+  def description
+    # if description contains newline, remove it
+    metadata["description"].split("\n").join(" ")
+  end
+
+  def method_missing(name, *args, &block)
+    # name?, description?, etc
+    return metadata.key?(name.to_s.chop) if valid_key_with_question?(name)
+    # name, etc
+    return metadata[name.to_s] if valid_key?(name)
+
+    super
+  end
+
+  def respond_to_missing?(name, include_private = false)
+    # when name is not something we don't know, do `super`, i.e. raising
+    # unknown methods error.
+    super unless valid_key_with_question?(name) || valid_key?(name)
+  end
+
+  def group_of?(arg)
+    group_name = arg.respond_to?(:name) ? arg.name : arg
+    group.name == group_name || groups.map(&:name).include?(group_name)
+  end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/devtools/spec/component_spec.rb	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,169 @@
+# frozen_string_literal: true
+
+require_relative "spec_helper"
+
+VALID_THREAD_SAFE_VALUES = [true, false, "N/A"].freeze
+
+# rubocop:disable Metrics/BlockLength
+metadata_array.each do |m|
+  RSpec.describe "metadata #{m}" do
+    it "has components" do
+      expect(m.components?).to be true
+    end
+
+    it "has one or more of components" do
+      expect(m.components.length).to be >= 1
+    end
+
+    m.components.each do |c|
+      describe "component #{c}" do
+        subject { c }
+        it "does not raise error" do
+          expect { subject }.not_to raise_error
+        end
+
+        describe "name" do
+          it "has name" do
+            expect(subject.name?).to be true
+          end
+
+          it "has String name" do
+            expect(subject.name).to be_kind_of(String)
+          end
+
+          it "has non-empty name" do
+            expect(subject.name).not_to be_empty
+          end
+        end
+
+        describe "description" do
+          it "has description" do
+            expect(subject.description?).to be true
+          end
+
+          it "has String description" do
+            expect(subject.description).to be_kind_of(String)
+          end
+        end
+
+        describe "group" do
+          it "has a primary group" do
+            expect(subject.group?).to be true
+          end
+
+          it "has a valid primary group" do
+            expect { subject.group }.not_to raise_error
+          end
+        end
+
+        describe "groups" do
+          context "when it has one or more groups" do
+            it "has zero or more of groups" do
+              skip "it has no groups" unless subject.groups?
+              expect(subject.groups.length).to be >= 0
+            end
+
+            it "has valid groups" do
+              skip "it has no groups" unless subject.groups?
+              skip "it has zero group" if subject.groups? && subject.groups.empty?
+              expect { subject.groups }.not_to raise_error
+            end
+          end
+        end
+
+        describe "depends" do
+          # XXX `depends` needs better tests because `depends` has zero or
+          # more of components, and some components are one in our components,
+          # others are one in esp-idf. we need to know one in `depends`
+          # actually exists. other resources, such as `People` resolves the
+          # issue by having a list of `People`.
+          context "when it has depends" do
+            it "has zero or more of depends" do
+              skip "it has no depends" unless subject.depends?
+              expect(subject.depends.length).to be >= 0
+            end
+
+            it "has valid depends" do
+              skip "it has no depends" unless subject.depends?
+              skip "it has zero depends" if subject.depends? && subject.depends.empty?
+              expect { subject.depends }.not_to raise_error
+            end
+          end
+        end
+
+        describe "thread_safe" do
+          it "has thread_safe" do
+            expect(subject.thread_safe?).to be true
+          end
+
+          it "has valid values of thread_safe" do
+            expect(VALID_THREAD_SAFE_VALUES).to include subject.thread_safe
+          end
+        end
+
+        describe "targets" do
+          it "has targets" do
+            expect(subject.targets?).to be true
+          end
+
+          it "has valid targets" do
+            expect { subject.targets }.not_to raise_error
+          end
+        end
+
+        describe "licenses" do
+          it "has licenses" do
+            expect(subject.licenses?).to be true
+          end
+
+          it "has valid licenses" do
+            expect { subject.licenses }.not_to raise_error
+          end
+
+          it "has one or more of licenses" do
+            expect(subject.licenses.length).to be >= 1
+          end
+        end
+
+        describe "copyrights" do
+          it "has copyrights" do
+            expect(subject.copyrights?).to be true
+          end
+
+          it "has valid copyrights" do
+            expect { subject.copyrights }.not_to raise_error
+          end
+
+          it "has one or more of copyrights" do
+            expect(subject.copyrights.length).to be >= 1
+          end
+        end
+
+        describe "each copyright" do
+          it "has only one of  name or author in copyrights" do
+            subject.copyrights.each do |copyright|
+              expect(copyright.name? && copyright.author?).to be false
+            end
+          end
+
+          context "when a copyright has author" do
+            it "has valid Person as copyright author" do
+              subject.copyrights.select(&:author?).each do |copyright|
+                expect(copyright.author).to be_a Person
+              end
+            end
+          end
+
+          context "when a copyright has name" do
+            it "has valid Person as copyright author" do
+              subject.copyrights.select(&:name?).each do |copyright|
+                expect(copyright.name).to be_a Person
+              end
+            end
+          end
+        end
+      end
+    end
+  end
+end
+# rubocop:enable Metrics/BlockLength
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/devtools/spec/copyright.rb	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+require_relative "person"
+
+class Copyright
+  VALID_KEYS = %w[
+    author
+    name
+    year
+  ].freeze
+
+  def initialize(hash)
+    raise ArgumentError, "expect Hash, got `#{hash.class}`" unless hash.is_a?(Hash)
+
+    validate_keys(hash)
+    @metadata = hash
+  end
+
+  attr_reader :metadata
+
+  def validate_keys(hash)
+    hash.each_key do |k|
+      raise ArgumentError, "unknown key: `#{k}`. valid keys are: #{VALID_KEYS.join(' ')}" unless VALID_KEYS.include? k
+    end
+  end
+
+  def author?
+    metadata.key?("author")
+  end
+
+  def author
+    Person.new(metadata["author"])
+  end
+
+  def name
+    Person.new("name" => metadata["name"])
+  end
+
+  def name?
+    metadata.key?("name")
+  end
+
+  def year
+    metadata["year"]
+  end
+
+  def year?
+    metadata.key?("year")
+  end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/devtools/spec/group.rb	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+class Group
+  VALID_KEYS = %w[name description].freeze
+
+  def initialize(arg)
+    validate_arg(arg)
+    validate_keys(arg) if arg.is_a? Hash
+
+    @metadata = if arg.is_a? String
+                  { "name" => arg }
+                else
+                  arg
+                end
+  end
+
+  def validate_keys(arg)
+    arg.each_key do |k|
+      raise ArgumentError, "unknown key: `#{k}`. valid keys are: #{VALID_KEYS.join(' ')}" unless VALID_KEYS.include? k
+    end
+    raise ArgumentError, "a key, `name` is required, but missing" unless arg.key?("name")
+  end
+
+  def validate_arg(arg)
+    raise ArgumentError, "argument must be String or Hash" unless arg.is_a?(String) || arg.is_a?(Hash)
+  end
+
+  def name?
+    @metadata.key?("name")
+  end
+
+  def name
+    @metadata["name"]
+  end
+
+  def description?
+    @metadata.key?("description")
+  end
+
+  def description
+    @metadata["description"]
+  end
+
+  def to_s
+    @metadata["name"]
+  end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/devtools/spec/group_list.rb	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require "yaml"
+require_relative "group"
+
+class GroupList
+  # path to `groups.yml`
+  def initialize(arg)
+    @path = File.expand_path(arg)
+  end
+
+  attr_reader :path
+
+  def load_file
+    File.read(path)
+  end
+
+  def parse
+    YAML.safe_load(load_file)
+  end
+
+  def metadata
+    return @metadata if @metadata
+
+    @metadata = parse
+  end
+
+  def all
+    metadata.map { |g| Group.new(g) }
+  end
+
+  def lookup(name)
+    metadata.select { |g| g["name"] == name }.map { |g| Group.new(g) }
+  end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/devtools/spec/groups_spec.rb	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require_relative "spec_helper"
+require_relative "group_list"
+
+file = File.join(File.dirname(__FILE__), "..", "groups.yml")
+
+RSpec.describe GroupList do
+  subject { GroupList.new(file) }
+
+  it "creates new instance" do
+    expect { subject }.not_to raise_error
+  end
+
+  describe "#load_file" do
+    it "does not raise" do
+      expect { subject.load_file }.not_to raise_error
+    end
+  end
+
+  describe "#parse" do
+    it "does not raise" do
+      expect { subject.parse }.not_to raise_error
+    end
+  end
+
+  describe "#metadata" do
+    it "returns Array" do
+      expect(subject.metadata).to be_a Array
+    end
+  end
+end
+
+RSpec.describe "Group list metadata #{file}" do
+  groups = GroupList.new(file)
+
+  groups.all.each do |group|
+    describe "Group #{group}" do
+      subject { group }
+
+      it "is a Group" do
+        expect(group).to be_a Group
+      end
+
+      it "has name as a key" do
+        expect(group.name?).to be true
+      end
+
+      it "has non-empty name" do
+        expect(group.name).not_to be_empty
+      end
+
+      it "has description as a key" do
+        expect(group.description?).to be true
+      end
+
+      it "has non-empty description" do
+        expect(group.description).not_to be_empty
+      end
+
+      it "is a unique group" do
+        expect(groups.lookup(group.name).length).to be 1
+      end
+    end
+  end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/devtools/spec/license.rb	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class License
+  def initialize(hash)
+    raise ArgumentError, "missing key `name`" unless hash.key?("name")
+
+    @metadata = hash
+  end
+
+  def name
+    @metadata["name"]
+  end
+
+  def to_s
+    name
+  end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/devtools/spec/metadata.rb	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require_relative "component"
+
+# A class that represents metadaata, `.eil.yml`
+class Metadata
+  # path: path to component root directory
+  def initialize(path)
+    raise ArgumentError, "path is missing" unless path
+
+    @path = path
+    @name = File.basename(path)
+    raise ArgumentError, "path `#{path}` does not have basename" if @name.empty?
+
+    metadata
+  end
+
+  attr_reader :path, :name
+
+  def metadata
+    return @metadata if @metadata
+
+    file = File.join(path, ".eil.yml")
+    @metadata = YAML.safe_load(File.read(file))
+  rescue StandardError => e
+    warn "failed to open `#{file}`. does component `#{File.basename(path)}` have `.eil.yml` file?"
+    raise e
+  end
+
+  def components?
+    metadata.key?("components")
+  end
+
+  def components
+    metadata["components"].map { |c| Component.new(c) }
+  end
+
+  def to_s
+    name
+  end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/devtools/spec/person.rb	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,100 @@
+# frozen_string_literal: true
+
+require "yaml"
+
+# A class that repesents Peson
+class Person
+  PERSON_FILE = File.expand_path(File.join(File.dirname("__FILE__"), "persons.yml")).freeze
+
+  def initialize(arg)
+    validate_arg(arg)
+    validate_keys(arg) if arg.is_a? Hash
+
+    name = if arg.is_a? String
+             arg
+           else
+             arg["name"]
+           end
+    @metadata = lookup_person(name)
+  end
+
+  attr_reader :metadata, :persons
+
+  def validate_keys(hash)
+    raise ArgumentError, "missing key: `name`" unless hash.key?("name")
+  end
+
+  def valid_arg_class?(arg)
+    arg.is_a?(String) || arg.is_a?(Hash)
+  end
+
+  def validate_arg(arg)
+    raise ArgumentError, "String or dict is ecpected, but got `#{arg.class}`" unless valid_arg_class?(arg)
+  end
+
+  def load_person_file
+    File.read(PERSON_FILE)
+  end
+
+  def parse(string)
+    return @persons if @persons
+
+    @persons = YAML.safe_load(string)
+  rescue StandardError => e
+    warn "failed to parse #{PERSON_FILE} as YAML"
+    raise e
+  end
+
+  def lookup_person(name)
+    parse(load_person_file)
+    person = persons.select { |p| p["name"] == name }
+    raise ArgumentError, "cannot find Person with name `#{name}` in #{PERSON_FILE}" unless person
+    raise ArgumentError, "Person with name `#{name}` has duplicated entry in #{PERSON_FILE}" if person.length > 1
+
+    person.first
+  end
+
+  def name?
+    metadata.key?("name")
+  end
+
+  def name
+    metadata["name"]
+  end
+
+  def full_name?
+    metadata.key?("full_name")
+  end
+
+  def full_name
+    metadata["full_name"]
+  end
+
+  def gh_id?
+    metadata.key?("gh_id")
+  end
+
+  def gh_id
+    metadata["gh_id"]
+  end
+
+  def email?
+    metadata.key?("email")
+  end
+
+  def email
+    metadata["email"]
+  end
+
+  def website?
+    metadata.key?("website")
+  end
+
+  def website
+    metadata["website"]
+  end
+
+  def to_s
+    metadata["name"]
+  end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/devtools/spec/person_list.rb	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require "yaml"
+
+class PersonList
+  # path to `persons.yml`
+  def initialize(arg)
+    @path = File.expand_path(arg)
+  end
+
+  attr_reader :path
+
+  def load_file
+    File.read(path)
+  end
+
+  def parse
+    YAML.safe_load(load_file)
+  end
+
+  def metadata
+    return @metadata if @metadata
+
+    @metadata = parse
+  end
+
+  def all
+    metadata
+  end
+
+  def lookup(name)
+    metadata.select { |g| g["name"] == name }
+  end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/devtools/spec/persons_spec.rb	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require_relative "spec_helper"
+require_relative "person_list"
+require_relative "person"
+
+file = File.join(File.dirname(__FILE__), "..", "persons.yml")
+
+RSpec.describe "Person list metadata #{file}" do
+  persons = PersonList.new(file)
+
+  persons.all.each do |person|
+    describe "Person #{person}" do
+      subject { Person.new(person) }
+
+      it "has a name" do
+        expect(subject.name?).to be true
+      end
+
+      it "has non-empty name" do
+        expect(subject.name).not_to be_empty
+      end
+
+      it "has one or more of contact information" do
+        has_contact = subject.email? || subject.gh_id? || subject.full_name? || subject.website?
+        expect(has_contact).to be true
+      end
+
+      it "is unique in persons.yml" do
+        expect(persons.lookup(subject.name).length).to be 1
+      end
+    end
+  end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/devtools/spec/spec_helper.rb	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require "rspec"
+require "yaml"
+require_relative "metadata"
+
+@component_dir = File.expand_path(File.join(File.dirname(__FILE__), "../../components"))
+
+def metadata_array
+  directories = Dir.children(Dir.new(@component_dir)).map { |c| File.join(@component_dir, c) }
+  directories = directories.select { |d| File.directory?(d) }
+  directories.map { |path| Metadata.new(path) }
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/devtools/spec/target.rb	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+require "yaml"
+
+# A class that represents build target
+
+class Target
+  VALID_KEYS = %w[name].freeze
+  TARGETS_FILE = File.expand_path(File.join(File.dirname("__FILE__"), "..", "targets.yml")).freeze
+
+  def initialize(arg)
+    validate_arg(arg)
+    validate_keys(arg) if arg.is_a?(Hash)
+    @metadata = if arg.is_a?(String)
+                  { "name" => arg }
+                else
+                  arg
+                end
+  end
+
+  attr_reader :metadata
+
+  def valid_arg_class?(arg)
+    arg.is_a?(String) || arg.is_a?(Hash)
+  end
+
+  def validate_arg(arg)
+    raise ArgumentError, "String or dict is ecpected, but got `#{arg.class}`" unless valid_arg_class?(arg)
+  end
+
+  def validate_keys(hash)
+    hash.each_key do |k|
+      raise ArgumentError, "unknown key: `#{k}`. valid keys are: #{VALID_KEYS.join(' ')}" unless VALID_KEYS.include?(k)
+    end
+  end
+
+  def name?
+    metadata.key?("name")
+  end
+
+  def name
+    metadata["name"]
+  end
+
+  def load_file
+    File.read(TARGETS_FILE)
+  end
+
+  def parse(string)
+    YAML.safe_load(string)
+  rescue StandardError => e
+    warn "failed to parse #{TARGETS_FILE} as YAML"
+    raise e
+  end
+
+  def targets
+    return @targets if @targets
+
+    @targets = parse(load_file)
+  end
+
+  def lookup(name)
+    targets.select { |t| t.name == name }
+  end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/devtools/spec/target_list.rb	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+require "person_list"
+
+class TargetList < PersonList
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/devtools/spec/targets_spec.rb	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require_relative "spec_helper"
+require_relative "target_list"
+
+file = File.join(File.dirname(__FILE__), "..", "targets.yml")
+
+RSpec.describe "Target list metadata #{file}" do
+  targets = TargetList.new(file)
+
+  targets.all.each do |target|
+    describe "Target #{target}" do
+      subject { Target.new(target) }
+
+      it "has a name" do
+        expect(subject.name?).to be true
+      end
+
+      it "has non-empty name" do
+        expect(subject.name).not_to be_empty
+      end
+
+      it "is a unique target in the list" do
+        expect(targets.lookup(subject.name).length).to be 1
+      end
+    end
+  end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/esp-idf-lib/devtools/targets.yml	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,5 @@
+---
+
+- name: esp32
+
+- name: esp8266
--- a/main/CMakeLists.txt	Sun Mar 26 22:22:45 2023 +0200
+++ b/main/CMakeLists.txt	Mon Mar 27 22:13:21 2023 +0200
@@ -1,2 +1,2 @@
-idf_component_register(SRCS "iotbalkon.c"
+idf_component_register(SRCS config.c iotbalkon.c task_bmp280.c
                     INCLUDE_DIRS ".")
--- a/main/Kconfig.projbuild	Sun Mar 26 22:22:45 2023 +0200
+++ b/main/Kconfig.projbuild	Mon Mar 27 22:13:21 2023 +0200
@@ -2,6 +2,24 @@
 
     orsource "$IDF_PATH/examples/common_components/env_caps/$IDF_TARGET/Kconfig.env_caps"
 
+    choice CODE_PROJECT
+    	prompt "Select project build target"
+    	default CODE_TESTING
+    	help
+	    Select to build for Testing or Production
+
+    config CODE_TESTING
+    	bool "Build for testing"
+    	help
+	    Select this to build for the test environment
+
+    config CODE_PRODUCTION
+    	bool "Build for production"
+    	help
+	    Select this to build for final production
+
+    endchoice
+
     menu "I2C bus"
 
         config I2C_MASTER_SCL
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/config.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,10 @@
+
+#include "config.h"
+
+/*
+ * Sensors parameters and defines
+ */
+bmp280_params_t         bmp280_params;
+bmp280_t                bmp280_dev;
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/config.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,64 @@
+/**
+ * @file config.h
+ * @brief The 'iotbalkon' configuration data.
+ */
+
+#ifndef _CONFIG_H
+#define _CONFIG_H
+
+// Global includes for the project
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <time.h>
+#include <errno.h>
+#include <sys/unistd.h>
+#include <sys/fcntl.h>
+#include <sys/time.h>
+
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/semphr.h"
+#include "freertos/event_groups.h"
+//#include "freertos/queue.h"
+#include "driver/gpio.h"
+#include "driver/i2c.h"
+//#include "driver/adc.h"
+//#include "driver/rtc_io.h"
+//#include "soc/sens_periph.h"
+//#include "soc/rtc.h"
+//#include "esp_adc_cal.h"
+#include "esp_log.h"
+//#include "esp_spiffs.h"
+//#include "esp_vfs.h"
+//#include "esp_system.h"
+//#include "esp_wifi.h"
+//#include "esp_wpa2.h"
+//#include "esp_event.h"
+//#include "nvs_flash.h"
+//#include "lwip/sockets.h"
+//#include "lwip/dns.h"
+//#include "lwip/netdb.h"
+//#include "mqtt_client.h"
+
+/*
+ * esp-idf-lib
+ */
+#include <i2cdev.h>
+#include <bmp280.h>
+
+/*
+ * Application sources
+ */
+#include "task_bmp280.h"
+//#include "task_wifi.h"
+//#include "task_mqtt.h"
+//#include "task_user.h"
+//#include "xutil.h"
+
+
+
+
+#endif
--- a/main/iotbalkon.c	Sun Mar 26 22:22:45 2023 +0200
+++ b/main/iotbalkon.c	Mon Mar 27 22:13:21 2023 +0200
@@ -1,16 +1,76 @@
-
+/**
+ * @file iotbalkon.c
+ * @brief iotbalkon project.
+ */
 
-#include <stdio.h>
-#include "freertos/FreeRTOS.h"
-#include "freertos/task.h"
-#include "driver/gpio.h"
-#include "esp_log.h"
-
-
+#include "config.h"
 
 static const char *TAG = "iotbalkon";
 
+
+static TaskHandle_t			xTaskBMP280 = NULL;
+
+extern BMP280_State			*bmp280_state;			///< I2C state
+extern SemaphoreHandle_t		xSemaphoreBMP280;		///< I2C lock semaphore
+extern bmp280_params_t			bmp280_params;
+extern bmp280_t				bmp280_dev;
+
+
 void app_main(void)
 {
-    ESP_LOGI(TAG, "Starting");
+#ifdef CONFIG_CODE_PRODUCTION
+    ESP_LOGI(TAG, "Starting production");
+#endif
+#ifdef CONFIG_CODE_TESTING
+    ESP_LOGI(TAG, "Starting testing");
+#endif
+
+    ESP_ERROR_CHECK(i2cdev_init());
+
+    bmp280_init_default_params(&bmp280_params);
+    memset(&bmp280_dev, 0, sizeof(bmp280_t));
+
+    i2c_dev_t dev = { 0 };
+    dev.cfg.sda_io_num = CONFIG_I2C_MASTER_SDA;
+    dev.cfg.scl_io_num = CONFIG_I2C_MASTER_SCL;
+    dev.cfg.master.clk_speed = 1000000;
+
+    dev.addr = 0x39;
+    if (i2c_dev_probe(&dev, I2C_DEV_WRITE) == 0) {
+        ESP_LOGI(TAG, "Found ADPS-9930");
+    }
+    dev.addr = 0x40;
+    if (i2c_dev_probe(&dev, I2C_DEV_WRITE) == 0) {
+	ESP_LOGI(TAG, "Found INA219 Battery");
+    }
+    dev.addr = 0x41;
+    if (i2c_dev_probe(&dev, I2C_DEV_WRITE) == 0) {
+        ESP_LOGI(TAG, "Found INA219 Solar");
+    }
+    dev.addr = 0x76;
+    if (i2c_dev_probe(&dev, I2C_DEV_WRITE) == 0) {
+	ESP_ERROR_CHECK(bmp280_init_desc(&bmp280_dev, BMP280_I2C_ADDRESS_0, 0, CONFIG_I2C_MASTER_SDA, CONFIG_I2C_MASTER_SCL));
+	ESP_ERROR_CHECK(bmp280_init(&bmp280_dev, &bmp280_params));
+	ESP_LOGI(TAG, "Found BMP280 @ 0x76 id: 0x%02x", bmp280_dev.id);
+    }
+    dev.addr = 0x77;
+    if (i2c_dev_probe(&dev, I2C_DEV_WRITE) == 0) {
+        ESP_LOGI(TAG, "Found BMP280 @ 0x77");
+    }
+
+    /*
+     * Create FreeRTOS tasks
+     */
+    xSemaphoreBMP280 = xSemaphoreCreateMutex();
+
+    xTaskCreate(&task_bmp280,     "task_bmp280",      2560, NULL, 8, &xTaskBMP280);
+
+    /*
+     * Main application loop.
+     */
+    while (1) {
+	request_bmp280();
+        vTaskDelay(2000 / portTICK_PERIOD_MS);
+    }
+    // Not reached.
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/task_bmp280.c	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,84 @@
+/**
+ * @file task_bmp280.c
+ * @brief The FreeRTOS task to query the BMP280 sensor.
+ */
+
+
+#include "config.h"
+
+
+static const char		*TAG = "task_bmp280";
+
+SemaphoreHandle_t		xSemaphoreBMP280 = NULL;	///< Semaphore BMP280 task
+EventGroupHandle_t		xEventGroupBMP280;		///< Events BMP280 task
+BMP280_State			*bmp280_state;			///< Public state for other tasks
+
+extern bmp280_params_t                  bmp280_params;
+extern bmp280_t                         bmp280_dev;
+
+const int TASK_BMP280_REQUEST_DONE = BIT0;			///< All requests are done.
+const int TASK_BMP280_REQUEST_TB = BIT1;			///< Request Temperature and Barometer
+
+
+
+void request_bmp280(void)
+{
+    xEventGroupClearBits(xEventGroupBMP280, TASK_BMP280_REQUEST_DONE);
+    xEventGroupSetBits(xEventGroupBMP280, TASK_BMP280_REQUEST_TB);
+}
+
+
+
+bool ready_bmp280(void)
+{
+    if (xEventGroupGetBits(xEventGroupBMP280) & TASK_BMP280_REQUEST_DONE)
+	return true;
+    return false;
+}
+
+
+
+/*
+ * Task to read BMP280 sensor on request.
+ */
+void task_bmp280(void *pvParameter)
+{
+    float pressure, temperature, humidity;
+
+    ESP_LOGI(TAG, "Starting task BMP280 sda=%d scl=%d", CONFIG_I2C_MASTER_SDA, CONFIG_I2C_MASTER_SCL);
+    bmp280_state = malloc(sizeof(BMP280_State));
+    
+    bmp280_state->bmp280.valid = false;
+    bmp280_state->bmp280.fake = false;
+    bmp280_state->bmp280.address = 0;
+    bmp280_state->bmp280.error = BMP280_ERR_NONE;
+
+    /* event handler and event group for this task */
+    xEventGroupBMP280 = xEventGroupCreate();
+    EventBits_t uxBits;
+
+    /*
+     * Task loop forever.
+     */
+    ESP_LOGI(TAG, "Starting loop BMP280 sensor");
+    while (1) {
+
+	uxBits = xEventGroupWaitBits(xEventGroupBMP280, TASK_BMP280_REQUEST_TB, pdFALSE, pdFALSE, portMAX_DELAY );
+
+	if (uxBits & TASK_BMP280_REQUEST_TB) {
+
+	    ESP_LOGI(TAG, "Requested BMP280 readings");
+
+	    if (bmp280_read_float(&bmp280_dev, &temperature, &pressure, &humidity) != ESP_OK) {
+		ESP_LOGI(TAG, "Temperature/pressure reading failed");
+	    } else {
+		ESP_LOGI(TAG, "Pressure: %.2f Pa, Temperature: %.2f C", pressure, temperature);
+	    }
+	    xEventGroupClearBits(xEventGroupBMP280, TASK_BMP280_REQUEST_TB);
+	    xEventGroupSetBits(xEventGroupBMP280, TASK_BMP280_REQUEST_DONE);
+#if 1
+//	    ESP_LOGI(TAG, "Battery    raw: %4d, atten: %d  %.3f volt, error: %d", i2c_reading, atten, i2c_state->Batt_voltage / 1000.0, i2c_state->Batt_error);
+#endif
+	}
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/task_bmp280.h	Mon Mar 27 22:13:21 2023 +0200
@@ -0,0 +1,62 @@
+/**
+ * @file task_bmp280.h
+ * @brief The FreeRTOS task to query the BMP280 sensor connected to
+ *        the I2C bus.
+ *        The task will update the sensor state structures.
+ */
+
+#ifndef	_TASK_BMP280_H
+#define	_TASK_BMP280_H
+
+/*
+ * Error codes in this task
+ */
+#define	BMP280_ERR_NONE			0	///< No errors
+//#define	I2C_ERR_READ			1	///< Generic read error
+
+
+/**
+ * @brief BMP280 sensor
+ */
+typedef struct strBMP280 {
+    bool		valid;			///< Valid measurement
+    bool		fake;			///< Fake measurement
+    uint8_t		address;		///< Device i2c address
+    float		temperature;		///< Temperature in celsius
+    float		humidity;
+    float		pressure;		///< Pressure in hPa
+    int			error;			///< Error result
+} bmp280_tt;
+
+
+/**
+ * @brief Structure containing the variables for the BMP280 task.
+ */
+typedef struct {
+    bmp280_tt		bmp280;			///< Sensor results
+} BMP280_State;
+
+
+
+/**
+ * @brief Request a new measurement from selected sensors.
+ */
+void request_bmp280(void);
+
+
+/**
+ * @brief Check if results are ready
+ * @return true of results are ready, else false.
+ */
+bool ready_bmp280(void);
+
+
+/**
+ * @brief The FreeRTOS task to update the BMP280 on request.
+ * @param pvParameters Parameters for the task.
+ */
+void task_bmp280(void *pvParameters);
+
+
+#endif
+
--- a/sdkconfig	Sun Mar 26 22:22:45 2023 +0200
+++ b/sdkconfig	Mon Mar 27 22:13:21 2023 +0200
@@ -351,14 +351,14 @@
 CONFIG_ESPTOOLPY_FLASHFREQ_80M_DEFAULT=y
 CONFIG_ESPTOOLPY_FLASHFREQ="80m"
 # CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set
-CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y
-# CONFIG_ESPTOOLPY_FLASHSIZE_4MB is not set
+# CONFIG_ESPTOOLPY_FLASHSIZE_2MB is not set
+CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
 # CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set
 # CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set
 # CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set
 # CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set
 # CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set
-CONFIG_ESPTOOLPY_FLASHSIZE="2MB"
+CONFIG_ESPTOOLPY_FLASHSIZE="4MB"
 # CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE is not set
 CONFIG_ESPTOOLPY_BEFORE_RESET=y
 # CONFIG_ESPTOOLPY_BEFORE_NORESET is not set
@@ -389,6 +389,8 @@
 CONFIG_ENV_GPIO_RANGE_MAX=19
 CONFIG_ENV_GPIO_IN_RANGE_MAX=19
 CONFIG_ENV_GPIO_OUT_RANGE_MAX=19
+CONFIG_CODE_TESTING=y
+# CONFIG_CODE_PRODUCTION is not set
 
 #
 # I2C bus
@@ -1056,8 +1058,8 @@
 # CONFIG_LOG_MAXIMUM_LEVEL_VERBOSE is not set
 CONFIG_LOG_MAXIMUM_LEVEL=3
 CONFIG_LOG_COLORS=y
-CONFIG_LOG_TIMESTAMP_SOURCE_RTOS=y
-# CONFIG_LOG_TIMESTAMP_SOURCE_SYSTEM is not set
+# CONFIG_LOG_TIMESTAMP_SOURCE_RTOS is not set
+CONFIG_LOG_TIMESTAMP_SOURCE_SYSTEM=y
 # end of Log output
 
 #
@@ -1555,6 +1557,81 @@
 CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y
 # CONFIG_WIFI_PROV_STA_FAST_SCAN is not set
 # end of Wi-Fi Provisioning Manager
+
+#
+# Button
+#
+CONFIG_BUTTON_MAX=5
+CONFIG_BUTTON_POLL_TIMEOUT=10
+CONFIG_BUTTON_LONG_PRESS_TIMEOUT=1000
+CONFIG_BUTTON_AUTOREPEAT_TIMEOUT=500
+CONFIG_BUTTON_AUTOREPEAT_INTERVAL=250
+# end of Button
+
+#
+# DPS310 driver
+#
+CONFIG_DPS310_PROTOCOL_USING_I2C=y
+# end of DPS310 driver
+
+#
+# Rotary encoders
+#
+CONFIG_RE_MAX=1
+CONFIG_RE_INTERVAL_US=1000
+CONFIG_RE_BTN_DEAD_TIME_US=10000
+CONFIG_RE_BTN_PRESSED_LEVEL_0=y
+# CONFIG_RE_BTN_PRESSED_LEVEL_1 is not set
+CONFIG_RE_BTN_LONG_PRESS_TIME_US=500000
+# end of Rotary encoders
+
+#
+# Example
+#
+CONFIG_EXAMPLE_USING_FOO=y
+# CONFIG_EXAMPLE_USING_BAR is not set
+CONFIG_EXAMPLE_NUMBER=1
+# end of Example
+
+#
+# HMC5883L
+#
+CONFIG_HMC5883L_MEAS_TIMEOUT=6000
+# end of HMC5883L
+
+#
+# I2C
+#
+CONFIG_I2CDEV_TIMEOUT=1000
+# CONFIG_I2CDEV_NOLOCK is not set
+# end of I2C
+
+#
+# LED strip
+#
+CONFIG_LED_STRIP_FLUSH_TIMEOUT=1000
+CONFIG_LED_STRIP_PAUSE_LENGTH=50
+# end of LED strip
+
+#
+# LED strip SPI
+#
+CONFIG_LED_STRIP_SPI_USING_SK9822=y
+CONFIG_LED_STRIP_SPI_MUTEX_TIMEOUT_MS=1
+# end of LED strip SPI
+
+#
+# MCP23x17
+#
+CONFIG_MCP23X17_IFACE_I2C=y
+# CONFIG_MCP23X17_IFACE_SPI is not set
+# end of MCP23x17
+
+#
+# OneWire
+#
+CONFIG_ONEWIRE_CRC8_TABLE=y
+# end of OneWire
 # end of Component config
 
 # Deprecated options for backward compatibility

mercurial