/
MiniPID2.cs
237 lines (209 loc) · 8.61 KB
/
MiniPID2.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
using Meadow.Hardware;
using Meadow.Peripherals.Sensors.Environmental;
using Meadow.Units;
using System;
using System.Threading.Tasks;
namespace Meadow.Foundation.Sensors.Environmental
{
/// <summary>
/// Represents an IonScience MiniPID2 analog photoionisation (PID) Volatile Organic Compounds (VOC) sensor
/// </summary>
public partial class MiniPID2 : SamplingSensorBase<Concentration>, IVOCConcentrationSensor, IDisposable
{
/// <summary>
/// The current VOC concentration value
/// </summary>
public Concentration? VOCConcentration { get; protected set; }
/// <summary>
/// The MiniPID2 device type
/// </summary>
public MiniPID2Type MiniPID2DeviceType { get; protected set; }
///<Summary>
/// AnalogInputPort connected to temperature sensor
///</Summary>
protected IAnalogInputPort AnalogInputPort { get; }
/// <summary>
/// Is the object disposed
/// </summary>
public bool IsDisposed { get; private set; }
/// <summary>
/// Did we create the port(s) used by the peripheral
/// </summary>
readonly bool createdPort = false;
SensorCalibration[]? calibrations;
/// <summary>
/// Create a new MiniPID2 object
/// </summary>
/// <param name="analogPin">The analog data pin connected to the sensor</param>
/// <param name="pid2Type">The MiniPID sensor type</param>
/// <param name="sampleCount">How many samples to take during a given reading.
/// These are automatically averaged to reduce noise</param>
/// <param name="sampleInterval">The time between sample readings</param>
public MiniPID2(IPin analogPin,
MiniPID2Type pid2Type,
int sampleCount = 5,
TimeSpan? sampleInterval = null) :
this(analogPin.CreateAnalogInputPort(sampleCount,
sampleInterval ?? TimeSpan.FromMilliseconds(40),
new Voltage(3.3, Voltage.UnitType.Volts)), pid2Type)
{
createdPort = true;
}
/// <summary>
/// Create a new MiniPID2 object
/// </summary>
/// <param name="analogInputPort">The analog port connected to the sensor</param>
/// <param name="pid2Type">The MiniPID sensor type</param>
public MiniPID2(IAnalogInputPort analogInputPort, MiniPID2Type pid2Type)
{
MiniPID2DeviceType = pid2Type;
AnalogInputPort = analogInputPort;
Initialize();
}
/// <summary>
/// Set the sensor voltage offset
/// Useful for compensating for air conditions
/// </summary>
/// <param name="offset">Offset voltage</param>
/// <param name="sensorType">The sensor to change</param>
public void SetOffsetForSensor(Voltage offset, MiniPID2Type sensorType)
{
calibrations![(int)sensorType].Offset = offset;
}
/// <summary>
/// Get the voltage offset for a sensor
/// </summary>
/// <param name="sensorType">The sensor</param>
/// <returns>The offset as voltage</returns>
public Voltage GetOffsetForSensor(MiniPID2Type sensorType)
=> calibrations![(int)sensorType].Offset;
/// <summary>
/// Initialize the sensor
/// </summary>
void Initialize()
{
static SensorCalibration GetCalibration(int airOffsetLow, int airOffsetHigh, double sensitivity, double minimumPPB)
{
return new SensorCalibration(new Voltage((airOffsetLow + airOffsetHigh) / 2, Voltage.UnitType.Millivolts),
new Voltage(sensitivity, Voltage.UnitType.Millivolts),
new Concentration(minimumPPB, Units.Concentration.UnitType.PartsPerBillion));
}
calibrations = new SensorCalibration[(int)MiniPID2Type.count];
calibrations[(int)MiniPID2Type.PPM] = GetCalibration(51, 65, 65, 100);
calibrations[(int)MiniPID2Type.PPM_WR] = GetCalibration(51, 64, 40, 500);
calibrations[(int)MiniPID2Type.PPB] = GetCalibration(51, 80, 30, 1);
calibrations[(int)MiniPID2Type.PPB_WR] = GetCalibration(51, 80, 5, 20);
calibrations[(int)MiniPID2Type.HS] = GetCalibration(100, 200, 600, 0.5);
calibrations[(int)MiniPID2Type._10ev] = GetCalibration(51, 80, 15, 5);
calibrations[(int)MiniPID2Type._11_7eV] = GetCalibration(51, 90, 1, 100);
AnalogInputPort.Subscribe
(
IAnalogInputPort.CreateObserver(
result =>
{
ChangeResult<Concentration> changeResult = new()
{
New = VoltageToConcentration(result.New),
Old = VOCConcentration
};
VOCConcentration = changeResult.New;
RaiseEventsAndNotify(changeResult);
}
)
);
}
/// <summary>
/// Convenience method to get the current concentration. For frequent reads, use
/// StartSampling() and StopSampling() in conjunction with the SampleBuffer.
/// </summary>
/// <returns>The concentration averages of the given sample count</returns>
protected override async Task<Concentration> ReadSensor()
{
var voltage = await AnalogInputPort.Read();
var newConcentration = VoltageToConcentration(voltage);
VOCConcentration = newConcentration;
return newConcentration;
}
/// <summary>
/// Starts updating the sensor on the updateInterval frequency specified
/// </summary>
public override void StartUpdating(TimeSpan? updateInterval = null)
{
lock (samplingLock)
{
if (IsSampling) { return; }
IsSampling = true;
AnalogInputPort.StartUpdating(updateInterval);
}
}
/// <summary>
/// Stops sampling the concentration
/// </summary>
public override void StopUpdating()
{
lock (samplingLock)
{
if (!IsSampling) { return; }
IsSampling = false;
AnalogInputPort.StopUpdating();
}
}
/// <summary>
/// Converts voltage to Concentration
/// </summary>
/// <param name="voltage"></param>
/// <returns>Concentration</returns>
protected Concentration VoltageToConcentration(Voltage voltage)
{
int i = (int)MiniPID2DeviceType;
var ppm = (voltage.Millivolts - calibrations![i].Offset.Millivolts) / calibrations[i].Sensitivity.Millivolts;
if (ppm < calibrations[i].MinimumDetectionLimit.PartsPerMillion)
{
return calibrations[i].MinimumDetectionLimit;
}
return new Concentration(ppm, Units.Concentration.UnitType.PartsPerMillion);
}
struct SensorCalibration
{
public SensorCalibration(Voltage offset, Voltage sensitivity, Concentration minimumDetectionLimit)
{
Offset = offset;
Sensitivity = sensitivity;
MinimumDetectionLimit = minimumDetectionLimit;
}
/// <summary>
/// The offset to compensate for air quality/conditions
/// </summary>
public Voltage Offset { get; set; }
/// <summary>
/// Sensitivity mv/ppm as voltage
/// </summary>
public Voltage Sensitivity { get; set; }
/// <summary>
/// The minimum concentration returned by the sensor
/// </summary>
public Concentration MinimumDetectionLimit { get; set; }
}
///<inheritdoc/>
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Dispose of the object
/// </summary>
/// <param name="disposing">Is disposing</param>
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing && createdPort)
{
AnalogInputPort?.Dispose();
}
IsDisposed = true;
}
}
}
}