diff --git a/.gitignore b/.gitignore index d4c3a57e..55e62e88 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ .cxx local.properties /.idea/ +secrets.properties diff --git a/app/src/main/java/com/openpositioning/PositionMe/Traj.java b/app/src/main/java/com/openpositioning/PositionMe/Traj.java index 7925fa55..8700e361 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/Traj.java +++ b/app/src/main/java/com/openpositioning/PositionMe/Traj.java @@ -1,3 +1,42 @@ +/** + * This class represents the Trajectory data structure generated from the Protocol Buffers definition. + * It contains various sensor data samples and metadata related to a trajectory data collection event. + * + *

Key Features:

+ * + * + *

Field Descriptions:

+ * + * + *

Note:

+ * + */ package com.openpositioning.PositionMe;// Generated by the protocol buffer compiler. DO NOT EDIT! // source: Cloud/app/src/main/proto/traj.proto diff --git a/app/src/main/java/com/openpositioning/PositionMe/data/local/TrajParser.java b/app/src/main/java/com/openpositioning/PositionMe/data/local/TrajParser.java index 2d2b1cbf..04e7d357 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/data/local/TrajParser.java +++ b/app/src/main/java/com/openpositioning/PositionMe/data/local/TrajParser.java @@ -4,6 +4,9 @@ import android.hardware.SensorManager; import android.util.Log; +import com.android.volley.RequestQueue; +import com.android.volley.toolbox.JsonObjectRequest; +import com.android.volley.toolbox.Volley; import com.google.android.gms.maps.model.LatLng; import com.google.gson.Gson; import com.google.gson.JsonArray; @@ -11,6 +14,10 @@ import com.google.gson.JsonParser; import com.openpositioning.PositionMe.presentation.fragment.ReplayFragment; import com.openpositioning.PositionMe.sensors.SensorFusion; +import com.openpositioning.PositionMe.sensors.WiFiPositioning; +import com.android.volley.Request; +import org.json.JSONException; +import org.json.JSONObject; import java.io.BufferedReader; import java.io.File; @@ -19,6 +26,8 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; /** * Handles parsing of trajectory data stored in JSON files, combining IMU, PDR, and GNSS data @@ -46,7 +55,7 @@ * * @see ReplayFragment which uses parsed trajectory data for visualization. * @see SensorFusion for motion processing and sensor integration. - * @see com.openpositioning.PositionMe.presentation.fragment.ReplayFragment for implementation details. + * @see ReplayFragment for implementation details. * * @author Shu Gu * @author Lin Cheng @@ -55,13 +64,17 @@ public class TrajParser { private static final String TAG = "TrajParser"; + private WiFiPositioning wiFiPositioning; + /** - * Represents a single replay point containing estimated PDR position, GNSS location, + * Represents a single replay point containing estimated PDR position, GNSS location, WiFi location * orientation, speed, and timestamp. */ public static class ReplayPoint { + public LatLng wifiLocation; public LatLng pdrLocation; // PDR-derived location estimate public LatLng gnssLocation; // GNSS location (may be null if unavailable) + public LatLng wifiLocation; // WiFi location (may be null if unavailable) public float orientation; // Orientation in degrees public float speed; // Speed in meters per second public long timestamp; // Relative timestamp @@ -75,9 +88,10 @@ public static class ReplayPoint { * @param speed The speed in meters per second. * @param timestamp The timestamp associated with this point. */ - public ReplayPoint(LatLng pdrLocation, LatLng gnssLocation, float orientation, float speed, long timestamp) { + public ReplayPoint(LatLng pdrLocation, LatLng gnssLocation, LatLng wifiLocation, float orientation, float speed, long timestamp) { this.pdrLocation = pdrLocation; this.gnssLocation = gnssLocation; + this.wifiLocation = wifiLocation;//更改构造函数,添加wifiLocation参数 this.orientation = orientation; this.speed = speed; this.timestamp = timestamp; @@ -104,6 +118,17 @@ private static class GnssRecord { public double latitude, longitude; // GNSS coordinates } + private static class MacScan { + public String mac; + public double rssi; + public int timestamp; + } + + private static class WiFiRecord{ + public long relativeTimestamp; + public List macScans; + } + /** * Parses trajectory data from a JSON file and reconstructs a list of replay points. * @@ -184,8 +209,80 @@ public static List parseTrajectoryData(String filePath, Context con GnssRecord closestGnss = findClosestGnssRecord(gnssList, pdr.relativeTimestamp); LatLng gnssLocation = closestGnss != null ? new LatLng(closestGnss.latitude, closestGnss.longitude) : null; + WifiRecord closestWifi = findClosestWifiRecord(wifiList, pdr.relativeTimestamp); + LatLng wifiLocation = closestWifi != null ? + new LatLng(closestWifi.latitude, closestWifi.longitude) : null; + + WiFiRecord closestWiFi = findClosestWiFiRecord(WiFiList, pdr.relativeTimestamp); + AtomicReference wifilocation = new AtomicReference<>(); + AtomicInteger floor = new AtomicInteger(); + try { + // Creating a JSON object to store the WiFi access points + JSONObject wifiAccessPoints = new JSONObject(); + for (MacScan scan : closestWiFi.macScans) { + wifiAccessPoints.put(scan.mac, scan.rssi); + } + // Creating POST Request + JSONObject wifiFingerPrint = new JSONObject(); + wifiFingerPrint.put("wf", wifiAccessPoints); + JsonObjectRequest jsonObjectRequest = new JsonObjectRequest( + Request.Method.POST, "https://openpositioning.org/api/position/fine", wifiFingerPrint, + // Parses the response to obtain the WiFi location and WiFi floor + response -> { + try { + wifilocation.set(new LatLng(response.getDouble("lat"), response.getDouble("lon"))); + floor.set(response.getInt("floor")); + } catch (JSONException e) { + // Error log to keep record of errors (for secure programming and maintainability) + Log.e("jsonErrors", "Error parsing response: " + e.getMessage() + " " + response); + } + // Block return when a message is received + synchronized (TrajParser.class) { + TrajParser.class.notify(); + } + }, + // Handles the errors obtained from the POST request + error -> { + // Validation Error + if (error.networkResponse != null && error.networkResponse.statusCode == 422) { + Log.e("WiFiPositioning", "Validation Error " + error.getMessage()); + } + // Other Errors + else { + // When Response code is available + if (error.networkResponse != null) { + Log.e("WiFiPositioning", "Response Code: " + error.networkResponse.statusCode + ", " + error.getMessage()); + } else { + Log.e("WiFiPositioning", "Error message: " + error.getMessage()); + } + } + // Block return when an error occurs + synchronized (TrajParser.class) { + TrajParser.class.notify(); + } + } + ); + + // Add the request to the RequestQueue + RequestQueue requestQueue = Volley.newRequestQueue(context); + requestQueue.add(jsonObjectRequest); + + } catch (JSONException e) { + // Catching error while making JSON object, to prevent crashes + // Error log to keep record of errors (for secure programming and maintainability) + Log.e("jsonErrors","Error creating json object"+e.toString()); + } + + // Wait for the response or error to be processed + synchronized (TrajParser.class) { + try { + TrajParser.class.wait(1000); // Sleep for 1 second + } catch (InterruptedException e) { + Log.e(TAG, "Thread interrupted while waiting for WiFi positioning response", e); + } + } - result.add(new ReplayPoint(pdrLocation, gnssLocation, orientationDeg, + result.add(new ReplayPoint(pdrLocation, gnssLocation, wifilocation.get(), orientationDeg, 0f, pdr.relativeTimestamp)); } @@ -229,17 +326,31 @@ private static List parseGnssData(JsonArray gnssArray) { gnssList.add(record); } return gnssList; -}/** Finds the closest IMU record to the given timestamp. */ +}/** + * Finds the closest IMU record to the given timestamp. + */ +private static List parseWiFiData(JsonArray WiFiArray) { + List WiFiList = new ArrayList<>(); + if (WiFiArray == null) return WiFiList; + Gson gson = new Gson(); + for (int i = 0; i < WiFiArray.size(); i++) { + WiFiRecord record = gson.fromJson(WiFiArray.get(i), WiFiRecord.class); + WiFiList.add(record); + } + return WiFiList; +} private static ImuRecord findClosestImuRecord(List imuList, long targetTimestamp) { return imuList.stream().min(Comparator.comparingLong(imu -> Math.abs(imu.relativeTimestamp - targetTimestamp))) .orElse(null); - }/** Finds the closest GNSS record to the given timestamp. */ private static GnssRecord findClosestGnssRecord(List gnssList, long targetTimestamp) { return gnssList.stream().min(Comparator.comparingLong(gnss -> Math.abs(gnss.relativeTimestamp - targetTimestamp))) .orElse(null); - }/** Computes the orientation from a rotation vector. */ +private static WiFiRecord findClosestWiFiRecord(List WiFiList, long targetTimestamp) { + return WiFiList.stream().min(Comparator.comparingLong(wifi -> Math.abs(wifi.relativeTimestamp - targetTimestamp))) + .orElse(null); +} private static float computeOrientationFromRotationVector(float rx, float ry, float rz, float rw, Context context) { float[] rotationVector = new float[]{rx, ry, rz, rw}; float[] rotationMatrix = new float[9]; diff --git a/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/RecordingFragment.java b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/RecordingFragment.java index 6362a971..5dfccd0d 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/RecordingFragment.java +++ b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/RecordingFragment.java @@ -265,6 +265,16 @@ private void updateUIandPosition() { } } + // WiFi location example + //sensorFusion.getWifiFloor() // Returns the current floor the user is on using WiFi positioning. + if(trajectoryMapFragment.isWiFiLocationOn()){ + LatLng wifiLocation = sensorFusion.getLatLngWifiPositioning(); // Returns the user's position obtained using WiFi positioning. + if(wifiLocation != null) { + trajectoryMapFragment.updateWifiLocation(wifiLocation); + } + } + //sensorFusion.getWifiList() // Returns the most recent list of WiFi names and levels. + // Update previous previousPosX = pdrValues[0]; previousPosY = pdrValues[1]; diff --git a/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/ReplayFragment.java b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/ReplayFragment.java index d15a4a83..86dbc269 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/ReplayFragment.java +++ b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/ReplayFragment.java @@ -18,6 +18,7 @@ import com.openpositioning.PositionMe.R; import com.openpositioning.PositionMe.presentation.activity.ReplayActivity; import com.openpositioning.PositionMe.data.local.TrajParser; +import com.openpositioning.PositionMe.sensors.WiFiPositioning; import java.io.File; import java.util.ArrayList; @@ -65,6 +66,7 @@ public class ReplayFragment extends Fragment { private int currentIndex = 0; private boolean isPlaying = false; + @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -134,18 +136,22 @@ public void onViewCreated(@NonNull View view, // 1) Check if the file contains any GNSS data - boolean gnssExists = hasAnyGnssData(replayData); +// boolean gnssExists = hasAnyGnssData(replayData); +// +// if (gnssExists) { +// showGnssChoiceDialog(); +// } else { +// // No GNSS data -> automatically use param lat/lon +// if (initialLat != 0f || initialLon != 0f) { +// LatLng startPoint = new LatLng(initialLat, initialLon); +// Log.i(TAG, "Setting initial map position: " + startPoint.toString()); +// trajectoryMapFragment.setInitialCameraPosition(startPoint); +// } +// } + // Set initial map position + setupInitialMapPosition(); + - if (gnssExists) { - showGnssChoiceDialog(); - } else { - // No GNSS data -> automatically use param lat/lon - if (initialLat != 0f || initialLon != 0f) { - LatLng startPoint = new LatLng(initialLat, initialLon); - Log.i(TAG, "Setting initial map position: " + startPoint.toString()); - trajectoryMapFragment.setInitialCameraPosition(startPoint); - } - } // Initialize UI controls playPauseButton = view.findViewById(R.id.playPauseButton); @@ -234,14 +240,16 @@ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { /** * Checks if any ReplayPoint contains a non-null gnssLocation. */ - private boolean hasAnyGnssData(List data) { - for (TrajParser.ReplayPoint point : data) { - if (point.gnssLocation != null) { - return true; - } - } - return false; - } +// private boolean hasAnyGnssData(List data) { +// for (TrajParser.ReplayPoint point : data) { +// if (point.gnssLocation != null) { +// return true; +// } +// } +// return false; +// } + + /** @@ -249,44 +257,74 @@ private boolean hasAnyGnssData(List data) { * 1) GNSS from file * 2) Lat/Lon from ReplayActivity arguments */ - private void showGnssChoiceDialog() { - new AlertDialog.Builder(requireContext()) - .setTitle("Choose Starting Location") - .setMessage("GNSS data is found in the file. Would you like to use the file's GNSS as the start, or the one you manually picked?") - .setPositiveButton("Use File's GNSS", (dialog, which) -> { - LatLng firstGnss = getFirstGnssLocation(replayData); - if (firstGnss != null) { - setupInitialMapPosition((float) firstGnss.latitude, (float) firstGnss.longitude); - } else { - // Fallback if no valid GNSS found - setupInitialMapPosition(initialLat, initialLon); - } - dialog.dismiss(); - }) - .setNegativeButton("Use Manual Set", (dialog, which) -> { - setupInitialMapPosition(initialLat, initialLon); - dialog.dismiss(); - }) - .setCancelable(false) - .show(); - } - private void setupInitialMapPosition(float latitude, float longitude) { - LatLng startPoint = new LatLng(initialLat, initialLon); +// private void showGnssChoiceDialog() { +// new AlertDialog.Builder(requireContext()) +// .setTitle("Choose Starting Location") +// .setMessage("GNSS data is found in the file. Would you like to use the file's GNSS as the start, or the one you manually picked?") +// .setPositiveButton("Use File's GNSS", (dialog, which) -> { +// LatLng firstGnss = getFirstGnssLocation(replayData); +// if (firstGnss != null) { +// setupInitialMapPosition((float) firstGnss.latitude, (float) firstGnss.longitude); +// } else { +// // Fallback if no valid GNSS found +// setupInitialMapPosition(initialLat, initialLon); +// } +// dialog.dismiss(); +// }) +// .setNegativeButton("Use Manual Set", (dialog, which) -> { +// setupInitialMapPosition(initialLat, initialLon); +// dialog.dismiss(); +// }) +// .setCancelable(false) +// .show(); +// } + + +// private void setupInitialMapPosition(float latitude, float longitude) { +// LatLng startPoint = new LatLng(initialLat, initialLon); +// Log.i(TAG, "Setting initial map position: " + startPoint.toString()); +// trajectoryMapFragment.setInitialCameraPosition(startPoint); +// } + private void setupInitialMapPosition() { + LatLng startPoint = getFirstGnssLocation(replayData); + if (startPoint == null) { + startPoint = getFirstWifiLocation(replayData); + } + if (startPoint == null) { + startPoint = new LatLng(initialLat, initialLon); + } Log.i(TAG, "Setting initial map position: " + startPoint.toString()); trajectoryMapFragment.setInitialCameraPosition(startPoint); } + /** * Retrieve the first available GNSS location from the replay data. */ +// private LatLng getFirstGnssLocation(List data) { +// for (TrajParser.ReplayPoint point : data) { +// if (point.gnssLocation != null) { +// return new LatLng(replayData.get(0).gnssLocation.latitude, replayData.get(0).gnssLocation.longitude); +// } +// } +// return null; // None found +// } private LatLng getFirstGnssLocation(List data) { for (TrajParser.ReplayPoint point : data) { if (point.gnssLocation != null) { - return new LatLng(replayData.get(0).gnssLocation.latitude, replayData.get(0).gnssLocation.longitude); + return point.gnssLocation; } } - return null; // None found + return null; + } + private LatLng getFirstWifiLocation(List data) { + for (TrajParser.ReplayPoint point : data) { + if (point.wifiLocation != null) { + return point.wifiLocation; + } + } + return null; } @@ -337,6 +375,10 @@ private void updateMapForIndex(int newIndex) { if (p.gnssLocation != null) { trajectoryMapFragment.updateGNSS(p.gnssLocation); } + // 添加 WiFi 定位更新 + if (p.wifiLocation != null) { + trajectoryMapFragment.updateWifiLocation(p.wifiLocation); + } } } else { // Normal sequential forward step: add just the new point @@ -345,6 +387,10 @@ private void updateMapForIndex(int newIndex) { if (p.gnssLocation != null) { trajectoryMapFragment.updateGNSS(p.gnssLocation); } + //添加 WiFi 定位更新 + if (p.wifiLocation != null) { + trajectoryMapFragment.updateWifiLocation(p.wifiLocation); + } } lastIndex = newIndex; diff --git a/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/TrajectoryMapFragment.java b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/TrajectoryMapFragment.java index eb0bad65..245d4616 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/TrajectoryMapFragment.java +++ b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/TrajectoryMapFragment.java @@ -57,12 +57,16 @@ public class TrajectoryMapFragment extends Fragment { private LatLng currentLocation; // Stores the user's current location private Marker orientationMarker; // Marker representing user's heading private Marker gnssMarker; // GNSS position marker + private Marker wifiMarker; private Polyline polyline; // Polyline representing user's movement path private boolean isRed = true; // Tracks whether the polyline color is red private boolean isGnssOn = false; // Tracks if GNSS tracking is enabled + private boolean isWiFiLocationOn = false; private Polyline gnssPolyline; // Polyline for GNSS path + private Polyline wifiPolyline; // Polyline for WiFi path private LatLng lastGnssLocation = null; // Stores the last GNSS location + private LatLng lastwifiLocation = null; private LatLng pendingCameraPosition = null; // Stores pending camera movement private boolean hasPendingCameraMove = false; // Tracks if camera needs to move @@ -75,6 +79,7 @@ public class TrajectoryMapFragment extends Fragment { private Spinner switchMapSpinner; private SwitchMaterial gnssSwitch; + private SwitchMaterial WiFiLoactionSwitch; private SwitchMaterial autoFloorSwitch; private com.google.android.material.floatingactionbutton.FloatingActionButton floorUpButton, floorDownButton; @@ -103,6 +108,7 @@ public void onViewCreated(@NonNull View view, // Grab references to UI controls switchMapSpinner = view.findViewById(R.id.mapSwitchSpinner); gnssSwitch = view.findViewById(R.id.gnssSwitch); + WiFiLoactionSwitch = view.findViewById(R.id.wifiSwitch); autoFloorSwitch = view.findViewById(R.id.autoFloor); floorUpButton = view.findViewById(R.id.floorUpButton); floorDownButton = view.findViewById(R.id.floorDownButton); @@ -151,6 +157,15 @@ public void onMapReady(@NonNull GoogleMap googleMap) { } }); + // wifi location display做了一个wifi开启的开关,可以开启或关闭使用wifi + WiFiLoactionSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { + isWiFiLocationOn = isChecked; + if (!isChecked && wifiMarker != null) { + wifiMarker.remove(); + wifiMarker = null; + } + }); + // Color switch switchColorButton.setOnClickListener(v -> { if (polyline != null) { @@ -226,6 +241,12 @@ private void initMapSettings(GoogleMap map) { .width(5f) .add() // start empty ); + // WiFi path in yellow + wifiPolyline = map.addPolyline(new PolylineOptions() + .color(Color.YELLOW) + .width(5f) + .add() // start empty + ); } @@ -388,6 +409,34 @@ public void updateGNSS(@NonNull LatLng gnssLocation) { } } + /** + * Set/update WiFi position based on GNSS + */ + public void updateWifiLocation(@NonNull LatLng wifiLocation) { + if (gMap == null) return; + if (!isWiFiLocationOn) return; + + // Create a marker for WiFi location + if (wifiMarker == null){ + wifiMarker = gMap.addMarker(new MarkerOptions() + .position(wifiLocation) + .title("WiFi Position") + .icon(BitmapDescriptorFactory + .defaultMarker(BitmapDescriptorFactory.HUE_ORANGE))); + lastwifiLocation = wifiLocation; + } else { + // Move existing WiFi marker + wifiMarker.setPosition(wifiLocation); + + // Add a segment to the yellow WiFi line, if this is a new location + if (lastwifiLocation != null && !lastwifiLocation.equals(wifiLocation)) { + List wifiPoints = new ArrayList<>(wifiPolyline.getPoints()); + wifiPoints.add(wifiLocation); + wifiPolyline.setPoints(wifiPoints); + } + lastwifiLocation = wifiLocation; + } + } /** * Remove GNSS marker if user toggles it off @@ -406,6 +455,10 @@ public boolean isGnssEnabled() { return isGnssOn; } + public boolean isWiFiLocationOn(){ + return isWiFiLocationOn; + } + private void setFloorControlsVisibility(int visibility) { floorUpButton.setVisibility(visibility); floorDownButton.setVisibility(visibility); @@ -421,6 +474,10 @@ public void clearMapAndReset() { gnssPolyline.remove(); gnssPolyline = null; } + if (wifiPolyline != null) { + wifiPolyline.remove(); + wifiPolyline = null; + } if (orientationMarker != null) { orientationMarker.remove(); orientationMarker = null; @@ -430,6 +487,7 @@ public void clearMapAndReset() { gnssMarker = null; } lastGnssLocation = null; + lastwifiLocation = null; currentLocation = null; // Re-create empty polylines with your chosen colors @@ -442,6 +500,10 @@ public void clearMapAndReset() { .color(Color.BLUE) .width(5f) .add()); + wifiPolyline = gMap.addPolyline(new PolylineOptions() + .color(Color.YELLOW) + .width(5f) + .add()); } } diff --git a/app/src/main/java/com/openpositioning/PositionMe/sensors/SensorFusion.java b/app/src/main/java/com/openpositioning/PositionMe/sensors/SensorFusion.java index 6eca847c..dd1efb7e 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/sensors/SensorFusion.java +++ b/app/src/main/java/com/openpositioning/PositionMe/sensors/SensorFusion.java @@ -52,10 +52,37 @@ *

