Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
74 changes: 74 additions & 0 deletions boards/inhero_mr2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
[
"0x239A",
"0x8029"
],
[
"0x239A",
"0x0029"
],
[
"0x239A",
"0x002A"
],
[
"0x239A",
"0x802A"
]
],
"usb_product": "Inhero MR2",
"mcu": "nrf52840",
"variant": "Inhero_MR2_Board",
"bsp": {
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "6.1.1",
"sd_fwid": "0x00B6"
},
"bootloader": {
"settings_addr": "0xFF000"
}
},
"connectivity": [
"bluetooth"
],
"debug": {
"jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52.cfg"
},
"frameworks": [
"arduino"
],
"name": "Inhero MR-2",
"upload": {
"maximum_ram_size": 235520,
"maximum_size": 815104,
"speed": 115200,
"protocol": "nrfutil",
"protocols": [
"jlink",
"nrfjprog",
"nrfutil",
"stlink",
"cmsis-dap"
],
"use_1200bps_touch": true,
"require_upload_port": true,
"wait_for_upload_port": true
},
"url": "https://inhero.de",
"vendor": "Inhero GmbH"
}
5 changes: 5 additions & 0 deletions examples/simple_repeater/MyMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,11 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t
}
sensors.querySensors(perm_mask, telemetry);

// Board-specific telemetry (boards can override queryBoardTelemetry in their Board class)
if (perm_mask & TELEM_PERM_ENVIRONMENT) {
board.queryBoardTelemetry(telemetry);
}

