Skip to content

Commit d7f8828

Browse files
committed
Enable trimmable + AOT support
1 parent b34a268 commit d7f8828

File tree

2 files changed

+175
-38
lines changed

2 files changed

+175
-38
lines changed

Program.cs

+174-38
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@
2727
.AddDeviceManager();
2828

2929
var startListenerSem = new SemaphoreSlim(0);
30-
string? appState = null;
30+
var sharedState = new SharedState();
3131
var serializerOptions = new JsonSerializerOptions { WriteIndented = false };
32-
var ditherReceived = 0;
32+
var phdEventJsonContent = new PHDEventJsonContext(serializerOptions);
3333

3434
using var host = builder.Build();
3535

@@ -50,27 +50,36 @@
5050
if (device is GuiderDevice guiderDevice)
5151
{
5252
guider = new Guider(guiderDevice, external);
53-
await guider.Driver.ConnectAsync(cts.Token);
53+
guider.Driver.DeviceConnectedEvent += GuiderDriver_DeviceConnectedEvent;
5454
guider.Driver.GuiderStateChangedEvent += GuiderDriver_GuiderStateChangedEvent;
55+
guider.Driver.GuidingErrorEvent += GuiderDriver_GuidingErrorEvent;
56+
57+
await guider.Driver.ConnectAsync(cts.Token);
5558
}
5659
else
5760
{
5861
throw new InvalidOperationException("Could not connect to guider");
5962
}
6063

