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
2 changes: 2 additions & 0 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ lib_deps =
https://github.com/DFRobot/DFRobot_RTU/archive/refs/tags/V1.0.6.zip
# renovate: datasource=git-refs depName=DFRobot_RainfallSensor packageName=https://github.com/DFRobot/DFRobot_RainfallSensor gitBranch=master
https://github.com/DFRobot/DFRobot_RainfallSensor/archive/38fea5e02b40a5430be6dab39a99a6f6347d667e.zip
# renovate: datasource=github-tags depName=SparkFun AS3935 packageName=sparkfun/SparkFun_AS3935_Lightning_Detector_Arduino_Library
https://github.com/sparkfun/SparkFun_AS3935_Lightning_Detector_Arduino_Library/archive/refs/tags/v1.4.9.zip
# renovate: datasource=github-tags depName=INA226 packageName=robtillaart/INA226
https://github.com/RobTillaart/INA226/archive/refs/tags/0.6.6.zip
# renovate: datasource=github-tags depName=SparkFun MAX3010x packageName=sparkfun/SparkFun_MAX3010x_Sensor_Library
Expand Down
4 changes: 3 additions & 1 deletion src/configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define DS248X_ADDR_ALT6 0x1E // same as HMC5883L_ADDR
#define DS248X_ADDR_ALT7 0x1F // same as BBQ10_KB_ADDR
#define HM330X_ADDR 0x40

#define AS3935_ADDR 0x03 // both address pins tied high, the common breakout-board default
#define AS3935_ADDR_ALT 0x01
#define AS3935_ADDR_ALT2 0x02

// -----------------------------------------------------------------------------
// ACCELEROMETER
Expand Down
3 changes: 2 additions & 1 deletion src/detect/ScanI2C.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ class ScanI2C
ISM330DHCX,
SPA06,
DS248X,
HM330X
HM330X,
AS3935
} DeviceType;

// typedef uint8_t DeviceAddress;
Expand Down
35 changes: 35 additions & 0 deletions src/detect/ScanI2CTwoWire.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1065,6 +1065,41 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
foundDevices[addr] = type;
}
}

#ifdef AS3935_IRQ
// AS3935 addresses (0x01-0x03) fall in the reserved range the loop above skips; probe
// them separately rather than widening that loop for every board.
static const uint8_t as3935Candidates[] = {AS3935_ADDR_ALT, AS3935_ADDR_ALT2, AS3935_ADDR};
for (uint8_t i = 0; i < sizeof(as3935Candidates); i++) {
// Respect the caller's address filter, same as the main loop above (line ~269).
if (asize != 0 && !in_array(address, asize, as3935Candidates[i]))
continue;

DeviceAddress as3935Addr(port, as3935Candidates[i]);
i2cBus->beginTransmission(as3935Candidates[i]);
uint8_t as3935Err = i2cBus->endTransmission();
if (as3935Err == 0) {
// No WHOAMI register, and a POR-only check can't survive a warm reboot (this
// driver rewrites REG0x00 on init). Instead, write a test pattern to bits[5:1]
// and confirm it reads back - initDevice() overwrites this field right after anyway.
constexpr uint8_t AS3935_PROBE_PATTERN = 0b01010; // arbitrary, bits[5:1]
i2cBus->beginTransmission(as3935Candidates[i]);
i2cBus->write((uint8_t)0x00); // REG0x00 (AFE_GAIN)
i2cBus->write((uint8_t)(AS3935_PROBE_PATTERN << 1)); // PWD=0, gain bits = pattern
if (i2cBus->endTransmission() == 0) {
uint16_t reg0 = getRegisterValue(ScanI2CTwoWire::RegisterLocation(as3935Addr, 0x00), 1);
if (((reg0 >> 1) & 0x1F) == AS3935_PROBE_PATTERN) {
logFoundDevice("AS3935", as3935Candidates[i]);
deviceAddresses[AS3935] = as3935Addr;
foundDevices[as3935Addr] = AS3935;
break; // only one AS3935 expected per bus
} else {
LOG_DEBUG("Unexpected REG0x00 readback for AS3935: addr=0x%x val=0x%x", as3935Candidates[i], reg0);
}
}
}
}
#endif
}

void ScanI2CTwoWire::scanPort(I2CPort port)
Expand Down
2 changes: 1 addition & 1 deletion src/modules/Modules.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ void setupModules()
#if HAS_TELEMETRY && HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
if (moduleConfig.has_telemetry &&
(moduleConfig.telemetry.environment_measurement_enabled || moduleConfig.telemetry.environment_screen_enabled)) {
new EnvironmentTelemetryModule();
environmentTelemetryModule = new EnvironmentTelemetryModule();
}
#if HAS_TELEMETRY && HAS_SENSOR && !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR
if (moduleConfig.has_telemetry &&
Expand Down
17 changes: 16 additions & 1 deletion src/modules/Telemetry/EnvironmentTelemetry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const c
#include "Sensor/DFRobotGravitySensor.h"
#endif

#if __has_include(<SparkFun_AS3935.h>)
#include "Sensor/AS3935Sensor.h"
#endif

#if __has_include(<SparkFun_Qwiic_Scale_NAU7802_Arduino_Library.h>)
#include "Sensor/NAU7802Sensor.h"
#endif
Expand Down Expand Up @@ -150,6 +154,7 @@ EnvironmentTelemetryModule::DisplaySource gDisplaySource = EnvironmentTelemetryM
} // namespace

static constexpr uint16_t TX_HISTORY_KEY_ENVIRONMENT_TELEMETRY = 0x8002;
static constexpr uint32_t IMMEDIATE_SEND_MAX_STALENESS_MS = 5UL * 60UL * 1000; // 5 minutes
static constexpr uint32_t LOCAL_DISPLAY_REFRESH_INTERVAL_MS = 1000;

EnvironmentTelemetryModule::DisplaySource EnvironmentTelemetryModule::getDisplaySource()
Expand Down Expand Up @@ -290,6 +295,9 @@ void EnvironmentTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
#if __has_include(<DFRobot_RainfallSensor.h>)
addSensor<DFRobotGravitySensor>(i2cScanner, ScanI2C::DeviceType::DFROBOT_RAIN);
#endif
#if __has_include(<SparkFun_AS3935.h>)
addSensor<AS3935Sensor>(i2cScanner, ScanI2C::DeviceType::AS3935);
#endif
#if __has_include(<Adafruit_AHTX0.h>)
addSensor<AHT10Sensor>(i2cScanner, ScanI2C::DeviceType::AHT10);
#endif
Expand Down Expand Up @@ -426,16 +434,23 @@ int32_t EnvironmentTelemetryModule::runOnce()
}
refreshDisplayedMeasurement();

// Give up on a stale immediate-send request rather than fire an arbitrarily late broadcast.
if (immediateSendRequested &&
!Throttle::isWithinTimespanMs(immediateSendRequestedAtMs, IMMEDIATE_SEND_MAX_STALENESS_MS)) {
immediateSendRequested = false;
}

uint32_t lastTelemetry =
transmitHistory ? transmitHistory->getLastSentToMeshMillis(TX_HISTORY_KEY_ENVIRONMENT_TELEMETRY) : 0;
if (((lastTelemetry == 0) ||
if (((lastTelemetry == 0) || immediateSendRequested ||
!Throttle::isWithinTimespanMs(
lastTelemetry, Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.environment_update_interval,
default_telemetry_broadcast_interval_secs, numOnlineNodes,
TrafficType::TELEMETRY))) &&
airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) &&
airTime->isTxAllowedAirUtil()) {
sendTelemetry();
immediateSendRequested = false;
if (transmitHistory)
transmitHistory->setLastSentToMesh(TX_HISTORY_KEY_ENVIRONMENT_TELEMETRY);
} else if (((lastSentToPhone == 0) || !Throttle::isWithinTimespanMs(lastSentToPhone, sendToPhoneIntervalMs)) &&
Expand Down
10 changes: 10 additions & 0 deletions src/modules/Telemetry/EnvironmentTelemetry.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ class EnvironmentTelemetryModule : private concurrency::OSThread,
virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) override;
#endif

/** Bypass the normal broadcast throttle once, for a sensor with a noteworthy event to
* report sooner than the next scheduled send (airtime limits still apply). */
void requestImmediateSend()
{
immediateSendRequested = true;
immediateSendRequestedAtMs = millis();
}

protected:
/** Called to handle a particular incoming message
@return true if you've guaranteed you've handled this message and no other handlers should be considered for it
Expand Down Expand Up @@ -87,6 +95,8 @@ class EnvironmentTelemetryModule : private concurrency::OSThread,
bool shouldDisplayRemoteNode(NodeNum nodeNum) const;

bool firstTime = 1;
bool immediateSendRequested = false;
uint32_t immediateSendRequestedAtMs = 0;
meshtastic_MeshPacket *lastMeasurementPacket;
uint32_t lastLocalDisplayRefreshMs = 0;
uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute
Expand Down
121 changes: 121 additions & 0 deletions src/modules/Telemetry/Sensor/AS3935Sensor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#include "configuration.h"

#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(<SparkFun_AS3935.h>)

#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "AS3935Sensor.h"
#include "TelemetrySensor.h"
#include "modules/Telemetry/EnvironmentTelemetry.h"
#include <SparkFun_AS3935.h>
#include <Throttle.h>

namespace
{
// No attachInterrupt(): the IRQ line stays asserted until read, so polling can't miss it,
// and the I2C read itself isn't ISR-safe anyway.
constexpr int32_t AS3935_CHECK_INTERVAL_MS = DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
constexpr uint8_t AS3935_DISTANCE_OUT_OF_RANGE = 0x3F;
// Strikes accumulate over a rolling window, reset by elapsed time rather than on
// getMetrics() (which also fires when replying to a peer's telemetry request).
constexpr uint32_t AS3935_STRIKE_WINDOW_MS = 60UL * 60UL * 1000; // 1 hour
} // namespace

AS3935Sensor::AS3935Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_AS3935, "AS3935") {}

AS3935Sensor::~AS3935Sensor()
{
if (lightning) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor"
delete lightning;
#pragma GCC diagnostic pop
lightning = nullptr;
}
}

bool AS3935Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
LOG_INFO("Init sensor: %s", sensorName);

lightning = new SparkFun_AS3935(dev->address.address);
status = lightning->begin(*bus);
if (!status) {
initI2CSensor();
return status;
}

// Defaults match the library's own example, except outdoor mode and unmasked
// disturbers (kept visible in the log).
lightning->setIndoorOutdoor(OUTDOOR);
lightning->setNoiseLevel(2);
lightning->watchdogThreshold(2);
lightning->spikeRejection(2);
lightning->maskDisturber(false);
lightning->lightningThreshold(1);

#ifdef AS3935_IRQ
pinMode(AS3935_IRQ, INPUT);
#endif

windowStartMs = millis();
initI2CSensor();
return status;
}

int32_t AS3935Sensor::runOnce()
{
#ifdef AS3935_IRQ
if (digitalRead(AS3935_IRQ) == HIGH) {
classifyPendingIrq();
}
#endif
if (!Throttle::isWithinTimespanMs(windowStartMs, AS3935_STRIKE_WINDOW_MS)) {
strikeCountWindow = 0;
lastDistanceKm = -1;
windowStartMs = millis();
}
return AS3935_CHECK_INTERVAL_MS;
}

void AS3935Sensor::classifyPendingIrq()
{
uint8_t interruptReason = lightning->readInterruptReg();
switch (interruptReason) {
case LIGHTNING: {
strikeCountWindow++;
uint8_t distance = lightning->distanceToStorm();
if (distance != AS3935_DISTANCE_OUT_OF_RANGE) {
lastDistanceKm = distance;
LOG_INFO("%s: lightning strike detected, distance=%dkm", sensorName, distance);
} else {
LOG_INFO("%s: lightning strike detected, distance unknown (out of range)", sensorName);
}
// No debounce here - EnvironmentTelemetryModule's airtime gate already paces every send.
if (environmentTelemetryModule) {
environmentTelemetryModule->requestImmediateSend();
}
break;
}
case DISTURBER_DETECT:
LOG_DEBUG("%s: disturber detected (ignored)", sensorName);
break;
case NOISE_TO_HIGH:
LOG_DEBUG("%s: noise floor too high", sensorName);
break;
default:
break;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

bool AS3935Sensor::getMetrics(meshtastic_Telemetry *measurement)
{
measurement->variant.environment_metrics.has_lightning_strike_count_1h = true;
measurement->variant.environment_metrics.lightning_strike_count_1h = strikeCountWindow;
if (lastDistanceKm >= 0) {
measurement->variant.environment_metrics.has_lightning_distance_km = true;
measurement->variant.environment_metrics.lightning_distance_km = lastDistanceKm;
}
return true;
}

#endif
32 changes: 32 additions & 0 deletions src/modules/Telemetry/Sensor/AS3935Sensor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#pragma once

#ifndef _MT_AS3935SENSOR_H
#define _MT_AS3935SENSOR_H
#include "configuration.h"

#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(<SparkFun_AS3935.h>)

#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "TelemetrySensor.h"
#include <SparkFun_AS3935.h>

class AS3935Sensor : public TelemetrySensor
{
private:
SparkFun_AS3935 *lightning = nullptr;
uint32_t strikeCountWindow = 0;
float lastDistanceKm = -1; // sentinel: no valid distance captured this window
uint32_t windowStartMs = 0;

void classifyPendingIrq();

public:
AS3935Sensor();
~AS3935Sensor();
virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
virtual int32_t runOnce() override;
};

#endif
#endif