Respect values over 11.1s.

This commit is contained in:
Holger Weber
2026-05-15 00:54:34 +02:00
parent d2ab1369f6
commit 8927a65573
7 changed files with 114 additions and 12 deletions

View File

@@ -1,8 +1,8 @@
# Tasmota remote pulse timer trigger
Das Programm für einen ESP8266 soll den PulseTimer einer Tasmota Steckdose triggern.
Die Hardware hat einen Rotary Encoder sowie ein OLED Display.
Mit dem Rotary Encoder (RT) stellt man eine Zeit im Bereich von 2s bis 10s mit einer Auflösung von 100ms.
Über den RT soll der Wert pro Tick um 100ms angepasst werden können.
Die Hardware hat einen Rotary Encoder sowie ein OLED Display.
Mit dem Rotary Encoder (RT) stellt man eine Zeit im Bereich von `MIN_TIMER_MS` bis `MAX_TIMER_MS`.
Über den RT soll der Wert pro Tick bis 11,1s um 100ms angepasst werden. Ab 11,1s erlaubt Tasmota nur noch Sekundenwerte; der nächste Wert nach 11,1s ist deshalb 12s, danach wird pro Tick um 1s angepasst.
Mit Druck auf den Taster des RT soll zunächst der PulseTimer eingestellt und dann getriggert werden.
Die Einstellungen für die SSID und das Passwort sollen aus einer separaten config.h Datei ausgelesen werden.
Dort soll auch die IP Adresse der Tasmota Steckdose eingestellt werden. Zudem dort noch den Initialwert für den Timer hinterlegen.
@@ -10,7 +10,7 @@ Der Project soll mittels PlatformIO und Arduino Framework umgesetzt werden. Das
## Umsetzung
Die Tasmota Steckdose wird per HTTP angesprochen. Beim Druck auf den Taster sendet das Programm:
1. `PulseTime1 <wert>` mit `<wert>` in 100ms-Schritten. Der erlaubte Bereich 2s bis 10s entspricht damit `20` bis `100`.
1. `PulseTime1 <wert>`. Werte `1` bis `111` entsprechen 0,1s-Schritten bis 11,1s. Werte ab `112` entsprechen Sekundenwerten mit Offset 100, also z.B. `112` fuer 12s und `113` fuer 13s.
2. `Power1 ON`, um den zuvor gesetzten PulseTimer auszulösen.
Die Datei `include/config.h` enthält:

View File

