diff --git a/.github/workflows/gradle_build.yml b/.github/workflows/gradle_build.yml
index 9523766c..e6a970d5 100644
--- a/.github/workflows/gradle_build.yml
+++ b/.github/workflows/gradle_build.yml
@@ -10,11 +10,6 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- - name: Configure SSH for private submodules
- uses: webfactory/ssh-agent@v0.9.0
- with:
- ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
-
- name: Checkout code
uses: actions/checkout@v4
with:
diff --git a/build.gradle b/build.gradle
index 46c4df04..fd680682 100644
--- a/build.gradle
+++ b/build.gradle
@@ -3,7 +3,7 @@ ext.compileSdkVersion = 34
ext.minSdkVersion = 33
ext.targetSdkVersion = 30
ext.buildToolsVersion = "34.0.0"
-version = '4.0.2'
+version = '4.1.1'
buildscript {
repositories {
@@ -13,6 +13,7 @@ buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:7.4.2'
+ classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.20'
}
}
diff --git a/gradle.properties b/gradle.properties
index 94adc3a3..4de64f2e 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,3 +1,3 @@
-org.gradle.jvmargs=-Xmx1536M
+org.gradle.jvmargs=-Xmx4096M
android.useAndroidX=true
android.enableJetifier=true
diff --git a/sensorhub-android-app/build.gradle b/sensorhub-android-app/build.gradle
index cc935964..c1b78ac4 100644
--- a/sensorhub-android-app/build.gradle
+++ b/sensorhub-android-app/build.gradle
@@ -82,6 +82,13 @@ android {
java.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
+ resources.srcDirs = ['src/main/resources']
+ }
+ }
+
+ applicationVariants.all { variant ->
+ variant.outputs.all {
+ outputFileName = "osh-android-${variant.versionName}-${variant.name}.apk"
}
}
diff --git a/sensorhub-android-app/res/color/bottom_nav_selector.xml b/sensorhub-android-app/res/color/bottom_nav_selector.xml
index 68a6dc27..32651b24 100644
--- a/sensorhub-android-app/res/color/bottom_nav_selector.xml
+++ b/sensorhub-android-app/res/color/bottom_nav_selector.xml
@@ -1,5 +1,5 @@
-
-
+
+
diff --git a/sensorhub-android-app/res/drawable/status_dot_background.xml b/sensorhub-android-app/res/drawable/ic_circle.xml
similarity index 100%
rename from sensorhub-android-app/res/drawable/status_dot_background.xml
rename to sensorhub-android-app/res/drawable/ic_circle.xml
diff --git a/sensorhub-android-app/res/layout/activity_app_status.xml b/sensorhub-android-app/res/layout/activity_app_status.xml
index 69a569ce..c4a64d8a 100644
--- a/sensorhub-android-app/res/layout/activity_app_status.xml
+++ b/sensorhub-android-app/res/layout/activity_app_status.xml
@@ -55,7 +55,7 @@
android:id="@+id/sos_status_dot"
android:layout_width="@dimen/status_dot_size"
android:layout_height="@dimen/status_dot_size"
- android:background="@drawable/status_dot_background" />
+ android:background="@drawable/ic_circle" />
+ android:background="@drawable/ic_circle" />
+ android:background="@drawable/ic_circle" />
+ android:background="@drawable/ic_circle" />
+ android:background="@drawable/ic_circle" />
+ android:background="@drawable/ic_circle" />
+ app:strokeWidth="0dp">
+ app:strokeWidth="0dp">
+ android:singleLine="true"
+ android:text="/sensorhub/api"/>
+ app:strokeWidth="0dp">
+ app:strokeWidth="0dp">
+ android:background="@drawable/ic_circle" />
+ app:strokeWidth="0dp">
+ android:background="@drawable/ic_circle" />
+ app:tint="@android:color/white"
+ app:backgroundTint="@color/md_theme_surfaceVariant" />
diff --git a/sensorhub-android-app/res/layout/item_datastream.xml b/sensorhub-android-app/res/layout/item_datastream.xml
new file mode 100644
index 00000000..361786ef
--- /dev/null
+++ b/sensorhub-android-app/res/layout/item_datastream.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
diff --git a/sensorhub-android-app/res/layout/item_faq_entry.xml b/sensorhub-android-app/res/layout/item_faq_entry.xml
index 6fe1b4a8..7d88c563 100644
--- a/sensorhub-android-app/res/layout/item_faq_entry.xml
+++ b/sensorhub-android-app/res/layout/item_faq_entry.xml
@@ -7,7 +7,7 @@
android:layout_marginBottom="8dp"
app:cardCornerRadius="@dimen/card_corner_radius"
app:cardElevation="@dimen/card_elevation"
- app:cardBackgroundColor="@color/md_theme_surface">
+ app:cardBackgroundColor="@color/dialog_bg">
@@ -36,7 +36,7 @@
android:layout_height="24dp"
android:layout_marginStart="8dp"
android:src="@drawable/ic_expand_more"
- app:tint="@color/md_theme_onSurfaceVariant"
+ app:tint="@android:color/white"
android:contentDescription="Expand" />
@@ -46,7 +46,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
- android:textColor="@color/md_theme_onSurfaceVariant"
+ android:textColor="@android:color/white"
android:textSize="14sp"
android:lineSpacingExtra="4dp"
android:visibility="gone" />
diff --git a/sensorhub-android-app/res/layout/item_sensor_group.xml b/sensorhub-android-app/res/layout/item_sensor_group.xml
new file mode 100644
index 00000000..b200401d
--- /dev/null
+++ b/sensorhub-android-app/res/layout/item_sensor_group.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sensorhub-android-app/res/layout/item_server_profile.xml b/sensorhub-android-app/res/layout/item_server_profile.xml
index 45fa407b..19bdf7df 100644
--- a/sensorhub-android-app/res/layout/item_server_profile.xml
+++ b/sensorhub-android-app/res/layout/item_server_profile.xml
@@ -11,8 +11,7 @@
app:cardCornerRadius="@dimen/card_corner_radius"
app:cardElevation="0dp"
app:cardBackgroundColor="@color/surface_card"
- app:strokeColor="@color/md_theme_outline"
- app:strokeWidth="1dp">
+ app:strokeWidth="0dp">
+ android:layout_marginHorizontal="16dp"
+ android:layout_marginTop="8dp"
+ app:cardCornerRadius="16dp"
+ app:cardElevation="2dp"
+ app:strokeWidth="1dp"
+ app:strokeColor="?attr/colorOutlineVariant">
+ android:orientation="horizontal"
+ android:padding="16dp">
-
+
+ android:orientation="vertical">
+ android:textAppearance="?attr/textAppearanceTitleMedium"
+ android:textColor="?attr/colorOnSurface"/>
-
+ android:layout_marginTop="2dp"
+ android:textAppearance="?attr/textAppearanceBodySmall"
+ android:textColor="?attr/colorOnSurfaceVariant"/>
-
+
-
+
+
-
+
\ No newline at end of file
diff --git a/sensorhub-android-app/res/layout/pref_switch_bottom.xml b/sensorhub-android-app/res/layout/pref_switch_bottom.xml
index 514d43bf..d62c2468 100644
--- a/sensorhub-android-app/res/layout/pref_switch_bottom.xml
+++ b/sensorhub-android-app/res/layout/pref_switch_bottom.xml
@@ -35,12 +35,10 @@
android:layout_marginTop="2dp" />
-
+ android:clickable="false" />
diff --git a/sensorhub-android-app/res/layout/pref_switch_mid.xml b/sensorhub-android-app/res/layout/pref_switch_mid.xml
index 4141a24f..82b79197 100644
--- a/sensorhub-android-app/res/layout/pref_switch_mid.xml
+++ b/sensorhub-android-app/res/layout/pref_switch_mid.xml
@@ -35,12 +35,10 @@
android:layout_marginTop="2dp" />
-
+ android:clickable="false" />
diff --git a/sensorhub-android-app/res/layout/pref_switch_top.xml b/sensorhub-android-app/res/layout/pref_switch_top.xml
index 9bf22283..e23458fd 100644
--- a/sensorhub-android-app/res/layout/pref_switch_top.xml
+++ b/sensorhub-android-app/res/layout/pref_switch_top.xml
@@ -35,12 +35,10 @@
android:layout_marginTop="2dp" />
-
+ android:clickable="false" />
diff --git a/sensorhub-android-app/res/layout/preference_switch_item.xml b/sensorhub-android-app/res/layout/preference_switch_item.xml
index 24ec3c5c..6a6101c9 100644
--- a/sensorhub-android-app/res/layout/preference_switch_item.xml
+++ b/sensorhub-android-app/res/layout/preference_switch_item.xml
@@ -30,16 +30,13 @@
android:textAlignment="viewStart" />
-
+ android:paddingEnd="0dp" />
\ No newline at end of file
diff --git a/sensorhub-android-app/res/values/colors.xml b/sensorhub-android-app/res/values/colors.xml
index d04f4a14..42b44bf1 100644
--- a/sensorhub-android-app/res/values/colors.xml
+++ b/sensorhub-android-app/res/values/colors.xml
@@ -1,73 +1,74 @@
- #D35400
+ #E64A19
#FFFFFF
- #24160A
- #E6B89C
+ #4E1700
+ #FFCCBC
- #C24E00
+ #BF360C
- #9A9A9A
+ #9E9E9E
#0A0A0A
- #242424
- #F5F5F5
+ #424242
+ #E0E0E0
- #B84A0D
+ #FF7043
#FFFFFF
- #1C0A00
- #E6B89C
+ #4E1700
+ #FFCCBC
#CF6679
#1C0006
- #4A0011
- #FFB3BA
+ #93000A
+ #FFDAD6
- #0A0A0A
+ #121212
- #F5F5F5
+ #E0E0E0
- #141414
- #F5F5F5
+ #1E1E1E
+ #E0E0E0
- #1C1C1C
- #9A9A9A
+ #2C2C2C
+ #BDBDBD
- #2E2E2E
+ #616161
- #0A0A0A
- #141414
- #1C1C1C
- #242424
+ #E64A19
+ #1E1E1E
+ #2C2C2C
+ #333333
- #F5F5F5
- #9A9A9A
- #5A5A5A
+ #E0E0E0
+ #BDBDBD
+ #757575
- #D35400
- #C24E00
- #1AD35400
+ #E64A19
+ #BF360C
+ #1AE64A19
- #141414
- #F5F5F5
+ #E64A19
+ #FFFFFF
- #141414
- #D35400
- #6A6A6A
+ #1E1E1E
+ #E64A19
+ #757575
#4CAF50
- #F44336
+ #EF5350
#FF9800
- #5A5A5A
+ #757575
#1AFFFFFF
#99000000
- #1C1C1C
+ #212121
+ #212121
-
\ No newline at end of file
+
diff --git a/sensorhub-android-app/res/values/styles.xml b/sensorhub-android-app/res/values/styles.xml
index f7d52d6c..1e68ed1b 100644
--- a/sensorhub-android-app/res/values/styles.xml
+++ b/sensorhub-android-app/res/values/styles.xml
@@ -4,12 +4,11 @@
-
+
+
+
@@ -77,8 +91,7 @@
- 16dp
- 0dp
- @color/surface_card
- - @color/md_theme_outline
- - 1dp
+ - 0dp
@@ -214,6 +228,8 @@
- bold
- sans-serif-medium
- 16sp
+ - 10dp
+
-
\ No newline at end of file
diff --git a/sensorhub-android-app/res/xml/pref_sensors.xml b/sensorhub-android-app/res/xml/pref_sensors.xml
index 0aab7631..4ac76b84 100644
--- a/sensorhub-android-app/res/xml/pref_sensors.xml
+++ b/sensorhub-android-app/res/xml/pref_sensors.xml
@@ -11,6 +11,7 @@
android:singleLine="true"
android:title="@string/pref_uid_extension"
android:summary="@string/summary_uid_extension"
+ app:useSimpleSummaryProvider="true"
android:layout="@layout/preference_item" />
diff --git a/sensorhub-android-app/res/xml/pref_settings.xml b/sensorhub-android-app/res/xml/pref_settings.xml
index 4394087b..6a30d18a 100644
--- a/sensorhub-android-app/res/xml/pref_settings.xml
+++ b/sensorhub-android-app/res/xml/pref_settings.xml
@@ -1,28 +1,26 @@
+ xmlns:app="http://schemas.android.com/apk/res-auto">
-
-
+
+
+ android:selectAllOnFocus="true"
+ app:useSimpleSummaryProvider="true"
+ android:layout="@layout/pref_value_single" />
-
-
-
+
+ android:layout="@layout/pref_switch_top" />
+ android:layout="@layout/pref_switch_mid" />
+ android:layout="@layout/pref_switch_bottom" />
+ android:layout="@layout/pref_edit_bottom" />
-
-
\ No newline at end of file
+
diff --git a/sensorhub-android-app/src/org/sensorhub/android/DashboardFragment.java b/sensorhub-android-app/src/org/sensorhub/android/DashboardFragment.java
index a4b15456..86350ec9 100644
--- a/sensorhub-android-app/src/org/sensorhub/android/DashboardFragment.java
+++ b/sensorhub-android-app/src/org/sensorhub/android/DashboardFragment.java
@@ -14,16 +14,19 @@
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.ImageButton;
+import android.widget.ImageView;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.card.MaterialCardView;
+import com.google.android.material.chip.Chip;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.textfield.TextInputEditText;
@@ -36,6 +39,10 @@
import android.widget.Toast;
import androidx.core.content.ContextCompat;
+import androidx.transition.AutoTransition;
+import androidx.transition.TransitionManager;
+
+import net.opengis.swe.v20.DataBlock;
import org.sensorhub.api.command.CommandData;
import org.sensorhub.api.command.IStreamingControlInterface;
@@ -55,6 +62,8 @@
import org.sensorhub.impl.sensor.meshtastic.control.TextMessageControl;
import org.sensorhub.impl.service.consys.client.ConSysApiClientModule;
+import java.io.IOException;
+
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
@@ -64,11 +73,11 @@
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.Flow;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import android.widget.LinearLayout;
-import net.opengis.swe.v20.DataBlock;
-
public class DashboardFragment extends Fragment implements TextureView.SurfaceTextureListener, Flow.Subscriber
{
@@ -82,7 +91,6 @@ public class DashboardFragment extends Fragment implements TextureView.SurfaceTe
private LinearLayout videoControlsOverlay;
private int currentZoomLevel = 0;
private MaterialCardView meshtasticCard;
-
private View videoStatusDot;
private FloatingActionButton fab;
private LinearLayout serverStatusContainer;
@@ -95,6 +103,34 @@ public class DashboardFragment extends Fragment implements TextureView.SurfaceTe
private final Map serverCardViews = new HashMap<>();
private final Set expandedServers = new HashSet<>();
+ private final Set expandedSensors = new HashSet<>();
+
+ private static class DataStreamStatus {
+ final String outputName;
+ final String statusText;
+ final int statusColor;
+ final boolean isOk;
+
+ DataStreamStatus(String outputName, String statusText, int statusColor, boolean isOk) {
+ this.outputName = outputName;
+ this.statusText = statusText;
+ this.statusColor = statusColor;
+ this.isOk = isOk;
+ }
+ }
+
+ private static class SensorGroupInfo {
+ final String sensorId;
+ final String sensorName;
+ final java.util.List streams = new java.util.ArrayList<>();
+ boolean allOk = true;
+ boolean hasError = false;
+
+ SensorGroupInfo(String sensorId, String sensorName) {
+ this.sensorId = sensorId;
+ this.sensorName = sensorName;
+ }
+ }
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
@@ -197,18 +233,21 @@ private void hideFabProgress() {
}
private void stopHub() {
+ Toast.makeText(requireContext(), "Stopping SensorHub", Toast.LENGTH_SHORT).show();
showFabProgress();
newStatusMessage(getString(R.string.stopping_sensorhub));
+
stopRefreshingStatus();
hideVideoPreview();
clearTextureView();
videoStatusCard.setVisibility(View.GONE);
if (videoControlsOverlay != null) videoControlsOverlay.setVisibility(View.GONE);
currentZoomLevel = 0;
+ if (btnFlipCamera != null) btnFlipCamera.setVisibility(View.GONE);
+ if (meshtasticCard != null) meshtasticCard.setVisibility(View.GONE);
+ requireActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
if (meshtasticCard != null) meshtasticCard.setVisibility(View.GONE);
provider.stopSensorHub();
-
- // Brief delay to let the service finish stopping on its background thread
displayHandler.postDelayed(() -> {
if (!isAdded()) return;
hideFabProgress();
@@ -317,7 +356,6 @@ private void pollHubReady() {
if (!provider.isOshStarted()) {
provider.setOshStarted(true);
hideFabProgress();
- updateFabIcon();
serverStatusContainer.removeAllViews();
serverCardViews.clear();
startRefreshingStatus();
@@ -329,8 +367,7 @@ private void pollHubReady() {
} else if (hubPollAttempts < HUB_POLL_MAX_ATTEMPTS) {
displayHandler.postDelayed(this::pollHubReady, HUB_POLL_INTERVAL_MS);
} else {
- hideFabProgress();
- newStatusMessage(getString(R.string.sensorhub_start_failed));
+ newStatusMessage("SensorHub failed to start");
updateFabIcon();
}
}
@@ -373,7 +410,8 @@ protected synchronized void displayStatus() {
String clientMode = "SOS-T";
Map dataStreams = client.getDataStreams();
- StringBuffer detailHtml = new StringBuffer();
+ String errorText = null;
+ String statusMsg = null;
boolean hasError = false;
if (client.getCurrentError() != null) {
@@ -383,35 +421,18 @@ protected synchronized void displayStatus() {
if (!errorMsg.endsWith(".")) errorMsg += ". ";
if (errorObj.getCause() != null && errorObj.getCause().getMessage() != null)
errorMsg += errorObj.getCause().getMessage();
- detailHtml.append("" + errorMsg + "
");
+ errorText = errorMsg;
}
if (dataStreams.isEmpty() && client.getStatusMessage() != null) {
- detailHtml.append(client.getStatusMessage() + "
");
+ statusMsg = client.getStatusMessage();
}
long now = System.currentTimeMillis();
boolean allOk = !hasError && !dataStreams.isEmpty();
- for (Entry stream : dataStreams.entrySet()) {
- detailHtml.append("" + stream.getKey() + " : ");
- long lastEventTime = stream.getValue().lastEventTime;
- long dt = now - lastEventTime;
- if (lastEventTime == Long.MIN_VALUE) {
- detailHtml.append("NO OBS");
- allOk = false;
- } else if (dt > stream.getValue().measPeriodMs) {
- detailHtml.append("NOK (" + dt + "ms ago)");
- allOk = false;
- } else {
- detailHtml.append("OK (" + dt + "ms ago)");
- }
- if (stream.getValue().errorCount > 0) {
- detailHtml.append(" (" + stream.getValue().errorCount + ")");
- allOk = false;
- }
- detailHtml.append("
");
- }
+ java.util.List sensorGroups = buildSensorGroups(dataStreams, now);
+ for (SensorGroupInfo g : sensorGroups) { if (!g.allOk) allOk = false; if (g.hasError) hasError = true; }
- updateServerCard(clientId, serverName, clientMode, allOk, hasError, detailHtml.toString());
+ updateServerCard(clientId, serverName, clientMode, allOk, hasError, errorText, statusMsg, sensorGroups);
}
for (ConSysApiClientModule client : provider.getConSysClients()) {
@@ -421,7 +442,8 @@ protected synchronized void displayStatus() {
String clientMode = "Connected Systems";
Map dataStreams = client.getDataStreams();
- StringBuffer detailHtml = new StringBuffer();
+ String errorText = null;
+ String statusMsg = null;
boolean hasError = false;
if (client.getCurrentError() != null) {
@@ -431,35 +453,18 @@ protected synchronized void displayStatus() {
if (!errorMsg.endsWith(".")) errorMsg += ". ";
if (errorObj.getCause() != null && errorObj.getCause().getMessage() != null)
errorMsg += errorObj.getCause().getMessage();
- detailHtml.append("" + errorMsg + "
");
+ errorText = errorMsg;
}
if (dataStreams.isEmpty() && client.getStatusMessage() != null) {
- detailHtml.append(client.getStatusMessage() + "
");
+ statusMsg = client.getStatusMessage();
}
long now = System.currentTimeMillis();
boolean allOk = !hasError && !dataStreams.isEmpty();
- for (Entry stream : dataStreams.entrySet()) {
- detailHtml.append("" + stream.getKey() + " : ");
- long lastEventTime = stream.getValue().lastEventTime;
- long dt = now - lastEventTime;
- if (lastEventTime == Long.MIN_VALUE) {
- detailHtml.append("NO OBS");
- allOk = false;
- } else if (dt > stream.getValue().measPeriodMs) {
- detailHtml.append("NOK (" + dt + "ms ago)");
- allOk = false;
- } else {
- detailHtml.append("OK (" + dt + "ms ago)");
- }
- if (stream.getValue().errorCount > 0) {
- detailHtml.append(" (" + stream.getValue().errorCount + ")");
- allOk = false;
- }
- detailHtml.append("
");
- }
+ java.util.List sensorGroups = buildSensorGroups(dataStreams, now);
+ for (SensorGroupInfo g : sensorGroups) { if (!g.allOk) allOk = false; if (g.hasError) hasError = true; }
- updateServerCard(clientId, serverName, clientMode, allOk, hasError, detailHtml.toString());
+ updateServerCard(clientId, serverName, clientMode, allOk, hasError, errorText, statusMsg, sensorGroups);
}
Set staleIds = new HashSet<>(serverCardViews.keySet());
@@ -526,6 +531,107 @@ protected synchronized void newStatusMessage(String msg) {
});
}
+ private String extractSensorId(String streamKey) {
+ Matcher m = Pattern.compile("systems/([^/]+)/").matcher(streamKey);
+ if (m.find()) return m.group(1);
+ return streamKey;
+ }
+
+ private String formatSensorName(String sensorId) {
+ String[] parts = sensorId.replace("urn:", "").split(":");
+ String name;
+ if (parts.length >= 3) {
+ name = parts[parts.length - 2];
+ } else if (parts.length == 2) {
+ name = parts[0];
+ } else {
+ name = sensorId;
+ }
+ name = name.replace("_", " ").replace("-", " ");
+ return name.substring(0, 1).toUpperCase() + name.substring(1); //capitalizes first letter
+ }
+
+ private String formatOutputName(String streamKey) {
+ Matcher m = Pattern.compile("outputs/([^/]+)").matcher(streamKey);
+ String raw;
+ if (m.find()) {
+ raw = m.group(1);
+ } else {
+ raw = streamKey;
+ }
+ raw = raw.replaceAll("(?i)_data$", "");
+ raw = raw.replace("_", " ").replace("-", " ");
+ return raw.substring(0, 1).toUpperCase() + raw.substring(1); //capitalizes first letter
+ }
+
+
+ private interface StreamAccessor {
+ long getLastEventTime(T info);
+ long getMeasPeriodMs(T info);
+ int getErrorCount(T info);
+ }
+
+ private java.util.List buildSensorGroups(Map dataStreams, long now) {
+ StreamAccessor