Skip to content

Commit

Permalink
system: generic duration parser and sleep commands
Browse files Browse the repository at this point in the history
updated duration parser with a fixed type instead of just millis
separate decimal and floating point converter as well, try to avoid
possible precision loss with microseconds use
  • Loading branch information
mcspr committed Mar 15, 2023
1 parent 6441a68 commit 0e86115
Show file tree
Hide file tree
Showing 8 changed files with 506 additions and 279 deletions.
58 changes: 42 additions & 16 deletions code/espurna/relay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -327,8 +327,6 @@ enum class Mode {
} // namespace relay
} // namespace espurna

#include "relay_pulse.ipp"

namespace espurna {
namespace relay {
namespace pulse {
Expand Down Expand Up @@ -385,13 +383,36 @@ PROGMEM_STRING(Mode, "relayPulse");

namespace {

Result time(size_t index) {
const auto time = espurna::settings::get(espurna::settings::Key{keys::Time, index}.value());
using ParseResult = espurna::settings::internal::duration_convert::Result;

Duration native_duration(ParseResult result) {
using namespace espurna::settings::internal;

if (result.ok) {
return duration_convert::to_chrono_duration<Duration>(result.value);
}

return Duration::min();
}

ParseResult parse_time(StringView view) {
using namespace espurna::settings::internal;
return duration_convert::parse(view, Seconds::period{});
}

Duration native_duration(StringView view) {
return native_duration(parse_time(view));
}

Duration time(size_t index) {
const auto time = espurna::settings::get(
espurna::settings::Key{keys::Time, index}.value());

if (!time) {
return Result(std::chrono::duration_cast<Duration>(build::time(index)));
return std::chrono::duration_cast<Duration>(build::time(index));
}

return parse(time.ref());
return native_duration(time.view());
}

Mode mode(size_t index) {
Expand Down Expand Up @@ -1001,7 +1022,9 @@ ID_VALUE(delayOff, settings::delayOff)
ID_VALUE(pulseMode, pulse::settings::mode)
String pulseTime(size_t index) {
const auto result = pulse::settings::time(index);
const auto as_seconds = std::chrono::duration_cast<pulse::Seconds>(result.duration());
const auto as_seconds =
std::chrono::duration_cast<pulse::Seconds>(result);

return espurna::settings::internal::serialize(as_seconds.count());
}

Expand Down Expand Up @@ -1637,10 +1660,11 @@ bool _relayHandlePulsePayload(size_t id, espurna::StringView payload) {
return false;
}

using namespace espurna::relay::pulse;
const auto pulse = parse(payload);
if (pulse) {
trigger(pulse.duration(), id, status);
using namespace espurna::relay::pulse::settings;
const auto pulse = parse_time(payload);

if (pulse.ok) {
espurna::relay::pulse::trigger(native_duration(pulse), id, status);
relayToggle(id, true, false);

return true;
Expand Down Expand Up @@ -2213,8 +2237,8 @@ void _relayConfigure() {

relay.pulse = espurna::relay::pulse::settings::mode(id);
relay.pulse_time = (relay.pulse != espurna::relay::pulse::Mode::None)
? espurna::relay::pulse::settings::time(id).duration()
: espurna::duration::Milliseconds { 0 };
? espurna::relay::pulse::settings::time(id)
: espurna::relay::pulse::Duration::min();

relay.delay_on = espurna::relay::settings::delayOn(id);
relay.delay_off = espurna::relay::settings::delayOff(id);
Expand Down Expand Up @@ -2808,12 +2832,14 @@ static void _relayCommandPulse(::terminal::CommandContext&& ctx) {
return;
}

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

const auto duration = espurna::relay::pulse::settings::native_duration(time);

bool toggle = true;
if (ctx.argv.size() == 4) {
auto* convert= espurna::settings::internal::convert<bool>;
Expand All @@ -2826,9 +2852,9 @@ static void _relayCommandPulse(::terminal::CommandContext&& ctx) {
return;
}

const auto duration = pulse.duration();
const auto target = toggle ? status : !status;
espurna::relay::pulse::trigger(duration, id, target);

if ((duration.count() > 0) && toggle) {
relayToggle(id, true, false);
}
Expand Down
232 changes: 0 additions & 232 deletions code/espurna/relay_pulse.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -16,238 +16,6 @@ namespace relay {
namespace pulse {
namespace {

struct Result {
Result() = default;
explicit Result(Duration duration) :
_duration(duration)
{}

template <typename T>
Result& operator+=(T duration) {
_result = true;
_duration += std::chrono::duration_cast<Duration>(duration);
return *this;
}

explicit operator bool() const {
return _result;
}

void reset() {
_result = false;
_duration = Duration::min();
}

Duration duration() const {
return _duration;
}

Duration::rep count() const {
return _duration.count();
}

private:
bool _result { false };
Duration _duration { Duration::min() };
};

namespace internal {

enum class Type {
Unknown,
Seconds,
Minutes,
Hours
};

bool validNextType(Type lhs, Type rhs) {
switch (lhs) {
case Type::Unknown:
return true;
case Type::Hours:
return (rhs == Type::Minutes) || (rhs == Type::Seconds);
case Type::Minutes:
return (rhs == Type::Seconds);
case Type::Seconds:
break;
}

return false;
}

Result parse(const char* begin, const char* end) {
Result out;

String token;
Type last { Type::Unknown };
Type type { Type::Unknown };

const char* ptr { begin };
if (!begin || !end || (begin == end)) {
goto output;
}

loop:
while (ptr != end) {
switch (*ptr) {
case '\0':
if (last == Type::Unknown) {
goto update_floating;
}
goto output;
case '0'...'9':
token += (*ptr);
++ptr;
break;
case 'h':
if (validNextType(last, Type::Hours)) {
type = Type::Hours;
goto update_decimal;
}
goto reset;
case 'm':
if (validNextType(last, Type::Minutes)) {
type = Type::Minutes;
goto update_decimal;
}
goto reset;
case 's':
if (validNextType(last, Type::Seconds)) {
type = Type::Seconds;
goto update_decimal;
}
goto reset;
case ',':
case '.':
if (out) {
goto reset;
}
goto read_floating;
}
}

if (token.length()) {
goto update_floating;
}

goto output;

update_floating:
{
char* endp { nullptr };
auto value = strtod(token.c_str(), &endp);
if (endp && (endp != token.c_str()) && endp[0] == '\0') {
out += Seconds(value);
goto output;
}

goto reset;
}

update_decimal:
last = type;
++ptr;

if (type != Type::Unknown) {
const auto result = parseUnsigned(token, 10);
if (result.ok) {
switch (type) {
case Type::Hours: {
out += ::espurna::duration::Hours { result.value };
break;
}
case Type::Minutes: {
out += ::espurna::duration::Minutes { result.value };
break;
}
case Type::Seconds: {
out += ::espurna::duration::Seconds { result.value };
break;
}
case Type::Unknown:
goto reset;
}

type = Type::Unknown;
token = "";

goto loop;
}
}

goto reset;

read_floating:
switch (*ptr) {
case ',':
case '.':
token += '.';
++ptr;
break;
default:
goto reset;
}

while (ptr != end) {
switch (*ptr) {
case '\0':
goto update_floating;
case '0'...'9':
token += (*ptr);
break;
case 'e':
case 'E':
token += (*ptr);
++ptr;

while (ptr != end) {
switch (*ptr) {
case '\0':
goto reset;
case '-':
case '+':
token += (*ptr);
++ptr;
goto read_floating_exponent;
case '0'...'9':
goto read_floating_exponent;
}
}

goto reset;
case ',':
case '.':
goto reset;
}

++ptr;
}

goto update_floating;

read_floating_exponent:
while (ptr != end) {
switch (*ptr) {
case '0'...'9':
token += *(ptr);
++ptr;
break;
}

goto reset;
}

goto update_floating;

reset:
out.reset();

output:
return out;
}

} // namespace internal

Result parse(StringView value) {
return internal::parse(value.begin(), value.end());
}
Expand Down
Loading

0 comments on commit 0e86115

Please sign in to comment.