64+
void GuiderDriver_DeviceConnectedEvent(object? sender, DeviceConnectedEventArgs e)
65+
{
66+
external.AppLogger.LogInformation("Guider {DeviceName} {YesOrNo}", (sender as IGuider)?.Name, e.Connected ? "connected" : "disconnected");
67+
}
68+
69+
void GuiderDriver_GuidingErrorEvent(object? sender, GuidingErrorEventArgs e)
70+
{
71+
external.AppLogger.LogError(e.Exception, "Guider {DeviceName} encountered error {ErrorMessage}", e.Device.DisplayName, e.Message);
72+
}
73+
6174
async void GuiderDriver_GuiderStateChangedEvent(object? sender, GuiderStateChangedEventArgs e)
6275
{
63-
if (e.AppState is { } state)
76+
if (e.AppState is { Length: > 0 } state && state is not SharedState.UnknownState && sharedState.SetState(state) is { } orig && orig != state)
6477
{
65-
var orig = Interlocked.Exchange(ref appState, state);
66-
if (orig != state)
67-
{
68-
Console.WriteLine("Guider in state = {0} on event = {1}", state, e.Event);
69-
}
78+
external.AppLogger.LogInformation("Guider in state = {NewState} (was {OrigState}) on event = {EventName}", state, orig ?? "Unknown", e.Event);
7079
}
7180
else
7281
{
73-
Console.WriteLine("Event = {0}", e.Event);
82+
external.AppLogger.LogInformation("Event = {EventName}", e.Event);
7483
}
7584

7685
if (startListenerSem.CurrentCount == 0)
@@ -81,6 +90,18 @@ async void GuiderDriver_GuiderStateChangedEvent(object? sender, GuiderStateChang
8190
{
8291
switch (e.Event)
8392
{
93+
case "ConfigurationChange":
94+
await BroadcastEventAsync(new ConfigurationChangeEvent(), cts.Token);
95+
break;
96+
97+
case "Calibrating":
98+
await BroadcastEventAsync(new CalibratingEvent(), cts.Token);
99+
break;
100+
101+
case "CalibrationComplete":
102+
await BroadcastEventAsync(new CalibrationCompleteEvent(), cts.Token);
103+
break;
104+
84105
case "GuideStep":
85106
if (await guider.Driver.GetStatsAsync(cts.Token) is { LastRaErr: { } ra, LastDecErr: { } dec } stats)
86107
{
@@ -110,6 +131,50 @@ async void GuiderDriver_GuiderStateChangedEvent(object? sender, GuiderStateChang
110131
}
111132
}
112133
break;
134+
135+
case "LockPositionSet":
136+
await BroadcastEventAsync(new LockPositionSetEvent(), cts.Token);
137+
break;
138+
139+
case "LockPositionLost":
140+
await BroadcastEventAsync(new LockPositionLostEvent(), cts.Token);
141+
break;
142+
143+
case "AppState":
144+
await BroadcastEventAsync(new AppStateEvent(e.AppState ?? "Unknown"), cts.Token);
145+
break;
146+
147+
case "Paused":
148+
await BroadcastEventAsync(new PausedEvent(), cts.Token);
149+
break;
150+
151+
case "StarLost":
152+
await BroadcastEventAsync(new StarLostEvent(), cts.Token);
153+
break;
154+
155+
case "LoopingExposures":
156+
await BroadcastEventAsync(new LoopingExposuresEvent(), cts.Token);
157+
break;
158+
159+
case "StarSelected":
160+
await BroadcastEventAsync(new StarSelectedEvent(), cts.Token);
161+
break;
162+
163+
case "StartGuiding":
164+
await BroadcastEventAsync(new StartGuidingEvent(), cts.Token);
165+
break;
166+
167+
case "GuidingStopped":
168+
await BroadcastEventAsync(new GuidingStoppedEvent(), cts.Token);
169+
break;
170+
171+
case "LoopingExposuresStopped":
172+
await BroadcastEventAsync(new LoopingExposuresStoppedEvent(), cts.Token);
173+
break;
174+
175+
default:
176+
external.AppLogger.LogWarning("Unhandled event = {EventName}", e.Event);
177+
break;
113178
}
114179
}
115180
}
@@ -119,7 +184,7 @@ async void GuiderDriver_GuiderStateChangedEvent(object? sender, GuiderStateChang
119184
await startListenerSem.WaitAsync(cts.Token);
120185
listener.Start();
121186

122-
while (!cts.IsCancellationRequested)
187+
while (!cts.IsCancellationRequested && guider.Driver.Connected)
123188
{
124189
var client = await listener.AcceptTcpClientAsync(cts.Token);
125190

@@ -142,6 +207,7 @@ async Task ClientWorkerAsync(NetworkStream stream, EndPoint endPoint)
142207

143208
await SendEventAsync(stream, new VersionEvent(version.FirstOrDefault() ?? "Unknown", version.LastOrDefault() ?? "Unknown"), endPoint, cts.Token);
144209

210+
var appState = sharedState.AppState;
145211
await (appState switch
146212
{
147213
"Stopped" => SendEventAsync(stream, new LoopingExposuresStoppedEvent(), endPoint, cts.Token), // PHD is idle
@@ -159,17 +225,18 @@ async Task ClientWorkerAsync(NetworkStream stream, EndPoint endPoint)
159225
try
160226
{
161227
string? line;
162-
while (!cts.IsCancellationRequested && (line = await reader.ReadLineAsync()) != null)
228+
while (guider.Driver.Connected && !cts.IsCancellationRequested && (line = await reader.ReadLineAsync(cts.Token)) != null)
163229
{
164-
Console.WriteLine("<< {0} from {1}", line, endPoint);
165-
var ditherCmd = JsonSerializer.Deserialize<DitherRPC>(line, serializerOptions);
230+
var ditherCmd = JsonSerializer.Deserialize(line, PHDJsonRPCJsonContext.Default.DitherRPC);
166231
if (ditherCmd?.Params is { } @params)
167232
{
233+
external.AppLogger.LogInformation("Recieved dithering command {@Params} from {RemoteEndpoint}", @params, endPoint);
234+
168235
var count = clients.Count;
169-
var prev = Interlocked.Increment(ref ditherReceived);
236+
var prev = sharedState.IncrementDitherReceived();
170237
if (prev == count)
171238
{
172-
if (Interlocked.CompareExchange(ref ditherReceived, 0, count) == count)
239+
if (sharedState.AllDitherReceived(count))
173240
{
174241
external.AppLogger.LogInformation("All {ClientCOunt} connected clients issued a dither command, actually dither now", count);
175242
await guider.Driver.DitherAsync(@params.Amount, @params.Settle.Pixels, @params.Settle.Time, @params.Settle.Timeout, @params.RaOnly, cts.Token);
@@ -184,6 +251,10 @@ async Task ClientWorkerAsync(NetworkStream stream, EndPoint endPoint)
184251
external.AppLogger.LogInformation("Only {DitherIssued} of {ClientCount} connected clients issued a dither command, ignoring", prev, count);
185252
}
186253
}
254+
else
255+
{
256+
external.AppLogger.LogWarning("Received unknown command {Line} from {RemoteEndpoint}", line, endPoint);
257+
}
187258
}
188259
}
189260
finally
@@ -198,39 +269,104 @@ async Task ClientWorkerAsync(NetworkStream stream, EndPoint endPoint)
198269

199270
async Task BroadcastEventAsync<TEvent>(TEvent @event, CancellationToken cancellationToken) where TEvent : PHDEvent
200271
{
272+
var failedClients = new ConcurrentBag<EndPoint>();
201273
await Parallel.ForEachAsync(clients, cancellationToken, async (kv, cancellationToken) =>
202274
{
203-
await SendEventAsync(kv.Value.Stream, @event, kv.Key, cancellationToken);
275+
try
276+
{
277+
await SendEventAsync(kv.Value.Stream, @event, kv.Key, cancellationToken);
278+
}
279+
catch (Exception ex)
280+
{
281+
external.AppLogger.LogError(ex, "Error while boradcasting event {EventTYpe} to {ClientEndpoint}", typeof(TEvent), kv.Key);
282+
failedClients.Add(kv.Key);
283+
}
204284
});
285+
286+
foreach (var failedClient in failedClients)
287+
{
288+
_ = clients.TryRemove(failedClient, out _);
289+
}
205290
}
206291

207292
async Task SendEventAsync<TEvent>(Stream stream, TEvent @event, EndPoint endPoint, CancellationToken cancellationToken) where TEvent : PHDEvent
208293
{
209-
await JsonSerializer.SerializeAsync(stream, @event, serializerOptions, cancellationToken);
294+
await JsonSerializer.SerializeAsync(stream, @event, typeof(TEvent), phdEventJsonContent, cancellationToken);
210295
await stream.WriteAsync(CRLF, cancellationToken);
211296
}
212297

213-
record PHDEvent(string Event, string? Host = "localhost", int MsgVersion = 1, int? Inst = 1);
214-
215-
record VersionEvent(string PHPVersion, string PHPSubVer, bool OverlapSupport = true) : PHDEvent("Version");
216-
record AppStateEvent(string State) : PHDEvent("AppState");
217-
record LoopingExposuresEvent() : PHDEvent("LoopingExposures");
218-
record LoopingExposuresStoppedEvent() : PHDEvent ("LoopingExposuresStopped");
219-
record PausedEvent() : PHDEvent("Paused");
220-
record StarLostEvent() : PHDEvent("StarLost");
221-
record StartCalibrationEvent() : PHDEvent("StartCalibration");
222-
record StarSelectedEvent() : PHDEvent("StarSelected");
223-
record StartGuidingEvent() : PHDEvent("StartGuiding");
224-
record LockPositionSetEvent() : PHDEvent("LockPositionSet");
225-
record GuideStepEvent(double RADistanceRaw, double DECDistanceRaw) : PHDEvent("GuideStep");
226-
record SettleDoneEvent(int Status, string Error, int TotalFrames, int DroppedFrames) : PHDEvent("SettleDone");
227-
record SettleBeginEvent() : PHDEvent("SettleBegin");
228-
record GuidingDitheredEvent(/* double dx, double dy */) : PHDEvent("GuidingDithered");
229-
record SettlingEvent(double Distance, double Time, double SettleTime, bool StarLocked) : PHDEvent("Settling");
298+
class SharedState
299+
{
300+
internal const string UnknownState = "Unknown";
301+
302+
private string _appState = UnknownState;
303+
private int _ditherReceived = 0;
304+
305+
public int IncrementDitherReceived() => Interlocked.Increment(ref _ditherReceived);
306+
307+
public bool AllDitherReceived(int count) => Interlocked.CompareExchange(ref _ditherReceived, 0, count) == count;
308+
309+
internal string AppState => Interlocked.CompareExchange(ref _appState, UnknownState, UnknownState);
310+
311+
internal string SetState(string state) => Interlocked.Exchange(ref _appState, state);
312+
}
230313

314+
[JsonSerializable(typeof(PHDEvent))]
315+
[JsonSerializable(typeof(ConfigurationChangeEvent))]
316+
[JsonSerializable(typeof(CalibratingEvent))]
317+
[JsonSerializable(typeof(CalibrationCompleteEvent))]
318+
[JsonSerializable(typeof(GuideStepEvent))]
319+
[JsonSerializable(typeof(GuidingDitheredEvent))]
320+
[JsonSerializable(typeof(SettleBeginEvent))]
321+
[JsonSerializable(typeof(SettleDoneEvent))]
322+
[JsonSerializable(typeof(SettlingEvent))]
323+
[JsonSerializable(typeof(LockPositionSetEvent))]
324+
[JsonSerializable(typeof(LockPositionLostEvent))]
325+
[JsonSerializable(typeof(AppStateEvent))]
326+
[JsonSerializable(typeof(PausedEvent))]
327+
[JsonSerializable(typeof(StarLostEvent))]
328+
[JsonSerializable(typeof(LoopingExposuresEvent))]
329+
[JsonSerializable(typeof(StarSelectedEvent))]
330+
[JsonSerializable(typeof(StartGuidingEvent))]
331+
[JsonSerializable(typeof(GuidingStoppedEvent))]
332+
[JsonSerializable(typeof(LoopingExposuresStoppedEvent))]
333+
[JsonSerializable(typeof(VersionEvent))]
334+
internal partial class PHDEventJsonContext : JsonSerializerContext
335+
{
336+
}
337+
338+
internal record PHDEvent(string Event, string? Host = "localhost", int MsgVersion = 1, int? Inst = 1);
339+
340+
internal record VersionEvent(string PHPVersion, string PHPSubVer, bool OverlapSupport = true) : PHDEvent("Version");
341+
internal record AppStateEvent(string State) : PHDEvent("AppState");
342+
internal record LoopingExposuresEvent() : PHDEvent("LoopingExposures");
343+
internal record LoopingExposuresStoppedEvent() : PHDEvent ("LoopingExposuresStopped");
344+
internal record PausedEvent() : PHDEvent("Paused");
345+
internal record StarLostEvent() : PHDEvent("StarLost");
346+
internal record StartCalibrationEvent() : PHDEvent("StartCalibration");
347+
internal record ConfigurationChangeEvent() :PHDEvent("ConfigurationChangeEvent");
348+
internal record CalibratingEvent() : PHDEvent("Calibrating");
349+
internal record CalibrationCompleteEvent() : PHDEvent("CalibrationComplete");
350+
internal record StarSelectedEvent() : PHDEvent("StarSelected");
351+
internal record StartGuidingEvent() : PHDEvent("StartGuiding");
352+
internal record LockPositionSetEvent() : PHDEvent("LockPositionSet");
353+
internal record LockPositionLostEvent() : PHDEvent("LockPositionLost");
354+
internal record GuidingStoppedEvent() : PHDEvent("GuidingStopped");
355+
internal record GuideStepEvent(double RADistanceRaw, double DECDistanceRaw) : PHDEvent("GuideStep");
356+
internal record SettleDoneEvent(int Status, string Error, int TotalFrames, int DroppedFrames) : PHDEvent("SettleDone");
357+
internal record SettleBeginEvent() : PHDEvent("SettleBegin");
358+
internal record GuidingDitheredEvent(/* double dx, double dy */) : PHDEvent("GuidingDithered");
359+
internal record SettlingEvent(double Distance, double Time, double SettleTime, bool StarLocked) : PHDEvent("Settling");
360+
361+
[JsonSerializable(typeof(DitherRPC))]
362+
[JsonSerializable(typeof(DitherParams))]
363+
[JsonSerializable(typeof(SettleArg))]
364+
internal partial class PHDJsonRPCJsonContext : JsonSerializerContext
365+
{
366+
}
231367

232368
// {"method":"dither","params":{"amount":20.0,"settle":{"pixels":2.0,"time":5.0,"timeout":80.0},"raOnly":true},"id":1}
233-
record DitherRPC([property: JsonPropertyName("id")] int Id, [property:JsonPropertyName("method")] string Method, [property: JsonPropertyName("params")] DitherParams Params);
369+
internal record DitherRPC([property: JsonPropertyName("id")] int Id, [property:JsonPropertyName("method")] string Method, [property: JsonPropertyName("params")] DitherParams Params);
234370

235-
record DitherParams([property: JsonPropertyName("amount")] double Amount, [property: JsonPropertyName("settle")] SettleArg Settle, [property: JsonPropertyName("raOnly")] bool RaOnly);
236-
record SettleArg([property: JsonPropertyName("pixels")] double Pixels, [property: JsonPropertyName("time")] double Time, [property: JsonPropertyName("timeout")] double Timeout);
371+
internal record DitherParams([property: JsonPropertyName("amount")] double Amount, [property: JsonPropertyName("settle")] SettleArg Settle, [property: JsonPropertyName("raOnly")] bool RaOnly);
372+
internal record SettleArg([property: JsonPropertyName("pixels")] double Pixels, [property: JsonPropertyName("time")] double Time, [property: JsonPropertyName("timeout")] double Timeout);

openphd2-multiscope.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<ImplicitUsings>enable</ImplicitUsings>
88
<Nullable>enable</Nullable>
99
<PublishAot>True</PublishAot>
10+
<PublishTrimmed>True</PublishTrimmed>
1011
</PropertyGroup>
1112

1213
<ItemGroup>

0 commit comments

Comments
 (0)