Skip to content

Commit

Permalink
relay: timer & pulse instead of toggle param
Browse files Browse the repository at this point in the history
- replace 'pulse #ID TIME TOGGLE' TOGGLE param with 'timer #ID TIME'
- drop timers list command, use 'pulse' or 'timer' interchangeably
- adjust api to use new command string, allow mqtt & http to pulse without 'toggle'

amends 530b64b
  • Loading branch information
mcspr committed Aug 23, 2024
1 parent 81d561e commit 9082785
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 62 deletions.
1 change: 1 addition & 0 deletions code/espurna/mqtt.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ Updated secure client support by Niek van der Maas < mail at niekvandermaas dot
#define MQTT_TOPIC_LOADAVG "loadavg"
#define MQTT_TOPIC_BOARD "board"
#define MQTT_TOPIC_PULSE "pulse"
#define MQTT_TOPIC_TIMER "timer"
#define MQTT_TOPIC_SPEED "speed"
#define MQTT_TOPIC_OTA "ota"
#define MQTT_TOPIC_TELNET_REVERSE "telnet_reverse"
Expand Down
170 changes: 108 additions & 62 deletions code/espurna/relay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1730,33 +1730,57 @@ void _relayProcessPulse(const Relay& relay, size_t id, bool status) {
// duration equal to 0 would cancel existing timer
// duration greater than 0 would toggle relay and schedule a timer
[[gnu::unused]]
void _relayHandlePulseResult(size_t id, espurna::duration::PairResult result) {
void _relayHandleTimerNative(size_t id, espurna::relay::pulse::Duration duration, bool toggle) {
const auto status = relayStatus(id);
const auto target = toggle ? status : !status;

auto timer =
espurna::relay::pulse::schedule(duration, id, target);

if (toggle) {
relayToggle(id, true, false);
} else {
(*timer).start();
}
}

[[gnu::unused]]
void _relayHandleTimerResult(size_t id, espurna::duration::PairResult result, bool toggle) {
using namespace espurna::relay;

const auto native = pulse::settings::native_duration(result);
if (native.count() == 0) {
pulse::reset(id);
}

pulse::schedule(native, id, relayStatus(id));
relayToggle(id, true, false);
_relayHandleTimerNative(id, native, toggle);
}

// expects generic time input which is converted to float first
[[gnu::unused]]
bool _relayHandlePulsePayload(size_t id, espurna::StringView payload) {
bool _relayHandleTimerPayloadImpl(size_t id, espurna::StringView payload, bool toggle) {

using namespace espurna::relay;
const auto result = pulse::settings::parse_time(payload);

if (result.ok) {
_relayHandlePulseResult(id, result);
_relayHandleTimerResult(id, result, toggle);
return true;
}

return false;
}

[[gnu::unused]]
bool _relayHandlePulsePayload(size_t id, espurna::StringView payload) {
return _relayHandleTimerPayloadImpl(id, payload, true);
}

[[gnu::unused]]
bool _relayHandleTimerPayload(size_t id, espurna::StringView payload) {
return _relayHandleTimerPayloadImpl(id, payload, false);
}

// Make sure expired pulse timers are removed, so any API calls don't try to re-use those
void _relayRemoveCompletedPulse() {
espurna::relay::pulse::removeCompleted();
Expand Down Expand Up @@ -2462,6 +2486,14 @@ bool _relayApiTryHandle(ApiRequest& request, T&& callback) {
return callback(id);
}

bool _relayApiTimerGet(ApiRequest& request, size_t id) {
using namespace espurna::relay::pulse;
const auto duration = findDuration(id);
const auto seconds = std::chrono::duration_cast<Seconds>(duration);
request.send(String(seconds.count(), 10));
return true;
}

} // namespace

void relaySetupAPI() {
Expand Down Expand Up @@ -2498,11 +2530,7 @@ void relaySetupAPI() {
apiRegister(F(MQTT_TOPIC_PULSE "/+"),
[](ApiRequest& request) {
return _relayApiTryHandle(request, [&](size_t id) {
using namespace espurna::relay::pulse;
const auto duration = findDuration(id);
const auto seconds = std::chrono::duration_cast<Seconds>(duration);
request.send(String(seconds.count(), 10));
return true;
return _relayApiTimerGet(request, id);
});
},
[](ApiRequest& request) {
Expand All @@ -2512,6 +2540,19 @@ void relaySetupAPI() {
}
);

apiRegister(F(MQTT_TOPIC_TIMER "/+"),
[](ApiRequest& request) {
return _relayApiTryHandle(request, [&](size_t id) {
return _relayApiTimerGet(request, id);
});
},
[](ApiRequest& request) {
return _relayApiTryHandle(request, [&](size_t id) {
return _relayHandleTimerPayload(id, request.param(F("value")));
});
}
);

apiRegister(F(MQTT_TOPIC_LOCK "/+"),
[](ApiRequest& request) {
return _relayApiTryHandle(request,
Expand Down Expand Up @@ -2635,6 +2676,7 @@ struct RelayCustomTopic {
void _relayMqttSubscribeBaseTopics() {
mqttSubscribe(MQTT_TOPIC_RELAY "/+");
mqttSubscribe(MQTT_TOPIC_PULSE "/+");
mqttSubscribe(MQTT_TOPIC_TIMER "/+");
mqttSubscribe(MQTT_TOPIC_LOCK "/+");
}

Expand Down Expand Up @@ -2817,11 +2859,13 @@ struct RelayMqttTopicHandler {

PROGMEM_STRING(MqttTopicRelay, MQTT_TOPIC_RELAY);
PROGMEM_STRING(MqttTopicPulse, MQTT_TOPIC_PULSE);
PROGMEM_STRING(MqttTopicTimer, MQTT_TOPIC_TIMER);
PROGMEM_STRING(MqttTopicLock, MQTT_TOPIC_LOCK);

static constexpr RelayMqttTopicHandler RelayMqttTopicHandlers[] PROGMEM {
{MqttTopicRelay, _relayHandlePayload},
{MqttTopicPulse, _relayHandlePulsePayload},
{MqttTopicTimer, _relayHandleTimerPayload},
{MqttTopicLock, _relayHandleLockPayload},
};

Expand Down Expand Up @@ -2945,9 +2989,50 @@ static void _relayCommand(::terminal::CommandContext&& ctx) {

PROGMEM_STRING(PulseCommand, "PULSE");

static void _relayCommandPulse(::terminal::CommandContext&& ctx) {
if (ctx.argv.size() < 2) {
terminalError(ctx, F("PULSE <ID> [<TIME>] [<TOGGLE>]"));
static void _relayCommandDumpTimers(::terminal::CommandContext&& ctx) {
using namespace espurna::relay;

if (pulse::internal::timers.empty()) {
terminalError(ctx, STRING_VIEW("no active timers").toString());
return;
}

for (auto& timer : pulse::internal::timers) {
espurna::StringView type;
if (relayStatus(timer.id()) == timer.status()) {
type = STRING_VIEW("Stalled");
} else if (static_cast<bool>(timer)) {
type = STRING_VIEW("Active");
} else {
type = STRING_VIEW("Pending");
}

ctx.output.printf_P(
PSTR("timer%zu\t{%.*s Duration=%u Status=%s}\n"),
timer.id(),
type.length(), type.data(),
timer.duration().count(),
timer.status() ? PSTR("ON") : PSTR("OFF"));
}

terminalOK(ctx);
}

static void _relayCommandPulseImpl(::terminal::CommandContext&& ctx, bool toggle) {
if (ctx.argv.size() > 3) {
String name = ctx.argv[0];
name.toUpperCase();

const auto error = name + STRING_VIEW("[<ID>] [<TIME>]").toString();
terminalError(ctx, error);

return;
}

using namespace espurna::relay;

if (ctx.argv.size() == 1) {
_relayCommandDumpTimers(std::move(ctx));
return;
}

Expand All @@ -2957,13 +3042,12 @@ static void _relayCommandPulse(::terminal::CommandContext&& ctx) {
return;
}

using namespace espurna::relay;
auto duration = pulse::Duration{ 0 };

if (ctx.argv.size() >= 3) {
if (ctx.argv.size() == 3) {
const auto parsed = pulse::settings::parse_time(ctx.argv[2]);
if (!parsed.ok) {
terminalError(ctx, F("Invalid pulse time"));
terminalError(ctx, F("Invalid time"));
return;
}

Expand All @@ -2976,56 +3060,18 @@ static void _relayCommandPulse(::terminal::CommandContext&& ctx) {
return;
}

bool toggle = true;
if (ctx.argv.size() >= 4) {
auto* convert = espurna::settings::internal::convert<bool>;
toggle = convert(ctx.argv[3]);
}

const auto status = relayStatus(id);
auto timer = pulse::schedule(
duration, id,
toggle
? status
: !status);

if (toggle) {
relayToggle(id, true, false);
} else {
(*timer).start();
}

_relayHandleTimerNative(id, duration, toggle);
terminalOK(ctx);
}

PROGMEM_STRING(PulseTimersCommand, "PULSE.TIMERS");

static void _relayCommandPulseTimers(::terminal::CommandContext&& ctx) {
using namespace espurna::relay;
if (pulse::internal::timers.empty()) {
terminalError(ctx, STRING_VIEW("no pulse timers").toString());
return;
}

for (auto& timer : pulse::internal::timers) {
espurna::StringView type;
if (relayStatus(timer.id()) == timer.status()) {
type = STRING_VIEW("Stalled");
} else if (static_cast<bool>(timer)) {
type = STRING_VIEW("Active");
} else {
type = STRING_VIEW("Pending");
}
static void _relayCommandPulse(::terminal::CommandContext&& ctx) {
_relayCommandPulseImpl(std::move(ctx), true);
}

ctx.output.printf_P(
PSTR("pulse%zu\t{%.*s Duration=%u Status=%s}\n"),
timer.id(),
type.length(), type.data(),
timer.duration().count(),
timer.status() ? PSTR("ON") : PSTR("OFF"));
}
PROGMEM_STRING(TimerCommand, "TIMER");

terminalOK(ctx);
static void _relayCommandTimer(::terminal::CommandContext&& ctx) {
_relayCommandPulseImpl(std::move(ctx), false);
}

PROGMEM_STRING(LockCommand, "LOCK");
Expand Down Expand Up @@ -3098,7 +3144,7 @@ static void _relayCommandUnlock(::terminal::CommandContext&& ctx) {
static constexpr ::terminal::Command RelayCommands[] PROGMEM {
{RelayCommand, _relayCommand},
{PulseCommand, _relayCommandPulse},
{PulseTimersCommand, _relayCommandPulseTimers},
{TimerCommand, _relayCommandTimer},
{LockCommand, _relayCommandLock},
{UnlockCommand, _relayCommandUnlock},
};
Expand Down

0 comments on commit 9082785

Please sign in to comment.