Skip to content
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
5 changes: 4 additions & 1 deletion Inferno.Api/Devices/Display.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,12 @@ protected virtual void Dispose(bool disposing)
if (disposing)
{
DisplayText("Shutting down...", "", "", "Goodbye!".PadLeft(20));
_i2c.Dispose();
// Dispose in dependency order (mirrors Init's teardown): the LCD
// writes through the driver to the I2C bus on Dispose, so the bus
// must outlive both. Disposing _i2c first would throw here.
_lcd.Dispose();
_driver.Dispose();
_i2c.Dispose();
}
disposedValue = true;
}
Expand Down
8 changes: 6 additions & 2 deletions Inferno.Api/Devices/RtdArray.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
using Inferno.Api.Interfaces;
using System.Linq;
using Iot.Device.Adc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;

namespace Inferno.Api.Devices
{
Expand All @@ -31,10 +33,12 @@ public class RtdArray : IRtdArray, IDisposable

Task _adcReadTask;
readonly CancellationTokenSource _stopCts = new();
readonly ILogger<RtdArray> _logger;

public RtdArray(SpiDevice spi)
public RtdArray(SpiDevice spi, ILogger<RtdArray>? logger = null)
{
_adc = new Mcp3008(spi);
_logger = logger ?? NullLogger<RtdArray>.Instance;
_grillResistances = new ConcurrentQueue<double>();
_probeResistances = new ConcurrentQueue<double>();

Expand Down Expand Up @@ -69,7 +73,7 @@ private async Task ReadAdc()
}
catch (Exception ex)
{
Console.WriteLine($"{DateTime.Now} {ex.Message} {ex.StackTrace}");
_logger.LogError(ex, "ADC read failed.");
try { await Task.Delay(TimeSpan.FromMilliseconds(10), _stopCts.Token); }
catch (OperationCanceledException) { break; }
continue;
Expand Down
32 changes: 20 additions & 12 deletions Inferno.Api/Pid/SmokerPid.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Diagnostics;
using Inferno.Common.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;

namespace Inferno.Api.Pid
{
Expand All @@ -13,31 +14,38 @@ public class SmokerPid
double _integral;
double _iMax = 0.5;

DateTime _lastUpdate;
// Monotonic timestamp of the last update. Using TimeProvider.GetTimestamp
// (Stopwatch-backed) instead of wall-clock DateTime keeps dt correct even when
// the Pi's clock steps on an NTP sync mid-cook.
readonly TimeProvider _timeProvider;
readonly ILogger<SmokerPid> _logger;
long _lastTimestamp;
double _lastTemp;

public double SetPoint { get; set; }
public SmokerPid(double PB, double Ti, double Td)
public SmokerPid(double PB, double Ti, double Td, TimeProvider? timeProvider = null, ILogger<SmokerPid>? logger = null)
{
_PB = PB;
_Ti = Ti;
_Td = Td;
_lastUpdate = DateTime.Now;
_timeProvider = timeProvider ?? TimeProvider.System;
_logger = logger ?? NullLogger<SmokerPid>.Instance;
_lastTimestamp = _timeProvider.GetTimestamp();
// NaN means "no valid previous sample yet" — the next reading seeds state
// instead of computing a derivative/integral across an unknown gap.
_lastTemp = double.NaN;
}

public double GetControlVariable(double currentTemp)
{
DateTime now = DateTime.Now;
long now = _timeProvider.GetTimestamp();

if (double.IsNaN(currentTemp))
{
// Sensor dropout: hold the integral, advance the clock, and force the
// next valid reading to re-seed so we don't compute a bogus derivative
// across the gap.
_lastUpdate = now;
_lastTimestamp = now;
_lastTemp = double.NaN;
return 0;
}
Expand All @@ -51,12 +59,12 @@ public double GetControlVariable(double currentTemp)
// proportional-only. A stale/huge dt here would otherwise spike the
// integral and derivative.
_lastTemp = currentTemp;
_lastUpdate = now;
Debug.WriteLine($"u={P} (seed)");
_lastTimestamp = now;
_logger.LogTrace("u={U} (seed)", P);
return P;
}

double dtSeconds = (now - _lastUpdate).TotalSeconds;
double dtSeconds = _timeProvider.GetElapsedTime(_lastTimestamp, now).TotalSeconds;

double I;
double D = 0;
Expand All @@ -77,10 +85,10 @@ public double GetControlVariable(double currentTemp)
}

double u = P + I + D;
Debug.WriteLine($"u={u} ({P}+{I}+{D})");
_logger.LogTrace("u={U} ({P}+{I}+{D})", u, P, I, D);

_lastTemp = currentTemp;
_lastUpdate = now;
_lastTimestamp = now;

return u;
}
Expand Down
22 changes: 17 additions & 5 deletions Inferno.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,23 @@

builder.Services.AddControllers();

builder.Services.AddSingleton<ISmoker>(new Smoker(new Auger(_gpio, 22),
new Blower(_gpio, 21),
new Igniter(_gpio, 23),
new RtdArray(_spi),
new Display()));
// Monotonic + wall clock, injectable so time-based logic can be driven in tests.
builder.Services.AddSingleton(TimeProvider.System);

// Build the smoker from the DI container so its collaborators get real loggers
// (and journald output) instead of Debug traces that vanish in a Release build.
builder.Services.AddSingleton<ISmoker>(sp =>
{
var loggerFactory = sp.GetRequiredService<ILoggerFactory>();
var timeProvider = sp.GetRequiredService<TimeProvider>();
return new Smoker(new Auger(_gpio, 22),
new Blower(_gpio, 21),
new Igniter(_gpio, 23),
new RtdArray(_spi, loggerFactory.CreateLogger<RtdArray>()),
new Display(),
loggerFactory,
timeProvider);
});

var app = builder.Build();

Expand Down
11 changes: 7 additions & 4 deletions Inferno.Api/Services/DisplayUpdater.cs
Original file line number Diff line number Diff line change
@@ -1,31 +1,34 @@
using System.Diagnostics;
using Inferno.Api.Interfaces;
using Inferno.Common.Interfaces;
using Inferno.Common.Models;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;

namespace Inferno.Api.Services
{
public class DisplayUpdater : IDisposable
{
ISmoker _smoker;
IDisplay _display;
readonly ILogger<DisplayUpdater> _logger;

bool _heartbeatFlag;

Task _updateDisplayLoop;
readonly CancellationTokenSource _stopCts = new();

public DisplayUpdater(ISmoker smoker, IDisplay display)
public DisplayUpdater(ISmoker smoker, IDisplay display, ILogger<DisplayUpdater>? logger = null)
{
_smoker = smoker;
_display = display;
_logger = logger ?? NullLogger<DisplayUpdater>.Instance;
_heartbeatFlag = false;
_updateDisplayLoop = UpdateDisplayLoop();
}

private async Task UpdateDisplayLoop()
{
Debug.WriteLine("Starting display thread.");
_logger.LogDebug("Starting display loop.");
while (!_stopCts.IsCancellationRequested)
{
try
Expand Down Expand Up @@ -70,7 +73,7 @@ private async Task UpdateDisplayLoop()
}
catch (Exception ex)
{
Debug.WriteLine($"{DateTime.Now} Display updater exception! {ex.Message}");
_logger.LogError(ex, "Display updater exception. Reinitializing display.");
_display.Init();
}
}
Expand Down
Loading
Loading