Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expander improvements I #74

Merged
merged 21 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/module_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,7 @@ The expander module allows communication with another microcontroller connected
| `expander.run(command)` | Run any `command` on the other microcontroller | `string` |
| `expander.disconnect()` | Disconnect serial connection and pins | |
| `expander.flash()` | Flash other microcontroller with own binary data | |
| `expander.restart()` | Restart other microcontroller | |

The `flash()` method requires the `boot` and `enable` pins to be defined.

Expand All @@ -697,6 +698,10 @@ Note that the expander forwards all other method calls to the remote core module

| Properties | Description | Data type |
| ------------------ | ------------------------------------------------------- | --------- |
| `boot_timeout` | Time to wait for other microcontroller to boot (s) | `float` |
| `ping_interval` | Time between pings (s) | `float` |
| `ping_timeout` | Time before timing out (s) | `float` |
| `is_ready` | Whether the remote module has booted and is ready | `bool` |
| `last_message_age` | Time since last message from other microcontroller (ms) | `int` |

## Proxy
Expand All @@ -713,3 +718,7 @@ Note that the remote module has to have turned on broadcasting: `x.broadcast()`.
| `module = Proxy()` |

Note that the proxy module forwards all method calls to the remote module.

| Properties | Description | Data type |
| ---------- | ------------------------------------------------- | --------- |
| `is_ready` | Whether the remote module has booted and is ready | `bool` |
6 changes: 4 additions & 2 deletions main/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,8 @@ void run_step(Module_ptr module) {
}

