diff --git a/.gitignore b/.gitignore index 3c6e3bc..d8b4157 100644 --- a/.gitignore +++ b/.gitignore @@ -2,10 +2,4 @@ # This is an example and may include too much for your use-case. # You can modify this file to suit your needs. /.esphome/ -.esphome/external_components /secrets.yaml -/.esphome.bak/ -/fonts/ -**/__pycache__/ -components/ads131m08/__pycache__/sensor.cpython-312.pyc -components/ads131m08/__pycache__/__init__.cpython-312.pyc diff --git a/components/ads131m08/__init__.py b/components/ads131m08/__init__.py index 33705e3..0c4ea6e 100644 --- a/components/ads131m08/__init__.py +++ b/components/ads131m08/__init__.py @@ -1,29 +1,80 @@ -import esphome.codegen as cg -import esphome.config_validation as cv -from esphome import pins -from esphome.components import spi -from esphome.const import ( - CONF_ID -) - -CONF_DRDY_PIN = "drdy_pin" -CONF_REFERENCE_VOLTAGE = "reference_voltage" - -DEPENDENCIES = ["spi"] -ads131m08_ns = cg.esphome_ns.namespace("ads131m08") -ADS131M08Hub = ads131m08_ns.class_("ADS131M08Hub", cg.Component, spi.SPIDevice) - -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(ADS131M08Hub), - cv.Required(CONF_DRDY_PIN): pins.internal_gpio_input_pin_schema, - cv.Optional(CONF_REFERENCE_VOLTAGE, default=1.2): cv.float_range(min=1.1, max=1.3), -}).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema(cs_pin_required=True)) - -async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_component(var, config) - await spi.register_spi_device(var, config) - drdy = await cg.gpio_pin_expression(config[CONF_DRDY_PIN]) - reference_voltage = config[CONF_REFERENCE_VOLTAGE] - cg.add(var.set_reference_voltage(reference_voltage)) - cg.add(var.set_drdy_pin(drdy)) +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.components import spi +from esphome.const import CONF_ID #, CONF_MODE + +# Import ThreadModel enum +#from esphome.core import ThreadModel + +MULTI_CONF = True + +CONF_DRDY_PIN = "drdy_pin" +CONF_SYNC_RESET_PIN = "sync_reset_pin" +CONF_REFERENCE_VOLTAGE = "reference_voltage" +CONF_CLOCK_FREQUENCY = "clock_frequency" +CONF_ADS131M08_ID = "_ads131m08_id" +CONF_OSR = "oversampling_ratio" + +DEPENDENCIES = ["spi"] + +ads131m08_ns = cg.esphome_ns.namespace("ads131m08") +ADS131M08Hub = ads131m08_ns.class_("ADS131M08Hub", cg.Component, spi.SPIDevice) + +OSR = ads131m08_ns.enum("ADS131M08_OVERSAMPLING_RATIO") +ALLOWED_OSRS = { + 128: OSR.OSR_128, + 256: OSR.OSR_256, + 512: OSR.OSR_512, + 1024: OSR.OSR_1024, + 2048: OSR.OSR_2048, + 4096: OSR.OSR_4096, + 8192: OSR.OSR_8192, + 16256: OSR.OSR_16256, +} + +ALLOWED_CLOCK_FREQUENCIES = { + 2048000: 2048000, + 4096000: 4096000, + 8192000: 8192000, + "2048kHz": 2048000, + "4096kHz": 4096000, + "8192kHz": 8192000, + "2.048MHz": 2048000, + "4.096MHz": 4096000, + "8.192MHz": 8192000, + } + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(ADS131M08Hub), + cv.Required(CONF_DRDY_PIN): pins.internal_gpio_input_pin_schema, + cv.Optional(CONF_SYNC_RESET_PIN): pins.internal_gpio_input_pin_schema, + cv.Optional(CONF_CLOCK_FREQUENCY, default="8192kHz"): cv.All(cv.frequency, cv.one_of(*ALLOWED_CLOCK_FREQUENCIES.keys())), + cv.Optional(CONF_OSR, default=1024): cv.enum(ALLOWED_OSRS, int=True), + cv.Optional(CONF_REFERENCE_VOLTAGE, default=1.2): cv.float_range(min=1.1, max=1.3), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(spi.spi_device_schema(cs_pin_required=True, default_data_rate="10MHz")) +) + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await spi.register_spi_device(var, config) + drdy = await cg.gpio_pin_expression(config[CONF_DRDY_PIN]) + if CONF_SYNC_RESET_PIN in config: + sync_reset = await cg.gpio_pin_expression(config[CONF_SYNC_RESET_PIN]) + cg.add(var.set_sync_reset_pin(sync_reset)) + clock_frequency = ALLOWED_CLOCK_FREQUENCIES[config[CONF_CLOCK_FREQUENCY]] + cg.add(var.set_clock_frequency(clock_frequency)) + osr = config[CONF_OSR] + cg.add(var.set_osr(osr)) + reference_voltage = config[CONF_REFERENCE_VOLTAGE] + cg.add(var.set_reference_voltage(reference_voltage)) + cg.add(var.set_drdy_pin(drdy)) + #cg.add(var.set_thread_model(ThreadModel.MULTI_ATOMICS)) + + diff --git a/components/ads131m08/__pycache__/__init__.cpython-312.pyc b/components/ads131m08/__pycache__/__init__.cpython-312.pyc index eb65fde..9c5705f 100644 Binary files a/components/ads131m08/__pycache__/__init__.cpython-312.pyc and b/components/ads131m08/__pycache__/__init__.cpython-312.pyc differ diff --git a/components/ads131m08/__pycache__/sensor.cpython-312.pyc b/components/ads131m08/__pycache__/sensor.cpython-312.pyc index 23e1cae..4995d0b 100644 Binary files a/components/ads131m08/__pycache__/sensor.cpython-312.pyc and b/components/ads131m08/__pycache__/sensor.cpython-312.pyc differ diff --git a/components/ads131m08/ads131m08.cpp b/components/ads131m08/ads131m08.cpp index 55853f9..8ff9d5c 100644 --- a/components/ads131m08/ads131m08.cpp +++ b/components/ads131m08/ads131m08.cpp @@ -1,851 +1,2043 @@ -#include "ads131m08.h" - -namespace esphome { -namespace ads131m08 { - -static const char *const TAG = "ads131m08"; - - -void ADS131M08Hub::setup() -{ - // Initialize SPI with correct settings for ADS131M08 - // SPI mode 1: CPOL=0, CPHA=1 - this->spi_setup(); - // not sure if next few lines are needed here or is it handled in spi_setup() - SPI.begin(); - SPI.setDataMode(SPI_MODE1); - SPI.setBitOrder(MSBFIRST); - SPI.setClockDivider(SPI_CLOCK_DIV8); // Adjust clock speed as needed - this->drdy_pin_->setup(); - this->drdy_pin_->pin_mode(esphome::gpio::FLAG_INPUT | esphome::gpio::FLAG_PULLUP); - this->drdy_pin_->attach_interrupt(&ADS131M08Hub::isr, this, gpio::INTERRUPT_FALLING_EDGE); - this->initialize_ads131m08(); -} - -void ADS131M08Hub::initialize_ads131m08() -{ - // Send RESET command - SPI.transfer(0x06); - delay(1); - - // Send UNLOCK command - SPI.transfer(0x55); - SPI.transfer(0x55); - - // Configure registers as needed (example: set gain, etc.) - // Write to MODE register for continuous conversion, disable internal reference (use external MAX6070AAUT12+T at 1.25V) - write_register(0x02, 0x0000); // MODE register, continuous mode, INTREF_EN = 0 - - // Wait for external reference to settle - delay(100); - - // Start conversions - SPI.transfer(0x08); // START command -} - -void ADS131M08Hub::write_register(uint8_t reg, uint16_t value) -{ - SPI.transfer(0x40 | reg); // WREG command - SPI.transfer(0x00); // Number of registers - 1 (0 for one register) - SPI.transfer((value >> 8) & 0xFF); - SPI.transfer(value & 0xFF); -} - -void IRAM_ATTR ADS131M08Hub::isr(ADS131M08Hub *arg) -{ - arg->txf_init(); // implementing datasheet recommended TXF init in ISR - arg->data_ready_ = true; -} - -void ADS131M08Hub::loop() -{ - if (this->data_ready_) { - this->data_ready_ = false; - this->read_data_(); - } -} - -// ********************** from datasheet ************************ -void ADS131M08Hub::txf_init() -{ - if (firstRead) { - // Clear the ADC's 2-deep FIFO on the first read - for (int i = 0; i sensors_[ch] != nullptr) { - int offset = ch * 3; - int32_t raw = (data_buffer_[offset] << 16) | (data_buffer_[offset + 1] << 8) | data_buffer_[offset + 2]; - if (raw & 0x800000) - raw |= 0xFF000000; // Sign extend - float v = (raw / 8388608.0) * this->reference_voltage_; // 2^23 = 8388608, Vref = 1.25V (MAX6070AAUT12+T) - this->sensors_[ch]->publish_state(v); - } - } -} -*/ -void ADS131M08Hub::read_data_() -{ - this->enable(); - // ADS131M08 Frame: 1 Status Word + 8 Channel Words + 1 CRC Word (24-bit words) - uint8_t frame[30]; // 10 words * 3 bytes - this->read_array(frame, 30); - this->disable(); - - for (int i = 0; i < 8; i++) { - if (this->sensors_[i] != nullptr) { - // Convert 24-bit two's complement to float (Simplified) - int32_t raw = (frame[3+(i*3)] << 16) | (frame[4+(i*3)] << 8) | frame[5+(i*3)]; - if (raw & 0x800000) - raw |= 0xFF000000; // Sign extend - this->sensors_[i]->publish_state(raw * (this->reference_voltage_ / 8388608.0)); // 2^23 = 8388608, Vref = 1.25V ( for MAX6070AAUT12+T ) - } - } -} - -// =============== from tpcorrea ================= -ADS131M08Hub::ADS131M08Hub() : csPin(0), drdyPin(0), clkPin(0), misoPin(0), mosiPin(0), resetPin(0) -{ - for( uint16_t i = 0U; i < 8; i++){ - fullScale.ch[i].f = 1.2; // +-1.2V - pgaGain[i] = ADS131M08_PgaGain::PGA_1; - resultFloat.ch[i].f = 0.0; - resultRaw.ch[i].u[0] = 0U; - resultRaw.ch[i].u[1] = 0U; - } - -} - -uint8_t ADS131M08Hub::writeRegister(uint8_t address, uint16_t value) -{ - uint16_t res; - uint8_t addressRcv; - uint8_t bytesRcv; - uint16_t cmd = 0; - - digitalWrite(csPin, LOW); - delayMicroseconds(1); - - cmd = (CMD_WRITE_REG) | (address << 7) | 0; - - //res = spi.transfer16(cmd); - spi.transfer16(cmd); - spi.transfer(0x00); - - spi.transfer16(value); - spi.transfer(0x00); - - for(int i = 0; i < 8; i++) - { - spi.transfer16(0x0000); - spi.transfer(0x00); - } - - res = spi.transfer16(0x0000); - spi.transfer(0x00); - - for(int i = 0; i < 9; i++) - { - spi.transfer16(0x0000); - spi.transfer(0x00); - } - - delayMicroseconds(1); - digitalWrite(csPin, HIGH); - - addressRcv = (res & REGMASK_CMD_READ_REG_ADDRESS) >> 7; - bytesRcv = (res & REGMASK_CMD_READ_REG_BYTES); - - if (addressRcv == address) - { - return bytesRcv + 1; - } - return 0; -} - -void ADS131M08Hub::writeRegisterMasked(uint8_t address, uint16_t value, uint16_t mask) -{ - // Escribe un valor en el registro, aplicando la mascara para tocar unicamente los bits necesarios. - // No realiza el corrimiento de bits (shift), hay que pasarle ya el valor corrido a la posicion correcta - - // Leo el contenido actual del registro - uint16_t register_contents = readRegister(address); - - // Cambio bit aa bit la mascara (queda 1 en los bits que no hay que tocar y 0 en los bits a modificar) - // Se realiza un AND co el contenido actual del registro. Quedan "0" en la parte a modificar - register_contents = register_contents & ~mask; - - // se realiza un OR con el valor a cargar en el registro. Ojo, valor debe estar en el posicion (shitf) correcta - register_contents = register_contents | value; - - // Escribo nuevamente el registro - writeRegister(address, register_contents); -} - -uint16_t ADS131M08Hub::readRegister(uint8_t address) -{ - uint16_t cmd; - uint16_t data; - - cmd = CMD_READ_REG | (address << 7 | 0); - - digitalWrite(csPin, LOW); - delayMicroseconds(1); - - //data = spi.transfer16(cmd); - spi.transfer16(cmd); - spi.transfer(0x00); - - for(int i = 0; i < 9; i++) - { - spi.transfer16(0x0000); - spi.transfer(0x00); - } - - data = spi.transfer16(0x0000); - spi.transfer(0x00); - - for(int i = 0; i < 9; i++) - { - spi.transfer16(0x0000); - spi.transfer(0x00); - } - - delayMicroseconds(1); - digitalWrite(csPin, HIGH); - return data; -} - -void ADS131M08Hub::begin(uint8_t clk_pin, uint8_t miso_pin, uint8_t mosi_pin, uint8_t cs_pin, uint8_t drdy_pin, uint8_t reset_pin) -{ - // Set pins up - csPin = cs_pin; - drdyPin = drdy_pin; - clkPin = clk_pin; - misoPin = miso_pin; - mosiPin = mosi_pin; - resetPin = reset_pin; - - spi = SPIClass(mosi_pin, miso_pin, clk_pin, cs_pin); - spi.begin(); - spi.beginTransaction(settings); - // Configure chip select as an output - pinMode(csPin, OUTPUT); - pinMode(resetPin, OUTPUT); - // Configure DRDY as an input - pinMode(drdyPin, INPUT); -} - -int8_t ADS131M08Hub::isDataReadySoft(byte channel) -{ - if (channel == 0) - { - return (readRegister(REG_STATUS) & REGMASK_STATUS_DRDY0); - } - else if (channel == 1) - { - return (readRegister(REG_STATUS) & REGMASK_STATUS_DRDY1); - } - else if (channel == 2) - { - return (readRegister(REG_STATUS) & REGMASK_STATUS_DRDY2); - } - else if (channel == 3) - { - return (readRegister(REG_STATUS) & REGMASK_STATUS_DRDY3); - } - else if (channel == 4) - { - return (readRegister(REG_STATUS) & REGMASK_STATUS_DRDY4); - } - else if (channel == 5) - { - return (readRegister(REG_STATUS) & REGMASK_STATUS_DRDY5); - } - else if (channel == 6) - { - return (readRegister(REG_STATUS) & REGMASK_STATUS_DRDY6); - } - else if (channel == 7) - { - return (readRegister(REG_STATUS) & REGMASK_STATUS_DRDY7); - } - else - { - return -1; - } -} - -bool ADS131M08Hub::isResetStatus(void) -{ - return (readRegister(REG_STATUS) & REGMASK_STATUS_RESET); -} - -bool ADS131M08Hub::isLockSPI(void) -{ - return (readRegister(REG_STATUS) & REGMASK_STATUS_LOCK); -} - -bool ADS131M08Hub::setDrdyFormat(uint8_t drdyFormat) -{ - if (drdyFormat > 1) - { - return false; - } - else - { - writeRegisterMasked(REG_MODE, drdyFormat, REGMASK_MODE_DRDY_FMT); - return true; - } -} - -bool ADS131M08Hub::setDrdyStateWhenUnavailable(uint8_t drdyState) -{ - if (drdyState > 1) - { - return false; - } - else - { - writeRegisterMasked(REG_MODE, drdyState < 1, REGMASK_MODE_DRDY_HiZ); - return true; - } -} - -bool ADS131M08Hub::setPowerMode(uint8_t powerMode) -{ - if (powerMode > 3) - { - return false; - } - else - { - writeRegisterMasked(REG_CLOCK, powerMode, REGMASK_CLOCK_PWR); - return true; - } -} - -bool ADS131M08Hub::setOsr(uint16_t osr) -{ - if (osr > 7) - { - return false; - } - else - { - writeRegisterMasked(REG_CLOCK, osr << 2 , REGMASK_CLOCK_OSR); - return true; - } -} - -void ADS131M08Hub::setFullScale(uint8_t channel, float scale) -{ - if (channel > 7) { - return; - } - - this->fullScale.ch[channel].f = scale; - -} - -float ADS131M08Hub::getFullScale(uint8_t channel) -{ - if (channel > 7) { - return 0.0; - } - - return this->fullScale.ch[channel].f; - -} - -void ADS131M08Hub::reset() -{ - digitalWrite(this->resetPin, LOW); - delay(10); - digitalWrite(this->resetPin, HIGH); -} - -bool ADS131M08Hub::setChannelEnable(uint8_t channel, uint16_t enable) -{ - if (channel > 7) - { - return false; - } - if (channel == 0) - { - writeRegisterMasked(REG_CLOCK, enable << 8, REGMASK_CLOCK_CH0_EN); - return true; - } - else if (channel == 1) - { - writeRegisterMasked(REG_CLOCK, enable << 9, REGMASK_CLOCK_CH1_EN); - return true; - } - else if (channel == 2) - { - writeRegisterMasked(REG_CLOCK, enable << 10, REGMASK_CLOCK_CH2_EN); - return true; - } - else if (channel == 3) - { - writeRegisterMasked(REG_CLOCK, enable << 11, REGMASK_CLOCK_CH3_EN); - return true; - } - else if (channel == 4) - { - writeRegisterMasked(REG_CLOCK, enable << 11, REGMASK_CLOCK_CH4_EN); - return true; - } - else if (channel == 5) - { - writeRegisterMasked(REG_CLOCK, enable << 11, REGMASK_CLOCK_CH5_EN); - return true; - } - else if (channel == 6) - { - writeRegisterMasked(REG_CLOCK, enable << 11, REGMASK_CLOCK_CH6_EN); - return true; - } - else if (channel == 7) - { - writeRegisterMasked(REG_CLOCK, enable << 11, REGMASK_CLOCK_CH7_EN); - return true; - } - return false; -} - -bool ADS131M08Hub::setChannelPGA(uint8_t channel, ADS131M08_PgaGain pga) -{ uint16_t pgaCode = (uint16_t) pga; - - if (channel > 7) - { - return false; - } - if (channel == 0) - { - writeRegisterMasked(REG_GAIN1, pgaCode, REGMASK_GAIN_PGAGAIN0); - this->pgaGain[0] = pga; - return true; - } - else if (channel == 1) - { - writeRegisterMasked(REG_GAIN1, pgaCode << 4, REGMASK_GAIN_PGAGAIN1); - this->pgaGain[1] = pga; - return true; - } - else if (channel == 2) - { - writeRegisterMasked(REG_GAIN1, pgaCode << 8, REGMASK_GAIN_PGAGAIN2); - this->pgaGain[2] = pga; - return true; - } - else if (channel == 3) - { - writeRegisterMasked(REG_GAIN1, pgaCode << 12, REGMASK_GAIN_PGAGAIN3); - this->pgaGain[3] = pga; - return true; - } - if (channel == 4) - { - writeRegisterMasked(REG_GAIN2, pgaCode, REGMASK_GAIN_PGAGAIN4); - this->pgaGain[4] = pga; - return true; - } - else if (channel == 5) - { - writeRegisterMasked(REG_GAIN2, pgaCode << 4, REGMASK_GAIN_PGAGAIN5); - this->pgaGain[5] = pga; - return true; - } - else if (channel == 6) - { - writeRegisterMasked(REG_GAIN2, pgaCode << 8, REGMASK_GAIN_PGAGAIN6); - this->pgaGain[6] = pga; - return true; - } - else if (channel == 7) - { - writeRegisterMasked(REG_GAIN2, pgaCode << 12, REGMASK_GAIN_PGAGAIN7); - this->pgaGain[7] = pga; - return true; - } - return false; -} - -ADS131M08_PgaGain ADS131M08Hub::getChannelPGA(uint8_t channel) -{ - if(channel > 7) - { - return ADS131M08_PgaGain::PGA_INVALID; - } - return this->pgaGain[channel]; -} - -void ADS131M08Hub::setGlobalChop(uint16_t global_chop) -{ - writeRegisterMasked(REG_CFG, global_chop << 8, REGMASK_CFG_GC_EN); -} - -void ADS131M08Hub::setGlobalChopDelay(uint16_t delay) -{ - writeRegisterMasked(REG_CFG, delay << 9, REGMASK_CFG_GC_DLY); -} - -bool ADS131M08Hub::setInputChannelSelection(uint8_t channel, uint8_t input) -{ - if (channel > 3) - { - return false; - } - if (channel == 0) - { - writeRegisterMasked(REG_CH0_CFG, input, REGMASK_CHX_CFG_MUX); - return true; - } - else if (channel == 1) - { - writeRegisterMasked(REG_CH1_CFG, input, REGMASK_CHX_CFG_MUX); - return true; - } - else if (channel == 2) - { - writeRegisterMasked(REG_CH2_CFG, input, REGMASK_CHX_CFG_MUX); - return true; - } - else if (channel == 3) - { - writeRegisterMasked(REG_CH3_CFG, input, REGMASK_CHX_CFG_MUX); - return true; - } - else if (channel == 4) - { - writeRegisterMasked(REG_CH4_CFG, input, REGMASK_CHX_CFG_MUX); - return true; - } - else if (channel == 5) - { - writeRegisterMasked(REG_CH5_CFG, input, REGMASK_CHX_CFG_MUX); - return true; - } - else if (channel == 6) - { - writeRegisterMasked(REG_CH6_CFG, input, REGMASK_CHX_CFG_MUX); - return true; - } - else if (channel == 7) - { - writeRegisterMasked(REG_CH7_CFG, input, REGMASK_CHX_CFG_MUX); - return true; - } - return false; -} - -bool ADS131M08Hub::setChannelOffsetCalibration(uint8_t channel, int32_t offset) -{ - - uint16_t MSB = offset >> 8; - uint8_t LSB = offset; - - if (channel > 7) - { - return false; - } - if (channel == 0) - { - writeRegisterMasked(REG_CH0_OCAL_MSB, MSB, 0xFFFF); - writeRegisterMasked(REG_CH0_OCAL_LSB, LSB << 8, REGMASK_CHX_OCAL0_LSB); - return true; - } - else if (channel == 1) - { - writeRegisterMasked(REG_CH1_OCAL_MSB, MSB, 0xFFFF); - writeRegisterMasked(REG_CH1_OCAL_LSB, LSB << 8, REGMASK_CHX_OCAL0_LSB); - return true; - } - else if (channel == 2) - { - writeRegisterMasked(REG_CH2_OCAL_MSB, MSB, 0xFFFF); - writeRegisterMasked(REG_CH2_OCAL_LSB, LSB << 8, REGMASK_CHX_OCAL0_LSB); - return true; - } - else if (channel == 3) - { - writeRegisterMasked(REG_CH3_OCAL_MSB, MSB, 0xFFFF); - writeRegisterMasked(REG_CH3_OCAL_LSB, LSB << 8 , REGMASK_CHX_OCAL0_LSB); - return true; - } - else if (channel == 4) - { - writeRegisterMasked(REG_CH4_OCAL_MSB, MSB, 0xFFFF); - writeRegisterMasked(REG_CH4_OCAL_LSB, LSB << 8 , REGMASK_CHX_OCAL0_LSB); - return true; - } - else if (channel == 5) - { - writeRegisterMasked(REG_CH5_OCAL_MSB, MSB, 0xFFFF); - writeRegisterMasked(REG_CH5_OCAL_LSB, LSB << 8 , REGMASK_CHX_OCAL0_LSB); - return true; - } - else if (channel == 6) - { - writeRegisterMasked(REG_CH6_OCAL_MSB, MSB, 0xFFFF); - writeRegisterMasked(REG_CH6_OCAL_LSB, LSB << 8 , REGMASK_CHX_OCAL0_LSB); - return true; - } - else if (channel == 7) - { - writeRegisterMasked(REG_CH7_OCAL_MSB, MSB, 0xFFFF); - writeRegisterMasked(REG_CH7_OCAL_LSB, LSB << 8 , REGMASK_CHX_OCAL0_LSB); - return true; - } - return false; -} - -bool ADS131M08Hub::setChannelGainCalibration(uint8_t channel, uint32_t gain) -{ - - uint16_t MSB = gain >> 8; - uint8_t LSB = gain; - - if (channel > 7) - { - return false; - } - if (channel == 0) - { - writeRegisterMasked(REG_CH0_GCAL_MSB, MSB, 0xFFFF); - writeRegisterMasked(REG_CH0_GCAL_LSB, LSB << 8, REGMASK_CHX_GCAL0_LSB); - return true; - } - else if (channel == 1) - { - writeRegisterMasked(REG_CH1_GCAL_MSB, MSB, 0xFFFF); - writeRegisterMasked(REG_CH1_GCAL_LSB, LSB << 8, REGMASK_CHX_GCAL0_LSB); - return true; - } - else if (channel == 2) - { - writeRegisterMasked(REG_CH2_GCAL_MSB, MSB, 0xFFFF); - writeRegisterMasked(REG_CH2_GCAL_LSB, LSB << 8, REGMASK_CHX_GCAL0_LSB); - return true; - } - else if (channel == 3) - { - writeRegisterMasked(REG_CH3_GCAL_MSB, MSB, 0xFFFF); - writeRegisterMasked(REG_CH3_GCAL_LSB, LSB << 8, REGMASK_CHX_GCAL0_LSB); - return true; - } - else if (channel == 4) - { - writeRegisterMasked(REG_CH4_GCAL_MSB, MSB, 0xFFFF); - writeRegisterMasked(REG_CH4_GCAL_LSB, LSB << 8, REGMASK_CHX_GCAL0_LSB); - return true; - } - else if (channel == 5) - { - writeRegisterMasked(REG_CH5_GCAL_MSB, MSB, 0xFFFF); - writeRegisterMasked(REG_CH5_GCAL_LSB, LSB << 8, REGMASK_CHX_GCAL0_LSB); - return true; - } - else if (channel == 6) - { - writeRegisterMasked(REG_CH6_GCAL_MSB, MSB, 0xFFFF); - writeRegisterMasked(REG_CH6_GCAL_LSB, LSB << 8, REGMASK_CHX_GCAL0_LSB); - return true; - } - else if (channel == 7) - { - writeRegisterMasked(REG_CH7_GCAL_MSB, MSB, 0xFFFF); - writeRegisterMasked(REG_CH7_GCAL_LSB, LSB << 8, REGMASK_CHX_GCAL0_LSB); - return true; - } - return false; -} - -bool ADS131M08Hub::isDataReady() -{ - if (digitalRead(drdyPin) == HIGH) - { - return false; - } - return true; -} - -uint16_t ADS131M08Hub::getId() -{ - return readRegister(REG_ID); -} - -uint16_t ADS131M08Hub::getModeReg() -{ - return readRegister(REG_MODE); -} - -uint16_t ADS131M08Hub::getClockReg() -{ - return readRegister(REG_CLOCK); -} - -uint16_t ADS131M08Hub::getCfgReg() -{ - return readRegister(REG_CFG); -} - -AdcOutput ADS131M08Hub::readAdcRaw(void) -{ - uint8_t x = 0; - uint8_t x2 = 0; - uint8_t x3 = 0; - int32_t aux; - AdcOutput res; - - digitalWrite(csPin, LOW); - delayMicroseconds(1); - - x = spi.transfer(0x00); - x2 = spi.transfer(0x00); - spi.transfer(0x00); - - this->resultRaw.status = ((x << 8) | x2); - - for(int i = 0; i<8; i++) - { - x = spi.transfer(0x00); - x2 = spi.transfer(0x00); - x3 = spi.transfer(0x00); - - aux = (((x << 16) | (x2 << 8) | x3) & 0x00FFFFFF); - if (aux > 0x7FFFFF) - { - this->resultRaw.ch[i].i = ((~(aux)&0x00FFFFFF) + 1) * -1; - } - else - { - this->resultRaw.ch[i].i = aux; - } - } - - delayMicroseconds(1); - digitalWrite(csPin, HIGH); - - return this->resultRaw; -} - -float ADS131M08Hub::scaleResult(uint8_t num) -{ - if( num >= 8) { - return 0.0; - } - - return this->resultFloat.ch[num].f = (float)(this->resultRaw.ch[num].i * rawToVolts * this->fullScale.ch[num].f); -} - -AdcOutput ADS131M08Hub::scaleResult(void) -{ - // update status - this->resultFloat.status = this->resultRaw.status; - // Scale all channels - for(int i = 0; i<8; i++) - { - this->scaleResult(i); - } - - return this->resultFloat; -} - -AdcOutput ADS131M08Hub::readAdcFloat(void) -{ - this->readAdcRaw(); - return this->scaleResult(); -} -// end of from tpcorrea -void ADS131M08Hub::dump_config() -{ - ESP_LOGCONFIG(TAG, "ADS131M08:"); - LOG_PIN(" CS Pin:", this->cs_); - LOG_PIN(" DRDY Pin:", this->drdy_pin_); - ESP_LOGCONFIG(TAG, " Reference Voltage: %.2fV", this->reference_voltage_); -} - -} // namespace ads131m08 -} // namespace esphome \ No newline at end of file +#include "ads131m08.h" +//#include +//#include +//#include "esphome/components/spi/spi.h" +#include "esphome/core/log.h" +#include +#include +#include +#include "esp_system.h" +#include "spi_flash_mmap.h" + +//using namespace esphome::spi; + +namespace esphome { +namespace ads131m08 { + +static const char *const TAG = "ads131m08"; + +ADS131M08Hub::ADS131M08Hub() + : base_frame(40, 0) +{ + +} +void ADS131M08Hub::setup() +{ + for(int i = 0; i < ADC_CHANNELS; i++) { + //last_publish_time_[i] = 0; + num_samples_[i] = 0; + sample_sum_[i] = 0; + sample_squared_sum_[i] = 0; + } + // 1. Create a binary semaphore + this->data_ready_semhandle = xSemaphoreCreateBinary(); + sync_reset_pin_->setup(); + sync_reset_pin_->pin_mode(esphome::gpio::FLAG_OUTPUT); // Set SYNC/RESET pin as output + sync_reset_pin_->digital_write(true); + // setup DRDY pin interrupt + this->drdy_pin_->setup(); + this->drdy_pin_->pin_mode(esphome::gpio::FLAG_INPUT | esphome::gpio::FLAG_PULLUP); // external pull-up + this->spi_setup(); + // Check if setup failed + if (this->is_failed()) { + ESP_LOGE(TAG, "SPI setup failed!"); + this->mark_failed(LOG_STR("SPI setup failed." )); + return; + } + // Send RESET command + bool reset_ok = adc_reset_retry(); + //ESP_LOGW(TAG, "adc word length: %d", adc_word_length_); + if(!reset_ok) { + ESP_LOGE(TAG, "ADC reset failed!"); + this->mark_failed(LOG_STR("Reset failed." )); + this->adc_init_ = 0; + return; + } + if(!this->adc_initialize(32)) { + ESP_LOGE(TAG, "ADC reset failed!"); + this->mark_failed(LOG_STR("Initialisation failed." )); + this->adc_init_ = 0; + return; + } + set_reg_osr(); + if(!adc_lock(true)) { + ESP_LOGE(TAG, "ADC lock failed!"); + this->mark_failed(LOG_STR("ADC lock failed." )); + this->adc_init_ = 0; + return; + } + if(!adc_lock(false)) { + ESP_LOGE(TAG, "ADC unlock failed!"); + this->mark_failed(LOG_STR("ADC unlock failed." )); + this->adc_init_ = 0; + return; + } + this->drdy_pin_->attach_interrupt(&ADS131M08Hub::isr_handler, this, gpio::INTERRUPT_FALLING_EDGE); + //cs_ctr_=1000; + //SPIDevice::enable(); // leave CS low + +} + +void ADS131M08Hub::dump_config() +{ + ESP_LOGCONFIG(TAG, "ADS131M08:"); + LOG_PIN(" CS Pin: ", this->cs_); + LOG_PIN(" DRDY Pin: ", this->drdy_pin_); + if(this->sync_reset_pin_ != nullptr) { + LOG_PIN(" SYNC/RESET Pin: ", this->sync_reset_pin_); + } + if (this->data_rate_ < 1000000) { + ESP_LOGCONFIG(TAG, " Data rate: %" PRId32 "kHz", this->data_rate_ / 1000); + } else { + ESP_LOGCONFIG(TAG, " Data rate: %" PRId32 "MHz", this->data_rate_ / 1000000); + } + if (this->clock_frequency_ < 1000000) { + ESP_LOGCONFIG(TAG, " Clock frequency: %.3fkHz", this->clock_frequency_ / 1000); + } else { + ESP_LOGCONFIG(TAG, " Clock frequency: %.3fMHz", this->clock_frequency_ / 1000000); + } + uint16_t osr = 1 << (7 + this->osr_); + if (osr == 16384) { + osr = 16256; + } + ESP_LOGCONFIG(TAG, " Oversampling ratio: %" PRId32, osr); + ESP_LOGCONFIG(TAG, " Reference Voltage: %.2fV", this->reference_voltage_); + ESP_LOGCONFIG(TAG, " SPI Mode: %d", this->mode_); + ESP_LOGCONFIG(TAG, " Bit Order: %s", this->bit_order_ == 1 ? "msb_first" : this->bit_order_ == 0 ? "lsb_first" : "unknown"); + +} + +void ADS131M08Hub::set_channel_gain(uint8_t channel, ADS131M08_PGA_GAIN gain) +{ + if(channel < ADC_CHANNELS) { + set_channel_pga(channel, gain); + } +} + +uint8_t ADS131M08Hub::update_adc_word_length() +{ + int word_nbytes = this->adc_word_length_ >> 3; + spiframe frame(2 * word_nbytes, 0); // prepare with CMD_NULL; always cater for crc + write_array(frame); // write CMD_NULL to ADC to retrieve status + read_array(frame); + uint16_t status = frame[0] << 8 | frame[1]; + //print_command_response_to_string(CMD_NULL, frame).c_str(); + switch(status & MASK_STATUS_WLENGTH) { + case WLENGTH_16_BITS: + this->adc_word_length_ = 16; + this->conversion_factor_ = this->reference_voltage_/32768.0; // TODO: must double check this with a test + break; + case WLENGTH_24_BITS: + this->adc_word_length_ = 24; + this->conversion_factor_ = this->reference_voltage_/8388608.0; + break; + default: + this->adc_word_length_ = 32; + this->conversion_factor_ = this->reference_voltage_/8388608.0; + break; + } + return this->adc_word_length_; +} + +bool ADS131M08Hub::adc_initialize(uint8_t word_length) +{ + this->adc_init_++; + update_adc_word_length(); + + if (!adc_register_write(REG_CLOCK, reg_clock_allch_off)) { + ESP_LOGE(TAG, "CLOCK register write / read to turn off all channels failed"); + return false; + } + ESP_LOGV(TAG, "Turned off all channels so short frames can be written during config"); + if(!adc_register_write(REG_MODE, MODE_MASK_RESET_HAPPENED & reg_mode_cfg24)) { + ESP_LOGE(TAG, "MODE register write / read to set word size to 24bits failed"); + return false; + } + //update_adc_word_length(); + ESP_LOGV(TAG, "Written MODE register; Cleared the RESET flag, made DRDY active low pulse"); + if(!adc_register_write(REG_THRSHLD_LSB, 0x09)) { + ESP_LOGE(TAG, "THRSHLD_LSB register write / read to set DBLOCK filter failed"); + return false; + } + ESP_LOGV(TAG, "Written THRSHLD_LSB register; Set DCBLOCK filter to have a corner frequency of 622 mHz"); + if(!adc_set_word_length(word_length)) + return false; + // we leave should channels off, as the individual sensors should turn it on + if(!adc_register_write(REG_CLOCK, reg_clock_allch_off)) { + ESP_LOGE(TAG, "CLOCK register write / read to turn-off all channels failed"); + return false; + } + + //ESP_LOGV(TAG, "Turned on all ADC channels; Re-wrote defaults for other bits in CLOCK register"); + if(!drdy_pin_->digital_read()) { + ESP_LOGE(TAG, "DRDY pin is low after initialization!"); + return false; + } + this->adc_init_--; + return true; +} + +bool ADS131M08Hub::adc_set_word_length(uint8_t word_length) +{ + uint16_t setting; + switch(word_length) { + case 32: + setting = reg_mode_cfg32; + break; + case 16: + setting = reg_mode_cfg16; + break; + default: + setting = reg_mode_cfg24; + break; + } + if(!adc_register_write_masked(REG_MODE, setting, reg_mode_cfg_mask, __LINE__)) { + ESP_LOGE(TAG, "MODE register write / read to set word size to %ubits failed", word_length); + return false; + } + ESP_LOGV(TAG, "Written MODE register; Made ADC word size %u bits. Re-wrote other set bits in MODE register", word_length); + //update_adc_word_length(); + return true; +} + +// Implementing nested CS enable/disable handling in ADS131M08Hub to allow multiple transfers in a row without toggling CS between them, which is required for proper communication with the ADC according to the datasheet recommendations. +void ADS131M08Hub::enable(const char *txt) +{ + cs_ctr_++; // Increment counter on each enable call + //esph_log_i(TAG, "cs_ctr_e: %s %d", txt, cs_ctr_.load()); + if(cs_ctr_ == 1) { // Only set CS low on the first enable call + SPIDevice::enable(); // Call the base class enable to set CS low + if(txt != nullptr) + esph_log_i(TAG, "enable: %s", txt); + } +} + +void ADS131M08Hub::disable(const char *txt) +{ + //esph_log_i(TAG, "cs_ctr_d: %s %d", txt, cs_ctr.load()); + cs_ctr_--; // Decrement counter on each disable call + if(cs_ctr_ == 0) { // Only set CS high when counter returns to 0 + SPIDevice::disable(); // Call the base class disable to set CS high + if(txt != nullptr) + esph_log_i(TAG, "disable: %s", txt); + } + else if (cs_ctr_ < 0) { // Sanity check to prevent counter from going negative + cs_ctr_ = 0; + } +} + +// ISR function to handle interrupt from drdy pin +// MUST be fast and non-blocking +void ADS131M08Hub::isr_handler(ADS131M08Hub *arg) +{ + { + InterruptLock lock; + if(arg != nullptr) { + arg->isr_ctr_++; + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + xSemaphoreGiveFromISR(arg->data_ready_semhandle, &xHigherPriorityTaskWoken); + if (xHigherPriorityTaskWoken == pdTRUE) { + portYIELD_FROM_ISR(); // Switch to the waiting task immediately + } + } + } +} + +void ADS131M08Hub::loop() +{ + // Check the semaphore (0 timeout means non-blocking) + if (xSemaphoreTake(data_ready_semhandle, 0) == pdTRUE) { + if(this->adc_init_ == 0) { + uint32_t num_samples = 0; + uint32_t crc_errors = 0; + { + CHIP_SELECTx + // Perform the read here safely outside of ISR + uint64_t start = micros(); + //this->txf_init(); // implementing datasheet recommended TXF init in ISR + this->adc_sync(); + if(rms_calc_req_) { + std::pair result = read_multi(); + num_samples = result.first; + crc_errors = result.second; + uint64_t end = micros(); + if(this->sensors_ac[SAMPLE_TIME_SENSOR] != nullptr) + this->sensors_ac[SAMPLE_TIME_SENSOR]->publish_state(static_cast(end - start)/1000.0); + if(this->sensors_ac[MAX_SAMPLES_SENSOR] != nullptr) + this->sensors_ac[MAX_SAMPLES_SENSOR]->publish_state(static_cast(num_samples)); + if(this->sensors_ac[CRC_ERRORS_SENSOR] != nullptr) + this->sensors_ac[CRC_ERRORS_SENSOR]->publish_state(static_cast(crc_errors)); + uint16_t ctr = isr_ctr_; + ESP_LOGI(TAG, "WL: %u isr_ctr_: %u", this->adc_word_length_, ctr); } + else { + read_single(); + } + isr_ctr_ = 0; + } + if (crc_errors > 30) { + ESP_LOGW(TAG, "High CRC error rate."); + adc_set_word_length(this->adc_word_length_); // for some reason the adc occasionally reverts to 24bits; this will reset the word length to what it should be + //update_adc_word_length(); + spiframe recoverframe(40, 0); + read_array(recoverframe); + ESP_LOGI(TAG, "WL: %u Frame: %s", this->adc_word_length_, frame_to_string(recoverframe).c_str()); + } + //ESP_LOGW(TAG, "%llu ms (%llu us), max samples: %u", (end - start)/1000, (end - start), num_samples); + } + } +} +/// @brief Set 16 bit frameword +/// @param frame +/// @param w_index +/// @param data uint16_t denotes command or CRC +/// @return +bool ADS131M08Hub::set_frame_word(spiframe& frame, int w_index, uint16_t data) +{ + size_t word_nbytes = adc_word_length_ >> 3; + size_t frame_words = frame.size() / word_nbytes; + int b_index = w_index * word_nbytes; + //ESP_LOGD(TAG, "b_index: %d, word_nbytes: %d, frame words: %d, w_index: %d, frame size: %d", b_index, word_nbytes, frame_words, w_index, frame.size()); + if(w_index >= frame_words) { + if(w_index > MAX_FRAME_SIZE) { + ESP_LOGE(TAG, "Word index of %d out of bounds", w_index); + //esp_crosscore_int_send_print_backtrace(xPortGetCoreID()); + return false; // invalid - something has gone wrong + } + //int oldsize = frame.size(); + frame.resize(b_index + word_nbytes); + //ESP_LOGD(TAG, "Resized frame from %d, to %d.", oldsize, frame.size()); + } + switch(adc_word_length_) { + // deliberate no breaks after first two cases + case 32: + frame[b_index + 3] = 0; + case 24: + frame[b_index + 2] = 0; + case 16: + frame[b_index + 1] = data & 0xFF; + frame[b_index] = (data >> 8) & 0xFF; + break; + default: + ESP_LOGW(TAG, "Invalid adc word length of %d", adc_word_length_); + break; + } + return true; +} + +bool ADS131M08Hub::set_frame_word(spiframe& frame, int w_index, uint32_t data) +{ + size_t word_nbytes = adc_word_length_ >> 3; + size_t frame_words = frame.size() / word_nbytes; + int b_index = w_index * word_nbytes; + if(w_index >= frame_words) { + if(w_index > MAX_FRAME_SIZE) { + ESP_LOGE(TAG, "Word index of %d out of bounds", w_index); + //esp_crosscore_int_send_print_backtrace(xPortGetCoreID()); + return false; // invalid - something has gone wrong + } + frame.resize(b_index + word_nbytes); + } + switch(adc_word_length_) { + // deliberate no breaks after first two cases + case 32: + frame[b_index++] = (data >> 24) & 0xFF; + case 24: + frame[b_index++] = (data >> 16) & 0xFF; + case 16: + frame[b_index++] = (data >> 8) & 0xFF; + frame[b_index] = data & 0xFF; + break; + default: + ESP_LOGW(TAG, "Invalid adc word length of %d", adc_word_length_); + break; + } + return true; +} + +// index(i) = zero-reference, returns i'th word, LSB aligned +uint32_t ADS131M08Hub::get_unsigned_frame_word(const spiframe& frame, int w_index, bool force_16bits) +{ + uint32_t result = 0; + size_t word_nbytes = adc_word_length_ >> 3; + int b_index = w_index * word_nbytes; + if(b_index > (frame.size() - word_nbytes)) { + //ESP_LOGE(TAG, "Word index of %d out of bounds. b_index: %d, frame_size: %d, Caller: %s Line: %d", w_index, b_index, frame.size(), caller.c_str(), line); + //esp_crosscore_int_send_print_backtrace(xPortGetCoreID()); + return result; // illegal + } + switch(force_16bits ? 16 : adc_word_length_) { + // deliberate no breaks after first two cases + case 32: + result += frame[b_index++] << 24; + case 24: + result += frame[b_index++] << 16; + case 16: + result += frame[b_index++] << 8; + result += frame[b_index]; + break; + default: + ESP_LOGW(TAG, "Invalid adc word length of %d", adc_word_length_); + break; + } + return result; +} + +int32_t ADS131M08Hub::get_sign_ext_frame_word(const spiframe& frame, int w_index) +{ + int32_t result = 0; + size_t word_nbytes = adc_word_length_ >> 3; + int b_index = w_index * word_nbytes; + if(b_index > (frame.size() - word_nbytes)) { + ESP_LOGW(TAG, "Word index of %d out of bounds", w_index); + return result; // invalid + } + uint32_t raw = 0; + switch(adc_word_length_) { + case 32: + raw = (frame[b_index] << 24) | (frame[b_index + 1] << 16) | (frame[b_index + 2]) << 8 | frame[b_index + 3]; + result = static_cast(raw); + break; + case 24: + raw = (frame[b_index] << 16) | (frame[b_index + 1] << 8) | frame[b_index + 2]; + if (raw & 0x800000) + result = static_cast(raw | 0xFF000000); // Sign extend + else + result = static_cast(raw); + break; + case 16: + raw = ((frame[b_index] << 8) | frame[b_index + 1]); + result = static_cast(raw); + break; + default: + ESP_LOGW(TAG, "Invalid adc word length of %d", adc_word_length_); + break; + } + return result; + } + +//{ +// // Read STATUS register to check if data is ready for the specified channel +// // STATUS register is the first word in the SPI frame, so we can read it directly +// uint16_t status; +// this->enable(); +// this->transfer_byte(0x00); // Send dummy byte to read STATUS register +// status = this->transfer_byte(0x00); // Read upper byte of STATUS +// status <<= 8; +// status |= this->transfer_byte(0x00); // Read lower byte of STATUS +// this->disable(); +// // Check the corresponding bit in the STATUS register for the specified channel +// // Assuming each channel corresponds to a specific bit in the STATUS register, e.g. bit 0 for channel 0, bit 1 for channel 1, etc. +// return (status & (1 << channel)) != 0; // Return true if data is ready for the specified channel +//} + +void ADS131M08Hub::adc_hard_reset() +{ + float one_cycle = 1000000 / this->clock_frequency_; // one cycle duration in micro seconds + uint32_t reset_pulse_duration = static_cast(1.05 * 2048 * one_cycle); // 2048 clock cycles converted to microseconds, multiplied by 1.05 to ensure we meet the minimum pulse duration requirement + if(reset_pulse_duration < 1) { + reset_pulse_duration = 1; // set it to 1 microsecond + } + sync_reset_pin_->digital_write(false); // Pull SYNC/RESET pin low to reset the ADC + delay_microseconds_safe(reset_pulse_duration); // Hold reset for the calculated duration + sync_reset_pin_->digital_write(true); // Set SYNC/RESET pin high to allow normal operation +} +void ADS131M08Hub::adc_sync() +{ + float one_cycle = 1000000 / this->clock_frequency_; // one cycle duration in micro seconds + uint32_t sync_pulse_duration = static_cast(std::round(50 * one_cycle)); // minimum is 1/clock freq, max = 2047/clock freq + if(sync_pulse_duration < 1) { + sync_pulse_duration = 1; // set it to 1 microsecond + } + sync_reset_pin_->digital_write(false); // Pull SYNC/RESET pin low to sync the ADC + delay_microseconds_safe(sync_pulse_duration); // Hold sync for the calculated duration + sync_reset_pin_->digital_write(true); // Set SYNC/RESET pin high to allow normal operation +} + +// ********************** from datasheet ************************ +void ADS131M08Hub::txf_init() +{ + CHIP_SELECT + if (firstRead) { + // Clear the ADC's 2-deep FIFO on the first read + for(int i = 0; i < numFrameWords * adc_word_length_; i++) { + this->write_byte(0); + } + for(int i = 0; i < numFrameWords; i++) { + this->read_byte(); // should perhaps read dwords instead of bytes, but we just want to clear the FIFO so it doesn't matter much? + } + firstRead = false; // Clear the flag + // we will do this later, after sorting out basic communication + // DMA.enable(); // Let the DMA start sending ADC data to memory + } + // Send the dummy data to the ADC to get the ADC data + for(int i = 0; i < numFrameWords * adc_word_length_; i++) { + this->write_byte(0); + } +} + +bool ADS131M08Hub::adc_reset_retry() +{ + adc_word_length_ = 24; + bool reset_ok = adc_soft_reset(); + uint8_t retry_wordlengths[] = { 32, 32, 24, 24, 16, 16 } ; + int i = 0; + while( i < sizeof(retry_wordlengths) && !reset_ok) { + adc_word_length_ = retry_wordlengths[i]; + //ESP_LOGW(TAG, "ADC word length: %d", adc_word_length_); + reset_ok = adc_soft_reset(); + i++; + } + return reset_ok; +} + +//int ADS131M08Hub::build_frame() +bool ADS131M08Hub::adc_soft_reset() +{ + int index = 0; + uint16_t reset_response; + uint16_t cmd = 0; + // we assume 24 bit wordlength if adc_word_length_is illegal + //if(adc_word_length_ != 32 && adc_word_length_ != 24 && adc_word_length_ != 16) { + // adc_word_length_ = 24; + //} + update_adc_word_length(); + size_t word_nbytes = adc_word_length_ >> 3; + int framelength = numFrameWords * word_nbytes; + spiframe frame(framelength, 0); + size_t frame_len; + + // curly braces confine chip select scope + { + CHIP_SELECTx + cmd = CMD_RESET; + set_frame_word(frame, 0, cmd); + index += 2; + + for(; index < framelength; index++) { + frame[index] = 0; + } + frame_len = add_crc(frame, crc_pos::crc_after_firstword); + write_array(frame); + ESP_LOGVV(TAG, "Sent Frame: %s", frame_to_string(frame).c_str()); + } + delay_microseconds_safe(T_REGACQ); + { + CHIP_SELECTx + read_array(frame); + reset_response = get_unsigned_frame_word(frame, 0, true); + ESP_LOGVV(TAG, "Read Frame: %s", frame_to_string(frame).c_str()); + } + ESP_LOGVV(TAG, "reset_response: 0x%04X", reset_response); + if (reset_response == RSP_RESET_OK) { + return true; + } + return false; +} + +// overwrites last (or second) frameword with CRC, therefore frame should be large enough for payload and crc +size_t ADS131M08Hub::add_crc(spiframe& frame, crc_pos crcpos) +{ + size_t effective_frame_length; + size_t frame_length = frame.size(); + if (frame_length == 0 || adc_word_length_ == 0) + return 0; + size_t word_nbytes = adc_word_length_ >> 3; + if (crcpos == crc_pos::crc_after_firstword) { + effective_frame_length = word_nbytes * 2; + } + else { + effective_frame_length = frame_length; + } + if (effective_frame_length > frame_length) { + if (frame.capacity() >= effective_frame_length) + { + frame.resize(effective_frame_length); + if (frame.size() < effective_frame_length) { + return 0; + } + frame_length = effective_frame_length; + } + else { + return 0; + } + } + size_t frame_words = effective_frame_length / word_nbytes; // integer division, we discard remainder if any + size_t payload_len = word_nbytes * (frame_words - 1); + if (payload_len < word_nbytes) + return false; + uint16_t crc = get_crc(frame); + set_frame_word(frame, frame_words - 1, crc); + return frame_words * word_nbytes; +} + +// CRC should be 16bits, MSB aligned in last word of frame +bool ADS131M08Hub::check_crc(const spiframe& frame) +{ + if (adc_word_length_ == 0) + return false; + size_t word_nbytes = adc_word_length_ >> 3; + int frame_length = frame.size(); + size_t frame_words = frame_length / word_nbytes; // integer division, we discard remainder if any + size_t payload_len = word_nbytes * (frame_words - 1); + if (payload_len < word_nbytes) + return false; + uint16_t frame_crc = (frame[payload_len] << 8) + frame[payload_len + 1]; // Frames are MSB aligned, so is CRC + auto crc = get_crc(frame); + bool crc_ok = crc == frame_crc; + return crc_ok; +} + +uint16_t ADS131M08Hub::read_frame_crc(const spiframe& frame) +{ + if (adc_word_length_ == 0) + return 0; + size_t word_nbytes = adc_word_length_ >> 3; + int frame_length = frame.size(); + size_t frame_words = frame_length / word_nbytes; // integer division, we discard remainder if any + size_t payload_len = word_nbytes * (frame_words - 1); + if (payload_len < word_nbytes) + return 0; + return (frame[payload_len] << 8) + frame[payload_len + 1]; // Frames are MSB aligned, so is CRC +} +/* +CRC Parameters for ADS131M08 +Polynomial: 0x1021 (x^16 + x^12 + x^5 + 1). +Initial Value (Seed): 0xFFFF. +Input Data: The calculation is performed over all bits in the SPI frame preceding the CRC word. +Output Format: The resulting 16-bit CRC is placed in the most significant 16 bits of the final 24-bit (or 32-bit) SPI word; the remaining bits are typically padded with zeros. +*/ +uint16_t ADS131M08Hub::get_crc(const spiframe& frame) +{ + size_t word_nbytes = adc_word_length_ >> 3; + size_t frame_words = frame.size() / word_nbytes; // integer division, we discard remainder if any + size_t payload_len = word_nbytes * (frame_words - 1); + uint16_t crc_result = crc(0xFFFF, frame[0]); + for (int j = 1; j < payload_len; j++) { + crc_result = crc(crc_result, frame[j]); + } + return crc_result; +} + +uint16_t ADS131M08Hub::crc(uint16_t crc_register, uint8_t data) +{ + uint16_t xor_result, input_bit, crc_result; + crc_result = crc_register; + for (int i = 7; i >= 0; i--) { + input_bit = (data >> i) & 0x01; + xor_result = input_bit ^ (crc_result & 0x8000) >> 15; + crc_result = crc_result << 1; + if(xor_result) + crc_result = crc_result ^ 0x1021; + } + return crc_result; +} + +void ADS131M08Hub::write_byte(uint8_t byte) +{ + CHIP_SELECT + this->transfer_byte(byte); +} + +uint8_t ADS131M08Hub::read_byte() +{ + CHIP_SELECT + uint8_t result = this->transfer_byte(0x00); // Send dummy byte to read data + return result; +} + +void ADS131M08Hub::read_array(spiframe& frame) +{ + CHIP_SELECT + for (size_t i = 0; i < frame.size(); i++) { + frame[i] = this->transfer_byte(0x00); // Send dummy byte to read data + } +} + +void ADS131M08Hub::write_array(const spiframe& frame) +{ + CHIP_SELECT + for (size_t i = 0; i < frame.size(); i++) { + this->transfer_byte(frame[i]); + } +} + +void ADS131M08Hub::transfer_array(const spiframe& tx_frame, spiframe& rx_frame) +{ + CHIP_SELECT + auto frame_length = tx_frame.size(); + if(rx_frame.size() < frame_length) + rx_frame.resize(frame_length); + for (size_t i = 0; i < frame_length; i++) { + rx_frame[i] = this->transfer_byte(tx_frame[i]); + } +} + + +// ********************** end of from datasheet ************************ +void ADS131M08Hub::set_measure_rms(uint8_t channel, bool enable) +{ + if(channel < ADC_CHANNELS) { + ESP_LOGW(TAG, "set_measure_rms[%d] = %s", channel, enable ? "enabled" : "disabled"); + this->rms_enabled_[channel] = enable; + this->rms_calc_req_ = true; + } +} + +void ADS131M08Hub::read_single() +{ + int word_nbytes = adc_word_length_ >> 3; + int frame_length = numFrameWords * word_nbytes; + spiframe frame(frame_length, 0); + this->read_array(frame); + bool crc_ok = check_crc(frame); + uint16_t drdy_status = get_unsigned_frame_word(frame, 0, true) & MASK_STATUS_DRDY; + //data_ready = crc_ok && (drdy_status != 0); + if(crc_ok) { + // sample channels + for (int i = 0; i < ADC_CHANNELS; i++) { + bool channel_ready = drdy_status & 1; + drdy_status = drdy_status >> 1; + bool ac_sensor_exist = this->sensors_ac[i] != nullptr; + bool dc_sensor_exist = this->sensors_dc[i] != nullptr; + if (channel_ready && (ac_sensor_exist || dc_sensor_exist)) { + int32_t raw = get_sign_ext_frame_word(frame, i + 1); + this->sampled_values_[i] = this->conversion_factor_ * raw; + float value = this->conversion_factor_ * raw; + if(ac_sensor_exist) + this->sensors_ac[i]->publish_state(value); + if(dc_sensor_exist) + this->sensors_dc[i]->publish_state(value); + } + } + } +} + +std::pair ADS131M08Hub::read_multi() +{ + int32_t raw; + float value; + std::pair result; + result.first = 0; + result.second = 0; + bool crc_ok, data_ready, ac_sensor_exist, dc_sensor_exist; + uint16_t drdy_status, status; + int i, max_iters = 250; // going above 255 will result in overflows + uint32_t elapsed_time = 0; + uint32_t loop_start_time = micros(); + int word_nbytes = adc_word_length_ >> 3; + int frame_length = numFrameWords * word_nbytes; + spiframe frame(frame_length, 0); + // integrate for rms values over #iterations or sample_time depending on what comes first + for(int i = 0; i < ADC_CHANNELS; i++) { + //last_publish_time_[i] = 0; + num_samples_[i] = 0; + sample_sum_[i] = 0; + sample_squared_sum_[i] = 0; + } + while (elapsed_time < this->sample_time_ && max_iters-- > 0) { + this->read_array(frame); + //ESP_LOGD(TAG, "max_iters: %d, frame: %s", max_iters, frame_to_string(frame).c_str()); + crc_ok = check_crc(frame); + if (crc_ok) { + status = get_unsigned_frame_word(frame, 0, true); + // skip frame immediately afte resync + if ((status & MASK_STATUS_RESYNC) == 0) { + drdy_status = status & MASK_STATUS_DRDY; + // sample channels + for (i = 0; i < ADC_CHANNELS && drdy_status != 0; i++) { + data_ready = drdy_status & 1; + drdy_status = drdy_status >> 1; + ac_sensor_exist = this->sensors_ac[i] != nullptr; + dc_sensor_exist = this->sensors_dc[i] != nullptr; + if (data_ready && (ac_sensor_exist || dc_sensor_exist)) { + raw = get_sign_ext_frame_word(frame, i + 1); + value = this->conversion_factor_ * raw; + this->sampled_values_[i] = value; + if (this->rms_enabled_[i]) { + (this->num_samples_[i])++; + (this->sample_sum_[i]) += static_cast(raw); + (this->sample_squared_sum_[i]) += value * value; + } + else { + if(ac_sensor_exist) + this->sensors_ac[i]->publish_state(value); + if(dc_sensor_exist) + this->sensors_dc[i]->publish_state(value); + } + } + } + } + } + else { + result.second++; + //ESP_LOGW(TAG, "max_iters: %d, frame: %s CRC error", max_iters, frame_to_string(frame).c_str()); + } + elapsed_time = micros() - loop_start_time; + } + // publish integrated values + for (i = 0; i < ADC_CHANNELS; i++) { + if (this->rms_enabled_[i]) { + ac_sensor_exist = this->sensors_ac[i] != nullptr; + dc_sensor_exist = this->sensors_dc[i] != nullptr; + auto num_samples = this->num_samples_[i]; + if(num_samples > result.first) { + result.first = num_samples; + } + if (num_samples == 0) { + // should never happen, but let's play safe and avoid dividing by zero + if(ac_sensor_exist) + this->sensors_ac[i]->publish_state(NAN); + if(dc_sensor_exist) + this->sensors_dc[i]->publish_state(NAN); + } + else { + int64_t sample_sum = sample_sum_[i]; + float rms_dc = this->conversion_factor_ * static_cast(sample_sum) / num_samples; + float rms_ac_squared = sample_squared_sum_[i] / num_samples - rms_dc * rms_dc; + float rms_ac = 0; + if (rms_ac_squared > 0) { + rms_ac = std::sqrtf(rms_ac_squared); + } + this->sps_ = (1e6 * (float)num_samples) / elapsed_time; + if(ac_sensor_exist) + this->sensors_ac[i]->publish_state(rms_ac); + if(dc_sensor_exist) + this->sensors_dc[i]->publish_state(rms_dc); + } + this->num_samples_[i] = 0; + this->sample_sum_[i] = 0.0f; + this->sample_squared_sum_[i] = 0.0f; + } + } + return result; +} + +bool ADS131M08Hub::adc_lock(bool enable) +{ + int word_nbytes = adc_word_length_ >> 3; + spiframe frame(word_nbytes * 2, 0); + uint16_t command = CMD_UNLOCK; + if (enable) { + command = CMD_LOCK; + } + set_frame_word(frame, 0, command); + add_crc(frame, crc_after_frame); + write_array(frame); + delay_microseconds_safe(T_REGACQ); + read_array(frame); + auto ack = get_unsigned_frame_word(frame, 0, true); + return ack == command; +} + +/// @brief +/// @param address - register address +/// @param value - mask is used to remove unwanted bits before adding to value read from register +/// @param mask - register contents read from adc is masked with inverse of this +/// @param line - temporary / for debugging +/// @return success flag +bool ADS131M08Hub::adc_register_write_masked(uint8_t address, uint16_t value, uint16_t mask, int line) +{ + uint16_str reg_contents = adc_register_read(address, 1); + if(reg_contents.empty()) { + return false; + } + ESP_LOGW(TAG, "1: reg contents: 0x%04X, address: 0x%02X, value: 0x%04X, mask: 0x%04X, caller: %d", reg_contents[0],address, value,mask, line ); + reg_contents[0] = (reg_contents[0] & ~mask) | (value & mask); + ESP_LOGW(TAG, "After applying mask - reg contents: 0x%04X, caller: %d", reg_contents[0], line ); + return adc_register_write(address, reg_contents); +} +/// @brief +/// @param start_address - first register address +/// @param values - mask is used to remove unwanted bits before adding to value read from register +/// @param masks - register contents read from adc is masked with inverse of this +/// @param line - temporary / for debugging +/// @return success flag +bool ADS131M08Hub::adc_register_write_masked(uint8_t start_address, const uint16_str& values, const uint16_str& masks, int line) +{ + uint8_t nregs = values.size(); + uint8_t nmasks = masks.size(); + if(nregs != nmasks) { + ESP_LOGW(TAG, "Number register values to write (%d) are different than the corresponding number of masks (%d). Register write aborted.", nregs, nmasks); + return false; + } + uint16_str reg_contents = adc_register_read(start_address, nregs); + if(reg_contents.empty()) { + ESP_LOGW(TAG, "No contents read from adc. Expected %d values. Register write aborted.", nregs); + return false; + } + for (int i = 0; i < nregs; i++) { + //ESP_LOGW(TAG, "Before change %d: reg contents: 0x%04X, address: 0x%02X, value: 0x%04X, mask: 0x%04X, caller: %d", i, reg_contents[i], start_address + i, values[i], masks[i], line); + reg_contents[i] = (reg_contents[i] & ~masks[i]) | (values[i] & masks[i]); + //ESP_LOGW(TAG, "After applying mask - reg contents: 0x%04X, caller: %d", reg_contents[i], line); + } + return adc_register_write(start_address, reg_contents); +} + +bool ADS131M08Hub::adc_register_write(uint16_t address, uint16_t data) +{ + uint16_str arg; + arg += data; + return adc_register_write(address, arg); +} + +bool ADS131M08Hub::adc_register_write(uint16_t start_address, const uint16_str& data) +{ + int nregs = data.size(); + if(nregs == 0) { + return false; // invalid + } + int word_nbytes = adc_word_length_ >> 3; + int wreg_framelength = word_nbytes * (2 + nregs); // ensure room for crc + int rreg_resp_framelength = word_nbytes * (2 + (nregs == 1 ? -1 :nregs)); // add room for CRC if nregs > 1 + spiframe frame(wreg_framelength, 0); + spiframe rxframe(wreg_framelength, 0); + uint16_t addr_regcnt_mask = (start_address << 7) | ((nregs - 1 ) & MASK_CMD_RW_REG_COUNT); + uint16_t wreg_addr_opcode = CMD_WREG | addr_regcnt_mask; // Combine WREG command, start_address and register count + uint16_t rreg_addr_opcode = CMD_RREG | addr_regcnt_mask; // Combine RREG command, start_address and register count + bool has_mode_reg = (start_address == REG_MODE) || ((start_address < REG_MODE) && ((start_address + nregs) > REG_MODE)); + { + CHIP_SELECT + set_frame_word(frame, 0, wreg_addr_opcode); + for(int i = 0; i < nregs; i++) { + set_frame_word(frame, i + 1, data[i]); + } + add_crc(frame, crc_after_frame); + ESP_LOGVV(TAG, "%s\n", rwreg_command_frame_to_string(frame).c_str()); + transfer_array(frame, rxframe); // WREG + } + ESP_LOGVV(TAG, "Sent Frame: %s", frame_to_string(frame).c_str()); + if (has_mode_reg) { + update_adc_word_length(); + } + delay_microseconds_safe(T_REGACQ); + auto rxdata = adc_register_read(start_address, nregs); + bool verified = !rxdata.empty(); + for(int i = 0; i < rxdata.size() && verified; i++) { + verified = rxdata[i] == data[i]; + if(!verified) { + ESP_LOGE(TAG, "Write register failed: tx data= 0x%04X, rx data= 0x%04X", data[i] ,rxdata[i]); + } + } + delay_microseconds_safe(T_REGACQ); + return verified; +} + +// recommended not to read more than 20 registers at a time +uint16_str ADS131M08Hub::adc_register_read(uint8_t address, uint8_t nregs) +{ + uint16_str result; + if(nregs == 0) { + return result; // invalid + } + if(nregs > 31) { + // We are limited to 31 registers, otherwise we will ran into DMA / SPI buffer problems + ESP_LOGE(TAG, "Trying to read %d registers. This exceeds maximum register read count of 31!", nregs); + return result; // invalid + } + int word_nbytes = adc_word_length_ >> 3; + int request_framelength = 2 * word_nbytes; // ensure room for crc + int response_framelength = word_nbytes * (2 + (nregs == 1 ? -1 : nregs)); // add room for CRC if nregs > 1 + spiframe frame(response_framelength, 0); + uint16_t rreg_addr_opcode = CMD_RREG | (address << 7) | ((nregs - 1) & MASK_CMD_RW_REG_COUNT); // Combine RREG command, address and register count + { + CHIP_SELECT + frame.resize(request_framelength); + set_frame_word(frame, 0, rreg_addr_opcode); + add_crc(frame, crc_after_frame); + write_array(frame); // send RREG + } + ESP_LOGVV(TAG, "%s\n", rwreg_command_frame_to_string(frame).c_str()); + ESP_LOGVV(TAG, "Sent Frame: %s", frame_to_string(frame).c_str()); + delay_microseconds_safe(T_REGACQ); + { + CHIP_SELECT + frame.resize(response_framelength); + read_array(frame); + } + ESP_LOGVV(TAG, "Recv Frame: %s", frame_to_string(frame).c_str()); + if(nregs > 1) { + //auto crc = get_crc(frame); + //auto frame_crc = read_frame_crc(frame); + //ESP_LOGW(TAG, "CRC calc: 0x%04X, CRC recv: 0x%04X", crc, frame_crc); + bool crc_ok = check_crc(frame); + if(!crc_ok) { + ESP_LOGW(TAG, "CRC failed reading ADC registers!"); + result.clear(); + return result; // invalid + } + } + result.resize(nregs); + int offset = 0; + uint16_t rreg_ack = rreg_addr_opcode; // not really and ack, but since we don't get an ack when reading 1 register we pretend this is the ack + if(nregs > 1) { + offset = 1; + rreg_ack = get_unsigned_frame_word(frame, 0, true);; + } + //uint16_t rreg_ack = (nregs == 1) ? rreg_addr_opcode : get_unsigned_frame_word(frame, 0, true); + nregs = (rreg_ack & MASK_CMD_RW_REG_COUNT) + 1; + uint16_t start_addr = (rreg_ack & MASK_CMD_RW_REG_ADDRESS) >> 7; + for(int i = 0; i < nregs; i++) { + result[i] = get_unsigned_frame_word(frame, i + offset, true); + } + print_command_response_to_string(rreg_addr_opcode, frame); + delay_microseconds_safe(1); + return result; +} + +int8_t ADS131M08Hub::is_data_ready_soft(uint8_t channel) +{ + switch (channel) { + case 0: + return (readRegister(REG_STATUS) & MASK_STATUS_DRDY0); + case 1: + return (readRegister(REG_STATUS) & MASK_STATUS_DRDY1); + case 2: + return (readRegister(REG_STATUS) & MASK_STATUS_DRDY2); + case 3: + return (readRegister(REG_STATUS) & MASK_STATUS_DRDY3); + case 4: + return (readRegister(REG_STATUS) & MASK_STATUS_DRDY4); + case 5: + return (readRegister(REG_STATUS) & MASK_STATUS_DRDY5); + case 6: + return (readRegister(REG_STATUS) & MASK_STATUS_DRDY6); + case 7: + return (readRegister(REG_STATUS) & MASK_STATUS_DRDY7); + default: + return -1; // Invalid channel + } +} + +bool ADS131M08Hub::is_reset_status() +{ + return (readRegister(REG_STATUS) & MASK_STATUS_RESET); +} + +bool ADS131M08Hub::is_lock_spi() +{ + return (readRegister(REG_STATUS) & MASK_STATUS_LOCK); +} + +bool ADS131M08Hub::set_drdy_format(uint8_t drdy_format) +{ + if (drdy_format > 1) + { + return false; + } + return adc_register_write_masked(REG_MODE, drdy_format, MASK_MODE_DRDY_FMT, __LINE__); +} + +bool ADS131M08Hub::set_drdy_idle_state(uint8_t drdy_state) +{ + if (drdy_state > 1) + { + return false; + } + return adc_register_write_masked(REG_MODE, drdy_state < 1, MASK_MODE_DRDY_HiZ, __LINE__); +} + +bool ADS131M08Hub::set_power_mode(uint8_t power_mode) +{ + if (power_mode > 3) + { + return false; + } + return adc_register_write_masked(REG_CLOCK, power_mode, MASK_CLOCK_PWR, __LINE__); +} + +bool ADS131M08Hub::set_reg_osr() +{ + uint16_t osr = 3; // default + if (this->osr_ <= 16256 && this->osr_ >= 128) { + if (this->osr_ == 16256) + osr = 7; + else { + osr = 0; + auto tmp = this->osr_/128; + while (tmp >>= 1) + osr++; + } + } + return adc_register_write_masked(REG_CLOCK, osr << 2, MASK_CLOCK_OSR, __LINE__); +} + +//void ADS131M08Hub::set_full_scale(uint8_t channel, float scale) +//{ +// if (channel > 7) { +// return; +// } +// +// this->full_scale.ch[channel].f = scale; +// +//} +// +//float ADS131M08Hub::get_full_scale(uint8_t channel) +//{ +// if (channel > 7) { +// return 0.0; +// } +// +// return this->full_scale.ch[channel].f; +// +//} + +bool ADS131M08Hub::set_channel_enable(uint8_t channel, bool enable) +{ + if (channel > 7) + return false; + uint16_t enable_mask = MASK_CLOCK_CH0 << channel; + uint16_t reg_value = (enable) ? enable_mask : 0; + //ESP_LOGD(TAG, "adc_register_write_masked(REG_CLOCK=%02X, reg_value=%04X, enable_mask=%04X, __LINE__)", REG_CLOCK, reg_value, enable_mask); + return adc_register_write_masked(REG_CLOCK, reg_value, enable_mask, __LINE__); + //return true; +} + +bool ADS131M08Hub::set_channel_pga(uint8_t channel, ADS131M08_PGA_GAIN pga) +{ + uint16_t pgaCode = (uint16_t) pga; + switch (channel) { + case 0: + if(!adc_register_write_masked(REG_GAIN1, pgaCode, MASK_GAIN_PGAGAIN0, __LINE__)) + return false; + this->pgaGain[0] = pga; + return true; + case 1: + if(!adc_register_write_masked(REG_GAIN1, pgaCode << 4, MASK_GAIN_PGAGAIN1, __LINE__)) + return false; + this->pgaGain[1] = pga; + return true; + case 2: + if(!adc_register_write_masked(REG_GAIN1, pgaCode << 8, MASK_GAIN_PGAGAIN2, __LINE__)) + return false; + this->pgaGain[2] = pga; + return true; + case 3: + if(!adc_register_write_masked(REG_GAIN1, pgaCode << 12, MASK_GAIN_PGAGAIN3, __LINE__)) + return false; + this->pgaGain[3] = pga; + return true; + case 4: + if(!adc_register_write_masked(REG_GAIN2, pgaCode, MASK_GAIN_PGAGAIN4, __LINE__)) + return false; + this->pgaGain[4] = pga; + return true; + case 5: + if(!adc_register_write_masked(REG_GAIN2, pgaCode << 4, MASK_GAIN_PGAGAIN5, __LINE__)) + return false; + this->pgaGain[5] = pga; + return true; + case 6: + if(!adc_register_write_masked(REG_GAIN2, pgaCode << 8, MASK_GAIN_PGAGAIN6, __LINE__)) + return false; + this->pgaGain[6] = pga; + return true; + case 7: + if(!adc_register_write_masked(REG_GAIN2, pgaCode << 12, MASK_GAIN_PGAGAIN7, __LINE__)) + return false; + this->pgaGain[7] = pga; + return true; + default: + return false; // Invalid channel + } + return false; +} + +ADS131M08_PGA_GAIN ADS131M08Hub::get_channel_pga(uint8_t channel) +{ + if(channel > 7) + { + return ADS131M08_PGA_GAIN::PGA_INVALID; + } + return pgaGain[channel]; +} + +void ADS131M08Hub::set_global_chop(uint16_t global_chop) +{ + adc_register_write_masked(REG_CFG, global_chop << 8, MASK_CFG_GC_EN, __LINE__); +} + +void ADS131M08Hub::set_global_chop_delay(uint16_t delay) +{ + adc_register_write_masked(REG_CFG, delay << 9, MASK_CFG_GC_DLY, __LINE__); +} + +bool ADS131M08Hub::set_channel_phase_calibration(uint8_t channel, int16_t phase_offset) +{ + if(channel > 7) + return false; + uint16_t reg_addr = 5 * channel + REG_CH0_CFG; + if(phase_offset < -512 || phase_offset > 511) + return false; + return adc_register_write_masked(reg_addr, (phase_offset << 6), MASK_CHX_CFG_PHASE, __LINE__); +} + +bool ADS131M08Hub::set_dcblock_filter_disable(uint8_t channel, bool disable) +{ + if (channel > 7) + return false; + uint16_t reg_addr = 5 * channel + REG_CH0_CFG; + uint16_t reg_value = (disable) ? MASK_CHX_CFG_DCBLKX_DIS : 0; + return adc_register_write_masked(reg_addr, reg_value, MASK_CHX_CFG_DCBLKX_DIS, __LINE__); +} + +bool ADS131M08Hub::set_channel_input_selection(uint8_t channel, ADS131M08_INPUT_CHANNEL_MUX input) +{ + if(channel > 7) + return false; + uint16_t reg_addr = 5 * channel + REG_CH0_CFG; + return adc_register_write_masked(reg_addr, input, MASK_CHX_CFG_MUX, __LINE__); +} + +bool ADS131M08Hub::set_channel_offset_calibration(uint8_t channel, int32_t offset) +{ + if(channel > 7) + return false; + if(offset < -8388608 || offset > 8388607) + return false; + + uint16_t MSB = offset >> 8; + uint16_t LSB = offset << 8; + uint16_t reg_addr = 5 * channel + REG_CH0_OCAL_MSB; + return adc_register_write_masked(reg_addr, { MSB, LSB }, { MASK_CHX_OCAL_MSB, MASK_CHX_OCAL_LSB }, __LINE__); + +} + +bool ADS131M08Hub::set_channel_gain_calibration(uint8_t channel, uint32_t gain) +{ + if(channel > 7) + return false; + if(gain > 16777215) + return false; + + uint16_t MSB = gain >> 8; + uint8_t LSB = gain; + uint16_t reg_addr = 5 * channel + REG_CH0_GCAL_MSB; + return adc_register_write_masked(reg_addr, { MSB, LSB }, { MASK_CHX_GCAL_MSB, MASK_CHX_GCAL_LSB }, __LINE__); +} + +//bool ADS131M08Hub::is_data_ready() +//{ +// if (digitalRead(drdyPin) == HIGH) +// { +// return false; +// } +// return true; +//} + +uint16_t ADS131M08Hub::get_id() +{ + return readRegister(REG_ID); +} + +uint16_t ADS131M08Hub::get_mode_reg() +{ + return readRegister(REG_MODE); +} + +uint16_t ADS131M08Hub::get_clock_reg() +{ + return readRegister(REG_CLOCK); +} + +uint16_t ADS131M08Hub::get_cfg_reg() +{ + return readRegister(REG_CFG); +} + +//AdcOutput ADS131M08Hub::read_adc_raw() +//{ +// uint8_t x = 0; +// uint8_t x2 = 0; +// uint8_t x3 = 0; +// int32_t aux; +// AdcOutput res; +// +// digitalWrite(csPin, LOW); +// delayMicroseconds(1); +// +// x = spi.transfer(0x00); +// x2 = spi.transfer(0x00); +// spi.transfer(0x00); +// +// this->resultRaw.status = ((x << 8) | x2); +// +// for(int i = 0; i<8; i++) +// { +// x = spi.transfer(0x00); +// x2 = spi.transfer(0x00); +// x3 = spi.transfer(0x00); +// +// aux = (((x << 16) | (x2 << 8) | x3) & 0x00FFFFFF); +// if (aux > 0x7FFFFF) +// { +// this->resultRaw.ch[i].i = ((~(aux)&0x00FFFFFF) + 1) * -1; +// } +// else +// { +// this->resultRaw.ch[i].i = aux; +// } +// } +// +// delayMicroseconds(1); +// digitalWrite(csPin, HIGH); +// +// return this->resultRaw; +//} + +//float ADS131M08Hub::scale_result(uint8_t num) +//{ +// if( num >= 8) { +// return 0.0; +// } +// +// return this->resultFloat.ch[num].f = (float)(this->resultRaw.ch[num].i * rawToVolts * this->full_scale.ch[num].f); +//} + +//AdcOutput ADS131M08Hub::scale_result() +//{ +// // update status +// this->resultFloat.status = this->resultRaw.status; +// // Scale all channels +// for(int i = 0; i<8; i++) +// { +// this->scaleResult(i); +// } +// +// return this->resultFloat; +//} +// +//AdcOutput ADS131M08Hub::read_adc_float() +//{ +// this->readAdcRaw(); +// return this->scaleResult(); +//} +// end of from tpcorrea + + +// for debug only - remove references, declarations and definitions before submitting for production +std::string ADS131M08Hub::frame_to_string(const spiframe& frame) +{ + std::string str; + char buffer[20]; + const std::string::size_type new_cap(768); + str.reserve(new_cap); + for (int i = 0; i < frame.size(); i++) { + snprintf(buffer, sizeof(buffer), " %02X", frame[i]); + str += buffer; + } + return str; +} + +std::string ADS131M08Hub::conversion_frame_to_string(const spiframe& frame) +{ + std::string str; + char buffer[20]; + const std::string::size_type new_cap(768); + str.reserve(new_cap); + int word_nbytes = adc_word_length_ >> 3; + int frame_nwords = frame.size() / word_nbytes; + uint16_t drdy_status = get_unsigned_frame_word(frame, 0, true) & MASK_STATUS_DRDY; + // init i = 0 to also print status word + for (int i = 1; i < frame_nwords - 1; i++) { + if(i == 0) { + for (int j = 0; j < word_nbytes; j++) { + snprintf(buffer, sizeof(buffer), "%02X", frame[j]); + str += buffer; + } + } + else { + if (drdy_status & 1) { + str += std::format(" CH{}:", i - 1); + for (int j = 0; j < word_nbytes; j++) { + snprintf(buffer, sizeof(buffer), "%02X", frame[i * word_nbytes + j]); + str += buffer; + } + } + drdy_status = drdy_status >> 1; + } + } + return str; +} + +std::string ADS131M08Hub::rwreg_command_frame_to_string(const spiframe& frame) +{ + char buffer[100]; + std::string str; + str.reserve(512); + uint16_t cmdadr = get_unsigned_frame_word(frame, 0, true); + uint16_t address = (cmdadr & MASK_CMD_RW_REG_ADDRESS) >> 7; + int nregs = 1 + (cmdadr & MASK_CMD_RW_REG_COUNT); + str = command_to_string(cmdadr); + uint16_t command = (cmdadr & MASK_CMD_RW_REG) ? (cmdadr & MASK_CMD_RW_REG) : cmdadr; + const char* ws = " \t\n\r\f\v"; // Defines whitespace + if (command == CMD_RREG) { + str += " : regs "; + for (int i = 0; i < nregs; i++) { + str += reg_addr_to_string(address + i); + str.erase(str.find_last_not_of(ws) + 1); + str += (i < (nregs - 1) ? ", " : ""); + } + } + if (command == CMD_WREG) { + str += ", data:"; + for (int i = 0; i < nregs; i++) { + uint16_t data = get_unsigned_frame_word(frame, i + 1, true); + snprintf(buffer, sizeof(buffer), " 0x%04X", data); + str += buffer; + } + for (int i = 0; i < nregs; i++) { + uint16_t data = get_unsigned_frame_word(frame, i + 1, true); + str += "\n "; + str += reg_data_to_string(address + i, data); + str.erase(str.find_last_not_of(ws) + 1); + str += (i < (nregs - 1) ? ", " : ""); + } + } + return str; +} + +std::string ADS131M08Hub::reg_addr_to_string(int address) +{ + return reg_data_to_string(address, 0, true); +} + +std::string ADS131M08Hub::reg_data_to_string(int address, uint16_t data, bool nameonly) +{ + std::string str; + char buffer[100]; + str.reserve(120); + switch (address) { + case REG_ID: + str += std::format("{:<10}", "ID"); + if (nameonly) + break; + snprintf(buffer, sizeof(buffer), ": %d ", (data >> 8) & 0x0F); + str += buffer; + break; + case REG_STATUS: + str += std::format("{:<10}", "STATUS"); + if (nameonly) + break; + str += ":"; + str += status_to_string(data); + break; + case REG_MODE: + str += std::format("{:<10}", "MODE"); + if (nameonly) + break; + str += ":"; + if ((data & MASK_MODE_REG_CRC_EN) != 0) str += " REGMAP_CRC_EN /"; + if ((data & MASK_MODE_RX_CRC_EN) != 0) str += " SPI_IN_CRC_EN /"; + if ((data & MASK_MODE_TIMEOUT) != 0) str += " SPI_TIMEOUT_EN /"; + str += " CRC="; + str += ((data & MASK_MODE_CRC_TYPE) != 0) ? "ANSI /" : "CCITT /"; + if ((data & MASK_MODE_RESET) != 0) str += " RESET /"; + str += " WL="; + if ((data & MASK_MODE_WLENGTH) == WLENGTH_16_BITS) str += "16"; + if ((data & MASK_MODE_WLENGTH) == WLENGTH_24_BITS) str += "24"; + if ((data & MASK_MODE_WLENGTH) == WLENGTH_32_BITS_LSB_ZERO_PADDING) str += "32zp"; + if ((data & MASK_MODE_WLENGTH) == WLENGTH_32_BITS_MSB_SIGN_EXTEND) str += "32se"; + str += " / DRDY SEL="; + if ((data & MASK_MODE_DRDY_SEL) == DRDY_SEL_MOST_LAGGING) str += "MOST_LAGGING"; + if ((data & MASK_MODE_DRDY_SEL) == DRDY_SEL_LOGICAL_OR) str += "OR_OF_CHANLS"; + if ((data & MASK_MODE_DRDY_SEL) == DRDY_SEL_MOST_LEADING_CHAN) str += "MOST_LEADING"; + if ((data & MASK_MODE_DRDY_SEL) == DRDY_SEL_MOST_LEADING_CHAN2) str += "MOST_LEADING"; + str += " / DRDY IDLE="; + str += ((data & MASK_MODE_DRDY_HiZ) != 0) ? "Hi_IMP" : "LOGIC_HI"; + str += " / DRDY ACTV="; + str += ((data & MASK_MODE_DRDY_FMT) != 0) ? "LOW_FIX_DUR" : "LOGIC_LOW"; + break; + case REG_CLOCK: + str += std::format("{:<10}", "CLOCK"); + if (nameonly) + break; + str += ": CH ENABLED="; + str += ((data & MASK_CLOCK_CH7) != 0) ? "7" : "_"; + str += ((data & MASK_CLOCK_CH6) != 0) ? "6" : "_"; + str += ((data & MASK_CLOCK_CH5) != 0) ? "5" : "_"; + str += ((data & MASK_CLOCK_CH4) != 0) ? "4" : "_"; + str += ((data & MASK_CLOCK_CH3) != 0) ? "3" : "_"; + str += ((data & MASK_CLOCK_CH2) != 0) ? "2" : "_"; + str += ((data & MASK_CLOCK_CH1) != 0) ? "1" : "_"; + str += ((data & MASK_CLOCK_CH0) != 0) ? "0" : "_"; + str += " / XTAL OSC="; + str += ((data & MASK_CLOCK_XTAL_DIS) != 0) ? "DIS" : "EN"; + str += " / EXT VREF="; + str += ((data & MASK_CLOCK_EXTREF_EN) != 0) ? "EN" : "DIS"; + { + uint16_t osr = 1 << (7 + ((data & MASK_CLOCK_OSR) >> 2)); + if (osr == 16384) { + osr = 16256; + } + snprintf(buffer, sizeof(buffer), " / OSR=%d", osr); + str += buffer; + } + str += " / POWER="; + if ((data & MASK_CLOCK_PWR) == PM_VERY_LOW_POWER) str += "VERY_LOW"; + if ((data & MASK_CLOCK_PWR) == PM_LOW_POWER) str += "VERY_LOW"; + if ((data & MASK_CLOCK_PWR) == PM_HIGH_RESOLUTION) str += "HIGH_RES"; + if ((data & MASK_CLOCK_PWR) == PM_HIGH_RESOLUTION2) str += "HIGH_RES"; + break; + + case REG_GAIN1: + { + str += std::format("{:<10}", "GAIN1"); + if (nameonly) + break; + auto gain = 1 << ((data & MASK_GAIN_PGAGAIN3) >> 24); + snprintf(buffer, sizeof(buffer), ": Ch3=%d /", gain); + str += buffer; + gain = 1 << ((data & MASK_GAIN_PGAGAIN2) >> 16); + snprintf(buffer, sizeof(buffer), " Ch2=%d /", gain); + str += buffer; + gain = 1 << ((data & MASK_GAIN_PGAGAIN1) >> 8); + snprintf(buffer, sizeof(buffer), " Ch1=%d /", gain); + str += buffer; + gain = 1 << (data & MASK_GAIN_PGAGAIN0); + snprintf(buffer, sizeof(buffer), " Ch0=%d", gain); + str += buffer; + break; + } + + case REG_GAIN2: + { + str += std::format("{:<10}", "GAIN2"); + if (nameonly) + break; + auto gain = 1 << ((data & MASK_GAIN_PGAGAIN7) >> 24); + snprintf(buffer, sizeof(buffer), ": Ch7=%d /", gain); + str += buffer; + gain = 1 << ((data & MASK_GAIN_PGAGAIN6) >> 16); + snprintf(buffer, sizeof(buffer), " Ch6=%d /", gain); + str += buffer; + gain = 1 << ((data & MASK_GAIN_PGAGAIN5) >> 8); + snprintf(buffer, sizeof(buffer), " Ch5=%d /", gain); + str += buffer; + gain = 1 << (data & MASK_GAIN_PGAGAIN4); + snprintf(buffer, sizeof(buffer), " Ch4=%d", gain); + str += buffer; + break; + } + case REG_CFG: + { + str += std::format("{:<10}", "CFG"); + if (nameonly) + break; + str += ": GC_EN="; + str += ((data & MASK_CFG_GC_EN) != 0) ? "Y /" : "N /"; + auto gc_dly = 1 << (1 + ((data & MASK_CFG_GC_DLY) >> 9)); + snprintf(buffer, sizeof(buffer), " GC DELAY= %d", gc_dly); + str += buffer; + str += " / CUR DET= ["; + str += ((data & MASK_CFG_CD_EN) != 0) ? "Y] " : "N] "; + str += ((data & MASK_CFG_CD_ALLCH) != 0) ? "ALL_CH" : "ANY_CH"; + auto cd_num = 1 << ((data & MASK_CFG_CD_NUM) >> 4); + snprintf(buffer, sizeof(buffer), " / CD_NUM= %d", cd_num); + str += buffer; + auto cd_len = (data & MASK_CFG_CD_LEN) >> 1; + switch (cd_len) { + case 0: + cd_len = 128; + break; + case 1: + cd_len = 256; + break; + case 2: + cd_len = 512; + break; + case 3: + cd_len = 768; + break; + case 4: + cd_len = 1280; + break; + case 5: + cd_len = 1792; + break; + case 6: + cd_len = 2560; + break; + case 7: + cd_len = 3584; + break; + default: + cd_len = 0; // illegal case + break; + } + snprintf(buffer, sizeof(buffer), " / CD_LEN= %d", cd_len); + str += buffer; + break; + } + case REG_THRSHLD_MSB: + str += std::format("{:<10}", "TH_MSB"); + if (nameonly) + break; + snprintf(buffer, sizeof(buffer), ": 0x%04X", data); + str += buffer; + break; + + case REG_THRSHLD_LSB: + str += std::format("{:<10}", "TH_LSB"); + if (nameonly) + break; + snprintf(buffer, sizeof(buffer), ": 0x%04X / ", (data & MASK_THRSHLD_LSB_CD_TH_LSB) >> 8); + str += buffer; + snprintf(buffer, sizeof(buffer), " DCBLOCK: 0x%04X", data & MASK_THRSHLD_LSB_DCBLOCK); + str += buffer; + break; + + case REG_CH0_CFG: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH0_OCAL_MSB: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH0_OCAL_LSB: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH0_GCAL_MSB: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH0_GCAL_LSB: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH1_CFG: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH1_OCAL_MSB: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH1_OCAL_LSB: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH1_GCAL_MSB: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH1_GCAL_LSB: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH2_CFG: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH2_OCAL_MSB: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH2_OCAL_LSB: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH2_GCAL_MSB: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH2_GCAL_LSB: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH3_CFG: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH3_OCAL_MSB: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH3_OCAL_LSB: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH3_GCAL_MSB: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH3_GCAL_LSB: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH4_CFG: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH4_OCAL_MSB: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH4_OCAL_LSB: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH4_GCAL_MSB: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH4_GCAL_LSB: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH5_CFG: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH5_OCAL_MSB: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH5_OCAL_LSB: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH5_GCAL_MSB: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH5_GCAL_LSB: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH6_CFG: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH6_OCAL_MSB: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH6_OCAL_LSB: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH6_GCAL_MSB: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH6_GCAL_LSB: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH7_CFG: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH7_OCAL_MSB: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH7_OCAL_LSB: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH7_GCAL_MSB: + str += reg_config_to_string(address, data, nameonly); + break; + + case REG_CH7_GCAL_LSB: + str += reg_config_to_string(address, data, nameonly); + break; + + case REGMAP_CRC: + str += std::format("{:<10}", std::format("REGMAP_CRC[{:#02}]", address)); + if (nameonly) + break; + snprintf(buffer, sizeof(buffer), " value: 0x%04X", data); + str += buffer; + } + return str; +} + +std::string ADS131M08Hub::reg_config_to_string(int address, uint16_t data, bool nameonly) +{ + std::string str; + str.reserve(256); + int ch_num = (address - REG_CH0_CFG) / 5; + int gen_addr = (address - REG_CH0_CFG) % 5; + std::string ch_str = std::format("CH{}_", ch_num); + switch (gen_addr) + { + case REG_CHX_CFG: + str += std::format("{:<10}", std::format("{}CFG", ch_str)); + if (nameonly) + break; + str += std::format(": PH_DEL={0:}", (data & MASK_CHX_CFG_PHASE) >> 6); + str += " DCBLOCK_EN="; + str += ((data & MASK_CHX_CFG_DCBLKX_DIS) != 0) ? "N /" : "Y /"; + str += std::format("{}INPUT=", ch_str); + if ((data & MASK_CHX_CFG_MUX) == ICM_AIN0P_AIN0N) str += "AIN0P and AIN0N"; + if ((data & MASK_CHX_CFG_MUX) == ICM_INPUT_SHORTED) str += "ADC inputs shorted"; + if ((data & MASK_CHX_CFG_MUX) == ICM_POSITIVE_DC_TEST_SIGNAL) str += "Positive DC test signal"; + if ((data & MASK_CHX_CFG_MUX) == ICM_NEGATIVE_DC_TEST_SIGNAL) str += "Negative DC test signal"; + break; + + case REG_CHX_OCAL_MSB: + str += std::format("{:<13}", std::format("{}OCAL_MSB", ch_str)); + if (nameonly) + break; + str += std::format(": {:#04x}", data); + break; + + case REG_CHX_OCAL_LSB: + str += std::format("{:<13}", std::format("{}OCAL_LSB", ch_str)); + if (nameonly) + break; + str += std::format(": {:#04x}", (data & MASK_CHX_OCAL_LSB) >> 8); + break; + + case REG_CHX_GCAL_MSB: + str += std::format("{:<13}", std::format("{}GCAL_MSB", ch_str)); + if (nameonly) + break; + str += std::format(": {:#04x}", data); + break; + + case REG_CHX_GCAL_LSB: + str += std::format("{:<13}", std::format("{}GCAL_LSB", ch_str)); + if (nameonly) + break; + str += std::format(": {:#04x}", (data & MASK_CHX_GCAL_LSB) >> 8); + break; + } + return str; +} + +std::string ADS131M08Hub::command_to_string(uint16_t cmdadr) +{ + std::string str; + uint16_t reg_addr, nregs; + char buffer[40]; + const std::string::size_type new_cap(256); + str.reserve(new_cap); + snprintf(buffer, sizeof(buffer), "0x%04X", cmdadr); + uint16_t cmd_sw = (cmdadr & MASK_CMD_RW_REG) ? (cmdadr & MASK_CMD_RW_REG) : cmdadr; + switch (cmd_sw) { + case CMD_NULL: + str = "NULL ["; + str += buffer; + str += "]"; + break; + case CMD_RESET: + str = "RESET ["; + str += buffer; + str += "]"; + break; + case CMD_STANDBY: + str = "STANDBY ["; + str += buffer; + str += "]"; + break; + case CMD_WAKEUP: + str = "WAKEUP ["; + str += buffer; + str += "]"; + break; + case CMD_LOCK: + str = "LOCK ["; + str += buffer; + str += "]"; + break; + case CMD_UNLOCK: + str = "UNLOCK ["; + str += buffer; + str += "]"; + break; + case CMD_RREG: + str = "RREG ["; + str += buffer; + str += "]: "; + nregs = cmdadr & MASK_CMD_RW_REG_COUNT; + nregs++; + reg_addr = (cmdadr & MASK_CMD_RW_REG_ADDRESS) >> 7; + snprintf(buffer, sizeof(buffer), "#regs:%d, start_addr:%d ", nregs, reg_addr); + str += buffer; + break; + case CMD_WREG: + str = "WREG ["; + str += buffer; + str += "]: "; + nregs = cmdadr & MASK_CMD_RW_REG_COUNT; + nregs++; + reg_addr = (cmdadr & MASK_CMD_RW_REG_ADDRESS) >> 7; + snprintf(buffer, sizeof(buffer), "#regs:%d, start_addr:%d ", nregs, reg_addr); + str += buffer; + break; + default: + str = "UNKNOWN command ["; + str += buffer; + str += "]"; + break; + } + return str; +} +// we do not return strings from this function as its length could exceed maximum length that log print can handle +void ADS131M08Hub::print_command_response_to_string(uint16_t cmdadr_sent, const spiframe& frame) +{ + std::string str; + uint16_t nregs, start_addr, respcmd; + char buffer[40]; + str.reserve(256); + uint16_t response = frame[0] << 8 | frame[1]; + snprintf(buffer, sizeof(buffer), " [0x%04X]: ", response); + std::string resp_txt(buffer); + uint16_t command = (cmdadr_sent & MASK_CMD_RW_REG) ? (cmdadr_sent & MASK_CMD_RW_REG) : cmdadr_sent; + switch (command) { + case CMD_NULL: + str = "STATUS" + resp_txt; + str += status_to_string(response); + ESP_LOGD(TAG, "%s", str.c_str()); + break; + case CMD_RESET: + str = "RESET RESPONSE" + resp_txt; + switch (response) { + case RSP_RESET_OK: + str += "RESET SUCCESS"; + break; + case RSP_RESET_NOK: + str += "RESET NOT COMPLETE"; + break; + default: + str += "UNKNOWN RESET RESPONSE"; + break; + } + ESP_LOGD(TAG, "%s", str.c_str()); + break; + case CMD_STANDBY: + str = "STANDBY RESPONSE" + resp_txt; + if (response == CMD_STANDBY) { + str += "IN STANDBY"; + } + else { + str += "UNKNOWN"; + } + ESP_LOGD(TAG, "%s", str.c_str()); + break; + case CMD_WAKEUP: + str = "WAKEUP RESPONSE" + resp_txt; + if (response == CMD_WAKEUP) { + str += "EXITED STANDBY"; + } + else { + str += "UNKNOWN"; + } + ESP_LOGD(TAG, "%s", str.c_str()); + break; + case CMD_LOCK: + str = "LOCK RESPONSE " + resp_txt; + if (response == CMD_LOCK) { + str += "LOCKED"; + } + else { + str += "UNKNOWN"; + } + ESP_LOGD(TAG, "%s", str.c_str()); + break; + case CMD_UNLOCK: + str = "UNLOCK RESPONSE" + resp_txt; + if (response == CMD_UNLOCK) { + str += "UNLOCKED"; + } + else { + str += "UNKNOWN"; + } + ESP_LOGD(TAG, "%s", str.c_str()); + break; + case CMD_RREG: + { + int offset = 0; + str = "RREG RESPONSE" + resp_txt; + nregs = (cmdadr_sent & MASK_CMD_RW_REG_COUNT) + 1; + uint16_t rreg_ack = cmdadr_sent; // not really and ack, but since we don't get an ack when reading 1 register we pretend this is the ack + if (nregs > 1) { + offset = 1; + rreg_ack = get_unsigned_frame_word(frame, 0, true);; + nregs = (rreg_ack & MASK_CMD_RW_REG_COUNT) + 1; // should not change here, but just in case adc decided to sent different number of registers + } + start_addr = (rreg_ack & MASK_CMD_RW_REG_ADDRESS) >> 7; + snprintf(buffer, sizeof(buffer), "#regs:%d, start_addr:%d ", nregs, start_addr); + ESP_LOGVV(TAG, "%s", buffer); + if (nregs == 1) { + start_addr = (cmdadr_sent & MASK_CMD_RW_REG_ADDRESS) >> 7; + str = reg_data_to_string(start_addr, response); + ESP_LOGVV(TAG, "%s", str.c_str()); + } + else { + respcmd = response & MASK_CMD_RW_REG_RESP; + if (respcmd == RREG_RESP) { + for (int i = 0; i < nregs; i++) { + uint16_t rxdata = get_unsigned_frame_word(frame, i + offset, true); + str = reg_data_to_string(start_addr + i, rxdata); + ESP_LOGVV(TAG, "%s", str.c_str()); + } + } + else { + str = "WARNING: invalid response to RREG. Should be Exxxx or Fxxxx"; + ESP_LOGW(TAG, "%s", str.c_str()); + } + } + } + break; + case CMD_WREG: + str = "WREG RESPONSE" + resp_txt; + respcmd = response & MASK_CMD_RW_REG_RESP; + if (respcmd == WREG_RESP) { + nregs = response & MASK_CMD_RW_REG_COUNT; + start_addr = (response & MASK_CMD_RW_REG_ADDRESS) >> 7; + snprintf(buffer, sizeof(buffer), "#regs:%d, start-addr:%d ", nregs + 1, start_addr); + str += buffer; + ESP_LOGVV(TAG, "%s", str.c_str()); + } + else { + str += "WARNING: invalid response to WREG. Should be 4xxxx or 5xxxx"; + ESP_LOGW(TAG, "%s", str.c_str()); + } + break; + default: + str = "Response to UNKNOWN command ["; + str += buffer; + str += "]"; + ESP_LOGW(TAG, "%s", str.c_str()); + break; + } +} + +std::string ADS131M08Hub::status_to_string(uint16_t response) +{ + std::string str; + str.reserve(90); + if ((response & MASK_STATUS_LOCK) != 0) str += " LOCK /"; + if ((response & MASK_STATUS_RESYNC) != 0) str += " RESYNC /"; + if ((response & MASK_STATUS_REGMAP) != 0) str += " REGMAP /"; + if ((response & MASK_STATUS_CRC_ERR) != 0) str += " CRC_ERR /"; + str += " CRC="; + str += ((response & MASK_STATUS_CRC_TYPE) != 0) ? "ANSI /" : "CCITT /"; + if ((response & MASK_STATUS_RESET) != 0) str += " RESET /"; + str += " WL="; + if ((response & MASK_STATUS_WLENGTH) == WLENGTH_16_BITS) str += "16"; + if ((response & MASK_STATUS_WLENGTH) == WLENGTH_24_BITS) str += "24"; + if ((response & MASK_STATUS_WLENGTH) == WLENGTH_32_BITS_LSB_ZERO_PADDING) str += "32zp"; + if ((response & MASK_STATUS_WLENGTH) == WLENGTH_32_BITS_MSB_SIGN_EXTEND) str += "32se"; + str += " / DRDY="; + str += ((response & MASK_STATUS_DRDY7) != 0) ? "7" : "_"; + str += ((response & MASK_STATUS_DRDY6) != 0) ? "6" : "_"; + str += ((response & MASK_STATUS_DRDY5) != 0) ? "5" : "_"; + str += ((response & MASK_STATUS_DRDY4) != 0) ? "4" : "_"; + str += ((response & MASK_STATUS_DRDY3) != 0) ? "3" : "_"; + str += ((response & MASK_STATUS_DRDY2) != 0) ? "2" : "_"; + str += ((response & MASK_STATUS_DRDY1) != 0) ? "1" : "_"; + str += ((response & MASK_STATUS_DRDY0) != 0) ? "0" : "_"; + return str; +} + +// end of for debug only section + +} // namespace ads131m08 +} // namespace esphome diff --git a/components/ads131m08/ads131m08.h b/components/ads131m08/ads131m08.h index a1ed8a6..8d5bb4e 100644 --- a/components/ads131m08/ads131m08.h +++ b/components/ads131m08/ads131m08.h @@ -1,503 +1,229 @@ -#pragma once -// using external voltage reference - -#include "esphome/core/component.h" -#include "esphome/components/spi/spi.h" -#include "esphome/components/sensor/sensor.h" - -namespace esphome { -namespace ads131m08 { - -typedef union { - - int32_t i; - float f; - uint16_t u[2]; - uint8_t b[4]; -} flex32_t; - - -/* Adc Structure. Ch can be read as int32 or float*/ -struct AdcOutput -{ - uint16_t status; - flex32_t ch[8]; -}; - -enum ADS131M08_DRDY_STATE -{ - DS_LOGIC_HIGH = 0, // DEFAULT - DS_HI_Z = 1 -}; - -enum ADS131M08_POWERMODE -{ - PM_VERY_LOW_POWER = 0, - PM_LOW_POWER = 1, - PM_HIGH_RESOLUTION = 2 // DEFAULT -}; - -enum ADS131M08_PGA_GAIN -{ - PGA_1 = 0, - PGA_2 = 1, - PGA_4 = 2, - PGA_8 = 3, - PGA_16 = 4, - PGA_32 = 5, - PGA_64 = 6, - PGA_128 = 7, - PGA_INVALID -}; - -enum ADS131M08_INPUT_CHANNEL_MUX -{ - ICM_AIN0P_AIN0N = 0, // DEFAULT - ICM_INPUT_SHORTED = 1, - ICM_POSITIVE_DC_TEST_SIGNAL = 2, - ICM_NEGATIVE_DC_TEST_SIGNAL = 3, -}; - -enum ADS131M08_OVERSAMPLING_RATIO -{ - OSR_128 = 0, - OSR_256 = 1, - OSR_512 = 2, - OSR_1024 = 3, // default - OSR_2048 = 4, - OSR_4096 = 5, - OSR_8192 = 6, - OSR_16384 = 7 -}; - -enum ADS131M08_WAIT_TIME -{ - WT_128 = 856, - WT_256 = 1112, - WT_512 = 1624, - WT_1024 = 2648, - WT_2048 = 4696, - WT_4096 = 8792, - WT_8192 = 16984, - WT_16384 = 33368 -}; - -// MODE Register -enum ADS131M08_RESET : uint16_t -{ - MODE_NO_RESET = 0x0000, // DEFAULT - MODE_RESET_HAPPENED = 0x0400 -}; - -enum ADS131M08_CRC_TYPE : uint16_t -{ - CRC_CCITT_16BIT = 0x0000, // DEFAULT - CRC_ANSI_16BIT = 0x0800 -}; - -enum ADS131M08_WORD_LENGTH : uint16_t -{ - WLENGTH_16_BITS = 0x0000, - WLENGTH_24_BITS = 0x0100, // DEFAULT - WLENGTH_32_BITS_LSB_ZERO_PADDING = 0x0200, - WLENGTH_32_BITS_MSB_SIGN_EXTEND = 0x0300 -}; - -enum ADS131M08_TIMEOUT : uint16_t -{ - TIMEOUT_DISABLED = 0x0000, - TIMEOUT_ENABLED = 0x0010 // DEFAULT -}; - -enum ADS131M08_DRDY_SELECTION : uint16_t -{ - DRDY_SEL_MOST_LAGGING = 0x0000, // DEFAULT - DRDY_SEL_LOGICAL_OR = 0x0004, - DRDY_SEL_MOST_LEADING_CHAN = 0x0008, - DRDY_SEL_MOST_LEADING_CHAN = 0x000C -}; -enum ADS131M08_DRDY_FORMAT : uint16_t -{ - DRDY_FMT_LEVEL = 0x0000, // Logic low (default) - DRDY_FMT_PULSE = 0x0001 // Low pulse with a fixed duration -}; -// end of MODE Register - -// GAIN1 Register -enum ADS131M08_GAIN1_CHANNEL_PGA : uint16_t { - PGAGAIN0_1 = 0x0000, // default - PGAGAIN0_2 = 0x0001, - PGAGAIN0_4 = 0x0002, - PGAGAIN0_8 = 0x0003, - PGAGAIN0_16 = 0x0004, - PGAGAIN0_32 = 0x0005, - PGAGAIN0_64 = 0x0006, - PGAGAIN0_128 = 0x0007, - PGAGAIN1_1 = 0x0000, // default - PGAGAIN1_2 = 0x0010, - PGAGAIN1_4 = 0x0020, - PGAGAIN1_8 = 0x0030, - PGAGAIN1_16 = 0x0040, - PGAGAIN1_32 = 0x0050, - PGAGAIN1_64 = 0x0060, - PGAGAIN1_128 = 0x0070, - PGAGAIN2_1 = 0x0000, // default - PGAGAIN2_2 = 0x0100, - PGAGAIN2_4 = 0x0200, - PGAGAIN2_8 = 0x0300, - PGAGAIN2_16 = 0x0400, - PGAGAIN2_32 = 0x0500, - PGAGAIN2_64 = 0x0600, - PGAGAIN2_128 = 0x0700, - PGAGAIN3_1 = 0x0000, // default - PGAGAIN3_2 = 0x1000, - PGAGAIN3_4 = 0x2000, - PGAGAIN3_8 = 0x3000, - PGAGAIN3_16 = 0x4000, - PGAGAIN3_32 = 0x5000, // Set PGA gain to 32 for channel - PGAGAIN3_64 = 0x6000, - PGAGAIN3_128 = 0x7000 -}; -// end of GAIN1 Register - -// GAIN2 Register -enum ADS131M08_GAIN2_CHANNEL_PGA : uint16_t { - PGAGAIN4_1 = 0x0000, // default - PGAGAIN4_2 = 0x0001, - PGAGAIN4_4 = 0x0002, - PGAGAIN4_8 = 0x0003, - PGAGAIN4_16 = 0x0004, - PGAGAIN4_32 = 0x0005, - PGAGAIN4_64 = 0x0006, - PGAGAIN4_128 = 0x0007, - PGAGAIN5_1 = 0x0000, // default - PGAGAIN5_2 = 0x0010, - PGAGAIN5_4 = 0x0020, - PGAGAIN5_8 = 0x0030, - PGAGAIN5_16 = 0x0040, - PGAGAIN5_32 = 0x0050, - PGAGAIN5_64 = 0x0060, - PGAGAIN5_128 = 0x0070, - PGAGAIN6_1 = 0x0000, // default - PGAGAIN6_2 = 0x0100, - PGAGAIN6_4 = 0x0200, - PGAGAIN6_8 = 0x0300, - PGAGAIN6_16 = 0x0400, - PGAGAIN6_32 = 0x0500, - PGAGAIN6_64 = 0x0600, - PGAGAIN6_128 = 0x0700, - PGAGAIN7_1 = 0x0000, // default - PGAGAIN7_2 = 0x1000, - PGAGAIN7_4 = 0x2000, - PGAGAIN7_8 = 0x3000, - PGAGAIN7_16 = 0x4000, - PGAGAIN7_32 = 0x5000, - PGAGAIN7_64 = 0x6000, - PGAGAIN7_128 = 0x7000 -}; -// end of GAIN2 Register - - -// Commands -enum ADS131M08_COMMANDS : uint16_t { - CMD_NULL = 0x0000, // No operation; used to read STATUS register - CMD_RESET = 0x0011, // RESET the device - CMD_STANDBY = 0x0022, // Place the device into standby mode - CMD_WAKEUP = 0x0033, // Wake up device from standby mode to conversion mode - CMD_LOCK = 0x0555, // Lock the interface such that only the NULL, UNLOCK, and RREG commands are valid - CMD_UNLOCK = 0x0655, // Unlock the interface after the interface is locked - CMD_RREG = 0xA000, // Read register command base; number of registers to read added to lower byte; register address to upper byte - CMD_WREG = 0x6000, // Write register command base; number of registers to write added to lower byte; register address to upper byte -}; - -// Responses -enum ADS131M08_RESPONSES : uint16_t { - RSP_RESET_OK = 0xFF28, - RSP_RESET_NOK = 0x0011 -}; - -enum ADS131M08_REG { - REG_ID = 0x00, - REG_STATUS = 0x01, - REG_MODE = 0x02, - REG_CLOCK = 0x03, - REG_GAIN1 = 0x04, - REG_GAIN2 = 0x05, - REG_CFG = 0x06, - REG_THRSHLD_MSB = 0x07, - REG_THRSHLD_LSB = 0x08, - REG_CH0_CFG = 0x09, - REG_CH0_OCAL_MSB = 0x0A, - REG_CH0_OCAL_LSB = 0x0B, - REG_CH0_GCAL_MSB = 0x0C, - REG_CH0_GCAL_LSB = 0x0D, - REG_CH1_CFG = 0x0E, - REG_CH1_OCAL_MSB = 0x0F, - REG_CH1_OCAL_LSB = 0x10, - REG_CH1_GCAL_MSB = 0x11, - REG_CH1_GCAL_LSB = 0x12, - REG_CH2_CFG = 0x13, - REG_CH2_OCAL_MSB= 0x14, - REG_CH2_OCAL_LSB= 0x15, - REG_CH2_GCAL_MSB= 0x16, - REG_CH2_GCAL_LSB= 0x17, - REG_CH3_CFG = 0x18, - REG_CH3_OCAL_MSB= 0x19, - REG_CH3_OCAL_LSB= 0x1A, - REG_CH3_GCAL_MSB= 0x1B, - REG_CH3_GCAL_LSB= 0x1C, - REG_CH4_CFG = 0x1D, - REG_CH4_OCAL_MSB= 0x1E, - REG_CH4_OCAL_LSB= 0x1F, - REG_CH4_GCAL_MSB= 0x20, - REG_CH4_GCAL_LSB= 0x21, - REG_CH5_CFG = 0x22, - REG_CH5_OCAL_MSB= 0x23, - REG_CH5_OCAL_LSB= 0x24, - REG_CH5_GCAL_MSB= 0x25, - REG_CH5_GCAL_LSB= 0x26, - REG_CH6_CFG = 0x27, - REG_CH6_OCAL_MSB= 0x28, - REG_CH6_OCAL_LSB= 0x29, - REG_CH6_GCAL_MSB= 0x2A, - REG_CH6_GCAL_LSB= 0x2B, - REG_CH7_CFG = 0x2C, - REG_CH7_OCAL_MSB= 0x2D, - REG_CH7_OCAL_LSB= 0x2E, - REG_CH7_GCAL_MSB= 0x2F, - REG_CH7_GCAL_LSB= 0x30, - REGMAP_CRC = 0x3E, - }; -// Mask READ_REG -static constexpr uint16_t MASK_CMD_READ_REG_ADDRESS = 0x1F80; -static constexpr uint16_t MASK_CMD_READ_REG_BYTES = 0x007F; - -// Mask Register STATUS -static constexpr uint16_t MASK_STATUS_LOCK = 0x8000; -static constexpr uint16_t MASK_STATUS_RESYNC = 0x4000; -static constexpr uint16_t MASK_STATUS_REGMAP = 0x2000; -static constexpr uint16_t MASK_STATUS_CRC_ERR = 0x1000; -static constexpr uint16_t MASK_STATUS_CRC_TYPE = 0x0800; -static constexpr uint16_t MASK_STATUS_RESET = 0x0400; -static constexpr uint16_t MASK_STATUS_WLENGTH = 0x0300; -static constexpr uint16_t MASK_STATUS_DRDY7 = 0x0080; -static constexpr uint16_t MASK_STATUS_DRDY6 = 0x0040; -static constexpr uint16_t MASK_STATUS_DRDY5 = 0x0020; -static constexpr uint16_t MASK_STATUS_DRDY4 = 0x0010; -static constexpr uint16_t MASK_STATUS_DRDY3 = 0x0008; -static constexpr uint16_t MASK_STATUS_DRDY2 = 0x0004; -static constexpr uint16_t MASK_STATUS_DRDY1 = 0x0002; -static constexpr uint16_t MASK_STATUS_DRDY0 = 0x0001; - -// Mask Register MODE -static constexpr uint16_t MASK_MODE_REG_CRC_EN = 0x2000; -static constexpr uint16_t MASK_MODE_RX_CRC_EN = 0x1000; -static constexpr uint16_t MASK_MODE_CRC_TYPE = 0x0800; -static constexpr uint16_t MASK_MODE_RESET = 0x0400; -static constexpr uint16_t MASK_MODE_WLENGTH = 0x0300; -static constexpr uint16_t MASK_MODE_TIMEOUT = 0x0010; -static constexpr uint16_t MASK_MODE_DRDY_SEL = 0x000C; -static constexpr uint16_t MASK_MODE_DRDY_HiZ = 0x0002; -static constexpr uint16_t MASK_MODE_DRDY_FMT = 0x0001; - -// Mask Register CLOCK -static constexpr uint16_t MASK_CLOCK_CH7_EN = 0x8000; -static constexpr uint16_t MASK_CLOCK_CH6_EN = 0x4000; -static constexpr uint16_t MASK_CLOCK_CH5_EN = 0x2000; -static constexpr uint16_t MASK_CLOCK_CH4_EN = 0x1000; -static constexpr uint16_t MASK_CLOCK_CH3_EN = 0x0800; -static constexpr uint16_t MASK_CLOCK_CH2_EN = 0x0400; -static constexpr uint16_t MASK_CLOCK_CH1_EN = 0x0200; -static constexpr uint16_t MASK_CLOCK_CH0_EN = 0x0100; -static constexpr uint16_t MASK_CLOCK_OSR = 0x001C; -static constexpr uint16_t MASK_CLOCK_PWR = 0x0003; -static constexpr uint32_t MASK_CLOCK_ALL_CH_DISABLE = 0x0000; -static constexpr uint32_t MASK_CLOCK_ALL_CH_ENABLE = 0xFF00; -// Mask Register GAIN -static constexpr uint16_t MASK_GAIN_PGAGAIN7 = 0x7000; -static constexpr uint16_t MASK_GAIN_PGAGAIN6 = 0x0700; -static constexpr uint16_t MASK_GAIN_PGAGAIN5 = 0x0070; -static constexpr uint16_t MASK_GAIN_PGAGAIN4 = 0x0007; -static constexpr uint16_t MASK_GAIN_PGAGAIN3 = 0x7000; -static constexpr uint16_t MASK_GAIN_PGAGAIN2 = 0x0700; -static constexpr uint16_t MASK_GAIN_PGAGAIN1 = 0x0070; -static constexpr uint16_t MASK_GAIN_PGAGAIN0 = 0x0007; - -// Mask Register CFG -static constexpr uint16_t MASK_CFG_GC_DLY = 0x1E00; -static constexpr uint16_t MASK_CFG_GC_EN = 0x0100; -static constexpr uint16_t MASK_CFG_CD_ALLCH = 0x0080; -static constexpr uint16_t MASK_CFG_CD_NUM = 0x0070; -static constexpr uint16_t MASK_CFG_CD_LEN = 0x000E; -static constexpr uint16_t MASK_CFG_CD_EN = 0x0001; - -// Mask Register THRSHLD_MSB - dummy, for completeness -static constexpr uint16_t MASK_THRSHLD_MSB_CD_TH_MSB = 0xFFFF; - -// Mask Register THRSHLD_LSB -static constexpr uint16_t MASK_THRSHLD_LSB_CD_TH_LSB = 0xFF00; -static constexpr uint16_t MASK_THRSHLD_LSB_DCBLOCK = 0x000F; - -// Mask Register CHX_CFG -static constexpr uint16_t MASK_CHX_CFG_PHASE = 0xFFC0; -static constexpr uint16_t MASK_CHX_CFG_DCBLKX_DIS = 0x0004; -static constexpr uint16_t MASK_CHX_CFG_MUX = 0x0003; - -// Mask Register CHX_OCAL_MSB - dummy, for completeness -static constexpr uint16_t MASK_CHX_OCAL_MSB = 0xFFFF; - -// Mask Register CHX_OCAL_LSB -static constexpr uint16_t MASK_CHX_OCAL_LSB = 0xFF00; - -// Mask Register CHX_GCAL_MSB - dummy, for completeness -static constexpr uint16_t MASK_CHX_GCAL_MSB = 0xFFFF; - -// Mask Register CHX_GCAL_LSB -static constexpr uint16_t MASK_CHX_GCAL_LSB = 0xFF00; - -// -------------------------------------------------------------------- - -// Conversion modes -static constexpr uint16_t CONVERSION_MODE_CONT = 0; -static constexpr uint16_t CONVERSION_MODE_SINGLE = 1; - -// Data Format -static constexpr uint16_t DATA_FORMAT_TWO_COMPLEMENT = 0; -static constexpr uint16_t DATA_FORMAT_BINARY = 1; - -// Measure Mode -static constexpr uint8_t MEASURE_UNIPOLAR = 1; -static constexpr uint8_t MEASURE_BIPOLAR = 0; - -// Clock Type -static constexpr uint8_t CLOCK_EXTERNAL = 1; -static constexpr uint8_t CLOCK_INTERNAL = 0; - -// PGA Gain -// static constexpr uint16_t PGA_GAIN_1 = 0; -// static constexpr uint16_t PGA_GAIN_2 = 1; -// static constexpr uint16_t PGA_GAIN_4 = 2; -// static constexpr uint16_t PGA_GAIN_8 = 3; -// static constexpr uint16_t PGA_GAIN_16 = 4; -// static constexpr uint16_t PGA_GAIN_32 = 5; -// static constexpr uint16_t PGA_GAIN_64 = 6; -// static constexpr uint16_t PGA_GAIN_128 = 7; - -// Input Filter -static constexpr uint16_t FILTER_SYNC = 0; -static constexpr uint16_t FILTER_FIR = 2; -static constexpr uint16_t FILTER_FIR_IIR = 3; - -// Data Mode -static constexpr uint8_t DATA_MODE_24BITS = 0; -static constexpr uint8_t DATA_MODE_32BITS = 1; - -// Data Rate -static constexpr uint8_t DATA_RATE_0 = 0; -static constexpr uint8_t DATA_RATE_1 = 1; -static constexpr uint8_t DATA_RATE_2 = 2; -static constexpr uint8_t DATA_RATE_3 = 3; -static constexpr uint8_t DATA_RATE_4 = 4; -static constexpr uint8_t DATA_RATE_5 = 5; -static constexpr uint8_t DATA_RATE_6 = 6; -static constexpr uint8_t DATA_RATE_7 = 7; -static constexpr uint8_t DATA_RATE_8 = 8; -static constexpr uint8_t DATA_RATE_9 = 9; -static constexpr uint8_t DATA_RATE_10 = 10; -static constexpr uint8_t DATA_RATE_11 = 11; -static constexpr uint8_t DATA_RATE_12 = 12; -static constexpr uint8_t DATA_RATE_13 = 13; -static constexpr uint8_t DATA_RATE_14 = 14; -static constexpr uint8_t DATA_RATE_15 = 15; -// Sync Mpdes -static constexpr uint16_t SYNC_CONTINUOUS = 1; -static constexpr uint16_t SYNC_PULSE = 0; - -// DIO Config Mode -static constexpr uint8_t DIO_OUTPUT = 1; -static constexpr uint8_t DIO_INPUT = 0; - -static constexpr uint8_t SPI_MASTER_DUMMY = 0xFF; -static constexpr uint16_t SPI_MASTER_DUMMY16 = 0xFFFF; -static constexpr uint32_t SPI_MASTER_DUMMY32 = 0xFFFFFFFF; - - -// end of from datasheet - -class ADS131M08Sensor : public sensor::Sensor, public Component -{ -}; - -class ADS131M08Hub : public Component, public spi::SPIDevice -{ - public: - // from datasheet pg. 93: - const int numFrameWords = 10; // Number of words in a full ADS131M08 SPI frame - unsigned long spiDummyWord[10 /*numFrameWords*/] = { - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000 - }; // Dummy word frame to write ADC during ADC data reads - bool firstRead = true; // Flag to tell us if we are reading ADC data for the first time - signed long adcData; // Location where DMA will store ADC data in memory, length defined elsewhere - void txf_init(); - bool adcRegisterWrite(unsigned short addrMask, unsigned short data, unsigned char adcWordLength); - void initialize_ads131m08_datasheet(); - // end of from datasheet - - bool setChannelPGA(uint8_t channel, uint8_t pga); - void setup() override; - void loop() override; - void set_drdy_pin(InternalGPIOPin *pin) { drdy_pin_ = pin; } - void register_sensor(int channel, ADS131M08Sensor *s) { sensors_[channel] = s; } - void set_reference_voltage(float reference_voltage) { this->reference_voltage_ = reference_voltage; } - void dump_config() override; - - // from tpcorrea - void begin(uint8_t clk_pin, uint8_t miso_pin, uint8_t mosi_pin, uint8_t cs_pin, uint8_t drdy_pin, uint8_t reset_pin); - int8_t isDataReadySoft(uint8_t channel); - bool isDataReady(void); - bool isResetStatus(void); - bool isLockSPI(void); - bool setDrdyFormat(uint8_t drdyFormat); - bool setDrdyStateWhenUnavailable(uint8_t drdyState); - bool setPowerMode(uint8_t powerMode); - bool setChannelEnable(uint8_t channel, uint16_t enable); - bool setChannelPGA(uint8_t channel, ADS131M08_PGA_GAIN pga); - ADS131M08_PGA_GAIN getChannelPGA(uint8_t channel); - void setGlobalChop(uint16_t global_chop); - void setGlobalChopDelay(uint16_t delay); - bool setInputChannelSelection(uint8_t channel, uint8_t input); - bool setChannelOffsetCalibration(uint8_t channel, int32_t offset); - bool setChannelGainCalibration(uint8_t channel, uint32_t gain); - bool setOsr(uint16_t osr); - void setFullScale(uint8_t channel, float scale); - float getFullScale(uint8_t channel); - void reset(); - uint16_t getId(); - uint16_t getModeReg(); - uint16_t getClockReg(); - uint16_t getCfgReg(); - AdcOutput readAdcRaw(void); - AdcOutput readAdcFloat(void); - - protected: - float reference_voltage_; - InternalGPIOPin *drdy_pin_; - ADS131M08Sensor *sensors_[8] = {nullptr}; - volatile bool data_ready_{false}; - static void isr(ADS131M08Hub *arg); - void read_data_(); - void write_register(uint8_t reg, uint16_t value); - -}; - -} // namespace ads131m08 -} // namespace esphome \ No newline at end of file +#pragma once +// using external voltage reference + +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "esphome/core/optional.h" +#include "ads131m08_defs.h" +#include "frame.h" +#include +#include +#include + +//#include "esphome/core/component.h" +//#include +#include "esphome/components/spi/spi.h" +#include "esphome/components/sensor/sensor.h" +// +//#include +//#include +//#include +//#include +//#include + +namespace esphome { +namespace ads131m08 { + +static const int MAX_CHANNELS = 11; // for debugging +static const int ADC_CHANNELS = 8; +static const int SAMPLE_TIME_SENSOR = 8; // for debugging +static const int MAX_SAMPLES_SENSOR = 9; // for debugging +static const int CRC_ERRORS_SENSOR = 10; // for debugging + +typedef union { + + int32_t i; + float f; + uint16_t u[2]; + uint8_t b[4]; +} flex32_t; + + +/* Adc Structure. Ch can be read as int32 or float*/ +struct AdcOutput +{ + uint16_t status; + flex32_t ch[ADC_CHANNELS]; +}; + +//#define SET_IRAM IRAM_ATTR +#define SET_IRAM + +typedef std::vector spiframe; +typedef uint_str::Ty_string uint16_str; // we want to use the stack to pass small arrays + +class ADS131M08Hub : public Component, public spi::SPIDevice +{ + friend class ads131m08_select; + public: + enum crc_pos: uint8_t { + crc_after_frame, + crc_after_firstword, + }; + // register config values used + static constexpr uint16_t reg_clock_cfg = MASK_CLOCK_EXTREF_EN | OSR_1024 | PM_HIGH_RESOLUTION; + static constexpr uint16_t reg_clock_allch_on = MASK_CLOCK_ALLCH | reg_clock_cfg; + static constexpr uint16_t reg_clock_allch_off = ~MASK_CLOCK_ALLCH & reg_clock_cfg; + static constexpr uint16_t reg_mode_cfg16 = WLENGTH_16_BITS | DRDY_FMT_PULSE | TIMEOUT_ENABLED; + static constexpr uint16_t reg_mode_cfg24 = WLENGTH_24_BITS | DRDY_FMT_PULSE | TIMEOUT_ENABLED; + static constexpr uint16_t reg_mode_cfg32 = WLENGTH_32_BITS_MSB_SIGN_EXTEND | DRDY_FMT_PULSE | TIMEOUT_ENABLED; + // masks for the above + static constexpr uint16_t reg_clock_cfg_mask = MASK_CLOCK_EXTREF_EN | MASK_CLOCK_OSR | MASK_CLOCK_PWR; + static constexpr uint16_t reg_clock_allch_mask = MASK_CLOCK_ALLCH_OFF | reg_clock_cfg_mask; + static constexpr uint16_t reg_mode_cfg_mask = MASK_MODE_WLENGTH | MASK_MODE_DRDY_FMT | MASK_MODE_TIMEOUT; + // Semaphore handles + SemaphoreHandle_t data_ready_semhandle = NULL; + //SemaphoreHandle_t spi_mutex = NULL; + // from datasheet pg. 93: + const int numFrameWords = 10; // Number of words in a full ADS131M08 SPI frame + spiframe base_frame; + bool firstRead = true; // Flag to tell us if we are reading ADC data for the first time + signed long adcData; // Location where DMA will store ADC data in memory, length defined elsewhere + ADS131M08Hub(); + void txf_init(); + bool adc_initialize(uint8_t word_length); + bool adc_set_word_length(uint8_t word_length); + // end of from datasheet + void set_channel_gain(uint8_t channel, ADS131M08_PGA_GAIN gain); + //virtual void spi_setup() override; + void setup() override; + void loop() override; + void set_drdy_pin(InternalGPIOPin *pin) { drdy_pin_ = pin; } + void set_sync_reset_pin(InternalGPIOPin *pin) { sync_reset_pin_ = pin; } + void set_clock_frequency(float clock_frequency) { this->clock_frequency_ = clock_frequency; } + void register_sensor_ac(int channel, sensor::Sensor *s) { sensors_ac[channel] = s; } + void register_sensor_dc(int channel, sensor::Sensor *s) { sensors_dc[channel] = s; } + void set_reference_voltage(float reference_voltage) { this->reference_voltage_ = reference_voltage; } + void set_osr(uint16_t osr) { this->osr_ = osr; } + void dump_config() override; + + // from tpcorrea + ADS131M08_PGA_GAIN pgaGain[ADC_CHANNELS] = {PGA_1}; // Store the current PGA gain settings for each channel + int8_t is_data_ready_soft(uint8_t channel); + bool is_data_ready(); + bool is_reset_status(); + bool is_lock_spi(); + bool set_drdy_format(uint8_t drdy_format); + bool set_drdy_idle_state(uint8_t drdy_state); + bool set_power_mode(uint8_t power_mode); + bool set_channel_enable(uint8_t channel, bool enable); + bool set_channel_pga(uint8_t channel, ADS131M08_PGA_GAIN pga); + ADS131M08_PGA_GAIN get_channel_pga(uint8_t channel); + void set_global_chop(uint16_t global_chop); + void set_global_chop_delay(uint16_t delay); + bool set_channel_input_selection(uint8_t channel, ADS131M08_INPUT_CHANNEL_MUX input); + bool set_channel_offset_calibration(uint8_t channel, int32_t offset); + bool set_channel_gain_calibration(uint8_t channel, uint32_t gain); + bool set_channel_phase_calibration(uint8_t channel, int16_t phase_offset); + bool set_dcblock_filter_disable(uint8_t channel, bool disable); + void set_measure_rms(uint8_t channel, bool enable); + float get_sampled_value(uint8_t channel) { return sampled_values_[channel]; } + bool set_reg_osr(); + void set_full_scale(uint8_t channel, float scale); + float get_full_scale(uint8_t channel); + uint16_t get_id(); + uint16_t get_mode_reg(); + uint16_t get_clock_reg(); + uint16_t get_cfg_reg(); + AdcOutput read_adc_raw(); + AdcOutput read_adc_float(); + + protected: + static void IRAM_ATTR isr_handler(ADS131M08Hub *arg); + //volatile bool data_ready_{false}; + //static void isr(ADS131M08Hub *arg); + float reference_voltage_; + float clock_frequency_; + InternalGPIOPin *drdy_pin_; + InternalGPIOPin *sync_reset_pin_= {nullptr}; + sensor::Sensor *sensors_ac[MAX_CHANNELS] = {nullptr}; + sensor::Sensor *sensors_dc[MAX_CHANNELS] = {nullptr}; + float sampled_values_[ADC_CHANNELS]; + bool rms_enabled_[ADC_CHANNELS]; + bool rms_calc_req_{false}; + uint16_t osr_{3}; + uint8_t update_adc_word_length(); + void SET_IRAM read_single(); + std::pair SET_IRAM read_multi(); + bool adc_lock(bool enable); + bool adc_register_write(uint16_t address, uint16_t data); + bool adc_register_write(uint16_t address, const uint16_str& data); + uint16_str adc_register_read(uint8_t address, uint8_t nregs); + bool adc_register_write_masked(uint8_t address, uint16_t value, uint16_t mask, int line); + bool adc_register_write_masked(uint8_t start_address, const uint16_str& values, const uint16_str& masks, int line); + uint16_t readRegister(uint8_t address); + bool adc_reset_retry(); + bool adc_soft_reset(); + void adc_hard_reset(); + void adc_sync(); + void write_byte(uint8_t byte); + bool write(uint32_t data, uint8_t adcWordLength); + uint8_t read_byte(); + void write_array(const spiframe& data); + void SET_IRAM read_array(spiframe& buffer); + void transfer_array(const spiframe& data_out, spiframe& data_in); + uint16_t SET_IRAM get_crc(const spiframe& frame); + bool SET_IRAM check_crc(const spiframe& frame_with_crc); + size_t SET_IRAM add_crc(spiframe& frame, crc_pos crcpos); + uint16_t SET_IRAM read_frame_crc(const spiframe& frame); + uint16_t SET_IRAM crc(uint16_t crc_register, uint8_t data); + bool SET_IRAM set_frame_word(spiframe& frame, int w_index, uint16_t data); // write 16 bit data to first two bytes of word + bool SET_IRAM set_frame_word(spiframe& frame, int w_index, uint32_t data); + uint32_t SET_IRAM get_unsigned_frame_word(const spiframe& frame, int w_index, bool force_16bits = false); + int32_t SET_IRAM get_sign_ext_frame_word(const spiframe& frame, int w_index); + std::atomic adc_init_{0}; + std::atomic cs_ctr_{0}; // Counter to track nested CS enable/disable calls for proper handling of multiple transfers in a row + std::atomic isr_ctr_{0}; + void enable(const char *txt); + void disable(const char *txt); + struct chipselect { + ADS131M08Hub* parent_{nullptr}; + const char *txt_; + chipselect(ADS131M08Hub *parent, const char *txt = nullptr) { + parent_ = parent; + if(parent_ != nullptr) + txt_ = txt; + parent_->enable(txt_); + } + ~chipselect() { + if(parent_ != nullptr) { + parent_->disable(txt_); + parent_ = nullptr; + } + } + }; + private: + + uint8_t adc_word_length_{24}; + float conversion_factor_{1.2/8388608.0}; + const uint32_t sample_time_ = 40000; // 250 ms + float sps_{0.0f}; + uint32_t num_samples_[ADC_CHANNELS]; + int64_t sample_sum_[ADC_CHANNELS]; + float sample_squared_sum_[ADC_CHANNELS]; +// for debug only + std::string frame_to_string(const spiframe& frame); + std::string conversion_frame_to_string(const spiframe& frame); + std::string command_to_string(uint16_t cmdadr); + void print_command_response_to_string(uint16_t cmdadr_sent, const spiframe& frame); + std::string rwreg_command_frame_to_string(const spiframe& frame); + std::string status_to_string(uint16_t response); + std::string reg_data_to_string(int address, uint16_t data, bool nameonly = false); + std::string reg_addr_to_string(int address); + std::string reg_config_to_string(int address, uint16_t data, bool nameonly = false); + //temp + int first_read_data {0}; +}; + +#ifndef CHIP_SELECT +# if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE +# define CHIP_SELECT volatile chipselect cs(this, __FUNCTION__); +# else +# define CHIP_SELECT volatile chipselect cs(this); +# endif +# define CHIP_SELECTx volatile chipselect cs(this); +#endif + +} // namespace ads131m08 +} // namespace esphome + diff --git a/components/ds3231/ds3231.cpp b/components/ds3231/ds3231.cpp index 7f0da7c..6bb5185 100644 --- a/components/ds3231/ds3231.cpp +++ b/components/ds3231/ds3231.cpp @@ -23,7 +23,7 @@ void DS3231Component::dump_config() { if (this->is_failed()) { ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); } - ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str()); +// ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str()); if (!this->read_bytes(0, this->ds3231_.raw, sizeof(this->ds3231_.raw))) { ESP_LOGE(TAG, "Can't read I2C data."); } @@ -50,7 +50,8 @@ void DS3231Component::read_time() { .year = uint16_t(ds3231_.reg.year + 10u * ds3231_.reg.year_10 + 2000), .is_dst = false, // not used .timestamp = 0 // overwritten by recalc_timestamp_utc(false) - }; + + }; rtc_time.recalc_timestamp_utc(false); if (!rtc_time.is_valid()) { ESP_LOGE(TAG, "Invalid RTC time, not syncing to system clock."); @@ -79,7 +80,7 @@ void DS3231Component::write_time() { ds3231_.reg.second = now.second % 10; ds3231_.reg.second_10 = now.second / 10; ds3231_.reg.ch = false; - + // ESPHOME emits warning about synchronous mode; must check datasheet to see if this is a problem for us. Datasheet pg. 6 says "The DS3231 is designed to operate in a synchronous mode, where the timekeeping registers are updated at the end of each second. This ensures that the timekeeping registers always contain valid data and eliminates the need for software to handle potential timing issues when reading or writing the registers." this->write_rtc_(); } diff --git a/components/tlc59208f_ext/tlc59208f_output.cpp b/components/tlc59208f_ext/tlc59208f_output.cpp index 638c7fb..3d740f0 100644 --- a/components/tlc59208f_ext/tlc59208f_output.cpp +++ b/components/tlc59208f_ext/tlc59208f_output.cpp @@ -77,30 +77,30 @@ void TLC59208FOutput::setup() { auto errcode = this->bus_->write_readv(TLC59208F_SWRST_ADDR >> 1, TLC59208F_SWRST_SEQ, sizeof TLC59208F_SWRST_SEQ, nullptr, 0); if (errcode != i2c::ERROR_OK) { ESP_LOGE(TAG, "RESET failed"); - this->mark_failed("RESET failed"); + this->mark_failed(LOG_STR("RESET failed")); return; } // Auto increment registers, and respond to all-call address if (!this->write_byte(TLC59208F_REG_MODE1, TLC59208F_MODE1_AI2 | TLC59208F_MODE1_ALLCALL)) { ESP_LOGE(TAG, "MODE1 failed"); - this->mark_failed("MODE1 failed"); + this->mark_failed(LOG_STR("MODE1 failed")); return; } if (!this->write_byte(TLC59208F_REG_MODE2, this->mode_)) { ESP_LOGE(TAG, "MODE2 failed"); - this->mark_failed("MODE2 failed"); + this->mark_failed(LOG_STR("MODE2 failed")); return; } // Set all 3 outputs to be individually controlled // TODO: think of a way to support group dimming if (!this->write_byte(TLC59208F_REG_LEDOUT0, (LDR_PWM << 6) | (LDR_PWM << 4) | (LDR_PWM << 2) | (LDR_PWM << 0))) { ESP_LOGE(TAG, "LEDOUT0 failed"); - this->mark_failed("LEDOUT0 failed"); + this->mark_failed(LOG_STR("LEDOUT0 failed")); return; } if (!this->write_byte(TLC59208F_REG_LEDOUT1, (LDR_PWM << 6) | (LDR_PWM << 4) | (LDR_PWM << 2) | (LDR_PWM << 0))) { ESP_LOGE(TAG, "LEDOUT1 failed"); - this->mark_failed("LEDOUT1 failed"); + this->mark_failed(LOG_STR("LEDOUT1 failed")); return; } delayMicroseconds(500); diff --git a/source/solar/cb_frame.h b/source/solar/cb_frame.h index 0646610..94d04a7 100644 --- a/source/solar/cb_frame.h +++ b/source/solar/cb_frame.h @@ -2,6 +2,7 @@ #define __SOLAR_CB_FRAME #include "esphome.h" #include +#include #include "common.h" using namespace esphome; diff --git a/source/solar/common.h b/source/solar/common.h index ae88eaf..3ceb9b1 100644 --- a/source/solar/common.h +++ b/source/solar/common.h @@ -12,8 +12,8 @@ namespace solar #define SEARCHWILD 0x0100000 // do wildcard comparison (only searchkey should contain wildcards) #define num_compare(a, b) ((a) > (b)) ? +1 : ((a) < (b)) ? -1 : 0 #define bool_compare(a, b) ((!(a) && !(b)) || ((a) && (b))) ? 0 : (a) ? +1 : -1 - #define max(a, b) ((a) > (b)) ? (a) : (b) - #define min(a, b) ((a) < (b)) ? (a) : (b) + #define mmax(a, b) ((a) > (b)) ? (a) : (b) + #define mmin(a, b) ((a) < (b)) ? (a) : (b) typedef unsigned int uint; typedef std::vector byte_vector; diff --git a/sthome-ut10.yaml b/sthome-ut10.yaml index f7bd674..c59b427 100644 --- a/sthome-ut10.yaml +++ b/sthome-ut10.yaml @@ -1,43 +1,54 @@ +###### sthome-ut10 ###### + +external_components: + - source: + type: local + path: components # Path relative to this YAML file + components: [ ds3231, tlc59208f_ext, ads131m08 ] #, ac_voltage_sensor ] + packages: - - !include common/wifi.yaml + - !include common/wifi.yaml + - !include common/geyser.yaml + - !include common/felicityinverter.yaml + - !include common/modbus.yaml + - !include common/gpio_assignments.yaml - !include common/canbus.yaml +# - device: !include device.yaml substitutions: name: sthome-ut10 friendly_name: "sthome-ut10" - #ALLOWED_CHARACTERS_FULL: " !#%\"'()+,-./0123456789:;<>?@ABCDEFGHIJKLMNOPQRSTUVWYZ[]_abcdefghijklmnopqrstuvwxyz{|}°²³µ¿ÁÂÄÅÉÖÚßàáâãäåæçèéêëìíîðñòóôõöøùúûüýþāăąćčďĐđēėęěğĮįıļľŁłńňőřśšťũūůűųźŻżŽžơưșțΆΈΌΐΑΒΓΔΕΖΗΘΚΜΝΠΡΣΤΥΦάέήίαβγδεζηθικλμνξοπρςστυφχψωϊόύώАБВГДЕЖЗИКЛМНОПРСТУХЦЧШЪЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяёђєіїјљњћ" - ALLOWED_CHARACTERS: " !#%\"'()+,-./0123456789:;<>?@ABCDEFGHIJKLMNOPQRSTUVWYZ[]_abcdefghijklmnopqrstuvwxyz{|}°²³µ•" - DD_MAX_YEARS: "5" - - -globals: - - id: g_month_idx - type: int - restore_value: yes - initial_value: '0' - - id: g_year_idx - type: int - restore_value: yes - initial_value: '0' - - id: g_options_year - type: char[1 + ${DD_MAX_YEARS} * 5] - restore_value: yes -# initial_value: "{2022\n2023\n2024\n2025\n2026}" - -# CAN BUS - - id: can_msgctr - type: int - restore_value: no - - id: g_cb_cache - type: solar::cbf_cache - restore_value: no + MAX_SCHOOL_HOLIDAY_PERIODS: 12 + BATTERY_INFO_TIMEOUT: 30 # 30 sec timeout + GEYSER_HEAT_IND_MIN: 20 + GEYSER_HEAT_IND_MAX: 70 + ENERGY_RESET_PIN: GPIO0 + CB1CS_PIN: GPIO3 + CB2CS_PIN: GPIO10 + RELAY1_PIN: GPIO4 + RELAY2_PIN: GPIO5 + RELAY3_PIN: GPIO6 + RELAY4_PIN: GPIO7 + HEAT_SENSOR_PIN: GPIO14 + ADC_SYNC_RESET_PIN: GPIO21 + ADC_CS_PIN: GPIO41 + ADC_DRDY_PIN: GPIO42 + SYSTEM_OK_LED_PIN: GPIO43 + ECONOMY_MODE_LED_PIN: GPIO44 + ECONOMY_MODE_SWITCH_PIN: GPIO45 + LOW_BATTERY_SENSOR_PIN: GPIO46 + LOW_BATTERY_LED_PIN: GPIO47 + MAINS_FAIL_LED_PIN: GPIO48 + LED_RESET_PIN: GPIO19 # temporary - to debug TLC59208F esphome: name: "${name}" friendly_name: "${friendly_name}" + #libraries: + # - "SPI" includes: - source # copies folder with files to relevant to be included in esphome compile - - # angle brackets ensure file is included above globals in main.cpp. Make sure to use include GUARDS in the file to prevent double inclusion + - # angle brackets ensure file is included above globals in main.cpp. Make sure to use include GUARDS in the file to prevent double inclusion - - - @@ -47,16 +58,38 @@ esphome: on_boot: - priority: 600 # This is where most sensors are set up (higher number means higher priority) then: - #- ds1307.read_time: # read the RTC time + - ds3231.read_time: # read the RTC time - lambda: |- - id(can_msgctr) = 0; - id(update_heating_display).execute(false); + id(light_economy_mode).turn_off(); + id(light_mains_fail).turn_off().set_brightness(0).perform(); + id(light_system_ok).turn_off().set_brightness(0).perform(); + id(light_inverter_battery_low).turn_off(); + - delay: 300ms + +globals: + - id: time_synched + type: bool + restore_value: no + initial_value: 'false' + - id: can1_msgctr + type: int + restore_value: no + - id: can2_msgctr + type: int + restore_value: no + - id: g_cb_cache # the cache is used to only accept a frame after it has been received a specified number of times within a specified period. this hopefully will iron out spurious corrupted frames + type: solar::cbf_cache + restore_value: no + - id: g_cb_request_queue + type: std::queue< std::set > + restore_value: no esp32: board: esp32-s3-devkitc-1 flash_size: 16MB + cpu_frequency: 240MHz framework: - type: esp-idf + type: esp-idf #arduino # # sdkconfig_options: # CONFIG_ESP32_S3_BOX_BOARD: "y" @@ -64,18 +97,58 @@ psram: mode: octal speed: 80MHz +debug: + update_interval: 15s + # Enable logging logger: - level: very_verbose - initial_level: very_verbose + baud_rate: 115200 + level: INFO + initial_level: INFO logs: - canbus: very_verbose - touchscreen: INFO - xpt2046: INFO - script: INFO - lvgl: INFO - ili9xxx: INFO +# i2c: DEBUG +# i2c.idf: DEBUG +# uart: INFO +# light: INFO + sensor: INFO +# ds3231: DEBUG +# tlc59208f: DEBUG +# tlc59208f_ext: DEBUG +# ads131m08: DEBUG +# ads131m08_sensor: DEBUG +# spi: DEBUG +# spi_device: DEBUG +# canbus: DEBUG +# canbus_mcp2515: DEBUG +# mcp2515: DEBUG +# modbus: INFO +# modbus_controller: INFO +# modbus_controller.sensor: INFO +# text_sensor: INFO +# ac_voltage_sensor: INFO +# uart_debug: INFO # If set to DEBUG, logs raw UART data in hex format, with direction indication (RX/TX) and timestamp. Can be very verbose, so use with caution. +# uart.idf: INFO +# logger: INFO +# switch.gpio: INFO +# ledc.output: INFO +# gpio.binary_sensor: INFO +# template.binary_sensor: INFO +# restart: INFO +# status: INFO +# wifi_info: INFO +# homeassistant.time: INFO +# time: INFO +# captive_portal: INFO +# wifi: INFO +# web_server: INFO +# esphome.ota: INFO +# safe_mode: INFO +# web_server.ota: INFO +# api: INFO +# wifi_signal.sensor: INFO +# mdns: INFO + # Enable Home Assistant API api: encryption: @@ -97,155 +170,265 @@ wifi: captive_portal: -time: - #- platform: ds1307 - # repeated synchronization is not necessary unless the external RTC - # is much more accurate than the internal clock - # update_interval: never - - - platform: homeassistant - id: time_source - #update_interval: 360min # Change sync interval from default 5min to 6 hours - on_time_sync: - then: - #- ds1307.write_time: - - if: # Publish the time the device was last restarted, but only once. - condition: - lambda: 'return id(device_last_restart).state == "";' - then: - - text_sensor.template.publish: - id: device_last_restart - state: !lambda 'return id(time_source).now().strftime("%a %d %b %Y - %I:%M:%S %p");' +one_wire: + - platform: gpio + pin: ${HEAT_SENSOR_PIN} + id: geyser_temperature_sensors -# - script.execute: time_update -# - script.execute: init_calendar +i2c: + - id: bus_a + sda: ${ESP32_S3_N16R8_DEVKITC_I2C1_SDA} + scl: ${ESP32_S3_N16R8_DEVKITC_I2C1_SCL} + scan: true + frequency: 400kHz + - id: bus_b + sda: ${ESP32_S3_N16R8_DEVKITC_I2C2_SDA} + scl: ${ESP32_S3_N16R8_DEVKITC_I2C2_SCL} + scan: true + frequency: 20kHz + sda_pullup_enabled: true + scl_pullup_enabled: true -# on_time: -# - minutes: '*' -# seconds: '*' -# then: -# - script.execute: time_update -# - lambda: |- -# id(get_calendar_days_state).execute("T"); - #- script.execute: get_calendar_days_state -# -# - hours: 1,2,3,4 -# minutes: 5 -# seconds: 0 -# then: -# - switch.turn_on: switch_antiburn -# - hours: 1,2,3,4 -# minutes: 35 -# seconds: 0 -# then: -# - switch.turn_off: switch_antiburn +# Example configuration entry +#web_server: +# port: 80 +# include_internal: true -font: - - file: "gfonts://Roboto" - id: roboto_200 - size: 200 - bpp: 4 - glyphs: [ - 0123456789,.,°,a,n, - "\u0020", # space - "\u002D", # minus - "\u003A", # colon - "\u003F", # question mark - ] - - file: "gfonts://Roboto" - id: roboto_192 - size: 192 - bpp: 4 - glyphs: [ - 0123456789,.,°,a,n, - "\u0020", # space - "\u002D", # minus - "\u003A", # colon - "\u003F", # question mark - ] - - file: "gfonts://Roboto" - id: geyser_temperature_font2 - size: 60 - bpp: 4 - glyphs: [ - °,C, - ] - - file: "gfonts://Roboto" - id: geyser_temperature_font3 - size: 30 - bpp: 4 - glyphs: [ - b,o,m,p,t, - ] - - file: "fonts/misc/materialdesignicons-webfont.ttf" - id: font_icon_small - size: 24 #45 - glyphs: [ - "\U0000F5A9", - ] +spi: + - id: spi_bus0 + clk_pin: ${ESP32_S3_N16R8_DEVKITC_SPI1_SCK} + mosi_pin: ${ESP32_S3_N16R8_DEVKITC_SPI1_MOSI} + miso_pin: ${ESP32_S3_N16R8_DEVKITC_SPI1_MISO} + interface: any + - id: spi_bus1 + clk_pin: ${ESP32_S3_N16R8_DEVKITC_SPI2_SCK} + mosi_pin: ${ESP32_S3_N16R8_DEVKITC_SPI2_MOSI} + miso_pin: ${ESP32_S3_N16R8_DEVKITC_SPI2_MISO} + interface: hardware -color: - - id: grey_light - hex: 'e0e0e0' +ads131m08: + id: highres_adc + spi_id: spi_bus1 + cs_pin: ${ADC_CS_PIN} + drdy_pin: ${ADC_DRDY_PIN} + sync_reset_pin: ${ADC_SYNC_RESET_PIN} + reference_voltage: 1.25 + data_rate: 10MHz + clock_frequency: 8192kHz + oversampling_ratio: 512 -image: -# - file: https://esphome.io/_static/favicon-512x512.png -# id: boot_logo -# resize: 200x200 -# type: RGB565 -# transparency: alpha_channel - - file: mdi:fire - id: icon_fire - resize: 100x100 - type: BINARY - - file: mdi:transmission-tower - id: icon_utility - resize: 80x80 - type: BINARY +sensor: +# # 30A clamp +# - platform: ct_clamp +# sensor: ads_ch2 +# id: current2 +# name: "ACL Current2" +# update_interval: 5s +# sample_duration: 200ms +# state_class: measurement +# device_class: current +# filters: +# # burden resistor is 62Ω in parallel with 33Ω = 21.54Ω +# # multiplier should be 1860/21.54 = x86.35 +# - multiply: 100 #86.35 +# # 30A clamp +# - platform: ct_clamp +# sensor: ads_ch3 +# id: current3 +# name: "ACL Current3" +# update_interval: 5s +# sample_duration: 200ms +# state_class: measurement +# device_class: current +# filters: +# # burden resistor is 62Ω in parallel with 33Ω = 21.54Ω +# # multiplier should be 1860/21.54 = x86.35 +# - multiply: 100 #86.35 + + - platform: ads131m08 + ads131m08_id: highres_adc + channel: 0 + accuracy_decimals: 6 + state_class: measurement + device_class: voltage + name: "ads_ch0" + gain: 1 + input_select: normal + offset_calibration: 0 + gain_calibration: 0.98587182 + + - platform: ads131m08 + ads131m08_id: highres_adc + channel: 1 + accuracy_decimals: 6 + state_class: measurement + device_class: voltage + name: "ads_ch1" + gain: 1 + input_select: normal + offset_calibration: 0 + gain_calibration: 0.98642854 + + - platform: ads131m08 + ads131m08_id: highres_adc + channel: 2 + accuracy_decimals: 6 + state_class: measurement + device_class: voltage + id: ads_ch2 + name: "ads_ch2" + gain: 1 + input_select: normal + offset_calibration: 0 + gain_calibration: 1.36153846 + filters: + - multiply: 102 + read_rms: enable + + - platform: ads131m08 + ads131m08_id: highres_adc + channel: 3 + accuracy_decimals: 6 + state_class: measurement + device_class: voltage + id: ads_ch3 + name: "ads_ch3" + gain: 1 + input_select: normal + offset_calibration: 0 + gain_calibration: 1.36153846 + filters: + - multiply: 102 + read_rms: true + + - platform: ads131m08 + ads131m08_id: highres_adc + channel: 4 + accuracy_decimals: 4 + state_class: measurement + device_class: voltage + name: "ads_ch4" + gain: 1 + input_select: normal + offset_calibration: 0 + gain_calibration: 1.36153846 + read_rms: yes + + - platform: ads131m08 + ads131m08_id: highres_adc + channel: 5 + accuracy_decimals: 4 + state_class: measurement + device_class: voltage + name: "ads_ch5" + gain: 1 + input_select: normal + offset_calibration: 0 + gain_calibration: 1 + read_rms: disable + + - platform: ads131m08 + ads131m08_id: highres_adc + channel: 6 + accuracy_decimals: 4 + state_class: measurement + device_class: voltage + name: "ads_ch6" + gain: 1 + input_select: normal + offset_calibration: 0 + gain_calibration: 1 + read_rms: yes + + - platform: ads131m08 + ads131m08_id: highres_adc + channel: 7 + accuracy_decimals: 4 + state_class: measurement + device_class: voltage + name: "ads_ch7" + gain: 1 + input_select: normal + offset_calibration: 0 + gain_calibration: 1 + read_rms: yes + + - platform: ads131m08 + ads131m08_id: highres_adc + channel: 8 + name: "Sample time" + unit_of_measurement: "ms" + + - platform: ads131m08 + ads131m08_id: highres_adc + channel: 9 + name: "Max samples" + accuracy_decimals: 0 + unit_of_measurement: "" + + - platform: ads131m08 + ads131m08_id: highres_adc + channel: 10 + name: "CRC errors" + accuracy_decimals: 0 + unit_of_measurement: "" + +#sensor: + # Report wifi signal strength every 5 min if changed + - platform: wifi_signal + name: WiFi Signal + update_interval: 300s + filters: + - delta: 10% +uart: + - id: inv_uart1 + tx_pin: ${ESP32_S3_N16R8_DEVKITC_UART1_TX} # Tx1 + rx_pin: + number: ${ESP32_S3_N16R8_DEVKITC_UART1_RX} # Rx1 + inverted: false + mode: + input: true + pullup: false # external pullup + baud_rate: 2400 + stop_bits: 1 + parity: NONE + debug: + direction: BOTH + dummy_receiver: false +# after: +# delimiter: "\r" + sequence: + - lambda: UARTDebug::log_hex(direction, bytes, ','); + + - id: inv_uart2 + tx_pin: ${ESP32_S3_N16R8_DEVKITC_UART2_TX} # Tx2 + rx_pin: + number: ${ESP32_S3_N16R8_DEVKITC_UART2_RX} # Rx2 + inverted: false + mode: + input: true + pullup: false # external pullup + baud_rate: 2400 + stop_bits: 1 + parity: NONE + debug: + direction: BOTH + dummy_receiver: false +# after: +# delimiter: "\r" + sequence: + - lambda: UARTDebug::log_hex(direction, bytes, ' '); sun: id: sun_sensor latitude: !secret latitude longitude: !secret longitude - -interval: - - interval: 30s - then: - - script.execute: send_info_request - - interval: 15s # 500ms - then: - - script.execute: send_quick_update_request - -spi: - - id: spi_bus0 - clk_pin: GPIO12 - mosi_pin: GPIO11 - miso_pin: GPIO13 - interface: any - - - id: spi_bus1 - clk_pin: GPIO42 - mosi_pin: GPIO21 - miso_pin: GPIO20 - interface: any - -i2c: - - id: i2c_bus0 - sda: GPIO8 - scl: GPIO9 - scan: true - -#one_wire: -# - platform: gpio -# pin: GPIO4 -# id: temperature_sensors - -# CAN BUS canbus: - platform: mcp2515 - cs_pin: GPIO10 + cs_pin: ${CB1CS_PIN} # CB1CS spi_id: spi_bus0 id: canbus_sthome + mode: NORMAL can_id: ${CB_CANBUS_ID10} bit_rate: 500KBPS on_frame: @@ -253,1238 +436,394 @@ canbus: can_id_mask: 0 then: - lambda: |- - id(can_msgctr)++; using namespace solar; + id(can1_msgctr)++; auto time_obj = id(time_source).now(); - //ESP_LOGI("CB REC", "%s", id(time_source).now().strftime("%a %d %b %Y - %I:%M:%S %p")); if(time_obj.is_valid()) { if(can_id >= 0x350 && can_id < 0x380) { - auto cbitem = cbf_store_pylon(id(can_msgctr), can_id, x, remote_transmission_request, time_obj.timestamp); + auto cbitem = cbf_store_pylon(id(can1_msgctr), can_id, x, remote_transmission_request, time_obj.timestamp); + //ESP_LOGI(cbitem.tag().c_str(), "%s", cbitem.to_string().c_str()); bool publish = id(g_cb_cache).additem(cbitem); - if(publish) { - //ESP_LOGI(cbitem.tag().c_str(), "%s", cbitem.to_string().c_str()); - switch (can_id) { - case cbf_pylon::CB_BATTERY_STATE: - { - uint soc = static_cast((x[1] << 8) + x[0]); - uint soh = static_cast((x[3] << 8) + x[2]); - ESP_LOGI(cbitem.tag().c_str(), "SOC= %d%% SOH= %d%%", soc, soh); - } - } - } + if(publish) { + ESP_LOGV(cbitem.tag().c_str(), "%s", cbitem.to_string().c_str()); + } } - else if(can_id >= 0x400 && can_id <= 0x580) { - auto cbitem = cbf_store_sthome(id(can_msgctr), can_id, x, remote_transmission_request, time_obj.timestamp); - bool publish = id(g_cb_cache).additem(cbitem); - //ESP_LOGI(cbitem.tag().c_str(), "%s P[%s]", cbitem.to_string().c_str(), publish ? "Y" : "N"); - if(publish) { - ESP_LOGI(cbitem.tag().c_str(), "%s", cbitem.to_string().c_str()); - switch (can_id) { - case cbf_sthome::CB_CONTROLLER_STATES: - { - uint8_t alarms = x[0]; - uint8_t states = x[1]; - uint8_t modes = x[2]; - id(update_heating_display).execute(states & 0x80 != 0); - //if(states & 0x40 == 0) { - // lv_obj_add_flag(id(ind_geyser_relay_on), LV_OBJ_FLAG_HIDDEN); - //} - //else { - // lv_obj_clear_flag(id(ind_geyser_relay_on), LV_OBJ_FLAG_HIDDEN); - //} - if(states & 0x20 == 0) { - lv_obj_add_flag(id(ind_utility_on), LV_OBJ_FLAG_HIDDEN); - } - else { - lv_obj_clear_flag(id(ind_utility_on), LV_OBJ_FLAG_HIDDEN); - } - } - break; - case cbf_sthome::CB_GEYSER_TEMPERATURE_TOP: - { - double temperature = (x[1] == 0xFF && x[0] == 0xFF) ? std::numeric_limits::quiet_NaN() : (double) static_cast(256 * x[1] + x[0]) / 256; - if(!isnan(temperature)) { - id(update_temp_display).execute(temperature, rect_gtoptemp, ind_utility_on, lbl_gtoptemp); - } - } - break; - case cbf_sthome::CB_GEYSER_TEMPERATURE_BOTTOM: - { - double temperature = (x[1] == 0xFF && x[0] == 0xFF) ? std::numeric_limits::quiet_NaN() : (double) static_cast(256 * x[1] + x[0]) / 256; - if(!isnan(temperature)) { - id(update_temp_display).execute(temperature, rect_gbottemp, ind_geyser_heating_on, lbl_gbottemp); - } - } - break; - } - } - } else { - ESP_LOGI("WARN", "CAN ID within unhandled range: 0x%X", can_id); - } + auto cbitem = cbf_store_sthome(id(can1_msgctr), can_id, x, remote_transmission_request, time_obj.timestamp); + ESP_LOGI(cbitem.tag().c_str(), "%s", cbitem.to_string().c_str()); + } } - -display: - - platform: ili9xxx - model: ili9488 - id: tft_display - color_palette: 8BIT - data_rate: 40MHz - spi_id: spi_bus1 - cs_pin: GPIO41 - dc_pin: GPIO39 - reset_pin: GPIO40 - auto_clear_enabled: false + - platform: mcp2515 + cs_pin: ${CB2CS_PIN} # CB2CS + spi_id: spi_bus0 + id: canbus_solarbattery + mode: NORMAL + can_id: ${CB_CANBUS_ID10} + bit_rate: 500KBPS + on_frame: + - can_id: 0 + can_id_mask: 0 + then: + - lambda: |- + using namespace solar; + id(can2_msgctr)++; + auto time_obj = id(time_source).now(); + if(time_obj.is_valid()) { + if(can_id >= 0x350 && can_id < 0x380) { + auto cbitem = cbf_store_pylon(id(can2_msgctr), can_id, x, remote_transmission_request, time_obj.timestamp); + //ESP_LOGI(cbitem.tag().c_str(), "%s", cbitem.to_string().c_str()); + bool publish = id(g_cb_cache).additem(cbitem); + if(publish) { + ESP_LOGV(cbitem.tag().c_str(), "%s", cbitem.to_string().c_str()); + } + } + else { + auto cbitem = cbf_store_sthome(id(can2_msgctr), can_id, x, remote_transmission_request, time_obj.timestamp); + ESP_LOGI(cbitem.tag().c_str(), "%s", cbitem.to_string().c_str()); + } + } +time: + - platform: ds3231 + address: 0x68 + i2c_id: bus_a +# # repeated synchronization is not necessary unless the external RTC +# # is much more accurate than the internal clock update_interval: never - invert_colors: false -# show_test_card: true - transform: - swap_xy: true # landscape -# mirror_x: true # landscape - dimensions: - height: 480 - width: 320 - -# Define a PWM output on the ESP32 -output: - - platform: ledc - pin: GPIO38 - id: backlight_pwm - -# Define a monochromatic, dimmable light for the backlight -light: - - platform: monochromatic - output: backlight_pwm - name: "Display Backlight" - id: back_light - restore_mode: ALWAYS_ON - -touchscreen: - platform: xpt2046 - id: touch_screen - spi_id: spi_bus1 - cs_pin: GPIO47 - interrupt_pin: GPIO19 - transform: - swap_xy: true # landscape - # mirror_y: true # portrait - calibration: - x_min: 231 #201 #281 - x_max: 3878 #3793 #3848 - y_min: 221 #228 #347 - y_max: 3861 #3914 #3878 - -lvgl: -# color_depth: 16 -# bg_color: 0x0F0F0F - default_font: unscii_8 -# align: center - theme: - button: - bg_color: grey_light #0x2F8CD8 -# bg_grad_color: 0x005782 -# bg_grad_dir: VER - bg_opa: COVER - border_color: 0x0077b3 - border_width: 1 - text_color: 0xFFFFFF - pressed: # set some button colors to be different in pressed state - bg_color: 0x006699 - bg_grad_color: 0x00334d - checked: # set some button colors to be different in checked state - bg_color: 0x1d5f96 - bg_grad_color: 0x03324A - text_color: 0xfff300 -# switch: -# bg_color: 0xC0C0C0 -# bg_grad_color: 0xb0b0b0 -# bg_grad_dir: VER -# bg_opa: COVER -# checked: -# bg_color: 0x1d5f96 -# bg_grad_color: 0x03324A -# bg_grad_dir: VER -# bg_opa: COVER -# knob: -# bg_color: 0xFFFFFF -# bg_grad_color: 0xC0C0C0 -# bg_grad_dir: VER -# bg_opa: COVER -# slider: -# border_width: 1 -# border_opa: 15% -# bg_color: 0xcccaca -# bg_opa: 15% -# indicator: -# bg_color: 0x1d5f96 -# bg_grad_color: 0x03324A -# bg_grad_dir: VER -# bg_opa: COVER -# knob: -# bg_color: 0x2F8CD8 -# bg_grad_color: 0x005782 -# bg_grad_dir: VER -# bg_opa: COVER -# border_color: 0x0077b3 -# border_width: 1 -# text_color: 0xFFFFFF - style_definitions: - - id: header_footer - bg_color: darkgrey #0x2F8CD8 - bg_opa: COVER - border_opa: TRANSP - radius: 0 - pad_all: 0 - pad_row: 0 - pad_column: 0 - border_color: 0x0077b3 - text_color: 0xFFFFFF - width: 100% - height: 30 - - id: clockdate_style - text_font: montserrat_20 #roboto_20 #unscii_8 - text_align: center - text_color: 0x000000 - radius: 4 - pad_all: 2 - - id: sty_calendar_small - radius: 0 - pad_all: 0 - pad_row: 0 - pad_column: 0 - text_font: unscii_8 - shadow_opa: TRANSP - text_color: black - bg_color: white - bg_opa: COVER - border_color: grey_light - border_width: 1 - border_opa: cover #TRANSP - - id: sty_calendar_small_noborders - radius: 0 - pad_all: 0 - pad_row: 0 - pad_column: 0 - text_font: unscii_8 - shadow_opa: TRANSP - text_color: black - bg_color: white - bg_opa: COVER - border_color: grey_light - border_width: 0 - border_opa: cover #TRANSP - displays: - - tft_display - buffer_size: 12% - top_layer: - widgets: - - label: - text: "\U0000F5A9" # "\uF1EB" - id: lbl_hastatus - hidden: true - align: top_right - x: -2 - y: 1 - text_font: font_icon_small #montserrat_16 - text_align: right - text_color: 0x202020 # 0xFFFFFF - - obj: # clipping rectangle - x: 0 #15 - y: -24 #7 - pad_all: 0 - height: 90 - width: 65 - align: BOTTOM_RIGHT - bg_color: 0x000000 - border_color: 0xFFFFFF - border_width: 0 - radius: 0 - bg_opa: LV_OPA_TRANSP - scrollbar_mode: "OFF" - widgets: - - image: - id: ind_geyser_heating_on - align: CENTER #BOTTOM_RIGHT #TOP_RIGHT - src: icon_fire - image_recolor: RED - image_recolor_opa: 100% - x: 0 #15 #15 - y: 0 #-22 #7 - height: 100 #25 - width: 100 #25 - - obj: # clipping rectangle - x: 0 #15 - y: 2 #-24 #7 - pad_all: 0 - height: 80 - width: 65 - align: TOP_LEFT - bg_color: 0x000000 - border_color: 0xFFFFFF - border_width: 0 - radius: 0 - bg_opa: LV_OPA_TRANSP - scrollbar_mode: "OFF" - widgets: - - image: - id: ind_utility_on - align: CENTER #BOTTOM_RIGHT #TOP_RIGHT - src: icon_utility - image_recolor: grey #!lambda 'return lv_color_hex(0x000000);' - image_recolor_opa: 100% - x: 0 #15 #15 - y: 0 #-22 #7 - height: 80 #25 - width: 80 #25 -# - obj: -# id: boot_screen -# x: 0 -# y: 0 -# width: 100% -# height: 100% -# bg_color: 0xffffff -# bg_opa: COVER -# radius: 0 -# pad_all: 0 -# border_width: 0 -# widgets: -# - image: -# align: CENTER -# src: boot_logo -# y: -40 -# - spinner: -# align: CENTER -# y: 95 -# height: 50 -# width: 50 -# spin_time: 1s -# arc_length: 60deg -# arc_width: 8 -# indicator: -# arc_color: 0x18bcf2 -# arc_width: 8 -# on_press: -# - lvgl.widget.hide: boot_screen - - buttonmatrix: - text_font: montserrat_16 - align: bottom_mid - styles: header_footer - pad_all: 0 - outline_width: 0 - id: footer - width: 480 - items: - styles: header_footer - rows: - - buttons: - - id: page_prev - text: "\uF053" - on_press: - then: - lvgl.page.previous: - - id: page_home - text: "\uF015" - on_press: - then: - lvgl.page.show: main_page - - id: page_next - text: "\uF054" - on_press: - then: - lvgl.page.next: - pages: - # - id: pg_calendar - # widgets: - # - button: - # id: cal_btn_prev_month - # styles: sty_calendar_small - # align: TOP_MID - # pad_all: 0 - # outline_width: 0 - # border_color: black - # border_width: 0 #1 - # border_opa: TRANSP - # x: -75 - # y: 30 - # width: 20 - # height: 20 - # bg_color: grey_light - # text_color: 0xD3D3D3 - # text_font: montserrat_14 - # widgets: - # - label: - # align: center - # text_font: montserrat_14 - # text: "<" - # on_press: - # then: - # lambda: |- - # id(update_calendar_month).execute(-1); - # - dropdown: - # id: cal_dd_year - # styles: sty_calendar_small - # text_font: montserrat_12 - # height: 20 - # width: 55 - # radius: 0 - # align_to: - # id: cal_btn_prev_month - # align: out_right_top - # x: 80 - # y: 0 #12.5% - # options: - # - 2024 - # - 2025 - # selected_index: 0 - # dropdown_list: - # text_line_space: 3 - # pad_all: 1 - # text_font: unscii_8 - # max_height: 260 - # radius: 0 - # selected: - # checked: - # text_color: 0xFF0000 - # on_value: - # then: - # - lambda: |- - # id(update_calendar).execute(); - # - dropdown: - # id: cal_dd_month - # styles: sty_calendar_small - # text_font: montserrat_12 - # height: 20 - # width: 55 - # radius: 0 - # align_to: - # id: cal_dd_year - # align: out_right_top - # x: 0 - # y: 0 #12.5% - # options: - # - Jan - # - Feb - # - Mar - # - Apr - # - May - # - Jun - # - Jul - # - Aug - # - Sep - # - Oct - # - Nov - # - Dec - # selected_index: 0 - # dropdown_list: - # text_line_space: 3 - # pad_all: 1 - # text_font: unscii_8 - # max_height: 260 - # radius: 0 - # selected: - # checked: - # text_color: 0xFF0000 - # on_value: - # then: - # - lambda: |- - # id(update_calendar).execute(); - # - button: - # id: cal_btn_next_month - # styles: sty_calendar_small - # align_to: - # id: cal_dd_month - # align: out_right_top - # x: 0 - # y: 0 - # pad_all: 0 - # outline_width: 0 - # border_color: black - # border_width: 0 #1 - # border_opa: TRANSP - # x: -75 - # y: 30 - # width: 20 - # height: 20 - # bg_color: grey_light - # text_color: 0xD3D3D3 - # text_font: montserrat_14 - # widgets: - # - label: - # align: center - # text_font: montserrat_14 - # text: ">" - # on_press: - # then: - # lambda: |- - # id(update_calendar_month).execute(1); - # - buttonmatrix: - # id: bmx_cal_header_dow - # styles: sty_calendar_small_noborders - # align_to: - # id: cal_btn_prev_month - # align: out_bottom_left - # x: 80 - # y: 0 #12.5% - # pad_all: 0 - # outline_width: 0 - # border_color: black - # border_width: 0 #1 - # border_opa: TRANSP - # x: 0 - # y: 0 - # width: 150 - # height: 20 - # bg_color: black - # text_color: 0xD3D3D3 - # items: - # styles: sty_calendar_small_noborders - # pressed: - # bg_color: 0x006699 - # bg_grad_color: 0x00334d - # checked: - # bg_color: 0x1d5f96 - # bg_grad_color: 0x03324A - # rows: - # - buttons: - # - id: r0c1 - # text: "Su" - # width: 1 - # - id: r0c2 - # text: "Mo" - # width: 1 - # - id: r0c3 - # text: "Tu" - # width: 1 - # - id: r0c4 - # text: "We" - # width: 1 - # - id: r0c5 - # text: "Th" - # width: 1 - # - id: r0c6 - # text: "Fr" - # width: 1 - # - id: r0c7 - # text: "Sa" - # width: 1 - # on_press: - # then: - # - lambda: |- - # ESP_LOGI("day of week", "%d", x); - # - buttonmatrix: - # id: bmx_calendar - # styles: sty_calendar_small - # align_to: - # id: bmx_cal_header_dow - # align: out_bottom_left - # x: 0 - # y: 0 #12.5% - # pad_all: 0 - # outline_width: 0 - # border_color: black - # border_width: 0 #1 - # border_opa: TRANSP - # x: 0 - # y: 0 - # width: 150 - # height: 100 - # bg_color: black - # text_color: 0xD3D3D3 - # items: - # styles: sty_calendar_small - # pressed: - # bg_color: 0x006699 - # bg_grad_color: 0x00334d - # checked: - # bg_color: 0x1d5f96 - # bg_grad_color: 0x03324A - # rows: - # - buttons: - # - id: r1c1 - # text: " " - # width: 1 - # control: - # recolor: true - # - id: r1c2 - # text: " " - # width: 1 - # - id: r1c3 - # text: "1" - # width: 1 - # - id: r1c4 - # text: "2" - # width: 1 - # - id: r1c5 - # text: "3" - # width: 1 - # - id: r1c6 - # text: "4" - # width: 1 - # - id: r1c7 - # text: "5" - # width: 1 - - # # Define actions on button press - # on_press: - # then: - # lambda: |- - # //lv_btnmatrix_set_btn_ctrl_all(bmx_calendar->obj, LV_BTNMATRIX_CTRL_CHECKABLE | LV_BTNMATRIX_CTRL_RECOLOR); - # //lv_btnmatrix_set_one_checked(bmx_calendar->obj, false); - # //id(get_calendar_days_state).execute("P1"); - # //auto stat = lv_btnmatrix_has_btn_ctrl(bmx_calendar->obj, x, LV_BTNMATRIX_CTRL_CHECKED); - # //ESP_LOGI("on press", "day: %s, stat: %d", lv_btnmatrix_get_btn_text(bmx_calendar->obj, x), stat); - # //id(get_calendar_days_state).execute("P2"); - # on_release: - # then: - # lambda: |- - # id(get_calendar_days_state).execute("R1"); - # //auto stat = lv_btnmatrix_has_btn_ctrl(bmx_calendar->obj, x, LV_BTNMATRIX_CTRL_CHECKED); - # //auto* day = lv_btnmatrix_get_btn_text(bmx_calendar->obj, x); - # // if(stat) { - # // lv_btnmatrix_clear_btn_ctrl(bmx_calendar->obj, x, LV_BTNMATRIX_CTRL_CHECKED); - # // } - # // else { - # // lv_btnmatrix_set_btn_ctrl(bmx_calendar->obj, x, LV_BTNMATRIX_CTRL_CHECKED); - # // } - # //auto stat = lv_btnmatrix_has_btn_ctrl(bmx_calendar->obj, x, LV_BTNMATRIX_CTRL_CHECKED); - # //ESP_LOGI("on relse", "day: %s, stat: %d", lv_btnmatrix_get_btn_text(bmx_calendar->obj, x), stat); - # //id(get_calendar_days_state).execute("R2"); - - - id: main_page #pg_geyser_temp - widgets: - - obj: - id: rect_gtoptemp - x: 0 - y: 0 #30 - pad_all: 0 - height: 290 - width: 240 - align: TOP_LEFT - bg_color: 0x000000 - border_color: 0xFFFFFF - border_width: 0 - radius: 0 - bg_opa: COVER - - obj: - id: rect_gbottemp - y: 0 - pad_all: 0 - height: 290 - width: 240 - align_to: - id: rect_gtoptemp - align: out_right_top - x: 0 - y: 0 #12.5% - bg_color: 0x000000 #0xFF4500 - border_color: 0xFFFFFF - border_width: 0 - radius: 0 - bg_opa: COVER - - label: - text: " " - id: lbl_gtoptemp - hidden: false - align: LEFT_MID - x: 0 - y: -10 - text_font: roboto_200 - text_align: center - text_color: 0x0 - bg_opa: LV_OPA_TRANSP - bg_color: 0xffffff - - label: - text: " " - id: lbl_gbottemp - hidden: false - align: RIGHT_MID - x: 0 - y: -10 - text_font: roboto_200 - text_align: center - text_color: 0x0 - bg_opa: LV_OPA_TRANSP - bg_color: 0xffffff -# - label: -# text: "°C" -# id: lbl_degree -# hidden: false -# align: BOTTOM_MID -# x: 0 -# y: -30 -# text_font: geyser_temperature_font2 -# text_align: center -# text_color: 0x0 -# bg_opa: LV_OPA_TRANSP -# bg_color: 0xffffff - - label: - text: "top" - id: lbl_top - hidden: false - align: TOP_MID - x: -120 - y: 20 - text_font: geyser_temperature_font3 - text_align: center - text_color: 0x0 - bg_opa: LV_OPA_TRANSP - bg_color: 0xffffff - - label: - text: "bottom" - id: lbl_bottom - hidden: false - align: TOP_MID - x: 120 - y: 20 - text_font: geyser_temperature_font3 - text_align: center - text_color: 0x0 - bg_opa: LV_OPA_TRANSP - bg_color: 0xffffff - -# - id: pg_settings -# widgets: -# - textarea: -# id: geyser_schedule -# one_line: true -# placeholder_text: "Enter text here" -# - keyboard: -# id: keyboard_id -# textarea: geyser_schedule -# mode: TEXT_UPPER -# text_font: montserrat_20 -# on_focus: -# then: -# - lvgl.keyboard.update: -# id: keyboard_id -# mode: number -# textarea: geyser_schedule -# on_ready: -# then: -# - logger.log: Keyboard is ready -# on_cancel: -# then: -# - logger.log: Keyboard cancelled# - -# - id: pg_clock -# widgets: -# - obj: # clock container -# height: 300 #SIZE_CONTENT -# width: 300 # 100% -# align: TOP_MID -# pad_all: 0 -# border_width: 0 -# bg_color: 0xFFFFFF -# widgets: -# - meter: # clock face -# height: 300 -# width: 300 -# align: TOP_MID -# bg_opa: TRANSP -# border_width: 0 -# text_color: 0x000000 -# scales: -# - range_from: 0 # minutes scale -# range_to: 720 -# angle_range: 360 -# rotation: 270 -# ticks: -# width: 1 -# count: 61 -# length: 10 -# color: 0x000000 -# indicators: -# - line: -# id: minute_hand -# width: 3 -# color: 0xa6a6a6 -# r_mod: -4 -# value: 0 -# - range_from: 1 # hours scale for labels -# range_to: 12 -# angle_range: 330 -# rotation: 300 -# ticks: -# width: 1 -# count: 12 -# length: 1 -# major: -# stride: 1 -# width: 4 -# length: 10 -# color: 0xC0C0C0 -# label_gap: 12 -# - range_from: 0 # hi-res hours scale for hand -# range_to: 720 -# angle_range: 360 -# rotation: 270 -# ticks: -# count: 0 -# indicators: -# - line: -# id: hour_hand -# width: 5 -# color: 0xa6a6a6 -# r_mod: -30 -# value: 0 -# # Second hand -# - angle_range: 360 -# rotation: 270 -# range_from: 0 -# range_to: 60 -# indicators: -# - line: -# id: second_hand -# width: 2 -# color: Red -# r_mod: -10 -# - label: -# align: CENTER -# styles: clockdate_style -# id: day_label -# y: -50 -# - label: -# align: CENTER -# id: date_label -# styles: clockdate_style -# y: 50 - -# - id: pg_digital_clock -# widgets: -# - obj: -# id: rect_gtoptemp1 -# x: 0 -# y: 0 #30 -# pad_all: 0 -# height: 290 -# width: 240 -# align: TOP_LEFT -# bg_color: 0x000000 -# border_color: 0xFFFFFF -# border_width: 0 -# radius: 0 -# bg_opa: COVER -# - obj: -# id: rect_gbottemp1 -# y: 0 -# pad_all: 0 -# height: 290 -# width: 240 -# align_to: -# id: rect_gtoptemp -# align: out_right_top -# x: 0 -# y: 0 #12.5% -# bg_color: 0x000000 #0xFF4500 -# border_color: 0xFFFFFF -# border_width: 0 -# radius: 0 -# bg_opa: COVER -# - label: -# text: " " -# id: lbl_digitalclock -# hidden: false -# align: TOP_MID -# x: 0 -# y: 20 -# text_font: roboto_192 -# text_align: center -# text_color: RED -# bg_opa: LV_OPA_TRANSP -# bg_color: 0xffffff +# +# - platform: sntp +# timezone: Africa/Johannesburg +# servers: +# - ntp1.meraka.csir.co.za # 146.64.24.58 +# - ntp.as3741.net # 196.4.160.4 +# - ntp1.inx.net.za # 196.10.52.57 + - platform: homeassistant + id: time_source + on_time_sync: + - ds3231.write_time: + - lambda: |- + id(time_synched) = true; + - logger.log: "Synchronized system clock" + on_time: +# # do every second + - seconds: '*' + minutes: '*' + then: + - lambda: |- + auto time_obj = id(time_source).now(); + if(time_obj.is_valid()) { + id(set_status_indicators).execute(); + } switch: + # in economy mode, geyser is only switched on when it can be powered by solar only, i.e. without using mains + - platform: gpio + pin: + number: ${ECONOMY_MODE_SWITCH_PIN} + inverted: true + mode: + input: true + pullup: false # external pullup +# filters: +# - delayed_off: 100ms + id: economy_mode # mode_select_switch + name: "Economy Mode" # "Mode Select" + icon: "mdi:beach" + - platform: restart name: "${name} Restart" id: "restart_switch" -# - platform: template -# name: Antiburn -# id: switch_antiburn -# icon: mdi:television-shimmer -# optimistic: true -# entity_category: "config" -# turn_on_action: -# - logger.log: "Starting Antiburn" -# - if: -# condition: lvgl.is_paused -# then: -# - lvgl.resume: -# - lvgl.widget.redraw: -# - lvgl.pause: -# show_snow: true -# turn_off_action: -# - logger.log: "Stopping Antiburn" -# - if: -# condition: lvgl.is_paused -# then: -# - lvgl.resume: -# - lvgl.widget.redraw: -sensor: -# - platform: dallas_temp -# address: 0xfe00000037b3d528 -# name: "Study Temperature" -# id: study_temperature -# update_interval: "60s" -# resolution: 12 -# one_wire_id: temperature_sensors -# unit_of_measurement: "°C" -# #icon: "mdi:water-thermometer" -# device_class: "temperature" -# state_class: "measurement" -# accuracy_decimals: 1 -# filters: -# - filter_out: nan -# # - sliding_window_moving_average: -# # window_size: 120 # averages over 120 update intervals -# # send_every: 60 # reports every 60 update intervals + - platform: gpio + id: reset_energy_counters + pin: + number: ${ENERGY_RESET_PIN} + inverted: true + mode: + input: true + pullup: true + name: "Reset Energy Counters" + disabled_by_default: True + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + then: + - lambda: |- + ESP_LOGI("reset_energy_counters", "Time source is %s", id(time_source).now().is_valid() ? "valid" : "invalid"); - # Report wifi signal strength every 5 min if changed - - platform: wifi_signal - name: WiFi Signal - id: wifi_sig - update_interval: 300s + - platform: gpio + pin: + number: ${RELAY1_PIN} + inverted: false + mode: output + id: geyser_relay + name: "Geyser Relay" + icon: "mdi:water-thermometer" + restore_mode: ALWAYS_OFF + + - platform: gpio + pin: + number: ${RELAY2_PIN} + inverted: true + mode: output + id: pool_relay + name: "Pool Relay" + icon: "mdi:pool" + restore_mode: ALWAYS_OFF + +tlc59208f: + address: 0x20 + id: tlc59208f_1 + i2c_id: bus_b + +#tlc59208f_ext: +# address: 0x20 +# id: tlc59208f_1 +# i2c_id: bus_b +# reset_pin: +# number: ${RELAY4_PIN} #${LED_RESET_PIN} # there was no more spare GPIO. if reset functionality is needed, we can consider using an IO expander pin for it. +# mode: +# output: true +# pullup: true + +output: + - platform: ledc + pin: + number: ${LOW_BATTERY_LED_PIN} # LED_LOW_BAT + inverted: false #true + id: led_inverter_battery_low + - platform: ledc + pin: + number: ${ECONOMY_MODE_LED_PIN} + inverted: false #true + id: led_economy_mode + - platform: ledc + pin: + number: ${MAINS_FAIL_LED_PIN} + inverted: false #true + id: led_mains_fail + - platform: ledc + pin: + number: ${SYSTEM_OK_LED_PIN} + inverted: false #true + id: led_system_ok + + - platform: tlc59208f + channel: 0 + tlc59208f_id: 'tlc59208f_1' + id: led0 + + - platform: tlc59208f + channel: 1 + tlc59208f_id: 'tlc59208f_1' + id: led1 + + - platform: tlc59208f + channel: 2 + tlc59208f_id: 'tlc59208f_1' + id: led2 + + - platform: tlc59208f + channel: 3 + tlc59208f_id: 'tlc59208f_1' + id: led3 + + - platform: tlc59208f + channel: 4 + tlc59208f_id: 'tlc59208f_1' + id: led4 + + - platform: tlc59208f + channel: 5 + tlc59208f_id: 'tlc59208f_1' + id: led5 + + - platform: tlc59208f + channel: 6 + tlc59208f_id: 'tlc59208f_1' + id: led6 + + - platform: tlc59208f + channel: 7 + tlc59208f_id: 'tlc59208f_1' + id: led7 + +light: + - platform: monochromatic + output: led_economy_mode + name: "LED Economy Mode" + id: light_economy_mode + default_transition_length: 20ms + - platform: monochromatic + output: led_mains_fail + name: "LED Mains Fail" + id: light_mains_fail + default_transition_length: 20ms + - platform: monochromatic + output: led_system_ok + name: "LED System OK" + id: light_system_ok + default_transition_length: 20ms + + - platform: monochromatic + output: led0 + name: "LED Geyser Temperature 0" + id: led_geyser_temp0 + default_transition_length: 20ms + on_turn_on: + - lambda: |- + ESP_LOGV("geyser", "Geyser Temperature LED BLUE on"); + on_turn_off: + - lambda: |- + ESP_LOGV("geyser", "Geyser Temperature LED BLUE off"); + - platform: monochromatic + output: led1 + name: "LED Geyser Temperature 1" + id: led_geyser_temp1 + default_transition_length: 20ms + on_turn_on: + - lambda: |- + ESP_LOGV("geyser", "Geyser Temperature LED GREEN on"); + on_turn_off: + - lambda: |- + ESP_LOGV("geyser", "Geyser Temperature LED GREEN off"); + - platform: monochromatic + output: led2 + name: "LED Geyser Temperature 2" + id: led_geyser_temp2 + default_transition_length: 20ms + on_turn_on: + - lambda: |- + ESP_LOGV("geyser", "Geyser Temperature LED YELLOW on"); + on_turn_off: + - lambda: |- + ESP_LOGV("geyser", "Geyser Temperature LED YELLOW off"); + - platform: monochromatic + output: led3 + name: "LED Geyser Temperature 3" + id: led_geyser_temp3 + default_transition_length: 20ms + on_turn_on: + - lambda: |- + ESP_LOGV("geyser", "Geyser Temperature LED YELLOW2 on"); + on_turn_off: + - lambda: |- + ESP_LOGV("geyser", "Geyser Temperature LED YELLOW2 off"); + - platform: monochromatic + output: led4 + name: "LED Geyser Temperature 4" + id: led_geyser_temp4 + default_transition_length: 20ms + on_turn_on: + - lambda: |- + ESP_LOGV("geyser", "Geyser Temperature LED ORANGE on"); + on_turn_off: + - lambda: |- + ESP_LOGV("geyser", "Geyser Temperature LED ORANGE off"); + - platform: monochromatic + output: led5 + name: "LED Geyser Temperature 5" + id: led_geyser_temp5 + default_transition_length: 20ms + on_turn_on: + - lambda: |- + ESP_LOGV("geyser", "Geyser Temperature LED ORANGE2 on"); + on_turn_off: + - lambda: |- + ESP_LOGV("geyser", "Geyser Temperature LED ORANGE2 off"); + - platform: monochromatic + output: led6 + name: "LED Geyser Temperature 6" + id: led_geyser_temp6 + default_transition_length: 20ms + on_turn_on: + - lambda: |- + ESP_LOGV("geyser", "Geyser Temperature LED RED on"); + on_turn_off: + - lambda: |- + ESP_LOGV("geyser", "Geyser Temperature LED RED off"); + - platform: monochromatic + output: led7 + name: "LED Geyser Temperature 7" + id: led_geyser_temp7 + default_transition_length: 20ms + on_turn_on: + - lambda: |- + ESP_LOGV("geyser", "Geyser Temperature LED RED2 on"); + on_turn_off: + - lambda: |- + ESP_LOGV("geyser", "Geyser Temperature LED RED2 off"); + - platform: monochromatic + output: led_inverter_battery_low + name: "LED Inverter Battery Low" + id: light_inverter_battery_low + default_transition_length: 20ms + on_turn_on: + - lambda: |- + ESP_LOGI("battery", "Battery Low"); +# on_turn_off: +# - lambda: |- +# ESP_LOGI("battery", "Battery OK"); + +binary_sensor: + - platform: status + # Status platform provides a connectivity sensor + name: "Status" + device_class: connectivity + + - platform: gpio + pin: + number: ${LOW_BATTERY_SENSOR_PIN} # LOW_BAT + inverted: false + mode: + input: true + pullup: true filters: - - delta: 10% + - delayed_off: 50ms + id: inverter_battery_charge_state + name: "Inverter Battery Charge" + device_class: battery - # human readable uptime sensor output to the text sensor above -# - platform: uptime -# name: Uptime in Days -# id: uptime_sensor_days -# update_interval: 10s -# on_raw_value: -# then: -# - text_sensor.template.publish: -# id: uptime_human -# state: !lambda |- -# int seconds = round(id(uptime_sensor_days).raw_state); -# int days = seconds / (24 * 3600); -# seconds = seconds % (24 * 3600); -# int hours = seconds / 3600; -# seconds = seconds % 3600; -# int minutes = seconds / 60; -# seconds = seconds % 60; -# auto days_str = std::to_string(days); -# auto hours_str = std::to_string(hours); -# auto minutes_str = std::to_string(minutes); -# auto seconds_str = std::to_string(seconds); -# return ( -# (days ? days_str + "d " : "") + -# (hours ? hours_str + "h " : "") + -# (minutes ? minutes_str + "m " : "") + -# (seconds_str + "s") -# ).c_str(); -# -## number of seconds since midnight -# - platform: template -# id: time_of_day -# name: "Time of day" -# accuracy_decimals: 0 -# unit_of_measurement: "s" -# lambda: |- -# auto currenttime = id(time_source).now(); -# ESPTime time_obj = currenttime; -# time_obj.second = 0; -# time_obj.minute = 0; -# time_obj.hour = 0; -# time_obj.recalc_timestamp_local(); -# return currenttime.timestamp - time_obj.timestamp; -# update_interval: 60s + - platform: template + id: mains_supply + name: "Mains Supply" + lambda: |- + return 200; + device_class: power text_sensor: -## - platform: template -## id: module_time -## name: "Module time" -## icon: mdi:clock -## lambda: |- -## auto time_obj = id(time_source).now(); -## return time_obj.strftime("%Y-%m-%d %H:%M:%S"); -## update_interval: 60s -# -# # Expose WiFi information as sensors -# - platform: wifi_info -# ip_address: -# name: IP -# mac_address: -# name: Mac Address -# entity_category: diagnostic -# ssid: -# name: "Connected SSID" -# id: ssid -# entity_category: diagnostic -# -# # human readable update text sensor from sensor:uptime -# - platform: template -# name: Uptime -# id: uptime_human -# icon: mdi:clock-start -# - - platform: template - name: 'Last Restart' - id: device_last_restart - icon: mdi:clock - entity_category: diagnostic + - platform: debug + device: + name: "Device Info" + reset_reason: + name: "Reset Reason" + # Expose WiFi information as sensors + - platform: wifi_info + ip_address: + name: IP + mac_address: + name: Mac Address script: - - id: send_quick_update_request + - id: set_status_indicators then: - lambda: |- - for(int i = 0; i < ${CB_MAX_RETRANSMISSIONS}; i++) { - id(send_request_block1).execute(); + if(id(economy_mode).state) { + id(led_economy_mode).turn_on(); } - - - id: send_info_request - then: - - lambda: |- - for(int i = 0; i < ${CB_MAX_RETRANSMISSIONS}; i++) { - id(send_request_block2).execute(); - } - - - id: send_request_block1 - mode: queued - then: - - lambda: "id(g_cb_cache).send_request(id(canbus_sthome), solar::cbf_sthome::CB_CONTROLLER_STATES);" - - delay: 20ms - - - id: send_request_block2 - mode: queued - then: - - lambda: "id(g_cb_cache).send_request(id(canbus_sthome), solar::cbf_pylon::CB_BATTERY_STATE);" - - delay: 50ms - - lambda: "id(g_cb_cache).send_request(id(canbus_sthome), solar::cbf_sthome::CB_GEYSER_TEMPERATURE_TOP);" - - delay: 50ms - - lambda: "id(g_cb_cache).send_request(id(canbus_sthome), solar::cbf_sthome::CB_GEYSER_TEMPERATURE_BOTTOM);" - - delay: 50ms - - - id: update_temp_display - mode: queued - parameters: - value: double - rect: lv_obj_t* - indicator: lv_obj_t* - label: lv_obj_t* - then: - - lambda: |- - char buffer [4]; - buffer[0] = '\0'; - snprintf (buffer, 4, "%.0f", value); - auto bgcolor = lv_color_hex(0xFF0000); - auto ind_color = lv_color_hex(0xFF0000); - if(value < 40) { - bgcolor = lv_color_hex(0x0000FF); - } - else if(value < 50) { - bgcolor = lv_color_hex(0x00FF00); - } - else if(value < 60) { - bgcolor = lv_color_hex(0xFFFF00); - } else { - ind_color = lv_color_hex(0xFFFF00); // make different to bgcolor + id(led_economy_mode).turn_off(); + } + if(id(inverter_battery_charge_state).state) { + id(led_inverter_battery_low).turn_on(); + } + else { + id(led_inverter_battery_low).turn_off(); + } + if(id(mains_supply).state) { + id(light_system_ok).turn_on().set_brightness(1).perform(); + id(light_mains_fail).turn_off().set_brightness(0).perform(); + } + else { + id(light_system_ok).turn_off().set_brightness(0).perform(); + id(light_mains_fail).turn_on().set_brightness(1).perform(); } - lv_obj_set_style_bg_color(rect, bgcolor, LV_PART_MAIN); - lv_obj_set_style_img_recolor(indicator, ind_color, LV_PART_MAIN); - if(isnan(value)) - lv_label_set_text(label, "??"); - else - lv_label_set_text(label, buffer); - - - id: update_heating_display - parameters: - heat_on: bool - then: - - lvgl.widget.update: - id: ind_geyser_heating_on - hidden: !lambda 'return !heat_on;' - -# - id: time_update -# then: -# - lvgl.indicator.update: -# id: minute_hand -# value: !lambda |- -# auto now = id(time_source).now(); -# return now.minute * 12 + now.second/5; -# - lvgl.indicator.update: -# id: hour_hand -# value: !lambda |- -# auto now = id(time_source).now(); -# return std::fmod(now.hour, 12) * 60 + now.minute; -# - lvgl.indicator.update: -# id: second_hand -# value: !lambda |- -# auto now = id(time_source).now(); -# return now.second; -# - lvgl.label.update: -# id: date_label -# text: !lambda |- -# static const char * const mon_names[] = {"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"}; -# static char date_buf[8]; -# auto now = id(time_source).now(); -# snprintf(date_buf, sizeof(date_buf), "%s %2d", mon_names[now.month-1], now.day_of_month); -# return date_buf; -# - lvgl.label.update: -# id: day_label -# text: !lambda |- -# static const char * const day_names[] = {"SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"}; -# return day_names[id(time_source).now().day_of_week - 1]; -# - lvgl.label.update: -# id: lbl_digitalclock -# text: !lambda |- -# auto time_obj = id(time_source).now(); -# return time_obj.strftime("%H:%M"); - -# - id: init_calendar -# then: -# - lambda: |- -# auto now = id(time_source).now(); -# //ESP_LOGI("yopts before", stroptions.c_str()); -# int y = 0; -# std::string stroptions = to_string(now.year + y); -# while(++y < ${DD_MAX_YEARS}) { -# stroptions += "\n" + to_string(now.year + y); -# } -# //ESP_LOGI("yopts after", stroptions.c_str()); -# lv_dropdown_set_options(cal_dd_year->obj, stroptions.c_str()); -# lv_dropdown_set_selected(cal_dd_year->obj, 0); // this year is first index -# lv_dropdown_set_selected(cal_dd_month->obj, now.month-1); -# id(g_year_idx) = 0; -# id(update_calendar).execute(); -# lv_btnmatrix_set_btn_ctrl_all(bmx_calendar->obj, LV_BTNMATRIX_CTRL_CHECKABLE | LV_BTNMATRIX_CTRL_RECOLOR); - -# - id: update_calendar_month -# parameters: -# increment : int -# then: -# - lambda: |- -# char yearstr[8]; -# lv_dropdown_get_selected_str(cal_dd_year->obj, yearstr, sizeof(yearstr)); -# auto year = atoi(yearstr); -# int year_idx = lv_dropdown_get_selected(cal_dd_year->obj); -# int month_idx = increment + lv_dropdown_get_selected(cal_dd_month->obj); -# int month = 1 + month_idx; -# if(month > 12 && year_idx < ${DD_MAX_YEARS} - 1) { -# month -= 12; -# month_idx -= 12; -# year++; -# year_idx++; -# } -# else if(month < 1 && year_idx > 0) { -# month += 12; -# month_idx += 12; -# year--; -# year_idx--; -# } -# ESP_LOGI("cm", "month: %d, year: %d", month, year); -# if(month < 13 && month > 0) { -# lv_dropdown_set_selected(cal_dd_year->obj, year_idx); -# lv_dropdown_set_selected(cal_dd_month->obj, month_idx); -# id(g_year_idx) = year_idx; -# id(update_calendar).execute(); -# } -# -# - id: update_calendar -# then: -# lambda: |- -# char yearstr[8]; -# int monthdays[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; -# static std::string strdays[44]; -# static const char *pstrdays[49]; // including newline at end of week -# static const char *newline = "\n"; -# id(persist_calendar).execute(); -# lv_dropdown_get_selected_str(cal_dd_year->obj, yearstr, sizeof(yearstr)); -# int year = atoi(yearstr); -# int month = 1 + lv_dropdown_get_selected(cal_dd_month->obj); -# bool isLeapYear = (year % 400 == 0) || ((year % 4 == 0) && (year % 100 != 0)); -# monthdays[1] = (isLeapYear) ? 29 : 28; -# // calculate day of week of 1st of month using Zeller's rule -# // https://beginnersbook.com/2013/04/calculating-day-given-date -# // modified month, year -# int mM = month - 2; -# int m = mM < 1 ? 12 + mM : mM; -# int mY = mM < 1 ? year - 1 : year; -# int k = 1; // day of month -# int D = mY % 100; // last two digits of the year -# int C = trunc(mY / 100); // first two digits of the year -# int F = k + trunc((13 * m - 1) / 5) + D + trunc(D / 4) + trunc(C / 4) - 2 * C; -# int Z = F % 7; -# int start_of_month = Z < 0 ? Z + 7 : Z; -# // end of Zeller's rule -# int previous_month = (month == 1) ? 12 : month - 1; -# int month_days = monthdays[month - 1]; -# int prev_month_days = monthdays[previous_month - 1]; -# int i = 0; -# int j = -1; -# //ESP_LOGI("vals", "start_of_month: %d, previous_month: %d, month_days: %d, prev_month_days: %d", start_of_month, previous_month, month_days, prev_month_days); -# for (int w = 0; w < 6 && i < (month_days + start_of_month); w++) { -# for(int wd = 0; wd < 7; wd++) { -# int day = i + 1 - start_of_month; -# if (i < start_of_month) { -# day += prev_month_days; -# strdays[i] = "#e0e0e0 " + to_string(day) + "#"; -# } -# else if (i >= (month_days + start_of_month)) { -# day -= month_days; -# strdays[i] = "#e0e0e0 " + to_string(day) + "#"; -# } -# else { -# strdays[i] = to_string(day); -# } -# pstrdays[++j] = strdays[i].c_str(); -# //ESP_LOGI("bmx", "%s, i: %d, j: %d", pstrdays[j], i, j); -# i++; -# } -# pstrdays[++j] = newline; -# //ESP_LOGI("bmxnl", "%s, i: %d, j: %d", pstrdays[j], i, j); -# } -# pstrdays[j] = NULL; // terminator, overwrites last newline -# //ESP_LOGW("day", "terminating at: %d", j); -# lv_btnmatrix_set_btn_ctrl_all(bmx_calendar->obj, LV_BTNMATRIX_CTRL_CHECKABLE | LV_BTNMATRIX_CTRL_RECOLOR); -# lv_btnmatrix_set_map(bmx_calendar->obj, pstrdays); -# lv_btnmatrix_set_btn_ctrl_all(bmx_calendar->obj, LV_BTNMATRIX_CTRL_CHECKABLE | LV_BTNMATRIX_CTRL_RECOLOR); -# -# - id: persist_calendar -# then: -# lambda: |- -# id(g_year_idx) = lv_dropdown_get_selected(cal_dd_year->obj); -# id(g_month_idx) = lv_dropdown_get_selected(cal_dd_month->obj); -# // copy year options to persistent globals -# const char* opts = lv_dropdown_get_options(cal_dd_year->obj); -# int opt_store_size = sizeof(id(g_options_year)); -# strncpy(id(g_options_year), opts, opt_store_size); -# id(g_options_year)[opt_store_size] = '\0'; -# //ESP_LOGI("year options", id(g_options_year)); -# -# - id: get_calendar_days_state -# parameters: -# flag: std::string -# then: -# lambda: |- -# // count buttons -# int num_buttons = 0; -# auto* buttonmap = lv_btnmatrix_get_map(bmx_calendar->obj); -# int i = 0; -# for (; buttonmap[i] != NULL && buttonmap[i][0] != '\0' && i < 48; i++) { -# bool isNewLine = strcmp(buttonmap[i], "\n") == 0; -# if (!isNewLine) { -# num_buttons++; -# } -# } -# std::string sch_holidays = ""; -# std::string pub_holidays = ""; -# std::string vac_days = ""; -# int h = 0; -# for(int i = 0; i < num_buttons; i++) { -# bool isSchoolHoliday = lv_btnmatrix_has_btn_ctrl(bmx_calendar->obj, i, LV_BTNMATRIX_CTRL_CHECKED); -# bool isPublicHoliday = false; //lv_btnmatrix_has_btn_ctrl(bmx_calendar->obj, i, LV_BTNMATRIX_CTRL_CUSTOM_1); -# bool isVacationDay = false; //lv_btnmatrix_has_btn_ctrl(bmx_calendar->obj, i, LV_BTNMATRIX_CTRL_CUSTOM_2); -# if(isSchoolHoliday || isPublicHoliday || isVacationDay) { -# sch_holidays = sch_holidays + lv_btnmatrix_get_btn_text(bmx_calendar->obj, i) + " "; -# h++; -# } -# } -# if(h > 0) { -# ESP_LOGI("day", "[%s] s: %s \tp: %s \tv: %s", flag.c_str(), sch_holidays.c_str(), pub_holidays.c_str(), vac_days.c_str()); -# } - - \ No newline at end of file diff --git a/sthome-ut2.yaml b/sthome-ut2.yaml index 58fc0c5..c713415 100644 --- a/sthome-ut2.yaml +++ b/sthome-ut2.yaml @@ -1,6 +1,8 @@ packages: - !include common/wifi.yaml - !include common/felicityinverter.yaml + - !include common/canbus.yaml + - !include common/modbus.yaml substitutions: name: sthome-ut2 @@ -9,13 +11,38 @@ substitutions: esphome: name: "${name}" friendly_name: "${friendly_name}" + includes: + - source # copies folder with files to relevant to be included in esphome compile + - # angle brackets ensure file is included above globals in main.cpp. Make sure to use include GUARDS in the file to prevent double inclusion + - + - + - + - + - + - -external_components: - - source: github://pr#8103 - components: [uart] - - source: github://pr#8032 - components: [modbus, modbus_controller, growatt_solar] - refresh: 1h +#external_components: +# - source: github://pr#8103 +# components: [uart] +# - source: github://pr#8032 +# components: [modbus, modbus_controller, growatt_solar] +# refresh: 1h +globals: + - id: can_msgctr + type: int + restore_value: no + - id: can2_msgctr + type: int + restore_value: no + - id: last_battery_message_time + type: time_t + restore_value: no + - id: g_cb_cache # the cache is used to only accept a frame after it has been received a specified number of times within a specified period. this hopefully will iron out spurious corrupted frames + type: solar::cbf_cache + restore_value: no + - id: g_cb_request_queue + type: std::queue< std::set > + restore_value: no esp32: board: esp32dev @@ -27,9 +54,10 @@ logger: level: VERY_VERBOSE initial_level: INFO logs: - uart: VERY_VERBOSE - modbus: DEBUG - modbus_controller: DEBUG + canbus: INFO + uart_debug: VERY_VERBOSE + modbus: VERBOSE + modbus_controller: VERBOSE # Enable Home Assistant API api: @@ -51,18 +79,30 @@ wifi: captive_portal: +spi: + - id: spi_bus0 + clk_pin: GPIO18 + mosi_pin: GPIO23 + miso_pin: GPIO19 + interface: any + uart: - - id: inv_uart1 - rx_pin: GPIO16 - tx_pin: GPIO17 - baud_rate: 2400 + - id: sth_uart + rx_pin: + number: GPIO17 + inverted: false + mode: + input: true + pullup: true + tx_pin: GPIO16 + baud_rate: 9600 stop_bits: 1 parity: NONE debug: direction: BOTH dummy_receiver: false after: - delimiter: "\r" + delimiter: "\n" sequence: - lambda: UARTDebug::log_hex(direction, bytes, ','); @@ -80,328 +120,404 @@ switch: name: "${name} Restart" id: "restart_switch" +interval: + - interval: 10s + then: + - lambda: |- + id(canbus_send_heartbeat).execute(); +# - interval: 2.36s +# then: +# - uart.write: +# data: "sthome2 heartbeat\r\n" + # - sth_uart.write: !lambda + # return std::vector({0x0D, 0x0A, 0x43, 0x50, 0x53, 0x00, 0x0D, 0x0A, 0x43, 0x50, 0x53, 0x00}); + +canbus: + - platform: mcp2515 + cs_pin: GPIO32 # CBCS + spi_id: spi_bus0 + id: canbus_sthome + mode: NORMAL + can_id: ${CB_CANBUS_ID03} + bit_rate: 500KBPS + on_frame: + - can_id: 0 + can_id_mask: 0 + then: + - lambda: |- + id(can_msgctr)++; + using namespace solar; + auto time_obj = id(time_source).now(); + //ESP_LOGI("CB REC", "%s", id(time_source).now().strftime("%a %d %b %Y - %I:%M:%S %p")); + if(time_obj.is_valid()) { + if (can_id >= 0x380 && can_id < 0x3C0) { + auto cbitem = cbf_store_sthome(id(can_msgctr), can_id, x, remote_transmission_request, time_obj.timestamp); + bool publish = id(g_cb_cache).additem(cbitem); + if(publish) { + ESP_LOGI(cbitem.tag().c_str(), "%s", cbitem.to_string().c_str()); + } + } else { + auto cbitem = cbf_store_sthome(id(can_msgctr), can_id, x, remote_transmission_request, time_obj.timestamp); + ESP_LOGI(cbitem.tag().c_str(), "%s", cbitem.to_string().c_str()); + } + } + modbus: - - id: modbus1 - uart_id: inv_uart1 - flow_control_pin: GPIO4 - send_wait_time: 1000ms #250ms + - id: modbus_server + uart_id: sth_uart + send_wait_time: 1200ms #250ms disable_crc: false role: server modbus_controller: - - id: modbus_device1 - modbus_id: modbus1 - address: 0x1 - allow_duplicate_commands: False - command_throttle: 0ms - update_interval: 4s #30s - offline_skip_updates: 2 - max_cmd_retries: 04 - setup_priority: -10 + - id: modbus_alarm_server + modbus_id: modbus_server + address: 0x01 server_registers: - - address: ${Felicity_Inv_WorkingMode} # 0x1101 - value_type: U_WORD - read_lambda: |- - return 0x0001; - - address: ${Felicity_Inv_BatteryChargingStage} # 0x1102 - value_type: U_WORD - read_lambda: |- - return 0x0002; - - address: ${Felicity_Inv_FaultCode} # 0x1103 - value_type: U_WORD - read_lambda: |- - return 0x0003; - - address: ${Felicity_Inv_PowerFlowMsg} # 0x1104 - value_type: U_DWORD_R - read_lambda: |- - return 0x0004; - - address: 0x1106 - value_type: U_DWORD_R - read_lambda: |- - return 0; - - address: ${Felicity_Inv_BatteryVoltage} # 0x1108 - value_type: U_WORD - read_lambda: |- - return 0x0005; - - address: ${Felicity_Inv_BatteryCurrent} # 0x1109 - value_type: U_WORD - read_lambda: |- - return 0x0006; - - address: ${Felicity_Inv_BatteryPower} # 0x110A - value_type: U_DWORD_R - read_lambda: |- - return 0x0007; - - address: 0x110C - value_type: U_QWORD_R - read_lambda: |- - return 0; - - address: 0x1110 - value_type: U_WORD - read_lambda: |- - return 0; - - address: ${Felicity_Inv_ACOutputVoltage} # 0x1111 - value_type: U_QWORD_R - read_lambda: |- - return 0x0008; - - address: 0x1115 - value_type: U_DWORD_R - read_lambda: |- - return 0; - - address: ${Felicity_Inv_ACInputVoltage} # 0x1117 - value_type: U_DWORD_R - read_lambda: |- - return 0x0009; - - address: ${Felicity_Inv_ACInputFrequency} # 0x1119 - value_type: U_QWORD_R - read_lambda: |- - return 0x000A; - - address: 0x111D - value_type: U_WORD - read_lambda: |- - return 0; - - address: ${Felicity_Inv_ACOutputActivePower} # 0x111E - value_type: U_WORD - read_lambda: |- - return 0x000B; - - address: ${Felicity_Inv_ACOutputApparentPower} # 0x111F - value_type: U_WORD - read_lambda: |- - return 0x000C; - - address: ${Felicity_Inv_LoadPercentage} # 0x1120 - value_type: U_QWORD_R - read_lambda: |- - return 0x000D; - - address: 0x1124 - value_type: U_DWORD_R - read_lambda: |- - return 0; - - address: ${Felicity_Inv_PVInputVoltage} # 0x1126 - value_type: U_QWORD_R - read_lambda: |- - return 0x000E; - - address: ${Felicity_Inv_PVInputPower} # 0x112A - value_type: U_WORD - read_lambda: |- - return 0x0010; - - address: 0x112C - value_type: U_WORD - read_lambda: |- - return 0; - - - address: 0xF800 + - address: 0x0001 value_type: S_DWORD_R read_lambda: |- - return 0x400D; - - address: 0xF802 - value_type: S_DWORD_R - read_lambda: |- - return 0x500E; - - address: 0xF804 - value_type: S_DWORD_R - read_lambda: |- - return 0x4142; - - address: 0xF806 - value_type: S_DWORD_R - read_lambda: |- - return 0x4344; - - address: 0xF808 - value_type: S_DWORD_R - read_lambda: |- - return 0x4546; - - address: 0xF80A - value_type: S_DWORD_R - read_lambda: |- - return 0x4748; - - address: 0xF80C - value_type: S_DWORD_R - read_lambda: |- - return 0x494A; - - address: 0xF80E - value_type: S_DWORD_R - read_lambda: |- - return 0x4B4C; - - address: 0xF810 - value_type: S_DWORD_R - read_lambda: |- - return 0; + uint16_t alarm_status = id(person_detected_switch).state ? 1 : 0; + alarm_status |= id(alarm_zone4_relay).state ? 2 : 0; + alarm_status |= id(lights_backyard_relay).state ? 4 : 0; + alarm_status |= id(lights_frontyard_relay).state ? 8 : 0; + alarm_status |= id(floodlight_test).state ? 16 : 0; + alarm_status |= id(night_time).state ? 32 : 0; + alarm_status |= id(zone1_triggered).state ? 64 : 0; + alarm_status |= id(zone2_triggered).state ? 128 : 0; + alarm_status |= id(zone3_triggered).state ? 256 : 0; + alarm_status |= id(zone4_triggered).state ? 512 : 0; + alarm_status |= id(zone5_triggered).state ? 1024 : 0; + alarm_status |= id(zone6_triggered).state ? 2048 : 0; + return alarm_status; +binary_sensor: + - platform: template + id: person_detected_switch + name: "Person Detected Switch" + lambda: |- + return true; + - platform: template + id: alarm_zone4_relay + name: "Alarm Zone 4 Relay" + lambda: |- + return true; + - platform: template + id: lights_backyard_relay + name: "Lights Backyard Relay" + lambda: |- + return true; + - platform: template + id: lights_frontyard_relay + name: "Lights Frontyard Relay" + lambda: |- + return true; + - platform: template + id: floodlight_test + name: "Floodlight Test" + lambda: |- + return true; + - platform: template + id: night_time + name: "Night Time" + lambda: |- + return true; + - platform: template + id: zone1_triggered + name: "Zone 1 Triggered" + lambda: |- + return true; + - platform: template + id: zone2_triggered + name: "Zone 2 Triggered" + lambda: |- + return true; + - platform: template + id: zone3_triggered + name: "Zone 3 Triggered" + lambda: |- + return true; + - platform: template + id: zone4_triggered + name: "Zone 4 Triggered" + lambda: |- + return true; + - platform: template + id: zone5_triggered + name: "Zone 5 Triggered" + lambda: |- + return true; + - platform: template + id: zone6_triggered + name: "Zone 6 Triggered" + lambda: |- + return true; + + #modbus: -# - id: modbus1 -# uart_id: inv_uart1 -# flow_control_pin: GPIO4 -# send_wait_time: 1000ms #250ms +# - id: modbus_client +# uart_id: sth_uart +# send_wait_time: 1200ms #250ms # disable_crc: false # role: client # #modbus_controller: -# - id: modbus_device1 -# modbus_id: modbus1 +# - id: modbus_alarm_client +# modbus_id: modbus_client # address: 0x01 # allow_duplicate_commands: False -# command_throttle: 0ms -# update_interval: 4s #30s +# command_throttle: 700ms #2022ms +# update_interval: 10s #305s # offline_skip_updates: 2 -# max_cmd_retries: 0 +# max_cmd_retries: 1 # setup_priority: -10 # +#binary_sensor: +#- platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: person_detected +# name: "Person Detected" +# register_type: read +# address: 0x0001 +# bitmask: 0x1 +#- platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: alarm_zone4_relay +# name: "Alarm Zone 4 Relay" +# register_type: read +# address: 0x0001 +# bitmask: 0x2 +#- platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: lights_backyard_relay +# name: "Lights Backyard Relay" +# register_type: read +# address: 0x0001 +# bitmask: 0x4 +#- platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: lights_frontyard_relay +# name: "Lights Frontyard Relay" +# register_type: read +# address: 0x0001 +# bitmask: 0x8 +#- platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: floodlight_test +# name: "Floodlight Test" +# register_type: read +# address: 0x0001 +# bitmask: 0x10 +#- platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: night_time +# name: "Night Time" +# register_type: read +# address: 0x0001 +# bitmask: 0x20 +#- platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: zone1_triggered +# name: "Zone 1 Triggered" +# register_type: read +# address: 0x0001 +# bitmask: 0x40 +#- platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: zone2_triggered +# name: "Zone 2 Triggered" +# register_type: read +# address: 0x0001 +# bitmask: 0x80 +#- platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: zone3_triggered +# name: "Zone 3 Triggered" +# register_type: read +# address: 0x0001 +# bitmask: 0x100 +#- platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: zone4_triggered +# name: "Zone 4 Triggered" +# register_type: read +# address: 0x0001 +# bitmask: 0x200 +#- platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: zone5_triggered +# name: "Zone 5 Triggered" +# register_type: read +# address: 0x0001 +# bitmask: 0x400 +#- platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: zone6_triggered +# name: "Zone 6 Triggered" +# register_type: read +# address: 0x0001 +# bitmask: 0x800 +# #sensor: -#- platform: modbus_controller -# modbus_controller_id: modbus_device1 -# name: "Inverter1 Type" -# register_type: holding -# address: ${Felicity_Inv_Type} # 0xF800 -# value_type: U_DWORD -# register_count: 1 -#- platform: modbus_controller -# modbus_controller_id: modbus_device1 -# name: "Inverter1 Sub Type" -# register_type: holding -# address: ${Felicity_Inv_SubType} # 0xF801 -# value_type: U_DWORD -# register_count: 3 +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: z1z4_current +# name: "Z1Z4 Current" +# address: 0x0002 +# register_type: holding +# value_type: S_WORD +# register_count: 4 +# unit_of_measurement: "mA" +# lambda: |- +# return x / 1024.0; # -#- platform: modbus_controller -# modbus_controller_id: modbus_device1 -# name: "Inverter1 CPU1 F/W Version" -# register_type: holding -# address: ${Felicity_Inv_CPU1_FW_Version} # 0xF80B -# value_type: U_DWORD -# register_count: 1 +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: z2z5_current +# name: "Z2Z5 Current" +# address: 0x0006 +# register_type: holding +# value_type: S_WORD +# register_count: 4 +# unit_of_measurement: "mA" +# lambda: |- +# return x / 1024.0; # -#- platform: modbus_controller -# modbus_controller_id: modbus_device1 -# name: "Inverter1 CPU2 F/W Version" -# register_type: holding -# address: ${Felicity_Inv_CPU2_FW_Version} # 0xF80C -# value_type: U_DWORD -# register_count: 3 +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: z3z6_current +# name: "Z3Z6 Current" +# address: 0x000A +# register_type: holding +# value_type: S_WORD +# register_count: 4 +# unit_of_measurement: "mA" +# lambda: |- +# return x / 1024.0; # -#- platform: modbus_controller -# modbus_controller_id: modbus_device1 -# name: "Inverter1 Working Mode" -# register_type: holding -# address: ${Felicity_Inv_WorkingMode} # 0x1101 -# value_type: U_WORD -# register_count: 1 +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: z1z4_bus_voltage +# name: "Z1Z4 Bus Voltage" +# address: 0x000E +# register_type: holding +# value_type: S_WORD +# register_count: 4 +# unit_of_measurement: "V" +# lambda: |- +# return x / 1024.0; # -#- platform: modbus_controller -# modbus_controller_id: modbus_device1 -# name: "Inverter1 Charge Mode" -# register_type: holding -# address: ${Felicity_Inv_BatteryChargingStage} # 0x1102 -# value_type: U_WORD -# register_count: 1 +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: z2z5_bus_voltage +# name: "Z2Z5 Bus Voltage" +# address: 0x0012 +# register_type: holding +# value_type: S_WORD +# register_count: 4 +# unit_of_measurement: "V" +# lambda: |- +# return x / 1024.0; # -#- platform: modbus_controller -# modbus_controller_id: modbus_device1 -# name: "Inverter1 Fault Code" -# register_type: holding -# address: ${Felicity_Inv_FaultCode} # 0x1103 -# value_type: U_WORD -# register_count: 1 +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: z3z6_bus_voltage +# name: "Z3Z6 Bus Voltage" +# address: 0x0016 +# register_type: holding +# value_type: S_WORD +# register_count: 4 +# unit_of_measurement: "V" +# lambda: |- +# return x / 1024.0; # -#- platform: modbus_controller -# modbus_controller_id: modbus_device1 -# name: "Inverter1 Power Flow" -# register_type: holding -# address: ${Felicity_Inv_PowerFlowMsg} # 0x1104 -# value_type: U_WORD -# register_count: 4 +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: z1z4_power +# name: "Z1Z4 Power" +# address: 0x001A +# register_type: holding +# value_type: S_WORD +# register_count: 4 +# unit_of_measurement: "W" +# lambda: |- +# return x / 1024.0; # -#- platform: modbus_controller -# modbus_controller_id: modbus_device1 -# name: "Inverter1 Battery Voltage" -# register_type: holding -# address: ${Felicity_Inv_BatteryVoltage} # 0x1108 -# value_type: U_WORD -# register_count: 1 -# -#- platform: modbus_controller -# modbus_controller_id: modbus_device1 -# name: "Inverter1 Battery Current" -# register_type: holding -# address: ${Felicity_Inv_BatteryCurrent} # 0x1109 -# value_type: U_WORD -# register_count: 1 -# -#- platform: modbus_controller -# modbus_controller_id: modbus_device1 -# name: "Inverter1 BatteryPower" -# register_type: holding -# address: ${Felicity_Inv_BatteryPower} # 0x110A -# value_type: U_WORD -# register_count: 7 -# -#- platform: modbus_controller -# modbus_controller_id: modbus_device1 -# name: "Inverter1 AC Output Voltage" -# register_type: holding -# address: ${Felicity_Inv_ACOutputVoltage} # 0x1111 -# value_type: U_WORD -# register_count: 6 -# -#- platform: modbus_controller -# modbus_controller_id: modbus_device1 -# name: "Inverter1 AC Input Voltage" -# register_type: holding -# address: ${Felicity_Inv_ACInputVoltage} # 0x1117 -# value_type: U_WORD -# register_count: 2 -# -#- platform: modbus_controller -# modbus_controller_id: modbus_device1 -# name: "Inverter1 AC Input Frequency" -# register_type: holding -# address: ${Felicity_Inv_ACInputFrequency} # 0x1119 -# value_type: U_WORD -# register_count: 5 -# -#- platform: modbus_controller -# modbus_controller_id: modbus_device1 -# name: "Inverter1 AC Output Active Power" -# register_type: holding -# address: ${Felicity_Inv_ACOutputActivePower} # 0x111E -# value_type: U_WORD -# register_count: 1 -# -#- platform: modbus_controller -# modbus_controller_id: modbus_device1 -# name: "Inverter1 AC Output Apparent Power" -# register_type: holding -# address: ${Felicity_Inv_ACOutputApparentPower} # 0x111F -# value_type: U_WORD -# register_count: 1 -# -#- platform: modbus_controller -# modbus_controller_id: modbus_device1 -# name: "Inverter1 Load Percentage" -# register_type: holding -# address: ${Felicity_Inv_LoadPercentage} # 0x1120 -# value_type: U_WORD -# register_count: 6 -# -#- platform: modbus_controller -# modbus_controller_id: modbus_device1 -# name: "Inverter1 PV Input Voltage" -# register_type: holding -# address: ${Felicity_Inv_PVInputVoltage} # 0x1126 -# value_type: U_WORD -# register_count: 4 -# -#- platform: modbus_controller -# modbus_controller_id: modbus_device1 -# name: "Inverter1 PV Input Power" -# register_type: holding -# address: ${Felicity_Inv_PVInputPower} # 0x112A -# value_type: U_WORD -# register_count: 1 -# -#text_sensor: -#- platform: modbus_controller -# modbus_controller_id: modbus_device1 -# name: "Inverter1 SerialNo" -# register_type: holding -# address: ${Felicity_Inv_SerialNo} # 0xF804 -# response_size: 14 -# register_count: 7 -# raw_encode: HEXBYTES -# \ No newline at end of file +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: z2z5_power +# name: "Z2Z5 Power" +# address: 0x001E +# register_type: holding +# value_type: S_WORD +# register_count: 4 +# unit_of_measurement: "W" +# lambda: |- +# return x / 1024.0; +# +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: z3z6_power +# name: "Z3Z6 Power" +# address: 0x0022 +# register_type: holding +# value_type: S_WORD +# register_count: 4 +# unit_of_measurement: "W" +# lambda: |- +# return x / 1024.0; +# +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: z1z4_shunt_voltage +# name: "Z1Z4 Shunt Voltage" +# address: 0x0026 +# register_type: holding +# value_type: S_WORD +# register_count: 4 +# unit_of_measurement: "V" +# lambda: |- +# return x / 1024.0; +# +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: z2z5_shunt_voltage +# name: "Z2Z5 Shunt Voltage" +# address: 0x002A +# register_type: holding +# value_type: S_WORD +# register_count: 4 +# unit_of_measurement: "V" +# lambda: |- +# return x / 1024.0; +# +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: z3z6_shunt_voltage +# name: "Z3Z6 Shunt Voltage" +# address: 0x002E +# register_type: holding +# value_type: S_WORD +# register_count: 4 +# unit_of_measurement: "V" +# lambda: |- +# return x / 1024.0; +# +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: alarm_signal +# name: "Alarm Signal" +# address: 0x0032 +# register_type: holding +# value_type: S_WORD +# register_count: 4 +# unit_of_measurement: "V" +# lambda: |- +# return x / 1024.0; + +script: + - id: canbus_send_heartbeat + then: + lambda: |- + using namespace solar; + std::vector x(cbf_sthome::heartbeat.begin(), cbf_sthome::heartbeat.end()); + id(g_cb_cache).send_frame(id(canbus_sthome), cbf_sthome::CB_CANBUS_ID02, x); \ No newline at end of file diff --git a/sthome-ut3.yaml b/sthome-ut3.yaml index 66dd441..4cb618e 100644 --- a/sthome-ut3.yaml +++ b/sthome-ut3.yaml @@ -1,10 +1,18 @@ +external_components: + - source: + type: local + path: components # Path relative to this YAML file + components: [ rtq6056 ] #ads1115_int, tlc59208f_ext, ds3231, ac_voltage_sensor ] #, ads131m08 ] + packages: - - !include common/wifi.yaml + - !include ./common/wifi.yaml + - !include common/canbus.yaml + - !include common/modbus.yaml substitutions: name: sthome-ut3 friendly_name: "sthome-ut3" - + esphome: name: "${name}" friendly_name: "${friendly_name}" @@ -19,27 +27,55 @@ esphome: - globals: + - id: ctr + type: int + restore_value: no + initial_value: '0' + - id: geyser_relay_status type: bool restore_value: yes initial_value: 'false' - + + - id: can_msgctr + type: int + restore_value: no + - id: can2_msgctr + type: int + restore_value: no + - id: last_battery_message_time + type: time_t + restore_value: no + - id: g_cb_cache # the cache is used to only accept a frame after it has been received a specified number of times within a specified period. this hopefully will iron out spurious corrupted frames + type: solar::cbf_cache + restore_value: no + - id: g_cb_request_queue + type: std::queue< std::set > + restore_value: no + debug: update_interval: 10s esp32: board: nodemcu-32s #esp32dev + cpu_frequency: 240MHz framework: - type: arduino #esp-idf - + type: esp-idf #arduino # + # Enable logging logger: - level: VERY_VERBOSE - initial_level: DEBUG + level: DEBUG + initial_level: INFO logs: - uart: VERY_VERBOSE - modbus: VERBOSE - modbus_controller: VERBOSE + uart_debug: INFO # If set to DEBUG, logs raw UART data in hex format, with direction indication (RX/TX) and timestamp. Can be very verbose, so use with caution. + modbus: DEBUG + modbus_controller: INFO + light: INFO + output: INFO + canbus: DEBUG + canbus_mcp2515: DEBUG + sensor: INFO + rtq6056: INFO # Enable Home Assistant API api: @@ -71,6 +107,339 @@ sun: time: - platform: homeassistant + id: time_source + +interval: + - interval: 30s + then: + - lambda: |- + id(canbus_send_heartbeat).execute(); + +# - interval: 1.707s +# then: +# - uart.write: +# data: "sthome3 heartbeat\r\n" +# int loopsec = id(ctr) % 10; +# id(ctr)++; +# ESP_LOGI("main_loop", "Loop sec: %d", loopsec); +# if (loopsec == 0) { +# id(zone1_light).turn_on().set_brightness(1).perform(); +# } else { +# id(zone1_light).turn_off().perform(); +# } +# if (loopsec == 1) { +# id(zone2_light).turn_on().set_brightness(1).perform(); +# } else { +# id(zone2_light).turn_off().perform(); +# } +# if (loopsec == 2) { +# id(zone3_light).turn_on().set_brightness(1).perform(); +# } else { +# id(zone3_light).turn_off().perform(); +# } +# if (loopsec == 3) { +# id(zone4_light).turn_on().set_brightness(1).perform(); +# } else { +# id(zone4_light).turn_off().perform(); +# } +# if (loopsec == 4) { +# id(zone5_light).turn_on().set_brightness(1).perform(); +# } else { +# id(zone5_light).turn_off().perform(); +# } +# if (loopsec == 5) { +# id(zone6_light).turn_on().set_brightness(1).perform(); +# } else { +# id(zone6_light).turn_off().perform(); +# } +# if (loopsec == 7) { +# id(lights_frontyard_relay).turn_on(); +# } else { +# id(lights_frontyard_relay).turn_off(); +# } +# if (loopsec == 8) { +# id(lights_backyard_relay).turn_on(); +# } else { +# id(lights_backyard_relay).turn_off(); +# } +# if (loopsec == 9) { +# id(alarm_zone4_relay).turn_on(); +# } else { +# id(alarm_zone4_relay).turn_off(); +# } + +i2c: + - id: bus_a + sda: GPIO21 + scl: GPIO22 + scan: true + frequency: 200kHz + +spi: + - id: spi_bus0 + clk_pin: GPIO18 + mosi_pin: GPIO23 + miso_pin: GPIO19 + interface: any + +uart: + - id: sth_uart + tx_pin: GPIO17 # Tx1 + rx_pin: + number: GPIO16 # Rx1 + inverted: false + mode: + input: true + pullup: true + baud_rate: ${STHOME_ALARM_MODBUS_BAUD_RATE} + stop_bits: 1 + parity: NONE + debug: + direction: BOTH + dummy_receiver: false + after: + delimiter: "\n" + sequence: + - lambda: UARTDebug::log_hex(direction, bytes, ','); +####################### MODBUS SERVER ############################################# +modbus: + - id: modbus_server + uart_id: sth_uart + send_wait_time: 1200ms #250ms + disable_crc: false + role: server + +modbus_controller: + - id: modbus_alarm_server + modbus_id: modbus_server + address: ${STHOME_ALARM_MODBUS_DEVICE_ADDRESS} + server_registers: + - address: ${STHOME_ALARM_MODBUS_REG_ALARM_STATUS} + value_type: S_DWORD_R + read_lambda: |- + uint16_t alarm_status = id(person_detected_switch).state ? 1 : 0; + alarm_status |= id(alarm_zone4_relay).state ? 2 : 0; + alarm_status |= id(lights_backyard_relay).state ? 4 : 0; + alarm_status |= id(lights_frontyard_relay).state ? 8 : 0; + alarm_status |= id(floodlight_test).state ? 16 : 0; + alarm_status |= id(night_time).state ? 32 : 0; + alarm_status |= id(zone1_triggered).state ? 64 : 0; + alarm_status |= id(zone2_triggered).state ? 128 : 0; + alarm_status |= id(zone3_triggered).state ? 256 : 0; + alarm_status |= id(zone4_triggered).state ? 512 : 0; + alarm_status |= id(zone5_triggered).state ? 1024 : 0; + alarm_status |= id(zone6_triggered).state ? 2048 : 0; + return alarm_status; + - address: ${STHOME_ALARM_MODBUS_REG_ALARM_SIGNAL} + value_type: FP32_R + read_lambda: |- + return ${STHOME_ALARM_MODBUS_FLOAT_SCALE} * id(alarm_signal).state; + - address: ${STHOME_ALARM_MODBUS_REG_Z1Z4_CURRENT} + value_type: FP32_R + read_lambda: |- + return ${STHOME_ALARM_MODBUS_FLOAT_SCALE} * id(z1z4_current_ma).state; + - address: ${STHOME_ALARM_MODBUS_REG_Z2Z5_CURRENT} + value_type: FP32_R + read_lambda: |- + return ${STHOME_ALARM_MODBUS_FLOAT_SCALE} * id(z2z5_current_ma).state; + - address: ${STHOME_ALARM_MODBUS_REG_Z3Z6_CURRENT} + value_type: FP32_R + read_lambda: |- + return ${STHOME_ALARM_MODBUS_FLOAT_SCALE} * id(z3z6_current_ma).state; + - address: ${STHOME_ALARM_MODBUS_REG_Z1Z4_BUS_VOLTAGE} + value_type: FP32_R + read_lambda: |- + return ${STHOME_ALARM_MODBUS_FLOAT_SCALE} * id(z1z4_bus_voltage).state; + - address: ${STHOME_ALARM_MODBUS_REG_Z2Z5_BUS_VOLTAGE} + value_type: FP32_R + read_lambda: |- + return ${STHOME_ALARM_MODBUS_FLOAT_SCALE} * id(z2z5_bus_voltage).state; + - address: ${STHOME_ALARM_MODBUS_REG_Z3Z6_BUS_VOLTAGE} + value_type: FP32_R + read_lambda: |- + return ${STHOME_ALARM_MODBUS_FLOAT_SCALE} * id(z3z6_bus_voltage).state; + - address: ${STHOME_ALARM_MODBUS_REG_Z1Z4_POWER} + value_type: FP32_R + read_lambda: |- + return ${STHOME_ALARM_MODBUS_FLOAT_SCALE} * id(z1z4_power).state; + - address: ${STHOME_ALARM_MODBUS_REG_Z2Z5_POWER} + value_type: FP32_R + read_lambda: |- + return ${STHOME_ALARM_MODBUS_FLOAT_SCALE} * id(z2z5_power).state; + - address: ${STHOME_ALARM_MODBUS_REG_Z3Z6_POWER} + value_type: FP32_R + read_lambda: |- + return ${STHOME_ALARM_MODBUS_FLOAT_SCALE} * id(z3z6_power).state; + - address: ${STHOME_ALARM_MODBUS_REG_Z1Z4_SHUNT_VOLTAGE} + value_type: FP32_R + read_lambda: |- + return ${STHOME_ALARM_MODBUS_FLOAT_SCALE} * id(z1z4_shunt_voltage).state; + - address: ${STHOME_ALARM_MODBUS_REG_Z2Z5_SHUNT_VOLTAGE} + value_type: FP32_R + read_lambda: |- + return ${STHOME_ALARM_MODBUS_FLOAT_SCALE} * id(z2z5_shunt_voltage).state; + - address: ${STHOME_ALARM_MODBUS_REG_Z3Z6_SHUNT_VOLTAGE} + value_type: FP32_R + read_lambda: |- + return ${STHOME_ALARM_MODBUS_FLOAT_SCALE} * id(z3z6_shunt_voltage).state; + + +############################ MODBUS CLIENT EXAMPLE ############################################# +#modbus: +# - id: modbus_client +# uart_id: sth_uart +# send_wait_time: 250ms #1200ms #250ms +# disable_crc: false +# role: client +# +#modbus_controller: +# - id: modbus_alarm_client +# modbus_id: modbus_client +# address: 0x01 +# allow_duplicate_commands: False +# command_throttle: 700ms #2022ms +# update_interval: 10s #305s +# offline_skip_updates: 2 +# max_cmd_retries: 1 +# setup_priority: -10 + + +canbus: + - platform: mcp2515 + cs_pin: GPIO32 # CBCS + spi_id: spi_bus0 + id: canbus_sthome + mode: NORMAL + can_id: ${CB_CANBUS_ID03} + bit_rate: 500KBPS + on_frame: + - can_id: 0 + can_id_mask: 0 + then: + - lambda: |- + id(can_msgctr)++; + using namespace solar; + auto time_obj = id(time_source).now(); + //ESP_LOGI("CB REC", "%s", id(time_source).now().strftime("%a %d %b %Y - %I:%M:%S %p")); + if(time_obj.is_valid()) { + if (can_id >= 0x380 && can_id < 0x3C0) { + auto cbitem = cbf_store_sthome(id(can_msgctr), can_id, x, remote_transmission_request, time_obj.timestamp); + bool publish = id(g_cb_cache).additem(cbitem); + if(publish) { + ESP_LOGI(cbitem.tag().c_str(), "%s", cbitem.to_string().c_str()); + } + } + else { + auto cbitem = cbf_store_sthome(id(can_msgctr), can_id, x, remote_transmission_request, time_obj.timestamp); + ESP_LOGI(cbitem.tag().c_str(), "%s", cbitem.to_string().c_str()); + } + } + +output: + - platform: ledc + pin: + number: GPIO14 + inverted: false + id: zone1_led + frequency: 1000 Hz + - platform: ledc + pin: + number: GPIO12 + inverted: false + id: zone2_led + frequency: 1000 Hz + - platform: ledc + pin: + number: GPIO13 + inverted: false + id: zone3_led + frequency: 1000 Hz + - platform: ledc + pin: + number: GPIO4 + inverted: false + id: zone4_led + frequency: 1000 Hz + - platform: ledc + pin: + number: GPIO2 + inverted: false + id: zone5_led + frequency: 1000 Hz + - platform: ledc + pin: + number: GPIO15 + inverted: false + id: zone6_led + frequency: 1000 Hz + +light: + - platform: monochromatic + output: zone1_led + name: "Zone 1" + id: zone1_light + default_transition_length: 5ms + on_turn_on: + - lambda: |- + ESP_LOGV("zone", "Zone 1 on"); + on_turn_off: + - lambda: |- + ESP_LOGV("zone", "Zone 1 off"); + - platform: monochromatic + output: zone2_led + name: "Zone 2" + id: zone2_light + default_transition_length: 5ms + on_turn_on: + - lambda: |- + ESP_LOGV("zone", "Zone 2 on"); + on_turn_off: + - lambda: |- + ESP_LOGV("zone", "Zone 2 off"); + - platform: monochromatic + output: zone3_led + name: "Zone 3" + id: zone3_light + default_transition_length: 5ms + on_turn_on: + - lambda: |- + ESP_LOGV("zone", "Zone 3 on"); + on_turn_off: + - lambda: |- + ESP_LOGV("zone", "Zone 3 off"); + - platform: monochromatic + output: zone4_led + name: "Zone 4" + id: zone4_light + default_transition_length: 5ms + on_turn_on: + - lambda: |- + ESP_LOGV("zone", "Zone 4 on"); + on_turn_off: + - lambda: |- + ESP_LOGV("zone", "Zone 4 off"); + - platform: monochromatic + output: zone5_led + name: "Zone 5" + id: zone5_light + default_transition_length: 5ms + on_turn_on: + - lambda: |- + ESP_LOGV("zone", "Zone 5 on"); + on_turn_off: + - lambda: |- + ESP_LOGV("zone", "Zone 5 off"); + - platform: monochromatic + output: zone6_led + name: "Zone 6" + id: zone6_light + default_transition_length: 5ms + on_turn_on: + - lambda: |- + ESP_LOGV("zone", "Zone 6 on"); + on_turn_off: + - lambda: |- + ESP_LOGV("zone", "Zone 6 off"); text_sensor: - platform: debug @@ -91,11 +460,43 @@ switch: name: "${name} Restart" id: "restart_switch" + # if person detected, trigger zone4. If the alarm is armed, alarm_zone4_relay will cause the alarm to sound + # if alarm is not armed, alarm_zone4_relay will not have any effect and the alarm will not sound. However, we will still turn on the floodlights for both backyard and frontyard if it's night time or floodlight test mode is on. + # the person detected switch will be turned on by home-assistant automation when the person detection frigate sensor detects a person. We will use this switch to trigger the alarm and floodlights, and then turn it off after a short delay to reset the state for the next detection. + - platform: template + name: "Person Detected" + id: person_detected_switch + icon: "mdi:alarm-light-outline" + restore_mode: RESTORE_DEFAULT_OFF + turn_on_action: + - switch.turn_on: alarm_zone4_relay + - if: + condition: + or: + - binary_sensor.is_on: night_time + - binary_sensor.is_on: floodlight_test + then: + # if alarm is triggered and lights are enabled, turn on the floodlights for both backyard and frontyard + - switch.turn_on: lights_backyard_relay + - switch.turn_on: lights_frontyard_relay + - platform: gpio pin: - number: GPIO16 + number: GPIO25 inverted: true - id: relay1 + id: alarm_zone4_relay + name: "Alarm Zone 4" + icon: "mdi:alarm-light-outline" + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + - delay: 20s + - switch.turn_off: alarm_zone4_relay + + - platform: gpio + pin: + number: GPIO26 + inverted: true + id: lights_backyard_relay name: "Floodlights Backyard" icon: "mdi:light-flood-down" restore_mode: RESTORE_DEFAULT_OFF @@ -103,65 +504,117 @@ switch: # TODO: remove or extend auto turn off to >= 10min on_turn_on: - delay: 250s - - switch.turn_off: relay1 + - switch.turn_off: lights_backyard_relay - platform: gpio pin: - number: GPIO17 + number: GPIO27 inverted: true - id: relay2 - name: "Relay 2" - icon: "mdi:run-fast" + id: lights_frontyard_relay + name: "Floodlights Frontyard" + icon: "mdi:light-flood-down" restore_mode: RESTORE_DEFAULT_OFF + # the frontyard floodlight auto turns off in x sec. So we need to switch relay off just before or at this time + # TODO: remove or extend auto turn off to >= 10min on_turn_on: - - delay: 1000ms - - switch.turn_off: relay2 - - - platform: gpio - pin: - number: GPIO18 - inverted: true - id: relay3 - name: "Relay 3" - icon: "mdi:run-fast" - restore_mode: RESTORE_DEFAULT_OFF - on_turn_on: - - delay: 1000ms - - switch.turn_off: relay3 - - - platform: gpio - pin: - number: GPIO19 - inverted: true - id: relay4 - name: "Alarm Zone 4" - icon: "mdi:alarm-light-outline" - restore_mode: RESTORE_DEFAULT_OFF - on_turn_on: - - if: - condition: - - lambda: |- - double sun_elevation = id(sun_sensor).elevation(); - return (sun_elevation <= -6); // -6° = civil twilight, -12° = nautical twilight, -18° = astronomical twilight - #- sun.is_below_horizon: - then: - - switch.turn_on: relay1 - - if: - condition: - - binary_sensor.is_on: floodlight_test - then: - - switch.turn_on: relay1 - - delay: 30s - - switch.turn_off: relay4 + - delay: 250s + - switch.turn_off: lights_frontyard_relay # define DIGITAL_D1 04 binary_sensor: +##### MODBUS CLIENT BINARY SENSORS ### +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: mbc_person_detected +# name: "MBC Person Detected" +# register_type: read +# address: 0x0001 +# bitmask: 0x1 +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: mbc_alarm_zone4_relay +# name: "MBC Alarm Zone 4 Relay" +# register_type: read +# address: 0x0001 +# bitmask: 0x2 +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: mbc_lights_backyard_relay +# name: "MBC Lights Backyard Relay" +# register_type: read +# address: 0x0001 +# bitmask: 0x4 +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: mbc_lights_frontyard_relay +# name: "MBC Lights Frontyard Relay" +# register_type: read +# address: 0x0001 +# bitmask: 0x8 +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: mbc_floodlight_test +# name: "MBC Floodlight Test" +# register_type: read +# address: 0x0001 +# bitmask: 0x10 +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: mbc_night_time +# name: "MBC Night Time" +# register_type: read +# address: 0x0001 +# bitmask: 0x20 +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: mbc_zone1_triggered +# name: "MBC Zone 1 Triggered" +# register_type: read +# address: 0x0001 +# bitmask: 0x40 +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: mbc_zone2_triggered +# name: "MBC Zone 2 Triggered" +# register_type: read +# address: 0x0001 +# bitmask: 0x80 +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: mbc_zone3_triggered +# name: "MBC Zone 3 Triggered" +# register_type: read +# address: 0x0001 +# bitmask: 0x100 +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: mbc_zone4_triggered +# name: "MBC Zone 4 Triggered" +# register_type: read +# address: 0x0001 +# bitmask: 0x200 +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: mbc_zone5_triggered +# name: "MBC Zone 5 Triggered" +# register_type: read +# address: 0x0001 +# bitmask: 0x400 +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: mbc_zone6_triggered +# name: "MBC Zone 6 Triggered" +# register_type: read +# address: 0x0001 +# bitmask: 0x800 +### END OF MODBUS CLIENT BINARY SENSORS ### + - platform: gpio # device_class: light id: floodlight_test pin: - number: GPIO04 + number: GPIO33 mode: input: true pullup: true @@ -170,9 +623,385 @@ binary_sensor: name: "Floodlights Test Mode" icon: "mdi:lightbulb-on-outline" + - platform: template + name: "Night Time" + id: night_time + lambda: |- + // we will use the sun elevation to determine if the floodlights should be on or not. If the sun elevation is below -6 degrees, we will consider it as night time + return false; // debug + double sun_elevation = id(sun_sensor).elevation(); + return (sun_elevation <= -6); // -6° = civil twilight, -12° = nautical twilight, -18° = astronomical twilight + + - platform: template + name: "Zone 1 Triggered" + id: zone1_triggered + lambda: |- + // NB! LED will light up when zone is interrupted, so the logic is inverted. When the zone is not interrupted, there will be current flowing through the shunt and the LED will be off. When the zone is interrupted, there will be no current flowing through the shunt and the LED will be on. So we will use the current reading to determine if the zone is interrupted or not. We will also use the current reading to determine if both zones S1 and S4 are active or not, since they are on the same shunt and we can only get combined current for both zones. + // Using up to maximum of 13V supply, 2200ohm and 3300ohm resistors, we can get a maximum of around 10.91mA current through the shunt when both zones S1 and S4 are active, below 7.6mA and above 4.6mA when S1 is active and S4 is not active, and below 3.3mA when S1 is not active and S4 is active. So we can use 3.3mA as a threshold to determine if S1 is #active r not, and 7.6mA as a threshold to determine if S4 is active or not. If the current is below 3.3mA, we can assume that both zones are inactive and return false. If the #current is bove 7.6mA, we can assume that both zones are active and return true. If the current is between 3.3mA and 7.6mA, we can assume that only one of the zones is active #and return true or zone 1 and false for zone 4. + double current = id(z1z4_current_ma).state; // in mA + if (current >= 8) { + id(zone1_light).turn_on().set_brightness(1).perform(); + return true; // Error condition, current is higher than expected. This could be a full short circuit done by burglar to bypass the shunt and make the alarm think that zone 1 is not active, so we will treat it as zone 1 being active to be safe. We can later add a separate binary sensor for short circuit detection if needed. + } + if (current > 4.5) { + id(zone1_light).turn_off().perform(); + return false; // Zone 1 or both zones are inactive + } + id(zone1_light).turn_on().set_brightness(1).perform(); + return true; // Zone 1 is active (either alone or with Zone 4) + + - platform: template + name: "Zone 4 Triggered" + id: zone4_triggered + lambda: |- + double current = id(z1z4_current_ma).state; // in mA + if(current < 2) { + id(zone4_light).turn_on().set_brightness(1).perform(); + return true; // both zones are active + } + if (current < 4.5) { + id(zone4_light).turn_off().perform(); + return false; // Zone 4 is inactive, Zone 1 is active + } + if (current < 6) { + id(zone4_light).turn_on().set_brightness(1).perform(); + return true; // Zone 4 is active, Zone 1 is inactive + } + if (current < 8){ + id(zone4_light).turn_off().perform(); + return false; // Zone 4 is inactive, Zone 1 is inactive + } + id(zone4_light).turn_on().set_brightness(1).perform(); + return true; // Error case, current is higher than expected. This could be a full short circuit done by burglar to bypass the shunt and make the alarm think that zone 4 is not active, so we will treat it as zone 4 being active to be safe. We can later add a separate binary sensor for short circuit detection if needed. + + - platform: template + name: "Zone 2 Triggered" + id: zone2_triggered + lambda: |- + double current = id(z2z5_current_ma).state; // in mA + if (current >= 8) { + id(zone2_light).turn_on().set_brightness(1).perform(); + return true; + } + if (current > 4.5) { + id(zone2_light).turn_off().perform(); + return false; // Zone 2 or both zones are inactive + } + id(zone2_light).turn_on().set_brightness(1).perform(); + return true; // Zone 2 is active (either alone or with Zone 5) + + - platform: template + name: "Zone 5 Triggered" + id: zone5_triggered + lambda: |- + double current = id(z2z5_current_ma).state; // in mA + if(current < 2) { + id(zone5_light).turn_on().set_brightness(1).perform(); + return true; // both zones are active + } + if (current < 4.5) { + id(zone5_light).turn_off().perform(); + return false; // Zone 5 is inactive, Zone 2 is active + } + if (current < 6) { + id(zone5_light).turn_on().set_brightness(1).perform(); + return true; // Zone 5 is active, Zone 2 is inactive + } + if (current < 8){ + id(zone5_light).turn_off().perform(); + return false; // Zone 5 is inactive, Zone 2 is inactive + } + id(zone5_light).turn_on().set_brightness(1).perform(); + return true; // Error case + + - platform: template + name: "Zone 3 Triggered" + id: zone3_triggered + lambda: |- + double current = id(z3z6_current_ma).state; // in mA + if (current >= 8) { + id(zone3_light).turn_on().set_brightness(1).perform(); + return true; + } + if (current > 4.5) { + id(zone3_light).turn_off().perform(); + return false; // Zone 3 or both zones are inactive + } + id(zone3_light).turn_on().set_brightness(1).perform(); + return true; // Zone 3 is active (either alone or with Zone 6) + + - platform: template + name: "Zone 6 Triggered" + id: zone6_triggered + lambda: |- + double current = id(z3z6_current_ma).state; // in mA + if(current < 2) { + id(zone6_light).turn_on().set_brightness(1).perform(); + return true; // both zones are active + } + if (current < 4.5) { + id(zone6_light).turn_off().perform(); + return false; // Zone 6 is inactive, Zone 3 is active + } + if (current < 6) { + id(zone6_light).turn_on().set_brightness(1).perform(); + return true; // Zone 6 is active, Zone 3 is inactive + } + if (current < 8){ + id(zone6_light).turn_off().perform(); + return false; // Zone 6 is inactive, Zone 3 is inactive + } + id(zone6_light).turn_on().set_brightness(1).perform(); + return true; // Error case + sensor: +#### MODBUS CLIENT SENSORS ### +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: mbc_z1z4_current +# name: "MBC Z1Z4 Current" +# address: 0x0002 +# register_type: holding +# value_type: S_WORD +# register_count: 4 +# unit_of_measurement: "mA" +# lambda: |- +# return x / 1024.0; +# +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: mbc_z2z5_current +# name: "MBC Z2Z5 Current" +# address: 0x0006 +# register_type: holding +# value_type: S_WORD +# register_count: 4 +# unit_of_measurement: "mA" +# lambda: |- +# return x / 1024.0; +# +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: mbc_z3z6_current +# name: "MBC Z3Z6 Current" +# address: 0x000A +# register_type: holding +# value_type: S_WORD +# register_count: 4 +# unit_of_measurement: "mA" +# lambda: |- +# return x / 1024.0; +# +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: mbc_z1z4_bus_voltage +# name: "MBC Z1Z4 Bus Voltage" +# address: 0x000E +# register_type: holding +# value_type: S_WORD +# register_count: 4 +# unit_of_measurement: "V" +# lambda: |- +# return x / 1024.0; +# +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: mbc_z2z5_bus_voltage +# name: "MBC Z2Z5 Bus Voltage" +# address: 0x0012 +# register_type: holding +# value_type: S_WORD +# register_count: 4 +# unit_of_measurement: "V" +# lambda: |- +# return x / 1024.0; +# +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: mbc_z3z6_bus_voltage +# name: "MBC Z3Z6 Bus Voltage" +# address: 0x0016 +# register_type: holding +# value_type: S_WORD +# register_count: 4 +# unit_of_measurement: "V" +# lambda: |- +# return x / 1024.0; +# +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: mbc_z1z4_power +# name: "MBC Z1Z4 Power" +# address: 0x001A +# register_type: holding +# value_type: S_WORD +# register_count: 4 +# unit_of_measurement: "W" +# lambda: |- +# return x / 1024.0; +# +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: mbc_z2z5_power +# name: "MBC Z2Z5 Power" +# address: 0x001E +# register_type: holding +# value_type: S_WORD +# register_count: 4 +# unit_of_measurement: "W" +# lambda: |- +# return x / 1024.0; +# +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: mbc_z3z6_power +# name: "MBC Z3Z6 Power" +# address: 0x0022 +# register_type: holding +# value_type: S_WORD +# register_count: 4 +# unit_of_measurement: "W" +# lambda: |- +# return x / 1024.0; +# +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: mbc_z1z4_shunt_voltage +# name: "MBC Z1Z4 Shunt Voltage" +# address: 0x0026 +# register_type: holding +# value_type: S_WORD +# register_count: 4 +# unit_of_measurement: "V" +# lambda: |- +# return x / 1024.0; +# +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: mbc_z2z5_shunt_voltage +# name: "MBC Z2Z5 Shunt Voltage" +# address: 0x002A +# register_type: holding +# value_type: S_WORD +# register_count: 4 +# unit_of_measurement: "V" +# lambda: |- +# return x / 1024.0; +# +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: mbc_z3z6_shunt_voltage +# name: "MBC Z3Z6 Shunt Voltage" +# address: 0x002E +# register_type: holding +# value_type: S_WORD +# register_count: 4 +# unit_of_measurement: "V" +# lambda: |- +# return x / 1024.0; +# +# - platform: modbus_controller +# modbus_controller_id: modbus_alarm_client +# id: mbc_alarm_signal +# name: "MBC Alarm Signal" +# address: 0x0032 +# register_type: holding +# value_type: S_WORD +# register_count: 4 +# unit_of_measurement: "V" +# lambda: |- +# return x / 1024.0; +### END OF MODBUS CLIENT SENSORS ### + + - platform: rtq6056 + address: 0x40 + id: adc_z1z4 # Zone S1 and S4 are on the same shunt, so we can only get combined current for both zones. We will use this sensor for both zones and then split the readings in the template binary_sensor + shunt_resistance: 5 ohm + max_current: 16mA + # adc time used for both, Bus Voltage and Shunt Voltage + adc_time: 140us + adc_averaging: 16 + update_interval: 1s + current: + id: z1z4_current + power: + name: "Z1_Z4 Power" + id: z1z4_power + bus_voltage: + name: "Z1_Z4 Bus Voltage" + id: z1z4_bus_voltage + shunt_voltage: + name: "Z1_Z4 Shunt Voltage" + id: z1z4_shunt_voltage + + - platform: template + name: "Z1_Z4 Current" + id: z1z4_current_ma + unit_of_measurement: "mA" + accuracy_decimals: 3 + lambda: |- + return 1000 * id(z1z4_current).state; + + - platform: rtq6056 + address: 0x41 + id: adc_z2z5 # Zone S2 and S5 are on the same shunt, so we can only get combined current for both zones. We will use this sensor for both zones and then split the readings in the template binary_sensor + shunt_resistance: 5.5 ohm + max_current: 16mA + # adc time used for both, Bus Voltage and Shunt Voltage + adc_time: 140us + adc_averaging: 16 + update_interval: 1s + current: + id: z2z5_current + power: + name: "Z2_Z5 Power" + id: z2z5_power + bus_voltage: + name: "Z2_Z5 Bus Voltage" + id: z2z5_bus_voltage + shunt_voltage: + name: "Z2_Z5 Shunt Voltage" + id: z2z5_shunt_voltage + + - platform: template + name: "Z2_Z5 Current" + id: z2z5_current_ma + unit_of_measurement: "mA" + accuracy_decimals: 3 + lambda: |- + return 1000 * id(z2z5_current).state; + + - platform: rtq6056 + address: 0x42 + id: adc_z3z6 # Zone S3 and S6 are on the same shunt, so we can only get combined current for both zones. We will use this sensor for both zones and then split the readings in the template binary_sensor + shunt_resistance: 5 ohm + max_current: 16mA + # adc time used for both, Bus Voltage and Shunt Voltage + adc_time: 140us + adc_averaging: 16 + update_interval: 1s + current: + id: z3z6_current + power: + name: "Z3_Z6 Power" + id: z3z6_power + bus_voltage: + name: "Z3_Z6 Bus Voltage" + id: z3z6_bus_voltage + shunt_voltage: + name: "Z3_Z6 Shunt Voltage" + id: z3z6_shunt_voltage + + - platform: template + name: "Z3_Z6 Current" + id: z3z6_current_ma + unit_of_measurement: "mA" + accuracy_decimals: 3 + lambda: |- + return 1000 * id(z3z6_current).state; + - platform: adc - pin: 35 + pin: 39 name: "Alarm Signal" id: alarm_signal update_interval: 2000ms @@ -189,10 +1018,11 @@ sensor: } on_value: then: - # switch on floodlights + # alarm is sounding when the signal is above 1.5V, so we can use this threshold to determine if the alarm is triggered or not. If the alarm is triggered by external trigger, we will turn on the floodlights for both backyard and frontyard, whether it is night time or not. The floodlights will be turned on for 4min 14 sec, which is the auto turn off time of the floodlights, so we don't need to worry about turning them off manually. lambda: |- if (id(alarm_signal).state > 1.5) { - id(relay1).turn_on(); + id(lights_frontyard_relay).turn_on(); + id(lights_backyard_relay).turn_on(); } # human readable uptime sensor output to the text sensor above @@ -221,4 +1051,12 @@ sensor: (hours ? hours_str + "h " : "") + (minutes ? minutes_str + "m " : "") + (seconds_str + "s") - ).c_str(); \ No newline at end of file + ).c_str(); + +script: + - id: canbus_send_heartbeat + then: + lambda: |- + using namespace solar; + std::vector x(cbf_sthome::heartbeat.begin(), cbf_sthome::heartbeat.end()); + id(g_cb_cache).send_frame(id(canbus_sthome), cbf_sthome::CB_CANBUS_ID03, x); \ No newline at end of file diff --git a/sthome-ut9.yaml b/sthome-ut9.yaml index aac8c70..d90ba09 100644 --- a/sthome-ut9.yaml +++ b/sthome-ut9.yaml @@ -47,6 +47,30 @@ wifi: captive_portal: +uart: + - id: sth_uart + rx_pin: GPIO15 + tx_pin: GPIO16 + baud_rate: 2400 + stop_bits: 1 + parity: NONE + debug: + direction: BOTH + dummy_receiver: true + after: + delimiter: "\r" + sequence: + - lambda: UARTDebug::log_hex(direction, bytes, ','); + +interval: +# - interval: 10s +# then: +# - lambda: |- +# id(canbus_send_heartbeat).execute(); + - interval: 3.14s + then: + - uart.write: + data: "sthome9 heartbeat\r\n" sun: id: sun_sensor @@ -69,126 +93,12 @@ text_sensor: id: uptime_human icon: mdi:clock-start - -#define OUTPUT_R1 16 -#define OUTPUT_R2 17 -#define OUTPUT_R3 18 -#define OUTPUT_R4 19 switch: - platform: restart name: "${name} Restart" id: "restart_switch" - - platform: gpio - pin: - number: GPIO16 - inverted: true - id: relay1 - name: "Floodlights Backyard" - icon: "mdi:light-flood-down" - restore_mode: RESTORE_DEFAULT_OFF - # the backyard floodlight auto turns off in 4min 14 sec. So we need to switch relay off just before or at this time - # TODO: remove or extend auto turn off to >= 10min - on_turn_on: - - delay: 250s - - switch.turn_off: relay1 - - - platform: gpio - pin: - number: GPIO17 - inverted: true - id: relay2 - name: "Relay 2" - icon: "mdi:run-fast" - restore_mode: RESTORE_DEFAULT_OFF - on_turn_on: - - delay: 1000ms - - switch.turn_off: relay2 - - - platform: gpio - pin: - number: GPIO18 - inverted: true - id: relay3 - name: "Relay 3" - icon: "mdi:run-fast" - restore_mode: RESTORE_DEFAULT_OFF - on_turn_on: - - delay: 1000ms - - switch.turn_off: relay3 - - - platform: gpio - pin: - number: GPIO19 - inverted: true - id: relay4 - name: "Alarm Zone 4" - icon: "mdi:alarm-light-outline" - restore_mode: RESTORE_DEFAULT_OFF - on_turn_on: - - if: - condition: - - lambda: |- - double sun_elevation = id(sun_sensor).elevation(); - return (sun_elevation <= -6); // -6° = civil twilight, -12° = nautical twilight, -18° = astronomical twilight - #- sun.is_below_horizon: - then: - - switch.turn_on: relay1 - - if: - condition: - - binary_sensor.is_on: floodlight_test - then: - - switch.turn_on: relay1 - - delay: 30s - - switch.turn_off: relay4 - - -# define DIGITAL_D1 04 -binary_sensor: - - platform: gpio - # device_class: light - id: floodlight_test - pin: - number: GPIO04 - mode: - input: true - pullup: true - filters: - - delayed_off: 100ms - name: "Floodlights Test Mode" - icon: "mdi:lightbulb-on-outline" - -#define ANALOG_A1 33 -#define ANALOG_A2 32 -#define ANALOG_A3 35 -#define ANALOG_A4 34 -#define ANALOG_A5 39 -#define ANALOG_A6 36 -sensor: -# - platform: adc -# pin: 35 -# name: "Alarm Signal" -# id: alarm_signal -# update_interval: 2000ms -# attenuation: 12db -# sampling_mode: avg -# filters: -# - lambda: -# if (x >= 3.11) { -# return x * 1.60256; -# } else if (x <= 0.25) { -# return 0; -# } else { -# return x * 1.51; -# } -# on_value: -# then: -# # switch on floodlights -# lambda: |- -# if (id(alarm_signal).state > 1.5) { -# id(relay1).turn_on(); -# } - +sensor: # human readable uptime sensor output to the text sensor above - platform: uptime name: Uptime in Days