Respect values over 11.1s.
This commit is contained in:
6
SPEC.md
6
SPEC.md
@@ -1,8 +1,8 @@
|
|||||||
# Tasmota remote pulse timer trigger
|
# Tasmota remote pulse timer trigger
|
||||||
Das Programm für einen ESP8266 soll den PulseTimer einer Tasmota Steckdose triggern.
|
Das Programm für einen ESP8266 soll den PulseTimer einer Tasmota Steckdose triggern.
|
||||||
Die Hardware hat einen Rotary Encoder sowie ein OLED Display.
|
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.
|
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 um 100ms angepasst werden können.
|
Ü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.
|
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.
|
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.
|
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
|
## Umsetzung
|
||||||
Die Tasmota Steckdose wird per HTTP angesprochen. Beim Druck auf den Taster sendet das Programm:
|
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.
|
2. `Power1 ON`, um den zuvor gesetzten PulseTimer auszulösen.
|
||||||
|
|
||||||
Die Datei `include/config.h` enthält:
|
Die Datei `include/config.h` enthält:
|
||||||
|
|||||||
@@ -9,8 +9,11 @@ namespace trbc {
|
|||||||
constexpr uint16_t kMinTimerMs = MIN_TIMER_MS;
|
constexpr uint16_t kMinTimerMs = MIN_TIMER_MS;
|
||||||
constexpr uint16_t kMaxTimerMs = MAX_TIMER_MS;
|
constexpr uint16_t kMaxTimerMs = MAX_TIMER_MS;
|
||||||
constexpr uint16_t kTimerStepMs = 100;
|
constexpr uint16_t kTimerStepMs = 100;
|
||||||
|
constexpr uint16_t kTasmotaTenthsLimitMs = 11100;
|
||||||
|
constexpr uint16_t kTasmotaSecondsStartMs = 12000;
|
||||||
|
|
||||||
uint16_t clampTimerMs(int valueMs);
|
uint16_t clampTimerMs(int valueMs);
|
||||||
|
uint16_t normalizeTimerMs(int valueMs);
|
||||||
uint16_t adjustTimerMs(uint16_t currentMs, int ticks);
|
uint16_t adjustTimerMs(uint16_t currentMs, int ticks);
|
||||||
uint16_t tasmotaPulseTimeValue(uint16_t timerMs);
|
uint16_t tasmotaPulseTimeValue(uint16_t timerMs);
|
||||||
|
|
||||||
|
|||||||
@@ -7,4 +7,4 @@
|
|||||||
#define TASMOTA_HOST "192.168.1.50"
|
#define TASMOTA_HOST "192.168.1.50"
|
||||||
#define INITIAL_TIMER_MS 5000
|
#define INITIAL_TIMER_MS 5000
|
||||||
#define MIN_TIMER_MS 2000
|
#define MIN_TIMER_MS 2000
|
||||||
#define MAX_TIMER_MS 10000
|
#define MAX_TIMER_MS 13000
|
||||||
|
|||||||
@@ -7,5 +7,5 @@
|
|||||||
#define WIFI_PASSWORD "!Sternenlabor99!"
|
#define WIFI_PASSWORD "!Sternenlabor99!"
|
||||||
#define TASMOTA_HOST "192.168.240.101"
|
#define TASMOTA_HOST "192.168.240.101"
|
||||||
#define INITIAL_TIMER_MS 5000
|
#define INITIAL_TIMER_MS 5000
|
||||||
#define MIN_TIMER_MS 2000
|
#define MIN_TIMER_MS 0
|
||||||
#define MAX_TIMER_MS 13000
|
#define MAX_TIMER_MS (2 * 60 * 1000)
|
||||||
|
|||||||
@@ -2,6 +2,30 @@
|
|||||||
|
|
||||||
namespace trbc {
|
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) {
|
uint16_t clampTimerMs(int valueMs) {
|
||||||
if (valueMs < kMinTimerMs) {
|
if (valueMs < kMinTimerMs) {
|
||||||
return kMinTimerMs;
|
return kMinTimerMs;
|
||||||
@@ -12,13 +36,70 @@ uint16_t clampTimerMs(int valueMs) {
|
|||||||
return static_cast<uint16_t>(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) {
|
uint16_t adjustTimerMs(uint16_t currentMs, int ticks) {
|
||||||
const int adjusted = static_cast<int>(currentMs) + ticks * kTimerStepMs;
|
uint16_t adjusted = normalizeTimerMs(currentMs);
|
||||||
return clampTimerMs(adjusted);
|
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) {
|
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
|
} // namespace trbc
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ constexpr unsigned long kWifiRetryMs = 10000;
|
|||||||
constexpr unsigned long kButtonDebounceMs = 35;
|
constexpr unsigned long kButtonDebounceMs = 35;
|
||||||
constexpr unsigned long kStatusHoldMs = 2500;
|
constexpr unsigned long kStatusHoldMs = 2500;
|
||||||
|
|
||||||
uint16_t timerMs = trbc::clampTimerMs(INITIAL_TIMER_MS);
|
uint16_t timerMs = trbc::normalizeTimerMs(INITIAL_TIMER_MS);
|
||||||
bool needsDisplayUpdate = true;
|
bool needsDisplayUpdate = true;
|
||||||
unsigned long lastDisplayRefresh = 0;
|
unsigned long lastDisplayRefresh = 0;
|
||||||
unsigned long lastWifiAttempt = 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));
|
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() {
|
void test_adjustment_stays_in_range() {
|
||||||
TEST_ASSERT_EQUAL_UINT16(MIN_TIMER_MS, trbc::adjustTimerMs(MIN_TIMER_MS, -1));
|
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));
|
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() {
|
void test_converts_to_tasmota_pulsetime_units() {
|
||||||
TEST_ASSERT_EQUAL_UINT16(20, trbc::tasmotaPulseTimeValue(2000));
|
TEST_ASSERT_EQUAL_UINT16(20, trbc::tasmotaPulseTimeValue(2000));
|
||||||
TEST_ASSERT_EQUAL_UINT16(55, trbc::tasmotaPulseTimeValue(5500));
|
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() {
|
int main() {
|
||||||
UNITY_BEGIN();
|
UNITY_BEGIN();
|
||||||
RUN_TEST(test_clamps_timer_range);
|
RUN_TEST(test_clamps_timer_range);
|
||||||
RUN_TEST(test_adjusts_in_100_ms_steps);
|
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_adjustment_stays_in_range);
|
||||||
|
RUN_TEST(test_normalizes_to_tasmota_representable_values);
|
||||||
RUN_TEST(test_converts_to_tasmota_pulsetime_units);
|
RUN_TEST(test_converts_to_tasmota_pulsetime_units);
|
||||||
return UNITY_END();
|
return UNITY_END();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user