static async Task Main(string[] args) { #if DEBUG // Attach remote debugger while (true) { Console.WriteLine("Waiting for remote debugger to attach..."); if (Debugger.IsAttached) { break; } System.Threading.Thread.Sleep(1000); } #endif // init log file Log.Logger = new LoggerConfiguration() .WriteTo.Console() .WriteTo.File( "logfile.txt", fileSizeLimitBytes: 1024 * 1024, flushToDiskInterval: TimeSpan.FromSeconds(30), rollOnFileSizeLimit: true, retainedFileCountLimit: 2) .MinimumLevel.Debug() .CreateLogger(); Log.Information($"{Assembly.GetExecutingAssembly()} V{FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion}"); // init Modbus TCP client for wallbox ModbusTCPClient wallbox = new ModbusTCPClient(); wallbox.Connect(WallbeWallboxBaseAddress, WallbeWallboxModbusTCPPort); // init Modbus TCP client for inverter ModbusTCPClient inverter = new ModbusTCPClient(); inverter.Connect(FroniusInverterBaseAddress, FroniusInverterModbusTCPPort); // read current inverter power limit (percentage) byte[] WMaxLimit = inverter.Read( FroniusInverterModbusUnitID, ModbusTCPClient.FunctionCode.ReadHoldingRegisters, SunSpecInverterModbusRegisterMapFloat.InverterBaseAddress + SunSpecInverterModbusRegisterMapFloat.WMaxLimPctOffset, SunSpecInverterModbusRegisterMapFloat.WMaxLimPctLength); int existingLimitPercent = Utils.ByteSwap(BitConverter.ToUInt16(WMaxLimit)) / 100; // go to the maximum grid export power limit with immediate effect without timeout ushort InverterPowerOutputPercent = (ushort)((GridExportPowerLimit / FroniusSymoMaxPower) * 100); inverter.WriteHoldingRegisters( FroniusInverterModbusUnitID, SunSpecInverterModbusRegisterMapFloat.InverterBaseAddress + SunSpecInverterModbusRegisterMapFloat.WMaxLimPctOffset, new ushort[] { (ushort)(InverterPowerOutputPercent * 100), 0, 0, 0, 1 }); // check new setting WMaxLimit = inverter.Read( FroniusInverterModbusUnitID, ModbusTCPClient.FunctionCode.ReadHoldingRegisters, SunSpecInverterModbusRegisterMapFloat.InverterBaseAddress + SunSpecInverterModbusRegisterMapFloat.WMaxLimPctOffset, SunSpecInverterModbusRegisterMapFloat.WMaxLimPctLength); int newLimitPercent = Utils.ByteSwap(BitConverter.ToUInt16(WMaxLimit)) / 100; // print a list of all available serial ports for convenience string[] ports = SerialPort.GetPortNames(); foreach (string port in ports) { Log.Information("Serial port available: " + port); } // start processing smart meter messages SmartMessageLanguage sml = new SmartMessageLanguage(LinuxUSBSerialPort); sml.ProcessStream(); DeviceClient deviceClient = null; try { // register the device string scopeId = "0ne0010B637"; string deviceId = "RasPi2B"; string primaryKey = ""; string secondaryKey = ""; var security = new SecurityProviderSymmetricKey(deviceId, primaryKey, secondaryKey); var transport = new ProvisioningTransportHandlerMqtt(TransportFallbackType.TcpWithWebSocketFallback); var provisioningClient = ProvisioningDeviceClient.Create("global.azure-devices-provisioning.net", scopeId, security, transport); var result = await provisioningClient.RegisterAsync(); var connectionString = "HostName=" + result.AssignedHub + ";DeviceId=" + result.DeviceId + ";SharedAccessKey=" + primaryKey; deviceClient = DeviceClient.CreateFromConnectionString(connectionString, TransportType.Mqtt); // register our methods await deviceClient.SetMethodHandlerAsync("ChargeNowToggle", ChargeNowHandler, null); await deviceClient.SetMethodHandlerAsync("ChargingPhases", ChargingPhasesHandler, null); } catch (Exception ex) { Log.Error(ex, "Registering device failed!"); } TelemetryData telemetryData = new TelemetryData(); while (true) { telemetryData.ChargeNow = _chargeNow; telemetryData.NumChargingPhases = _chargingPhases; try { // read the current weather data from web service WebClient webClient = new WebClient { BaseAddress = "https://api.openweathermap.org/" }; string json = webClient.DownloadString("data/2.5/weather?q=Munich,de&units=metric&appid=2898258e654f7f321ef3589c4fa58a9b"); WeatherInfo weather = JsonConvert.DeserializeObject <WeatherInfo>(json); if (weather != null) { telemetryData.Temperature = weather.main.temp; telemetryData.WindSpeed = weather.wind.speed; telemetryData.CloudCover = weather.weather[0].description; } webClient.Dispose(); } catch (Exception ex) { Log.Error(ex, "Getting weather data failed!"); } try { // read the current forecast data from web service WebClient webClient = new WebClient { BaseAddress = "https://api.openweathermap.org/" }; string json = webClient.DownloadString("data/2.5/forecast?q=Munich,de&units=metric&appid=2898258e654f7f321ef3589c4fa58a9b"); Forecast forecast = JsonConvert.DeserializeObject <Forecast>(json); if (forecast != null && forecast.list != null && forecast.list.Count == 40) { telemetryData.CloudinessForecast = string.Empty; for (int i = 0; i < 40; i++) { telemetryData.CloudinessForecast += "Cloudiness on " + forecast.list[i].dt_txt + ": " + forecast.list[i].clouds.all + "%\r\n"; } } webClient.Dispose(); } catch (Exception ex) { Log.Error(ex, "Getting weather forecast failed!"); } try { // read the current converter data from web service WebClient webClient = new WebClient { BaseAddress = "http://" + FroniusInverterBaseAddress }; string json = webClient.DownloadString("solar_api/v1/GetInverterRealtimeData.cgi?Scope=Device&DeviceID=1&DataCollection=CommonInverterData"); DCACConverter converter = JsonConvert.DeserializeObject <DCACConverter>(json); if (converter != null) { if (converter.Body.Data.PAC != null) { telemetryData.PVOutputPower = converter.Body.Data.PAC.Value; } if (converter.Body.Data.DAY_ENERGY != null) { telemetryData.PVOutputEnergyDay = ((double)converter.Body.Data.DAY_ENERGY.Value) / 1000.0; } if (converter.Body.Data.YEAR_ENERGY != null) { telemetryData.PVOutputEnergyYear = ((double)converter.Body.Data.YEAR_ENERGY.Value) / 1000.0; } if (converter.Body.Data.TOTAL_ENERGY != null) { telemetryData.PVOutputEnergyTotal = ((double)converter.Body.Data.TOTAL_ENERGY.Value) / 1000.0; } } webClient.Dispose(); } catch (Exception ex) { Log.Error(ex, "Getting converter data failed!"); } try { // read the current smart meter data telemetryData.MeterEnergyPurchased = sml.Meter.EnergyPurchased; telemetryData.MeterEnergySold = sml.Meter.EnergySold; telemetryData.CurrentPower = sml.Meter.CurrentPower; telemetryData.EnergyCost = telemetryData.MeterEnergyPurchased * KWhCost; telemetryData.EnergyProfit = telemetryData.MeterEnergySold * KWhProfit; // calculate energy consumed from the other telemetry, if available telemetryData.MeterEnergyConsumed = 0.0; if ((telemetryData.MeterEnergyPurchased != 0.0) && (telemetryData.MeterEnergySold != 0.0) && (telemetryData.PVOutputEnergyTotal != 0.0)) { telemetryData.MeterEnergyConsumed = telemetryData.PVOutputEnergyTotal + sml.Meter.EnergyPurchased - sml.Meter.EnergySold; telemetryData.CurrentPowerConsumed = telemetryData.PVOutputPower + sml.Meter.CurrentPower; } } catch (Exception ex) { Log.Error(ex, "Getting smart meter data failed!"); } try { // ramp up or down EV charging, based on surplus bool chargingInProgress = IsEVChargingInProgress(wallbox); telemetryData.EVChargingInProgress = chargingInProgress? 1 : 0; if (chargingInProgress) { // read current current (in Amps) ushort wallbeWallboxCurrentCurrentSetting = Utils.ByteSwap(BitConverter.ToUInt16(wallbox.Read( WallbeWallboxModbusUnitID, ModbusTCPClient.FunctionCode.ReadHoldingRegisters, WallbeWallboxCurrentCurrentSettingAddress, 1))); telemetryData.WallboxCurrent = wallbeWallboxCurrentCurrentSetting; OptimizeEVCharging(wallbox, sml.Meter.CurrentPower); } else { telemetryData.WallboxCurrent = 0; // check if we should start charging our EV with the surplus power, but we need at least 6A of current per charing phase // or the user set the "charge now" flag via direct method if (((sml.Meter.CurrentPower / 230) < (_chargingPhases * -6.0f)) || _chargeNow) { StartEVCharging(wallbox); } } } catch (Exception ex) { Log.Error(ex, "EV charing control failed!"); } try { string messageString = JsonConvert.SerializeObject(telemetryData); Message cloudMessage = new Message(Encoding.UTF8.GetBytes(messageString)); await deviceClient.SendEventAsync(cloudMessage); Debug.WriteLine("{0}: {1}", DateTime.Now, messageString); } catch (Exception ex) { Log.Error(ex, "Sending telemetry failed!"); } // wait 5 seconds and go again await Task.Delay(5000).ConfigureAwait(false); } }
static async Task Main(string[] args) { #if DEBUG // Attach remote debugger while (true) { Console.WriteLine("Waiting for remote debugger to attach..."); if (Debugger.IsAttached) { break; } System.Threading.Thread.Sleep(1000); } #endif // print a list of all available serial ports for convenience string[] ports = SerialPort.GetPortNames(); foreach (string port in ports) { Console.WriteLine("Serial port available: " + port); } // start processing smart meter messages SmartMeterLanguage sml = new SmartMeterLanguage(LinuxUSBSerialPort); sml.ProcessStream(); DeviceClient deviceClient = null; try { // register the device string scopeId = "0ne0010B637"; string deviceId = "RasPi2B"; string primaryKey = ""; string secondaryKey = ""; var security = new SecurityProviderSymmetricKey(deviceId, primaryKey, secondaryKey); var transport = new ProvisioningTransportHandlerMqtt(TransportFallbackType.TcpWithWebSocketFallback); var provisioningClient = ProvisioningDeviceClient.Create("global.azure-devices-provisioning.net", scopeId, security, transport); var result = await provisioningClient.RegisterAsync(); var connectionString = "HostName=" + result.AssignedHub + ";DeviceId=" + result.DeviceId + ";SharedAccessKey=" + primaryKey; deviceClient = DeviceClient.CreateFromConnectionString(connectionString, TransportType.Mqtt); } catch (Exception ex) { Debug.WriteLine(ex.Message); } while (true) { TelemetryData telemetryData = new TelemetryData(); try { // read the current weather data from web service using WebClient webClient = new WebClient { BaseAddress = "https://api.openweathermap.org/" }; var json = webClient.DownloadString("data/2.5/weather?q=Munich,de&units=metric&appid=2898258e654f7f321ef3589c4fa58a9b"); WeatherInfo weather = JsonConvert.DeserializeObject <WeatherInfo>(json); if (weather != null) { telemetryData.Temperature = weather.main.temp; telemetryData.WindSpeed = weather.wind.speed; telemetryData.CloudCover = weather.weather[0].description; } } catch (Exception ex) { Debug.WriteLine(ex.Message); } try { // read the current converter data from web service using WebClient webClient = new WebClient { BaseAddress = "http://192.168.178.31/" }; var json = webClient.DownloadString("solar_api/v1/GetInverterRealtimeData.cgi?Scope=Device&DeviceID=1&DataCollection=CommonInverterData"); DCACConverter converter = JsonConvert.DeserializeObject <DCACConverter>(json); if (converter != null) { if (converter.Body.Data.PAC != null) { telemetryData.PVOutputPower = ((double)converter.Body.Data.PAC.Value) / 1000.0; } if (converter.Body.Data.DAY_ENERGY != null) { telemetryData.PVOutputEnergyDay = ((double)converter.Body.Data.DAY_ENERGY.Value) / 1000.0; } if (converter.Body.Data.YEAR_ENERGY != null) { telemetryData.PVOutputEnergyYear = ((double)converter.Body.Data.YEAR_ENERGY.Value) / 1000.0; } if (converter.Body.Data.TOTAL_ENERGY != null) { telemetryData.PVOutputEnergyTotal = ((double)converter.Body.Data.TOTAL_ENERGY.Value) / 1000.0; } } } catch (Exception ex) { Debug.WriteLine(ex.Message); } try { // read the current smart meter data if (sml != null) { telemetryData.MeterEnergyPurchased = sml.Meter.EnergyPurchased; telemetryData.MeterEnergySold = sml.Meter.EnergySold; telemetryData.MeterEnergyConsumed = 0.0; // calculate energy consumed from the other telemetry, if available if ((telemetryData.MeterEnergyPurchased != 0.0) && (telemetryData.MeterEnergySold != 0.0) && (telemetryData.PVOutputEnergyTotal != 0.0)) { telemetryData.MeterEnergyConsumed = telemetryData.PVOutputEnergyTotal + sml.Meter.EnergyPurchased - sml.Meter.EnergySold; } } } catch (Exception ex) { Debug.WriteLine(ex.Message); } try { string messageString = JsonConvert.SerializeObject(telemetryData); Message cloudMessage = new Message(Encoding.UTF8.GetBytes(messageString)); await deviceClient.SendEventAsync(cloudMessage); Debug.WriteLine("{0}: {1}", DateTime.Now, messageString); } catch (Exception ex) { Debug.WriteLine(ex.Message); } await Task.Delay(5000); } }