* The class provides a number of setters and getters so that other classes can have access to the * sensor data and influence the behaviour of data collection. - * + * + *

+ * functions in this class: + *

    + *
  • {@link #getInstance()} - Returns the singleton instance of SensorFusion.
  • + *
  • {@link #setContext(Context)} - Initializes the SensorFusion instance with the application context.
  • + *
  • {@link #resumeListening()} - Registers all device listeners and starts data collection.
  • + *
  • {@link #stopListening()} - Unregisters all device listeners and pauses data collection.
  • + *
  • {@link #startRecording()} - Enables saving sensor values to the trajectory object.
  • + *
  • {@link #stopRecording()} - Disables saving sensor values to the trajectory object.
  • + *
  • {@link #sendTrajectoryToCloud()} - Sends the trajectory object to the server.
  • + *
  • {@link #getGNSSLatitude(boolean)} - Returns the GNSS latitude and longitude data.
  • + *
  • {@link #setStartGNSSLatitude(float[])} - Sets the initial GNSS latitude and longitude data.
  • + *
  • {@link #redrawPath(float)} - Redraws the path in the corrections fragment.
  • + *
  • {@link #passAverageStepLength()} - Returns the average step length calculated by PDR.
  • + *
  • {@link #passOrientation()} - Returns the current device orientation.
  • + *
  • {@link #getSensorValueMap()} - Returns a map of the most recent sensor readings.
  • + *
  • {@link #getWifiList()} - Returns the most recent list of WiFi names and levels.
  • + *
  • {@link #getSensorInfos()} - Returns information about all the sensors registered in SensorFusion.
  • + *
  • {@link #registerForServerUpdate(Observer)} - Registers an observer to receive updates from the server.
  • + *
  • {@link #getElevation()} - Returns the estimated elevation value in meters.
  • + *
  • {@link #getElevator()} - Returns whether the user is currently in an elevator.
  • + *
  • {@link #getHoldMode()} - Estimates the phone's position based on proximity and light sensors.
  • + *
  • {@link #getLatLngWifiPositioning()} - Returns the user's position obtained using WiFi positioning.
  • + *
  • {@link #getWifiFloor()} - Returns the current floor the user is on using WiFi positioning.
  • + *
+ * * @author Michal Dvorak * @author Mate Stodulka * @author Virginia Cangelosi + * @author JackShenYt */ public class SensorFusion implements SensorEventListener, Observer { @@ -159,6 +186,15 @@ public class SensorFusion implements SensorEventListener, Observer { // WiFi positioning object private WiFiPositioning wiFiPositioning; + //km filter + private final Timer fuseTimer = new Timer(); + + // Add gyroOrientation and accMagOrientation arrays + private final float[] gyroOrientation = new float[3]; + private final float[] accMagOrientation = new float[3]; + private float[] gyroMatrix = new float[9]; + private final float[] fusedOrientation = new float[3]; + //region Initialisation /** * Private constructor for implementing singleton design pattern for SensorFusion. @@ -191,8 +227,14 @@ private SensorFusion() { this.R = new float[9]; // GNSS initial Long-Lat array this.startLocation = new float[2]; - } + // Initialize gyroMatrix in the constructor + gyroOrientation[0] = 0.0f; + gyroOrientation[1] = 0.0f; + gyroOrientation[2] = 0.0f; + + gyroMatrix = new float[]{1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}; + } /** * Static function to access singleton instance of SensorFusion. @@ -259,6 +301,10 @@ public void setContext(Context context) { // Keep app awake during the recording (using stored appContext) PowerManager powerManager = (PowerManager) this.appContext.getSystemService(Context.POWER_SERVICE); wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyApp::MyWakelockTag"); + + //kalmanFilter service + fuseTimer.schedule(new calculateFusedOrientationTask(), 5000, 30); + Log.d("SensorFusionService", "Service started"); } //endregion @@ -284,11 +330,11 @@ public void onSensorChanged(SensorEvent sensorEvent) { if (lastTimestamp != null) { long timeGap = currentTime - lastTimestamp; -// // Log a warning if the time gap is larger than the threshold -// if (timeGap > LARGE_GAP_THRESHOLD_MS) { -// Log.e("SensorFusion", "Large time gap detected for sensor " + sensorType + -// " | Time gap: " + timeGap + " ms"); -// } + // // Log a warning if the time gap is larger than the threshold + // if (timeGap > LARGE_GAP_THRESHOLD_MS) { + // Log.e("SensorFusion", "Large time gap detected for sensor " + sensorType + + // " | Time gap: " + timeGap + " ms"); + // } } // Update timestamp and frequency counter for this sensor @@ -302,6 +348,7 @@ public void onSensorChanged(SensorEvent sensorEvent) { acceleration[0] = sensorEvent.values[0]; acceleration[1] = sensorEvent.values[1]; acceleration[2] = sensorEvent.values[2]; + calculateAccMagOrientation(); break; case Sensor.TYPE_PRESSURE: @@ -317,6 +364,8 @@ public void onSensorChanged(SensorEvent sensorEvent) { angularVelocity[0] = sensorEvent.values[0]; angularVelocity[1] = sensorEvent.values[1]; angularVelocity[2] = sensorEvent.values[2]; + gyroFunction(sensorEvent); + break; case Sensor.TYPE_LINEAR_ACCELERATION: filteredAcc[0] = sensorEvent.values[0]; @@ -330,12 +379,8 @@ public void onSensorChanged(SensorEvent sensorEvent) { Math.pow(filteredAcc[2], 2) ); this.accelMagnitude.add(accelMagFiltered); - -// // Debug logging -// Log.v("SensorFusion", -// "Added new linear accel magnitude: " + accelMagFiltered -// + "; accelMagnitude size = " + accelMagnitude.size()); - + // Debug logging + //Log.v("SensorFusion", "Added new linear accel magnitude: " + accelMagFiltered + "; accelMagnitude size = " + accelMagnitude.size()); elevator = pdrProcessing.estimateElevator(gravity, filteredAcc); break; @@ -396,7 +441,7 @@ public void onSensorChanged(SensorEvent sensorEvent) { float[] newCords = this.pdrProcessing.updatePdr( stepTime, this.accelMagnitude, - this.orientation[0] + this.fusedOrientation[0] //km filter ); // Clear the accelMagnitude after using it @@ -506,6 +551,7 @@ private void createWifiPositioningRequest(){ Log.e("jsonErrors","Error creating json object"+e.toString()); } } + // Callback Example Function /** * Function to create a request to obtain a wifi location for the obtained wifi fingerprint @@ -620,6 +666,32 @@ private float[] matrixMultiplication(float[] A, float[] B) { return result; } + /** + * KalmanFilter time irs + */ + class calculateFusedOrientationTask extends TimerTask { + private final kalmanfilter[] kalmanFilters = new kalmanfilter[3]; + + public calculateFusedOrientationTask() { + // Initialize one Kalman filter for each axis (yaw, pitch, roll) + for (int i = 0; i < 3; i++) { + kalmanFilters[i] = new kalmanfilter(); + } + } + + public void run() { + float dt = 0.03f; // Assume 33Hz update rate + + for (int i = 0; i < 3; i++) { + fusedOrientation[i] = kalmanFilters[i].update(accMagOrientation[i], gyroOrientation[i], dt); + } + + // Update gyro matrix with the fused orientation + gyroMatrix = getRotationMatrixFromOrientation(fusedOrientation); + System.arraycopy(fusedOrientation, 0, gyroOrientation, 0, 3); + } + } + /** * {@inheritDoc} */ @@ -968,10 +1040,10 @@ public void run() { .setMagY(magneticField[1]) .setMagZ(magneticField[2]) .setRelativeTimestamp(SystemClock.uptimeMillis()-bootTime)) -// .addGnssData(Traj.GNSS_Sample.newBuilder() -// .setLatitude(latitude) -// .setLongitude(longitude) -// .setRelativeTimestamp(SystemClock.uptimeMillis()-bootTime)) + // .addGnssData(Traj.GNSS_Sample.newBuilder() + // .setLatitude(latitude) + // .setLongitude(longitude) + // .setRelativeTimestamp(SystemClock.uptimeMillis()-bootTime)) ; // Divide timer with a counter for storing data every 1 second @@ -997,6 +1069,8 @@ public void run() { .setMac(currentWifi.getBssid()) .setSsid(currentWifi.getSsid()) .setFrequency(currentWifi.getFrequency())); + + //trajectory.addAllWifiData } else { secondCounter++; @@ -1011,4 +1085,50 @@ public void run() { //endregion + // Add calculateAccMagOrientation method + private void calculateAccMagOrientation() { + if (SensorManager.getRotationMatrix(R, null, acceleration, magneticField)) { + SensorManager.getOrientation(R, accMagOrientation); + } + } + + // Add gyroFunction method + private static final float EPSILON = 0.000000001f; + private void gyroFunction(SensorEvent event) { + if (gyroMatrix == null) return; + + float[] deltaVector = new float[4]; + if (lastEventTimestamps.containsKey(Sensor.TYPE_GYROSCOPE)) { + float dT = (event.timestamp - lastEventTimestamps.get(Sensor.TYPE_GYROSCOPE)) * (1.0f / 1_000_000_000.0f); + getRotationVectorFromGyro(event.values, deltaVector, dT / 2.0f); + } + + float[] deltaMatrix = new float[9]; + SensorManager.getRotationMatrixFromVector(deltaMatrix, deltaVector); + gyroMatrix = matrixMultiplication(gyroMatrix, deltaMatrix); + SensorManager.getOrientation(gyroMatrix, gyroOrientation); + } + + // Add getRotationVectorFromGyro method + private void getRotationVectorFromGyro(float[] gyroValues, float[] deltaRotationVector, float timeFactor) { + float omegaMagnitude = (float) Math.sqrt(gyroValues[0] * gyroValues[0] + + gyroValues[1] * gyroValues[1] + + gyroValues[2] * gyroValues[2]); + + if (omegaMagnitude > EPSILON) { + gyroValues[0] /= omegaMagnitude; + gyroValues[1] /= omegaMagnitude; + gyroValues[2] /= omegaMagnitude; + } + + float thetaOverTwo = omegaMagnitude * timeFactor; + float sinThetaOverTwo = (float) Math.sin(thetaOverTwo); + float cosThetaOverTwo = (float) Math.cos(thetaOverTwo); + + deltaRotationVector[0] = sinThetaOverTwo * gyroValues[0]; + deltaRotationVector[1] = sinThetaOverTwo * gyroValues[1]; + deltaRotationVector[2] = sinThetaOverTwo * gyroValues[2]; + deltaRotationVector[3] = cosThetaOverTwo; + } + } diff --git a/app/src/main/java/com/openpositioning/PositionMe/sensors/kalmanfilter.java b/app/src/main/java/com/openpositioning/PositionMe/sensors/kalmanfilter.java new file mode 100644 index 00000000..87199ce1 --- /dev/null +++ b/app/src/main/java/com/openpositioning/PositionMe/sensors/kalmanfilter.java @@ -0,0 +1,57 @@ +package com.openpositioning.PositionMe.sensors; + +public class kalmanfilter { + private float theta; // Estimated angle (θ) + private float thetaDotBias; // Bias in angular rate (θ̇b) + + private float p00, p01, p10, p11; // Error covariance matrix + + // Constructor + public kalmanfilter() { + this.theta = 0.0f; // Initial estimated angle + this.thetaDotBias = 0.0f; // Initial bias + + this.p00 = 1.0f; // Initial error covariance + this.p01 = 0.0f; + this.p10 = 0.0f; + this.p11 = 1.0f; + } + + public float update(float accMagAngle, float gyroAngle, float dt) { + // Prediction step + theta += gyroAngle * dt; + theta -= thetaDotBias * dt; + + // Process noise variance for the angle + float qAngle = 0.01f; + p00 += dt * (dt * p11 - p01 - p10 + qAngle); + p01 -= dt * p11; + p10 -= dt * p11; + + // Process noise variance for the gyro bias + float qBias = 0.003f; + p11 += qBias * dt; + + // Update step + float z = accMagAngle - theta; + + // Measurement noise variance + float rMeasure = 0.01f; + float k0 = p00 / (p00 + rMeasure); + float k1 = p10 / (p00 + rMeasure); + + theta += k0 * z; + thetaDotBias += k1 * z; + + // Update covariance matrix + float tempP00 = p00; + float tempP01 = p01; + + p00 -= k0 * tempP00; + p01 -= k0 * tempP01; + p10 -= k1 * tempP00; + p11 -= k1 * tempP01; + + return theta; + } +} diff --git a/app/src/main/res/layout/fragment_trajectory_map.xml b/app/src/main/res/layout/fragment_trajectory_map.xml index a8ab9118..0573e232 100644 --- a/app/src/main/res/layout/fragment_trajectory_map.xml +++ b/app/src/main/res/layout/fragment_trajectory_map.xml @@ -37,6 +37,12 @@ android:layout_height="wrap_content" android:text="@string/gnssSwitch" /> + + Floor height in meters Color 🛰 Show GNSS + 📶WiFi Loc Floor Down button Floor Up button Choose Map