void app_main() {
vTaskDelay(2000 / portTICK_PERIOD_MS); // ensure that all log messages are sent out completely before proceeding

const uart_config_t uart_config = {
.baud_rate = 115200,
.data_bits = UART_DATA_8_BITS,
Expand All @@ -398,8 +400,6 @@ void app_main() {
uart_enable_pattern_det_baud_intr(UART_NUM_0, '\n', 1, 9, 0, 0);
uart_pattern_queue_reset(UART_NUM_0, 100);

printf("\nReady.\n");

try {
Global::add_module("core", core_module = std::make_shared<Core>("core"));
} catch (const std::runtime_error &e) {
Expand All @@ -420,6 +420,8 @@ void app_main() {
echo("error while verifying OTA: %s", e.what());
}

printf("\nReady.\n");

while (true) {
try {
process_uart();
Expand Down
130 changes: 111 additions & 19 deletions main/modules/expander.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "utils/string_utils.h"
#include "utils/timing.h"
#include "utils/uart.h"
#include <algorithm>
#include <cstring>
#include <stdexcept>

Expand All @@ -13,7 +14,16 @@ Expander::Expander(const std::string name,
const gpio_num_t boot_pin,
const gpio_num_t enable_pin,
MessageHandler message_handler)
: Module(expander, name), serial(serial), boot_pin(boot_pin), enable_pin(enable_pin), message_handler(message_handler) {
: Module(expander, name),
serial(serial),
boot_pin(boot_pin),
enable_pin(enable_pin),
message_handler(message_handler) {

this->properties["boot_timeout"] = std::make_shared<NumberVariable>(5.0);
this->properties["ping_interval"] = std::make_shared<NumberVariable>(1.0);
this->properties["ping_timeout"] = std::make_shared<NumberVariable>(1.0);
this->properties["is_ready"] = std::make_shared<BooleanVariable>(false);
this->properties["last_message_age"] = std::make_shared<IntegerVariable>();

serial->enable_line_detection();
Expand All @@ -23,48 +33,130 @@ Expander::Expander(const std::string name,
gpio_set_direction(boot_pin, GPIO_MODE_OUTPUT);
gpio_set_direction(enable_pin, GPIO_MODE_OUTPUT);
gpio_set_level(boot_pin, 1);
gpio_set_level(enable_pin, 0);
delay(100);
gpio_set_level(enable_pin, 1);
}

char buffer[1024] = "";
int len = 0;
const unsigned long int start = millis();
do {
if (millis_since(start) > 1000) {
echo("warning: expander is not booting");
this->restart();
}

void Expander::step() {
if (!this->properties.at("is_ready")->boolean_value) {
check_boot_progress();
} else {
ping();
handle_messages();
}
this->properties.at("last_message_age")->integer_value = millis_since(this->last_message_millis);
Module::step();
}

void Expander::check_boot_progress() {
static char buffer[1024];
while (this->serial->has_buffered_lines()) {
const int len = this->serial->read_line(buffer, sizeof(buffer));
check(buffer, len);
this->last_message_millis = millis();
echo("%s: %s", this->name.c_str(), buffer);
if (strcmp("Ready.", buffer) == 0) {
for (auto &proxy : this->proxies) {
if (!proxy.is_setup) {
setup_proxy(proxy);
}
}
this->properties.at("is_ready")->boolean_value = true;
echo("%s: Booting process completed successfully", this->name.c_str());
break;
}
if (serial->available()) {
len = serial->read_line(buffer, sizeof(buffer));
strip(buffer, len);
echo("%s: %s", name.c_str(), buffer);
}

const unsigned long boot_timeout = this->get_property("boot_timeout")->number_value * 1000;
if (boot_timeout > 0 && millis_since(this->boot_start_time) > boot_timeout) {
echo("warning: expander %s did not send 'Ready.', trying restart", this->name.c_str());
restart();
}
}

void Expander::ping() {
const double last_message_age = this->get_property("last_message_age")->integer_value / 1000.0;
const double ping_interval = this->get_property("ping_interval")->number_value;
const double ping_timeout = this->get_property("ping_timeout")->number_value;
if (!this->ping_pending) {
if (last_message_age >= ping_interval) {
this->serial->write_checked_line("core.print('__PONG__')");
this->ping_pending = true;
}
} while (strcmp("Ready.", buffer));
} else {
if (last_message_age >= ping_interval + ping_timeout) {
echo("warning: expander %s ping timed out, restarting", this->name.c_str());
restart();
}
}
}

void Expander::step() {
void Expander::restart() {
this->ping_pending = false;

for (auto &proxy : this->proxies) {
proxy.is_setup = false;
}

if (this->boot_pin != GPIO_NUM_NC && this->enable_pin != GPIO_NUM_NC) {
gpio_set_level(this->enable_pin, 0);
delay(100);
gpio_set_level(this->enable_pin, 1);
} else {
serial->write_checked_line("core.restart()");
}
this->boot_start_time = millis();
this->properties.at("is_ready")->boolean_value = false;
}

void Expander::handle_messages() {
static char buffer[1024];
while (this->serial->has_buffered_lines()) {
int len = this->serial->read_line(buffer, sizeof(buffer));
check(buffer, len);
this->last_message_millis = millis();
this->ping_pending = false;
if (buffer[0] == '!' && buffer[1] == '!') {
this->message_handler(&buffer[2], false, true); // Don't trigger core keep-alive from expander broadcasts
this->message_handler(&buffer[2], false, true);
} else {
echo("%s: %s", this->name.c_str(), buffer);
}
}
this->properties.at("last_message_age")->integer_value = millis_since(this->last_message_millis);
Module::step();
}

void Expander::add_proxy(const std::string module_name,
const std::string module_type,
const std::vector<ConstExpression_ptr> arguments) {
ProxyInformation proxy = {module_name, module_type, arguments, false};
this->proxies.push_back(proxy);

if (this->properties.at("is_ready")->boolean_value) {
// Reset ready state, since we're not ready until all proxies are setup
echo("%s: New proxy added, setting up...", this->name.c_str());
setup_proxy(proxy);
}
}

void Expander::setup_proxy(ProxyInformation &proxy) {
static char buffer[256];
int pos = csprintf(buffer, sizeof(buffer), "%s = %s(",
proxy.module_name.c_str(), proxy.module_type.c_str());
pos += write_arguments_to_buffer(proxy.arguments, &buffer[pos], sizeof(buffer) - pos);
pos += csprintf(&buffer[pos], sizeof(buffer) - pos, "); ");
pos += csprintf(&buffer[pos], sizeof(buffer) - pos, "%s.broadcast()", proxy.module_name.c_str());
this->serial->write_checked_line(buffer, pos);
proxy.is_setup = true;
}

void Expander::call(const std::string method_name, const std::vector<ConstExpression_ptr> arguments) {
if (method_name == "run") {
Module::expect(arguments, 1, string);
std::string command = arguments[0]->evaluate_string();
this->serial->write_checked_line(command.c_str(), command.length());
} else if (method_name == "restart") {
Module::expect(arguments, 0);
restart();
} else if (method_name == "disconnect") {
Module::expect(arguments, 0);
this->serial->deinstall();
Expand Down
19 changes: 19 additions & 0 deletions main/modules/expander.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,23 @@ using Expander_ptr = std::shared_ptr<Expander>;

class Expander : public Module {
private:
struct ProxyInformation {
std::string module_name;
std::string module_type;
std::vector<ConstExpression_ptr> arguments;
bool is_setup = false;
};

unsigned long int last_message_millis = 0;
bool ping_pending = false;
unsigned long boot_start_time;
std::vector<ProxyInformation> proxies;

void check_boot_progress();
void ping();
void restart();
void handle_messages();
void setup_proxy(ProxyInformation &proxy);

public:
const ConstSerial_ptr serial;
Expand All @@ -24,4 +40,7 @@ class Expander : public Module {
MessageHandler message_handler);
void step() override;
void call(const std::string method_name, const std::vector<ConstExpression_ptr> arguments) override;
void add_proxy(const std::string module_name,
const std::string module_type,
const std::vector<ConstExpression_ptr> arguments);
};
9 changes: 2 additions & 7 deletions main/modules/proxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,8 @@ Proxy::Proxy(const std::string name,
const Expander_ptr expander,
const std::vector<ConstExpression_ptr> arguments)
: Module(proxy, name), expander(expander) {
static char buffer[256];
int pos = csprintf(buffer, sizeof(buffer), "%s = %s(", name.c_str(), module_type.c_str());
pos += write_arguments_to_buffer(arguments, &buffer[pos], sizeof(buffer) - pos);
pos += csprintf(&buffer[pos], sizeof(buffer) - pos, "); ");
pos += csprintf(&buffer[pos], sizeof(buffer) - pos, "%s.broadcast()", name.c_str());

expander->serial->write_checked_line(buffer, pos);
this->properties["is_ready"] = expander->get_property("is_ready");
expander->add_proxy(name, module_type, arguments);
}

void Proxy::call(const std::string method_name, const std::vector<ConstExpression_ptr> arguments) {
Expand Down
4 changes: 4 additions & 0 deletions main/modules/serial.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ size_t Serial::write(const uint8_t byte) const {
return 1;
}

void Serial::write_checked_line(const char *message) const {
this->write_checked_line(message, std::strlen(message));
}

void Serial::write_checked_line(const char *message, const int length) const {
static char checksum_buffer[16];
uint8_t checksum = 0;
Expand Down
1 change: 1 addition & 0 deletions main/modules/serial.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class Serial : public Module {
int read(const uint32_t timeout = 0) const;
int read_line(char *buffer, size_t buffer_len) const;
size_t write(const uint8_t byte) const;
void write_checked_line(const char *message) const;
void write_checked_line(const char *message, const int length) const;
void flush() const;
void clear() const;
Expand Down