Compare commits

...
5 Commits
Author SHA1 Message Date
Amir 6b1fb6ac3d Update CHANGELOG for v7.2 release
Document bug fixes and improvements for v7.2 release, including button responsiveness and screen control issues.
2026-08-08 23:32:33 +02:00
Amir 4fb5a6bc5f Update VERSION.md for firmware v7.2 release
Updated version information from 7.1 to 7.2, including release date and highlights of changes made in v7.2. Fixes include button robustness and screen-control improvements.
2026-08-08 23:31:02 +02:00
Amir 20bff8b00a Update README for version 7.2 UI screen control fixes 2026-08-08 23:29:03 +02:00
Amir 0933a06973 UI control - button issues Fix 2026-08-08 23:25:21 +02:00
Amir 48819161af Update README for version 7.1 features and changes
Added support for separate provider fee for negative spot prices in version 7.1. Updated documentation to reflect changes in pricing calculations and display behavior.
2026-04-06 04:08:17 +02:00
4 changed files with 2060 additions and 22 deletions
+205
View File
@@ -2,6 +2,211 @@
All notable changes to this project are documented here.
## v7.2 - Button Robustness & Screen-Control Fixes (2026-08-04)
**Summary**
Bug-fix release that resolves the three control glitches reported for the v7.1
firmware on the Seeed XIAO ESP32C3:
- "It is 20:35 and I cannot scroll the values of the primary screen."
- "Double click does not switch to the secondary screen."
- "Strange behaviour at the end of the day, if there is no tomorrow's data
available yet and the time is let's say 22:15."
No fee/VAT math, NVS layout, API scheduling or 48-hour scrolling behaviour
was changed. The v7.1 negative-price provider fee logic is preserved
verbatim.
### Fix 1 Primary-screen scroll works at any hour of the day
**Symptom**
Clicking the button on the primary screen appeared to do nothing — the
display stayed on the current hour and refused to advance.
**Root cause (two compounding bugs)**
1. `displayPriceRow()` only blanked past hours while `currentHour < 22`:
```cpp
if (localHourIndex < currentHour && currentHour < 22) {
lcd.print(" "); return;
}
```
After 22:00 the guard fell through, so the screen was allowed to repaint
already-finished morning hours (00:00 21:59). The moment the user
scrolled forward, the new "top" hour was visually overwritten by the
previous morning's data, making the screen look frozen.
2. `displayPrimaryList()` contained an override:
```cpp
if (currentHour >= 21 && timeOffsetHours > 0) {
displayStartHourOffset = 21 + timeOffsetHours;
}
```
From 21:00 onward this pinned the top row at `21 + offset`. At 22:15
every click computed start = 22+offset, was then clamped to 21+offset,
and the user saw no movement.
**Fix**
- `displayPriceRow()`: simplified the past-hour guard to
`if (localHourIndex < currentHour) blank();` so past hours of today are
hidden at every hour of the day, not just before 22:00.
- `displayPrimaryList()`: removed the `currentHour >= 21` override
entirely. Past-hour blanking is now handled correctly by Fix 1a, so the
override is no longer needed.
### Fix 2 Double-click reliably toggles to the secondary screen
**Symptom**
A quick double-click did nothing — the display stayed on the primary
price view. In some cases the screen showed a stuck "Long press detected!
Release to refresh" message right after the device booted or was reset.
**Root cause**
The double-click path itself was correct; it was being starved by a false
"Long press detected!" that fired immediately after every reset. On the
ESP32-C3 the button pin (configured as `INPUT_PULLUP`) floats HIGH for a
few seconds during boot while the internal pull-up is settling and
power-rail noise is ringing. Because `buttonPressStartTime` is
initialised to `0`, the long-press detector's predicate
`millis() - buttonPressStartTime >= 3000` evaluated to
`millis() >= 3000` a few seconds after boot — the firmware interpreted
the floating-pin noise as a genuine 3-second hold, cleared the LCD to:
```
Long press detected!
Release to refresh
```
…and from then on the user could not see any prices to click on
(single- and double-click recognisers both still ran, but their visible
effect was hidden behind the long-press splash).
**Fix**
Added a single new global flag and gated the long-press detector on it:
```cpp
// New flag, set true the first time the pin is observed LOW after boot
bool buttonEverReleased = false;
// In the "reading went LOW" branch:
if (reading == LOW) {
buttonPressStartTime = millis();
longPressDetected = false;
buttonEverReleased = true; // v7.2
}
// In the long-press trip block:
if (buttonState == LOW && !longPressDetected && buttonEverReleased) { // v7.2
if (millis() - buttonPressStartTime >= longPressThreshold) {
longPressDetected = true;
// ... show "Long press detected! Release to refresh"
}
}
```
The detector now refuses to fire until the user (or the power-rail noise)
has released the button at least once. The v7.1 button logic (50 ms
debounce, 3 s long-press threshold, 500 ms double-click window, TTP223
timing) is otherwise preserved verbatim.
### Fix 3 End-of-day scroll is stable with no tomorrow data
**Symptom**
Around 22:00 23:59 with no tomorrow data in the buffer, scrolling
through the primary screen produced a screen full of blank past-hour
rows, or wrapped the start hour back to 00:00 in a confusing way.
**Root cause**
`advanceDisplayOffset()` contained a hack:
```cpp
if (allowedAhead < 2 && currentHour >= 21 && !isTomorrowDataAvailable)
allowedAhead = 2;
```
At 22:00 (with no tomorrow data) this let the user click past hour 23
into "24:00 / 25:00". `displayStartHourOffset` then exceeded
`maxOffsetLimit = 23` and the wrap logic:
```cpp
if (displayStartHourOffset > maxOffsetLimit)
displayStartHourOffset %= (maxOffsetLimit + 1);
```
…wrapped it back to 0. Combined with the buggy past-hour blanking in
Fix 1a, the result was a screen full of stale blank rows from 00:00 to
21:59.
**Fix**
Removed the `allowedAhead = 2` hack. The natural cap is now sufficient:
- 22:00 → can step 22 → 23, then wraps back to current
- 23:00 → cannot step forward at all
No wrap to 00:00 of the previous day is reachable any more, and Fix 1a
ensures any past hour that does briefly land on the screen is blanked
correctly.
### Fix 4 (bonus) Auto-return timer now resets on every click
**Symptom**
Scrolling through the 20-line secondary status page (4 lines at a time)
did not push the 10 s auto-return-to-top timeout forward — the display
could jump back to the primary price view mid-read.
**Root cause**
`lastButtonActivity` and `autoScrollExecuted` were only updated inside the
primary-list branches of `advanceDisplayOffset()`. The secondary-list
branch scrolled the offset but did not touch the timer.
**Fix**
Moved the two resets to the very top of `advanceDisplayOffset()`:
```cpp
void advanceDisplayOffset() {
// Any successful click (single, double, or long-press-release) ends up
// here, so this is the single place that resets the auto-return timer.
lastButtonActivity = millis();
autoScrollExecuted = false;
// ... rest of the function unchanged
}
```
### Other changes (cosmetic / non-behavioural)
- Filename and three user-visible version strings bumped to v7.2:
- `connectToWiFi()` splash: `"Elec. Rate SI v7.1"` → `"v7.2"`
- `displaySecondaryList()` credit line (line 18): `"price ticker v7.1"` → `"v7.2"`
- `setup()` debug banner: `"Starting Dynamic Electricity Ticker v7.1 (Neg Price Fee) - DST-SAFE"`
→ `"Starting Dynamic Electricity Ticker v7.2 (Button Robustness) - DST-SAFE"`
- Inline comments added at each fix site explaining what v7.1 did wrong,
so future maintainers do not re-introduce the overrides.
- New global flag `bool buttonEverReleased` (see Fix 2).
### Files changed
| File | Change |
|---|---|
| `ESP32_standalone_electricity_ticker_7_2.ino` | Filename, header comment, `buttonEverReleased` flag, `handleButton()` long-press gate, `displayPriceRow()` past-hour guard, `displayPrimaryList()` removed override, `advanceDisplayOffset()` timer reset, three version strings |
---
## v7.1 - Negative Price Provider Fee (2026-04-06)
**Summary**
File diff suppressed because it is too large Load Diff
+104 -20
View File
@@ -11,12 +11,80 @@ This project is an ArduinoIDEfriendly firmware for the **Seeed XIAO ESP32
- Uses a white LED and an optional presence sensor to give quick visual feedback.
- Stores daily price data in **NVS** to survive reboots and reduce API calls.
The latest sketch implements **Version 7.0** with the revolutionary **Rolling 48-Hour Logic & Midnight Bridge** system.
The latest sketch implements **Version 7.2** — a bug-fix release that
restores primary-screen scrolling, fixes double-click → secondary-screen
switching on the ESP32-C3, and stabilises end-of-day behaviour when no
tomorrow data is available yet. The v7.1 negative-price provider fee logic
is preserved unchanged.
---
## Version Highlights
### v7.2 - Button Robustness & Screen-Control Fixes (2026-08-04)
Bug-fix release. Resolves the three control glitches reported for v7.1:
"cannot scroll the primary screen at 20:35", "double-click does not switch
to the secondary screen", and "strange end-of-day behaviour at 22:15 with
no tomorrow data". No fee/VAT math, NVS layout, API scheduling or
48-hour scrolling behaviour was changed.
- **Fix 1 Primary-screen scroll** works at any hour of the day:
past-hour blanking in `displayPriceRow()` simplified from
`currentHour < 22` to a plain `localHourIndex < currentHour`, and the
`currentHour >= 21` override in `displayPrimaryList()` removed.
- **Fix 2 Double-click** reliably toggles to the secondary screen.
A new `bool buttonEverReleased` flag prevents a false
"Long press detected!" from firing right after every reset on the
ESP32-C3 (floating button pin + `buttonPressStartTime = 0` was making
the long-press detector trip on phantom 3-second holds during boot).
- **Fix 3 End-of-day scroll** is stable with no tomorrow data. The
`allowedAhead = 2` hack in `advanceDisplayOffset()` is gone, so the
user can no longer scroll into "24:00 / 25:00" and wrap back to 00:00.
- **Fix 4 (bonus) Auto-return timer** now resets on every click, so
scrolling the secondary status page no longer jumps back to the primary
view mid-read.
See [`VERSION.md`](./VERSION.md) for the highlights and
[`CHANGELOG.md`](./CHANGELOG.md) for full implementation details.
---
### v7.1 - Negative Price Provider Fee (2026-04-06)
Added a separate configurable provider fee for negative spot prices, correctly
modelling contracts where the provider's fee structure differs between positive
and negative market prices.
**New constant:**
```cpp
const float NEG_PRICE_COMPANY_FEE_PERCENTAGE = 30.0;
```
**Price calculation is now:**
| Market price | Formula |
|---|---|
| Positive (`raw >= 0`) | `raw × (1 + POWER_COMPANY_FEE_PERCENTAGE/100) × (1 + VAT_PERCENTAGE/100)` |
| Negative (`raw < 0`) | `raw × (1 - NEG_PRICE_COMPANY_FEE_PERCENTAGE/100) × (1 + VAT_PERCENTAGE/100)` |
The switch happens on the **raw API price** before any multiplier is applied.
VAT is applied to both cases, consistent with net billing contracts where VAT
is calculated on the monthly net sum (mathematically equivalent due to VAT
being a linear multiplier).
**Key values for `NEG_PRICE_COMPANY_FEE_PERCENTAGE`:**
| Value | Meaning |
|---|---|
| `30.0` | Provider keeps 30%, pays you 70% of the negative market price |
| `0.0` | Provider passes the full negative price to you (no fee deducted) |
All 5 fee calculation sites updated: `updateLeds()`, `format15MinPrice()`,
`displayPriceRow()`, `displaySecondaryList()` (daily average), and version strings.
---
### v7.0 - Rolling 48-Hour Logic & Midnight Bridge (Major Upgrade)
This version is the **"Golden Build"** for this hardware platform. It combines all hardware stability fixes from v6.2.4 with a revolutionary new 48-hour price prediction system.
@@ -37,7 +105,7 @@ This version is the **"Golden Build"** for this hardware platform. It combines a
- **LED Indicators Pinned to Current**: White LED always reflects actual current prices
### v6.2.4 - Exact-boundary display refresh bug (critical fix of v6.2.3 update)
- Problem: At the exact top of the hour (e.g., 20:00:00), the display automatically refreshed but showed the PREVIOUS hour's data (19:00). This happened because the "next-boundary" rounding logic in findCurrentPriceIndex() incorrectly excluded the current interval if the time was exactly on the boundary.
- Problem: At the exact top of the hour (e.g., 20:00:00), the display automatically refreshed but showed the PREVIOUS hour's data (19:00). This happened because the "next-boundary" rounding logic in findCurrentPriceIndex() pre-calculated the next boundary.
- Fix: Simplified findCurrentPriceIndex() to use a robust "last entry <= now" comparison. This ensures the display transitions to the new hour instantaneously at XX:00:00.
### v6.2.3 - State-based display refresh logic (critical fix of v6.2.2 update)
@@ -57,7 +125,7 @@ This version is the **"Golden Build"** for this hardware platform. It combines a
Major update sketch implements **Version 6.2.0**, focusing on:
- **Version 6.2.0 FIX**: **DST (Daylight Saving Time) handling fully fixed** the ticker now works correctly on ALL days including DST switch days (spring forward and fall back). Uses timestamp-based price lookups instead of arithmetic calculations.
- **Version 6.2.0 FIX**: **DST (Daylight Saving Time) handling fully fixed** the ticker now works correctly on ALL days including DST switch days (spring forward and fall back). Uses timestamp-based lookups throughout.
- Version 6.1.2 fix: restore correct **white LED indicator** behavior (ESP32 PWM fix; no dim glow when off).
- Version 6.1.1 fix: Correct daily **low/high hourly markers** (now includes negative and **0.0** prices).
- Daily (not hourly) API fetching.
@@ -194,16 +262,16 @@ if (dataIndex == lowIdx) {
---
## Behavior & Display States (v7.0)
## Behavior & Display States (v7.2)
The display changes based on which data buffer is being used and the status of the fetch:
| **State** | **Display Output** | **LED Behavior** |
|----------|-------------------|-----------------|
| **Normal (Today)** | Shows current prices and 15-min details. Hours are marked as HH:00. | White LED reflects current price status (Breathe, Solid, or Blink). |
| **Scrolling (Tomorrow)** | Future prices are displayed. Hours are marked with HH:>> to indicate "Tomorrow". | **Pinned to Today:** The LEDs continue showing the _actual current_ price status even while you scroll through tomorrow. |
| **Scrolling (Tomorrow)** | Future prices are displayed. Hours are marked with HH:>> to indicate "Tomorrow". | **Pinned to Today:** The LEDs continue showing the _actual current_ price status even while browsing future hours. |
| **No Data** | Displays: "No data for today, Press & hold to, refresh manually." | White LED is turned **OFF** to avoid misleading price signals. |
| **Connecting** | "Elec. Rate SI v7.0" followed by "Connecting..." and progress dots. | Built-in LED is **OFF** until connection is established. |
| **Connecting** | "Elec. Rate SI v7.2" followed by "Connecting..." and progress dots. | Built-in LED is **OFF** until connection is established. |
### Key UX Principle: LEDs Stay Pinned to Current Time
@@ -264,7 +332,7 @@ const char* api_url = "https://api.energy-charts.info/price?bzn=SI";
to any supported BZN.
All available bidding zones (from the original README):
All available bidding zones:
- `AT` Austria
- `BE` Belgium
@@ -318,7 +386,7 @@ All available bidding zones (from the original README):
## Hardware Setup (Detailed)
This section merges the original v5.5 instructions with the current v7.0 hardware expectations.
This section merges the original v5.5 instructions with the current v7.2 hardware expectations.
Follow it carefully to reproduce the working setup.
### 1. Microcontroller
@@ -468,7 +536,7 @@ The LED is driven with various patterns to indicate price level; see "LED Price
---
## Firmware Features (v7.0)
## Firmware Features (v7.2)
### Core Display & Pricing
@@ -478,10 +546,18 @@ The LED is driven with various patterns to indicate price level; see "LED Price
- **Row 0**: Current hour, four 15minute values
- **Rows 13**: Current hour + next two hours as hourly averages
- **v7.0 Feature**: Tomorrow's hours are marked with `HH:>>` format
- Price calculation: Raw MWh → EUR/kWh with configurable surcharges (power company fee + VAT)
- Two configurable surcharges:
- `POWER_COMPANY_FEE_PERCENTAGE` (default `12.0` %).
- `VAT_PERCENTAGE` (default `22.0` %).
- Price calculation: Raw MWh → EUR/kWh with configurable surcharges
- Three configurable constants:
- `POWER_COMPANY_FEE_PERCENTAGE` (default `12.0` %) — fee for **positive** spot prices
- `NEG_PRICE_COMPANY_FEE_PERCENTAGE` (default `30.0` %) — fee kept by provider on **negative** spot prices
- `VAT_PERCENTAGE` (default `22.0` %)
- **v7.1**: Positive and negative spot prices use independent fee multipliers:
| Market price | Formula |
|---|---|
| Positive (`raw >= 0`) | `raw × (1 + POWER_COMPANY_FEE_PERCENTAGE/100) × (1 + VAT_PERCENTAGE/100)` |
| Negative (`raw < 0`) | `raw × (1 - NEG_PRICE_COMPANY_FEE_PERCENTAGE/100) × (1 + VAT_PERCENTAGE/100)` |
- LCD:
- `LiquidCrystal_I2C` with custom characters for:
- Local language letters.
@@ -535,7 +611,7 @@ One button (or touch) on GPIO 4 controls the UI:
- Toggles between:
- Primary price view.
- Secondary status/info view.
- **Long press (~3 seconds in v6.1)**:
- **Long press (~3 seconds)**:
- While held:
- LCD shows: "Long press detected! Release to refresh".
- On release:
@@ -676,7 +752,7 @@ In `processJsonData()`:
A **secondary screen** (toggled via **doubleclick**) provides 20 lines of status information, displayed 4 lines at a time:
Typical content (updated for v7.0):
Typical content (updated for v7.2):
1. Current date and time (`HH:MM DD.MM.YYYY`)
2. Separator line (`--------------------`)
@@ -690,7 +766,7 @@ Typical content (updated for v7.0):
10. Local IP address
11. API success rate (`API: xx% (succ/fail)`)
12. Device uptime in days, hours, minutes
1316. **NVS status block** (enhanced for v7.0):
1316. **NVS status block**:
- `NVS status:`
- `Data day: DD.MM.YYYY` or `Data day: none`
- `Last save: DD.MM.YY` or `Last save: none`
@@ -698,8 +774,8 @@ Typical content (updated for v7.0):
1720. Credits and version:
- `energy-charts.info`
- `dynamic electricity`
- `price ticker v7.0`
- `by Legolas-2025` (or your preferred credit line)
- `price ticker v7.2`
- `by Legolas-2025`
---
@@ -731,7 +807,7 @@ If NVS does not contain valid WiFi credentials, or if connecting fails repeat
- `DNSServer` (from ESP32 core)
- `WebServer` (from ESP32 core)
- `Preferences` (builtin for ESP32)
3. Open the v7.0 `.ino` file (e.g. `ESP32_standalone_electricity_ticker_v7_0_Rolling_48H.ino`).
3. Open the v7.2 `.ino` file (`ESP32_standalone_electricity_ticker_7_2.ino`).
4. In Tools:
- Board: `Seeed XIAO ESP32C3`
- Port: choose the correct serial port.
@@ -741,12 +817,20 @@ If NVS does not contain valid WiFi credentials, or if connecting fails repeat
- NTP sync messages.
- NVS load/save status.
- Midnight rollover and retry debug output.
- **v7.0 NEW**: Tomorrow fetch logs (`Fetching Tomorrow's Data...`)
- Tomorrow fetch logs (`Fetching Tomorrow's Data...`)
---
## Versioning & Changelog
- **v7.2** Button robustness & screen-control fixes (2026-08-04). See
highlights above; full details in [`CHANGELOG.md`](./CHANGELOG.md).
- **v7.1** Negative price provider fee:
- Separate `NEG_PRICE_COMPANY_FEE_PERCENTAGE` constant (default `30.0` %)
- Positive prices: `raw × (1 + pos_fee) × (1 + VAT)`
- Negative prices: `raw × (1 - neg_fee) × (1 + VAT)`
- Switch on raw API price before any multiplier
- All 5 fee calculation sites updated
- **v7.0** Rolling 48-Hour Logic & Midnight Bridge:
- Dual-buffer NVS system for today and tomorrow data
- Midnight Bridge for seamless day rollover
+88 -2
View File
@@ -2,13 +2,99 @@
## Current firmware
- **Version:** 7.1
- **Release date:** 2026-04-06
- **Version:** 7.2
- **Release date:** 2026-08-04
- **Target MCU:** Seeed XIAO ESP32C3
- **Display:** 20x4 I²C LCD (PCF8574, default address `0x27`)
- **API endpoint:** `https://api.energy-charts.info/price?bzn=SI`
- **Resolution:** 15minute intervals, hourly averages for overview
## Highlights of v7.2
### Button Robustness & Screen-Control Fixes
Bug-fix release that resolves the three control glitches reported for the v7.1
firmware on the Seeed XIAO ESP32C3: the primary screen looked unscrollable,
double-clicks failed to switch to the secondary status screen, and the end-of-day
behaviour (no tomorrow data yet) was unstable around 22:0023:59. No fee/VAT
math, NVS layout, API scheduling or 48-hour scrolling behaviour was changed.
#### Fix 1 Primary-screen scroll now works at any hour of the day
Two compounding bugs in `displayPrimaryList()` and `displayPriceRow()` were
cancelling each other out and made single-click scrolling look dead:
- `displayPriceRow()` only blanked past hours while `currentHour < 22`. After
22:00 the screen could repaint already-finished morning hours, so as soon
as the user scrolled forward the new "top" hour was visually over-written
by the previous morning's data. The guard is now
`if (localHourIndex < currentHour) blank();`, so past hours of today are
hidden at every hour of the day.
- `displayPrimaryList()` contained an override
`if (currentHour >= 21 && timeOffsetHours > 0) displayStartHourOffset = 21 + timeOffsetHours;`
which pinned the top row at 21:00 + offset from 21:00 onward. At 22:15
every click computed start = 22+offset, was then clamped to 21+offset, and
the user saw no movement. The override is no longer needed and has been
removed.
#### Fix 2 Double-click on the secondary screen now fires reliably
The double-click path itself was correct; it was being starved by a false
"Long press detected!" message that fired immediately after every reset.
On the ESP32-C3 the button pin (`INPUT_PULLUP`) floats HIGH for a few
seconds during boot, while `buttonPressStartTime` is initialised to `0`.
As soon as `millis()` crossed the 3 s threshold, the long-press detector
tripped on a phantom 3-second hold, cleared the LCD to
"Long press detected! / Release to refresh", and from then on the user
could not see any prices to click on (single- and double-click recognisers
both still ran, but their visible effect was hidden behind the long-press
splash).
Fix: a new `bool buttonEverReleased` is set to `true` the first time the
pin is observed LOW after boot, and the long-press detector is gated on
it: `if (buttonState == LOW && !longPressDetected && buttonEverReleased)`.
The detector refuses to fire until the user (or the power-rail noise) has
released the button at least once. The v7.1 button logic (debounce, 3 s
long-press threshold, 500 ms double-click window, TTP223 timing) is
otherwise preserved verbatim.
#### Fix 3 End-of-day scroll is now stable when no tomorrow data is available
`advanceDisplayOffset()` contained a hack
`if (allowedAhead < 2 && currentHour >= 21 && !isTomorrowDataAvailable) allowedAhead = 2;`
which at 22:00 (with no tomorrow data) let the user click past hour 23 into
"24:00 / 25:00", where `displayStartHourOffset` wrapped back to 0 and filled
the LCD with the now-unblanked past-hour rows. The hack has been removed;
the natural cap is now sufficient:
- **22:00** → can step 22 → 23, then wraps back to current
- **23:00** → cannot step forward at all
No wrap to 00:00 of the previous day is reachable any more.
#### Fix 4 (bonus) Auto-return timer now resets on every click
`lastButtonActivity` / `autoScrollExecuted` were only updated in the
primary-list branches of `advanceDisplayOffset()`. Scrolling the secondary
status page therefore did not push the 10 s auto-return-to-top timeout
forward. The two resets are now at the top of `advanceDisplayOffset()` so
every successful click (single, double, or long-press-release) refreshes
the timer regardless of which list is showing.
#### Cosmetic / non-behavioural changes
- Filename and three user-visible version strings bumped to v7.2:
- `connectToWiFi()` splash: `"Elec. Rate SI v7.1"``"v7.2"`
- `displaySecondaryList()` credit line: `"price ticker v7.1"``"v7.2"`
- `setup()` debug banner: `"v7.1 (Neg Price Fee)"``"v7.2 (Button Robustness)"`
- Inline comments added at each fix site explaining what v7.1 did wrong,
so future maintainers do not re-introduce the overrides.
- New global flag `bool buttonEverReleased` (see Fix 2).
See `CHANGELOG.md` for full implementation details.
---
## Highlights of v7.1
### Negative Price Provider Fee