Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ jobs:
arduino-cli lib install "SensorManager"
arduino-cli lib install "SdFat"
arduino-cli lib install "DHT11"
arduino-cli lib install "NextionControl"
arduino-cli lib install "TinyGPSPlus"
arduino-cli lib install "DS1302"
- name: Compile SmartFuseBox firmware
run: |
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ jobs:
arduino-cli lib install "SerialCommandManager"
arduino-cli lib install "SensorManager"
arduino-cli lib install "SdFat"
arduino-cli lib install "NextionControl"
arduino-cli lib install "TinyGPSPlus"
arduino-cli lib install "DS1302"
- name: Generate FirmwareVersion.h
run: |
Expand Down
645 changes: 358 additions & 287 deletions Docs/Commands.md

Large diffs are not rendered by default.

35 changes: 16 additions & 19 deletions SmartFuseBox/AckCommandHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,23 @@
#include "SystemFunctions.h"
#include "ConfigController.h"

#if defined(NEXTION_DISPLAY_DEVICE)
#include <NextionControl.h>
#include "BasePage.h"
#endif

const char AckCommand[] = "ACK";

#if defined(BOAT_CONTROL_PANEL)
AckCommandHandler::AckCommandHandler(BroadcastManager* broadcastManager, NextionControl* nextionControl, WarningManager* warningManager)
: BaseBoatCommandHandler(broadcastManager, nextionControl, warningManager)
#elif defined(FUSE_BOX_CONTROLLER)
AckCommandHandler::AckCommandHandler(BroadcastManager* broadcastManager, WarningManager* warningManager)
: SharedBaseCommandHandler(broadcastManager, warningManager), _configController(nullptr)
AckCommandHandler::AckCommandHandler(BroadcastManager* broadcastManager,
#if defined(NEXTION_DISPLAY_DEVICE)
NextionControl* nextionControl,
#endif
WarningManager* warningManager)
: BaseNextionCommandHandler(broadcastManager,
#if defined(NEXTION_DISPLAY_DEVICE)
nextionControl,
#endif
warningManager)
{

}
Expand Down Expand Up @@ -92,18 +99,8 @@ bool AckCommandHandler::handleCommand(SerialCommandManager* sender, const char*

// only process known ACK keys if you need to take action

#if defined(BOAT_CONTROL_PANEL)
if (SystemFunctions::commandMatches(params[0].key, SystemHeartbeatCommand) && strcmp(params[0].value, AckSuccess) == 0)
{
// Heartbeat acknowledgement
processHeartbeatAck(sender, params[0].key, params[0].value);
}
else if (strcmp(params[0].key, WarningsList) == 0 && strcmp(params[0].value, AckSuccess) == 0)
{
// Warnings list acknowledgement - merge remote warnings
processWarningsListAck(sender, params[0].key, params[0].value, params, paramCount);
}
else if (strcmp(params[0].key, RelayRetrieveStates) == 0 && strcmp(params[0].value, AckSuccess) == 0)
#if defined(NEXTION_DISPLAY_DEVICE)
if (strcmp(params[0].key, RelayRetrieveStates) == 0 && strcmp(params[0].value, AckSuccess) == 0)
{
// Relay state acknowledgement - handle both formats:
// 1. ACK:R2=ok (just acknowledgement, no relay state - paramCount == 1)
Expand Down Expand Up @@ -180,7 +177,7 @@ bool AckCommandHandler::handleCommand(SerialCommandManager* sender, const char*
sendDebugMessage(F("Invalid F2 ACK format free memory"), AckCommand);
}
}
#elif defined(FUSE_BOX_CONTROLLER)
#else
processConfigAck(sender, params[0].key, params[0].value);
#endif

Expand Down
14 changes: 11 additions & 3 deletions SmartFuseBox/AckCommandHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,29 @@

#include "Local.h"
#include "ConfigManager.h"
#include "SharedBaseCommandHandler.h"
#include "BaseNextionCommandHandler.h"

#if defined(NEXTION_DISPLAY_DEVICE)
#include <NextionControl.h>
#endif

// Forward declarations
class ConfigController;


class AckCommandHandler : public SharedBaseCommandHandler
class AckCommandHandler : public BaseNextionCommandHandler
{
private:
bool processConfigAck(SerialCommandManager* sender, const char* key, const char* value);

ConfigController* _configController;

public:
explicit AckCommandHandler(BroadcastManager* broadcastManager, WarningManager* warningManager);
explicit AckCommandHandler(BroadcastManager* broadcastManager,
#if defined(NEXTION_DISPLAY_DEVICE)
NextionControl* nextionControl,
#endif
WarningManager* warningManager);

// Set the config sync manager (optional - needed for config sync feature)
void setConfigController(ConfigController* configController);
Expand Down
164 changes: 164 additions & 0 deletions SmartFuseBox/Astronomy.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/*
* SmartFuseBox
* Copyright (C) 2025 Simon Carter (s1cart3r@gmail.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once

#include <Arduino.h>
#include <math.h>

enum class MoonPhase {
NewMoon = 0,
WaxingCrescent,
FirstQuarter,
WaxingGibbous,
FullMoon,
WaningGibbous,
LastQuarter,
WaningCrescent
};

// Buffer sizes for moon phase strings with a little extra space
constexpr uint8_t BufferSizeMoonPhaseName = 32;
constexpr uint8_t BufferSizeMoonPhaseDescription = 128;
constexpr uint8_t BufferSizeMoonPhaseSeaDescription = 64;

// Moon phase names, minimum size is 16 bytes to fit longest string
static const char moon_phase_0[] PROGMEM = "New Moon";
static const char moon_phase_1[] PROGMEM = "Waxing Crescent";
static const char moon_phase_2[] PROGMEM = "First Quarter";
static const char moon_phase_3[] PROGMEM = "Waxing Gibbous";
static const char moon_phase_4[] PROGMEM = "Full Moon";
static const char moon_phase_5[] PROGMEM = "Waning Gibbous";
static const char moon_phase_6[] PROGMEM = "Last Quarter";
static const char moon_phase_7[] PROGMEM = "Waning Crescent";

static const char* const MoonPhaseNames[] PROGMEM = {
moon_phase_0, moon_phase_1, moon_phase_2, moon_phase_3,
moon_phase_4, moon_phase_5, moon_phase_6, moon_phase_7
};

// Moon phase descriptions, minimum size is 128 bytes to fit longest string
static const char moon_desc_0[] PROGMEM = "The Moon is between Earth and Sun.\r\nThe illuminated side faces away from\r\nus, so it's effectively invisible.";
static const char moon_desc_1[] PROGMEM = "A sliver of the moon becomes visible\r\nas it starts to wax.";
static const char moon_desc_2[] PROGMEM = "Half of the moon is illuminated as\r\n it continues to wax.";
static const char moon_desc_3[] PROGMEM = "More than half of the moon is\r\nilluminated, approaching full moon.";
static const char moon_desc_4[] PROGMEM = "The entire face of the moon is\r\nilluminated.";
static const char moon_desc_5[] PROGMEM = "More than half of the moon is\r\nilluminated, starting to wane.";
static const char moon_desc_6[] PROGMEM = "Half of the moon is illuminated as\r\nit continues to wane.";
static const char moon_desc_7[] PROGMEM = "A sliver of the moon remains\r\nvisible as it nears new moon.";

static const char* const MoonPhaseDescriptions[] PROGMEM = {
moon_desc_0, moon_desc_1, moon_desc_2, moon_desc_3,
moon_desc_4, moon_desc_5, moon_desc_6, moon_desc_7
};

// Moon phase sea descriptions, minimum size is 64 bytes to fit longest string
static const char moon_sea_0[] PROGMEM = "Darker nights, minimal\r\nmoonlight.";
static const char moon_sea_1[] PROGMEM = "Increasing night visibility\r\nafter sunset.";
static const char moon_sea_2[] PROGMEM = "Strong visual reference in\r\nthe evening sky.";
static const char moon_sea_3[] PROGMEM = "Bright nights, increasing\r\ntidal range.";
static const char moon_sea_4[] PROGMEM = "Brightest nights, strongest\r\nspring tides.";
static const char moon_sea_5[] PROGMEM = "Bright late-night and\r\nearly-morning light.";
static const char moon_sea_6[] PROGMEM = "Rises around midnight,\r\nvisible in early morning.";
static const char moon_sea_7[] PROGMEM = "Low pre-dawn light, calming\r\nvisual indicator of cycle reset.";

static const char* const MoonPhaseSeaDescriptions[] PROGMEM = {
moon_sea_0, moon_sea_1, moon_sea_2, moon_sea_3,
moon_sea_4, moon_sea_5, moon_sea_6, moon_sea_7
};

// average length of lunar month in days
static constexpr double SYNODIC_MONTH = 29.53058867;

class Astronomy
{
public:
/**
* @brief Return moon age in days (0 = new moon, ~29.53 = next new moon).
* @param unixTimestamp Seconds since epoch (UTC)
* @return Age in days (floating)
*/
static float getMoonAgeDays(unsigned long unixTimestamp)
{
// Convert Unix time to Julian Day (JD)
// JD for unix epoch: 2440587.5
double jd = unixTimestamp / 86400.0 + 2440587.5;
// Reference new moon near Jan 6, 2000 -> JD 2451550.1 (commonly used reference)
double daysSinceRef = jd - 2451550.1;
double fraction = fmod(daysSinceRef / SYNODIC_MONTH, 1.0);
if (fraction < 0.0) fraction += 1.0;
double age = fraction * SYNODIC_MONTH;
return (float)age;
}

/**
* @brief Calculate approximate MoonPhase from unix timestamp.
* Uses a simple astronomical approximation based on Julian date and average synodic month.
* @param unixTimestamp Seconds since epoch (UTC)
* @return MoonPhase enum value
*/
static MoonPhase getMoonPhaseFromUnix(unsigned long unixTimestamp)
{
float age = getMoonAgeDays(unixTimestamp);
// Map age into 8 equal sectors
double phaseFraction = age / SYNODIC_MONTH; // 0..1
int index = (int)(phaseFraction * 8.0 + 0.5) % 8;

switch (index)
{
case 0: return MoonPhase::NewMoon;
case 1: return MoonPhase::WaxingCrescent;
case 2: return MoonPhase::FirstQuarter;
case 3: return MoonPhase::WaxingGibbous;
case 4: return MoonPhase::FullMoon;
case 5: return MoonPhase::WaningGibbous;
case 6: return MoonPhase::LastQuarter;
default: return MoonPhase::WaningCrescent;
}
}

// Copy the name from PROGMEM into a RAM buffer (safe copy)
static size_t getMoonPhaseName(MoonPhase phase, char* buffer, size_t bufferSize)
{
if (!buffer || bufferSize == 0) return 0;
uint8_t idx = static_cast<uint8_t>(phase);
PGM_P p = reinterpret_cast<PGM_P>(pgm_read_ptr(&MoonPhaseNames[idx]));
strncpy_P(buffer, p, bufferSize);
buffer[bufferSize - 1] = '\0';
return strlen(buffer);
}

static size_t getMoonPhaseDescription(MoonPhase phase, char* buffer, size_t bufferSize)
{
if (!buffer || bufferSize == 0) return 0;
uint8_t idx = static_cast<uint8_t>(phase);
PGM_P p = reinterpret_cast<PGM_P>(pgm_read_ptr(&MoonPhaseDescriptions[idx]));
strncpy_P(buffer, p, bufferSize);
buffer[bufferSize - 1] = '\0';
return strlen(buffer);
}

static size_t getMoonPhaseSeaDescription(MoonPhase phase, char* buffer, size_t bufferSize)
{
if (!buffer || bufferSize == 0) return 0;
uint8_t idx = static_cast<uint8_t>(phase);
PGM_P p = reinterpret_cast<PGM_P>(pgm_read_ptr(&MoonPhaseSeaDescriptions[idx]));
strncpy_P(buffer, p, bufferSize);
buffer[bufferSize - 1] = '\0';
return strlen(buffer);
}
};
32 changes: 32 additions & 0 deletions SmartFuseBox/BaseNextionCommandHandler.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* SmartFuseBox
* Copyright (C) 2025 Simon Carter (s1cart3r@gmail.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "Local.h"
#include "BaseNextionCommandHandler.h"

BaseNextionCommandHandler::BaseNextionCommandHandler(
BroadcastManager* broadcaster,
#if defined(NEXTION_DISPLAY_DEVICE)
NextionControl* nextionControl,
#endif
WarningManager* warningManager)
: SharedBaseCommandHandler(broadcaster, warningManager)
#if defined(NEXTION_DISPLAY_DEVICE)
, _nextionControl(nextionControl)
#endif
{
}
Loading
Loading