move some vectors to psram, fix syslog start/stop

This commit is contained in:
MichaelDvP
2025-12-04 19:57:01 +01:00
parent dd0ab8f962
commit 1d03056784
28 changed files with 3172 additions and 70 deletions

View File

@@ -166,9 +166,9 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/
"CS5800i, CS6800i, WLW176i, WLW186i",boiler,8,dhw.comfoff,comfort switch off,uint8 (>=15<=65),C,true,number.boiler_dhw_comfort_switch_off,number.boiler_dhw_comfoff,5,9,1,18,1
"CS5800i, CS6800i, WLW176i, WLW186i",boiler,8,dhw.ecooff,eco switch off,uint8 (>=15<=65),C,true,number.boiler_dhw_eco_switch_off,number.boiler_dhw_ecooff,5,9,1,19,1
"CS5800i, CS6800i, WLW176i, WLW186i",boiler,8,dhw.ecoplusoff,eco+ switch off,uint8 (>=48<=63),C,true,number.boiler_dhw_eco+_switch_off,number.boiler_dhw_ecoplusoff,5,9,1,20,1
"CS5800i, CS6800i, WLW176i, WLW186i",boiler,8,dhw.comfdiff,comfort diff,uint8 (>=4<=12),K,true,number.boiler_dhw_comfort_diff,number.boiler_dhw_comfdiff,5,9,1,21,1
"CS5800i, CS6800i, WLW176i, WLW186i",boiler,8,dhw.ecodiff,eco diff,uint8 (>=4<=12),K,true,number.boiler_dhw_eco_diff,number.boiler_dhw_ecodiff,5,9,1,22,1
"CS5800i, CS6800i, WLW176i, WLW186i",boiler,8,dhw.ecoplusdiff,eco+ diff,uint8 (>=6<=12),K,true,number.boiler_dhw_eco+_diff,number.boiler_dhw_ecoplusdiff,5,9,1,23,1
"CS5800i, CS6800i, WLW176i, WLW186i",boiler,8,dhw.comfdiff,comfort diff,uint8 (>=4<=15),K,true,number.boiler_dhw_comfort_diff,number.boiler_dhw_comfdiff,5,9,1,21,1
"CS5800i, CS6800i, WLW176i, WLW186i",boiler,8,dhw.ecodiff,eco diff,uint8 (>=4<=15),K,true,number.boiler_dhw_eco_diff,number.boiler_dhw_ecodiff,5,9,1,22,1
"CS5800i, CS6800i, WLW176i, WLW186i",boiler,8,dhw.ecoplusdiff,eco+ diff,uint8 (>=4<=15),K,true,number.boiler_dhw_eco+_diff,number.boiler_dhw_ecoplusdiff,5,9,1,23,1
"CS5800i, CS6800i, WLW176i, WLW186i",boiler,8,dhw.comfstop,comfort stop temp,uint8 (>=0<=254),C,true,number.boiler_dhw_comfort_stop_temp,number.boiler_dhw_comfstop,5,9,1,24,1
"CS5800i, CS6800i, WLW176i, WLW186i",boiler,8,dhw.ecostop,eco stop temp,uint8 (>=0<=254),C,true,number.boiler_dhw_eco_stop_temp,number.boiler_dhw_ecostop,5,9,1,25,1
"CS5800i, CS6800i, WLW176i, WLW186i",boiler,8,dhw.ecoplusstop,eco+ stop temp,uint8 (>=0<=254),C,true,number.boiler_dhw_eco+_stop_temp,number.boiler_dhw_ecoplusstop,5,9,1,26,1
@@ -2721,9 +2721,9 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/
"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.comfoff,comfort switch off,uint8 (>=15<=65),C,true,number.boiler_dhw_comfort_switch_off,number.boiler_dhw_comfoff,5,9,1,18,1
"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.ecooff,eco switch off,uint8 (>=15<=65),C,true,number.boiler_dhw_eco_switch_off,number.boiler_dhw_ecooff,5,9,1,19,1
"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.ecoplusoff,eco+ switch off,uint8 (>=48<=63),C,true,number.boiler_dhw_eco+_switch_off,number.boiler_dhw_ecoplusoff,5,9,1,20,1
"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.comfdiff,comfort diff,uint8 (>=4<=12),K,true,number.boiler_dhw_comfort_diff,number.boiler_dhw_comfdiff,5,9,1,21,1
"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.ecodiff,eco diff,uint8 (>=4<=12),K,true,number.boiler_dhw_eco_diff,number.boiler_dhw_ecodiff,5,9,1,22,1
"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.ecoplusdiff,eco+ diff,uint8 (>=6<=12),K,true,number.boiler_dhw_eco+_diff,number.boiler_dhw_ecoplusdiff,5,9,1,23,1
"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.comfdiff,comfort diff,uint8 (>=4<=15),K,true,number.boiler_dhw_comfort_diff,number.boiler_dhw_comfdiff,5,9,1,21,1
"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.ecodiff,eco diff,uint8 (>=4<=15),K,true,number.boiler_dhw_eco_diff,number.boiler_dhw_ecodiff,5,9,1,22,1
"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.ecoplusdiff,eco+ diff,uint8 (>=4<=15),K,true,number.boiler_dhw_eco+_diff,number.boiler_dhw_ecoplusdiff,5,9,1,23,1
"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.comfstop,comfort stop temp,uint8 (>=0<=254),C,true,number.boiler_dhw_comfort_stop_temp,number.boiler_dhw_comfstop,5,9,1,24,1
"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.ecostop,eco stop temp,uint8 (>=0<=254),C,true,number.boiler_dhw_eco_stop_temp,number.boiler_dhw_ecostop,5,9,1,25,1
"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.ecoplusstop,eco+ stop temp,uint8 (>=0<=254),C,true,number.boiler_dhw_eco+_stop_temp,number.boiler_dhw_ecoplusstop,5,9,1,26,1
@@ -2936,9 +2936,9 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/
"Geo 5xx",boiler,173,dhw.comfoff,comfort switch off,uint8 (>=15<=65),C,true,number.boiler_dhw_comfort_switch_off,number.boiler_dhw_comfoff,5,9,1,18,1
"Geo 5xx",boiler,173,dhw.ecooff,eco switch off,uint8 (>=15<=65),C,true,number.boiler_dhw_eco_switch_off,number.boiler_dhw_ecooff,5,9,1,19,1
"Geo 5xx",boiler,173,dhw.ecoplusoff,eco+ switch off,uint8 (>=48<=63),C,true,number.boiler_dhw_eco+_switch_off,number.boiler_dhw_ecoplusoff,5,9,1,20,1
"Geo 5xx",boiler,173,dhw.comfdiff,comfort diff,uint8 (>=4<=12),K,true,number.boiler_dhw_comfort_diff,number.boiler_dhw_comfdiff,5,9,1,21,1
"Geo 5xx",boiler,173,dhw.ecodiff,eco diff,uint8 (>=4<=12),K,true,number.boiler_dhw_eco_diff,number.boiler_dhw_ecodiff,5,9,1,22,1
"Geo 5xx",boiler,173,dhw.ecoplusdiff,eco+ diff,uint8 (>=6<=12),K,true,number.boiler_dhw_eco+_diff,number.boiler_dhw_ecoplusdiff,5,9,1,23,1
"Geo 5xx",boiler,173,dhw.comfdiff,comfort diff,uint8 (>=4<=15),K,true,number.boiler_dhw_comfort_diff,number.boiler_dhw_comfdiff,5,9,1,21,1
"Geo 5xx",boiler,173,dhw.ecodiff,eco diff,uint8 (>=4<=15),K,true,number.boiler_dhw_eco_diff,number.boiler_dhw_ecodiff,5,9,1,22,1
"Geo 5xx",boiler,173,dhw.ecoplusdiff,eco+ diff,uint8 (>=4<=15),K,true,number.boiler_dhw_eco+_diff,number.boiler_dhw_ecoplusdiff,5,9,1,23,1
"Geo 5xx",boiler,173,dhw.comfstop,comfort stop temp,uint8 (>=0<=254),C,true,number.boiler_dhw_comfort_stop_temp,number.boiler_dhw_comfstop,5,9,1,24,1
"Geo 5xx",boiler,173,dhw.ecostop,eco stop temp,uint8 (>=0<=254),C,true,number.boiler_dhw_eco_stop_temp,number.boiler_dhw_ecostop,5,9,1,25,1
"Geo 5xx",boiler,173,dhw.ecoplusstop,eco+ stop temp,uint8 (>=0<=254),C,true,number.boiler_dhw_eco+_stop_temp,number.boiler_dhw_ecoplusstop,5,9,1,26,1
@@ -5468,6 +5468,8 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/
"SM50",solar,162,heatcnt,heat counter impulses,uint8 (>=0<=254), ,false,sensor.solar_heat_counter_impulses,sensor.solar_heatcnt,8,0,1,67,1
"SM50",solar,162,swapflowtemp,swap flow temperature (TS14),uint16 (>=0<=3199),C,false,sensor.solar_swap_flow_temperature_(TS14),sensor.solar_swapflowtemp,8,0,1/10,68,1
"SM50",solar,162,swaprettemp,swap return temperature (TS15),uint16 (>=0<=3199),C,false,sensor.solar_swap_return_temperature_(TS15),sensor.solar_swaprettemp,8,0,1/10,69,1
"SM50",solar,162,heatassiston,heat assistance on,int8 (>=-12<=12),K,true,number.solar_heat_assistance_on,number.solar_heatassiston,8,0,1/10,-1,1
"SM50",solar,162,heatassistoff,heat assistance off,int8 (>=-12<=12),K,true,number.solar_heat_assistance_off,number.solar_heatassistoff,8,0,1/10,-1,1
"SM100, MS100",solar,163,collectortemp,collector temperature (TS1),int16 (>=-3199<=3199),C,false,sensor.solar_collector_temperature_(TS1),sensor.solar_collectortemp,8,0,1/10,0,1
"SM100, MS100",solar,163,cylbottomtemp,cylinder bottom temperature (TS2),int16 (>=-3199<=3199),C,false,sensor.solar_cylinder_bottom_temperature_(TS2),sensor.solar_cylbottomtemp,8,0,1/10,1,1
"SM100, MS100",solar,163,solarpump,pump (PS1),boolean, ,false,binary_sensor.solar_pump_(PS1),binary_sensor.solar_solarpump,8,0,1,2,1
@@ -5529,6 +5531,8 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/
"SM100, MS100",solar,163,heatcnt,heat counter impulses,uint8 (>=0<=254), ,false,sensor.solar_heat_counter_impulses,sensor.solar_heatcnt,8,0,1,67,1
"SM100, MS100",solar,163,swapflowtemp,swap flow temperature (TS14),uint16 (>=0<=3199),C,false,sensor.solar_swap_flow_temperature_(TS14),sensor.solar_swapflowtemp,8,0,1/10,68,1
"SM100, MS100",solar,163,swaprettemp,swap return temperature (TS15),uint16 (>=0<=3199),C,false,sensor.solar_swap_return_temperature_(TS15),sensor.solar_swaprettemp,8,0,1/10,69,1
"SM100, MS100",solar,163,heatassiston,heat assistance on,int8 (>=-12<=12),K,true,number.solar_heat_assistance_on,number.solar_heatassiston,8,0,1/10,-1,1
"SM100, MS100",solar,163,heatassistoff,heat assistance off,int8 (>=-12<=12),K,true,number.solar_heat_assistance_off,number.solar_heatassistoff,8,0,1/10,-1,1
"SM200, MS200",solar,164,collectortemp,collector temperature (TS1),int16 (>=-3199<=3199),C,false,sensor.solar_collector_temperature_(TS1),sensor.solar_collectortemp,8,0,1/10,0,1
"SM200, MS200",solar,164,cylbottomtemp,cylinder bottom temperature (TS2),int16 (>=-3199<=3199),C,false,sensor.solar_cylinder_bottom_temperature_(TS2),sensor.solar_cylbottomtemp,8,0,1/10,1,1
"SM200, MS200",solar,164,solarpump,pump (PS1),boolean, ,false,binary_sensor.solar_pump_(PS1),binary_sensor.solar_solarpump,8,0,1,2,1
@@ -5590,6 +5594,8 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/
"SM200, MS200",solar,164,heatcnt,heat counter impulses,uint8 (>=0<=254), ,false,sensor.solar_heat_counter_impulses,sensor.solar_heatcnt,8,0,1,67,1
"SM200, MS200",solar,164,swapflowtemp,swap flow temperature (TS14),uint16 (>=0<=3199),C,false,sensor.solar_swap_flow_temperature_(TS14),sensor.solar_swapflowtemp,8,0,1/10,68,1
"SM200, MS200",solar,164,swaprettemp,swap return temperature (TS15),uint16 (>=0<=3199),C,false,sensor.solar_swap_return_temperature_(TS15),sensor.solar_swaprettemp,8,0,1/10,69,1
"SM200, MS200",solar,164,heatassiston,heat assistance on,int8 (>=-12<=12),K,true,number.solar_heat_assistance_on,number.solar_heatassiston,8,0,1/10,-1,1
"SM200, MS200",solar,164,heatassistoff,heat assistance off,int8 (>=-12<=12),K,true,number.solar_heat_assistance_off,number.solar_heatassistoff,8,0,1/10,-1,1
"HP Module",heatpump,252,airhumidity,relative air humidity,uint8 (>=0<=100),%,false,sensor.heatpump_relative_air_humidity,sensor.heatpump_airhumidity,9,0,1,0,1
"HP Module",heatpump,252,dewtemperature,dew point temperature,uint8 (>=0<=254),C,false,sensor.heatpump_dew_point_temperature,sensor.heatpump_dewtemperature,9,0,1,1,1
"HP Module",heatpump,252,curflowtemp,current flow temperature,int16 (>=-3199<=3199),C,false,sensor.heatpump_current_flow_temperature,sensor.heatpump_curflowtemp,9,0,1/10,2,1
Can't render this file because it is too large.

91
lib/esp32-psram/README.md Normal file
View File