@@ -9,8 +9,11 @@ namespace trbc {
constexpr uint16_t kMinTimerMs = MIN_TIMER_MS;
constexpr uint16_t kMaxTimerMs = MAX_TIMER_MS;
constexpr uint16_t kTimerStepMs = 100;
constexpr uint16_t kTasmotaTenthsLimitMs = 11100;
constexpr uint16_t kTasmotaSecondsStartMs = 12000;
uint16_t clampTimerMs(int valueMs);
uint16_t normalizeTimerMs(int valueMs);
uint16_t adjustTimerMs(uint16_t currentMs, int ticks);
uint16_t tasmotaPulseTimeValue(uint16_t timerMs);

View File

@@ -7,4 +7,4 @@
#define TASMOTA_HOST "192.168.1.50"
#define INITIAL_TIMER_MS 5000
#define MIN_TIMER_MS 2000
#define MAX_TIMER_MS 10000
#define MAX_TIMER_MS 13000

View File

@@ -7,5 +7,5 @@
#define WIFI_PASSWORD "!Sternenlabor99!"
#define TASMOTA_HOST "192.168.240.101"
#define INITIAL_TIMER_MS 5000
#define MIN_TIMER_MS 2000
#define MAX_TIMER_MS 13000
#define MIN_TIMER_MS 0
#define MAX_TIMER_MS (2 * 60 * 1000)

View File

@@ -2,6 +2,30 @@
namespace trbc {
namespace {
uint16_t highestRepresentableAtOrBelow(uint16_t valueMs) {
if (valueMs <= kTasmotaTenthsLimitMs) {
return clampTimerMs((valueMs / kTimerStepMs) * kTimerStepMs);
}
if (valueMs < kTasmotaSecondsStartMs) {
return kTasmotaTenthsLimitMs;
}
return clampTimerMs((valueMs / 1000) * 1000);
}
uint16_t lowestRepresentableAtOrAbove(uint16_t valueMs) {
if (valueMs <= kTasmotaTenthsLimitMs) {
return clampTimerMs(((valueMs + kTimerStepMs - 1) / kTimerStepMs) * kTimerStepMs);
}
if (valueMs < kTasmotaSecondsStartMs) {
return clampTimerMs(kTasmotaSecondsStartMs);
}
return clampTimerMs(((valueMs + 999) / 1000) * 1000);
}
} // namespace
uint16_t clampTimerMs(int valueMs) {
if (valueMs < kMinTimerMs) {
return kMinTimerMs;
@@ -12,13 +36,70 @@ uint16_t clampTimerMs(int valueMs) {
return static_cast<uint16_t>(valueMs);
}
uint16_t normalizeTimerMs(int valueMs) {
const uint16_t clamped = clampTimerMs(valueMs);
if (clamped <= kTasmotaTenthsLimitMs) {
uint16_t rounded = ((clamped + (kTimerStepMs / 2)) / kTimerStepMs) * kTimerStepMs;
if (rounded > kTasmotaTenthsLimitMs) {
rounded = kTasmotaTenthsLimitMs;
}
return clampTimerMs(rounded);
}
uint16_t rounded = ((clamped + 500) / 1000) * 1000;
if (rounded < kTasmotaSecondsStartMs) {
rounded = kTasmotaSecondsStartMs;
}
if (rounded > kMaxTimerMs) {
return highestRepresentableAtOrBelow(kMaxTimerMs);
}
if (rounded < kMinTimerMs) {
return lowestRepresentableAtOrAbove(kMinTimerMs);
}
return rounded;
}
uint16_t adjustTimerMs(uint16_t currentMs, int ticks) {
const int adjusted = static_cast<int>(currentMs) + ticks * kTimerStepMs;
return clampTimerMs(adjusted);
uint16_t adjusted = normalizeTimerMs(currentMs);
const int direction = ticks < 0 ? -1 : 1;
for (int remaining = ticks < 0 ? -ticks : ticks; remaining > 0; remaining--) {
uint16_t next = adjusted;
if (direction > 0) {
if (adjusted < kTasmotaTenthsLimitMs) {
next = adjusted + kTimerStepMs;
} else if (adjusted < kTasmotaSecondsStartMs) {
next = kTasmotaSecondsStartMs;
} else {
next = adjusted + 1000;
}
if (next > kMaxTimerMs || next < adjusted) {
return highestRepresentableAtOrBelow(kMaxTimerMs);
}
} else {
if (adjusted > kTasmotaSecondsStartMs) {
next = adjusted - 1000;
} else if (adjusted > kTasmotaTenthsLimitMs) {
next = kTasmotaTenthsLimitMs;
} else {
next = adjusted - kTimerStepMs;
}
if (next < kMinTimerMs || next > adjusted) {
return lowestRepresentableAtOrAbove(kMinTimerMs);
}
}
adjusted = next;
}
return adjusted;
}
uint16_t tasmotaPulseTimeValue(uint16_t timerMs) {
return clampTimerMs(timerMs) / kTimerStepMs;
const uint16_t normalized = normalizeTimerMs(timerMs);
if (normalized <= kTasmotaTenthsLimitMs) {
return normalized / kTimerStepMs;
}
return (normalized / 1000) + 100;
}
} // namespace trbc

View File

@@ -20,7 +20,7 @@ constexpr unsigned long kWifiRetryMs = 10000;
constexpr unsigned long kButtonDebounceMs = 35;
constexpr unsigned long kStatusHoldMs = 2500;
uint16_t timerMs = trbc::clampTimerMs(INITIAL_TIMER_MS);
uint16_t timerMs = trbc::normalizeTimerMs(INITIAL_TIMER_MS);
bool needsDisplayUpdate = true;
unsigned long lastDisplayRefresh = 0;
unsigned long lastWifiAttempt = 0;

View File

@@ -13,22 +13,40 @@ void test_adjusts_in_100_ms_steps() {
TEST_ASSERT_EQUAL_UINT16(4700, trbc::adjustTimerMs(5000, -3));
}
void test_adjusts_in_seconds_above_tasmota_threshold() {
TEST_ASSERT_EQUAL_UINT16(11100, trbc::adjustTimerMs(11000, 1));
TEST_ASSERT_EQUAL_UINT16(12000, trbc::adjustTimerMs(11100, 1));
TEST_ASSERT_EQUAL_UINT16(13000, trbc::adjustTimerMs(12000, 1));
TEST_ASSERT_EQUAL_UINT16(12000, trbc::adjustTimerMs(13000, -1));
TEST_ASSERT_EQUAL_UINT16(11100, trbc::adjustTimerMs(12000, -1));
}
void test_adjustment_stays_in_range() {
TEST_ASSERT_EQUAL_UINT16(MIN_TIMER_MS, trbc::adjustTimerMs(MIN_TIMER_MS, -1));
TEST_ASSERT_EQUAL_UINT16(MAX_TIMER_MS, trbc::adjustTimerMs(MAX_TIMER_MS, 1));
}
void test_normalizes_to_tasmota_representable_values() {
TEST_ASSERT_EQUAL_UINT16(5500, trbc::normalizeTimerMs(5450));
TEST_ASSERT_EQUAL_UINT16(12000, trbc::normalizeTimerMs(11400));
TEST_ASSERT_EQUAL_UINT16(12000, trbc::normalizeTimerMs(11500));
}
void test_converts_to_tasmota_pulsetime_units() {
TEST_ASSERT_EQUAL_UINT16(20, trbc::tasmotaPulseTimeValue(2000));
TEST_ASSERT_EQUAL_UINT16(55, trbc::tasmotaPulseTimeValue(5500));
TEST_ASSERT_EQUAL_UINT16(100, trbc::tasmotaPulseTimeValue(10000));
TEST_ASSERT_EQUAL_UINT16(111, trbc::tasmotaPulseTimeValue(11100));
TEST_ASSERT_EQUAL_UINT16(112, trbc::tasmotaPulseTimeValue(12000));
TEST_ASSERT_EQUAL_UINT16(113, trbc::tasmotaPulseTimeValue(13000));
}
int main() {
UNITY_BEGIN();
RUN_TEST(test_clamps_timer_range);
RUN_TEST(test_adjusts_in_100_ms_steps);
RUN_TEST(test_adjusts_in_seconds_above_tasmota_threshold);
RUN_TEST(test_adjustment_stays_in_range);
RUN_TEST(test_normalizes_to_tasmota_representable_values);
RUN_TEST(test_converts_to_tasmota_pulsetime_units);
return UNITY_END();
}