Exemple #1
0
        private static bool IsEVChargingInProgress(ModbusTCPClient wallbox)
        {
            // read EV status
            char EVStatus = (char)Utils.ByteSwap(BitConverter.ToUInt16(wallbox.ReadRegisters(
                                                                           WallbeWallboxModbusUnitID,
                                                                           ModbusTCPClient.FunctionCode.ReadInputRegisters,
                                                                           WallbeWallboxEVStatusAddress,
                                                                           1)));

            switch (EVStatus)
            {
            case 'A': return(false);    // no vehicle connected

            case 'B': return(false);    // vehicle connected, not charging

            case 'C': return(true);     // vehicle connected, charging, no ventilation required

            case 'D': return(true);     // vehicle connected, charging, ventilation required

            case 'E': return(false);    // wallbox has no power

            case 'F': return(false);    // wallbox not available

            default: return(false);
            }
        }
Exemple #2
0
        private static void OptimizeEVCharging(ModbusTCPClient wallbox, double currentPower)
        {
            // we ramp up and down our charging current in 1 Amp increments/decrements
            // we increase our charging current until a) we have reached the maximum the wallbox can handle or
            // b) we are just below consuming power from the grid (indicated by currentPower becoming positive), we are setting this to -200 Watts
            // we decrease our charging current when currentPower is above 0 (again indicated we are comsuming pwoer from the grid)

            // read maximum current rating
            ushort maxCurrent = Utils.ByteSwap(BitConverter.ToUInt16(wallbox.ReadRegisters(
                                                                         WallbeWallboxModbusUnitID,
                                                                         ModbusTCPClient.FunctionCode.ReadInputRegisters,
                                                                         WallbeWallboxMaxCurrentSettingAddress,
                                                                         1)));

            // read current current (in Amps)
            ushort wallbeWallboxCurrentCurrentSetting = Utils.ByteSwap(BitConverter.ToUInt16(wallbox.ReadRegisters(
                                                                                                 WallbeWallboxModbusUnitID,
                                                                                                 ModbusTCPClient.FunctionCode.ReadHoldingRegisters,
                                                                                                 WallbeWallboxCurrentCurrentSettingAddress,
                                                                                                 1)));

            // check if we have reached our limits
            // "charge now" overwrites this and charges as quickly as possible
            if ((wallbeWallboxCurrentCurrentSetting < maxCurrent) && ((currentPower < -500) || _chargeNow))
            {
                // increse desired current by 1 Amp
                wallbox.WriteHoldingRegisters(
                    WallbeWallboxModbusUnitID,
                    WallbeWallboxDesiredCurrentSettingAddress,
                    new ushort[] { (ushort)(wallbeWallboxCurrentCurrentSetting + 1) });
            }
            else if (currentPower > 0)
            {
                // need to decrease our charging current
                // "charge now" overwrites this and charges as quickly as possible
                if (!_chargeNow)
                {
                    if (wallbeWallboxCurrentCurrentSetting == WallbeWallboxMinChargingCurrent)
                    {
                        // we are already at the minimum, stop instead
                        StopEVCharging(wallbox);
                    }
                    else
                    {
                        // decrease desired current by 1 Amp
                        wallbox.WriteHoldingRegisters(
                            WallbeWallboxModbusUnitID,
                            WallbeWallboxDesiredCurrentSettingAddress,
                            new ushort[] { (ushort)(wallbeWallboxCurrentCurrentSetting - 1) });
                    }
                }
            }
        }
Exemple #3
0
        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 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.ReadRegisters(
                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.ReadRegisters(
                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)
            {
                Console.WriteLine("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 method
                await deviceClient.SetMethodHandlerAsync("ChargeNowToggle", ChargeNowHandler, null);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }

            bool EVUpdate = false;
            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 forecast data from web service
                    using WebClient webClient = new WebClient
                          {
                              BaseAddress = "https://api.openweathermap.org/"
                          };
                    var      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 = "Cloudiness on " + forecast.list[0].dt_txt + ": " + forecast.list[0].clouds.all + "%\r\n";
                        for (int i = 1; i < 40; i++)
                        {
                            telemetryData.CloudinessForecast += "Cloudiness on " + forecast.list[i].dt_txt + ": " + forecast.list[i].clouds.all + "%\r\n";
                        }
                    }
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex.Message);
                }

                try
                {
                    // read the current converter data from web service
                    using WebClient webClient = new WebClient
                          {
                              BaseAddress = "http://" + FroniusInverterBaseAddress
                          };
                    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 = 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;
                        }
                    }
                }
                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.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;
                        }

                        if (IsEVChargingInProgress(wallbox))
                        {
                            telemetryData.EVChargingInProgress = 1;
                        }
                        else
                        {
                            telemetryData.EVChargingInProgress = 0;
                        }

                        telemetryData.ChargeNow = _chargeNow;

                        // read current current (in Amps)
                        ushort wallbeWallboxCurrentCurrentSetting = Utils.ByteSwap(BitConverter.ToUInt16(wallbox.ReadRegisters(
                                                                                                             WallbeWallboxModbusUnitID,
                                                                                                             ModbusTCPClient.FunctionCode.ReadHoldingRegisters,
                                                                                                             WallbeWallboxCurrentCurrentSettingAddress,
                                                                                                             1)));
                        telemetryData.WallboxCurrent = wallbeWallboxCurrentCurrentSetting;

                        // ramp up or down EV charging, based on surplus, every second pass through the loop
                        EVUpdate = !EVUpdate;
                        if (EVUpdate)
                        {
                            if (IsEVChargingInProgress(wallbox))
                            {
                                OptimizeEVCharging(wallbox, sml.Meter.CurrentPower);
                            }
                            else
                            {
                                // check if we should start charging our EV with the surplus power, but we need at least 6 A of current
                                // or the user set the "charge now" flag via direct method
                                if ((((sml.Meter.CurrentPower / 230) < -6.0f) || _chargeNow) && IsEVConnected(wallbox))
                                {
                                    StartEVCharging(wallbox);
                                }
                            }
                        }
                    }
                }
                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);
            }
        }