@@ -172,11 +172,18 @@ private void UnsubscribeObserver(HomeStreamObserverCollection collection, IObser
172
172
collection . Observers . Remove ( observer ) ;
173
173
}
174
174
175
+ private async Task ResubscribeStream ( Guid homeId , int subscriptionId , CancellationToken cancellationToken )
176
+ {
177
+ await UnsubscribeStream ( subscriptionId , cancellationToken ) ;
178
+ await SubscribeStream ( homeId , subscriptionId , cancellationToken ) ;
179
+ }
180
+
175
181
private async Task SubscribeStream ( Guid homeId , int subscriptionId , CancellationToken cancellationToken )
176
182
{
177
- Trace . WriteLine ( $ "subscribe to { homeId } ") ;
183
+ Trace . WriteLine ( $ "subscribe to home id { homeId } with subscription id { subscriptionId } ") ;
178
184
179
185
await ExecuteStreamRequest (
186
+ //$@"{{""payload"":{{""query"":""subscription{{testMeasurement(count:2, complete:false){{timestamp,power,powerReactive,powerProduction,powerProductionReactive,accumulatedConsumption,accumulatedConsumptionLastHour,accumulatedProduction,accumulatedProductionLastHour,accumulatedCost,accumulatedReward,currency,minPower,averagePower,maxPower,minPowerProduction,maxPowerProduction,voltagePhase1,voltagePhase2,voltagePhase3,currentL1,currentL2,currentL3,lastMeterConsumption,lastMeterProduction,powerFactor,signalStrength}}}}"",""variables"":{{}},""extensions"":{{}}}},""type"":""subscribe"",""id"":""{subscriptionId}""}}",
180
187
$@ "{{""payload"":{{""query"":""subscription{{liveMeasurement(homeId:\""{ homeId } \""){{timestamp,power,powerReactive,powerProduction,powerProductionReactive,accumulatedConsumption,accumulatedConsumptionLastHour,accumulatedProduction,accumulatedProductionLastHour,accumulatedCost,accumulatedReward,currency,minPower,averagePower,maxPower,minPowerProduction,maxPowerProduction,voltagePhase1,voltagePhase2,voltagePhase3,currentL1,currentL2,currentL3,lastMeterConsumption,lastMeterProduction,powerFactor,signalStrength}}}}"",""variables"":{{}},""extensions"":{{}}}},""type"":""subscribe"",""id"":""{ subscriptionId } ""}}",
181
188
cancellationToken ) ;
182
189
@@ -189,13 +196,14 @@ await ExecuteStreamRequest(
189
196
private async Task UnsubscribeStream ( int subscriptionId , CancellationToken cancellationToken )
190
197
{
191
198
Trace . WriteLine ( $ "unsubscribe subscription with id { subscriptionId } ") ;
192
- await ExecuteStreamRequest ( $@ "{{""type"":""complete"",""id"":{ subscriptionId } }}", cancellationToken ) ;
199
+ await ExecuteStreamRequest ( $@ "{{""type"":""complete"",""id"":"" { subscriptionId } "" }}", cancellationToken ) ;
193
200
}
194
201
195
202
private Task ExecuteStreamRequest ( string request , CancellationToken cancellationToken )
196
203
{
197
- var stopSubscriptionRequest = new ArraySegment < byte > ( Encoding . ASCII . GetBytes ( request ) ) ;
198
- return _wssClient . SendAsync ( stopSubscriptionRequest , WebSocketMessageType . Text , true , cancellationToken ) ;
204
+ Trace . WriteLine ( $ "send message; client state { _wssClient . State } { _wssClient . CloseStatus } { _wssClient . CloseStatusDescription } { request } ") ;
205
+ var requestBytes = new ArraySegment < byte > ( Encoding . ASCII . GetBytes ( request ) ) ;
206
+ return _wssClient . SendAsync ( requestBytes , WebSocketMessageType . Text , true , cancellationToken ) ;
199
207
}
200
208
201
209
private async Task Initialize ( Uri websocketSubscriptionUrl , CancellationToken cancellationToken )
@@ -216,7 +224,7 @@ private async Task Initialize(Uri websocketSubscriptionUrl, CancellationToken ca
216
224
217
225
Trace . WriteLine ( "web socket connected" ) ;
218
226
219
- var connectionInitMessage = new WebSocketConnectionInitMessage { Payload = connectionInitPayload } ;
227
+ var connectionInitMessage = new WebSocketConnectionInitMessage { Payload = connectionInitPayload } ;
220
228
var json = JsonConvert . SerializeObject ( connectionInitMessage , TibberApiClient . JsonSerializerSettings ) ;
221
229
var init = new ArraySegment < byte > ( Encoding . UTF8 . GetBytes ( json ) ) ;
222
230
@@ -253,6 +261,7 @@ private async void StartListening()
253
261
254
262
do
255
263
{
264
+ Trace . WriteLine ( $ "receive message; client state { _wssClient . State } { _wssClient . CloseStatus } { _wssClient . CloseStatusDescription } ") ;
256
265
result = await _wssClient . ReceiveAsync ( _receiveBuffer , _cancellationTokenSource . Token ) ;
257
266
var json = Encoding . ASCII . GetString ( _receiveBuffer . Array , 0 , result . Count ) ;
258
267
stringBuilder . Append ( json ) ;
@@ -285,7 +294,7 @@ private async void StartListening()
285
294
if ( ! _cancellationTokenSource . IsCancellationRequested )
286
295
{
287
296
Trace . WriteLine ( "connection re-established; re-initialize data streams" ) ;
288
- SubscribeStreams ( c => true ) ;
297
+ ResubscribeStreams ( c => true ) ;
289
298
continue ;
290
299
}
291
300
}
@@ -315,6 +324,7 @@ private async void StartListening()
315
324
continue ;
316
325
317
326
homeStreamObserverCollection . LastMessageReceivedAt = DateTimeOffset . UtcNow ;
327
+ homeStreamObserverCollection . ReconnectionAttempts = 0 ;
318
328
319
329
foreach ( var message in measurementGroup )
320
330
{
@@ -364,13 +374,13 @@ private async void StartListening()
364
374
} while ( ! _cancellationTokenSource . IsCancellationRequested ) ;
365
375
}
366
376
367
- private void SubscribeStreams ( Func < HomeStreamObserverCollection , bool > predicate )
377
+ private void ResubscribeStreams ( Func < HomeStreamObserverCollection , bool > predicate )
368
378
{
369
379
lock ( _homeObservables )
370
380
{
371
381
var subscriptionTask = ( Task ) Task . FromResult ( 0 ) ;
372
382
foreach ( var collection in _homeObservables . Values . Where ( predicate ) )
373
- subscriptionTask = subscriptionTask . ContinueWith ( _ => SubscribeStream ( collection . Observable . HomeId , collection . Observable . SubscriptionId , _cancellationTokenSource . Token ) ) ;
383
+ subscriptionTask = subscriptionTask . ContinueWith ( _ => ResubscribeStream ( collection . Observable . HomeId , collection . Observable . SubscriptionId , _cancellationTokenSource . Token ) ) ;
374
384
}
375
385
}
376
386
@@ -441,9 +451,9 @@ private async Task TryReconnect()
441
451
{
442
452
try
443
453
{
444
- var delay = GetDelaySeconds ( failures ) ;
445
- Trace . WriteLine ( $ "retrying to connect in { delay } seconds") ;
446
- await Task . Delay ( TimeSpan . FromSeconds ( delay ) , _cancellationTokenSource . Token ) ;
454
+ var delay = GetDelay ( failures ) ;
455
+ Trace . WriteLine ( $ "retrying to connect in { delay . TotalSeconds } seconds") ;
456
+ await Task . Delay ( delay , _cancellationTokenSource . Token ) ;
447
457
448
458
Trace . WriteLine ( "check there is a valid real time device" ) ;
449
459
var homes = await _tibberApiClient . ValidateRealtimeDevice ( ) ;
@@ -464,20 +474,31 @@ private void CheckDataStreamAlive(object state)
464
474
{
465
475
var now = DateTimeOffset . UtcNow ;
466
476
467
- SubscribeStreams (
477
+ ResubscribeStreams (
468
478
c =>
469
479
{
470
480
var sinceLastMessageMs = ( now - c . LastMessageReceivedAt ) . TotalMilliseconds ;
471
481
if ( sinceLastMessageMs <= StreamReSubscriptionCheckPeriodMs )
472
482
return false ;
473
483
474
- Trace . WriteLine ( $ "home { c . Observable . HomeId } subscription { c . Observable . SubscriptionId } : no data received during last { sinceLastMessageMs : N0} ms; re-initialize data stream") ;
475
- c . LastMessageReceivedAt = now ;
484
+ // Data not received during past minute; delay exponentially and then resubscribe
485
+ var sinceLastReconnectionMs = ( now - c . LastReconnectionAttemptAt ) . TotalMilliseconds ;
486
+ var delay = GetDelay ( c . ReconnectionAttempts ) ;
487
+ if ( sinceLastReconnectionMs <= delay . TotalMilliseconds )
488
+ {
489
+ Trace . WriteLine ( $ "{ now : yyyy-MM-dd HH:mm:ss.fff zzz} home { c . Observable . HomeId } subscription { c . Observable . SubscriptionId } : no data received during last { sinceLastMessageMs : N0} ms; reconnection attempts { c . ReconnectionAttempts } ; resubscription delay { delay . TotalSeconds } s not passed yet") ;
490
+ return false ;
491
+ }
492
+
493
+ Trace . WriteLine ( $ "{ now : yyyy-MM-dd HH:mm:ss.fff zzz} home { c . Observable . HomeId } subscription { c . Observable . SubscriptionId } : no data received during last { sinceLastMessageMs : N0} ms; reconnection attempts { c . ReconnectionAttempts } ; re-initialize data stream") ;
494
+ c . ReconnectionAttempts ++ ;
495
+ c . LastReconnectionAttemptAt = now ;
496
+
476
497
return true ;
477
498
} ) ;
478
499
}
479
500
480
- private static int GetDelaySeconds ( int failures )
501
+ private static TimeSpan GetDelay ( int failures )
481
502
{
482
503
// Jitter of 5 to 60 seconds
483
504
var jitter = Random . Next ( 5 , 60 ) ;
@@ -487,7 +508,7 @@ private static int GetDelaySeconds(int failures)
487
508
488
509
// Max one day 60 * 60 * 24
489
510
const double oneDayInSeconds = ( double ) 60 * 60 * 24 ;
490
- return jitter + ( int ) Math . Min ( delay , oneDayInSeconds ) ;
511
+ return TimeSpan . FromSeconds ( jitter + ( int ) Math . Min ( delay , oneDayInSeconds ) ) ;
491
512
}
492
513
493
514
private class WebSocketConnectionInitMessage
@@ -517,13 +538,18 @@ private class WebSocketData
517
538
{
518
539
[ JsonProperty ( "liveMeasurement" ) ]
519
540
public RealTimeMeasurement RealTimeMeasurement { get ; set ; }
541
+
542
+ [ JsonProperty ( "testMeasurement" ) ]
543
+ public RealTimeMeasurement TestMeasurement { set { RealTimeMeasurement = value ; } }
520
544
}
521
545
522
546
private class HomeStreamObserverCollection
523
547
{
524
548
public readonly List < IObserver < RealTimeMeasurement > > Observers = new ( ) ;
525
549
public HomeRealTimeMeasurementObservable Observable ;
526
550
public DateTimeOffset LastMessageReceivedAt = DateTimeOffset . MaxValue ;
551
+ public DateTimeOffset LastReconnectionAttemptAt = DateTimeOffset . MinValue ;
552
+ public int ReconnectionAttempts = 0 ;
527
553
}
528
554
529
555
private class Unsubscriber : IDisposable
0 commit comments