Respect values over 11.1s.
This commit is contained in:
8
SPEC.md
8
SPEC.md
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user