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 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 @@ + + + + + + + +