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

tera netxPM particulate sensor #618

Merged
merged 4 commits into from
Apr 20, 2023
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
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System;
using System.Linq;

namespace Meadow.Foundation.Sensors.Environmental
{
internal static class CommandManager
{
public static (int commandLength, int expectedResponseLength) GenerateCommand(NextPm.CommandByte command, byte[] buffer, params byte[] payload)
{
buffer[0] = NextPm.SensorAddress;
buffer[1] = (byte)command;

var payloadLength = 2;

if (payload != null && payload.Length > 0)
{
Array.Copy(payload, 0, buffer, 2, payload.Length);
payloadLength += payload.Length;
}

buffer[payloadLength] = CalculateChecksum(buffer, 0, payloadLength);

switch (command)
{
case NextPm.CommandByte.ReadFirmware:
return (3, 6);
case NextPm.CommandByte.ReadTempAndHumidity:
return (3, 8);
case NextPm.CommandByte.ReadWriteFanSpeed:
return (payloadLength + 1, 6);
case NextPm.CommandByte.SetPowerMode:
return (payloadLength + 1, 4);
case NextPm.CommandByte.ReadSensorState:
return (3, 4);
case NextPm.CommandByte.Read10sConcentrations:
case NextPm.CommandByte.Read60sConcentrations:
case NextPm.CommandByte.Read900sConcentrations:
return (3, 16);
default: throw new NotImplementedException();
}
}

public static byte CalculateChecksum(byte[] data, int start, int length)
{
return (byte)(0x100 - (data.Skip(start).Take(length).Sum(d => d) % 256));
}

public static bool ValidateChecksum(byte[] data, int start, int length)
{
var expected = CalculateChecksum(data, start, length - 1);
return expected == data[start + length - 1];
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Meadow.Foundation.Sensors.Environmental
{
public partial class NextPm
{
internal const byte SensorAddress = 0x81; // fixed address, per the data sheet
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;

namespace Meadow.Foundation.Sensors.Environmental
{
public partial class NextPm
{
internal enum CommandByte : byte
{
Read10sConcentrations = 0x11,
Read60sConcentrations = 0x12,
Read900sConcentrations = 0x13,
ReadTempAndHumidity = 0x14,
SetPowerMode = 0x15,
ReadSensorState = 0x16,
ReadFirmware = 0x17,
ReadWriteFanSpeed = 0x21,
ReadWriteModbusAddress = 0x22
}

[Flags]
internal enum SensorStatus : byte
{
Sleep = 1 << 0,
Degraded = 1 << 1,
NotReady = 1 << 2,
HeaterError = 1 << 3,
TempHumidyError = 1 << 4,
FanError = 1 << 5,
MemoryError = 1 << 6,
LaserError = 1 << 7,
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using Meadow.Hardware;
using System;
using System.Threading.Tasks;

namespace Meadow.Foundation.Sensors.Environmental
{
/// <summary>
/// Represents a TERA Sensor NextPM particulate matter sensor
/// </summary>
public partial class NextPm
{
private ISerialPort _port;
// dev note: these common buffers are used to minimize heap allocations during serial communications with the sensor
private byte[] _readBuffer = new byte[16];
private byte[] _writeBuffer = new byte[16];

private ISerialPort InitializePort(ISerialPort port)
{
if (port.IsOpen)
{
port.Close();
}

port.BaudRate = 115200;
port.DataBits = 8;
port.Parity = Parity.Even;
port.StopBits = StopBits.One;

return port;
}

private async Task SendCommand(CommandByte command, params byte[] payload)
{
var parameters = CommandManager.GenerateCommand(command, _writeBuffer, payload);

if (!_port.IsOpen)
{
_port.Open();
}

_port.Write(_writeBuffer, 0, parameters.commandLength);

var read = 0;
var expected = parameters.expectedResponseLength;
var timeoutMs = 3000;
var readDelay = 500;

await Task.Delay(100); // initial device response delay

Array.Clear(_readBuffer, 0, _readBuffer.Length);

do
{
read += _port.Read(_readBuffer, read, _readBuffer.Length - read);
if (read >= expected) break;

if (read > 2 && _readBuffer[1] != (byte)command && _readBuffer[1] == 0x16)
{
var status = (SensorStatus)_readBuffer[2];
// typically a "fail" due to the device being in sleep state
throw new TeraException($"Device status: {status}");
}

await Task.Delay(readDelay);
timeoutMs -= readDelay;
} while (timeoutMs > 0);

if (read < expected)
{
throw new TeraException("Serial timeout");
}
else
{
var checksumIsValid = CommandManager.ValidateChecksum(_readBuffer, 0, parameters.expectedResponseLength);

if (!checksumIsValid)
{
throw new TeraException("Invalid response checksum");
}
}
}
}
}
Loading