diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1b1d71b3c..e0a201f0e 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -868,6 +868,11 @@
android:name=".devices.supercars.ControlActivity"
android:exported="true" />
+
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/marstek/MarstekB2500DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/marstek/MarstekB2500DeviceCoordinator.java
index 87177a7c2..b35e584ee 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/marstek/MarstekB2500DeviceCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/marstek/MarstekB2500DeviceCoordinator.java
@@ -17,14 +17,15 @@
package nodomain.freeyourgadget.gadgetbridge.devices.marstek;
-import androidx.annotation.NonNull;
+import android.app.Activity;
+import androidx.annotation.NonNull;
import java.util.regex.Pattern;
import nodomain.freeyourgadget.gadgetbridge.GBException;
-import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
@@ -69,6 +70,16 @@ public class MarstekB2500DeviceCoordinator extends AbstractDeviceCoordinator {
}
+ @Override
+ public boolean supportsAppsManagement(final GBDevice device) {
+ return true;
+ }
+
+ @Override
+ public Class extends Activity> getAppsManagementActivity() {
+ return SolarEquipmentStatusActivity.class;
+ }
+
@Override
public int getBondingStyle() {
return BONDING_STYLE_NONE;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/marstek/SolarEquipmentStatusActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/marstek/SolarEquipmentStatusActivity.java
new file mode 100644
index 000000000..6ffbb4b0d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/marstek/SolarEquipmentStatusActivity.java
@@ -0,0 +1,170 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.marstek;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.core.content.ContextCompat;
+import androidx.gridlayout.widget.GridLayout;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
+
+import com.google.android.material.card.MaterialCardView;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.GaugeDrawer;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class SolarEquipmentStatusActivity extends AbstractGBActivity {
+ public static String ACTION_SEND_SOLAR_EQUIPMENT_STATUS = "send_solar_equipment_status";
+ public static String EXTRA_BATTERY_PCT = "battery_pct";
+ public static String EXTRA_PANEL1_WATT = "panel1_watt";
+ public static String EXTRA_PANEL2_WATT = "panel2_watt";
+ public static String EXTRA_OUTPUT1_WATT = "output1_watt";
+ public static String EXTRA_OUTPUT2_WATT = "output2_watt";
+
+ private final Map widgetMap = new HashMap<>();
+ private GridLayout gridLayout;
+ private SwipeRefreshLayout swipeLayout;
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (Objects.requireNonNull(action).equals(ACTION_SEND_SOLAR_EQUIPMENT_STATUS)) {
+ Bundle extras = intent.getExtras();
+ if (extras != null) {
+ int battery_pct = extras.getInt(EXTRA_BATTERY_PCT);
+ int panel1_watt = extras.getInt(EXTRA_PANEL1_WATT);
+ int panel2_watt = extras.getInt(EXTRA_PANEL2_WATT);
+ int output1_watt = extras.getInt(EXTRA_OUTPUT1_WATT);
+ int output2_watt = extras.getInt(EXTRA_OUTPUT2_WATT);
+ updateWidget("battery", battery_pct + "%", (float) (battery_pct / 100.0));
+ updateWidget("panel1", panel1_watt + "W", (float) (panel1_watt / 380.0));
+ updateWidget("panel2", panel2_watt + "W", (float) (panel1_watt / 380.0));
+ updateWidget("output1", output1_watt + "W", (float) (output1_watt / 400.0));
+ updateWidget("output2", output2_watt + "W", (float) (output1_watt / 400.0));
+
+ swipeLayout.setRefreshing(false);
+ }
+ }
+ }
+ };
+ private GBDevice gBDevice = null;
+
+ public static int[] getColors() {
+ return new int[]{
+ ContextCompat.getColor(GBApplication.getContext(), R.color.vo2max_value_poor_color),
+ ContextCompat.getColor(GBApplication.getContext(), R.color.vo2max_value_fair_color),
+ ContextCompat.getColor(GBApplication.getContext(), R.color.vo2max_value_good_color),
+ };
+ }
+
+ public static int[] getColorsOutput() {
+ return new int[]{
+ ContextCompat.getColor(GBApplication.getContext(), R.color.vo2max_value_good_color),
+ ContextCompat.getColor(GBApplication.getContext(), R.color.vo2max_value_fair_color),
+ ContextCompat.getColor(GBApplication.getContext(), R.color.vo2max_value_poor_color),
+ };
+ }
+
+ public static float[] getSegments() {
+ return new float[]{
+ 0.1f,
+ 0.2f,
+ 0.7f,
+ };
+ }
+
+ public static float[] getSegmentsOutput() {
+ return new float[]{
+ 0.33f,
+ 0.33f,
+ 0.34f,
+ };
+ }
+
+ private void updateWidget(String name, String value, float gaugeFill) {
+ View view = widgetMap.get(name);
+ if (view != null) {
+ TextView gaugeValue = view.findViewById(R.id.gauge_value);
+ gaugeValue.setText(value);
+ GaugeDrawer gaugeDrawer = new GaugeDrawer();
+ ImageView gaugeBar = view.findViewById(R.id.gauge_bar);
+ gaugeDrawer.drawSegmentedGauge(gaugeBar, name.startsWith("output") ? getColorsOutput() : getColors(), name.startsWith("output") ? getSegmentsOutput() : getSegments(), gaugeFill, true, false);
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ Bundle extras = getIntent().getExtras();
+ if (extras != null) {
+ gBDevice = extras.getParcelable(GBDevice.EXTRA_DEVICE);
+ }
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_solar_equipment_status);
+ gridLayout = findViewById(R.id.solarequipmentview_gridlayout);
+ createWidget("panel1", "Panel 1", 1);
+ createWidget("panel2", "Panel 2", 1);
+ createWidget("battery", "Battery", 2);
+ createWidget("output1", "Output 1", 1);
+ createWidget("output2", "Output 2", 1);
+
+ // Set pull-down-to-refresh action
+ swipeLayout = findViewById(R.id.solarequipmentview_swipe_layout);
+ swipeLayout.setOnRefreshListener(() -> {
+ if (gBDevice == null || !gBDevice.isInitialized()) {
+ swipeLayout.setRefreshing(false);
+ GB.toast(getString(R.string.info_no_devices_connected), Toast.LENGTH_LONG, GB.WARN);
+ return;
+ }
+ GBApplication.deviceService(gBDevice).onFetchRecordedData(0);
+ });
+
+ IntentFilter filterLocal = new IntentFilter();
+ filterLocal.addAction(ACTION_SEND_SOLAR_EQUIPMENT_STATUS);
+ LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
+ }
+
+ private void createWidget(String name, String label, int columnSpan) {
+ final float scale = getResources().getDisplayMetrics().density;
+
+ GridLayout.LayoutParams layoutParams = new GridLayout.LayoutParams(
+ GridLayout.spec(GridLayout.UNDEFINED, GridLayout.FILL, 1f),
+ GridLayout.spec(GridLayout.UNDEFINED, columnSpan, GridLayout.FILL, 1f)
+ );
+ layoutParams.width = 0;
+ int pixels_8dp = (int) (8 * scale + 0.5f);
+ layoutParams.setMargins(pixels_8dp, pixels_8dp, pixels_8dp, pixels_8dp);
+
+ MaterialCardView card = new MaterialCardView(this);
+ int pixels_4dp = (int) (4 * scale + 0.5f);
+ card.setRadius(pixels_4dp);
+ card.setCardElevation(pixels_4dp);
+ card.setContentPadding(pixels_4dp, pixels_4dp, pixels_4dp, pixels_4dp);
+ card.setLayoutParams(layoutParams);
+ LayoutInflater inflater = getLayoutInflater();
+
+ final View gaugeView = inflater.inflate(R.layout.dashboard_widget_generic_gauge, card, false);
+ final TextView gaugeLabel = gaugeView.findViewById(R.id.gauge_label);
+ gaugeLabel.setText(label);
+
+ card.addView(gaugeView);
+ widgetMap.put(name, gaugeView);
+ gridLayout.addView(card);
+ }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/marstek/MarstekB2500DeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/marstek/MarstekB2500DeviceSupport.java
index ea6648c10..02eec7349 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/marstek/MarstekB2500DeviceSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/marstek/MarstekB2500DeviceSupport.java
@@ -23,8 +23,11 @@ import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.Dev
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Intent;
import android.content.SharedPreferences;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -36,6 +39,7 @@ import java.util.SimpleTimeZone;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.devices.marstek.SolarEquipmentStatusActivity;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
@@ -148,6 +152,11 @@ public class MarstekB2500DeviceSupport extends AbstractBTLEDeviceSupport {
sendCommand("set time", encodeSetCurrentTime());
}
+ @Override
+ public void onFetchRecordedData(int dataTypes) {
+ sendCommand("get infos 1", COMMAND_GET_INFOS1);
+ }
+
@Override
public void onSendConfiguration(final String config) {
Prefs devicePrefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
@@ -181,7 +190,7 @@ public class MarstekB2500DeviceSupport extends AbstractBTLEDeviceSupport {
ByteBuffer buf = ByteBuffer.wrap(value);
buf.order(ByteOrder.LITTLE_ENDIAN);
buf.position(4); // skip header
- boolean p1_active = buf.get() != 0x00; // TODO: active = connected, or power incoming?
+ boolean p1_active = buf.get() != 0x00;
boolean p2_active = buf.get() != 0x00;
int p1_watt = buf.getShort();
int p2_watt = buf.getShort();
@@ -216,6 +225,15 @@ public class MarstekB2500DeviceSupport extends AbstractBTLEDeviceSupport {
int battery_percentage = (int) Math.ceil((battery_charge_kwh / 2240.0f) * 100);
getDevice().setBatteryLevel(battery_percentage);
getDevice().sendDeviceUpdateIntent(getContext());
+
+ Intent intent = new Intent(SolarEquipmentStatusActivity.ACTION_SEND_SOLAR_EQUIPMENT_STATUS);
+ intent.putExtra(SolarEquipmentStatusActivity.EXTRA_BATTERY_PCT, battery_percentage);
+ intent.putExtra(SolarEquipmentStatusActivity.EXTRA_PANEL1_WATT, p1_watt);
+ intent.putExtra(SolarEquipmentStatusActivity.EXTRA_PANEL2_WATT, p2_watt);
+ intent.putExtra(SolarEquipmentStatusActivity.EXTRA_OUTPUT1_WATT, output_to_inverter_1_watt);
+ intent.putExtra(SolarEquipmentStatusActivity.EXTRA_OUTPUT2_WATT, output_to_inverter_2_watt);
+ LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
+ getContext().sendBroadcast(intent);
}
private void decodeDischargeIntervalsToPreferences(byte[] value) {
diff --git a/app/src/main/res/layout/activity_solar_equipment_status.xml b/app/src/main/res/layout/activity_solar_equipment_status.xml
new file mode 100644
index 000000000..a5f071950
--- /dev/null
+++ b/app/src/main/res/layout/activity_solar_equipment_status.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+