Take reflections into account

This commit is contained in:
woheller69 2023-05-02 12:14:34 +02:00
parent 6fcdccfa4f
commit 07fb5f0b9e
16 changed files with 79 additions and 19 deletions

View file

@ -48,7 +48,12 @@ Enter the dependence of the cell power on temperature, usually in the range of -
Enter the size of your solar panel.
#### Diffuse radiation efficiency [%]
Specify the efficiency of your solar power plant for diffuse radiation. When pointing up, it should be around 100%, but when pointing towards the horizon, it may be around 50%, depending on reflections and other factors.
Specify the efficiency of your solar power plant for diffuse radiation. When pointing up, it should be around 100%, but when pointing towards the horizon, it may be 50% or less, depending on the environment.
You probably need to optimize this parameter.
#### Albedo [0..1]
Specify the average albedo for your environment to take reflections into account. The value ranges from 0 (all radiation is absorbed) to 1 (all radiation is reflected).
Examples: Fresh snow: 0.8, green gras: 0.25, asphalt: 0.1
You probably need to optimize this parameter.
#### Inverter power [W]

View file

@ -41,7 +41,12 @@ Geben Sie die Abhängigkeit der Zellleistung von der Temperatur an, normalerweis
Geben Sie die Größe Ihres Solarmoduls an.
<h3>Effizienz diffuse Strahlung [%]</h3>
Geben Sie die Effizienz Ihrer Solaranlage für diffuse Strahlung an. Wenn sie nach oben gerichtet ist, sollte sie etwa 100% betragen, aber wenn sie in Richtung Horizont gerichtet ist, kann sie je nach Reflexionen und anderen Faktoren etwa 50% betragen.
Geben Sie die Effizienz Ihrer Solaranlage für diffuse Strahlung an. Wenn sie nach oben gerichtet ist, sollte sie etwa 100% betragen, aber wenn sie in Richtung Horizont gerichtet ist, beträgt Sie 50% oder weniger - abhängig von der Umgebung.
Sie müssen diesen Parameter wahrscheinlich optimieren.
<h3>Albedo [0..1]</h3>
Geben Sie die durchschnittliche Albedo für Ihre Umgebung an, um Reflexionen zu berücksichtigen. Der Wertebereich geht von 0 (alle Strahlung wird absorbiert) bis 1 (alle Strahlung wird reflektiert).
Beispiele: Frischer Schnee: 0.8, grünes Gras: 0.25, Asphalt: 0.1
Sie müssen diesen Parameter wahrscheinlich optimieren.
<h3>Wechselrichterleistung [W]</h3>

View file

@ -41,7 +41,12 @@ Enter the dependence of the cell power on temperature, usually in the range of -
Enter the size of your solar panel.
<h3>Diffuse radiation efficiency [%]</h3>
Specify the efficiency of your solar power plant for diffuse radiation. When pointing up, it should be around 100%, but when pointing towards the horizon, it may be around 50%, depending on reflections and other factors.
Specify the efficiency of your solar power plant for diffuse radiation. When pointing up, it should be around 100%, but when pointing towards the horizon, it may be 50% or less, depending on the environment.
You probably need to optimize this parameter.
<h3>Albedo [0..1]</h3>
Specify the average albedo for your environment to take reflections into account. The value ranges from 0 (all radiation is absorbed) to 1 (all radiation is reflected).
Examples: Fresh snow: 0.8, green gras: 0.25, asphalt: 0.1
You probably need to optimize this parameter.
<h3>Inverter power [W]</h3>

View file

