diff --git a/platformio.ini b/platformio.ini
index 3f562ab33..2c7973ecf 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -257,6 +257,8 @@ lib_deps =
https://github.com/Sensirion/arduino-i2c-scd30/archive/1.1.1.zip
# renovate: datasource=github-tags depName=arduino-sht packageName=sensirion/arduino-sht
https://github.com/Sensirion/arduino-sht/archive/refs/tags/v1.2.6.zip
+ # renovate: datasource=custom.pio depName=Adafruit ADS1X15 packageName=adafruit/library/Adafruit ADS1X15 Library
+ https://github.com/adafruit/Adafruit_ADS1X15/archive/refs/tags/2.6.2.zip
# renovate: datasource=github-tags depName=Adafruit DS248x packageName=adafruit/Adafruit_DS248x
https://github.com/adafruit/Adafruit_DS248x/archive/refs/tags/1.2.0.zip
diff --git a/src/configuration.h b/src/configuration.h
index 0ed4dd893..795856955 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -301,6 +301,10 @@ along with this program. If not, see .
#define LTR553ALS_ADDR 0x23
#define SEN5X_ADDR 0x69
#define SCD30_ADDR 0x61
+#define ADS1X15_ADDR 0x48
+#define ADS1X15_ADDR_ALT1 0x49
+#define ADS1X15_ADDR_ALT2 0x4A
+#define ADS1X15_ADDR_ALT3 0x4B
#define DS248X_ADDR 0x18 // same as MCP9808_ADDR, STK8BXX_ADDR and LIS3DH_ADDR
#define DS248X_ADDR_ALT1 0x19 // same as LIS3DH_ADDR_ALT and BMA423_ADDR
#define DS248X_ADDR_ALT2 0x1A // same as CST328_ADDR
@@ -311,7 +315,6 @@ along with this program. If not, see .
#define DS248X_ADDR_ALT7 0x1F // same as BBQ10_KB_ADDR
#define HM330X_ADDR 0x40
-
// -----------------------------------------------------------------------------
// ACCELEROMETER
// -----------------------------------------------------------------------------
diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h
index 70f848b33..46d5395a6 100644
--- a/src/detect/ScanI2C.h
+++ b/src/detect/ScanI2C.h
@@ -99,13 +99,14 @@ class ScanI2C
SFA30,
CW2015,
SCD30,
- ADS1115,
+ ADS1X15,
+ ADS1X15_ALT,
IIS2MDCTR,
ISM330DHCX,
SPA06,
DS248X,
HM330X
- } DeviceType;
+ } DeviceType;
// typedef uint8_t DeviceAddress;
typedef enum I2CPort {
diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp
index 21a74b759..af484eb15 100644
--- a/src/detect/ScanI2CTwoWire.cpp
+++ b/src/detect/ScanI2CTwoWire.cpp
@@ -1040,10 +1040,11 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
break;
}
+ // ADS1X15 default config register is 8583h
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x01), 2);
- if (registerValue == 0x8583 || registerValue == 0x8580) {
- type = ADS1115;
- logFoundDevice("ADS1115 ADC", (uint8_t)addr.address);
+ if (registerValue == 0x8583 || registerValue == 0x8580 || registerValue == 0xf700) {
+ type = ADS1X15;
+ logFoundDevice("ADS1X15 ADC", (uint8_t)addr.address);
break;
}
@@ -1052,6 +1053,19 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
break;
}
+ case ADS1X15_ADDR_ALT1:
+ case ADS1X15_ADDR_ALT2:
+ case ADS1X15_ADDR_ALT3: {
+ // ADS1X15 default config register is 8583h
+ registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x01), 2);
+ if (registerValue == 0x8583 || registerValue == 0x8580 || registerValue == 0xf700) {
+ type = ADS1X15_ALT;
+ logFoundDevice("ADS1X15_ALT", (uint8_t)addr.address);
+ break;
+ }
+ break;
+ }
+
default:
LOG_INFO("Device found at address 0x%x was not able to be enumerated", (uint8_t)addr.address);
}
diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp
index 9a6076b28..aa985c909 100644
--- a/src/modules/Telemetry/EnvironmentTelemetry.cpp
+++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp
@@ -131,6 +131,10 @@ extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const c
#include "Sensor/BH1750Sensor.h"
#endif
+#if __has_include()
+#include "Sensor/ADS1X15Sensor.h"
+#endif
+
#if __has_include()
#include "Sensor/DS248XSensor.h"
#endif
@@ -347,6 +351,10 @@ void EnvironmentTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
#if __has_include()
addSensor(i2cScanner, ScanI2C::DeviceType::BH1750);
#endif
+#if __has_include()
+ addSensor(i2cScanner, ScanI2C::DeviceType::ADS1X15);
+ addSensor(i2cScanner, ScanI2C::DeviceType::ADS1X15_ALT);
+#endif
#if __has_include()
// TODO Can we scan for multiple sensors connected on the same bus?
addSensor(i2cScanner, ScanI2C::DeviceType::SHTXX);
@@ -759,21 +767,48 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
m.time = getTime();
bool validTelemetry = getEnvironmentTelemetry(&m);
+
if (validTelemetry) {
- LOG_INFO("Send: barometric_pressure=%f, current=%f, gas_resistance=%f, relative_humidity=%f, temperature=%f",
- m.variant.environment_metrics.barometric_pressure, m.variant.environment_metrics.current,
- m.variant.environment_metrics.gas_resistance, m.variant.environment_metrics.relative_humidity,
- m.variant.environment_metrics.temperature);
- LOG_INFO("Send: voltage=%f, IAQ=%d, distance=%f, lux=%f", m.variant.environment_metrics.voltage,
- m.variant.environment_metrics.iaq, m.variant.environment_metrics.distance, m.variant.environment_metrics.lux);
+ if (m.variant.environment_metrics.has_temperature || m.variant.environment_metrics.has_relative_humidity ||
+ m.variant.environment_metrics.has_barometric_pressure)
+ LOG_INFO("Send: barometric_pressure=%fkPa, relative_humidity=%f%RH, temperature=%fdegC",
+ m.variant.environment_metrics.barometric_pressure, m.variant.environment_metrics.relative_humidity,
+ m.variant.environment_metrics.temperature);
- LOG_INFO("Send: wind speed=%fm/s, direction=%d degrees, weight=%fkg", m.variant.environment_metrics.wind_speed,
- m.variant.environment_metrics.wind_direction, m.variant.environment_metrics.weight);
+ if (m.variant.environment_metrics.has_voltage || m.variant.environment_metrics.has_current ||
+ m.variant.environment_metrics.has_iaq || m.variant.environment_metrics.has_gas_resistance)
+ LOG_INFO("Send: voltage=%f, current=%f, IAQ=%d, gas_resistance=%f", m.variant.environment_metrics.voltage,
+ m.variant.environment_metrics.current, m.variant.environment_metrics.iaq,
+ m.variant.environment_metrics.gas_resistance);
- LOG_INFO("Send: radiation=%fµR/h", m.variant.environment_metrics.radiation);
+ if (m.variant.environment_metrics.has_distance || m.variant.environment_metrics.has_lux)
+ LOG_INFO("Send: distance=%f, lux=%f", m.variant.environment_metrics.distance, m.variant.environment_metrics.lux);
- LOG_INFO("Send: soil_temperature=%f, soil_moisture=%u", m.variant.environment_metrics.soil_temperature,
- m.variant.environment_metrics.soil_moisture);
+ if (m.variant.environment_metrics.has_wind_speed || m.variant.environment_metrics.has_wind_direction)
+ LOG_INFO("Send: wind speed=%fm/s, direction=%d degrees", m.variant.environment_metrics.wind_speed,
+ m.variant.environment_metrics.wind_direction);
+
+ if (m.variant.environment_metrics.has_weight)
+ LOG_INFO("Send: weight=%fkg", m.variant.environment_metrics.weight);
+
+ if (m.variant.environment_metrics.has_radiation)
+ LOG_INFO("Send: radiation=%fµR/h", m.variant.environment_metrics.radiation);
+
+ if (m.variant.environment_metrics.has_soil_temperature || m.variant.environment_metrics.has_soil_moisture)
+ LOG_INFO("Send: soil_temperature=%f, soil_moisture=%u", m.variant.environment_metrics.soil_temperature,
+ m.variant.environment_metrics.soil_moisture);
+
+ if (m.variant.environment_metrics.has_adc_voltage_ch0 || m.variant.environment_metrics.has_adc_voltage_ch1 ||
+ m.variant.environment_metrics.has_adc_voltage_ch2 || m.variant.environment_metrics.has_adc_voltage_ch3)
+ LOG_INFO("Send: adc_ch0=%f, adc_ch1=%f, adc_ch2=%f, adc_ch3=%f", m.variant.environment_metrics.adc_voltage_ch0,
+ m.variant.environment_metrics.adc_voltage_ch1, m.variant.environment_metrics.adc_voltage_ch2,
+ m.variant.environment_metrics.adc_voltage_ch3);
+
+ if (m.variant.environment_metrics.has_adc_voltage_ch4 || m.variant.environment_metrics.has_adc_voltage_ch5 ||
+ m.variant.environment_metrics.has_adc_voltage_ch6 || m.variant.environment_metrics.has_adc_voltage_ch7)
+ LOG_INFO("Send: adc_ch4=%f, adc_ch5=%f, adc_ch6=%f, adc_ch7=%f", m.variant.environment_metrics.adc_voltage_ch4,
+ m.variant.environment_metrics.adc_voltage_ch5, m.variant.environment_metrics.adc_voltage_ch6,
+ m.variant.environment_metrics.adc_voltage_ch7);
meshtastic_MeshPacket *p = allocDataProtobuf(m);
if (!p) {
diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp
index b00672d2d..60fe00c38 100644
--- a/src/modules/Telemetry/PowerTelemetry.cpp
+++ b/src/modules/Telemetry/PowerTelemetry.cpp
@@ -272,10 +272,15 @@ bool PowerTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
m.time = getTime();
bool validTelemetry = getPowerTelemetry(&m);
if (validTelemetry) {
- LOG_INFO("Send: ch1_voltage=%f, ch1_current=%f, ch2_voltage=%f, ch2_current=%f, "
- "ch3_voltage=%f, ch3_current=%f",
- m.variant.power_metrics.ch1_voltage, m.variant.power_metrics.ch1_current, m.variant.power_metrics.ch2_voltage,
- m.variant.power_metrics.ch2_current, m.variant.power_metrics.ch3_voltage, m.variant.power_metrics.ch3_current);
+ LOG_INFO("Send: ch1_voltage=%f, ch2_voltage=%f, ch3_voltage=%f", m.variant.power_metrics.ch1_voltage,
+ m.variant.power_metrics.ch2_voltage, m.variant.power_metrics.ch3_voltage);
+
+ bool hasAnyCurrent = m.variant.power_metrics.has_ch1_current || m.variant.power_metrics.has_ch2_current ||
+ m.variant.power_metrics.has_ch3_current;
+ if (hasAnyCurrent) {
+ LOG_INFO("Send: ch1_current=%f, ch2_current=%f, ch3_current=%f", m.variant.power_metrics.ch1_current,
+ m.variant.power_metrics.ch2_current, m.variant.power_metrics.ch3_current);
+ }
sensor_read_error_count = 0;
diff --git a/src/modules/Telemetry/Sensor/ADS1X15Sensor.cpp b/src/modules/Telemetry/Sensor/ADS1X15Sensor.cpp
new file mode 100644
index 000000000..1b22e6358
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/ADS1X15Sensor.cpp
@@ -0,0 +1,175 @@
+#include "configuration.h"
+
+#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include()
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "ADS1X15Sensor.h"
+#include "TelemetrySensor.h"
+#include
+
+ADS1X15Sensor::ADS1X15Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_ADS1X15, "ADS1X15") {}
+
+bool ADS1X15Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
+{
+ LOG_INFO("Init sensor: %s (address: 0x%x)", sensorName, dev->address.address);
+
+ _bus = bus;
+ _port = dev->address.port;
+ _address = dev->address.address;
+ _deviceType = dev->type;
+
+#ifdef ADS1X15_I2C_CLOCK_SPEED
+ reClockI2C.setup(_bus, _port);
+ reClockI2C.setClock(ADS1X15_I2C_CLOCK_SPEED);
+#endif /* ADS1X15_I2C_CLOCK_SPEED */
+
+ status = ads1x15.begin(_address, _bus);
+
+#ifdef ADS1X15_I2C_CLOCK_SPEED
+ reClockI2C.restoreClock();
+#endif /* ADS1X15_I2C_CLOCK_SPEED */
+
+ initI2CSensor();
+
+ return status;
+}
+
+struct _ADS1X15Measurement ADS1X15Sensor::getMeasurement(uint8_t ch)
+{
+ struct _ADS1X15Measurement measurement;
+
+ // Reset gain
+ ads1x15.setGain(GAIN_TWOTHIRDS);
+ double voltage_range = 6.144;
+
+ // Get value with full range
+ uint16_t value = ads1x15.readADC_SingleEnded(ch);
+
+ // Dynamic gain, to increase resolution of low voltage values
+ // If value is under 4.096v increase the gain depending on voltage
+ if (value < 21845) {
+ if (value > 10922) {
+
+ // 1x gain, 4.096V
+ ads1x15.setGain(GAIN_ONE);
+ voltage_range = 4.096;
+
+ } else if (value > 5461) {
+
+ // 2x gain, 2.048V
+ ads1x15.setGain(GAIN_TWO);
+ voltage_range = 2.048;
+
+ } else if (value > 2730) {
+
+ // 4x gain, 1.024V
+ ads1x15.setGain(GAIN_FOUR);
+ voltage_range = 1.024;
+
+ } else if (value > 1365) {
+
+ // 8x gain, 0.25V
+ ads1x15.setGain(GAIN_EIGHT);
+ voltage_range = 0.512;
+
+ } else {
+
+ // 16x gain, 0.125V
+ ads1x15.setGain(GAIN_SIXTEEN);
+ voltage_range = 0.256;
+ }
+
+ // Get the value again
+ value = ads1x15.readADC_SingleEnded(ch);
+ }
+
+ measurement.voltage = (float)value / 32768 * voltage_range;
+
+ return measurement;
+}
+
+struct _ADS1X15Measurements ADS1X15Sensor::getMeasurements()
+{
+ struct _ADS1X15Measurements measurements;
+
+ // ADS1X15 has 4 channels starting from 0
+ for (int i = 0; i < 4; i++) {
+ measurements.measurements[i] = getMeasurement(i);
+ }
+
+ return measurements;
+}
+
+bool ADS1X15Sensor::getMetrics(meshtastic_Telemetry *measurement)
+{
+ // Done here and not in getMeasurements to avoid the back-and-forth 4-8 times one after the other
+#ifdef ADS1X15_I2C_CLOCK_SPEED
+ reClockI2C.setClock(ADS1X15_I2C_CLOCK_SPEED);
+#endif /* ADS1X15_I2C_CLOCK_SPEED */
+
+ struct _ADS1X15Measurements m = getMeasurements();
+
+#ifdef ADS1X15_I2C_CLOCK_SPEED
+ reClockI2C.restoreClock();
+#endif /* ADS1X15_I2C_CLOCK_SPEED */
+
+ switch (_deviceType) {
+ case ScanI2C::DeviceType::ADS1X15: {
+ measurement->variant.environment_metrics.has_adc_voltage_ch0 = true;
+ measurement->variant.environment_metrics.has_adc_voltage_ch1 = true;
+ measurement->variant.environment_metrics.has_adc_voltage_ch2 = true;
+ measurement->variant.environment_metrics.has_adc_voltage_ch3 = true;
+
+ measurement->variant.environment_metrics.adc_voltage_ch0 = m.measurements[0].voltage;
+ measurement->variant.environment_metrics.adc_voltage_ch1 = m.measurements[1].voltage;
+ measurement->variant.environment_metrics.adc_voltage_ch2 = m.measurements[2].voltage;
+ measurement->variant.environment_metrics.adc_voltage_ch3 = m.measurements[3].voltage;
+
+ LOG_DEBUG(
+ "Got %s readings: adc_voltage_ch0=%f, adc_voltage_ch1=%f, adc_voltage_ch2=%f, adc_voltage_ch3=%f", sensorName,
+ measurement->variant.environment_metrics.adc_voltage_ch0, measurement->variant.environment_metrics.adc_voltage_ch1,
+ measurement->variant.environment_metrics.adc_voltage_ch2, measurement->variant.environment_metrics.adc_voltage_ch3);
+
+ break;
+ }
+ case ScanI2C::DeviceType::ADS1X15_ALT: {
+ measurement->variant.environment_metrics.has_adc_voltage_ch4 = true;
+ measurement->variant.environment_metrics.has_adc_voltage_ch5 = true;
+ measurement->variant.environment_metrics.has_adc_voltage_ch6 = true;
+ measurement->variant.environment_metrics.has_adc_voltage_ch7 = true;
+
+ measurement->variant.environment_metrics.adc_voltage_ch4 = m.measurements[0].voltage;
+ measurement->variant.environment_metrics.adc_voltage_ch5 = m.measurements[1].voltage;
+ measurement->variant.environment_metrics.adc_voltage_ch6 = m.measurements[2].voltage;
+ measurement->variant.environment_metrics.adc_voltage_ch7 = m.measurements[3].voltage;
+
+ LOG_DEBUG(
+ "Got %s readings: adc_voltage_ch4=%f, adc_voltage_ch5=%f, adc_voltage_ch6=%f, adc_voltage_ch7=%f", sensorName,
+ measurement->variant.environment_metrics.adc_voltage_ch4, measurement->variant.environment_metrics.adc_voltage_ch5,
+ measurement->variant.environment_metrics.adc_voltage_ch6, measurement->variant.environment_metrics.adc_voltage_ch7);
+
+ break;
+ }
+ default: {
+ measurement->variant.environment_metrics.has_adc_voltage_ch0 = true;
+ measurement->variant.environment_metrics.has_adc_voltage_ch1 = true;
+ measurement->variant.environment_metrics.has_adc_voltage_ch2 = true;
+ measurement->variant.environment_metrics.has_adc_voltage_ch3 = true;
+
+ measurement->variant.environment_metrics.adc_voltage_ch0 = m.measurements[0].voltage;
+ measurement->variant.environment_metrics.adc_voltage_ch1 = m.measurements[1].voltage;
+ measurement->variant.environment_metrics.adc_voltage_ch2 = m.measurements[2].voltage;
+ measurement->variant.environment_metrics.adc_voltage_ch3 = m.measurements[3].voltage;
+
+ LOG_DEBUG(
+ "Got %s readings: adc_voltage_ch0=%f, adc_voltage_ch1=%f, adc_voltage_ch2=%f, adc_voltage_ch3=%f", sensorName,
+ measurement->variant.environment_metrics.adc_voltage_ch0, measurement->variant.environment_metrics.adc_voltage_ch1,
+ measurement->variant.environment_metrics.adc_voltage_ch2, measurement->variant.environment_metrics.adc_voltage_ch3);
+
+ break;
+ }
+ }
+ return true;
+}
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/ADS1X15Sensor.h b/src/modules/Telemetry/Sensor/ADS1X15Sensor.h
new file mode 100644
index 000000000..4d56e2db7
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/ADS1X15Sensor.h
@@ -0,0 +1,52 @@
+#include "configuration.h"
+
+#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include()
+
+#include "../detect/ReClockI2C.h"
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "TelemetrySensor.h"
+#include
+
+#define ADS1X15_I2C_CLOCK_SPEED 100000
+// ADS1X15 has no practical way to be detected. Use this to toggle
+// between ADS1015 (0) or ADS1115 (1)
+#ifndef MESHTASTIC_ADC_ADS1115
+#define MESHTASTIC_ADC_ADS1115 1
+#endif
+
+class ADS1X15Sensor : public TelemetrySensor
+{
+ private:
+#if MESHTASTIC_ADC_ADS1115
+ Adafruit_ADS1115 ads1x15{};
+#else
+ Adafruit_ADS1015 ads1x15{};
+#endif
+
+#ifdef ADS1X15_I2C_CLOCK_SPEED
+ ReClockI2C reClockI2C;
+#endif
+ ScanI2C::DeviceType _deviceType{};
+
+ // get a single measurement for a channel
+ struct _ADS1X15Measurement getMeasurement(uint8_t ch);
+
+ // get all measurements for all channels
+ struct _ADS1X15Measurements getMeasurements();
+
+ public:
+ ADS1X15Sensor();
+ virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
+ virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+};
+
+struct _ADS1X15Measurement {
+ float voltage;
+};
+
+struct _ADS1X15Measurements {
+ // ADS1X15 has 4 channels
+ struct _ADS1X15Measurement measurements[4];
+};
+
+#endif