From 3d580f35774e1d547e4127723e54881c26e94e09 Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Fri, 23 Sep 2022 11:46:19 +0300 Subject: [PATCH] terminal: __FlashStringHelper -> StringView - clean-up comparison routines and get rid of most of the casts - reduces total number of calls to terminalRegisterCommand replace with a func accepting list of commands, which in turn get instantiated as constexpr PROGMEM arrays - reduce ram fragmentation, since we don't need to alloc as much - reduce flash consumption b/c of lambda -> standalone func conversion --- code/espurna/button.cpp | 56 +++-- code/espurna/crash.cpp | 19 +- code/espurna/debug.cpp | 42 +++- code/espurna/gpio.cpp | 20 +- code/espurna/homeassistant.cpp | 28 ++- code/espurna/i2c.cpp | 66 +++-- code/espurna/ifan.cpp | 40 ++- code/espurna/influxdb.cpp | 30 ++- code/espurna/ir.cpp | 52 ++-- code/espurna/led.cpp | 56 +++-- code/espurna/light.cpp | 301 +++++++++++++---------- code/espurna/lightfox.cpp | 36 ++- code/espurna/mqtt.cpp | 62 +++-- code/espurna/network.cpp | 20 +- code/espurna/nofuss.cpp | 18 +- code/espurna/ntp.cpp | 16 +- code/espurna/ota_asynctcp.cpp | 26 +- code/espurna/ota_httpupdate.cpp | 26 +- code/espurna/pwm.cpp | 46 ++-- code/espurna/relay.cpp | 99 ++++---- code/espurna/rfbridge.cpp | 113 +++++---- code/espurna/rpnrules.cpp | 146 +++++++---- code/espurna/rtcmem.cpp | 139 +++++++---- code/espurna/sensor.cpp | 149 ++++++----- code/espurna/sensors/PZEM004TSensor.h | 155 ++++++------ code/espurna/sensors/PZEM004TV30Sensor.h | 42 ++-- code/espurna/settings.cpp | 47 +++- code/espurna/storage_eeprom.cpp | 83 ++++--- code/espurna/telnet.cpp | 65 +++-- code/espurna/terminal.cpp | 74 ++++-- code/espurna/terminal.h | 3 +- code/espurna/terminal_commands.cpp | 70 +++--- code/espurna/terminal_commands.h | 22 +- code/espurna/tuya.cpp | 74 ++++-- code/espurna/types.h | 5 + code/espurna/wifi.cpp | 239 ++++++++++-------- code/test/unit/src/terminal/terminal.cpp | 48 +++- 37 files changed, 1556 insertions(+), 977 deletions(-) diff --git a/code/espurna/button.cpp b/code/espurna/button.cpp index 0af69449..71d012ef 100644 --- a/code/espurna/button.cpp +++ b/code/espurna/button.cpp @@ -767,6 +767,39 @@ void setup() { } // namespace } // namespace query } // namespace settings + +namespace terminal { + +void button(::terminal::CommandContext&& ctx) { + if (ctx.argv.size() == 2) { + size_t id; + if (!tryParseId(ctx.argv[1].c_str(), buttonCount, id)) { + terminalError(ctx, F("Invalid button ID")); + return; + } + + settingsDump(ctx, espurna::button::settings::query::IndexedSettings, id); + terminalOK(ctx); + return; + } + + size_t id { 0 }; + for (const auto& button : espurna::button::internal::buttons) { + ctx.output.printf_P( + PSTR("button%u {%s}\n"), id++, + button.event_emitter + ? (button.event_emitter->pin()->description().c_str()) + : PSTR("Virtual")); + } +} + +alignas(4) static constexpr char Button[] PROGMEM = "button"; + +static constexpr ::terminal::Command Commands[] PROGMEM { + {Button, button}, +}; + +} // namespace terminal } // namespace button } // namespace espurna @@ -1414,28 +1447,7 @@ void buttonSetup() { DEBUG_MSG_P(PSTR("[BUTTON] Number of buttons: %u\n"), count); #if TERMINAL_SUPPORT - terminalRegisterCommand(F("BUTTON"), [](::terminal::CommandContext&& ctx) { - if (ctx.argv.size() == 2) { - size_t id; - if (!tryParseId(ctx.argv[1].c_str(), buttonCount, id)) { - terminalError(ctx, F("Invalid button ID")); - return; - } - - settingsDump(ctx, espurna::button::settings::query::IndexedSettings, id); - terminalOK(ctx); - return; - } - - size_t id { 0 }; - for (const auto& button : espurna::button::internal::buttons) { - ctx.output.printf_P( - PSTR("button%u {%s}\n"), id++, - button.event_emitter - ? (button.event_emitter->pin()->description().c_str()) - : PSTR("Virtual")); - } - }); + espurna::terminal::add(espurna::button::terminal::Commands); #endif if (count) { diff --git a/code/espurna/crash.cpp b/code/espurna/crash.cpp index de5c3752..f5358c5b 100644 --- a/code/espurna/crash.cpp +++ b/code/espurna/crash.cpp @@ -222,6 +222,20 @@ void dump(Print& print) { dump(print, true); } +#if TERMINAL_SUPPORT +alignas(4) static constexpr char Name[] PROGMEM = "CRASH"; + +void command(::terminal::CommandContext&& ctx) { + debug::crash::forceDump(ctx.output); + terminalOK(ctx); +} + +static constexpr ::terminal::Command Commands[] PROGMEM { + {Name, command}, +}; + +#endif + } // namespace crash } // namespace } // namespace debug @@ -331,10 +345,7 @@ void crashSetup() { } #if TERMINAL_SUPPORT - terminalRegisterCommand(F("CRASH"), [](::terminal::CommandContext&& ctx) { - debug::crash::forceDump(ctx.output); - terminalOK(ctx); - }); + espurna::terminal::add(debug::crash::Commands); #endif debug::crash::enableFromSettings(); diff --git a/code/espurna/debug.cpp b/code/espurna/debug.cpp index 73eca916..2b450e49 100644 --- a/code/espurna/debug.cpp +++ b/code/espurna/debug.cpp @@ -613,6 +613,35 @@ void onBoot() { configure(); } +#if TERMINAL_SUPPORT +namespace terminal { + +alignas(4) static constexpr char DebugBuffer[] PROGMEM = "DEBUG.BUFFER"; + +void debug_buffer(::terminal::CommandContext&& ctx) { + debug::buffer::disable(); + if (!debug::buffer::size()) { + terminalError(ctx, F("buffer is empty\n")); + return; + } + + ctx.output.printf_P(PSTR("buffer size: %u / %u bytes\n"), + debug::buffer::size(), debug::buffer::capacity()); + debug::buffer::dump(ctx.output); + terminalOK(ctx); +} + +static constexpr ::terminal::Command commands[] PROGMEM { + {DebugBuffer, debug_buffer}, +}; + +void setup() { + espurna::terminal::add(commands); +} + +} // namespace terminal +#endif + } // namespace } // namespace debug } // namespace espurna @@ -684,18 +713,7 @@ void debugSetup() { #if DEBUG_LOG_BUFFER_SUPPORT #if TERMINAL_SUPPORT - terminalRegisterCommand(F("DEBUG.BUFFER"), [](::terminal::CommandContext&& ctx) { - espurna::debug::buffer::disable(); - if (!espurna::debug::buffer::size()) { - terminalError(ctx, F("buffer is empty\n")); - return; - } - - ctx.output.printf_P(PSTR("buffer size: %u / %u bytes\n"), - espurna::debug::buffer::size(), espurna::debug::buffer::capacity()); - espurna::debug::buffer::dump(ctx.output); - terminalOK(ctx); - }); + espurna::debug::terminal::setup(); #endif #endif } diff --git a/code/espurna/gpio.cpp b/code/espurna/gpio.cpp index 8258f2d0..9d866850 100644 --- a/code/espurna/gpio.cpp +++ b/code/espurna/gpio.cpp @@ -607,6 +607,8 @@ void add(Origin origin) { #if TERMINAL_SUPPORT namespace terminal { +alignas(4) static constexpr char GpioLocks[] PROGMEM = "GPIO.LOCKS"; + void gpio_list_origins(::terminal::CommandContext&& ctx) { for (const auto& origin : origin::internal::origins) { ctx.output.printf_P(PSTR("%c %s GPIO%hhu\t%d:%s:%s\n"), @@ -618,6 +620,8 @@ void gpio_list_origins(::terminal::CommandContext&& ctx) { } } +alignas(4) static constexpr char Gpio[] PROGMEM = "GPIO"; + void gpio_read_write(::terminal::CommandContext&& ctx) { const int pin = (ctx.argv.size() >= 2) ? espurna::settings::internal::convert(ctx.argv[1]) @@ -692,6 +696,8 @@ void gpio_read_write(::terminal::CommandContext&& ctx) { terminalOK(ctx); } +alignas(4) static constexpr char RegRead[] PROGMEM = "REG.READ"; + void reg_read(::terminal::CommandContext&& ctx) { if (ctx.argv.size() == 2) { const auto convert = espurna::settings::internal::convert; @@ -706,6 +712,8 @@ void reg_read(::terminal::CommandContext&& ctx) { terminalError(ctx, F("REG.READ
")); } +alignas(4) static constexpr char RegWrite[] PROGMEM = "REG.WRITE"; + void reg_write(::terminal::CommandContext&& ctx) { if (ctx.argv.size() == 3) { const auto convert = espurna::settings::internal::convert; @@ -723,11 +731,15 @@ void reg_write(::terminal::CommandContext&& ctx) { terminalError(ctx, F("REG.WRITE
")); } +static constexpr espurna::terminal::Command Commands[] PROGMEM { + {GpioLocks, gpio_list_origins}, + {Gpio, gpio_read_write}, + {RegRead, reg_read}, + {RegWrite, reg_write}, +}; + void setup() { - terminalRegisterCommand(F("GPIO.LOCKS"), gpio_list_origins); - terminalRegisterCommand(F("GPIO"), gpio_read_write); - terminalRegisterCommand(F("REG.READ"), reg_read); - terminalRegisterCommand(F("REG.WRITE"), reg_write); + espurna::terminal::add(Commands); } } // namespace terminal diff --git a/code/espurna/homeassistant.cpp b/code/espurna/homeassistant.cpp index 2469b10d..68988af5 100644 --- a/code/espurna/homeassistant.cpp +++ b/code/espurna/homeassistant.cpp @@ -1041,6 +1041,28 @@ bool onKeyCheck(espurna::StringView key, const JsonVariant& value) { } // namespace web +#if TERMINAL_SUPPORT +namespace terminal { + +alignas(4) static constexpr char Send[] PROGMEM = "HA.SEND"; + +void send(::terminal::CommandContext&& ctx) { + internal::state = internal::State::Pending; + publishDiscovery(); + terminalOK(ctx); +} + +static constexpr ::terminal::Command Commands[] PROGMEM { + {Send, send}, +}; + +void setup() { + espurna::terminal::add(Commands); +} + +} // namespace terminal +#endif + void setup() { #if WEB_SUPPORT wsRegister() @@ -1056,11 +1078,7 @@ void setup() { mqttRegister(mqttCallback); #if TERMINAL_SUPPORT - terminalRegisterCommand(F("HA.SEND"), [](::terminal::CommandContext&& ctx) { - internal::state = internal::State::Pending; - publishDiscovery(); - terminalOK(ctx); - }); + terminal::setup(); #endif espurnaRegisterReload(configure); diff --git a/code/espurna/i2c.cpp b/code/espurna/i2c.cpp index e9079f6b..7c3f8412 100644 --- a/code/espurna/i2c.cpp +++ b/code/espurna/i2c.cpp @@ -312,40 +312,56 @@ void init() { } #if TERMINAL_SUPPORT +namespace terminal { -void initTerminalCommands() { - terminalRegisterCommand(F("I2C.LOCKED"), [](::terminal::CommandContext&& ctx) { - for (size_t address = 0; address < lock::storage.size(); ++address) { - if (lock::storage.test(address)) { - ctx.output.printf_P(PSTR("0x%02X\n"), address); - } - } - - terminalOK(ctx); - }); +alignas(4) static constexpr char Locked[] PROGMEM = "I2C.LOCKED"; - terminalRegisterCommand(F("I2C.SCAN"), [](::terminal::CommandContext&& ctx) { - size_t devices { 0 }; - i2c::scan([&](uint8_t address) { - ++devices; +void locked(::terminal::CommandContext&& ctx) { + for (size_t address = 0; address < lock::storage.size(); ++address) { + if (lock::storage.test(address)) { ctx.output.printf_P(PSTR("0x%02X\n"), address); - }); - - if (devices) { - ctx.output.printf_P(PSTR("found %zu device(s)\n"), devices); - terminalOK(ctx); - return; } + } + + terminalOK(ctx); +} + +alignas(4) static constexpr char Scan[] PROGMEM = "I2C.SCAN"; - terminalError(ctx, F("no devices found")); +void scan(::terminal::CommandContext&& ctx) { + size_t devices { 0 }; + i2c::scan([&](uint8_t address) { + ++devices; + ctx.output.printf_P(PSTR("0x%02X\n"), address); }); - terminalRegisterCommand(F("I2C.CLEAR"), [](::terminal::CommandContext&& ctx) { - ctx.output.printf_P(PSTR("result %d\n"), i2c::clear()); + if (devices) { + ctx.output.printf_P(PSTR("found %zu device(s)\n"), devices); terminalOK(ctx); - }); + return; + } + + terminalError(ctx, F("no devices found")); +} + +alignas(4) static constexpr char Clear[] PROGMEM = "I2C.CLEAR"; + +void clear(::terminal::CommandContext&& ctx) { + ctx.output.printf_P(PSTR("result %d\n"), i2c::clear()); + terminalOK(ctx); +} + +static constexpr ::terminal::Command Commands[] PROGMEM { + {Locked, locked}, + {Scan, scan}, + {Clear, clear}, +}; + +void setup() { + espurna::terminal::add(Commands); } +} // namespace terminal #endif // TERMINAL_SUPPORT } // namespace @@ -578,7 +594,7 @@ void i2cSetup() { espurna::i2c::init(); #if TERMINAL_SUPPORT - espurna::i2c::initTerminalCommands(); + espurna::i2c::terminal::setup(); #endif if (espurna::i2c::build::performScanOnBoot()) { diff --git a/code/espurna/ifan.cpp b/code/espurna/ifan.cpp index 31d71f71..38d259b8 100644 --- a/code/espurna/ifan.cpp +++ b/code/espurna/ifan.cpp @@ -284,6 +284,35 @@ private: const Config& _config; }; +#if TERMINAL_SUPPORT +namespace terminal { + +alignas(4) static constexpr char Speed[] PROGMEM = "SPEED"; + +void speed(::terminal::CommandContext&& ctx) { + if (ctx.argv.size() == 2) { + updateSpeedFromPayload(ctx.argv[1]); + } + + ctx.output.printf_P(PSTR("%s %s\n"), + (config.speed != FanSpeed::Off) + ? PSTR("speed") + : PSTR("fan is"), + speedToPayload(config.speed).c_str()); + terminalOK(ctx); +} + +static constexpr ::terminal::Command Commands[] PROGMEM { + {Speed, speed}, +}; + +void setup() { + espurna::terminal::add(Commands); +} + +} // namespace terminal +#endif + void setup() { config.state_pins = setupStatePins(); @@ -322,16 +351,7 @@ void setup() { #endif #if TERMINAL_SUPPORT - terminalRegisterCommand(F("SPEED"), [](::terminal::CommandContext&& ctx) { - if (ctx.argv.size() == 2) { - updateSpeedFromPayload(ctx.argv[1]); - } - - ctx.output.printf_P(PSTR("%s %s\n"), - (config.speed != FanSpeed::Off) ? "speed" : "fan is", - speedToPayload(config.speed).c_str()); - terminalOK(ctx); - }); + terminal::setup(); #endif } diff --git a/code/espurna/influxdb.cpp b/code/espurna/influxdb.cpp index 7ba7e7ce..67231f84 100644 --- a/code/espurna/influxdb.cpp +++ b/code/espurna/influxdb.cpp @@ -278,6 +278,27 @@ bool _idbHeartbeat(espurna::heartbeat::Mask mask) { return true; } +#if TERMINAL_SUPPORT +alignas(4) static constexpr char IdbSend[] PROGMEM = "IDB.SEND"; + +static void idbTerminalSend(::terminal::CommandContext&& ctx) { + if (ctx.argv.size() != 4) { + terminalError(ctx, F("idb.send ")); + return; + } + + idbSend(ctx.argv[1].c_str(), ctx.argv[2].toInt(), ctx.argv[3].c_str()); +} + +static constexpr ::terminal::Command IdbCommands[] { + {IdbSend, idbTerminalSend}, +}; + +static void idbTerminalSetup() { + espurna::terminal::add(IdbCommands); +} +#endif + void idbSetup() { systemHeartbeat(_idbHeartbeat); systemHeartbeat(_idbHeartbeat, @@ -305,14 +326,7 @@ void idbSetup() { espurnaRegisterLoop(_idbFlush); #if TERMINAL_SUPPORT - terminalRegisterCommand(F("IDB.SEND"), [](::terminal::CommandContext&& ctx) { - if (ctx.argv.size() != 4) { - terminalError(ctx, F("idb.send ")); - return; - } - - idbSend(ctx.argv[1].c_str(), ctx.argv[2].toInt(), ctx.argv[3].c_str()); - }); + idbTerminalSetup(); #endif } diff --git a/code/espurna/ir.cpp b/code/espurna/ir.cpp index 395ff7af..20c4636f 100644 --- a/code/espurna/ir.cpp +++ b/code/espurna/ir.cpp @@ -1580,35 +1580,43 @@ void process(rx::DecodeResult& result) { } } -void setup() { - terminalRegisterCommand(F("IR.SEND"), [](::terminal::CommandContext&& ctx) { - if (ctx.argv.size() == 2) { - auto view = StringView{ctx.argv[1]}; +alignas(4) static constexpr char IrSend[] PROGMEM = "IR.SEND"; - auto simple = ir::simple::parse(view); - if (ir::tx::enqueue(std::move(simple))) { - terminalOK(ctx); - return; - } +void send(::terminal::CommandContext&& ctx) { + if (ctx.argv.size() == 2) { + auto view = StringView{ctx.argv[1]}; - auto state = ir::state::parse(view); - if (ir::tx::enqueue(std::move(state))) { - terminalOK(ctx); - return; - } + auto simple = ir::simple::parse(view); + if (ir::tx::enqueue(std::move(simple))) { + terminalOK(ctx); + return; + } - auto raw = ir::raw::parse(view); - if (ir::tx::enqueue(std::move(raw))) { - terminalOK(ctx); - return; - } + auto state = ir::state::parse(view); + if (ir::tx::enqueue(std::move(state))) { + terminalOK(ctx); + return; + } - terminalError(ctx, F("Invalid payload")); + auto raw = ir::raw::parse(view); + if (ir::tx::enqueue(std::move(raw))) { + terminalOK(ctx); return; } - terminalError(ctx, F("IR.SEND ")); - }); + terminalError(ctx, F("Invalid payload")); + return; + } + + terminalError(ctx, F("IR.SEND ")); +} + +static constexpr ::terminal::Command Commands[] PROGMEM { + {IrSend, send}, +}; + +void setup() { + espurna::terminal::add(Commands); } } // namespace terminal diff --git a/code/espurna/led.cpp b/code/espurna/led.cpp index 951a4e14..fddb8bf1 100644 --- a/code/espurna/led.cpp +++ b/code/espurna/led.cpp @@ -1020,36 +1020,44 @@ void onConnected(JsonObject& root) { namespace terminal { namespace { -void setup() { - terminalRegisterCommand(F("LED"), [](::terminal::CommandContext&& ctx) { - if (ctx.argv.size() > 1) { - size_t id; - if (!tryParseId(ctx.argv[1].c_str(), ledCount, id)) { - terminalError(ctx, F("Invalid ledID")); - return; - } - - auto& led = internal::leds[id]; - if (ctx.argv.size() == 2) { - settingsDump(ctx, settings::query::IndexedSettings, id); - } else if (ctx.argv.size() > 2) { - led.mode(LedMode::Manual); - pattern(led, Pattern(ctx.argv[2])); - } - - schedule(); - terminalOK(ctx); +alignas(4) static constexpr char Led[] PROGMEM = "LED"; +void led(::terminal::CommandContext&& ctx) { + if (ctx.argv.size() > 1) { + size_t id; + if (!tryParseId(ctx.argv[1].c_str(), ledCount, id)) { + terminalError(ctx, F("Invalid ledID")); return; } - size_t id { 0 }; - for (const auto& led : internal::leds) { - ctx.output.printf_P( + auto& led = internal::leds[id]; + if (ctx.argv.size() == 2) { + settingsDump(ctx, settings::query::IndexedSettings, id); + } else if (ctx.argv.size() > 2) { + led.mode(LedMode::Manual); + pattern(led, Pattern(ctx.argv[2])); + } + + schedule(); + terminalOK(ctx); + + return; + } + + size_t id { 0 }; + for (const auto& led : internal::leds) { + ctx.output.printf_P( PSTR("led%u {Gpio=%hhu Mode=%s}\n"), id++, led.pin(), espurna::settings::internal::serialize(led.mode()).c_str()); - } - }); + } +} + +static constexpr ::terminal::Command Commands[] PROGMEM { + {Led, led}, +}; + +void setup() { + espurna::terminal::add(Commands); } } // namespace diff --git a/code/espurna/light.cpp b/code/espurna/light.cpp index 454c9a5e..b002c431 100644 --- a/code/espurna/light.cpp +++ b/code/espurna/light.cpp @@ -2443,7 +2443,6 @@ void _lightWebSocketOnAction(uint32_t client_id, const char* action, JsonObject& #endif #if TERMINAL_SUPPORT - namespace { // TODO: at this point we have 3 different state save / restoration @@ -2494,171 +2493,205 @@ void _lightNotificationInit(size_t channel) { lightState(true); } -void _lightInitCommands() { +alignas(4) static constexpr char LightCommandNotify[] PROGMEM = "NOTIFY"; - terminalRegisterCommand(F("NOTIFY"), [](::terminal::CommandContext&& ctx) { - static constexpr auto NotifyTransition = LightTransition{ - .time = espurna::duration::Seconds(1), - .step = espurna::duration::Milliseconds(50), - }; +static void _lightCommandNotify(::terminal::CommandContext&& ctx) { + static constexpr auto NotifyTransition = LightTransition{ + .time = espurna::duration::Seconds(1), + .step = espurna::duration::Milliseconds(50), + }; - if ((ctx.argv.size() < 2) || (ctx.argv.size() > 5)) { - terminalError(ctx, F("NOTIFY [] [