├── .github ├── dependabot.yaml └── workflows │ ├── build.yml │ └── release.yaml ├── .gitignore ├── CHANGELOG.md ├── README.md ├── components └── i2c_eeprom │ ├── I2C_EEPROM.cpp │ ├── I2C_EEPROM.h │ └── __init__.py └── example.yaml /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: monthly 8 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - 'README.md' 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | runs-on: "ubuntu-latest" 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | 16 | - name: Check conventional commits 17 | uses: webiny/action-conventional-commits@v1.3.0 18 | 19 | - name: Setup Python 20 | uses: actions/setup-python@v5 21 | with: 22 | python-version: "pypy3.10" 23 | 24 | - name: Install ESPHome 25 | run: | 26 | pip3 install wheel 27 | pip3 install esphome 28 | 29 | - name: Compile 30 | run: | 31 | esphome compile example.yaml 32 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | release_helper: 10 | runs-on: ubuntu-latest 11 | outputs: 12 | release: ${{ steps.release.outputs.release_created }} 13 | version: ${{ steps.release.outputs.tag_name }} 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | 18 | - uses: googleapis/release-please-action@v4 19 | id: release 20 | with: 21 | release-type: simple -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.esphome/ 2 | /secrets.yaml 3 | __pycache__ -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.0.0 (2024-09-23) 4 | 5 | 6 | ### Features 7 | 8 | * change boot priority to DATA ([0ca50a2](https://github.com/pilotak/esphome-eeprom/commit/0ca50a2a5091645061345ae40bead76cb0c9121b)) 9 | * init ([bdcaae4](https://github.com/pilotak/esphome-eeprom/commit/bdcaae4515c778637dbf9c1fcc85cdf2cdd6370c)) 10 | * on_setup action & size ([3288d37](https://github.com/pilotak/esphome-eeprom/commit/3288d37bcf9bf47e015605db82655e6891c3e532)) 11 | * read/write multiple bytes ([8408463](https://github.com/pilotak/esphome-eeprom/commit/8408463aa6303283fa9c741d3aa8c5afc86a66b9)) 12 | 13 | 14 | ### Bug Fixes 15 | 16 | * error checking ([051baee](https://github.com/pilotak/esphome-eeprom/commit/051baeedc27a14e55f7a0d6a8ef81984e7ea46b1)) 17 | * get_connect_trigger ([7d450e6](https://github.com/pilotak/esphome-eeprom/commit/7d450e68377974b081b0641ff0999967ac49b3f9)) 18 | * init trigger ([c20450a](https://github.com/pilotak/esphome-eeprom/commit/c20450ae74d703f239c0fb50cb8d8b0e97e4680c)) 19 | * isConnected don't use bus_ ([27d2c03](https://github.com/pilotak/esphome-eeprom/commit/27d2c03397b13e01d81c532b79c4815143a8c8b4)) 20 | * when no on_setup trigger ([b318a54](https://github.com/pilotak/esphome-eeprom/commit/b318a54694d6afabc2ffe95540e272761f3a118a)) 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # esphome-eeprom 2 | 3 | [![build](https://github.com/pilotak/esphome-eeprom/actions/workflows/build.yml/badge.svg)](https://github.com/pilotak/esphome-eeprom/actions/workflows/build.yml) 4 | 5 | Lets you write/read EEPROM in lambdas 6 | 7 | ```yaml 8 | external_components: 9 | - source: github://pilotak/esphome-eeprom 10 | components: [i2c_eeprom] 11 | 12 | i2c: 13 | scl: 16 14 | sda: 17 15 | id: i2c_1 16 | 17 | i2c_eeprom: 18 | - id: eeprom_1 19 | size: 16KB 20 | - id: eeprom_2 21 | size: 32KB 22 | i2c_id: i2c_1 23 | address: 0x52 24 | 25 | some_option: 26 | on_something: 27 | - lambda: |- 28 | uint8_t write_byte = 0b1; 29 | eeprom_1->put(0x0001, write_byte); 30 | 31 | uint8_t read_byte; 32 | eeprom_1->get(0x0001, &read_byte); 33 | ESP_LOGD("eeprom","Read: 0x%02X ", read_byte); 34 | ``` 35 | 36 | > Multiple devices can be used at the same time. 37 | 38 | ### Configuration 39 | 40 | - **size** - _(required)_ Set the EEPPROM size (**in bits**, not bytes): `1KB`, `2KB`, `4KB`, `8KB`, `16KB`, `32KB`, `64KB`, `128KB`, `256KB`, `512KB` 41 | - **id** - _(required)_ Unique ID for use in lambdas 42 | - **i2c_id** - _(optional)_ ID of the I2C bus 43 | - **address** - _(optional)_(default 0x50) I2C address 44 | - **on_setup** - _(optional)_ Automation to run after setup 45 | 46 | Please see [example](./example.yaml) how to use it action. 47 | -------------------------------------------------------------------------------- /components/i2c_eeprom/I2C_EEPROM.cpp: -------------------------------------------------------------------------------- 1 | #include "I2C_EEPROM.h" 2 | #include "esphome/core/hal.h" 3 | #include "esphome/core/log.h" 4 | 5 | namespace esphome { 6 | namespace i2c_eeprom { 7 | 8 | static const char *const TAG = "eeprom"; 9 | 10 | void I2C_EEPROM::setup() { 11 | ESP_LOGCONFIG(TAG, "Setting up EEPROM..."); 12 | 13 | if (!this->isConnected()) { 14 | ESP_LOGE(TAG, "Device on address 0x%x not found!", this->address_); 15 | this->mark_failed(); 16 | return; 17 | } 18 | 19 | ESP_LOGCONFIG(TAG, "OK"); 20 | 21 | this->on_setup_trigger_->trigger(); 22 | } 23 | 24 | void I2C_EEPROM::dump_config() { 25 | ESP_LOGCONFIG(TAG, "EEPROM:"); 26 | LOG_I2C_DEVICE(this); 27 | } 28 | 29 | bool I2C_EEPROM::isConnected() { 30 | uint8_t data[1] = {0}; 31 | i2c::ErrorCode err = this->read(data, 1); 32 | return (err == i2c::ERROR_OK); 33 | } 34 | 35 | bool I2C_EEPROM::put(uint16_t memaddr, const uint8_t *value, size_t size) { 36 | uint8_t *data = new uint8_t[size + 2](); 37 | i2c::ErrorCode err; 38 | 39 | if (data == nullptr) { 40 | ESP_LOGE(TAG, "Not enough RAM"); 41 | return false; 42 | } 43 | 44 | if (this->isTwoByteAddress_) { 45 | data[0] = memaddr >> 8; 46 | data[1] = memaddr & 0xFF; 47 | memcpy(data + 2, value, size); 48 | 49 | err = this->write(data, size + 2); 50 | } else { 51 | data[0] = memaddr & 0xFF; 52 | memcpy(data + 1, value, size); 53 | 54 | err = this->write(data, size + 1); 55 | } 56 | 57 | delete[] data; 58 | 59 | delay(5); // EEPROM takes 5ms to write 60 | 61 | if (err != i2c::ERROR_OK) { 62 | ESP_LOGE(TAG, "Write failed!"); 63 | return false; 64 | } 65 | 66 | return true; 67 | } 68 | 69 | bool I2C_EEPROM::get(uint16_t memaddr, uint8_t *value, size_t size) { 70 | uint8_t data[2]; 71 | i2c::ErrorCode err; 72 | 73 | if (this->isTwoByteAddress_) { 74 | data[0] = memaddr >> 8; 75 | data[1] = memaddr & 0xFF; 76 | 77 | err = this->write(data, 2); 78 | } else { 79 | data[0] = memaddr & 0xFF; 80 | err = this->write(data, 1); 81 | } 82 | 83 | if (err != i2c::ERROR_OK) { 84 | ESP_LOGE(TAG, "Requesting data failed!"); 85 | return false; 86 | } 87 | 88 | err = this->read(value, size); 89 | 90 | if (err != i2c::ERROR_OK) { 91 | ESP_LOGE(TAG, "Reading failed!"); 92 | return false; 93 | } 94 | 95 | return true; 96 | } 97 | 98 | } // namespace i2c_eeprom 99 | } // namespace esphome 100 | -------------------------------------------------------------------------------- /components/i2c_eeprom/I2C_EEPROM.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "esphome/components/i2c/i2c.h" 3 | #include "esphome/core/automation.h" 4 | #include "esphome/core/component.h" 5 | 6 | namespace esphome { 7 | namespace i2c_eeprom { 8 | 9 | class I2C_EEPROM : public Component, public i2c::I2CDevice { 10 | public: 11 | void setup() override; 12 | void dump_config() override; 13 | float get_setup_priority() const override { return setup_priority::DATA; } 14 | 15 | bool isConnected(); 16 | 17 | bool put(uint16_t memaddr, const uint8_t *value, size_t size); 18 | 19 | bool put(uint16_t memaddr, uint8_t value) { return put(memaddr, &value, 1); } 20 | 21 | bool get(uint16_t memaddr, uint8_t *value, size_t size = 1); 22 | 23 | // set variable from config 24 | void set_size(uint16_t size) { 25 | this->isTwoByteAddress_ = size > 2048; // 16kb 26 | } 27 | 28 | Trigger<> *get_connect_trigger() const { return this->on_setup_trigger_; }; 29 | 30 | protected: 31 | bool isTwoByteAddress_ = false; 32 | Trigger<> *on_setup_trigger_{new Trigger<>()}; 33 | }; 34 | 35 | } // namespace i2c_eeprom 36 | } // namespace esphome 37 | -------------------------------------------------------------------------------- /components/i2c_eeprom/__init__.py: -------------------------------------------------------------------------------- 1 | import esphome.codegen as cg 2 | import esphome.config_validation as cv 3 | from esphome import automation 4 | from esphome.components import i2c 5 | from esphome.const import CONF_ID, CONF_SIZE 6 | 7 | DEPENDENCIES = ["i2c"] 8 | MULTI_CONF = True 9 | 10 | CONF_ON_SETUP = "on_setup" 11 | EEPROM_SIZES = { 12 | "512KB": 65536, 13 | "256KB": 32768, 14 | "128KB": 16384, 15 | "64KB": 8192, 16 | "32KB": 4096, 17 | "16KB": 2048, 18 | "8KB": 1024, 19 | "4KB": 512, 20 | "2KB": 256, 21 | "1KB": 128, 22 | } 23 | 24 | 25 | eeprom_ns = cg.esphome_ns.namespace("i2c_eeprom") 26 | EEPROMComponent = eeprom_ns.class_("I2C_EEPROM", cg.Component, i2c.I2CDevice) 27 | 28 | CONFIG_SCHEMA = ( 29 | cv.Schema( 30 | { 31 | cv.GenerateID(): cv.declare_id(EEPROMComponent), 32 | cv.Required(CONF_SIZE): cv.enum(EEPROM_SIZES, upper=True), 33 | cv.Optional(CONF_ON_SETUP): automation.validate_automation(single=True), 34 | } 35 | ) 36 | .extend(cv.COMPONENT_SCHEMA) 37 | .extend(i2c.i2c_device_schema(0x50)) 38 | ) 39 | 40 | 41 | async def to_code(config): 42 | var = cg.new_Pvariable(config[CONF_ID]) 43 | cg.add(var.set_size(config[CONF_SIZE])) 44 | await cg.register_component(var, config) 45 | await i2c.register_i2c_device(var, config) 46 | 47 | # Handle on_setup lambda 48 | if CONF_ON_SETUP in config: 49 | await automation.build_automation( 50 | var.get_connect_trigger(), [], config[CONF_ON_SETUP] 51 | ) 52 | -------------------------------------------------------------------------------- /example.yaml: -------------------------------------------------------------------------------- 1 | esphome: 2 | name: test 3 | platform: ESP8266 4 | board: esp01_1m 5 | esp8266_restore_from_flash: False 6 | on_boot: 7 | priority: 400 # set actual output on boot after all hadrware setup 8 | then: 9 | - lambda: |- 10 | if(id(switch_prev_state)){ 11 | id(output1).turn_on(); 12 | } 13 | 14 | external_components: 15 | - source: components 16 | 17 | globals: 18 | - id: switch_prev_state 19 | type: bool 20 | restore_value: no 21 | initial_value: "false" 22 | 23 | i2c: 24 | sda: GPIO4 25 | scl: GPIO5 26 | 27 | i2c_eeprom: 28 | - id: my_eeprom 29 | size: 16KB 30 | on_setup: 31 | then: 32 | - lambda: |- 33 | uint8_t prev_state[2]; 34 | if(!my_eeprom->get(0x0001, prev_state, sizeof(prev_state))){ 35 | ESP_LOGE("eeprom","Reading prev"); 36 | return; 37 | } 38 | ESP_LOGD("eeprom","Prev 0x%02X, 0x%02X", prev_state[0], prev_state[1]); 39 | id(switch_prev_state) = prev_state[0]; 40 | 41 | button: 42 | - platform: restart 43 | name: "Restart" 44 | 45 | output: 46 | - platform: gpio 47 | id: output1 48 | pin: 49 | number: GPIO14 50 | 51 | switch: 52 | - platform: template 53 | name: "Test Switch" 54 | restore_mode: DISABLED 55 | lambda: |- 56 | return id(switch_prev_state); 57 | turn_on_action: 58 | - output.turn_on: output1 59 | - lambda: |- 60 | my_eeprom->put(0x0001, true); 61 | id(switch_prev_state) = true; 62 | turn_off_action: 63 | - output.turn_off: output1 64 | - lambda: |- 65 | my_eeprom->put(0x0001, false); 66 | id(switch_prev_state) = false; 67 | --------------------------------------------------------------------------------