// This default temperature will be overridden by external sensors (if any)
float temperature = board.getMCUTemperature();
if(!isnan(temperature)) { // Supported boards with built-in temperature sensor. ESP32-C3 may return NAN
Expand Down
65 changes: 65 additions & 0 deletions examples/simple_repeater/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,50 @@ static unsigned long userBtnDownAt = 0;
#define USER_BTN_HOLD_OFF_MILLIS 1500
#endif

// USB power management (Inhero MR2 only)
#if defined(INHERO_MR2)
#include <nrf.h>
#include "BoardConfigContainer.h"
bool usb_active = true;

static bool isUSBPowered() {
return (NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_VBUSDETECT_Msk) != 0;
}

void disableUSB() {
if (usb_active) {
Serial.end();
NRF_USBD->ENABLE = 0;
usb_active = false;
BoardConfigContainer::setUsbConnected(false);
MESH_DEBUG_PRINTLN("USB disabled");
}
}

void enableUSB() {
if (!usb_active) {
NRF_USBD->ENABLE = 1;
Serial.begin(115200);
usb_active = true;
BoardConfigContainer::setUsbConnected(true);
MESH_DEBUG_PRINTLN("USB enabled");
}
}
#endif

void setup() {
Serial.begin(115200);
delay(1000);

board.begin();

#if defined(INHERO_MR2)
// Set initial USB IINDPM limit based on VBUS state at boot
if (isUSBPowered()) {
BoardConfigContainer::setUsbConnected(true);
}
#endif

#if defined(MESH_DEBUG) && defined(NRF52_PLATFORM)
// give some extra time for serial to settle so
// boot debug messages can be seen on terminal
Expand Down Expand Up @@ -106,7 +144,21 @@ void setup() {
}

void loop() {
#if defined(INHERO_MR2)
// Auto-enable USB when VBUS is plugged in
if (!usb_active && isUSBPowered()) {
enableUSB();
}
// Auto-disable USB when VBUS is removed
if (usb_active && !isUSBPowered()) {
disableUSB();
}
#endif

int len = strlen(command);
#if defined(INHERO_MR2)
if (usb_active) {
#endif
while (Serial.available() && len < sizeof(command)-1) {
char c = Serial.read();
if (c != '\n') {
Expand All @@ -131,6 +183,11 @@ void loop() {

command[0] = 0; // reset command buffer
}
#if defined(INHERO_MR2)
}
#endif

board.tick(); // Feed watchdog and perform board-specific tasks

#if defined(PIN_USER_BTN) && defined(_SEEED_SENSECAP_SOLAR_H_)
// Hold the user button to power off the SenseCAP Solar repeater.
Expand Down Expand Up @@ -167,4 +224,12 @@ void loop() {
}
#endif
}
#if defined(NRF52_PLATFORM)
else {
// Even without powersaving: briefly idle via WFE between loop iterations.
// Wakes on any interrupt (radio DIO1, SysTick, USB, I2C) - typically within 1ms.
// Reduces nRF52840 CPU current from ~3mA (busy-loop) to ~0.5-0.8mA.
board.sleep(0);
}
#endif
}
95 changes: 93 additions & 2 deletions examples/simple_sensor/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,41 @@
static UITask ui_task(display);
#endif

// For power saving
unsigned long lastActive = 0; // mark last active time
unsigned long nextSleepinSecs = 120; // next sleep in seconds. The first sleep (if enabled) is after 2 minutes from boot

// USB power management (Inhero MR2 only)
#if defined(INHERO_MR2)
#include <nrf.h>
#include "BoardConfigContainer.h"
bool usb_active = true;

static bool isUSBPowered() {
return (NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_VBUSDETECT_Msk) != 0;
}

void disableUSB() {
if (usb_active) {
Serial.end();
NRF_USBD->ENABLE = 0;
usb_active = false;
BoardConfigContainer::setUsbConnected(false);
MESH_DEBUG_PRINTLN("USB disabled");
}
}

void enableUSB() {
if (!usb_active) {
NRF_USBD->ENABLE = 1;
Serial.begin(115200);
usb_active = true;
BoardConfigContainer::setUsbConnected(true);
MESH_DEBUG_PRINTLN("USB enabled");
}
}
#endif

class MyMesh : public SensorMesh {
public:
MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables)
Expand Down Expand Up @@ -58,6 +93,16 @@ void setup() {

board.begin();

#if defined(INHERO_MR2)
// Set initial USB IINDPM limit based on VBUS state at boot
if (isUSBPowered()) {
BoardConfigContainer::setUsbConnected(true);
}
#endif

// For power saving
lastActive = millis(); // mark last active time since boot

#ifdef DISPLAY_CLASS
if (display.begin()) {
display.startFrame();
Expand All @@ -66,7 +111,10 @@ void setup() {
}
#endif

if (!radio_init()) { halt(); }
if (!radio_init()) {
MESH_DEBUG_PRINTLN("Radio init failed!");
halt();
}

fast_rng.begin(radio_get_rng_seed());

Expand Down Expand Up @@ -117,20 +165,36 @@ void setup() {
}

void loop() {
#if defined(INHERO_MR2)
// Auto-enable USB when VBUS is plugged in
if (!usb_active && isUSBPowered()) {
enableUSB();
}
// Auto-disable USB when VBUS is removed
if (usb_active && !isUSBPowered()) {
disableUSB();
}
#endif

int len = strlen(command);
#if defined(INHERO_MR2)
if (usb_active) {
#endif
while (Serial.available() && len < sizeof(command)-1) {
char c = Serial.read();
if (c != '\n') {
command[len++] = c;
command[len] = 0;
Serial.print(c);
}
Serial.print(c);
if (c == '\r') break;
}
if (len == sizeof(command)-1) { // command buffer full
command[sizeof(command)-1] = '\r';
}

if (len > 0 && command[len - 1] == '\r') { // received complete line
Serial.print('\n');
command[len - 1] = 0; // replace newline with C string null terminator
char reply[160];
the_mesh.handleCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial!
Expand All @@ -140,11 +204,38 @@ void loop() {

command[0] = 0; // reset command buffer
}
#if defined(INHERO_MR2)
}
#endif

board.tick(); // Feed watchdog and perform board-specific tasks

the_mesh.loop();
sensors.loop();
#ifdef DISPLAY_CLASS
ui_task.loop();
#endif
rtc_clock.tick();

if (the_mesh.getNodePrefs()->powersaving_enabled) {
#if defined(NRF52_PLATFORM)
board.sleep(1800); // nrf ignores seconds param, sleeps whenever possible
#else
if (the_mesh.millisHasNowPassed(lastActive + nextSleepinSecs * 1000)) { // To check if it is time to sleep
board.sleep(1800); // To sleep. Wake up after 30 minutes or when receiving a LoRa packet
lastActive = millis();
nextSleepinSecs = 5; // Default: To work for 5s and sleep again
} else {
nextSleepinSecs += 5; // When there is pending work, to work another 5s
}
#endif
}
#if defined(NRF52_PLATFORM)
else {
// Even without powersaving: briefly idle via WFE between loop iterations.
// Wakes on any interrupt (radio DIO1, SysTick, USB, I2C) - typically within 1ms.
// Reduces nRF52840 CPU current from ~3mA (busy-loop) to ~0.5-0.8mA.
board.sleep(0);
}
#endif
}
8 changes: 8 additions & 0 deletions src/MeshCore.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#pragma once

class CayenneLPP;

#include <stdint.h>
#include <math.h>

Expand Down Expand Up @@ -66,6 +68,12 @@ class MainBoard {
virtual const char* getResetReasonString(uint32_t reason) { return "Not available"; }
virtual uint8_t getShutdownReason() const { return 0; }
virtual const char* getShutdownReasonString(uint8_t reason) { return "Not available"; }

// Custom board commands and telemetry (boards can override these)
virtual void tick() {}
virtual bool getCustomGetter(const char* getCommand, char* reply, uint32_t maxlen) { return false; }
virtual const char* setCustomSetter(const char* setCommand) { return nullptr; }
virtual bool queryBoardTelemetry(CayenneLPP& telemetry) { return false; }
};

/**
Expand Down
15 changes: 15 additions & 0 deletions src/helpers/CommonCLI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,14 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
#else
strcpy(reply, "ERROR: unsupported");
#endif
} else if (memcmp(config, "board.", 6) == 0) {
char res[100];
memset(res, 0, sizeof(res));
if (_board->getCustomGetter(&config[6], res, sizeof(res))) {
strcpy(reply, res);
} else {
strcpy(reply, "Error: unknown board command");
}
} else if (memcmp(config, "adc.multiplier", 14) == 0) {
float adc_mult = _board->getAdcMultiplier();
if (adc_mult == 0.0f) {
Expand Down Expand Up @@ -704,6 +712,13 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
savePrefs();
strcpy(reply, "OK");
#endif
} else if (memcmp(config, "board.", 6) == 0) {
const char* result = _board->setCustomSetter(&config[6]);
if (result != nullptr) {
strcpy(reply, result);
} else {
strcpy(reply, "Error: unknown board command");
}
} else if (memcmp(config, "adc.multiplier ", 15) == 0) {
_prefs->adc_multiplier = atof(&config[15]);
if (_board->setAdcMultiplier(_prefs->adc_multiplier)) {
Expand Down
Loading