Home-Assistant Connected Aquarium Water Quality System with ESPHOME
We love our aquatic friends! However, we recently introduced some new finny friends and it did not go well. Unsure if there was an illness introduced or a water issue, we decided it was time to monitor the conditions within our aquariums.
We decided to monitor the following measurements:
1 Total Dissolved Solids - These solids include, but are not limited to, salts, minerals, and conductive metal ions. If this value gets too high, your fish can get sick or die from these contaminants.
2 Total Suspended Solids - These solids cause your water to become hazy and impede many natural life functions. If these get too high, your fish can become disoriented or even suffocate!
3 pH - This measures the acidity of the water in your aquarium. Different ecosystems and species prefer different pH levels - ours happen to prefer fairly neutral water around 7 - 7.5.
4 Temperature - Fish are highly susceptible to changes in their water temperature. A quick rise in temperature can cause heat fatigue or death a drop in temperature and they could freeze. Temperature changes can also invite unwanted pests to brood and breed and become a real problem.
The other measurements we are not, but would like to monitor are:
1 Dissolved oxygen: Obviously, your fishy friends need to breath and the way they do is by extracting the oxygen dissolved in water. Currently, optical DO sensors are a little out of our price range and we'd prefer to skip the galvanic sensors.
2. Water hardness: Much like total dissolved solids, the minerals in your water can change the hardness of it. Too hard and fish have a difficult time breathing or swimming. Some fish will die if the water is not in an acceptable hardness range.
```shell
pip3 install esphome
```
substitutions:
name: esp32-aquarium-water
friendly_name: "Aquarium"
esphome:
name: ${name}
friendly_name: ${name}
includes:
- EEPROM.h
- GravityTDS.h
- tds_sensor.h
ota:
password: ""
esp32:
board: esp32-s3-devkitc-1
# Enable logging
logger:
level: DEBUG
improv_serial:
# Enable Home Assistant API
api:
password: ""
mqtt:
broker: BROKER
port: 1183
username: aquarium
client_id: aquarium
wifi:
ssid: "SSID"
password: "PASSWORD"
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Bt Aquarium Fallback Hotspot"
password: "password"
i2c:
sda: GPIO17
scl: GPIO18
scan: true
dallas:
- pin: GPIO5
update_interval: 10s
sensor:
- name: "pH"
platform: ezo
id: ph
address: 99
unit_of_measurement: "pH"
update_interval: 5s
- platform: custom
lambda: |-
auto tds_sensor = new TdsSensor(id(tank_tempC));
App.register_component(tds_sensor);
return {tds_sensor->tds_sensor, tds_sensor->voltage_sensor};
sensors:
- name: "Water TDS"
unit_of_measurement: ppm
accuracy_decimals: 2
- name: "Water Voltage"
unit_of_measurement: mV
accuracy_decimals: 0
- name: "Main Water Temperature C"
platform: dallas
address: 0xe83ce1045777b828
id: tank_tempC
unit_of_measurement: "°C"
internal: true
- platform: adc
pin: GPIO2
name: "Main Water TSS"
id: tank_tss
attenuation: auto
unit_of_measurement: 'NTU'
update_interval: 7s
filters:
- calibrate_linear:
datapoints:
- 1.65 -> 3004.7
- 2.22 -> 0.0
/***************************************************
DFRobot Gravity: Analog TDS Sensor/Meter
<https://www.dfrobot.com/wiki/index.php/Gravity:_Analog_TDS_Sensor_/_Meter_For_Arduino_SKU:_SEN0244>
***************************************************
This sample code shows how to read the tds value and calibrate it with the standard buffer solution.
707ppm(1413us/cm)@25^c standard buffer solution is recommended.
Created 2018-1-3
By Jason <[email protected]@dfrobot.com>
GNU Lesser General Public License.
See <http://www.gnu.org/licenses/> for details.
All above must be included in any redistribution.
****************************************************/
#ifndef GRAVITY_TDS_H
#define GRAVITY_TDS_H
//#include "Arduino.h"
#include "EEPROM.h"
#define ReceivedBufferLength 15
#define TdsFactor 0.5 // tds = ec / 2
class GravityTDS
{
public:
GravityTDS();
~GravityTDS();
void begin(); //initialization
void update(); //read and calculate
void setPin(int pin);
void setTemperature(float temp); //set the temperature and execute temperature compensation
void setAref(float value); //reference voltage on ADC, default 5.0V on Arduino UNO
void setAdcRange(float range); //1024 for 10bit ADC;4096 for 12bit ADC
void setKvalueAddress(int address); //set the EEPROM address to store the k value,default address:0x08
float getKvalue();
float getTdsValue();
float getEcValue();
private:
int pin;
float aref; // default 5.0V on Arduino UNO
float adcRange;
float temperature;
int kValueAddress; //the address of the K value stored in the EEPROM
char cmdReceivedBuffer[ReceivedBufferLength+1]; // store the serial cmd from the serial monitor
uint8_t cmdReceivedBufferIndex;
float kValue; // k value of the probe,you can calibrate in buffer solution ,such as 706.5ppm(1413us/cm)@25^C
float analogValue;
float voltage;
float ecValue; //before temperature compensation
float ecValue25; //after temperature compensation
float tdsValue;
void readKValues();
bool cmdSerialDataAvailable();
uint8_t cmdParse();
void ecCalibration(uint8_t mode);
};
#define EEPROM_write(address, p) {int i = 0; uint8_t *pp = (uint8_t*)&(p);for(; i < sizeof(p); i++) EEPROM.write(address+i, pp[i]);}
#define EEPROM_read(address, p) {int i = 0; uint8_t *pp = (uint8_t*)&(p);for(; i < sizeof(p); i++) pp[i]=EEPROM.read(address+i);}
GravityTDS::GravityTDS()
{
this->pin = 17;
this->temperature = 25.0;
this->aref = 5.0;
this->adcRange = 1024.0;
this->kValueAddress = 8;
this->kValue = 1.0;
}
GravityTDS::~GravityTDS()
{
}
void GravityTDS::setPin(int pin)
{
this->pin = pin;
}
void GravityTDS::setTemperature(float temp)
{
this->temperature = temp;
}
void GravityTDS::setAref(float value)
{
this->aref = value;
}
void GravityTDS::setAdcRange(float range)
{
this->adcRange = range;
}
void GravityTDS::setKvalueAddress(int address)
{
this->kValueAddress = address;
}
void GravityTDS::begin()
{
pinMode(this->pin,INPUT);
readKValues();
}
float GravityTDS::getKvalue()
{
//ESP_LOGD("custom", "in kvalue");
if(!this->kValue) {
this->kValue = 1.0;
}
return this->kValue;
}
void GravityTDS::update()
{
//ESP_LOGD("custom", "in update");
this->analogValue = analogRead(this->pin);
this->voltage = this->analogValue/this->adcRange*this->aref;
this->ecValue=(133.42*this->voltage*this->voltage*this->voltage - 255.86*this->voltage*this->voltage + 857.39*this->voltage)*(1.0); //this->kValue;
this->ecValue25 = this->ecValue / (1.0+0.02*(this->temperature-25.0)); //temperature compensation
this->tdsValue = ecValue25 * TdsFactor;
// if(cmdSerialDataAvailable() > 0)
// {
// ecCalibration(cmdParse()); // if received serial cmd from the serial monitor, enter into the calibration mode
// }
}
float GravityTDS::getTdsValue()
{
return tdsValue;
}
float GravityTDS::getEcValue()
{
return ecValue25;
}
void GravityTDS::readKValues()
{
EEPROM_read(this->kValueAddress, this->kValue);
if(EEPROM.read(this->kValueAddress)==0xFF && EEPROM.read(this->kValueAddress+1)==0xFF && EEPROM.read(this->kValueAddress+2)==0xFF && EEPROM.read(this->kValueAddress+3)==0xFF)
{
this->kValue = 1.0; // default value: K = 1.0
EEPROM_write(this->kValueAddress, this->kValue);
}
}
boolean GravityTDS::cmdSerialDataAvailable()
{
char cmdReceivedChar;
static unsigned long cmdReceivedTimeOut = millis();
while (Serial.available()>0)
{
if (millis() - cmdReceivedTimeOut > 500U)
{
cmdReceivedBufferIndex = 0;
memset(cmdReceivedBuffer,0,(ReceivedBufferLength+1));
}
cmdReceivedTimeOut = millis();
cmdReceivedChar = Serial.read();
if (cmdReceivedChar == '\n' || cmdReceivedBufferIndex==ReceivedBufferLength){
cmdReceivedBufferIndex = 0;
strupr(cmdReceivedBuffer);
return true;
}else{
cmdReceivedBuffer[cmdReceivedBufferIndex] = cmdReceivedChar;
cmdReceivedBufferIndex++;
}
}
return false;
}
uint8_t GravityTDS::cmdParse()
{
uint8_t modeIndex = 0;
if(strstr(cmdReceivedBuffer, "ENTER") != NULL)
modeIndex = 1;
else if(strstr(cmdReceivedBuffer, "EXIT") != NULL)
modeIndex = 3;
else if(strstr(cmdReceivedBuffer, "CAL:") != NULL)
modeIndex = 2;
return modeIndex;
}
void GravityTDS::ecCalibration(uint8_t mode)
{
char *cmdReceivedBufferPtr;
static boolean ecCalibrationFinish = 0;
static boolean enterCalibrationFlag = 0;
float KValueTemp,rawECsolution;
switch(mode)
{
case 0:
if(enterCalibrationFlag)
Serial.println(F("Command Error"));
break;
case 1:
enterCalibrationFlag = 1;
ecCalibrationFinish = 0;
Serial.println();
Serial.println(F(">>>Enter Calibration Mode<<<"));
Serial.println(F(">>>Please put the probe into the standard buffer solution<<<"));
Serial.println();
break;
case 2:
cmdReceivedBufferPtr=strstr(cmdReceivedBuffer, "CAL:");
cmdReceivedBufferPtr+=strlen("CAL:");
rawECsolution = strtod(cmdReceivedBufferPtr,NULL)/(float)(TdsFactor);
rawECsolution = rawECsolution*(1.0+0.02*(temperature-25.0));
if(enterCalibrationFlag)
{
// Serial.print("rawECsolution:");
// Serial.print(rawECsolution);
// Serial.print(" ecvalue:");
// Serial.println(ecValue);
KValueTemp = rawECsolution/(133.42*voltage*voltage*voltage - 255.86*voltage*voltage + 857.39*voltage); //calibrate in the buffer solution, such as 707ppm(1413us/cm)@25^c
if((rawECsolution>0) && (rawECsolution<2000) && (KValueTemp>0.25) && (KValueTemp<4.0))
{
Serial.println();
Serial.print(F(">>>Confrim Successful,K:"));
Serial.print(KValueTemp);
Serial.println(F(", Send EXIT to Save and Exit<<<"));
kValue = KValueTemp;
ecCalibrationFinish = 1;
}
else{
Serial.println();
Serial.println(F(">>>Confirm Failed,Try Again<<<"));
Serial.println();
ecCalibrationFinish = 0;
}
}
break;
case 3:
if(enterCalibrationFlag)
{
Serial.println();
if(ecCalibrationFinish)
{
EEPROM_write(kValueAddress, kValue);
Serial.print(F(">>>Calibration Successful,K Value Saved"));
}
else Serial.print(F(">>>Calibration Failed"));
Serial.println(F(",Exit Calibration Mode<<<"));
Serial.println();
ecCalibrationFinish = 0;
enterCalibrationFlag = 0;
}
break;
}
}
#endif
#include "GravityTDS.h"
#define TDS_PIN 4
GravityTDS gravityTds;
class TdsSensor : public PollingComponent, public Sensor {
private:
esphome::sensor::Sensor* sTemp;
public:
// constructor
TdsSensor(esphome::sensor::Sensor* temp) : PollingComponent(15000) {
sTemp = temp;
};
Sensor *tds_sensor = new Sensor();
Sensor *voltage_sensor = new Sensor();
float get_setup_priority() const override { return esphome::setup_priority::DATA; }
void setup() override {
// gravityTds.setPin(TDS_PIN);
// gravityTds.setAref(3.3);
// gravityTds.setAdcRange(1024); //1024 for 10bit ADC;4096 for 12bit ADC
// gravityTds.begin(); //initialization
}
float ftoc(float temp) {
return (temp - 32.0) / 1.8;
}
void update() override {
//float ph_measurement = pH.read_ph();
//ESP_LOGD("custom", "%.2f | %.2d | %.2f", ph_measurement, analogRead(A0), pH.read_voltage());
//ESP_LOGD("custom", "%.2f | %.2f | %.2f", pH.pH.mid_cal, pH.pH.low_cal, pH.pH.high_cal);
// float nuteTemp = ftoc(sTemp->state);
// gravityTds.setTemperature(nuteTemp);
// gravityTds.update(); //sample and calculate
// float tdsValue = gravityTds.getTdsValue(); // then get the value
// // ESP_LOGD("custom", "%.2f | %.2f", tdsValue, nuteTemp);
// tds_sensor->publish_state(tdsValue);
// voltage_sensor->publish_state(analogRead(TDS_PIN));
}
};
/*
EEPROM.h -ported by Paolo Becchi to Esp32 from esp8266 EEPROM
-Modified by Elochukwu Ifediora <[email protected]>
-Converted to nvs [email protected]
Uses a nvs byte array to emulate EEPROM
Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef EEPROM_h
#define EEPROM_h
#ifndef EEPROM_FLASH_PARTITION_NAME
#define EEPROM_FLASH_PARTITION_NAME "eeprom"
#endif
#include <Arduino.h>
typedef uint32_t nvs_handle;
class EEPROMClass {
public:
EEPROMClass(uint32_t sector);
EEPROMClass(const char* name);
EEPROMClass(void);
~EEPROMClass(void);
bool begin(size_t size);
uint8_t read(int address);
void write(int address, uint8_t val);
uint16_t length();
bool commit();
void end();
uint8_t * getDataPtr();
uint16_t convert(bool clear, const char* EEPROMname = "eeprom", const char* nvsname = "eeprom");
template<typename T>
T &get(int address, T &t) {
if (address < 0 || address + sizeof(T) > _size)
return t;
memcpy((uint8_t*) &t, _data + address, sizeof(T));
return t;
}
template<typename T>
const T &put(int address, const T &t) {
if (address < 0 || address + sizeof(T) > _size)
return t;
memcpy(_data + address, (const uint8_t*) &t, sizeof(T));
_dirty = true;
return t;
}
uint8_t readByte(int address);
int8_t readChar(int address);
uint8_t readUChar(int address);
int16_t readShort(int address);
uint16_t readUShort(int address);
int32_t readInt(int address);
uint32_t readUInt(int address);
int32_t readLong(int address);
uint32_t readULong(int address);
int64_t readLong64(int address);
uint64_t readULong64(int address);
float_t readFloat(int address);
double_t readDouble(int address);
bool readBool(int address);
size_t readString(int address, char* value, size_t maxLen);
String readString(int address);
size_t readBytes(int address, void * value, size_t maxLen);
template <class T> T readAll (int address, T &);
size_t writeByte(int address, uint8_t value);
size_t writeChar(int address, int8_t value);
size_t writeUChar(int address, uint8_t value);
size_t writeShort(int address, int16_t value);
size_t writeUShort(int address, uint16_t value);
size_t writeInt(int address, int32_t value);
size_t writeUInt(int address, uint32_t value);
size_t writeLong(int address, int32_t value);
size_t writeULong(int address, uint32_t value);
size_t writeLong64(int address, int64_t value);
size_t writeULong64(int address, uint64_t value);
size_t writeFloat(int address, float_t value);
size_t writeDouble(int address, double_t value);
size_t writeBool(int address, bool value);
size_t writeString(int address, const char* value);
size_t writeString(int address, String value);
size_t writeBytes(int address, const void* value, size_t len);
template <class T> T writeAll (int address, const T &);
protected:
nvs_handle _handle;
uint8_t* _data;
size_t _size;
bool _dirty;
const char* _name;
};
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_EEPROM)
extern EEPROMClass EEPROM;
#endif
#include <nvs.h>
#include <esp_partition.h>
#include <esp_log.h>
EEPROMClass::EEPROMClass(void)
: _handle(0)
, _data(0)
, _size(0)
, _dirty(false)
, _name("eeprom")
{
}
EEPROMClass::EEPROMClass(uint32_t sector)
// Only for compatiility, no sectors in nvs!
: _handle(0)
, _data(0)
, _size(0)
, _dirty(false)
, _name("eeprom")
{
}
EEPROMClass::EEPROMClass(const char* name)
: _handle(0)
, _data(0)
, _size(0)
, _dirty(false)
, _name(name)
{
}
EEPROMClass::~EEPROMClass() {
end();
}
bool EEPROMClass::begin(size_t size) {
if (!size) {
return false;
}
esp_err_t res = nvs_open(_name, NVS_READWRITE, &_handle);
if (res != ESP_OK) {
log_e("Unable to open NVS namespace: %d", res);
return false;
}
size_t key_size = 0;
res = nvs_get_blob(_handle, _name, NULL, &key_size);
if(res != ESP_OK && res != ESP_ERR_NVS_NOT_FOUND) {
log_e("Unable to read NVS key: %d", res);
return false;
}
if (size < key_size) { // truncate
log_w("truncating EEPROM from %d to %d", key_size, size);
uint8_t* key_data = (uint8_t*) malloc(key_size);
if(!key_data) {
log_e("Not enough memory to truncate EEPROM!");
return false;
}
nvs_get_blob(_handle, _name, key_data, &key_size);
nvs_set_blob(_handle, _name, key_data, size);
nvs_commit(_handle);
free(key_data);
}
else if (size > key_size) { // expand or new
size_t expand_size = size - key_size;
uint8_t* expand_key = (uint8_t*) malloc(expand_size);
if(!expand_key) {
log_e("Not enough memory to expand EEPROM!");
return false;
}
// check for adequate free space
if(nvs_set_blob(_handle, "expand", expand_key, expand_size)) {
log_e("Not enough space to expand EEPROM from %d to %d", key_size, size);
free(expand_key);
return false;
}
free(expand_key);
nvs_erase_key(_handle, "expand");
uint8_t* key_data = (uint8_t*) malloc(size);
if(!key_data) {
log_e("Not enough memory to expand EEPROM!");
return false;
}
memset(key_data, 0xFF, size);
if(key_size) {
log_i("Expanding EEPROM from %d to %d", key_size, size);
// hold data while key is deleted
nvs_get_blob(_handle, _name, key_data, &key_size);
nvs_erase_key(_handle, _name);
} else {
log_i("New EEPROM of %d bytes", size);
}
nvs_commit(_handle);
nvs_set_blob(_handle, _name, key_data, size);
free(key_data);
nvs_commit(_handle);
}
if (_data) {
delete[] _data;
}
_data = (uint8_t*) malloc(size);
if(!_data) {
log_e("Not enough memory for %d bytes in EEPROM", size);
return false;
}
_size = size;
nvs_get_blob(_handle, _name, _data, &_size);
return true;
}
void EEPROMClass::end() {
if (!_size) {
return;
}
commit();
if (_data) {
delete[] _data;
}
_data = 0;
_size = 0;
nvs_close(_handle);
_handle = 0;
}
uint8_t EEPROMClass::read(int address) {
if (address < 0 || (size_t)address >= _size) {
return 0;
}
if (!_data) {
return 0;
}
return _data[address];
}
void EEPROMClass::write(int address, uint8_t value) {
if (address < 0 || (size_t)address >= _size)
return;
if (!_data)
return;
// Optimise _dirty. Only flagged if data written is different.
uint8_t* pData = &_data[address];
if (*pData != value)
{
*pData = value;
_dirty = true;
}
}
bool EEPROMClass::commit() {
bool ret = false;
if (!_size) {
return false;
}
if (!_data) {
return false;
}
if (!_dirty) {
return true;
}
esp_err_t err = nvs_set_blob(_handle, _name, _data, _size);
if (err != ESP_OK) {
log_e("error in write: %s", esp_err_to_name(err));
} else {
_dirty = false;
ret = true;
}
return ret;
}
uint8_t * EEPROMClass::getDataPtr() {
_dirty = true;
return &_data[0];
}
/*
Get EEPROM total size in byte defined by the user
*/
uint16_t EEPROMClass::length ()
{
return _size;
}
/*
Convert EEPROM partition into nvs blob
Call convert before you call begin
*/
uint16_t EEPROMClass::convert (bool clear, const char* EEPROMname, const char* nvsname)
{
uint16_t result = 0;
const esp_partition_t* mypart = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, EEPROMname);
if (mypart == NULL) {
log_i("EEPROM partition not found for conversion");
return result;
}
size_t size = mypart->size;
uint8_t* data = (uint8_t*) malloc(size);
if (!data) {
log_e("Not enough memory to convert EEPROM!");
goto exit;
}
if (esp_partition_read (mypart, 0, (void *) data, size) != ESP_OK) {
log_e("Unable to read EEPROM partition");
goto exit;
}
bool empty;
empty = true;
for (int x=0; x<size; x++) {
if (data[x] != 0xFF) {
empty = false;
break;
}
}
if (empty) {
log_i("EEPROM partition is empty, will not convert");
goto exit;
}
nvs_handle handle;
if (nvs_open(nvsname, NVS_READWRITE, &handle) != ESP_OK) {
log_e("Unable to open NVS");
goto exit;
}
esp_err_t err;
err = nvs_set_blob(handle, nvsname, data, size);
if (err != ESP_OK) {
log_e("Unable to add EEPROM data to NVS: %s", esp_err_to_name(err));
goto exit;
}
result = size;
if (clear) {
if (esp_partition_erase_range (mypart, 0, size) != ESP_OK) {
log_w("Unable to clear EEPROM partition");
}
}
exit:
free(data);
return result;
}
/*
Read 'value' from 'address'
*/
uint8_t EEPROMClass::readByte (int address)
{
uint8_t value = 0;
return EEPROMClass::readAll (address, value);
}
int8_t EEPROMClass::readChar (int address)
{
int8_t value = 0;
return EEPROMClass::readAll (address, value);
}
uint8_t EEPROMClass::readUChar (int address)
{
uint8_t value = 0;
return EEPROMClass::readAll (address, value);
}
int16_t EEPROMClass::readShort (int address)
{
int16_t value = 0;
return EEPROMClass::readAll (address, value);
}
uint16_t EEPROMClass::readUShort (int address)
{
uint16_t value = 0;
return EEPROMClass::readAll (address, value);
}
int32_t EEPROMClass::readInt (int address)
{
int32_t value = 0;
return EEPROMClass::readAll (address, value);
}
uint32_t EEPROMClass::readUInt (int address)
{
uint32_t value = 0;
return EEPROMClass::readAll (address, value);
}
int32_t EEPROMClass::readLong (int address)
{
int32_t value = 0;
return EEPROMClass::readAll (address, value);
}
uint32_t EEPROMClass::readULong (int address)
{
uint32_t value = 0;
return EEPROMClass::readAll (address, value);
}
int64_t EEPROMClass::readLong64 (int address)
{
int64_t value = 0;
return EEPROMClass::readAll (address, value);
}
uint64_t EEPROMClass::readULong64 (int address)
{
uint64_t value = 0;
return EEPROMClass::readAll (address, value);
}
float_t EEPROMClass::readFloat (int address)
{
float_t value = 0;
return EEPROMClass::readAll (address, value);
}
double_t EEPROMClass::readDouble (int address)
{
double_t value = 0;
return EEPROMClass::readAll (address, value);
}
bool EEPROMClass::readBool (int address)
{
int8_t value = 0;
return EEPROMClass::readAll (address, value) ? 1 : 0;
}
size_t EEPROMClass::readString (int address, char* value, size_t maxLen)
{
if (!value)
return 0;
if (address < 0 || address + maxLen > _size)
return 0;
uint16_t len;
for (len = 0; len <= _size; len++)
if (_data[address + len] == 0)
break;
if (address + len > _size)
return 0;
if (len > maxLen)
return 0; //Maybe return part of the string instead?
memcpy((uint8_t*) value, _data + address, len);
value[len] = 0;
return len;
}
String EEPROMClass::readString (int address)
{
if (address < 0 || address > _size)
return String();
uint16_t len;
for (len = 0; len <= _size; len++)
if (_data[address + len] == 0)
break;
if (address + len > _size)
return String();
char value[len+1];
memcpy((uint8_t*) value, _data + address, len);
value[len] = 0;
return String(value);
}
size_t EEPROMClass::readBytes (int address, void* value, size_t maxLen)
{
if (!value || !maxLen)
return 0;
if (address < 0 || address + maxLen > _size)
return 0;
memcpy((void*) value, _data + address, maxLen);
return maxLen;
}
template <class T> T EEPROMClass::readAll (int address, T &value)
{
if (address < 0 || address + sizeof(T) > _size)
return value;
memcpy((uint8_t*) &value, _data + address, sizeof(T));
return value;
}
/*
Write 'value' to 'address'
*/
size_t EEPROMClass::writeByte (int address, uint8_t value)
{
return EEPROMClass::writeAll (address, value);
}
size_t EEPROMClass::writeChar (int address, int8_t value)
{
return EEPROMClass::writeAll (address, value);
}
size_t EEPROMClass::writeUChar (int address, uint8_t value)
{
return EEPROMClass::writeAll (address, value);
}
size_t EEPROMClass::writeShort (int address, int16_t value)
{
return EEPROMClass::writeAll (address, value);
}
size_t EEPROMClass::writeUShort (int address, uint16_t value)
{
return EEPROMClass::writeAll (address, value);
}
size_t EEPROMClass::writeInt (int address, int32_t value)
{
return EEPROMClass::writeAll (address, value);
}
size_t EEPROMClass::writeUInt (int address, uint32_t value)
{
return EEPROMClass::writeAll (address, value);
}
size_t EEPROMClass::writeLong (int address, int32_t value)
{
return EEPROMClass::writeAll (address, value);
}
size_t EEPROMClass::writeULong (int address, uint32_t value)
{
return EEPROMClass::writeAll (address, value);
}
size_t EEPROMClass::writeLong64 (int address, int64_t value)
{
return EEPROMClass::writeAll (address, value);
}
size_t EEPROMClass::writeULong64 (int address, uint64_t value)
{
return EEPROMClass::writeAll (address, value);
}
size_t EEPROMClass::writeFloat (int address, float_t value)
{
return EEPROMClass::writeAll (address, value);
}
size_t EEPROMClass::writeDouble (int address, double_t value)
{
return EEPROMClass::writeAll (address, value);
}
size_t EEPROMClass::writeBool (int address, bool value)
{
int8_t Bool;
value ? Bool = 1 : Bool = 0;
return EEPROMClass::writeAll (address, Bool);
}
size_t EEPROMClass::writeString (int address, const char* value)
{
if (!value)
return 0;
if (address < 0 || address > _size)
return 0;
uint16_t len;
for (len = 0; len <= _size; len++)
if (value[len] == 0)
break;
if (address + len > _size)
return 0;
memcpy(_data + address, (const uint8_t*) value, len + 1);
_dirty = true;
return strlen(value);
}
size_t EEPROMClass::writeString (int address, String value)
{
return EEPROMClass::writeString (address, value.c_str());
}
size_t EEPROMClass::writeBytes (int address, const void* value, size_t len)
{
if (!value || !len)
return 0;
if (address < 0 || address + len > _size)
return 0;
memcpy(_data + address, (const void*) value, len);
_dirty = true;
return len;
}
template <class T> T EEPROMClass::writeAll (int address, const T &value)
{
if (address < 0 || address + sizeof(T) > _size)
return value;
memcpy(_data + address, (const uint8_t*) &value, sizeof(T));
_dirty = true;
return sizeof (value);
}
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_EEPROM)
EEPROMClass EEPROM;
#endif
#endif
```shell
esphome compile aquarium.yaml
```
```shell
esphome upload aquarium.yaml
```
Open Home-Assistant's Devices and Services dashboard. The ESPHome device should automatically be discovered, you simply need to add it to your Home-Assistant instance.