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

Feature/scd40 #472

Merged
merged 12 commits into from
Nov 18, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using Meadow.Devices;
using Meadow.Foundation.Sensors.Environmental;

namespace MeadowApp
namespace Sensors.Environmental.Ags01Db_Sample
{
public class MeadowApp : App<F7FeatherV2>
{
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Meadow.Hardware;

namespace Meadow.Foundation.Sensors.Environmental
{
/// <summary>
/// Represents an SCD40 C02 sensor
/// </summary>
public class Scd40 : Scd4x
{
/// <summary>
/// Create a new Scd40 object
/// </summary>
/// <param name="i2cBus">The I2C bus</param>
/// <param name="address">The I2C address</param>
public Scd40(II2cBus i2cBus, byte address = 98)
: base(i2cBus, address)
{ }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Meadow.Hardware;

namespace Meadow.Foundation.Sensors.Environmental
{
/// <summary>
/// Represents an SCD41 C02 sensor
/// </summary>
public class Scd41 : Scd4x
{
/// <summary>
/// Create a new Scd41 object
/// </summary>
/// <param name="i2cBus">The I2C bus</param>
/// <param name="address">The I2C address</param>
public Scd41(II2cBus i2cBus, byte address = 98)
: base(i2cBus, address)
{ }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
namespace Meadow.Foundation.Sensors.Environmental
{
partial class Scd4x
{
/// <summary>
/// Valid I2C addresses for the sensor
/// </summary>
public enum Addresses : byte
{
/// <summary>
/// Bus address 0x62
/// </summary>
Address_0x62 = 0x62,
/// <summary>
/// Default bus address
/// </summary>
Default = Address_0x62
}

/// <summary>
/// Sdc4x commands
/// </summary>
enum Commands : ushort
{
//Basic Commands
StartPeriodicMeasurement = 0x21b1,
ReadMeasurement = 0xec05, // execution time: 1ms
StopPeriodicMeasurement = 0x3f86, // execution time: 500ms

//On-chip output signal compensation
SetTemperatureOffset = 0x241d, // execution time: 1ms
GetTemperatureOffset = 0x2318, // execution time: 1ms
SetSensorAltitude = 0x2427, // execution time: 1ms
GetSensorAltitude = 0x2322, // execution time: 1ms
SetAmbientPressure = 0xe000, // execution time: 1ms

//Field calibration
PerformForcedCalibration = 0x362f, // execution time: 400ms
SetAutomaticSelfCalibrationEnabled = 0x2416, // execution time: 1ms
GetAutomaticSelfCalibrationEnabled = 0x2313, // execution time: 1ms

//Low power
StartLowPowerPeriodicMeasurement = 0x21ac,
GetDataReadyStatus = 0xe4b8, // execution time: 1ms

//Advanced features
PersistSettings = 0x3615, // execution time: 800ms
GetSerialNumber = 0x3682, // execution time: 1ms
PerformSelfTest = 0x3639, // execution time: 10000ms
PerformFactoryReset = 0x3632, // execution time: 1200ms
ReInit = 0x3646, // execution time: 20ms

//Low power single shot - SCD41 only
MeasureSingleShot = 0x219d, // execution time: 5000ms
MeasureSingleShotRhtOnly = 0x2196, // execution time: 50ms
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Meadow.Hardware;
using Meadow.Peripherals.Sensors;
using Meadow.Peripherals.Sensors.Environmental;
using Meadow.Units;

namespace Meadow.Foundation.Sensors.Environmental
{
/// <summary>
/// Base class for SCD4x series of C02 sensors
/// </summary>
public abstract partial class Scd4x : ByteCommsSensorBase<(Concentration? Concentration,
Units.Temperature? Temperature,
RelativeHumidity? Humidity)>,
ITemperatureSensor, IHumiditySensor, IConcentrationSensor
{
/// <summary>
/// Raised when the concentration changes
/// </summary>
public event EventHandler<IChangeResult<Concentration>> ConcentrationUpdated = delegate { };

/// <summary>
/// Raised when the temperature value changes
/// </summary>
public event EventHandler<IChangeResult<Units.Temperature>> TemperatureUpdated = delegate { };

/// <summary>
/// Raised when the humidity value changes
/// </summary>
public event EventHandler<IChangeResult<RelativeHumidity>> HumidityUpdated = delegate { };

/// <summary>
/// The current C02 concentration value
/// </summary>
public Concentration? Concentration => Conditions.Concentration;

/// <summary>
/// The current temperature
/// </summary>
public Units.Temperature? Temperature => Conditions.Temperature;

/// <summary>
/// The current humidity
/// </summary>
public RelativeHumidity? Humidity => Conditions.Humidity;

/// <summary>
/// Create a new Scd4x object
/// </summary>
/// <remarks>
/// The constructor sends the stop periodic updates method otherwise
/// the sensor may not respond to new commands
/// </remarks>
/// <param name="i2cBus">The I2C bus</param>
/// <param name="address">The I2C address</param>
public Scd4x(II2cBus i2cBus, byte address = (byte)Addresses.Default)
: base(i2cBus, address, readBufferSize: 9, writeBufferSize: 9)
{
StopPeriodicUpdates().Wait();
}

/// <summary>
/// Re-initialize the sensor
/// </summary>
public Task ReInitialize()
{
SendCommand(Commands.ReInit);
return Task.Delay(1000);
}

/// <summary>
/// Forced recalibration allows recalibration using an external CO2 reference
/// </summary>
public Task PerformForcedRecalibration()
{
SendCommand(Commands.PerformForcedCalibration);
return Task.Delay(400);
}

/// <summary>
/// Persist settings to EEPROM
/// </summary>
public void PersistSettings()
{
SendCommand(Commands.PersistSettings);
}

/// <summary>
/// Device factory reset and clear all saved settings
/// </summary>
public void PerformFactoryReset()
{
SendCommand(Commands.PerformFactoryReset);
}

/// <summary>
/// Get Serial Number from the device
/// </summary>
/// <returns>a 48bit (6 byte) serial number as a byte array</returns>
public byte[] GetSerialNumber()
{
SendCommand(Commands.GetSerialNumber);
Thread.Sleep(1);

var data = new byte[9];
Peripheral.Read(data);

var ret = new byte[6];

ret[0] = data[0];
ret[1] = data[1];
ret[2] = data[3];
ret[3] = data[4];
ret[4] = data[6];
ret[5] = data[7];

return ret;
}

/// <summary>
/// Is there sensor measurement data ready
/// Sensor returns data ~5 seconds in normal operation and ~30 seconds in low power mode
/// </summary>
/// <returns>True if ready</returns>
protected bool IsDataReady()
{
SendCommand(Commands.GetDataReadyStatus);
Thread.Sleep(1);
var data = new byte[3];
Peripheral.Read(data);

if (data[1] == 0 && (data[0] & 0x07) == 0)
{
return false;
}
return true;
}

/// <summary>
/// Stop the sensor from sampling
/// The sensor will not respond to commands for 500ms
/// </summary>
Task StopPeriodicUpdates()
{
SendCommand(Commands.StopPeriodicMeasurement);
return Task.Delay(500);
}

/// <summary>
/// Starts updating the sensor on the updateInterval frequency specified
/// The sensor updates every 5 seconds, its recommended to choose an interval of 5s or longer
/// If the update interval is 30 seconds or longer, the sensor will run in low power mode
/// </summary>
public override void StartUpdating(TimeSpan? updateInterval = null)
{
if (updateInterval != null && updateInterval.Value.TotalSeconds >= 30)
{
SendCommand(Commands.StartLowPowerPeriodicMeasurement);
}
else
{
SendCommand(Commands.StartPeriodicMeasurement);
}
base.StartUpdating(updateInterval);
}

/// <summary>
/// Stop updating the sensor
/// The sensor will not respond to commands for 500ms
/// The call will delay the calling thread for 500ms
/// </summary>
public override void StopUpdating()
{
_ = StopPeriodicUpdates();
base.StopUpdating();
}

void SendCommand(Commands command)
{
var data = new byte[2];
data[0] = (byte)((ushort)command >> 8);
data[1] = (byte)(ushort)command;

Peripheral.Write(data);
}

/// <summary>
/// Get Scdx40 C02 Gas Concentration and
/// Update the Concentration property
/// </summary>
protected override async Task<(Concentration? Concentration, Units.Temperature? Temperature, RelativeHumidity? Humidity)> ReadSensor()
{
return await Task.Run(() =>
{
while(IsDataReady() == false)
{
Thread.Sleep(500);
}

(Concentration Concentration, Units.Temperature Temperature, RelativeHumidity Humidity) conditions;

SendCommand(Commands.ReadMeasurement);
Thread.Sleep(1);
Peripheral.Read(ReadBuffer.Span[0..9]);

int value = ReadBuffer.Span[0] << 8 | ReadBuffer.Span[1];
conditions.Concentration = new Concentration(value, Units.Concentration.UnitType.PartsPerMillion);

conditions.Temperature = CalcTemperature(ReadBuffer.Span[3], ReadBuffer.Span[4]);

value = ReadBuffer.Span[6] << 8 | ReadBuffer.Span[8];
double humidiy = 100 * value / 65536.0;
conditions.Humidity = new RelativeHumidity(humidiy, RelativeHumidity.UnitType.Percent);

return conditions;
});
}

Units.Temperature CalcTemperature(byte valueHigh, byte valueLow)
{
int value = valueHigh << 8 | valueLow;
double temperature = -45 + value * 175 / 65536.0;
return new Units.Temperature(temperature, Units.Temperature.UnitType.Celsius);
}

/// <summary>
/// Raise change events for subscribers
/// </summary>
/// <param name="changeResult">The change result with the current sensor data</param>
protected void RaiseChangedAndNotify(IChangeResult<(Concentration? Concentration, Units.Temperature? Temperature, RelativeHumidity? Humidity)> changeResult)
{
if (changeResult.New.Temperature is { } temperature)
{
TemperatureUpdated?.Invoke(this, new ChangeResult<Units.Temperature>(temperature, changeResult.Old?.Temperature));
}
if (changeResult.New.Humidity is { } humidity)
{
HumidityUpdated?.Invoke(this, new ChangeResult<RelativeHumidity>(humidity, changeResult.Old?.Humidity));
}
if (changeResult.New.Concentration is { } concentration)
{
ConcentrationUpdated?.Invoke(this, new ChangeResult<Concentration>(concentration, changeResult.Old?.Concentration));
}
base.RaiseEventsAndNotify(changeResult);
}

byte GetCrc(byte value1, byte value2)
{
static byte CrcCalc(byte crc, byte value)
{
crc ^= value;
for (byte crcBit = 8; crcBit > 0; --crcBit)
{
if ((crc & 0x80) > 0)
{
crc = (byte)((crc << 1) ^ 0x31);
}
else
{
crc <<= 1;
}
}
return crc;
}

return CrcCalc(CrcCalc(0xFF, value1), value2);
}

async Task<Units.Temperature> ISamplingSensor<Units.Temperature>.Read()
=> (await Read()).Temperature.Value;

async Task<RelativeHumidity> ISamplingSensor<RelativeHumidity>.Read()
=> (await Read()).Humidity.Value;

async Task<Concentration> ISamplingSensor<Concentration>.Read()
=> (await Read()).Concentration.Value;
}
}
Loading