/
SerialWombatBase.AnalogInputPort.cs
201 lines (166 loc) · 6.88 KB
/
SerialWombatBase.AnalogInputPort.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
using Meadow.Hardware;
using Meadow.Units;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Meadow.Foundation.ICs.IOExpanders
{
public abstract partial class SerialWombatBase
{
/// <summary>
/// Represents a serial wombat analog input port
/// </summary>
public class AnalogInputPort : AnalogPortBase, IAnalogInputPort
{
/// <summary>
/// Raised when the port voltage value changes
/// </summary>
public event EventHandler<IChangeResult<Voltage>> Updated = (s, e) => { };
/// <summary>
/// Collection of event Observers for the Updated event
/// </summary>
protected List<IObserver<IChangeResult<Voltage>>> Observers { get; set; } = new List<IObserver<IChangeResult<Voltage>>>();
private readonly SerialWombatBase controller;
private readonly int supplyVoltage;
private readonly object _lock = new object();
private readonly Voltage[] buffer;
/// <summary>
/// Is the port sampling
/// </summary>
public bool IsSampling { get; protected set; } = false;
private CancellationTokenSource? SamplingTokenSource;
private Voltage? _previousVoltageReading;
/// <summary>
/// Current port voltage
/// </summary>
public Voltage Voltage { get; private set; }
/// <summary>
/// Port reference voltage
/// </summary>
public Voltage ReferenceVoltage { get; private set; }
/// <summary>
/// The sample count
/// </summary>
public int SampleCount { get; private set; }
/// <summary>
/// The update interval
/// </summary>
public TimeSpan UpdateInterval { get; private set; } = TimeSpan.FromSeconds(1);
/// <summary>
/// The voltage sampling buffer
/// </summary>
public Voltage[] VoltageSampleBuffer => buffer;
/// <summary>
/// The sampling interval
/// </summary>
public TimeSpan SampleInterval => TimeSpan.Zero;
/// <summary>
/// Create a new AnalogInputPort object
/// </summary>
public AnalogInputPort(SerialWombatBase controller, IPin pin, IAnalogChannelInfo channel, int sampleCount)
: base(pin, channel)
{
SampleCount = sampleCount;
this.controller = controller;
buffer = new Voltage[sampleCount];
supplyVoltage = (int)(controller.GetSupplyVoltage().Millivolts);
ReferenceVoltage = new Voltage(supplyVoltage, Voltage.UnitType.Millivolts);
controller.ConfigureAnalogInput((byte)pin.Key, (ushort)sampleCount);
}
/// <summary>
/// Take a reading
/// </summary>
public Task<Voltage> Read()
{
var data = controller.ReadPublicData((byte)Pin.Key);
Voltage = new Voltage((data * supplyVoltage) >> 16, Voltage.UnitType.Millivolts);
buffer[0] = Voltage;
return Task.FromResult(Voltage);
}
/// <summary>
/// Start updating
/// </summary>
public void StartUpdating(TimeSpan? updateInterval = null)
{
// the wombat does sampling internally
// thread safety
lock (_lock)
{
if (IsSampling) return;
IsSampling = true;
// if an update interval was passed in, override the default value
if (updateInterval is { } ui) { UpdateInterval = ui; }
SamplingTokenSource = new CancellationTokenSource();
var ct = SamplingTokenSource.Token;
Task.Factory.StartNew(async () =>
{
while (true)
{
// cleanup
if (ct.IsCancellationRequested)
{
// do task clean up here
Observers.ForEach(x => x.OnCompleted());
break;
}
var newVoltage = await Read();
var result = new ChangeResult<Voltage>(newVoltage, _previousVoltageReading);
_previousVoltageReading = newVoltage;
// raise our events and notify our subs
RaiseChangedAndNotify(result);
await Task.Delay(UpdateInterval);
}
IsSampling = false;
}, SamplingTokenSource.Token);
}
}
/// <summary>
/// Stop updating the port
/// </summary>
public void StopUpdating()
{
lock (_lock)
{
if (!IsSampling) return;
SamplingTokenSource?.Cancel();
IsSampling = false;
}
}
/// <summary>
/// Raise change events for subscribers
/// </summary>
/// <param name="changeResult">The change result with the current sensor data</param>
protected void RaiseChangedAndNotify(IChangeResult<Voltage> changeResult)
{
Updated?.Invoke(this, changeResult);
Observers.ForEach(x => x.OnNext(changeResult));
}
/// <summary>
/// Subscribe an observer for update events
/// </summary>
public IDisposable Subscribe(IObserver<IChangeResult<Voltage>> observer)
{
if (!Observers.Contains(observer)) Observers.Add(observer);
return new Unsubscriber(Observers, observer);
}
private class Unsubscriber : IDisposable
{
private readonly List<IObserver<IChangeResult<Voltage>>> _observers;
private readonly IObserver<IChangeResult<Voltage>> _observer;
public Unsubscriber(List<IObserver<IChangeResult<Voltage>>> observers, IObserver<IChangeResult<Voltage>> observer)
{
_observers = observers;
_observer = observer;
}
public void Dispose()
{
if (_observer != null)
{
_observers?.Remove(_observer);
}
}
}
}
}
}