@ -9,6 +9,7 @@ import java.time.ZoneId;
import java.time.ZonedDateTime;
public class SolarPowerPlant {
double albedo;
double latitude;
double longitude;
double cellsMaxPower;
@ -23,7 +24,8 @@ public class SolarPowerPlant {
private final int[] shadingElevation;
private final int[] shadingOpacity;
public SolarPowerPlant(double latitude, double longitude, double cellsMaxPower, double cellsArea, double cellsEfficiency, double cellsTempCoeff, double diffuseEfficiency, double inverterPowerLimit, double inverterEfficiency, double azimuthAngle, double tiltAngle, int[] shadingElevation, int[] shadingOpacity ) {
public SolarPowerPlant(double latitude, double longitude, double cellsMaxPower, double cellsArea, double cellsEfficiency, double cellsTempCoeff, double diffuseEfficiency, double inverterPowerLimit, double inverterEfficiency, double azimuthAngle, double tiltAngle, int[] shadingElevation, int[] shadingOpacity, double albedo ) {
this.albedo = albedo;
this.latitude = latitude;
this.longitude = longitude;
this.cellsMaxPower = cellsMaxPower;
@ -40,7 +42,7 @@ public class SolarPowerPlant {
}
public float getPower(double solarPowerNormal, double solarPowerDiffuse, long epochTimeSeconds, double ambientTemperature) {
public float getPower(double solarPowerNormal, double solarPowerDiffuse, double shortwaveRadiation, long epochTimeSeconds, double ambientTemperature) {
Instant i = Instant.ofEpochSecond(epochTimeSeconds); //currentTimeMillis is in GMT
ZonedDateTime dateTime = ZonedDateTime.ofInstant(i, ZoneId.of("GMT"));
@ -73,7 +75,7 @@ public class SolarPowerPlant {
}
}
double totalRadiationOnCell = solarPowerNormal * efficiency + solarPowerDiffuse * diffuseEfficiency; //flat plate equivalent of the solar irradiance
double totalRadiationOnCell = solarPowerNormal * efficiency + solarPowerDiffuse * diffuseEfficiency + shortwaveRadiation * (0.5-0.5*Math.cos(tiltAngle/180*Math.PI)) * albedo; //flat plate equivalent of the solar irradiance
double cellTemperature = calcCellTemperature(ambientTemperature,totalRadiationOnCell);
double dcPower;
if (cellsEfficiency!=0 && cellsArea!=0){

View file

@ -189,6 +189,7 @@ public class ManageLocationsActivity extends NavigationActivity {
EditText editCellsEfficiency = (EditText) dialogView.findViewById(R.id.EditLocation_Cell_Efficiency);
EditText editCellsTempCoeff = (EditText) dialogView.findViewById(R.id.EditLocation_Cell_Temp_Coeff);
EditText editDiffuseEfficiency = (EditText) dialogView.findViewById(R.id.EditLocation_Diffuse_Efficiency);
EditText editAlbedo = (EditText) dialogView.findViewById(R.id.EditLocation_Albedo);
EditText editInverterPowerLimit = (EditText) dialogView.findViewById(R.id.EditLocation_Inverter_Power_Limit);
EditText editInverterEfficiency = (EditText) dialogView.findViewById(R.id.EditLocation_Inverter_Efficiency);
@ -209,6 +210,8 @@ public class ManageLocationsActivity extends NavigationActivity {
editCellsTempCoeff.setFilters(new InputFilter[]{ new InputFilterMinMax(-100, 100)});
editDiffuseEfficiency.setText(Float.toString(city.getDiffuseEfficiency()));
editDiffuseEfficiency.setFilters(new InputFilter[]{ new InputFilterMinMax(0, 100)});
editAlbedo.setText(Float.toString(city.getAlbedo()));
editAlbedo.setFilters(new InputFilter[]{ new InputFilterMinMax(0,1)});
editInverterPowerLimit.setText(Float.toString(city.getInverterPowerLimit()));
editInverterEfficiency.setText(Float.toString(city.getInverterEfficiency()));
editInverterEfficiency.setFilters(new InputFilter[]{ new InputFilterMinMax(0, 100)});
@ -221,7 +224,7 @@ public class ManageLocationsActivity extends NavigationActivity {
@Override
public void afterTextChanged(Editable editable) {
float tilt = Float.parseFloat(!editTilt.getText().toString().isEmpty() ? editTilt.getText().toString() : "0");
int diffuseEfficiency = (int) (100-50 * tilt/90);
int diffuseEfficiency = (int) ( 50 + 50* Math.cos(tilt/180*Math.PI));
editDiffuseEfficiency.setText(Float.toString((float) diffuseEfficiency));
}
});
@ -242,6 +245,7 @@ public class ManageLocationsActivity extends NavigationActivity {
Float.parseFloat(editCellsEfficiency.getText().toString().isEmpty() ? "0" : editCellsEfficiency.getText().toString()),
Float.parseFloat(editCellsTempCoeff.getText().toString().isEmpty() ? "0" : editCellsTempCoeff.getText().toString()),
Float.parseFloat(editDiffuseEfficiency.getText().toString().isEmpty() ? "0" : editDiffuseEfficiency.getText().toString()),
Float.parseFloat(editAlbedo.getText().toString().isEmpty() ? "0" : editAlbedo.getText().toString()),
Float.parseFloat(editInverterPowerLimit.getText().toString().isEmpty() ? "0" : editInverterPowerLimit.getText().toString()),
Float.parseFloat(editInverterEfficiency.getText().toString().isEmpty() ? "0" : editInverterEfficiency.getText().toString()),
shadingElevation,
@ -277,6 +281,7 @@ public class ManageLocationsActivity extends NavigationActivity {
Float.parseFloat(editCellsEfficiency.getText().toString().isEmpty() ? "0" : editCellsEfficiency.getText().toString()),
Float.parseFloat(editCellsTempCoeff.getText().toString().isEmpty() ? "0" : editCellsTempCoeff.getText().toString()),
Float.parseFloat(editDiffuseEfficiency.getText().toString().isEmpty() ? "0" : editDiffuseEfficiency.getText().toString()),
Float.parseFloat(editAlbedo.getText().toString().isEmpty() ? "0" : editAlbedo.getText().toString()),
Float.parseFloat(editInverterPowerLimit.getText().toString().isEmpty() ? "0" : editInverterPowerLimit.getText().toString()),
Float.parseFloat(editInverterEfficiency.getText().toString().isEmpty() ? "0" : editInverterEfficiency.getText().toString()),
shadingElevation,

View file

@ -23,6 +23,7 @@ public class CityToWatch {
private float inverterEfficiency;
private float azimuthAngle;
private float tiltAngle;
private float albedo;
private int rank;
private int[] shadingElevation = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
private int[] shadingOpacity = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
@ -46,6 +47,7 @@ public class CityToWatch {
this.inverterEfficiency = 95.0f;
this.azimuthAngle = 170.0f;
this.tiltAngle = 90.0f;
this.albedo = 0f;
}
@ -192,4 +194,8 @@ public class CityToWatch {
public void setCellsTempCoeff(float cellsTempCoeff) {
this.cellsTempCoeff = cellsTempCoeff;
}
public float getAlbedo() { return this.albedo; }
public void setAlbedo (float albedo) { this.albedo = albedo; }
}

View file

@ -15,6 +15,7 @@ public class HourlyForecast {
private int weatherID;
private float directRadiationNormal;
private float diffuseRadiation;
private float shortwaveRadiation;
private float power;
private String city_name;
@ -104,4 +105,8 @@ public class HourlyForecast {
public void setDiffuseRadiation(float diffuseRadiation) { this.diffuseRadiation = diffuseRadiation; }
public void setPower(float power) { this.power = power; }
public void setShortwaveRadiation(float shortwaveRadiation) { this.shortwaveRadiation = shortwaveRadiation; }
public float getShortwaveRadiation() { return this.shortwaveRadiation; }
}

View file

@ -20,7 +20,7 @@ import static androidx.core.app.JobIntentService.enqueueWork;
*/
public class SQLiteHelper extends SQLiteOpenHelper {
private static final int DATABASE_VERSION = 2;
private static final int DATABASE_VERSION = 3;
private Context context;
private List<City> allCities = new ArrayList<>();
@ -54,6 +54,7 @@ public class SQLiteHelper extends SQLiteOpenHelper {
private static final String CITIES_TO_WATCH_SHADING_ELEVATION = "shading_elevation";
private static final String CITIES_TO_WATCH_SHADING_OPACITY = "shading_opacity";
private static final String CITIES_TO_WATCH_CELLS_TEMP_COEFF = "cells_temp_coeff";
private static final String CITIES_TO_WATCH_ALBEDO = "albedo";
//Names of columns in TABLE_FORECAST
private static final String FORECAST_ID = "forecast_id";
@ -137,7 +138,8 @@ public class SQLiteHelper extends SQLiteOpenHelper {
CITIES_TO_WATCH_TILT_ANGLE + " REAL NOT NULL," +
CITIES_TO_WATCH_SHADING_ELEVATION + " VARCHAR(255) NOT NULL," +
CITIES_TO_WATCH_SHADING_OPACITY + " VARCHAR(255) NOT NULL," +
CITIES_TO_WATCH_CELLS_TEMP_COEFF + " REAL NOT NULL)";
CITIES_TO_WATCH_CELLS_TEMP_COEFF + " REAL NOT NULL," +
CITIES_TO_WATCH_ALBEDO + " REAL NOT NULL)";
public static SQLiteHelper getInstance(Context context) {
if (instance == null && context != null) {
@ -166,6 +168,8 @@ public class SQLiteHelper extends SQLiteOpenHelper {
case 1:
db.execSQL("ALTER TABLE "+TABLE_CITIES_TO_WATCH+" ADD COLUMN "+CITIES_TO_WATCH_CELLS_TEMP_COEFF+" REAL DEFAULT 0");
// we want both updates, so no break statement here...
case 2:
db.execSQL("ALTER TABLE "+TABLE_CITIES_TO_WATCH+" ADD COLUMN "+CITIES_TO_WATCH_ALBEDO+" REAL DEFAULT 0");
}
}
@ -194,6 +198,7 @@ public class SQLiteHelper extends SQLiteOpenHelper {
values.put(CITIES_TO_WATCH_SHADING_ELEVATION,city.getShadingElevationString());
values.put(CITIES_TO_WATCH_SHADING_OPACITY,city.getShadingOpacityString());
values.put(CITIES_TO_WATCH_CELLS_TEMP_COEFF,city.getCellsTempCoeff());
values.put(CITIES_TO_WATCH_ALBEDO,city.getAlbedo());
long id=database.insert(TABLE_CITIES_TO_WATCH, null, values);
@ -228,6 +233,7 @@ public class SQLiteHelper extends SQLiteOpenHelper {
", " + CITIES_TO_WATCH_SHADING_ELEVATION +
", " + CITIES_TO_WATCH_SHADING_OPACITY +
", " + CITIES_TO_WATCH_CELLS_TEMP_COEFF +
", " + CITIES_TO_WATCH_ALBEDO +
", " + CITIES_TO_WATCH_COLUMN_RANK +
" FROM " + TABLE_CITIES_TO_WATCH +
" WHERE " + CITIES_TO_WATCH_CITY_ID + " = ?", arguments);
@ -251,7 +257,8 @@ public class SQLiteHelper extends SQLiteOpenHelper {
cityToWatch.setShadingElevation(cursor.getString(13));
cityToWatch.setShadingOpacity(cursor.getString(14));
cityToWatch.setCellsTempCoeff(Float.parseFloat(cursor.getString(15)));
cityToWatch.setRank(Integer.parseInt(cursor.getString(16)));
cityToWatch.setAlbedo(Float.parseFloat(cursor.getString(16)));
cityToWatch.setRank(Integer.parseInt(cursor.getString(17)));
cursor.close();
}
@ -283,6 +290,7 @@ public class SQLiteHelper extends SQLiteOpenHelper {
", " + CITIES_TO_WATCH_SHADING_ELEVATION +
", " + CITIES_TO_WATCH_SHADING_OPACITY +
", " + CITIES_TO_WATCH_CELLS_TEMP_COEFF +
", " + CITIES_TO_WATCH_ALBEDO +
", " + CITIES_TO_WATCH_COLUMN_RANK +
" FROM " + TABLE_CITIES_TO_WATCH
, new String[]{});
@ -308,7 +316,8 @@ public class SQLiteHelper extends SQLiteOpenHelper {
cityToWatch.setShadingElevation(cursor.getString(13));
cityToWatch.setShadingOpacity(cursor.getString(14));
cityToWatch.setCellsTempCoeff(Float.parseFloat(cursor.getString(15)));
cityToWatch.setRank(Integer.parseInt(cursor.getString(16)));
cityToWatch.setAlbedo(Float.parseFloat(cursor.getString(16)));
cityToWatch.setRank(Integer.parseInt(cursor.getString(17)));
cityToWatchList.add(cityToWatch);
} while (cursor.moveToNext());
@ -339,6 +348,7 @@ public class SQLiteHelper extends SQLiteOpenHelper {
values.put(CITIES_TO_WATCH_SHADING_ELEVATION,cityToWatch.getShadingElevationString());
values.put(CITIES_TO_WATCH_SHADING_OPACITY,cityToWatch.getShadingOpacityString());
values.put(CITIES_TO_WATCH_CELLS_TEMP_COEFF,cityToWatch.getCellsTempCoeff());
values.put(CITIES_TO_WATCH_ALBEDO,cityToWatch.getAlbedo());
database.update(TABLE_CITIES_TO_WATCH, values, CITIES_TO_WATCH_ID + " = ?",
new String[]{String.valueOf(cityToWatch.getId())});

View file

@ -130,7 +130,7 @@ public class RecyclerOverviewListAdapter extends RecyclerView.Adapter<ItemViewHo
public CityToWatch getCitytoWatch(int position){
return cities.get(position);
}
public void updateCity(CityToWatch cityToWatch, String cityName, float latitude, float longitude, float azimuth, float tilt, float cellsMaxPower, float cellsArea, float cellsEfficiency, float cellsTempCoeff, float diffuseEfficiency, float inverterPowerLimit, float inverterEfficiency, int[] shadingElevation, int[] shadingOpacity) {
public void updateCity(CityToWatch cityToWatch, String cityName, float latitude, float longitude, float azimuth, float tilt, float cellsMaxPower, float cellsArea, float cellsEfficiency, float cellsTempCoeff, float diffuseEfficiency, float albedo, float inverterPowerLimit, float inverterEfficiency, int[] shadingElevation, int[] shadingOpacity) {
cityToWatch.setCityName(cityName);
cityToWatch.setLatitude(latitude);
cityToWatch.setLongitude(longitude);
@ -141,6 +141,7 @@ public class RecyclerOverviewListAdapter extends RecyclerView.Adapter<ItemViewHo
cityToWatch.setCellsEfficiency(cellsEfficiency);
cityToWatch.setCellsTempCoeff(cellsTempCoeff);
cityToWatch.setDiffuseEfficiency(diffuseEfficiency);
cityToWatch.setAlbedo(albedo);
cityToWatch.setInverterPowerLimit(inverterPowerLimit);
cityToWatch.setInverterEfficiency(inverterEfficiency);
cityToWatch.setShadingElevation(shadingElevation);

View file

@ -69,12 +69,13 @@ public class OMDataExtractor implements IDataExtractor {
JSONArray tempArray = jsonData.has("temperature_2m") ? jsonData.getJSONArray("temperature_2m") : null;
JSONArray directRadiationArray = jsonData.has("direct_normal_irradiance") ? jsonData.getJSONArray("direct_normal_irradiance") : null;
JSONArray diffuseRadiationArray = jsonData.has("diffuse_radiation") ? jsonData.getJSONArray("diffuse_radiation") : null;
JSONArray shortwaveRadiationArray = jsonData.has("shortwave_radiation") ? jsonData.getJSONArray("shortwave_radiation") : null;
//TODO get Data for power plant from city to Watch
SQLiteHelper dbhelper = SQLiteHelper.getInstance(context);
CityToWatch city = dbhelper.getCityToWatch(cityID);
SolarPowerPlant spp = new SolarPowerPlant(city.getLatitude(), city.getLongitude(), city.getCellsMaxPower(), city.getCellsArea(), city.getCellsEfficiency(), city.getCellsTempCoeff(), city.getDiffuseEfficiency(), city.getInverterPowerLimit(), city.getInverterEfficiency(), city.getAzimuthAngle(), city.getTiltAngle(), city.getShadingElevation(), city.getShadingOpacity());
SolarPowerPlant spp = new SolarPowerPlant(city.getLatitude(), city.getLongitude(), city.getCellsMaxPower(), city.getCellsArea(), city.getCellsEfficiency(), city.getCellsTempCoeff(), city.getDiffuseEfficiency(), city.getInverterPowerLimit(), city.getInverterEfficiency(), city.getAzimuthAngle(), city.getTiltAngle(), city.getShadingElevation(), city.getShadingOpacity(), city.getAlbedo());
IApiToDatabaseConversion conversion = new OMToDatabaseConversion();
@ -87,7 +88,8 @@ public class OMDataExtractor implements IDataExtractor {
if (weathercodeArray!=null && !weathercodeArray.isNull(i)) hourlyForecast.setWeatherID(conversion.convertWeatherCategory(weathercodeArray.getString(i)));
if (directRadiationArray!=null && !directRadiationArray.isNull(i)) hourlyForecast.setDirectRadiationNormal((float) directRadiationArray.getDouble(i));
if (diffuseRadiationArray!=null && !diffuseRadiationArray.isNull(i)) hourlyForecast.setDiffuseRadiation((float) diffuseRadiationArray.getDouble(i));
hourlyForecast.setPower(spp.getPower(hourlyForecast.getDirectRadiationNormal(), hourlyForecast.getDiffuseRadiation(), timeArray.getLong(i)-1800, ambientTemperature)); //use solar position 1/2h earlier for calculation of average power in preceding hour
if (shortwaveRadiationArray!=null && !shortwaveRadiationArray.isNull(i)) hourlyForecast.setShortwaveRadiation((float) shortwaveRadiationArray.getDouble(i));
hourlyForecast.setPower(spp.getPower(hourlyForecast.getDirectRadiationNormal(), hourlyForecast.getDiffuseRadiation(), hourlyForecast.getShortwaveRadiation(), timeArray.getLong(i)-1800, ambientTemperature)); //use solar position 1/2h earlier for calculation of average power in preceding hour
hourlyForecasts.add(hourlyForecast);
}
return hourlyForecasts;

View file

@ -24,7 +24,7 @@ public class OMHttpRequest {
SharedPreferences sharedPreferences=PreferenceManager.getDefaultSharedPreferences(context);
return String.format(
"%sforecast?latitude=%s&longitude=%s&forecast_days=%s&hourly=temperature_2m,diffuse_radiation,direct_normal_irradiance,weathercode&daily=weathercode,sunrise,sunset,&timeformat=unixtime&timezone=auto",
"%sforecast?latitude=%s&longitude=%s&forecast_days=%s&hourly=temperature_2m,diffuse_radiation,direct_normal_irradiance,shortwave_radiation,weathercode&daily=weathercode,sunrise,sunset,&timeformat=unixtime&timezone=auto",
BuildConfig.BASE_URL,
lat,
lon,

View file

@ -111,6 +111,20 @@
android:layout_height="wrap_content"
android:inputType="numberDecimal"
android:hint="@string/edit_location_hint_diffuse_efficiency"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/edit_location_hint_albedo"/>
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/EditLocation_Albedo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="numberDecimal"
android:hint="@string/edit_location_hint_albedo"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"

View file

@ -107,6 +107,7 @@
<string name="dialog_add_clone_button">Klonen</string>
<string name="itemRemoved">ENTFERNT:\u0020\u0020%s</string>
<string name="undo">WIEDERHERSTELLEN</string>
<string name="edit_location_hint_albedo">Albedo [0..1]</string>
</resources>

View file

@ -110,4 +110,5 @@
<string name="dialog_add_clone_button">Clone</string>
<string name="itemRemoved">REMOVED:\u0020\u0020%s</string>
<string name="undo">UNDO</string>
<string name="edit_location_hint_albedo">Albedo [0..1]</string>
</resources>

View file

@ -1,7 +1,6 @@
solXpect prognostiziert den Ertrag Ihrer PV Anlage
Diese App nimmt direkte und diffuse Strahlungsdaten von Open-Meteo.com, berechnet die Position
der Sonne und projiziert die Strahlung auf Ihr Solarpanel.
Diese App nimmt direkte und diffuse Strahlungsdaten von Open-Meteo.com, berechnet die Position der Sonne und projiziert die Strahlung auf Ihr Solarpanel.
Es zeigt die geschätzte Energieproduktion für die nächsten Stunden und bis zu 16 Tage an.
Features:

View file

@ -1,7 +1,6 @@
solXpect forecasts the output of your solar power plant
This app takes direct and diffuse radiation data from Open-Meteo.com, calculates the position
of the sun and projects the radiation on your solar panel.
This app takes direct and diffuse radiation data from Open-Meteo.com, calculates the position of the sun and projects the radiation on your solar panel.
It shows the estimated energy production for the next hours and up to 16 days.
Features: