-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathHuaweiProducer.cs
More file actions
761 lines (713 loc) · 38.4 KB
/
HuaweiProducer.cs
File metadata and controls
761 lines (713 loc) · 38.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
using System.Net;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using SolarUseOptimiser.Models;
using SolarUseOptimiser.Models.ChargeHQ;
using SolarUseOptimiser.Models.Configuration;
using SolarUseOptimiser.Models.Huawei;
namespace SolarUseOptimiser
{
public class HuaweiProducer : IDataSource
{
private readonly IConfiguration configuration;
private readonly ILogger<HuaweiProducer> logger;
public string Name => "Huawei FusionSolar API";
private bool initialised = false;
public bool IsInitialised
{
get; set;
} = false;
private HttpClient _client;
private HttpClientHandler _handler;
private HuaweiSettings HuaweiSettings
{
get; set;
}
private string StationCode
{
get; set;
}
private DeviceInfo Inverter
{
get; set;
}
private DeviceInfo PowerSensor
{
get; set;
}
private DeviceInfo GridMeter
{
get; set;
}
private DeviceInfo Battery
{
get; set;
}
public int DeviceCount
{
get; set;
} = 0;
private static int RetryCount
{
get; set;
} = 0;
public double PollRate
{
get; set;
}
private static CancellationTokenSource CancellationTokenSource;
public HuaweiProducer(IConfiguration configuration, ILogger<HuaweiProducer> logger)
{
this.logger = logger;
this.configuration = configuration;
}
public async Task Restart(CancellationTokenSource cancellationTokenSource)
{
initialised = false;
await InitialiseAsync(cancellationTokenSource);
}
public async Task<IDataSource> InitialiseAsync(CancellationTokenSource cancellationTokenSource)
{
if (!initialised)
{
initialised = true;
CancellationTokenSource = cancellationTokenSource;
HuaweiSettings = configuration.GetSection(Constants.ConfigSections.HUAWEI_CONFIG_SECTION).Get<HuaweiSettings>();
PollRate = HuaweiSettings.PollRate;
var authResposne = await Authenticate(cancellationTokenSource);
IsInitialised = authResposne.Success;
}
return this;
}
private void CreateHttpClient()
{
var cookies = new CookieContainer();
_handler = new HttpClientHandler();
_handler.CookieContainer = cookies;
_handler.ClientCertificateOptions = ClientCertificateOption.Manual;
_handler.ServerCertificateCustomValidationCallback = (httpRequestMessage, cert, certChain, policyErrors) =>
{
return true;
};
_client = new HttpClient(_handler);
}
public async Task<CommandResponse> Authenticate(CancellationTokenSource cancellationTokenSource)
{
var success = await GetXsrfToken(CancellationTokenSource.Token);
if (success)
{
logger.LogInformation("Successfully authenticated the user during intialisation.");
var stationListResponse = await PostDataRequestAsync<GetStationListResponse>(GetUri(Constants.Huawei.STATION_LIST_URI), null, CancellationTokenSource.Token);
if (stationListResponse.success)
{
if (stationListResponse.data != null && stationListResponse.data.Count > 0)
{
var selectedPlant = stationListResponse.data.Where(plant => plant.stationName.Equals(HuaweiSettings.StationName, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault();
if (selectedPlant != null)
{
// In case someone has more than one plant/station in their account select the plant/station they are after
logger.LogInformation("Station name was found and will be used for all requests.");
StationCode = stationListResponse.data[0].stationCode;
var gdlr = new GetDeviceListRequest
{
stationCodes = StationCode
};
var deviceInfoResponse = await PostDataRequestAsync<DeviceInfoResponse>(GetUri(Constants.Huawei.DEV_LIST_URI),
Utility.GetStringContent(gdlr), CancellationTokenSource.Token);
if (deviceInfoResponse.success)
{
if (deviceInfoResponse.data != null && deviceInfoResponse.data.Count > 0)
{
bool foundInverter = SetDeviceInfos(deviceInfoResponse.data);
string msg = foundInverter ? "inverter found" : "inverter not found";
return new CommandResponse
{
Success = foundInverter,
Message = msg
};
}
else
{
return new CommandResponse
{
Success = false,
Message = "inverter not found"
};
}
}
else
{
return new CommandResponse
{
Success = false,
Message = "inverter not found"
};
}
}
else
{
string message = string.Format("The station with the name '{0}' could not be found when listing the stations.", HuaweiSettings.StationName);
logger.LogError(message);
return new CommandResponse
{
Success = false,
Message = message
};
}
}
else
{
string message = "There were no plants/stations associated with this login.";
logger.LogError(message);
return new CommandResponse
{
Success = false,
Message = message
};
}
}
else
{
if (stationListResponse.failCode == 401)
{
logger.LogInformation("Using new station interface as the old one was not available.");
var selectedPlant = await GetPlantInformation(1);
if (selectedPlant != null)
{
// In case someone has more than one plant/station in their account select the plant/station they are after
logger.LogInformation("Station name was found and will be used for all requests. (new interface)");
StationCode = selectedPlant.plantCode;
var gdlr = new GetDeviceListRequest
{
stationCodes = StationCode
};
var deviceInfoResponse = await PostDataRequestAsync<DeviceInfoResponse>(GetUri(Constants.Huawei.DEV_LIST_URI),
Utility.GetStringContent(gdlr), CancellationTokenSource.Token);
if (deviceInfoResponse.success)
{
if (deviceInfoResponse.data != null && deviceInfoResponse.data.Count > 0)
{
bool foundInverter = SetDeviceInfos(deviceInfoResponse.data);
string msg = foundInverter ? "inverter found" : "inverter not found";
return new CommandResponse
{
Success = foundInverter,
Message = msg
};
}
else
{
return new CommandResponse
{
Success = false,
Message = "inverter not found"
};
}
}
else
{
return new CommandResponse
{
Success = false,
Message = "inverter not found"
};
}
}
else
{
string message = string.Format("The station with the name '{0}' could not be found when listing the stations.", HuaweiSettings.StationName);
logger.LogError(message);
return new CommandResponse
{
Success = false,
Message = message
};
}
}
else
{
string message = string.Format("The reason for the failure of the original list stations interface was not 401, it was {0}", stationListResponse.failCode);
logger.LogError(message);
return new CommandResponse
{
Success = false,
Message = message
};
}
}
}
else
{
string message = "Failed to authenticate the user during initialisation.";
logger.LogError(message);
return new CommandResponse
{
Success = false,
Message = message
};
}
}
/// <summary>
/// <c>GetPlantInformation</c> - Selects the Plant/Station from the list of returned plants/stations associated with the login.
/// If there is a list of stations it will request each page of plants/stations until it has been through them all looking for a match.
/// </summary>
/// <returns>The matching plant/station or null if it couldn't be found</returns>
private async Task<PlantInfo> GetPlantInformation(int pageNumber)
{
GetStationsNewRequestParams requestParam = new GetStationsNewRequestParams
{
pageNo = pageNumber
};
var requestParamContent = Utility.GetStringContent(requestParam);
var newStationListResponse = await PostDataRequestAsync<GetStationListNewResponse>(GetUri(Constants.Huawei.STATION_LIST_NEW_URI), requestParamContent, CancellationTokenSource.Token);
if (newStationListResponse.success)
{
if (newStationListResponse.data != null && newStationListResponse.data.list != null && newStationListResponse.data.list.Count > 0)
{
var selectedPlant = newStationListResponse.data.list.Where(plant => plant.plantName.Equals(HuaweiSettings.StationName, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault();
if (selectedPlant != null)
{
return selectedPlant;
}
else if (selectedPlant == null && newStationListResponse.data.pageCount > pageNumber)
{
return await GetPlantInformation(pageNumber++); // see if it's on the next page of information
}
else
{
return null; // reached the last page and it's not found
}
}
else
{
return null; // this is an unexpected result, the station wasn't going to be found
}
}
else
{
return null; // the interface didn't return a station/plant list, it wasn't found
}
}
/// <summary>
/// <c>SetDeviceInfo</c> - Gets the Residential Inverter and the Power Sensor devices out of the returned
/// plant devices. It will also find any residential batteries to monitor.
/// </summary>
/// <returns>true if the inverter was found</returns>
private bool SetDeviceInfos(IList<DeviceInfo> devices)
{
logger.LogInformation("Detected Devices: " + JsonConvert.SerializeObject(devices));
var powerSensor = devices.Where(device => device.devTypeId == 47).FirstOrDefault();
if (powerSensor != null)
{
// If the setup has a power sensor use that for the accurate excess solar information
PowerSensor = powerSensor;
logger.LogDebug("Found a power sensor with ID: {0}", powerSensor.id);
if (HuaweiSettings.UsePowerSensorData)
{
DeviceCount++;
}
}
else
{
logger.LogDebug("There was no power sensor found, this is optional for richer solar monitoring data.");
}
var gridMeter = devices.Where(device => device.devTypeId == 17).FirstOrDefault();
if (gridMeter != null)
{
GridMeter = gridMeter;
logger.LogDebug("Found a grid meter with ID: {0}", gridMeter.id);
if (HuaweiSettings.UseGridMeterData)
{
DeviceCount++;
}
}
else
{
logger.LogDebug("There was no grid meter found, this is optional for richer solar monitoring data.");
}
var battery = devices.Where(device => device.devTypeId == 39).FirstOrDefault();
if (battery != null)
{
Battery = battery;
logger.LogDebug("Found a residential battery with ID: {0}", battery.id);
if (HuaweiSettings.UseBatteryData)
{
DeviceCount++;
}
}
else
{
logger.LogDebug("There was no residential battery detected in the system, this is optionally supported.");
}
var residentialInverter = devices.Where(device => device.devTypeId == 38).FirstOrDefault();
if (residentialInverter != null)
{
Inverter = residentialInverter;
logger.LogDebug("Found a residential inverter with ID: {0}", residentialInverter.id);
DeviceCount++;
}
else
{
var stringInverter = devices.Where(device => device.devTypeId == 1).FirstOrDefault();
if (stringInverter != null)
{
Inverter = stringInverter;
logger.LogDebug("Found a string inverter with ID: {0}", stringInverter.id);
DeviceCount++;
}
else
{
logger.LogError("No string or residential inverter was found, this is a mandatory requirement to use this system.");
}
}
if (Inverter != null)
{
return true;
}
else
{
return false;
}
}
/// <summary>
/// <c>GetXsrfToken</c> - Sends a Login Request to Huawei's Fusion Solar API, extracts the cookie and puts it in the default headers
// for all future requests to the API.
/// </summary>
/// <returns>True if it successfully logged in, otherwise False</returns>
private async Task<bool> GetXsrfToken(CancellationToken cancellationToken)
{
if (initialised)
{
CreateHttpClient();
LoginCredentialRequest lcr = new LoginCredentialRequest
{
userName = HuaweiSettings.Username,
systemCode = HuaweiSettings.Password
};
var content = Utility.GetStringContent(lcr);
var response = await PostDataRequestAsync<BaseResponse>(GetUri(Constants.Huawei.LOGIN_URI), content, cancellationToken);
if (response.success)
{
logger.LogInformation("Successfully did login and got back cookie");
bool cookieFound = false;
foreach (Cookie cookie in _handler.CookieContainer.GetCookies(new Uri(HuaweiSettings.BaseURI)).Cast<Cookie>())
{
if (cookie.Name == "XSRF-TOKEN")
{
_client.DefaultRequestHeaders.Add("XSRF-TOKEN", cookie.Value);
logger.LogDebug("XSRF-TOKEN found in coolies and added to default request headers");
cookieFound = true;
break;
}
}
return cookieFound;
}
else
{
logger.LogError("The login request failed, it returned a fail code of {0} and message {1}", response.failCode, response.message);
return false;
}
}
return false;
}
/// <summary>
/// <c>PostDataRequestAsync</c> - Does a HTTP POST to the Huawei Fusion Solar API with automatic re-authentication if required.
/// If it gets an error response from the API call it will retry every 5 seconds until it gets a valid response
/// (such as Fusion Solar being offline for maintenance).
/// </summary>
/// <returns>The HTTP response message or null if there was an error with the HTTP POST</returns>
private async Task<T> PostDataRequestAsync<T>(string uri, StringContent content, CancellationToken cancellationToken) where T : BaseResponse
{
try
{
var response = await _client.PostAsync(uri, content, cancellationToken);
if (response.StatusCode == HttpStatusCode.OK)
{
bool success = Utility.WasSuccessMessage<T>(response, out string json, out T responseObj, cancellationToken);
if (!success && responseObj.failCode == 305)
{
success = await GetXsrfToken(cancellationToken);
if (success)
{
logger.LogInformation("Successfully re-authenticated with the API. Resending original API request.");
responseObj = await PostDataRequestAsync<T>(uri, content, cancellationToken);
}
else
{
logger.LogError("Failed to re-authenticate with the API.");
}
}
return responseObj;
}
else
{
RetryCount++;
if (RetryCount < 5)
{
logger.LogWarning("There was a failed request, it is retring after 5 seconds...Retry Count: {0}", RetryCount);
Thread.Sleep(5000);
return await PostDataRequestAsync<T>(uri, content, cancellationToken);
}
else
{
RetryCount = 0;
return (T)new BaseResponse
{
success = false,
failCode = 1001,
message = string.Format("Failed after the maximum retry count for URI: '{0}'", uri)
};
}
}
}
catch (Exception ex)
{
logger.LogDebug("The exception that occurred while sending the POST was: {0}", ex);
RetryCount++;
if (RetryCount < 5)
{
logger.LogWarning("There was a failed request, it is retring after 5 seconds...Retry Count: {0}", RetryCount);
Thread.Sleep(5000);
return await PostDataRequestAsync<T>(uri, content, cancellationToken);
}
else
{
RetryCount = 0; //reset the retry count
return (T)new BaseResponse
{
success = false,
failCode = 1001,
message = string.Format("Failed after the maximum retry count for URI: '{0}'", uri)
};
}
}
}
public SiteMeterPush GetSiteMeterData(string userId, CancellationTokenSource cancellationTokenSource)
{
SiteMeterPush pushData = new SiteMeterPush
{
apiKey = userId,
siteMeters = new SiteMeter()
};
try
{
if (!CancellationTokenSource.IsCancellationRequested)
{
// Request the Residential Inverters power data to get how much production is happening
var requestParam = new GetDevRealKpiRequest
{
devIds = Inverter.id.ToString(),
devTypeId = Inverter.devTypeId
};
DevRealKpiResponse<DevRealKpiResInvDataItemMap> residentialPowerData = null;
DevRealKpiResponse<DevRealKpiStrInvDataItemMap> stringPowerData = null;
long timestamp = -1;
double? active_power = null;
double? total_cap = null;
double inverter_state = -1;
bool inverterDataFound = false;
if (Inverter.devTypeId == 1)
{
logger.LogDebug("Getting String Inverter Power Data.");
stringPowerData = PostDataRequestAsync<DevRealKpiResponse<DevRealKpiStrInvDataItemMap>>(GetUri(Constants.Huawei.DEV_REAL_KPI_URI),
Utility.GetStringContent(requestParam),
CancellationTokenSource.Token).GetAwaiter().GetResult();
if (stringPowerData != null && stringPowerData.success && stringPowerData.data != null &&
stringPowerData.data.Count > 0 && stringPowerData.data[0].dataItemMap.inverter_state != -1)
{
var stringInverterPowerData = stringPowerData.data[0].dataItemMap as DevRealKpiStrInvDataItemMap;
timestamp = stringPowerData.parameters.currentTime;
active_power = stringInverterPowerData.active_power;
total_cap = stringInverterPowerData.total_cap;
inverter_state = stringInverterPowerData.inverter_state;
inverterDataFound = true;
}
}
else if (Inverter.devTypeId == 38)
{
logger.LogDebug("Getting Residential Inverter Power Data.");
residentialPowerData = PostDataRequestAsync<DevRealKpiResponse<DevRealKpiResInvDataItemMap>>(GetUri(Constants.Huawei.DEV_REAL_KPI_URI),
Utility.GetStringContent(requestParam),
CancellationTokenSource.Token).GetAwaiter().GetResult();
if (residentialPowerData != null && residentialPowerData.success && residentialPowerData.data != null &&
residentialPowerData.data.Count > 0 && residentialPowerData.data[0].dataItemMap.inverter_state != -1)
{
var residentialInverterPowerData = residentialPowerData.data[0].dataItemMap as DevRealKpiResInvDataItemMap;
timestamp = residentialPowerData.parameters.currentTime;
active_power = residentialInverterPowerData.active_power;
total_cap = residentialInverterPowerData.total_cap;
inverter_state = residentialInverterPowerData.inverter_state;
inverterDataFound = true;
}
}
if (inverterDataFound)
{
if (active_power.HasValue)
{
pushData.tsms = timestamp;
pushData.siteMeters.production_kw = active_power;
pushData.siteMeters.exported_kwh = total_cap; // this is the total lifetime energy produced by the inverter
bool isShutdown = inverter_state != 512;
if (isShutdown)
{
logger.LogDebug("The inverter is currently shutdown due to no sunlight");
}
// If there is a Power Sensor device enrich the ChargeHQ Push Data with the consumption meter fields
if (PowerSensor != null && HuaweiSettings.UsePowerSensorData)
{
var powerSensorRequestParam = new GetDevRealKpiRequest
{
devIds = PowerSensor.id.ToString(),
devTypeId = PowerSensor.devTypeId
};
var powerSensorData = PostDataRequestAsync<DevRealKpiResponse<DevRealKpiPowerSensorDataItemMap>>(GetUri(Constants.Huawei.DEV_REAL_KPI_URI),
Utility.GetStringContent(powerSensorRequestParam),
CancellationTokenSource.Token).GetAwaiter().GetResult();
if (powerSensorData != null && powerSensorData.success &&
powerSensorData.data != null && powerSensorData.data.Count > 0 && powerSensorData.data[0].dataItemMap.meter_status != -1)
{
var powerSensorPowerData = powerSensorData.data[0].dataItemMap as DevRealKpiPowerSensorDataItemMap;
bool isOffline = powerSensorPowerData.meter_status == 0;
if (isOffline)
{
logger.LogDebug("The power sensor is currently reporting as offline.");
}
else
{
if (powerSensorPowerData.active_power.HasValue)
{
pushData.siteMeters.net_import_kw = -1 * (powerSensorPowerData.active_power.Value / 1000);
pushData.siteMeters.consumption_kw = pushData.siteMeters.production_kw - (powerSensorPowerData.active_power.Value / 1000);
// With the Power Sensor it measures the amount of power exported/imported a positive active_cap will be exported
if (powerSensorPowerData.active_cap.HasValue && powerSensorPowerData.active_cap > 0)
{
pushData.siteMeters.exported_kwh = powerSensorPowerData.active_cap; // this is the lifetime amount of energy exported
}
}
}
}
else
{
logger.LogWarning("The power sensor power data returned from Huawei's Fusion Solar was not valid.");
pushData.error = "Huawei's FusionSolar power sensor power data was not in an expected format.";
return pushData;
}
}
else
{
// If there isn't a Power Sensor try use a Grid Meter if it exists to get this data
if (GridMeter != null && HuaweiSettings.UseGridMeterData)
{
var gridMeterRequestParams = new GetDevRealKpiRequest
{
devIds = GridMeter.id.ToString(),
devTypeId = GridMeter.devTypeId
};
var gridMeterData = PostDataRequestAsync<DevRealKpiResponse<DevRealKpiGridMeterDataItemMap>>(GetUri(Constants.Huawei.DEV_REAL_KPI_URI),
Utility.GetStringContent(gridMeterRequestParams),
CancellationTokenSource.Token).GetAwaiter().GetResult();
if (gridMeterData != null && gridMeterData.success &&
gridMeterData.data != null && gridMeterData.data.Count > 0)
{
var gridMeterPowerData = gridMeterData.data[0].dataItemMap as DevRealKpiGridMeterDataItemMap;
if (gridMeterPowerData.active_power.HasValue)
{
pushData.siteMeters.net_import_kw = -1 * gridMeterPowerData.active_power.Value;
pushData.siteMeters.consumption_kw = pushData.siteMeters.production_kw - gridMeterPowerData.active_power.Value;
// With the Power Sensor it measures the amount of power exported/imported a positive active_cap will be exported
if (gridMeterPowerData.active_cap.HasValue && gridMeterPowerData.active_cap > 0)
{
pushData.siteMeters.exported_kwh = gridMeterPowerData.active_cap; // this is the lifetime amount of energy exported
}
}
}
else
{
logger.LogWarning("The grid meter power data returned from Huawei's Fusion Solar was not valid.");
pushData.error = "Huawei's FusionSolar grid meter power data was not in an expected format.";
return pushData;
}
}
}
if (Battery != null && HuaweiSettings.UseBatteryData)
{
var batteryReqestParams = new GetDevRealKpiRequest
{
devIds = Battery.id.ToString(),
devTypeId = Battery.devTypeId
};
var batteryData = PostDataRequestAsync<DevRealKpiResponse<DevRealKpiBattDataItemMap>>(GetUri(Constants.Huawei.DEV_REAL_KPI_URI),
Utility.GetStringContent(batteryReqestParams),
CancellationTokenSource.Token).GetAwaiter().GetResult();
if (batteryData != null && batteryData.success &&
batteryData.data != null && batteryData.data.Count > 0 && batteryData.data[0].dataItemMap.battery_status != -1)
{
var batteryPowerData = batteryData.data[0].dataItemMap as DevRealKpiBattDataItemMap;
bool isOffline = batteryPowerData.battery_status == 0;
if (isOffline)
{
logger.LogDebug("The residential battery is currently reporting as offline.");
}
else
{
double? battery_soc = null;
if (batteryPowerData.battery_soc > 1)
{
// TODO: Remove this logic after capturing the debug output and settling on what Huawei send
logger.LogInformation("The battery SOC was being reported as a double between 0 and 100");
battery_soc = batteryPowerData.battery_soc / 100;
}
else
{
logger.LogInformation("The battery SOC was being reported as a double between 0 and 1");
battery_soc = batteryPowerData.battery_soc;
}
pushData.siteMeters.battery_soc = battery_soc;
pushData.siteMeters.battery_energy_kwh = batteryPowerData.charge_cap;
pushData.siteMeters.battery_discharge_kw = batteryPowerData.ch_discharge_power;
}
}
}
// Send active power to ChargeHQ
logger.LogDebug("Successfully got the Huawei power data and sending it to the consumer.");
return pushData;
}
else
{
logger.LogInformation("There was no active_power value from the inverter so no data was sent to ChargeHQ.");
pushData.error = "Huawei's FusionSolar did not return any production output from the inverter.";
return pushData;
}
}
else
{
logger.LogWarning("The residential inverter power data returned from Huawei's Fusion Solar was not valid.");
pushData.error = "Huawei's FusionSolar residential inverter power data was not in an expected format.";
return pushData;
}
}
else
{
pushData.error = "The poller was being shutdown";
return pushData;
}
}
catch (Exception ex)
{
logger.LogError(ex, "An exception was caught while polling for power data to send to ChargeHQ.");
pushData.error = "An error occurred while polling the Huawei FusionSolar API";
return pushData;
}
}
/// <summary>
/// <c>GetUri</c> - Combines the method URI and base URI for the Huawei services ensuring it handles configuration errors.
/// </summary>
/// <param name="methodUri">The relative URI path to the relevant service</param>
/// <returns>The full URI for the Huawei Fusion Solar API target</returns>
private string GetUri(string methodUri)
{
return Utility.GetUrl(HuaweiSettings.BaseURI, methodUri);
}
}
}