27
27
. AddDeviceManager ( ) ;
28
28
29
29
var startListenerSem = new SemaphoreSlim ( 0 ) ;
30
- string ? appState = null ;
30
+ var sharedState = new SharedState ( ) ;
31
31
var serializerOptions = new JsonSerializerOptions { WriteIndented = false } ;
32
- var ditherReceived = 0 ;
32
+ var phdEventJsonContent = new PHDEventJsonContext ( serializerOptions ) ;
33
33
34
34
using var host = builder . Build ( ) ;
35
35
50
50
if ( device is GuiderDevice guiderDevice )
51
51
{
52
52
guider = new Guider ( guiderDevice , external ) ;
53
- await guider . Driver . ConnectAsync ( cts . Token ) ;
53
+ guider . Driver . DeviceConnectedEvent += GuiderDriver_DeviceConnectedEvent ;
54
54
guider . Driver . GuiderStateChangedEvent += GuiderDriver_GuiderStateChangedEvent ;
55
+ guider . Driver . GuidingErrorEvent += GuiderDriver_GuidingErrorEvent ;
56
+
57
+ await guider . Driver . ConnectAsync ( cts . Token ) ;
55
58
}
56
59
else
57
60
{
58
61
throw new InvalidOperationException ( "Could not connect to guider" ) ;
59
62
}
60
63
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
+
61
74
async void GuiderDriver_GuiderStateChangedEvent ( object ? sender , GuiderStateChangedEventArgs e )
62
75
{
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 )
64
77
{
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 ) ;
70
79
}
71
80
else
72
81
{
73
- Console . WriteLine ( "Event = {0 }" , e . Event ) ;
82
+ external . AppLogger . LogInformation ( "Event = {EventName }" , e . Event ) ;
74
83
}
75
84
76
85
if ( startListenerSem . CurrentCount == 0 )
@@ -81,6 +90,18 @@ async void GuiderDriver_GuiderStateChangedEvent(object? sender, GuiderStateChang
81
90
{
82
91
switch ( e . Event )
83
92
{
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
+
84
105
case "GuideStep" :
85
106
if ( await guider . Driver . GetStatsAsync ( cts . Token ) is { LastRaErr : { } ra , LastDecErr : { } dec } stats )
86
107
{
@@ -110,6 +131,50 @@ async void GuiderDriver_GuiderStateChangedEvent(object? sender, GuiderStateChang
110
131
}
111
132
}
112
133
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 ;
113
178
}
114
179
}
115
180
}
@@ -119,7 +184,7 @@ async void GuiderDriver_GuiderStateChangedEvent(object? sender, GuiderStateChang
119
184
await startListenerSem . WaitAsync ( cts . Token ) ;
120
185
listener . Start ( ) ;
121
186
122
- while ( ! cts . IsCancellationRequested )
187
+ while ( ! cts . IsCancellationRequested && guider . Driver . Connected )
123
188
{
124
189
var client = await listener . AcceptTcpClientAsync ( cts . Token ) ;
125
190
@@ -142,6 +207,7 @@ async Task ClientWorkerAsync(NetworkStream stream, EndPoint endPoint)
142
207
143
208
await SendEventAsync ( stream , new VersionEvent ( version . FirstOrDefault ( ) ?? "Unknown" , version . LastOrDefault ( ) ?? "Unknown" ) , endPoint , cts . Token ) ;
144
209
210
+ var appState = sharedState . AppState ;
145
211
await ( appState switch
146
212
{
147
213
"Stopped" => SendEventAsync ( stream , new LoopingExposuresStoppedEvent ( ) , endPoint , cts . Token ) , // PHD is idle
@@ -159,17 +225,18 @@ async Task ClientWorkerAsync(NetworkStream stream, EndPoint endPoint)
159
225
try
160
226
{
161
227
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 )
163
229
{
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 ) ;
166
231
if ( ditherCmd ? . Params is { } @params )
167
232
{
233
+ external . AppLogger . LogInformation ( "Recieved dithering command {@Params} from {RemoteEndpoint}" , @params , endPoint ) ;
234
+
168
235
var count = clients . Count ;
169
- var prev = Interlocked . Increment ( ref ditherReceived ) ;
236
+ var prev = sharedState . IncrementDitherReceived ( ) ;
170
237
if ( prev == count )
171
238
{
172
- if ( Interlocked . CompareExchange ( ref ditherReceived , 0 , count ) == count )
239
+ if ( sharedState . AllDitherReceived ( count ) )
173
240
{
174
241
external . AppLogger . LogInformation ( "All {ClientCOunt} connected clients issued a dither command, actually dither now" , count ) ;
175
242
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)
184
251
external . AppLogger . LogInformation ( "Only {DitherIssued} of {ClientCount} connected clients issued a dither command, ignoring" , prev , count ) ;
185
252
}
186
253
}
254
+ else
255
+ {
256
+ external . AppLogger . LogWarning ( "Received unknown command {Line} from {RemoteEndpoint}" , line , endPoint ) ;
257
+ }
187
258
}
188
259
}
189
260
finally
@@ -198,39 +269,104 @@ async Task ClientWorkerAsync(NetworkStream stream, EndPoint endPoint)
198
269
199
270
async Task BroadcastEventAsync < TEvent > ( TEvent @event , CancellationToken cancellationToken ) where TEvent : PHDEvent
200
271
{
272
+ var failedClients = new ConcurrentBag < EndPoint > ( ) ;
201
273
await Parallel . ForEachAsync ( clients , cancellationToken , async ( kv , cancellationToken ) =>
202
274
{
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
+ }
204
284
} ) ;
285
+
286
+ foreach ( var failedClient in failedClients )
287
+ {
288
+ _ = clients . TryRemove ( failedClient , out _ ) ;
289
+ }
205
290
}
206
291
207
292
async Task SendEventAsync < TEvent > ( Stream stream , TEvent @event , EndPoint endPoint , CancellationToken cancellationToken ) where TEvent : PHDEvent
208
293
{
209
- await JsonSerializer . SerializeAsync ( stream , @event , serializerOptions , cancellationToken ) ;
294
+ await JsonSerializer . SerializeAsync ( stream , @event , typeof ( TEvent ) , phdEventJsonContent , cancellationToken ) ;
210
295
await stream . WriteAsync ( CRLF , cancellationToken ) ;
211
296
}
212
297
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
+ }
230
313
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
+ }
231
367
232
368
// {"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 ) ;
234
370
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 ) ;
0 commit comments