@@ -0,0 +1,91 @@
# ESP32-PSRAM Library
[![Arduino Library](https://img.shields.io/badge/Arduino-Library-blue.svg)](https://www.arduino.cc/reference/en/libraries/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
A comprehensive memory management library for ESP32 microcontrollers, providing efficient data structures that leverage __PSRAM__ and __HIMEM__ for expanded storage capabilities.
## Overview
The ESP32 can only address 4MB of PSRAM directly, so the remaining 4MB (HIMEM) are usually unsued!
__esp32-psram__ is a C++ library that helps you manage large amounts of data on ESP32 devices by utilizing the full external PSRAM (SPI RAM) supporting the high memory (HIMEM) regions. This allows your applications to work with much larger datasets than would be possible using just internal RAM.
- **Use any STL Class in PSRAM**:
- Adapt any [STL class](https://en.wikipedia.org/wiki/Standard_Template_Library) that accepts with an Allocator by using the [AllocatorPSRAM](https://pschatzmann.github.io/esp32-psram/html/classesp32__psram_1_1_p_s_r_a_m_allocator.html)
- **Memory-Efficient Data Structures**:
- `VectorPSRAM`: Vector implementation that automatically stores data in PSRAM
- `VectorHIMEM`: Vector implementation for extremely large datasets using ESP32's high memory region
- **File System Abstractions**:
- `FilePSRAM`: File-like interface backed by PSRAM
- `FileHIMEM`: File-like interface backed by high memory
- SD card-like API using familiar file operations to write to PSRAM or HIMEM.
- **Streaming Data Handling**:
- `RingBufferStreamRAM`: Circular buffer implementation in RAM (Stream-based)
- `RingBufferStreamPSRAM`: Circular buffer implementation in PSRAM (Stream-based)
- `RingBufferStreamHIMEM`: Circular buffer implementation in high memory (Stream-based)
- Fully compatible with Arduino's Stream class
- **Typed Ring Buffers**:
- `TypedRingBufferRAM<T>`: Type-safe circular buffer for any data type using RAM
- `TypedRingBufferPSRAM<T>`: PSRAM version for storing complex data structures
- `TypedRingBufferHIMEM<T>`: High memory version for storing complex data structures
- Optimized for struct/class storage with proper memory management
## Installation
1. Download the latest release from GitHub
2. Extract the ZIP file
3. Move the folder to your Arduino libraries directory (typically `~/Arduino/libraries/` on Linux/macOS or `Documents\Arduino\libraries\` on Windows)
4. Restart Arduino IDE
## API Reference
- [Class Reference](https://pschatzmann.github.io/esp32-psram/html/namespaceesp32__psram.html)
- [Further Information](https://github.com/pschatzmann/esp32-psram/wiki)
## Memory Performance Comparison
| Memory Type | Access Speed | Capacity | Use Case |
|-------------|--------------|----------|----------|
| RAM (DRAM) | Very Fast | ~200-300KB | Small, performance-critical data |
| PSRAM | Fast | Up to 4MB | Medium-sized datasets, frequent access |
| HIMEM | Medium | Up to 8MB | Large datasets, less frequent access |
## Hardware Compatibility
| ESP32 Board | PSRAM Size | HIMEM Support | Notes |
|-------------|------------|--------------|-------|
| ESP32 | Up to 4MB | Yes | Most common ESP32 modules |
| ESP32-S2 | Up to 2MB | Limited | Check board specifications |
| ESP32-S3 | Up to 8MB | Yes | Best memory capabilities |
| ESP32-C3 | None | No | Not compatible with this library |
## Design Philosophy
The ESP32-PSRAM library is designed with these principles in mind:
1. **Familiar interfaces**: API design mimics standard C++ containers and Arduino libraries
2. **Memory efficiency**: Automatic use of appropriate memory regions
3. **Type safety**: Template-based to work with any data type
4. **Flexibility**: Configurable for different memory types and use cases
5. **Zero overhead**: Direct memory access with minimal abstraction layers
## License
MIT License
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
## Acknowledgements
This library was inspired by the need for better memory management solutions on ESP32, especially for audio, image processing, and data logging applications.

View File

@@ -0,0 +1,10 @@
name=ESP32-PSRAM
version=0.1.3
author=Phil Schatzmann
maintainer=https://github.com/pschatzmann
sentence=Memory management library for ESP32 using PSRAM and HIMEM
paragraph=A comprehensive memory management library that provides vector, file, and ring buffer implementations using ESP32's PSRAM and HIMEM. Allows applications to work with large datasets beyond internal memory limitations.
category=Data Storage
url=https://github.com/pschatzmann/esp32-psram
architectures=esp32
depends=

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Phil Schatzmann
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,29 @@
#pragma once
#ifdef ESP32
/**
* @file ESP32-PSRAM.h
* @brief Main include file for the ESP32-PSRAM library
*
* This header includes all components of the ESP32-PSRAM library and
* provides a using namespace directive for easier access to library features.
*/
// Include all library components
#include "esp32-psram/AllocatorPSRAM.h" // PSRAM-backed vector
#include "esp32-psram/VectorPSRAM.h" // PSRAM-backed vector
#include "esp32-psram/VectorHIMEM.h" // HIMEM-backed vector
// #include "esp32-psram/InMemoryFile.h" // File interface using vectors
// #include "esp32-psram/PSRAM.h" // PSRAM file system
// #include "esp32-psram/HIMEM.h" // HIMEM file system
#include "esp32-psram/RingBufferStream.h" // Stream-based ring buffer
#include "esp32-psram/TypedRingBuffer.h" // Typed ring buffer for structured data
#ifndef ESP32_PSRAM_NO_NAMESPACE
using namespace esp32_psram;
#endif
#else
#error "This library is only compatible with ESP32 platforms."
#endif // ESP32

View File

@@ -0,0 +1,159 @@
#pragma once
#include <esp_heap_caps.h>
/**
* @namespace esp32_psram
* @brief Namespace containing ESP32 PSRAM-specific implementations
*/
namespace esp32_psram {
/**
* @class AllocatorPSRAM
* @brief Custom allocator that uses ESP32's PSRAM for memory allocation
* @tparam T Type of elements to allocate
*
* This allocator uses ESP32's heap_caps_malloc with MALLOC_CAP_SPIRAM flag
* to ensure all memory is allocated in PSRAM instead of regular RAM. If PSRAM
* allocation fails, it falls back to regular RAM.
*/
template <typename T>
class AllocatorPSRAM {
public:
using value_type = T;
using pointer = T*;
using const_pointer = const T*;
using reference = T&;
using const_reference = const T&;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
/**
* @brief Default constructor
*/
AllocatorPSRAM() noexcept {}
/**
* @brief Copy constructor from another allocator type
* @tparam U Type of the other allocator
* @param other The other allocator
*/
template <typename U>
AllocatorPSRAM(const AllocatorPSRAM<U>&) noexcept {}
/**
* @brief Allocate memory from PSRAM
* @param n Number of elements to allocate
* @return Pointer to allocated memory
* @throws std::bad_alloc If allocation fails or size is too large
*/
pointer allocate(size_type n) {
// if (n > std::numeric_limits<size_type>::max() / sizeof(T))
// throw std::bad_alloc();
// in Arduino excepitons are disabled!
assert(n <= std::numeric_limits<size_type>::max() / sizeof(T));
pointer p = static_cast<pointer>(
heap_caps_malloc(n * sizeof(T), MALLOC_CAP_SPIRAM));
if (p == nullptr) {
p = static_cast<pointer>(malloc(n * sizeof(T)));
}
// if (!p) throw std::bad_alloc();
// in Arduino excepitons are disabled!
assert(p);
return p;
}
/**
* @brief Deallocate memory
* @param p Pointer to memory to deallocate
* @param size Size of allocation (unused)
*/
void deallocate(pointer p, size_type) noexcept { heap_caps_free(p); }
/**
* @brief Rebind allocator to another type
* @tparam U Type to rebind the allocator to
*/
template <typename U>
struct rebind {
using other = AllocatorPSRAM<U>;
};
};
/**
* @class AllocatorOnlyPSRAM
* @brief Custom allocator that uses ESP32's PSRAM for memory allocation
* @tparam T Type of elements to allocate
*
* This allocator uses ESP32's heap_caps_malloc with MALLOC_CAP_SPIRAM flag
* to ensure all memory is allocated in PSRAM instead of regular RAM.
* If PSRAM allocation fails, it does not fall back to regular RAM.
*/
template <typename T>
class AllocatorOnlyPSRAM {
public:
using value_type = T;
using pointer = T*;
using const_pointer = const T*;
using reference = T&;
using const_reference = const T&;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
/**
* @brief Default constructor
*/
AllocatorOnlyPSRAM() noexcept {}
/**
* @brief Copy constructor from another allocator type
* @tparam U Type of the other allocator
* @param other The other allocator
*/
template <typename U>
AllocatorOnlyPSRAM(const AllocatorOnlyPSRAM<U>&) noexcept {}
/**
* @brief Allocate memory from PSRAM
* @param n Number of elements to allocate
* @return Pointer to allocated memory
* @throws std::bad_alloc If allocation fails or size is too large
*/
pointer allocate(size_type n) {
// if (n > std::numeric_limits<size_type>::max() / sizeof(T))
// throw std::bad_alloc();
// in Arduino excepitons are disabled!
assert(n <= std::numeric_limits<size_type>::max() / sizeof(T));
pointer p = static_cast<pointer>(
heap_caps_malloc(n * sizeof(T), MALLOC_CAP_SPIRAM));
// if (!p) throw std::bad_alloc();
// in Arduino excepitons are disabled!
assert(p);
return p;
}
/**
* @brief Deallocate memory
* @param p Pointer to memory to deallocate
* @param size Size of allocation (unused)
*/
void deallocate(pointer p, size_type) noexcept { heap_caps_free(p); }
/**
* @brief Rebind allocator to another type
* @tparam U Type to rebind the allocator to
*/
template <typename U>
struct rebind {
using other = AllocatorOnlyPSRAM<U>;
};
};
} // namespace esp32_psram

View File

@@ -0,0 +1,54 @@
#pragma once
#include <Arduino.h>
#include "InMemoryFS.h"
#include "VectorHIMEM.h"
namespace esp32_psram {
/**
* @class HIMEMClass
* @brief Class for managing files stored in ESP32's High Memory (HIMEM)
*
* This class provides an interface similar to SD.h for managing files
* that are stored in HIMEM memory (beyond the 4MB boundary) rather than on an SD card.
* HIMEM offers larger storage capacity but slightly slower access than regular PSRAM.
*/
class HIMEMClass : public InMemoryFS<VectorHIMEM<uint8_t>, FileHIMEM> {
public:
/**
* @brief Initialize the HIMEM filesystem
* @return true if initialization was successful, false otherwise
*/
bool begin() override {
if (esp_himem_get_free_size() > 0) {
initialized = true;
return true;
}
initialized = false;
return false;
}
/**
* @brief Get total space (returns available HIMEM)
* @return Total HIMEM size in bytes
*/
uint64_t totalBytes() override {
return esp_himem_get_phys_size();
}
/**
* @brief Get free space (returns free HIMEM)
* @return Free HIMEM size in bytes
*/
uint64_t freeBytes() override {
return esp_himem_get_free_size();
}
};
/**
* @brief Global instance of HIMEMClass for easy access
*/
static HIMEMClass HIMEM;
} // namespace esp32_psram

View File

@@ -0,0 +1,362 @@
#pragma once
#include <algorithm>
#include <limits>
#include <memory>
#include <vector>
// ESP32 HIMEM headers - using conditional inclusion for compatibility
#if __has_include("esp32/himem.h")
#include "esp32/himem.h"
#elif __has_include("esp_himem.h")
#include "esp_himem.h"
#else
// Fall back to Arduino ESP32 core path
extern "C" {
#include "esp32/himem.h"
}
// Define missing constants if needed
#ifndef ESP_HIMEM_BLKSZ
#define ESP_HIMEM_BLKSZ (32 * 1024)
#endif
#endif
// Make sure ESP_HIMEM_PROT_RW is defined
#ifndef ESP_HIMEM_PROT_RW
#define ESP_HIMEM_PROT_RW 0
#endif
namespace esp32_psram {
static constexpr const char* TAG = "HIMEM"; // Tag for ESP logging
/**
* @class HimemBlock
* @brief Manages a block of himem memory with mapping functionality
*/
class HimemBlock {
public:
/**
* @brief Default constructor
*/
HimemBlock() { ESP_LOGD(TAG, "HimemBlock constructor called"); }
/**
* @brief Allocate a block of himem
* @param block_size Size of memory to allocate in bytes
* @return the allocated (block) size in bytes, 0 otherwise
*/
size_t allocate(size_t block_size) {
ESP_LOGI(TAG, "HimemBlock::allocate(%u bytes) - Current handle: %p",
block_size, handle);
if (handle != 0) {
ESP_LOGW(TAG, "Cannot allocate: Block already allocated");
return false; // Already allocated
}
// Round up to the nearest multiple of ESP_HIMEM_BLKSZ (32K)
block_size = ((block_size + ESP_HIMEM_BLKSZ - 1) / ESP_HIMEM_BLKSZ) *
ESP_HIMEM_BLKSZ;
size = block_size;
esp_err_t err = esp_himem_alloc(block_size, &handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "HIMEM allocation failed - error: %d, size: %d", err,
block_size);
return 0;
}
ESP_LOGD(TAG, "- Successfully allocated %u bytes, handle: %p", block_size,
handle);
return block_size;
}
/**
* @brief Read data from HIMEM at specified offset
* @param dest Destination buffer
* @param offset Offset in HIMEM to read from
* @param length Number of bytes to read
* @return Number of bytes actually read
*/
size_t read(void* dest, size_t offset, size_t length) {
ESP_LOGD(TAG, "HimemBlock::read(dst=%p, offset=%u, length=%u)", dest,
offset, length);
if (!handle || offset >= size) {
ESP_LOGW(TAG, "Read failed: %s",
!handle ? "Invalid handle" : "Offset beyond size");
return 0;
}
// Ensure we don't read past the end
length = std::min(length, size - offset);
if (length == 0) {
ESP_LOGW(TAG, "Read failed: Zero length after bounds check");
return 0;
}
// Calculate which block this belongs to
size_t block_index = offset / ESP_HIMEM_BLKSZ;
size_t block_offset = offset % ESP_HIMEM_BLKSZ;
size_t bytes_read = 0;
ESP_LOGD(TAG, "- Reading from block %u, offset %u", block_index,
block_offset);
uint8_t* dest_ptr = static_cast<uint8_t*>(dest);
while (bytes_read < length) {
// Ensure the correct block is mapped
if (!ensure_block_mapped(block_index)) {
return bytes_read;
}
// Calculate how much to read from this block
size_t block_remain = ESP_HIMEM_BLKSZ - block_offset;
size_t to_read = std::min(block_remain, length - bytes_read);
ESP_LOGD(TAG, "- Reading %u bytes from window at offset %u", to_read,
block_offset);
// Copy the data
memcpy(dest_ptr + bytes_read,
static_cast<uint8_t*>(mapped_ptr) + block_offset, to_read);
bytes_read += to_read;
block_index++;
block_offset = 0; // Reset offset for next blocks
}
ESP_LOGD(TAG, "- Successfully read %u bytes", bytes_read);
return bytes_read;
}
/**
* @brief Write data to HIMEM at specified offset
* @param src Source buffer
* @param offset Offset in HIMEM to write to
* @param length Number of bytes to write
* @return Number of bytes actually written
*/
size_t write(const void* src, size_t offset, size_t length) {
ESP_LOGD(TAG, "HimemBlock::write(src=%p, offset=%u, length=%u)", src,
offset, length);
if (!handle || offset >= size) {
ESP_LOGW(TAG, "Write failed: %s",
!handle ? "Invalid handle" : "Offset beyond size");
return 0;
}
// Ensure we don't write past the end
length = std::min(length, size - offset);
if (length == 0) {
ESP_LOGW(TAG, "Write failed: Zero length after bounds check");
return 0;
}
// Calculate which block this belongs to
size_t block_index = offset / ESP_HIMEM_BLKSZ;
size_t block_offset = offset % ESP_HIMEM_BLKSZ;
size_t bytes_written = 0;
ESP_LOGD(TAG, "- Writing to block %u, offset %u", block_index,
block_offset);
const uint8_t* src_ptr = static_cast<const uint8_t*>(src);
while (bytes_written < length) {
// Ensure the correct block is mapped
if (!ensure_block_mapped(block_index)) {
return bytes_written;
}
// Calculate how much to write to this block
size_t block_remain = ESP_HIMEM_BLKSZ - block_offset;
size_t to_write = std::min(block_remain, length - bytes_written);
ESP_LOGD(TAG, "- Writing %u bytes to window at offset %u", to_write,
block_offset);
// Copy the data
memcpy(static_cast<uint8_t*>(mapped_ptr) + block_offset,
src_ptr + bytes_written, to_write);
bytes_written += to_write;
block_index++;
block_offset = 0; // Reset offset for next blocks
}
ESP_LOGD(TAG, "- Successfully wrote %u bytes", bytes_written);
return bytes_written;
}
bool getAddress(size_t offset, void* &result, size_t &available) {
size_t block_index = offset / ESP_HIMEM_BLKSZ;
size_t block_offset = offset % ESP_HIMEM_BLKSZ;
if (!ensure_block_mapped(block_index)) return false;
available = ESP_HIMEM_BLKSZ - block_offset;
result = static_cast<uint8_t*>(mapped_ptr)+block_offset;
return true;
}
/**
* @brief Unmap the himem block
*/
void unmap() {
ESP_LOGD(TAG, "HimemBlock::unmap() - mapped_ptr=%p, range=%p", mapped_ptr,
range);
if (mapped_ptr && range) {
ESP_LOGD(TAG, "- Unmapping memory and freeing range");
esp_himem_unmap(range, mapped_ptr, ESP_HIMEM_BLKSZ);
esp_himem_free_map_range(range);
mapped_ptr = nullptr;
range = 0;
current_mapped_block = SIZE_MAX; // Reset currently mapped block
ESP_LOGD(TAG, "- Unmapped successfully");
} else {
ESP_LOGD(TAG, "- Nothing to unmap");
}
}
/**
* @brief Free the himem block
*/
void free() {
ESP_LOGD(TAG, "HimemBlock::free() - handle=%p", handle);
if (handle) {
ESP_LOGD(TAG, "- Unmapping before freeing");
unmap();
ESP_LOGD(TAG, "- Freeing HIMEM handle %p", handle);
esp_himem_free(handle);
handle = 0;
size = 0;
ESP_LOGD(TAG, " - Successfully freed HIMEM block");
} else {
ESP_LOGD(TAG, "- Nothing to free");
}
}
/**
* @brief Get the size of the allocated block
* @return Size of the allocated block in bytes
*/
size_t get_size() const {
ESP_LOGD(TAG, "HimemBlock::get_size() = %u", size);
return size;
}
/**
* @brief Destructor - ensures memory is properly freed
*/
~HimemBlock() {
ESP_LOGD(TAG, "HimemBlock destructor called");
free();
}
/**
* @brief Copy constructor (deleted)
*/
HimemBlock(const HimemBlock&) = delete;
/**
* @brief Move constructor
*/
HimemBlock(HimemBlock&& other) noexcept
: handle(other.handle),
range(other.range),
mapped_ptr(other.mapped_ptr),
size(other.size),
current_mapped_block(other.current_mapped_block) {
ESP_LOGD(TAG, "HimemBlock move constructor - moving handle=%p, size=%u",
other.handle, other.size);
other.handle = 0;
other.range = 0;
other.mapped_ptr = nullptr;
other.size = 0;
other.current_mapped_block = SIZE_MAX;
}
/**
* @brief Copy assignment operator (deleted)
*/
HimemBlock& operator=(const HimemBlock&) = delete;
/**
* @brief Move assignment operator
*/
HimemBlock& operator=(HimemBlock&& other) noexcept {
ESP_LOGD(TAG, "HimemBlock move assignment - from handle=%p to handle=%p",
other.handle, handle);
if (this != &other) {
ESP_LOGD(TAG, "Freeing current resources before move assignment");
free();
handle = other.handle;
range = other.range;
mapped_ptr = other.mapped_ptr;
size = other.size;
current_mapped_block = other.current_mapped_block;
ESP_LOGD(TAG, "Moved resources, new size=%u", size);
other.handle = 0;
other.range = 0;
other.mapped_ptr = nullptr;
other.size = 0;
other.current_mapped_block = SIZE_MAX;
} else {
ESP_LOGD(TAG, "Self-assignment detected, no action taken");
}
return *this;
}
protected:
esp_himem_handle_t handle = 0;
esp_himem_rangehandle_t range = 0;
void* mapped_ptr = nullptr;
size_t size = 0;
size_t current_mapped_block =
SIZE_MAX; // Track which block is currently mapped
/**
* @brief Ensure a specific block is mapped into memory
* @param block_index The index of the block to map
* @return true if mapping successful, false otherwise
*/
bool ensure_block_mapped(size_t block_index) {
// If the requested block is already mapped, we're done
if (block_index == current_mapped_block) {
return true;
}
// Unmap previous block if any
if (mapped_ptr) {
ESP_LOGD(TAG, "- Unmapping block %u before mapping new block %u",
current_mapped_block, block_index);
unmap(); // Unmap previous block
}
// Allocate map range
ESP_LOGD(TAG, "- Allocating map range for block %u", block_index);
esp_err_t err = esp_himem_alloc_map_range(ESP_HIMEM_BLKSZ, &range);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to allocate map range: %d", err);
return false;
}
// Map the current block
ESP_LOGD(TAG, "- Mapping block %u (offset %u)", block_index,
block_index * ESP_HIMEM_BLKSZ);
err = esp_himem_map(handle, range, block_index * ESP_HIMEM_BLKSZ, 0,
ESP_HIMEM_BLKSZ, ESP_HIMEM_PROT_RW, &mapped_ptr);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to map memory: %d", err);
esp_himem_free_map_range(range);
range = 0;
return false;
}
current_mapped_block = block_index;
return true;
}
};
} // namespace esp32_psram

View File

@@ -0,0 +1,251 @@
#pragma once
#include <Arduino.h>
#include <map>
#include "InMemoryFile.h"
// Define Arduino file mode constants if not already defined
#ifndef FILE_READ
#define FILE_READ 0
#endif
#ifndef FILE_WRITE
#define FILE_WRITE 1
#endif
#ifndef FILE_APPEND
#define FILE_APPEND 2
#endif
namespace esp32_psram {
/**
* @class InMemoryFS
* @brief Base class for memory-based file systems like PSRAM and HIMEM
*
* This class provides a common interface for in-memory file systems,
* with methods for file management and traversal.
*
* @tparam VectorType The vector implementation to use (VectorPSRAM or
* VectorHIMEM)
* @tparam FileType The file implementation to return (FilePSRAM or FileHIMEM)
*/
template <typename VectorType, typename FileType>
class InMemoryFS {
public:
/**
* @brief Initialize the file system
* @return true if initialization was successful, false otherwise
*/
virtual bool begin() = 0;
/**
* @brief Check if a file exists
* @param filename Name of the file to check
* @return true if the file exists, false otherwise
*/
bool exists(const char* filename) {
if (!initialized) return false;
return fileData.find(std::string(filename)) != fileData.end();
}
/**
* @brief Open a file
* @param filename Name of the file to open
* @param mode Mode to open the file in (FILE_READ, FILE_WRITE, etc.)
* @return A file object for the opened file
*/
FileType open(const char* filename, uint8_t mode) {
ESP_LOGD("InMemoryFS", "Opening file %s with mode %d", filename, mode);
if (!initialized) {
ESP_LOGW("InMemoryFS", "Filesystem not initialized");
FileType emptyFile;
return emptyFile;
}
// Convert Arduino file modes to our enum
FileMode fileMode;
if (mode == FILE_READ) {
fileMode = FileMode::READ;
} else if (mode == FILE_WRITE) {
fileMode = FileMode::WRITE;
} else if (mode == FILE_APPEND) {
fileMode = FileMode::APPEND;
} else {
fileMode = FileMode::READ_WRITE;
}
std::string filenameStr(filename);
auto it = fileData.find(filenameStr);
FileType file;
if (it != fileData.end()) {
// File exists, create a new file pointing to existing data
ESP_LOGD("InMemoryFS", "File exists, connecting to existing data");
file.setVector(&(it->second));
} else if (mode != FILE_READ) {
// File doesn't exist, create it for writing or appending
ESP_LOGD("InMemoryFS", "Creating new file for writing");
fileData[filenameStr] = VectorType(); // Create empty vector in the map
file.setVector(&(fileData[filenameStr]));
} else {
// File doesn't exist and mode is READ
ESP_LOGW("InMemoryFS", "File doesn't exist and mode is READ");
return file; // Return empty file
}
// Configure the file
file.setName(filename);
file.open(fileMode);
// Set up the next file callback
file.setNextFileCallback(
[this](const char* currentFileName, FileMode currentMode) -> FileType {
ESP_LOGD("InMemoryFS", "NextFile callback called for %s",
currentFileName);
// Get the next filename
String nextName = this->getNextFileName(currentFileName);
if (nextName.isEmpty()) {
ESP_LOGD("InMemoryFS", "No next file found");
return FileType(); // Return empty file if no next file
}
// Convert FileMode to Arduino mode
uint8_t arduinoMode;
if (currentMode == FileMode::READ)
arduinoMode = FILE_READ;
else if (currentMode == FileMode::WRITE)
arduinoMode = FILE_WRITE;
else if (currentMode == FileMode::APPEND)
arduinoMode = FILE_APPEND;
else
arduinoMode = FILE_READ | FILE_WRITE;
// Open and return the next file
ESP_LOGD("InMemoryFS", "Returning next file: %s", nextName.c_str());
return this->open(nextName.c_str(), arduinoMode);
});
ESP_LOGD("InMemoryFS", "File opened successfully");
return file;
}
/**
* @brief Remove a file
* @param filename Name of the file to remove
* @return true if the file was removed, false otherwise
*/
bool remove(const char* filename) {
if (!initialized) return false;
std::string filenameStr(filename);
auto it = fileData.find(filenameStr);
if (it != fileData.end()) {
fileData.erase(it);
return true;
}
return false;
}
/**
* @brief Create a directory (no-op for compatibility)
* @param dirname Name of the directory
* @return Always returns true for compatibility
*/
bool mkdir(const char* dirname) {
// No directories in this implementation, just for compatibility
return true;
}
/**
* @brief Remove a directory (no-op for compatibility)
* @param dirname Name of the directory
* @return Always returns true for compatibility
*/
bool rmdir(const char* dirname) {
// No directories in this implementation, just for compatibility
return true;
}
/**
* @brief Get the name of the next file after the specified file
* @param currentFileName Name of the current file
* @return Name of the next file, or empty string if there are no more files
*/
String getNextFileName(const char* currentFileName) {
if (!initialized || fileData.empty()) {
return String();
}
if (currentFileName == nullptr || strlen(currentFileName) == 0 ||
strcmp(currentFileName, "/") == 0) {
// Return the first file if current is empty or is root directory
return String(fileData.begin()->first.c_str());
}
std::string current(currentFileName);
auto it = fileData.find(current);
if (it == fileData.end()) {
// Current file not found, find the next one alphabetically
for (auto& entry : fileData) {
if (entry.first > current) {
return String(entry.first.c_str());
}
}
// No file after the specified name
return String();
} else {
// Found the current file, get the next one
++it;
if (it != fileData.end()) {
return String(it->first.c_str());
} else {
// Was the last file
return String();
}
}
}
/**
* @brief Get the first file in the filesystem
* @return Name of the first file, or empty string if there are no files
*/
String getFirstFileName() {
if (!initialized || fileData.empty()) {
return String();
}
return String(fileData.begin()->first.c_str());
}
/**
* @brief Get the total number of files
* @return Number of files in the filesystem
*/
size_t fileCount() const {
if (!initialized) return 0;
return fileData.size();
}
/**
* @brief Get total space
* @return Total memory size in bytes
*/
virtual uint64_t totalBytes() = 0;
/**
* @brief Get free space
* @return Free memory size in bytes
*/
virtual uint64_t freeBytes() = 0;
protected:
bool initialized = false;
std::map<std::string, VectorType> fileData;
};
} // namespace esp32_psram

View File

@@ -0,0 +1,421 @@
#pragma once
#include <Arduino.h>
#include "VectorHIMEM.h"
#include "VectorPSRAM.h"
namespace esp32_psram {
// Define file modes
enum class FileMode { READ, WRITE, APPEND, READ_WRITE };
/**
* @class InMemoryFile
* @brief A file-like interface for vector-backed storage in memory
*
* This class provides a file-like interface (compatible with Arduino's Stream)
* for reading and writing data to vector-backed storage. It can use either
* standard PSRAM or ESP32's HIMEM (high memory beyond the 4MB boundary)
* as its underlying storage mechanism through the VectorPSRAM and VectorHIMEM
* implementations.
*
* InMemoryFile offers familiar file operations like open, close, read, write,
* seek, and truncate while managing the data in memory rather than on disk.
* This makes it useful for temporary storage, data processing, or situations
* where file operations are needed but filesystem access is not available
* or desirable.
*
* The class can either manage its own internal vector or reference an external
* vector supplied by another component (like PSRAMClass or HIMEMClass).
*
* @tparam VectorType The vector implementation to use for storage (typically
* VectorPSRAM<uint8_t> or VectorHIMEM<uint8_t>)
*
* @note The performance characteristics will depend on the underlying vector
* implementation. HIMEM operations are generally slower than regular
* PSRAM operations but allow for larger storage capacity.
*/
template <typename VectorType>
class InMemoryFile : public Stream {
public:
// Define a single callback type that returns the next file directly
using NextFileCallback =
std::function<InMemoryFile<VectorType>(const char*, FileMode)>;
/**
* @brief Default constructor - initializes with internal vector
*/
InMemoryFile() : data_ptr(&internal_data) {
ESP_LOGD(TAG, "InMemoryFile constructor called");
}
/**
* @brief Constructor with filename and mode
* @param filename Name of the file
* @param mode Mode to open the file in
*/
InMemoryFile(const char* filename, FileMode mode)
: data_ptr(&internal_data), name_(filename) {
ESP_LOGD(TAG, "InMemoryFile constructor with name and mode called");
open(mode);
}
/**
* @brief Destructor - ensures proper cleanup
*/
virtual ~InMemoryFile() {
ESP_LOGD(TAG, "InMemoryFile destructor called");
close();
if (!using_external_vector) {
data_ptr = nullptr;
}
}
/**
* @brief Set the vector to use for this file
* @param vec Pointer to the vector to use
*/
void setVector(VectorType* vec) {
ESP_LOGD(TAG, "Setting external vector pointer: %p", vec);
if (vec) {
data_ptr = vec;
using_external_vector = true;
} else {
data_ptr = &internal_data;
using_external_vector = false;
}
}
/**
* @brief Set the name of this file
* @param name The name to set
*/
void setName(const char* name) {
ESP_LOGD(TAG, "Setting file name: %s", name);
name_ = name;
}
/**
* @brief Open the file with the specified mode
* @param mode Mode to open the file in
* @return true if opened successfully, false otherwise
*/
bool open(FileMode mode) {
ESP_LOGD(TAG, "Opening file '%s' with mode %d", name_.c_str(),
static_cast<int>(mode));
this->mode = mode;
if (mode == FileMode::WRITE) {
// Clear the vector for writing
data_ptr->clear();
position_ = 0;
} else if (mode == FileMode::APPEND) {
// Position at end for appending
position_ = data_ptr->size();
} else {
// READ or READ_WRITE - position at beginning
position_ = 0;
}
open_ = true;
return true;
}
/**
* @brief Close the file
*/
void close() {
ESP_LOGD(TAG, "Closing file '%s'", name_.c_str());
open_ = false;
}
/**
* @brief Check if file is open
* @return true if open, false otherwise
*/
bool isOpen() const { return open_; }
/**
* @brief Get the name of the file
* @return File name
*/
String name() const { return name_; }
/**
* @brief Get the size of the file
* @return File size in bytes
*/
size_t size() const { return data_ptr->size(); }
// Stream interface implementation
/**
* @brief Read a single byte from the file
* @return The next byte, or -1 if no data is available
*/
int read() override {
ESP_LOGD(TAG, "InMemoryFile::read() - position %u", (unsigned)position_);
if (!open_ || (mode != FileMode::READ && mode != FileMode::READ_WRITE)) {
ESP_LOGE(TAG, "read failed: file not open for reading");
return -1;
}
if (position_ >= data_ptr->size()) {
ESP_LOGD(TAG, "read at EOF: pos=%u, size=%u", (unsigned)position_,
(unsigned)data_ptr->size());
return -1; // EOF
}
uint8_t byte = (*data_ptr)[position_++];
return byte;
}
/**
* @brief Read multiple bytes from the file
* @param buffer Buffer to store the read data
* @param size Maximum number of bytes to read
* @return Number of bytes actually read
*/
size_t readBytes(char* buffer, size_t size) override {
ESP_LOGD(TAG, "InMemoryFile::readBytes: %u", (unsigned)size);
if (!open_ || (mode != FileMode::READ && mode != FileMode::READ_WRITE)) {
ESP_LOGE(TAG, "read failed: file not open for reading");
return 0;
}
// Calculate how many bytes we can actually read
size_t available_bytes = data_ptr->size() - position_;
size_t bytes_to_read = min(size, available_bytes);
// Read the bytes
for (size_t i = 0; i < bytes_to_read; i++) {
buffer[i] = (*data_ptr)[position_ + i];
}
position_ += bytes_to_read;
return bytes_to_read;
}
/**
* @brief Get the number of bytes available to read
* @return Number of bytes available
*/
int available() override {
if (!open_) return 0;
return data_ptr->size() - position_;
}
/**
* @brief Peek at the next byte without advancing the position
* @return The next byte, or -1 if no data is available
*/
int peek() override {
if (!open_ || (mode != FileMode::READ && mode != FileMode::READ_WRITE)) {
return -1;
}
if (position_ >= data_ptr->size()) {
return -1; // EOF
}
return (*data_ptr)[position_];
}
/**
* @brief Ensure all data is committed to the storage
* No-op for this implementation since data is in memory
*/
void flush() override {
// No-op for in-memory implementation
}
/**
* @brief Write a single byte to the file
* @param b The byte to write
* @return 1 if the byte was written, 0 otherwise
*/
size_t write(uint8_t b) override {
ESP_LOGD(TAG, "InMemoryFile::write: 1 byte");
if (!open_ || (mode != FileMode::WRITE && mode != FileMode::APPEND &&
mode != FileMode::READ_WRITE)) {
ESP_LOGE(TAG, "write failed: file not open for writing");
return 0;
}
if (position_ >= data_ptr->size()) {
// Append to the end
data_ptr->push_back(b);
} else {
// Replace existing byte
(*data_ptr)[position_] = b;
}
position_++;
return 1;
}
/**
* @brief Write multiple bytes to the file
* @param buffer Buffer containing the data to write
* @param size Number of bytes to write
* @return Number of bytes actually written
*/
size_t write(const uint8_t* buffer, size_t size) override {
ESP_LOGD(TAG, "InMemoryFile::write: %u", (unsigned)size);
if (!open_ || (mode != FileMode::WRITE && mode != FileMode::APPEND &&
mode != FileMode::READ_WRITE)) {
ESP_LOGE(TAG, "write failed: file not open for writing");
return 0;
}
if (position_ >= data_ptr->size()) {
// Append to the end
data_ptr->reserve(data_ptr->size() + size); // Optimize capacity
for (size_t i = 0; i < size; i++) {
data_ptr->push_back(buffer[i]);
}
} else {
// Replace/insert bytes
size_t space_available = data_ptr->size() - position_;
size_t bytes_to_replace = min(size, space_available);
size_t bytes_to_append = size - bytes_to_replace;
// Replace existing bytes
for (size_t i = 0; i < bytes_to_replace; i++) {
(*data_ptr)[position_ + i] = buffer[i];
}
// Append remaining bytes
for (size_t i = 0; i < bytes_to_append; i++) {
data_ptr->push_back(buffer[bytes_to_replace + i]);
}
}
position_ += size;
return size;
}
// File-specific methods
/**
* @brief Set the current position in the file
* @param pos The position to seek to
* @return true if successful, false otherwise
*/
bool seek(size_t pos) {
ESP_LOGD(TAG, "InMemoryFile::seek: %u", (unsigned)pos);
if (!open_ || pos > data_ptr->size()) {
ESP_LOGE(TAG, "seek failed: file not open or position beyond size");
return false;
}
position_ = pos;
return true;
}
/**
* @brief Get the current position in the file
* @return Current position
*/
size_t position() const { return position_; }
/**
* @brief Truncate the file to the current position
*/
void truncate() {
ESP_LOGD(TAG, "InMemoryFile::truncate at position %u", (unsigned)position_);
if (!open_ || (mode != FileMode::WRITE && mode != FileMode::READ_WRITE)) {
ESP_LOGE(TAG, "truncate failed: file not open for writing");
return;
}
if (position_ < data_ptr->size()) {
// Create a new vector with just the data up to position_
VectorType new_data;
new_data.reserve(position_);
for (size_t i = 0; i < position_; i++) {
new_data.push_back((*data_ptr)[i]);
}
// Replace the old data
*data_ptr = std::move(new_data);
}
}
operator bool() const { return open_; }
/**
* @brief Set callback for navigating to the next file
*
* This function sets a callback that will be used to retrieve the next
* file when getNextFile() is called. The callback takes the current file name
* and mode, and returns the next file object directly.
*
* @param callback Function that returns the next file
*/
void setNextFileCallback(NextFileCallback callback) {
ESP_LOGD(TAG, "Setting next file callback");
nextFileCallback = callback;
}
/**
* @brief Get the next file in the filesystem
*
* This method uses the registered callback to get the next file
* after this one in the filesystem.
*
* @return The next file, or an empty file if there is no next file or
* callback isn't set
*/
InMemoryFile<VectorType> getNextFile() {
if (!nextFileCallback || name_.isEmpty()) {
// Return empty file if callback isn't set or no name
InMemoryFile<VectorType> emptyFile;
return emptyFile;
}
// Get the next file directly using the callback
return nextFileCallback(name_.c_str(), mode);
}
/**
* @brief Reserve storage
* @param new_cap The new capacity of the file
*/
bool reserve(size_t new_cap) {
if (data_ptr == nullptr) return false;
data_ptr->reserve(new_cap);
return true;
}
/**
* @brief Get the number of bytes that can be held in current storage.
* Please note that this might dynamically grow!
* @return The capacity of the file
*/
size_t capacity() const {
if (data_ptr == nullptr) return 0;
return data_ptr->capacity();
}
private:
VectorType* data_ptr =
nullptr; // Pointer to vector data (internal or external)
VectorType internal_data; // Internal vector for standalone use
bool using_external_vector = false;
size_t position_ = 0;
bool open_ = false;
FileMode mode = FileMode::READ;
String name_;
// Single callback for getting the next file
NextFileCallback nextFileCallback = nullptr;
// Tags for debug logging
static constexpr const char* TAG = "InMemoryFile";
};
// Type aliases for convenience
using FilePSRAM = InMemoryFile<VectorPSRAM<uint8_t>>;
using FileHIMEM = InMemoryFile<VectorHIMEM<uint8_t>>;
} // namespace esp32_psram

View File

@@ -0,0 +1,54 @@
#pragma once
#include <Arduino.h>
#include "InMemoryFS.h"
#include "VectorPSRAM.h"
namespace esp32_psram {
/**
* @class PSRAMClass
* @brief Class for managing files stored in ESP32's PSRAM
*
* This class provides an interface similar to SD.h for managing files
* that are stored in PSRAM memory rather than on an SD card.
*/
class PSRAMClass : public InMemoryFS<VectorPSRAM<uint8_t>, FilePSRAM> {
public:
/**
* @brief Initialize the PSRAM filesystem
* @return true if initialization was successful, false otherwise
*/
bool begin() override {
if (ESP.getFreePsram() > 0) {
initialized = true;
return true;
}
initialized = false;
return false;
}
/**
* @brief Get total space (returns available PSRAM)
* @return Total PSRAM size in bytes
*/
uint64_t totalBytes() override {
return ESP.getPsramSize();
}
/**
* @brief Get free space (returns free PSRAM)
* @return Free PSRAM size in bytes
*/
uint64_t freeBytes() override {
return ESP.getFreePsram();
}
};
/**
* @brief Global instance of PSRAMClass for easy access
*/
static PSRAMClass PSRAM;
} // namespace esp32_psram

View File

@@ -0,0 +1,250 @@
#pragma once
#include <Arduino.h>
#include <Stream.h>
#include "VectorPSRAM.h"
#include "VectorHIMEM.h"
namespace esp32_psram {
/**
* @class RingBufferStream
* @brief A circular buffer implementation that extends Arduino's Stream
* @tparam VectorType The vector type to use as underlying storage
*
* This class implements a ring buffer (circular buffer) that uses a vector container
* for storage. It extends Arduino's Stream class to provide standard stream functionality.
* The buffer can be used with any vector type, including VectorPSRAM and VectorHIMEM.
*/
template <typename VectorType>
class RingBufferStream : public Stream {
private:
VectorType buffer;
size_t readIndex = 0;
size_t writeIndex = 0;
bool full = false;
size_t maxSize;
public:
/**
* @brief Constructor with specified buffer size
* @param size The size of the buffer in bytes
*/
RingBufferStream(size_t size) : maxSize(size) {
buffer.resize(size);
}
/**
* @brief Get the number of bytes available for reading
* @return Number of bytes available
*/
int available() override {
if (full) {
return maxSize;
}
if (writeIndex >= readIndex) {
return writeIndex - readIndex;
} else {
return maxSize - (readIndex - writeIndex);
}
}
/**
* @brief Read a byte from the buffer
* @return The byte read, or -1 if the buffer is empty
*/
int read() override {
if (isEmpty()) {
return -1;
}
uint8_t value = buffer[readIndex];
readIndex = (readIndex + 1) % maxSize;
full = false;
return value;
}
/**
* @brief Look at the next byte in the buffer without removing it
* @return The next byte to be read, or -1 if the buffer is empty
*/
int peek() override {
if (isEmpty()) {
return -1;
}
return buffer[readIndex];
}
/**
* @brief Write a byte to the buffer
* @param value The byte to write
* @return 1 if the byte was written, 0 if the buffer is full
*/
size_t write(uint8_t value) override {
if (full) {
return 0;
}
buffer[writeIndex] = value;
writeIndex = (writeIndex + 1) % maxSize;
// Check if buffer is now full
if (writeIndex == readIndex) {
full = true;
}
return 1;
}
/**
* @brief Write multiple bytes to the buffer
* @param data Pointer to the data to write
* @param size Number of bytes to write
* @return Number of bytes written
*/
size_t write(const uint8_t *data, size_t size) {
size_t bytesWritten = 0;
for (size_t i = 0; i < size; i++) {
if (write(data[i])) {
bytesWritten++;
} else {
break;
}
}
return bytesWritten;
}
/**
* @brief Clear the buffer, removing all content
*/
void flush() override {
readIndex = 0;
writeIndex = 0;
full = false;
}
/**
* @brief Check if the buffer is empty
* @return true if the buffer is empty, false otherwise
*/
bool isEmpty() const {
return !full && (readIndex == writeIndex);
}
/**
* @brief Check if the buffer is full
* @return true if the buffer is full, false otherwise
*/
bool isFull() const {
return full;
}
/**
* @brief Get the number of bytes that can be written without blocking
* @return Number of bytes available for writing
*/
int availableForWrite() override {
if (full) {
return 0;
}
if (readIndex > writeIndex) {
return readIndex - writeIndex;
} else {
return maxSize - (writeIndex - readIndex);
}
}
/**
* @brief Get the total capacity of the buffer
* @return Total buffer capacity in bytes
*/
size_t size() const {
return maxSize;
}
/**
* @brief Get the number of bytes currently in the buffer
* @return Number of bytes in the buffer
*/
size_t used() const {
return available();
}
/**
* @brief Get the number of bytes still available in the buffer
* @return Number of free bytes in the buffer
*/
size_t free() const {
return maxSize - available();
}
/**
* @brief Read multiple bytes from the buffer
* @param buffer Buffer to store the read data
* @param size Maximum number of bytes to read
* @return Number of bytes actually read
*/
size_t readBytes(char* buffer, size_t size) override {
size_t bytesRead = 0;
for (size_t i = 0; i < size; i++) {
int value = read();
if (value >= 0) {
buffer[i] = (char)value;
bytesRead++;
} else {
break;
}
}
return bytesRead;
}
/**
* @brief Read multiple bytes from the buffer
* @param buffer Buffer to store the read data
* @param size Maximum number of bytes to read
* @return Number of bytes actually read
*/
size_t readBytes(uint8_t* buffer, size_t size) {
return readBytes(reinterpret_cast<char*>(buffer), size);
}
/**
* @brief Get direct access to the underlying vector
* @return Reference to the underlying vector
*/
VectorType& getVector() {
return buffer;
}
/**
* @brief Get const access to the underlying vector
* @return Const reference to the underlying vector
*/
const VectorType& getVector() const {
return buffer;
}
};
/**
* @brief Type alias for a RingBufferStream that uses PSRAM-backed vector storage
*/
using RingBufferStreamPSRAM = RingBufferStream<VectorPSRAM<uint8_t>>;
/**
* @brief Type alias for a RingBufferStream that uses HIMEM-backed vector storage
*/
using RingBufferStreamHIMEM = RingBufferStream<VectorHIMEM<uint8_t>>;
/**
* @brief Type alias for a RingBufferStream that uses std::vector storage
*/
using RingBufferStreamRAM = RingBufferStream<std::vector<uint8_t>>;
} // namespace esp32_psram

View File

@@ -0,0 +1,227 @@
#pragma once
#include <Arduino.h>
#include <vector>
#include "VectorPSRAM.h"
#include "VectorHIMEM.h"
namespace esp32_psram {
/**
* @class TypedRingBuffer
* @brief A generic ring buffer implementation for any data type
* @tparam T The data type to store in the buffer
* @tparam VectorType The vector type to use as underlying storage
*
* This class implements a ring buffer (circular buffer) that uses a vector container
* for storage. Unlike the Stream-based RingBuffer, this can store any data type.
*/
template <typename T, typename VectorType>
class TypedRingBuffer {
private:
VectorType buffer;
size_t readIndex = 0;
size_t writeIndex = 0;
bool full = false;
size_t maxSize;
public:
/**
* @brief Constructor with specified buffer capacity
* @param capacity The maximum number of elements the buffer can hold
*/
TypedRingBuffer(size_t capacity) : maxSize(capacity) {
buffer.resize(capacity);
}
/**
* @brief Push an element to the buffer
* @param value The value to add
* @return true if the element was added, false if the buffer is full
*/
bool push(const T& value) {
if (full) {
return false;
}
buffer[writeIndex] = value;
advanceWriteIndex();
return true;
}
/**
* @brief Push an element to the buffer, overwriting oldest data if full
* @param value The value to add
* @return true if an old element was overwritten, false otherwise
*/
bool pushOverwrite(const T& value) {
bool overwritten = full;
buffer[writeIndex] = value;
advanceWriteIndex();
return overwritten;
}
/**
* @brief Pop an element from the buffer
* @param value Reference to store the popped value
* @return true if an element was popped, false if the buffer is empty
*/
bool pop(T& value) {
if (isEmpty()) {
return false;
}
value = buffer[readIndex];
advanceReadIndex();
return true;
}
/**
* @brief Peek at the next element without removing it
* @param value Reference to store the peeked value
* @return true if an element was peeked, false if the buffer is empty
*/
bool peek(T& value) const {
if (isEmpty()) {
return false;
}
value = buffer[readIndex];
return true;
}
/**
* @brief Get the element at a specific index relative to the read position
* @param index The relative index from current read position
* @param value Reference to store the value
* @return true if the element exists, false otherwise
*/
bool peekAt(size_t index, T& value) const {
if (isEmpty() || index >= available()) {
return false;
}
size_t actualIndex = (readIndex + index) % maxSize;
value = buffer[actualIndex];
return true;
}
/**
* @brief Clear the buffer, removing all content
*/
void clear() {
readIndex = 0;
writeIndex = 0;
full = false;
}
/**
* @brief Check if the buffer is empty
* @return true if the buffer is empty, false otherwise
*/
bool isEmpty() const {
return !full && (readIndex == writeIndex);
}
/**
* @brief Check if the buffer is full
* @return true if the buffer is full, false otherwise
*/
bool isFull() const {
return full;
}
/**
* @brief Get the number of elements in the buffer
* @return Number of elements in the buffer
*/
size_t available() const {
if (full) {
return maxSize;
}
if (writeIndex >= readIndex) {
return writeIndex - readIndex;
} else {
return maxSize - (readIndex - writeIndex);
}
}
/**
* @brief Get the number of empty slots in the buffer
* @return Number of empty slots
*/
size_t availableForWrite() const {
return maxSize - available();
}
/**
* @brief Get the total capacity of the buffer
* @return Total buffer capacity
*/
size_t capacity() const {
return maxSize;
}
/**
* @brief Get direct access to the underlying vector
* @return Reference to the underlying vector
*/
VectorType& getVector() {
return buffer;
}
/**
* @brief Get const access to the underlying vector
* @return Const reference to the underlying vector
*/
const VectorType& getVector() const {
return buffer;
}
private:
/**
* @brief Advance the write index
*/
void advanceWriteIndex() {
writeIndex = (writeIndex + 1) % maxSize;
if (writeIndex == readIndex) {
full = true;
}
}
/**
* @brief Advance the read index
*/
void advanceReadIndex() {
readIndex = (readIndex + 1) % maxSize;
full = false;
}
};
/**
* @brief Type alias for a typed ring buffer that uses std::vector (in RAM)
*/
template<typename T>
using TypedRingBufferRAM = TypedRingBuffer<T, std::vector<T>>;
/**
* @brief Type alias for a typed ring buffer that uses HIMEM-backed vector storage
*/
template<typename T>
using TypedRingBufferHIMEM = TypedRingBuffer<T, VectorHIMEM<T>>;
/**
* @brief Type alias for a typed ring buffer that uses PSRAM-backed vector storage
*/
template<typename T>
using TypedRingBufferPSRAM = TypedRingBuffer<T, VectorPSRAM<T>>;
/**
* @brief Type alias for a typed ring buffer that uses HIMEM-backed vector storage
*/
// template<typename T>
// using TypedRingBufferHIMEM = TypedRingBuffer<T, VectorHIMEM<T>>;
} // namespace esp32_psram

View File

@@ -0,0 +1,529 @@
#pragma once
#include "HimemBlock.h"
namespace esp32_psram {
/**
* @class VectorHIMEM
* @brief Vector implementation that uses ESP32's high memory (himem) for
* storage
* @tparam T Type of elements stored in the vector
*/
template <typename T>
class VectorHIMEM {
public:
// Type definitions
using value_type = T;
using reference = T&;
using const_reference = const T&;
using pointer = T*;
using const_pointer = const T*;
using size_type = size_t;
using difference_type = std::ptrdiff_t;
/**
* @brief Default constructor - creates an empty vector
*/
VectorHIMEM() = default;
/**
* @brief Constructs a vector with the given number of default-initialized
* elements
* @param count The size of the vector
*/
explicit VectorHIMEM(size_type count) { resize(count); }
/**
* @brief Constructs a vector with the given number of copies of a value
* @param count The size of the vector
* @param value The value to initialize elements with
*/
VectorHIMEM(size_type count, const T& value) { resize(count, value); }
/**
* @brief Copy constructor
* @param other The vector to copy from
*/
VectorHIMEM(const VectorHIMEM& other) {
if (other.element_count > 0) {
if (reallocate(other.element_count)) {
T temp;
VectorHIMEM& other_ = const_cast<VectorHIMEM&>(other);
for (size_t i = 0; i < other.element_count; ++i) {
other_.memory.read((void*)&temp, i * sizeof(T), sizeof(T));
memory.write((void*)&temp, i * sizeof(T), sizeof(T));
}
element_count = other.element_count;
}
}
}
/**
* @brief Move constructor
* @param other The vector to move from
*/
VectorHIMEM(VectorHIMEM&& other) noexcept
: memory(std::move(other.memory)),
element_count(other.element_count),
element_capacity(other.element_capacity) {
other.element_count = 0;
other.element_capacity = 0;
}
/**
* @brief Initializer list constructor
* @param init The initializer list to copy from
*/
VectorHIMEM(std::initializer_list<T> init) {
if (init.size() > 0) {
if (reallocate(init.size())) {
size_t i = 0;
for (const auto& item : init) {
memory.write(&item, i * sizeof(T), sizeof(T));
++i;
}
element_count = init.size();
}
}
}
/**
* @brief Destructor - ensures all elements are properly destroyed
*/
~VectorHIMEM() { clear(); }
/**
* @brief Copy assignment operator
* @param other The vector to copy from
* @return Reference to this vector
*/
VectorHIMEM& operator=(const VectorHIMEM& other) {
if (this != &other) {
clear();
if (other.element_count > 0) {
if (reallocate(other.element_count)) {
T temp;
for (size_t i = 0; i < other.element_count; ++i) {
const_cast<esp32_psram::HimemBlock*>(&other.memory)->read(&temp, i * sizeof(T), sizeof(T));
memory.write(&temp, i * sizeof(T), sizeof(T));
}
element_count = other.element_count;
}
}
}
return *this;
}
/**
* @brief Move assignment operator
* @param other The vector to move from
* @return Reference to this vector
*/
VectorHIMEM& operator=(VectorHIMEM&& other) noexcept {
if (this != &other) {
clear();
memory = std::move(other.memory);
element_count = other.element_count;
element_capacity = other.element_capacity;
other.element_count = 0;
other.element_capacity = 0;
}
return *this;
}
/**
* @brief Initializer list assignment operator
* @param ilist The initializer list to copy from
* @return Reference to this vector
*/
VectorHIMEM& operator=(std::initializer_list<T> ilist) {
clear();
if (ilist.size() > 0) {
if (reallocate(ilist.size())) {
size_t i = 0;
for (const auto& item : ilist) {
memory.write(&item, i * sizeof(T), sizeof(T));
++i;
}
element_count = ilist.size();
}
}
return *this;
}
/**
* @brief Access element with bounds checking
* @param pos The position of the element
* @return Reference to the element at position pos
* @throws std::out_of_range if pos is not within the range of the vector
*/
reference at(size_type pos) {
if (pos >= element_count) {
throw std::out_of_range("VectorHIMEM: index out of range");
}
static T temp;
memory.read(&temp, pos * sizeof(T), sizeof(T));
return temp;
}
/**
* @brief Access element with bounds checking (const version)
* @param pos The position of the element
* @return Const reference to the element at position pos
* @throws std::out_of_range if pos is not within the range of the vector
*/
const_reference at(size_type pos) const {
if (pos >= element_count) {
throw std::out_of_range("VectorHIMEM: index out of range");
}
static T temp;
memory.read(&temp, pos * sizeof(T), sizeof(T));
return temp;
}
/**
* @brief Access element without bounds checking
* @param pos The position of the element
* @return Reference to the element at position pos
*/
reference operator[](size_type pos) {
static T temp;
memory.read(&temp, pos * sizeof(T), sizeof(T));
return temp;
}
// Add const version of operator[]
const_reference operator[](size_type pos) const {
static T result;
// Need to cast away const for the read operation since it doesn't modify content
HimemBlock& non_const_memory = const_cast<HimemBlock&>(memory);
non_const_memory.read(&result, pos * sizeof(T), sizeof(T));
return result;
}
/**
* @brief Access the first element
* @return Reference to the first element
*/
reference front() {
static T temp;
memory.read(&temp, 0, sizeof(T));
return temp;
}
/**
* @brief Access the first element (const version)
* @return Const reference to the first element
*/
const_reference front() const {
static T temp;
memory.read(&temp, 0, sizeof(T));
return temp;
}
/**
* @brief Access the last element
* @return Reference to the last element
*/
reference back() {
static T temp;
memory.read(&temp, (element_count - 1) * sizeof(T), sizeof(T));
return temp;
}
/**
* @brief Access the last element (const version)
* @return Const reference to the last element
*/
const_reference back() const {
static T temp;
memory.read(&temp, (element_count - 1) * sizeof(T), sizeof(T));
return temp;
}
/**
* @brief Check if the vector is empty
* @return true if the vector is empty, false otherwise
*/
bool empty() const { return element_count == 0; }
/**
* @brief Get the number of elements
* @return The number of elements in the vector
*/
size_type size() const { return element_count; }
/**
* @brief Get the maximum possible number of elements
* @return The maximum possible number of elements the vector can hold
*/
size_type max_size() const {
return std::numeric_limits<size_type>::max() / sizeof(T);
}
/**
* @brief Reserve storage
* @param new_cap The new capacity of the vector
*/
void reserve(size_type new_cap) {
if (new_cap > element_capacity) {
reallocate(new_cap);
}
}
/**
* @brief Get the number of elements that can be held in current storage
* @return The capacity of the vector
*/
size_type capacity() const { return element_capacity; }
/**
* @brief Clear the contents
*/
void clear() {
// We're using plain memory so no destructors to call
element_count = 0;
}
/**
* @brief Add an element to the end
* @param value The value to append
*/
void push_back(const T& value) {
if (element_count >= element_capacity) {
size_t new_capacity =
element_capacity == 0 ? min_elements : element_capacity * 2;
if (!reallocate(new_capacity)) {
ESP_LOGE(TAG, "Failed to reallocate for push_back");
return;
}
}
// Write the new element at the end
memory.write(&value, element_count * sizeof(T), sizeof(T));
++element_count;
}
/**
* @brief Add an element to the end by moving it
* @param value The value to append
*/
void push_back(T&& value) {
// For POD types, this is the same as the const& version
push_back(static_cast<const T&>(value));
}
/**
* @brief Remove the last element
*/
void pop_back() {
if (element_count > 0) {
--element_count;
}
}
/**
* @brief Change the number of elements stored
* @param count The new size of the vector
*/
void resize(size_type count) {
if (count > element_capacity) {
if (!reallocate(count)) {
return;
}
}
element_count = count;
}
/**
* @brief Change the number of elements stored
* @param count The new size of the vector
* @param value The value to initialize new elements with
*/
void resize(size_type count, const value_type& value) {
size_t old_size = element_count;
if (count > element_count) {
// Need to add elements
if (count > element_capacity) {
if (!reallocate(count)) {
return;
}
}
// Initialize new elements with value
for (size_t i = old_size; i < count; ++i) {
memory.write(&value, i * sizeof(T), sizeof(T));
}
}
element_count = count;
}
/**
* @brief Erase an element
* @param pos Index of the element to erase
*/
void erase(size_type pos) {
if (pos >= element_count) {
return;
}
// Move each element down by one, overwriting the erased element
T temp;
for (size_t i = pos + 1; i < element_count; ++i) {
memory.read(&temp, i * sizeof(T), sizeof(T));
memory.write(&temp, (i - 1) * sizeof(T), sizeof(T));
}
--element_count;
}
/**
* @brief Insert an element at a specific position
* @param pos Position where the element should be inserted
* @param value The value to insert
*/
void insert(size_type pos, const T& value) {
if (pos > element_count) {
return;
}
if (element_count >= element_capacity) {
size_t new_capacity =
element_capacity == 0 ? min_elements : element_capacity * 2;
if (!reallocate(new_capacity)) {
return;
}
}
// Move elements up to make space
T temp;
for (size_t i = element_count; i > pos; --i) {
memory.read(&temp, (i - 1) * sizeof(T), sizeof(T));
memory.write(&temp, i * sizeof(T), sizeof(T));
}
// Insert new element
memory.write(&value, pos * sizeof(T), sizeof(T));
++element_count;
}
/**
* @brief Swap the contents of this vector with another
* @param other Vector to swap with
*/
void swap(VectorHIMEM& other) noexcept {
std::swap(memory, other.memory);
std::swap(element_count, other.element_count);
std::swap(element_capacity, other.element_capacity);
}
protected:
HimemBlock memory;
size_t element_count = 0;
size_t element_capacity = 0;
static constexpr size_t min_elements = 16; // Minimum allocation size
/**
* @brief Calculate required memory size in bytes for a given number of
* elements
* @param count Number of elements
* @return Size in bytes
*/
static size_t calculate_size_bytes(size_t count) { return count * sizeof(T); }
/**
* @brief Reallocate memory with a new capacity
* @param new_capacity The new capacity to allocate
* @return true if reallocation was successful, false otherwise
*/
bool reallocate(size_t new_capacity) {
if (new_capacity <= element_capacity) {
return true; // No need to reallocate
}
// Calculate new size (at least min_elements)
new_capacity = std::max(new_capacity, min_elements);
// Create a new memory block
HimemBlock new_memory;
element_capacity = new_memory.allocate(calculate_size_bytes(new_capacity)) / sizeof(T);
if (element_capacity == 0) {
ESP_LOGE(TAG, "Failed to allocate new memory block");
return false;
}
// Copy existing elements if any
if (element_count > 0) {
T temp;
for (size_t i = 0; i < element_count; ++i) {
// Read from old memory
memory.read(&temp, i * sizeof(T), sizeof(T));
// Write to new memory
new_memory.write(&temp, i * sizeof(T), sizeof(T));
}
}
// Swap the memory blocks
memory = std::move(new_memory);
return true;
}
};
/**
* @brief Equality comparison operator
* @tparam T Type of elements in the vectors
* @param lhs First vector
* @param rhs Second vector
* @return true if the vectors are equal, false otherwise
*/
template <typename T>
bool operator==(const VectorHIMEM<T>& lhs, const VectorHIMEM<T>& rhs) {
if (lhs.size() != rhs.size()) {
return false;
}
T lhs_val, rhs_val;
for (size_t i = 0; i < lhs.size(); ++i) {
lhs_val = lhs[i]; // Uses operator[] which calls read()
rhs_val = rhs[i];
if (!(lhs_val == rhs_val)) {
return false;
}
}
return true;
}
/**
* @brief Inequality comparison operator
* @tparam T Type of elements in the vectors
* @param lhs First vector
* @param rhs Second vector
* @return true if the vectors are not equal, false otherwise
*/
template <typename T>
bool operator!=(const VectorHIMEM<T>& lhs, const VectorHIMEM<T>& rhs) {
return !(lhs == rhs);
}
/**
* @brief Swap the contents of two vectors
* @tparam T Type of elements in the vectors
* @param lhs First vector
* @param rhs Second vector
*/
template <typename T>
void swap(VectorHIMEM<T>& lhs, VectorHIMEM<T>& rhs) noexcept {
lhs.swap(rhs);
}
} // namespace esp32_psram

View File

@@ -0,0 +1,521 @@
#pragma once
#include <vector>
#include <memory>
#include <limits>
#include <algorithm>
#include "AllocatorPSRAM.h"
/**
* @namespace esp32_psram
* @brief Namespace containing ESP32 PSRAM-specific implementations
*/
namespace esp32_psram {
/**
* @brief Equality comparison operator for allocators
* @tparam T Type of first allocator
* @tparam U Type of second allocator
* @return Always true since all instances are equivalent
*/
template <typename T, typename U>
bool operator==(const AllocatorPSRAM<T>&, const AllocatorPSRAM<U>&) noexcept {
return true;
}
/**
* @brief Inequality comparison operator for allocators
* @tparam T Type of first allocator
* @tparam U Type of second allocator
* @return Always false since all instances are equivalent
*/
template <typename T, typename U>
bool operator!=(const AllocatorPSRAM<T>&, const AllocatorPSRAM<U>&) noexcept {
return false;
}
/**
* @class VectorPSRAM
* @brief Vector implementation that uses ESP32's PSRAM for storage
* @tparam T Type of elements stored in the vector
*
* This class provides an interface identical to std::vector but allocates
* all memory in ESP32's PSRAM, which helps preserve the limited internal RAM.
* It wraps std::vector with a custom allocator that uses PSRAM.
*/
template <typename T>
class VectorPSRAM {
private:
using vector_type = std::vector<T, AllocatorPSRAM<T>>;
vector_type vec;
public:
// Type definitions
using value_type = typename vector_type::value_type;
using allocator_type = typename vector_type::allocator_type;
using size_type = typename vector_type::size_type;
using difference_type = typename vector_type::difference_type;
using reference = typename vector_type::reference;
using const_reference = typename vector_type::const_reference;
using pointer = typename vector_type::pointer;
using const_pointer = typename vector_type::const_pointer;
using iterator = typename vector_type::iterator;
using const_iterator = typename vector_type::const_iterator;
using reverse_iterator = typename vector_type::reverse_iterator;
using const_reverse_iterator = typename vector_type::const_reverse_iterator;
/**
* @brief Default constructor - creates an empty vector
*/
VectorPSRAM() : vec(AllocatorPSRAM<T>()) {}
/**
* @brief Constructs a vector with the given number of default-initialized elements
* @param count The size of the vector
*/
explicit VectorPSRAM(size_type count) : vec(count, AllocatorPSRAM<T>()) {}
/**
* @brief Constructs a vector with the given number of copies of a value
* @param count The size of the vector
* @param value The value to initialize elements with
*/
VectorPSRAM(size_type count, const T& value) : vec(count, value, AllocatorPSRAM<T>()) {}
/**
* @brief Constructs a vector with the contents of the range [first, last)
* @tparam InputIt Input iterator type
* @param first Iterator to the first element in the range
* @param last Iterator to one past the last element in the range
*/
template <typename InputIt>
VectorPSRAM(InputIt first, InputIt last) : vec(first, last, AllocatorPSRAM<T>()) {}
/**
* @brief Copy constructor
* @param other The vector to copy from
*/
VectorPSRAM(const VectorPSRAM& other) : vec(other.vec) {}
/**
* @brief Move constructor
* @param other The vector to move from
*/
VectorPSRAM(VectorPSRAM&& other) noexcept : vec(std::move(other.vec)) {}
/**
* @brief Initializer list constructor
* @param init The initializer list to copy from
*/
VectorPSRAM(std::initializer_list<T> init) : vec(init, AllocatorPSRAM<T>()) {}
/**
* @brief Copy assignment operator
* @param other The vector to copy from
* @return Reference to this vector
*/
VectorPSRAM& operator=(const VectorPSRAM& other) {
vec = other.vec;
return *this;
}
/**
* @brief Move assignment operator
* @param other The vector to move from
* @return Reference to this vector
*/
VectorPSRAM& operator=(VectorPSRAM&& other) noexcept {
vec = std::move(other.vec);
return *this;
}
/**
* @brief Initializer list assignment operator
* @param ilist The initializer list to copy from
* @return Reference to this vector
*/
VectorPSRAM& operator=(std::initializer_list<T> ilist) {
vec = ilist;
return *this;
}
/**
* @brief Access element with bounds checking
* @param pos The position of the element
* @return Reference to the element at position pos
* @throws std::out_of_range if pos is not within the range of the vector
*/
reference at(size_type pos) { return vec.at(pos); }
/**
* @brief Access element with bounds checking (const version)
* @param pos The position of the element
* @return Const reference to the element at position pos
* @throws std::out_of_range if pos is not within the range of the vector
*/
const_reference at(size_type pos) const { return vec.at(pos); }
/**
* @brief Access element without bounds checking
* @param pos The position of the element
* @return Reference to the element at position pos
*/
reference operator[](size_type pos) { return vec[pos]; }
/**
* @brief Access element without bounds checking (const version)
* @param pos The position of the element
* @return Const reference to the element at position pos
*/
const_reference operator[](size_type pos) const { return vec[pos]; }
/**
* @brief Access the first element
* @return Reference to the first element
*/
reference front() { return vec.front(); }
/**
* @brief Access the first element (const version)
* @return Const reference to the first element
*/
const_reference front() const { return vec.front(); }
/**
* @brief Access the last element
* @return Reference to the last element
*/
reference back() { return vec.back(); }
/**
* @brief Access the last element (const version)
* @return Const reference to the last element
*/
const_reference back() const { return vec.back(); }
/**
* @brief Get pointer to the underlying array
* @return Pointer to the underlying array
*/
T* data() noexcept { return vec.data(); }
/**
* @brief Get pointer to the underlying array (const version)
* @return Const pointer to the underlying array
*/
const T* data() const noexcept { return vec.data(); }
/**
* @brief Get iterator to the beginning
* @return Iterator to the first element
*/
iterator begin() noexcept { return vec.begin(); }
/**
* @brief Get const iterator to the beginning
* @return Const iterator to the first element
*/
const_iterator begin() const noexcept { return vec.begin(); }
/**
* @brief Get const iterator to the beginning
* @return Const iterator to the first element
*/
const_iterator cbegin() const noexcept { return vec.cbegin(); }
/**
* @brief Get iterator to the end
* @return Iterator to one past the last element
*/
iterator end() noexcept { return vec.end(); }
/**
* @brief Get const iterator to the end
* @return Const iterator to one past the last element
*/
const_iterator end() const noexcept { return vec.end(); }
/**
* @brief Get const iterator to the end
* @return Const iterator to one past the last element
*/
const_iterator cend() const noexcept { return vec.cend(); }
/**
* @brief Get reverse iterator to the beginning
* @return Reverse iterator to the first element
*/
reverse_iterator rbegin() noexcept { return vec.rbegin(); }
/**
* @brief Get const reverse iterator to the beginning
* @return Const reverse iterator to the first element
*/
const_reverse_iterator rbegin() const noexcept { return vec.rbegin(); }
/**
* @brief Get const reverse iterator to the beginning
* @return Const reverse iterator to the first element
*/
const_reverse_iterator crbegin() const noexcept { return vec.crbegin(); }
/**
* @brief Get reverse iterator to the end
* @return Reverse iterator to one past the last element
*/
reverse_iterator rend() noexcept { return vec.rend(); }
/**
* @brief Get const reverse iterator to the end
* @return Const reverse iterator to one past the last element
*/
const_reverse_iterator rend() const noexcept { return vec.rend(); }
/**
* @brief Get const reverse iterator to the end
* @return Const reverse iterator to one past the last element
*/
const_reverse_iterator crend() const noexcept { return vec.crend(); }
/**
* @brief Check if the vector is empty
* @return true if the vector is empty, false otherwise
*/
bool empty() const noexcept { return vec.empty(); }
/**
* @brief Get the number of elements
* @return The number of elements in the vector
*/
size_type size() const noexcept { return vec.size(); }
/**
* @brief Get the maximum possible number of elements
* @return The maximum possible number of elements the vector can hold
*/
size_type max_size() const noexcept { return vec.max_size(); }
/**
* @brief Reserve storage
* @param new_cap The new capacity of the vector
* @throws std::length_error if new_cap > max_size()
*/
void reserve(size_type new_cap) { vec.reserve(new_cap); }
/**
* @brief Get the number of elements that can be held in current storage
* @return The capacity of the vector
*/
size_type capacity() const noexcept { return vec.capacity(); }
/**
* @brief Reduce memory usage by freeing unused memory
*/
void shrink_to_fit() { vec.shrink_to_fit(); }
/**
* @brief Clear the contents
*/
void clear() noexcept { vec.clear(); }
/**
* @brief Insert an element
* @param pos Iterator to the position before which the element will be inserted
* @param value The value to insert
* @return Iterator to the inserted element
*/
iterator insert(const_iterator pos, const T& value) { return vec.insert(pos, value); }
/**
* @brief Insert an element by moving it
* @param pos Iterator to the position before which the element will be inserted
* @param value The value to insert
* @return Iterator to the inserted element
*/
iterator insert(const_iterator pos, T&& value) { return vec.insert(pos, std::move(value)); }
/**
* @brief Insert multiple copies of an element
* @param pos Iterator to the position before which the elements will be inserted
* @param count Number of copies to insert
* @param value The value to insert
* @return Iterator to the first inserted element
*/
iterator insert(const_iterator pos, size_type count, const T& value) { return vec.insert(pos, count, value); }
/**
* @brief Insert elements from a range
* @tparam InputIt Input iterator type
* @param pos Iterator to the position before which the elements will be inserted
* @param first Iterator to the first element in the range
* @param last Iterator to one past the last element in the range
* @return Iterator to the first inserted element
*/
template <typename InputIt>
iterator insert(const_iterator pos, InputIt first, InputIt last) { return vec.insert(pos, first, last); }
/**
* @brief Insert elements from an initializer list
* @param pos Iterator to the position before which the elements will be inserted
* @param ilist The initializer list to insert from
* @return Iterator to the first inserted element
*/
iterator insert(const_iterator pos, std::initializer_list<T> ilist) { return vec.insert(pos, ilist); }
/**
* @brief Construct an element in-place
* @tparam Args Types of arguments to forward to the constructor
* @param pos Iterator to the position before which the element will be constructed
* @param args Arguments to forward to the constructor
* @return Iterator to the inserted element
*/
template <typename... Args>
iterator emplace(const_iterator pos, Args&&... args) { return vec.emplace(pos, std::forward<Args>(args)...); }
/**
* @brief Erase an element
* @param pos Iterator to the element to erase
* @return Iterator to the element after the erased element
*/
iterator erase(const_iterator pos) { return vec.erase(pos); }
/**
* @brief Erase a range of elements
* @param first Iterator to the first element to erase
* @param last Iterator to one past the last element to erase
* @return Iterator to the element after the erased range
*/
iterator erase(const_iterator first, const_iterator last) { return vec.erase(first, last); }
/**
* @brief Add an element to the end
* @param value The value to append
*/
void push_back(const T& value) { vec.push_back(value); }
/**
* @brief Add an element to the end by moving it
* @param value The value to append
*/
void push_back(T&& value) { vec.push_back(std::move(value)); }
/**
* @brief Construct an element in-place at the end
* @tparam Args Types of arguments to forward to the constructor
* @param args Arguments to forward to the constructor
* @return Reference to the inserted element
*/
template <typename... Args>
reference emplace_back(Args&&... args) { return vec.emplace_back(std::forward<Args>(args)...); }
/**
* @brief Remove the last element
*/
void pop_back() { vec.pop_back(); }
/**
* @brief Change the number of elements stored
* @param count The new size of the vector
*/
void resize(size_type count) { vec.resize(count); }
/**
* @brief Change the number of elements stored
* @param count The new size of the vector
* @param value The value to initialize new elements with
*/
void resize(size_type count, const value_type& value) { vec.resize(count, value); }
/**
* @brief Swap the contents
* @param other Vector to swap with
*/
void swap(VectorPSRAM& other) noexcept { vec.swap(other.vec); }
};
/**
* @brief Equality comparison operator
* @tparam T Type of elements in the vectors
* @param lhs First vector
* @param rhs Second vector
* @return true if the vectors are equal, false otherwise
*/
template <typename T>
bool operator==(const VectorPSRAM<T>& lhs, const VectorPSRAM<T>& rhs) {
return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
}
/**
* @brief Inequality comparison operator
* @tparam T Type of elements in the vectors
* @param lhs First vector
* @param rhs Second vector
* @return true if the vectors are not equal, false otherwise
*/
template <typename T>
bool operator!=(const VectorPSRAM<T>& lhs, const VectorPSRAM<T>& rhs) {
return !(lhs == rhs);
}
/**
* @brief Less than comparison operator
* @tparam T Type of elements in the vectors
* @param lhs First vector
* @param rhs Second vector
* @return true if lhs is lexicographically less than rhs, false otherwise
*/
template <typename T>
bool operator<(const VectorPSRAM<T>& lhs, const VectorPSRAM<T>& rhs) {
return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
}
/**
* @brief Less than or equal comparison operator
* @tparam T Type of elements in the vectors
* @param lhs First vector
* @param rhs Second vector
* @return true if lhs is lexicographically less than or equal to rhs, false otherwise
*/
template <typename T>
bool operator<=(const VectorPSRAM<T>& lhs, const VectorPSRAM<T>& rhs) {
return !(rhs < lhs);
}
/**
* @brief Greater than comparison operator
* @tparam T Type of elements in the vectors
* @param lhs First vector
* @param rhs Second vector
* @return true if lhs is lexicographically greater than rhs, false otherwise
*/
template <typename T>
bool operator>(const VectorPSRAM<T>& lhs, const VectorPSRAM<T>& rhs) {
return rhs < lhs;
}
/**
* @brief Greater than or equal comparison operator
* @tparam T Type of elements in the vectors
* @param lhs First vector
* @param rhs Second vector
* @return true if lhs is lexicographically greater than or equal to rhs, false otherwise
*/
template <typename T>
bool operator>=(const VectorPSRAM<T>& lhs, const VectorPSRAM<T>& rhs) {
return !(lhs < rhs);
}
/**
* @brief Swap the contents of two vectors
* @tparam T Type of elements in the vectors
* @param lhs First vector
* @param rhs Second vector
*/
template <typename T>
void swap(VectorPSRAM<T>& lhs, VectorPSRAM<T>& rhs) noexcept {
lhs.swap(rhs);
}
} // namespace esp32_psram

View File

@@ -25,6 +25,10 @@
#include <uuid/log.h>
#ifndef EMSESP_STANDALONE
#include <esp32-psram.h>
#endif
namespace emsesp {
// names, same order as AnalogType, see list_sensortype in local_common.h
@@ -146,7 +150,11 @@ class AnalogSensor {
bool updated_values();
// return back reference to the sensor list, used by other classes
#ifndef EMSESP_STANDALONE
std::vector<Sensor, AllocatorPSRAM<Sensor>> sensors() const {
#else
std::vector<Sensor> sensors() const {
#endif
return sensors_;
}
@@ -174,9 +182,9 @@ class AnalogSensor {
return sensors_.size();
}
bool update(uint8_t gpio, std::string & name, double offset, double factor, uint8_t uom, int8_t type, bool deleted, bool is_system);
bool get_value_info(JsonObject output, const char * cmd, const int8_t id = -1);
void store_counters();
bool update(uint8_t gpio, std::string & name, double offset, double factor, uint8_t uom, int8_t type, bool deleted, bool is_system);
bool get_value_info(JsonObject output, const char * cmd, const int8_t id = -1);
void store_counters();
static std::vector<uint8_t> exclude_types() {
return exclude_types_;
}
@@ -191,13 +199,17 @@ class AnalogSensor {
static constexpr uint32_t MEASURE_ANALOG_INTERVAL = 500;
static uuid::log::Logger logger_;
void remove_ha_topic(const int8_t type, const uint8_t id) const;
bool command_setvalue(const char * value, const int8_t gpio);
void measure();
void addSensorJson(JsonObject output, const Sensor & sensor);
void get_value_json(JsonObject output, const Sensor & sensor);
void remove_ha_topic(const int8_t type, const uint8_t id) const;
bool command_setvalue(const char * value, const int8_t gpio);
void measure();
void addSensorJson(JsonObject output, const Sensor & sensor);
void get_value_json(JsonObject output, const Sensor & sensor);
#ifndef EMSESP_STANDALONE
std::vector<Sensor, AllocatorPSRAM<Sensor>> sensors_; // our list of sensors
#else
std::vector<Sensor> sensors_; // our list of sensors
#endif
static std::vector<uint8_t> exclude_types_;
bool analog_enabled_;

View File

@@ -25,6 +25,9 @@
#include "helpers.h"
#include "emsdevicevalue.h"
#ifndef EMSESP_STANDALONE
#include <esp32-psram.h>
#endif
#include <unordered_map>
namespace emsesp {
@@ -549,13 +552,21 @@ class EMSdevice {
}
};
std::vector<uint16_t> handlers_ignored_;
#ifndef EMSESP_STANDALONE
std::vector<uint16_t, AllocatorPSRAM<uint16_t>> handlers_ignored_, handlers_broadcasted_, handlers_config_;
#else
std::vector<uint16_t> handlers_ignored_, handlers_broadcasted_, handlers_config_;
#endif
#if defined(EMSESP_STANDALONE) || defined(EMSESP_TEST)
public: // so we can call it from WebCustomizationService::load_test_data() and EMSESP::dump_all_entities()
#endif
#ifndef EMSESP_STANDALONE
std::vector<TelegramFunction, AllocatorPSRAM<TelegramFunction>> telegram_functions_; // each EMS device has its own set of registered telegram types
std::vector<DeviceValue, AllocatorPSRAM<DeviceValue>> devicevalues_; // all the device values
#else
std::vector<TelegramFunction> telegram_functions_; // each EMS device has its own set of registered telegram types
std::vector<DeviceValue> devicevalues_; // all the device values
#endif
};
} // namespace emsesp

View File

@@ -29,22 +29,28 @@ static_assert(uuid::console::thread_safe, "uuid-console must be thread-safe");
namespace emsesp {
// Static member definitions
#ifndef EMSESP_STANDALONE
std::vector<std::unique_ptr<EMSdevice>, AllocatorPSRAM<std::unique_ptr<EMSdevice>>> EMSESP::emsdevices{};
std::vector<EMSESP::Device_record, AllocatorPSRAM<EMSESP::Device_record>> EMSESP::device_library_;
#else
std::vector<std::unique_ptr<EMSdevice>> EMSESP::emsdevices{};
std::vector<EMSESP::Device_record> EMSESP::device_library_;
uuid::log::Logger EMSESP::logger_{F_(emsesp), uuid::log::Facility::KERN};
uint16_t EMSESP::watch_id_ = WATCH_ID_NONE;
uint8_t EMSESP::watch_ = 0;
uint16_t EMSESP::read_id_ = WATCH_ID_NONE;
bool EMSESP::read_next_ = false;
uint16_t EMSESP::publish_id_ = 0;
uint16_t EMSESP::response_id_ = 0;
bool EMSESP::tap_water_active_ = false;
uint8_t EMSESP::publish_all_idx_ = 0;
uint8_t EMSESP::unique_id_count_ = 0;
bool EMSESP::trace_raw_ = false;
uint16_t EMSESP::wait_validate_ = 0;
bool EMSESP::wait_km_ = false;
uint32_t EMSESP::last_fetch_ = 0;
#endif
uuid::log::Logger EMSESP::logger_{F_(emsesp), uuid::log::Facility::KERN};
uint16_t EMSESP::watch_id_ = WATCH_ID_NONE;
uint8_t EMSESP::watch_ = 0;
uint16_t EMSESP::read_id_ = WATCH_ID_NONE;
bool EMSESP::read_next_ = false;
uint16_t EMSESP::publish_id_ = 0;
uint16_t EMSESP::response_id_ = 0;
bool EMSESP::tap_water_active_ = false;
uint8_t EMSESP::publish_all_idx_ = 0;
uint8_t EMSESP::unique_id_count_ = 0;
bool EMSESP::trace_raw_ = false;
uint16_t EMSESP::wait_validate_ = 0;
bool EMSESP::wait_km_ = false;
uint32_t EMSESP::last_fetch_ = 0;
AsyncWebServer webServer(80);
@@ -78,10 +84,6 @@ uuid::log::Logger EMSESP::logger() {
return logger_;
}
#ifndef EMSESP_STANDALONE
uuid::syslog::SyslogService System::syslog_;
#endif
// The services
RxService EMSESP::rxservice_; // incoming Telegram Rx handler
TxService EMSESP::txservice_; // outgoing Telegram Tx handler

View File

@@ -72,6 +72,9 @@
#include "command.h"
#include "../emsesp_version.h"
#ifndef EMSESP_STANDALONE
#include <esp32-psram.h>
#endif
// Load external modules
class Module {}; // forward declaration
@@ -220,8 +223,11 @@ class EMSESP {
static void scan_devices();
static void clear_all_devices();
#ifndef EMSESP_STANDALONE
static std::vector<std::unique_ptr<EMSdevice>, AllocatorPSRAM<std::unique_ptr<EMSdevice>>> emsdevices;
#else
static std::vector<std::unique_ptr<EMSdevice>> emsdevices;
#endif
// services
static Mqtt mqtt_;
static Modbus * modbus_;
@@ -266,8 +272,11 @@ class EMSESP {
const char * default_name;
uint8_t flags;
};
#ifndef EMSESP_STANDALONE
static std::vector<Device_record, AllocatorPSRAM<Device_record>> device_library_;
#else
static std::vector<Device_record> device_library_;
#endif
static uint16_t watch_id_;
static uint8_t watch_;
static uint16_t read_id_;

View File

@@ -83,15 +83,24 @@ const char * const languages[] = {EMSESP_LOCALE_EN,
static constexpr uint8_t NUM_LANGUAGES = sizeof(languages) / sizeof(const char *);
#ifndef EMSESP_STANDALONE
uuid::syslog::SyslogService System::syslog_;
#endif
uuid::log::Logger System::logger_{F_(system), uuid::log::Facility::KERN};
// init statics
PButton System::myPButton_;
bool System::test_set_all_active_ = false;
uint32_t System::max_alloc_mem_;
uint32_t System::heap_mem_;
PButton System::myPButton_;
bool System::test_set_all_active_ = false;
uint32_t System::max_alloc_mem_;
uint32_t System::heap_mem_;
#ifndef EMSESP_STANDALONE
std::vector<uint8_t, AllocatorPSRAM<uint8_t>> System::valid_system_gpios_;
std::vector<uint8_t, AllocatorPSRAM<uint8_t>> System::used_gpios_;
#else
std::vector<uint8_t> System::valid_system_gpios_;
std::vector<uint8_t> System::used_gpios_;
#endif
// find the index of the language
// 0 = EN, 1 = DE, etc...
@@ -366,21 +375,19 @@ void System::syslog_init() {
#ifndef EMSESP_STANDALONE
if (syslog_enabled_) {
// start & configure syslog
EMSESP::logger().info("Starting Syslog service");
syslog_.start();
syslog_.log_level((uuid::log::Level)syslog_level_);
syslog_.mark_interval(syslog_mark_interval_);
syslog_.destination(syslog_host_.c_str(), syslog_port_);
syslog_.hostname(hostname().c_str());
syslog_.hostname(hostname());
EMSESP::logger().info("Starting Syslog service");
} else if (syslog_.started()) {
// in case service is still running, this flushes the queue
// https://github.com/emsesp/EMS-ESP/issues/496
EMSESP::logger().info("Stopping Syslog");
syslog_.log_level((uuid::log::Level)-1); // stop server
syslog_.loop();
syslog_.log_level(uuid::log::Level::OFF); // stop server
syslog_.mark_interval(0);
syslog_.destination("");
// syslog_.destination("");
}
if (Mqtt::publish_single()) {
if (Mqtt::publish_single2cmd()) {
@@ -1638,7 +1645,8 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
});
// NTP status
node = output["ntp"].to<JsonObject>();
node = output["ntp"].to<JsonObject>();
node["NTPstatus"] = EMSESP::system_.ntp_connected() ? "connected" : "disconnected";
EMSESP::esp32React.getNTPSettingsService()->read([&](const NTPSettings & settings) {
#ifndef EMSESP_STANDALONE
node["enabled"] = settings.enabled;
@@ -2270,8 +2278,13 @@ uint8_t System::systemStatus() {
}
// takes a string range like "6-11, 1, 23, 24-48" which has optional ranges and single values and converts to a vector of ints
#ifndef EMSESP_STANDALONE
std::vector<uint8_t, AllocatorPSRAM<uint8_t>> System::string_range_to_vector(const std::string & range) {
std::vector<uint8_t, AllocatorPSRAM<uint8_t>> gpios;
#else
std::vector<uint8_t> System::string_range_to_vector(const std::string & range) {
std::vector<uint8_t> gpios;
std::vector<uint8_t> gpios;
#endif
std::string::size_type pos = 0;
std::string::size_type prev = 0;

View File

@@ -31,6 +31,7 @@
#include <esp_wifi.h>
#include <ETH.h>
#include <uuid/syslog.h>
#include <esp32-psram.h>
#endif
#include <uuid/log.h>
@@ -394,11 +395,17 @@ class System {
void led_monitor();
void system_check();
#ifndef EMSESP_STANDALONE
static std::vector<uint8_t, AllocatorPSRAM<uint8_t>> string_range_to_vector(const std::string & range);
static std::vector<uint8_t, AllocatorPSRAM<uint8_t>> valid_system_gpios_; // list of valid GPIOs for the ESP32 board that can be used
static std::vector<uint8_t, AllocatorPSRAM<uint8_t>> used_gpios_; // list of GPIOs used by the application
#else
static std::vector<uint8_t> string_range_to_vector(const std::string & range);
static std::vector<uint8_t> valid_system_gpios_; // list of valid GPIOs for the ESP32 board that can be used
static std::vector<uint8_t> used_gpios_; // list of GPIOs used by the application
#endif
int8_t wifi_quality(int8_t dBm);
uint8_t healthcheck_ = HEALTHCHECK_NO_NETWORK | HEALTHCHECK_NO_BUS; // start with all flags set, no wifi and no ems bus connection

View File

@@ -31,6 +31,9 @@
#endif
#include "helpers.h"
#ifndef EMSESP_STANDALONE
#include <esp32-psram.h>
#endif
#define MAX_RX_TELEGRAMS 100 // size of Rx queue
#define MAX_TX_TELEGRAMS 160 // size of Tx queue
@@ -287,7 +290,11 @@ class RxService : public EMSbus {
}
};
#ifndef EMSESP_STANDALONE
std::deque<QueuedRxTelegram, AllocatorPSRAM<QueuedRxTelegram>> queue() const {
#else
std::deque<QueuedRxTelegram> queue() const {
#endif
return rx_telegrams_;
}
@@ -298,7 +305,11 @@ class RxService : public EMSbus {
uint32_t telegram_count_ = 0; // # Rx received
uint32_t telegram_error_count_ = 0; // # Rx CRC errors
std::shared_ptr<const Telegram> rx_telegram; // the incoming Rx telegram
std::deque<QueuedRxTelegram> rx_telegrams_; // the Rx Queue
#ifndef EMSESP_STANDALONE
std::deque<QueuedRxTelegram, AllocatorPSRAM<QueuedRxTelegram>> rx_telegrams_; // the Rx Queue
#else
std::deque<QueuedRxTelegram> rx_telegrams_; // the Rx Queue
#endif
};
class TxService : public EMSbus {
@@ -419,7 +430,11 @@ class TxService : public EMSbus {
}
};
#ifndef EMSESP_STANDALONE
std::deque<QueuedTxTelegram, AllocatorPSRAM<QueuedTxTelegram>> queue() const {
#else
std::deque<QueuedTxTelegram> queue() const {
#endif
return tx_telegrams_;
}
@@ -431,7 +446,11 @@ class TxService : public EMSbus {
static constexpr uint32_t POST_SEND_DELAY = 2000;
private:
#ifndef EMSESP_STANDALONE
std::deque<QueuedTxTelegram, AllocatorPSRAM<QueuedTxTelegram>> tx_telegrams_; // the Tx queue
#else
std::deque<QueuedTxTelegram> tx_telegrams_; // the Tx queue
#endif
uint32_t telegram_read_count_ = 0; // # Tx successful reads
uint32_t telegram_write_count_ = 0; // # Tx successful writes

View File

@@ -29,6 +29,7 @@
#ifndef EMSESP_STANDALONE
#include <OneWire.h>
#include <esp32-psram.h>
#endif
namespace emsesp {
@@ -94,7 +95,11 @@ class TemperatureSensor {
bool get_value_info(JsonObject output, const char * cmd, const int8_t id = -1);
// return back reference to the sensor list, used by other classes
#ifndef EMSESP_STANDALONE
std::vector<Sensor, AllocatorPSRAM<Sensor>> sensors() const {
#else
std::vector<Sensor> sensors() const {
#endif
return sensors_;
}
@@ -165,15 +170,16 @@ class TemperatureSensor {
void get_value_json(JsonObject output, const Sensor & sensor);
void remove_ha_topic(const std::string & id);
std::vector<Sensor> sensors_; // our list of active sensors
#ifndef EMSESP_STANDALONE
std::vector<Sensor, AllocatorPSRAM<Sensor>> sensors_; // our list of active sensors
OneWire bus_;
uint32_t last_activity_ = uuid::get_uptime();
State state_ = State::IDLE;
int8_t scancnt_ = SCAN_START;
uint8_t firstscan_ = 0;
int8_t scanretry_ = 0;
#else
std::vector<Sensor> sensors_; // our list of active sensors
#endif
uint8_t dallas_gpio_ = 0;

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "3.7.3-dev.34"
#define EMSESP_APP_VERSION "3.7.3-dev.35"

View File

@@ -16,6 +16,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "../core/telegram.h"
#ifndef EMSESP_STANDALONE
#include <esp32-psram.h>
#endif
#ifndef WebCustomEntityService_h
#define WebCustomEntityService_h
@@ -45,7 +48,11 @@ class CustomEntityItem {
class WebCustomEntity {
public:
#ifndef EMSESP_STANDALONE
std::list<CustomEntityItem, AllocatorPSRAM<CustomEntityItem>> customEntityItems;
#else
std::list<CustomEntityItem> customEntityItems;
#endif
static void read(WebCustomEntity & webEntity, JsonObject root);
static StateUpdateResult update(JsonObject root, WebCustomEntity & webEntity);
@@ -82,8 +89,11 @@ class WebCustomEntityService : public StatefulService<WebCustomEntity> {
void getEntities(AsyncWebServerRequest * request);
#ifndef EMSESP_STANDALONE
std::list<CustomEntityItem, AllocatorPSRAM<CustomEntityItem>> * customEntityItems_; // pointer to the list of entity items
#else
std::list<CustomEntityItem> * customEntityItems_; // pointer to the list of entity items
#endif
bool ha_registered_ = false;
};

View File

@@ -18,6 +18,9 @@
#ifndef WebCustomizationService_h
#define WebCustomizationService_h
#ifndef EMSESP_STANDALONE
#include <esp32-psram.h>
#endif
#define EMSESP_CUSTOMIZATION_FILE "/config/emsespCustomization.json"
@@ -70,11 +73,17 @@ class EntityCustomization {
class WebCustomization {
public:
#ifndef EMSESP_STANDALONE
std::list<SensorCustomization, AllocatorPSRAM<SensorCustomization>> sensorCustomizations; // for sensor names and offsets
std::list<AnalogCustomization, AllocatorPSRAM<AnalogCustomization>> analogCustomizations; // for analog sensors
std::list<EntityCustomization, AllocatorPSRAM<EntityCustomization>> entityCustomizations; // for a list of entities that have a special mask set
#else
std::list<SensorCustomization> sensorCustomizations; // for sensor names and offsets
std::list<AnalogCustomization> analogCustomizations; // for analog sensors
std::list<EntityCustomization> entityCustomizations; // for a list of entities that have a special mask set
static void read(WebCustomization & customizations, JsonObject root);
static StateUpdateResult update(JsonObject root, WebCustomization & customizations);
#endif
static void read(WebCustomization & customizations, JsonObject root);
static StateUpdateResult update(JsonObject root, WebCustomization & customizations);
private:
static bool _start;

View File

@@ -21,6 +21,9 @@
#define EMSESP_EVENT_SOURCE_LOG_PATH "/es/log"
#define EMSESP_LOG_SETTINGS_PATH "/rest/logSettings"
#ifndef EMSESP_STANDALONE
#include <esp32-psram.h>
#endif
using ::uuid::console::Shell;
@@ -65,12 +68,16 @@ class WebLogService : public uuid::log::Handler {
char * messagetime(char * out, const uint64_t t, const size_t bufsize);
size_t maximum_log_messages_ = MAX_LOG_MESSAGES; // Maximum number of log messages to buffer before they are output
size_t limit_log_messages_ = 1; // dynamic limit
unsigned long log_message_id_ = 0; // The next identifier to use for queued log messages
unsigned long log_message_id_tail_ = 0; // last event shown on the screen after fetch
std::deque<QueuedLogMessage> log_messages_; // Queued log messages, in the order they were received
bool compact_ = true;
#ifndef EMSESP_STANDALONE
std::deque<QueuedLogMessage, AllocatorPSRAM<QueuedLogMessage>> log_messages_; // Queued log messages, in the order they were received
#else
std::deque<QueuedLogMessage> log_messages_; // Queued log messages, in the order they were received
#endif
size_t maximum_log_messages_ = MAX_LOG_MESSAGES; // Maximum number of log messages to buffer before they are output
size_t limit_log_messages_ = 1; // dynamic limit
unsigned long log_message_id_ = 0; // The next identifier to use for queued log messages
unsigned long log_message_id_tail_ = 0; // last event shown on the screen after fetch
bool compact_ = true;
};
} // namespace emsesp

View File

@@ -16,6 +16,10 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef EMSESP_STANDALONE
#include <esp32-psram.h>
#endif
#ifndef WebSchedulerService_h
#define WebSchedulerService_h
@@ -63,8 +67,11 @@ class ScheduleItem {
class WebScheduler {
public:
#ifndef EMSESP_STANDALONE
std::list<ScheduleItem, AllocatorPSRAM<ScheduleItem>> scheduleItems;
#else
std::list<ScheduleItem> scheduleItems;
#endif
static void read(WebScheduler & webScheduler, JsonObject root);
static StateUpdateResult update(JsonObject root, WebScheduler & webScheduler);
};
@@ -104,10 +111,15 @@ class WebSchedulerService : public StatefulService<WebScheduler> {
HttpEndpoint<WebScheduler> _httpEndpoint;
FSPersistence<WebScheduler> _fsPersistence;
bool ha_registered_ = false;
std::list<ScheduleItem> * scheduleItems_; // pointer to the list of schedule events
bool ha_registered_ = false;
std::deque<ScheduleItem *> cmd_changed_;
#ifndef EMSESP_STANDALONE
std::list<ScheduleItem, AllocatorPSRAM<ScheduleItem>> * scheduleItems_; // pointer to the list of schedule events
std::list<ScheduleItem *, AllocatorPSRAM<ScheduleItem *>> cmd_changed_; // pointer to commands in list that are triggert by change
#else
std::list<ScheduleItem> * scheduleItems_; // pointer to the list of schedule events
std::list<ScheduleItem *> cmd_changed_; // pointer to commands in list that are triggert by change
#endif
};
} // namespace emsesp