From 8af010794306ba28b2eb444f98434e8cbad0a1ef Mon Sep 17 00:00:00 2001 From: Oliver Kleinecke Date: Tue, 18 Feb 2025 11:29:29 +0100 Subject: [PATCH] feat: add FRAM support to DynamicLampComponent for enhanced functionality --- esphome/components/dynamic_lamp/__init__.py | 4 + .../components/dynamic_lamp/dynamic_lamp.h | 2 +- esphome/components/fram/FRAM.cpp | 629 ++++++++++++++++++ esphome/components/fram/FRAM.h | 164 +++++ esphome/components/fram/__init__.py | 64 ++ 5 files changed, 862 insertions(+), 1 deletion(-) create mode 100644 esphome/components/fram/FRAM.cpp create mode 100644 esphome/components/fram/FRAM.h create mode 100644 esphome/components/fram/__init__.py diff --git a/esphome/components/dynamic_lamp/__init__.py b/esphome/components/dynamic_lamp/__init__.py index 465d0691f0..4ec29ff127 100644 --- a/esphome/components/dynamic_lamp/__init__.py +++ b/esphome/components/dynamic_lamp/__init__.py @@ -1,5 +1,6 @@ import esphome.codegen as cg from esphome.components import output +from esphome.components.fram import FRAM from esphome.components.time import RealTimeClock import esphome.config_validation as cv from esphome.const import CONF_ID @@ -12,11 +13,13 @@ DynamicLampComponent = dynamic_lamp_ns.class_('DynamicLampComponent', cg.Compone CONF_DYNAMIC_LAMP_ID = "dynamic_lamp_id" CONF_RTC = 'rtc' +CONF_FRAM = 'fram' CONF_SAVE_MODE = 'save_mode' CONF_AVAILABLE_OUTPUTS = 'available_outputs' CONFIG_SCHEMA = cv.Schema({ cv.GenerateID(CONF_ID): cv.declare_id(DynamicLampComponent), cv.Required(CONF_RTC): cv.use_id(RealTimeClock), + cv.Required(CONF_FRAM): cv.use_id(FRAM), cv.Required(CONF_AVAILABLE_OUTPUTS): [cv.use_id(output.FloatOutput)], cv.Optional(CONF_SAVE_MODE, default=0): cv.int_range(0, 1), }).extend(cv.COMPONENT_SCHEMA) @@ -26,6 +29,7 @@ async def to_code(config): var = cg.new_Pvariable( config[CONF_ID], await cg.get_variable(config[CONF_RTC]), + await cg.get_variable(config[CONF_FRAM]), ) await cg.register_component(var, config) for outputPointer in config.get(CONF_AVAILABLE_OUTPUTS, []): diff --git a/esphome/components/dynamic_lamp/dynamic_lamp.h b/esphome/components/dynamic_lamp/dynamic_lamp.h index 4226daa607..7f09ceffa1 100644 --- a/esphome/components/dynamic_lamp/dynamic_lamp.h +++ b/esphome/components/dynamic_lamp/dynamic_lamp.h @@ -109,6 +109,7 @@ class DynamicLampComponent : public Component { protected: friend class DynamicLamp; time::RealTimeClock *rtc_; + FRAM *fram_; void restore_lamp_values_(uint8_t lamp_number); void set_lamp_values_(uint8_t lamp_number, bool active, uint16_t selected_outputs, uint8_t mode, uint8_t mode_value); bool write_state_(uint8_t lamp_number, float state); @@ -117,7 +118,6 @@ class DynamicLampComponent : public Component { LinkedOutput available_outputs_[16]; uint8_t save_mode_; uint8_t lamp_count_ = 0; - }; diff --git a/esphome/components/fram/FRAM.cpp b/esphome/components/fram/FRAM.cpp new file mode 100644 index 0000000000..ff578f1ff5 --- /dev/null +++ b/esphome/components/fram/FRAM.cpp @@ -0,0 +1,629 @@ +// +// FILE: FRAM.cpp +// AUTHOR: Rob Tillaart +// VERSION: 0.5.3 +// DATE: 2018-01-24 +// PURPOSE: Arduino library for I2C FRAM +// URL: https://github.com/RobTillaart/FRAM_I2C +// +// ESPHome port: https://github.com/sharkydog/esphome-fram + +#include "FRAM.h" + +namespace esphome { +namespace fram { + +// used for metadata and sleep +const uint8_t FRAM_SLAVE_ID_ = 0x7C; // == 0xF8 +const uint8_t FRAM_SLEEP_CMD = 0x86; // +static const char * const TAG = "fram"; + +///////////////////////////////////////////////////////////////////////////// +// +// FRAM PUBLIC +// + +void FRAM::setup() +{ + if (!this->isConnected()) + { + ESP_LOGE(TAG, "Device on address 0x%x not found!", this->address_); + this->mark_failed(); + } + else if (!this->getSize()) + { + ESP_LOGW(TAG, "Device on address 0x%x returned 0 size, set size in config!", this->address_); + } +} + +void FRAM::dump_config() +{ + ESP_LOGCONFIG(TAG, "FRAM:"); + ESP_LOGCONFIG(TAG, " Address: 0x%x", this->address_); + + bool ok = this->isConnected(); + + if (!ok) { + ESP_LOGE(TAG, " Device not found!"); + } + + if (this->_sizeBytes) { + ESP_LOGCONFIG(TAG, " Size: %uKiB", this->_sizeBytes / 1024UL); + } else if(ok) { + ESP_LOGW(TAG, " Size: 0KiB, set size in config!"); + } +} + + +bool FRAM::isConnected() +{ + i2c::ErrorCode err = this->bus_->write(this->address_, nullptr, 0, true); + return (err == i2c::ERROR_OK); +} + + +void FRAM::write8(uint16_t memaddr, uint8_t value) +{ + uint8_t val = value; + this->_writeBlock(memaddr, (uint8_t *)&val, sizeof(uint8_t)); +} + + +void FRAM::write16(uint16_t memaddr, uint16_t value) +{ + uint16_t val = value; + this->_writeBlock(memaddr, (uint8_t *)&val, sizeof(uint16_t)); +} + + +void FRAM::write32(uint16_t memaddr, uint32_t value) +{ + uint32_t val = value; + this->_writeBlock(memaddr, (uint8_t *)&val, sizeof(uint32_t)); +} + + +void FRAM::writeFloat(uint16_t memaddr, float value) +{ + float val = value; + this->_writeBlock(memaddr, (uint8_t *)&val, sizeof(float)); +} + + +void FRAM::writeDouble(uint16_t memaddr, double value) +{ + double val = value; + this->_writeBlock(memaddr, (uint8_t *)&val, sizeof(double)); +} + + +void FRAM::write(uint16_t memaddr, uint8_t * obj, uint16_t size) +{ + const int blocksize = 24; + uint8_t * p = obj; + while (size >= blocksize) + { + this->_writeBlock(memaddr, p, blocksize); + memaddr += blocksize; + p += blocksize; + size -= blocksize; + } + // remaining + if (size > 0) + { + this->_writeBlock(memaddr, p, size); + } +} + + +uint8_t FRAM::read8(uint16_t memaddr) +{ + uint8_t val; + this->_readBlock(memaddr, (uint8_t *)&val, sizeof(uint8_t)); + return val; +} + + +uint16_t FRAM::read16(uint16_t memaddr) +{ + uint16_t val; + this->_readBlock(memaddr, (uint8_t *)&val, sizeof(uint16_t)); + return val; +} + + +uint32_t FRAM::read32(uint16_t memaddr) +{ + uint32_t val; + this->_readBlock(memaddr, (uint8_t *)&val, sizeof(uint32_t)); + return val; +} + + +float FRAM::readFloat(uint16_t memaddr) +{ + float val; + this->_readBlock(memaddr, (uint8_t *)&val, sizeof(float)); + return val; +} + + +double FRAM::readDouble(uint16_t memaddr) +{ + double val; + this->_readBlock(memaddr, (uint8_t *)&val, sizeof(double)); + return val; +} + + +void FRAM::read(uint16_t memaddr, uint8_t * obj, uint16_t size) +{ + const uint8_t blocksize = 24; + uint8_t * p = obj; + while (size >= blocksize) + { + this->_readBlock(memaddr, p, blocksize); + memaddr += blocksize; + p += blocksize; + size -= blocksize; + } + // remainder + if (size > 0) + { + this->_readBlock(memaddr, p, size); + } +} + + +//////////////////////////////////////////////////////////////////////// + + +int32_t FRAM::readUntil(uint16_t memaddr, char * buf, uint16_t buflen, char separator) +{ + // read and fill the buffer at once. + this->read(memaddr, (uint8_t *)buf, buflen); + for (uint16_t length = 0; length < buflen; length++) + { + if (buf[length] == separator) + { + buf[length] = 0; // replace separator => \0 EndChar + return length; + } + } + // entry does not fit in given buffer. + return (int32_t)-1; +} + + +int32_t FRAM::readLine(uint16_t memaddr, char * buf, uint16_t buflen) +{ + // read and fill the buffer at once. + this->read(memaddr, (uint8_t *)buf, buflen); + for (uint16_t length = 0; length < buflen-1; length++) + { + if (buf[length] == '\n') + { + buf[length + 1] = 0; // add \0 EndChar after '\n' + return length + 1; + } + } + // entry does not fit in given buffer. + return (int32_t)-1; +} + + +//////////////////////////////////////////////////////////////////////// + + +uint16_t FRAM::getManufacturerID() +{ + return this->_getMetaData(0); +} + + +uint16_t FRAM::getProductID() +{ + return this->_getMetaData(1); +} + + +// NOTE: returns the size in kiloBYTE +uint16_t FRAM::getSize() +{ + uint16_t density = this->_getMetaData(2); + uint16_t size = 0; + if (density > 0) + { + size = (1UL << density); + this->_sizeBytes = size * 1024UL; + } + return size; +} + + +uint32_t FRAM::getSizeBytes() +{ + return this->_sizeBytes; +} + + +// override to be used when getSize() fails == 0 +void FRAM::setSizeBytes(uint32_t value) +{ + this->_sizeBytes = value; +} + + +uint32_t FRAM::clear(uint8_t value) +{ + uint8_t buffer[16]; + for (uint8_t i = 0; i < 16; i++) buffer[i] = value; + uint32_t start = 0; + uint32_t end = this->_sizeBytes; + for (uint32_t address = start; address < end; address += 16) + { + this->_writeBlock(address, buffer, 16); + } + return end - start; +} + + +// EXPERIMENTAL - to be confirmed +// page 12 datasheet +// command = S 0xF8 A address A S 86 A P (A = Ack from slave ) +void FRAM::sleep() +{ + uint8_t addr = this->address_ << 1; + this->bus_->write(FRAM_SLAVE_ID_, &addr, 1, false); + this->bus_->write(FRAM_SLEEP_CMD >> 1, nullptr, 0, true); +} + + +// page 12 datasheet trec <= 400us +bool FRAM::wakeup(uint32_t trec) +{ + bool b = this->isConnected(); // wakeup + if (trec == 0) return b; + // wait recovery time + delayMicroseconds(trec); + return this->isConnected(); // check recovery OK +} + + +///////////////////////////////////////////////////////////////////////////// +// +// FRAM PROTECTED +// + +// metadata is packed as [....MMMM][MMMMDDDD][PPPPPPPP] +// M = manufacturerID +// D = density => memory size = 2^D KB +// P = product ID (together with D) +// P part might be proprietary +uint16_t FRAM::_getMetaData(uint8_t field) +{ + if (field > 2) return 0; + + uint8_t addr = this->address_ << 1; + this->bus_->write(FRAM_SLAVE_ID_, &addr, 1, false); + + uint8_t data[3] = {0,0,0}; + i2c::ErrorCode err = this->bus_->read(FRAM_SLAVE_ID_, data, 3); + if (err != i2c::ERROR_OK) return 0; + + // MANUFACTURER + if (field == 0) return (data[0] << 4) + (data[1] >> 4); + // PRODUCT ID + if (field == 1) return ((data[1] & 0x0F) << 8) + data[2]; + // DENSITY + // Fujitsu data sheet + // 3 => MB85RC64 = 64 Kbit. + // 5 => MB85RC256 + // 6 => MB85RC512 + // 7 => MB85RC1M + if (field == 2) return data[1] & 0x0F; + return 0; +} + + +void FRAM::_writeBlock(uint16_t memaddr, uint8_t * obj, uint8_t size) +{ + i2c::WriteBuffer buff[2]; + uint8_t maddr[] = { (uint8_t)(memaddr >> 8), (uint8_t)(memaddr & 0xFF) }; + + buff[0].data = maddr; + buff[0].len = 2; + buff[1].data = obj; + buff[1].len = size; + + this->bus_->writev(this->address_, buff, 2, true); +} + + +void FRAM::_readBlock(uint16_t memaddr, uint8_t * obj, uint8_t size) +{ + uint8_t maddr[] = { (uint8_t)(memaddr >> 8), (uint8_t)(memaddr & 0xFF) }; + this->bus_->write(this->address_, maddr, 2, false); + this->bus_->read(this->address_, obj, size); +} + + +///////////////////////////////////////////////////////////////////////////// +// +// FRAM32 PUBLIC +// + +void FRAM32::write8(uint32_t memaddr, uint8_t value) +{ + uint8_t val = value; + this->_writeBlock(memaddr, (uint8_t *)&val, sizeof(uint8_t)); +} + + +void FRAM32::write16(uint32_t memaddr, uint16_t value) +{ + uint16_t val = value; + this->_writeBlock(memaddr, (uint8_t *)&val, sizeof(uint16_t)); +} + + +void FRAM32::write32(uint32_t memaddr, uint32_t value) +{ + uint32_t val = value; + this->_writeBlock(memaddr, (uint8_t *)&val, sizeof(uint32_t)); +} + + +void FRAM32::writeFloat(uint32_t memaddr, float value) +{ + float val = value; + this->_writeBlock(memaddr, (uint8_t *)&val, sizeof(float)); +} + + +void FRAM32::writeDouble(uint32_t memaddr, double value) +{ + double val = value; + this->_writeBlock(memaddr, (uint8_t *)&val, sizeof(double)); +} + + +void FRAM32::write(uint32_t memaddr, uint8_t * obj, uint16_t size) +{ + const int blocksize = 24; + uint8_t * p = obj; + while (size >= blocksize) + { + this->_writeBlock(memaddr, p, blocksize); + memaddr += blocksize; + p += blocksize; + size -= blocksize; + } + // remaining + if (size > 0) + { + this->_writeBlock(memaddr, p, size); + } +} + + +uint8_t FRAM32::read8(uint32_t memaddr) +{ + uint8_t val; + this->_readBlock(memaddr, (uint8_t *)&val, sizeof(uint8_t)); + return val; +} + + +uint16_t FRAM32::read16(uint32_t memaddr) +{ + uint16_t val; + this->_readBlock(memaddr, (uint8_t *)&val, sizeof(uint16_t)); + return val; +} + + +uint32_t FRAM32::read32(uint32_t memaddr) +{ + uint32_t val; + this->_readBlock(memaddr, (uint8_t *)&val, sizeof(uint32_t)); + return val; +} + + +float FRAM32::readFloat(uint32_t memaddr) +{ + float val; + this->_readBlock(memaddr, (uint8_t *)&val, sizeof(float)); + return val; +} + + +double FRAM32::readDouble(uint32_t memaddr) +{ + double val; + this->_readBlock(memaddr, (uint8_t *)&val, sizeof(double)); + return val; +} + + +void FRAM32::read(uint32_t memaddr, uint8_t * obj, uint16_t size) +{ + const uint8_t blocksize = 24; + uint8_t * p = obj; + while (size >= blocksize) + { + this->_readBlock(memaddr, p, blocksize); + memaddr += blocksize; + p += blocksize; + size -= blocksize; + } + // remainder + if (size > 0) + { + this->_readBlock(memaddr, p, size); + } +} + + +int32_t FRAM32::readUntil(uint32_t memaddr, char * buf, uint16_t buflen, char separator) +{ + // read and fill the buffer at once. + this->read(memaddr, (uint8_t *)buf, buflen); + for (uint16_t length = 0; length < buflen; length++) + { + if (buf[length] == separator) + { + buf[length] = 0; // replace separator => \0 EndChar + return length; + } + } + // entry does not fit in given buffer. + return (int32_t)-1; +} + + +int32_t FRAM32::readLine(uint32_t memaddr, char * buf, uint16_t buflen) +{ + // read and fill the buffer at once. + this->read(memaddr, (uint8_t *)buf, buflen); + for (uint16_t length = 0; length < buflen-1; length++) + { + if (buf[length] == '\n') + { + buf[length + 1] = 0; // add \0 EndChar after '\n' + return length + 1; + } + } + // entry does not fit in given buffer. + return (int32_t)-1; +} + + +template uint32_t FRAM32::writeObject(uint32_t memaddr, T &obj) +{ + this->write(memaddr, (uint8_t *) &obj, sizeof(obj)); + return memaddr + sizeof(obj); +}; + + +template uint32_t FRAM32::readObject(uint32_t memaddr, T &obj) +{ + this->read(memaddr, (uint8_t *) &obj, sizeof(obj)); + return memaddr + sizeof(obj); +} + + +///////////////////////////////////////////////////////////////////////////// +// +// FRAM32 PROTECTED +// + +void FRAM32::_writeBlock(uint32_t memaddr, uint8_t * obj, uint8_t size) +{ + uint8_t _addr = this->address_; + if (memaddr & 0x00010000) _addr += 0x01; + + i2c::WriteBuffer buff[2]; + uint8_t maddr[] = { (uint8_t)(memaddr >> 8), (uint8_t)(memaddr & 0xFF) }; + + buff[0].data = maddr; + buff[0].len = 2; + buff[1].data = obj; + buff[1].len = size; + + this->bus_->writev(_addr, buff, 2, true); +} + + +void FRAM32::_readBlock(uint32_t memaddr, uint8_t * obj, uint8_t size) +{ + uint8_t _addr = this->address_; + if (memaddr & 0x00010000) _addr += 0x01; + + uint8_t maddr[] = { (uint8_t)(memaddr >> 8), (uint8_t)(memaddr & 0xFF) }; + this->bus_->write(this->address_, maddr, 2, false); + this->bus_->read(_addr, obj, size); +} + + +///////////////////////////////////////////////////////////////////////////// +// +// FRAM11 +// + +///////////////////////////////////////////////////////////////////////////// +// +// FRAM11 PROTECTED +// + +void FRAM11::_writeBlock(uint16_t memaddr, uint8_t * obj, uint8_t size) +{ + // Device uses Address Pages + uint8_t DeviceAddrWithPageBits = this->address_ | ((memaddr & 0x0700) >> 8); + + i2c::WriteBuffer buff[2]; + uint8_t maddr = memaddr & 0xFF; + + buff[0].data = &maddr; + buff[0].len = 1; + buff[1].data = obj; + buff[1].len = size; + + this->bus_->writev(DeviceAddrWithPageBits, buff, 2, true); +} + + +void FRAM11::_readBlock(uint16_t memaddr, uint8_t * obj, uint8_t size) +{ + // Device uses Address Pages + uint8_t DeviceAddrWithPageBits = this->address_ | ((memaddr & 0x0700) >> 8); + uint8_t maddr = memaddr & 0xFF; + + this->bus_->write(DeviceAddrWithPageBits, &maddr, 1, false); + this->bus_->read(DeviceAddrWithPageBits, obj, size); +} + + +///////////////////////////////////////////////////////////////////////////// +// +// FRAM9 +// + +///////////////////////////////////////////////////////////////////////////// +// +// FRAM9 PROTECTED +// + +void FRAM9::_writeBlock(uint16_t memaddr, uint8_t * obj, uint8_t size) +{ + // Device uses Address Pages + uint8_t DeviceAddrWithPageBits = this->address_ | ((memaddr & 0x0100) >> 8); + + i2c::WriteBuffer buff[2]; + uint8_t maddr = memaddr & 0xFF; + + buff[0].data = &maddr; + buff[0].len = 1; + buff[1].data = obj; + buff[1].len = size; + + this->bus_->writev(DeviceAddrWithPageBits, buff, 2, true); +} + + +void FRAM9::_readBlock(uint16_t memaddr, uint8_t * obj, uint8_t size) +{ + // Device uses Address Pages + uint8_t DeviceAddrWithPageBits = this->address_ | ((memaddr & 0x0100) >> 8); + uint8_t maddr = memaddr & 0xFF; + + this->bus_->write(DeviceAddrWithPageBits, &maddr, 1, false); + this->bus_->read(DeviceAddrWithPageBits, obj, size); +} + + +} // namespace fram +} // namespace esphome + +// -- END OF FILE -- diff --git a/esphome/components/fram/FRAM.h b/esphome/components/fram/FRAM.h new file mode 100644 index 0000000000..61c17fd355 --- /dev/null +++ b/esphome/components/fram/FRAM.h @@ -0,0 +1,164 @@ +#pragma once +// +// FILE: FRAM.h +// AUTHOR: Rob Tillaart +// VERSION: 0.5.3 +// DATE: 2018-01-24 +// PURPOSE: Arduino library for I2C FRAM +// URL: https://github.com/RobTillaart/FRAM_I2C +// +// ESPHome port: https://github.com/sharkydog/esphome-fram + +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include "esphome/core/component.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace fram { + +class FRAM : public Component, public i2c::I2CDevice +{ +public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::BUS; } + + bool isConnected(); + + void write8(uint16_t memaddr, uint8_t value); + void write16(uint16_t memaddr, uint16_t value); + void write32(uint16_t memaddr, uint32_t value); + void writeFloat(uint16_t memaddr, float value); + void writeDouble(uint16_t memaddr, double value); + void write(uint16_t memaddr, uint8_t * obj, uint16_t size); + + uint8_t read8(uint16_t memaddr); + uint16_t read16(uint16_t memaddr); + uint32_t read32(uint16_t memaddr); + float readFloat(uint16_t memaddr); + double readDouble(uint16_t memaddr); + void read(uint16_t memaddr, uint8_t * obj, uint16_t size); + + // Experimental 0.5.1 + // readUntil returns length 0.. n of the buffer. + // readUntil does NOT include the separator character. + // readUntil returns -1 if data does not fit into buffer, + // => separator not encountered. + int32_t readUntil(uint16_t memaddr, char * buf, uint16_t buflen, char separator); + // readLine returns length 0.. n of the buffer. + // readLine does include '\n' as end character. + // readLine returns -1 if data does not fit into buffer. + // buffer needs one place for end char '\0'. + int32_t readLine(uint16_t memaddr, char * buf, uint16_t buflen); + + template uint16_t writeObject(uint16_t memaddr, T &obj) + { + this->write(memaddr, (uint8_t *) &obj, sizeof(obj)); + return memaddr + sizeof(obj); + }; + template uint16_t readObject(uint16_t memaddr, T &obj) + { + this->read(memaddr, (uint8_t *) &obj, sizeof(obj)); + return memaddr + sizeof(obj); + } + + // meta info + // Fujitsu = 0x000A, Ramtron = 0x004 + uint16_t getManufacturerID(); + // Proprietary + uint16_t getProductID(); + // Returns size in KILO-BYTE (or 0) + // Verify for all manufacturers. + uint16_t getSize(); + // Returns size in BYTE + uint32_t getSizeBytes(); + // override when getSize() fails == 0 (see readme.md) + void setSizeBytes(uint32_t value); + + // fills FRAM with value, default 0. + uint32_t clear(uint8_t value = 0); + + // 0.3.6 + void sleep(); + // trec <= 400us P12 + bool wakeup(uint32_t trec = 400); + + +protected: + uint32_t _sizeBytes{0}; + + uint16_t _getMetaData(uint8_t id); + + // virtual so derived classes FRAM9/11 use their implementation. + virtual void _writeBlock(uint16_t memaddr, uint8_t * obj, uint8_t size); + virtual void _readBlock(uint16_t memaddr, uint8_t * obj, uint8_t size); +}; + + +///////////////////////////////////////////////////////////////////////////// +// +// FRAM32 +// + +class FRAM32 : public FRAM +{ +public: + void write8(uint32_t memaddr, uint8_t value); + void write16(uint32_t memaddr, uint16_t value); + void write32(uint32_t memaddr, uint32_t value); + void writeFloat(uint32_t memaddr, float value); + void writeDouble(uint32_t memaddr, double value); + void write(uint32_t memaddr, uint8_t * obj, uint16_t size); + + uint8_t read8(uint32_t memaddr); + uint16_t read16(uint32_t memaddr); + uint32_t read32(uint32_t memaddr); + float readFloat(uint32_t memaddr); + double readDouble(uint32_t memaddr); + void read(uint32_t memaddr, uint8_t * obj, uint16_t size); + + // readUntil returns length 0.. n of the buffer. + // readUntil returns -1 if data does not fit into buffer, + // => separator not encountered. + int32_t readUntil(uint32_t memaddr, char * buf, uint16_t buflen, char separator); + // buffer needs one place for end char '\0'. + int32_t readLine(uint32_t memaddr, char * buf, uint16_t buflen); + + template uint32_t writeObject(uint32_t memaddr, T &obj); + template uint32_t readObject(uint32_t memaddr, T &obj); + +protected: + void _writeBlock(uint32_t memaddr, uint8_t * obj, uint8_t size); + void _readBlock(uint32_t memaddr, uint8_t * obj, uint8_t size); +}; + + +///////////////////////////////////////////////////////////////////////////// +// +// FRAM11 for FRAM that use 11 bits addresses - e.g. MB85RC16 +// + +class FRAM11 : public FRAM +{ +protected: + void _writeBlock(uint16_t memaddr, uint8_t * obj, uint8_t size); + void _readBlock(uint16_t memaddr, uint8_t * obj, uint8_t size); +}; + + +///////////////////////////////////////////////////////////////////////////// +// +// FRAM9 for FRAM that use 9 bits addresses - e.g. MB85RC04 +// +class FRAM9 : public FRAM +{ +protected: + void _writeBlock(uint16_t memaddr, uint8_t * obj, uint8_t size); + void _readBlock(uint16_t memaddr, uint8_t * obj, uint8_t size); +}; + +} // namespace fram +} // namespace esphome + +// -- END OF FILE -- diff --git a/esphome/components/fram/__init__.py b/esphome/components/fram/__init__.py new file mode 100644 index 0000000000..d16623bb69 --- /dev/null +++ b/esphome/components/fram/__init__.py @@ -0,0 +1,64 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +import re +from esphome.components import i2c +from esphome.const import CONF_ID, CONF_TYPE, CONF_SIZE + +DEPENDENCIES = ["i2c"] +MULTI_CONF = True + +fram_ns = cg.esphome_ns.namespace("fram") +FRAMComponent = fram_ns.class_("FRAM", cg.Component, i2c.I2CDevice) +FRAM9Component = fram_ns.class_("FRAM9", FRAMComponent) +FRAM11Component = fram_ns.class_("FRAM11", FRAMComponent) +FRAM32Component = fram_ns.class_("FRAM32", FRAMComponent) + +def validate_bytes_1024(value): + value = cv.string(value).lower() + match = re.match(r"^([0-9]+)\s*(\w*)$", value) + + if match is None: + raise cv.Invalid(f"Expected number of bytes with unit, got {value}") + + SUFF_LIST = ["B","KB","KiB","MB","MiB"] + SUFF = { + "": 1, + "b": 1, + "kb": 1000, + "kib": 1024, + "mb": pow(1000,2), + "mib": pow(1024,2) + } + + if match.group(2) not in SUFF: + raise cv.Invalid(f"Invalid metric suffix {match.group(2)}, valid: {SUFF_LIST}") + + return int(int(match.group(1)) * SUFF[match.group(2)]) + + +FRAM_SCHEMA = cv.Schema({ + cv.Optional(CONF_SIZE): validate_bytes_1024 +}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x50)) + +CONFIG_SCHEMA = cv.typed_schema({ + "FRAM": FRAM_SCHEMA.extend({ + cv.GenerateID(): cv.declare_id(FRAMComponent) + }), + "FRAM9": FRAM_SCHEMA.extend({ + cv.GenerateID(): cv.declare_id(FRAM9Component) + }), + "FRAM11": FRAM_SCHEMA.extend({ + cv.GenerateID(): cv.declare_id(FRAM11Component) + }), + "FRAM32": FRAM_SCHEMA.extend({ + cv.GenerateID(): cv.declare_id(FRAM32Component) + }) +}, key=CONF_TYPE, default_type="FRAM", upper=True) + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + if CONF_SIZE in config: + cg.add(var.setSizeBytes(config[CONF_SIZE])) \ No newline at end of file