Skip to content

Commit b936a31

Browse files
committed
Add some hard-coded full hardware device code.
Fix two of the sensors not starting their loop.
1 parent 4c0cf15 commit b936a31

File tree

5 files changed

+107
-31
lines changed

5 files changed

+107
-31
lines changed

src/Aether/Devices/Sensors/ObservableMs5637.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ internal sealed class ObservableMs5637 : ObservableSensor, IObservableI2cSensorF
1111
private ObservableMs5637(I2cDevice device)
1212
{
1313
_sensor = new Drivers.Ms5637(device);
14+
Start();
1415
}
1516

1617
protected override void DisposeCore() =>

src/Aether/Devices/Sensors/ObservableScd4x.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ private ObservableScd4x(I2cDevice device, IObservable<Measurement> dependencies)
1515
{
1616
_sensor = new Drivers.Scd4x(device);
1717
_dependencies = dependencies;
18+
Start();
1819
}
1920

2021
protected override void DisposeCore() =>

src/Aether/Program.cs

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,10 @@
1313
using System.Reactive.Subjects;
1414
using UnitsNet;
1515

16+
// TODO: this needs to be replaced by something that reads config, resolves dependencies, etc.
1617
var runDeviceCommand = new Command("run-device", "Runs an Aether device");
1718
runDeviceCommand.Handler = CommandHandler.Create(async () =>
1819
{
19-
// Initialize display.
20-
var spiConfig = new System.Device.Spi.SpiConnectionSettings(0, 0)
21-
{
22-
ClockFrequency = 10000000
23-
};
24-
using var gpio = new GpioController(PinNumberingScheme.Logical);
25-
using SpiDevice displayDevice = SpiDevice.Create(spiConfig);
26-
using var displayDriver = new WaveshareEPD2_9inV2(displayDevice, gpio, dcPinId: 25, rstPinId: 17, busyPinId: 24);
27-
2820
// Initialize MS5637.
2921
using I2cDevice ms5637Device = I2cDevice.Create(new I2cConnectionSettings(1, ObservableMs5637.DefaultAddress));
3022
await using ObservableSensor ms5637Driver = ObservableMs5637.OpenSensor(ms5637Device, dependencies: Observable.Empty<Measurement>());
@@ -34,7 +26,20 @@
3426
await using ObservableSensor scdDriver = ObservableScd4x.OpenSensor(scd4xDevice, dependencies: ms5637Driver);
3527

3628
// All the measurements funnel through here.
37-
IObservable<Measurement> measurements = Observable.Merge(ms5637Driver, scdDriver);
29+
// Multiple sensors can support the same measures. In this case, both devices support teperature. To prevent inconsistencies, only use one.
30+
IObservable<Measurement> measurements = Observable.Merge(
31+
ms5637Driver.Where(x => x.Measure == Measure.BarometricPressure),
32+
scdDriver
33+
);
34+
35+
// Initialize display.
36+
var spiConfig = new System.Device.Spi.SpiConnectionSettings(0, 0)
37+
{
38+
ClockFrequency = 10000000
39+
};
40+
using var gpio = new GpioController(PinNumberingScheme.Logical);
41+
using SpiDevice displayDevice = SpiDevice.Create(spiConfig);
42+
using var displayDriver = new WaveshareEPD2_9inV2(displayDevice, gpio, dcPinId: 25, rstPinId: 17, busyPinId: 24);
3843

3944
// Initialize the theme, which takes all the measurements and renders them to a display.
4045
var lines = new[] { Measure.CO2, Measure.Humidity, Measure.BarometricPressure, Measure.Temperature };
@@ -59,7 +64,7 @@
5964
{
6065
string type = sensorInfo switch
6166
{
62-
I2cSensorInfo i2c => $"i2c(0x{i2c.DefaultAddress:X2})",
67+
I2cSensorInfo i2c => $"i2c({i2c.DefaultAddress})",
6368
_ => throw new Exception($"Unknown {nameof(SensorInfo)} subclass.")
6469
};
6570

src/Aether/Reactive/AetherObservable.cs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Reactive;
22
using System.Reactive.Linq;
3+
using System.Threading.Channels;
34

45
namespace Aether.Reactive
56
{
@@ -53,6 +54,58 @@ public static IObservable<T> Finally<T>(this IObservable<T> observable, IObserva
5354
.Catch<T, Exception>(exception => Observable.Concat(finallySequence, Observable.Throw<T>(exception)))
5455
.Concat(finallySequence);
5556

57+
/// <summary>
58+
/// Buffers observed items until the observer is ready to be called.
59+
/// </summary>
60+
/// <param name="observable">An observable sequence.</param>
61+
/// <remarks>
62+
/// This is useful when an observer takes a long time to process, but can process any number of items in that time.
63+
/// </remarks>
64+
public static IObservable<IList<T>> Gate<T>(this IObservable<T> observable) =>
65+
Observable.Create(async (IObserver<IList<T>> observer, CancellationToken cancellationToken) =>
66+
{
67+
Channel<T> channel = Channel.CreateUnbounded<T>();
68+
69+
using CancellationTokenRegistration reg = cancellationToken.UnsafeRegister(static obj => ((ChannelWriter<T>)obj!).TryComplete(), channel.Writer);
70+
71+
using IDisposable sub = observable.Subscribe(next =>
72+
{
73+
try
74+
{
75+
channel.Writer.TryWrite(next);
76+
}
77+
catch (ChannelClosedException)
78+
{
79+
// ignore.
80+
}
81+
},
82+
err =>
83+
{
84+
channel.Writer.TryComplete(err);
85+
},
86+
() =>
87+
{
88+
channel.Writer.TryComplete();
89+
});
90+
91+
var list = new List<T>();
92+
93+
do
94+
{
95+
while (channel.Reader.TryRead(out T? value))
96+
{
97+
list.Add(value);
98+
}
99+
100+
if (list.Count != 0)
101+
{
102+
observer.OnNext(list);
103+
list = new List<T>();
104+
}
105+
}
106+
while (await channel.Reader.WaitToReadAsync().ConfigureAwait(false));
107+
});
108+
56109
/// <summary>
57110
/// Gets an event that triggers when the console's cancel key (CTRL+C) is pressed is pressed.
58111
/// </summary>

src/Aether/Themes/MultiLineTheme.cs

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
using Aether.Devices.Drivers;
22
using Aether.Devices.Sensors;
3+
using Aether.Reactive;
34
using SixLabors.Fonts;
45
using SixLabors.ImageSharp;
56
using SixLabors.ImageSharp.Drawing.Processing;
67
using SixLabors.ImageSharp.Processing;
8+
using System.Reactive.Linq;
79

810
namespace Aether.Themes
911
{
@@ -106,30 +108,44 @@ public static IDisposable CreateTheme(DisplayDriver driver, IEnumerable<Measure>
106108
// wire up against the source.
107109
// TODO: abstract and localize stringy bits.
108110

109-
return source.Subscribe(measurement =>
111+
var seen = new HashSet<Measure>();
112+
113+
return source.Gate().Subscribe(measurements =>
110114
{
111-
if (!offsets.TryGetValue(measurement.Measure, out int measureOffset))
115+
for (int i = measurements.Count - 1; i >= 0; --i)
112116
{
113-
return;
117+
Measurement measurement = measurements[i];
118+
119+
if (!seen.Add(measurement.Measure))
120+
{
121+
continue;
122+
}
123+
124+
if (!offsets.TryGetValue(measurement.Measure, out int measureOffset))
125+
{
126+
return;
127+
}
128+
129+
string text = measurement.Measure switch
130+
{
131+
Measure.Humidity => (measurement.RelativeHumidity.Value * (1.0 / 100.0)).ToString("P0"),
132+
Measure.Temperature => measurement.Temperature.DegreesFahrenheit.ToString("N1"),
133+
Measure.CO2 => measurement.Co2.PartsPerMillion.ToString("N0"),
134+
Measure.BarometricPressure => measurement.BarometricPressure.Atmospheres.ToString("N2"),
135+
_ => throw new Exception($"Unsupported measure '{measurement.Measure}'.")
136+
};
137+
138+
float measureOffsetY = measureOffset * measureHeight;
139+
var location = new PointF(measureOffsetX, measureOffsetY);
140+
141+
image.Mutate(ctx =>
142+
{
143+
ctx.Fill(Color.White, new RectangleF(0.0f, measureOffsetY - measureHeight, measureOffsetX, measureHeight));
144+
ctx.DrawText(bottomRightAlignDrawingOptions, text, measurementFont, Color.Black, location);
145+
});
114146
}
115147

116-
string text = measurement.Measure switch
117-
{
118-
Measure.Humidity => (measurement.RelativeHumidity.Value * (1.0 / 100.0)).ToString("P0"),
119-
Measure.Temperature => measurement.Temperature.DegreesFahrenheit.ToString("N1"),
120-
Measure.CO2 => measurement.Co2.PartsPerMillion.ToString("N0"),
121-
Measure.BarometricPressure => measurement.BarometricPressure.Atmospheres.ToString("N2"),
122-
_ => throw new Exception($"Unsupported measure '{measurement.Measure}'.")
123-
};
124-
125-
float measureOffsetY = measureOffset * measureHeight;
126-
var location = new PointF(measureOffsetX, measureOffsetY);
127-
128-
image.Mutate(ctx =>
129-
{
130-
ctx.DrawText(bottomRightAlignDrawingOptions, text, measurementFont, Color.Black, location);
131-
});
132-
148+
seen.Clear();
133149
driver.DisplayImage(image);
134150
});
135151
}

0 commit comments

Comments
 (0)