14 June 2025 - Dumon Geolocation Plugin for Capacitor - Android

This commit is contained in:
wengki81 2025-06-14 15:29:36 +08:00
parent 89b0bb514c
commit 6d7e1f25ce
160 changed files with 15784 additions and 76 deletions

View File

@ -0,0 +1,129 @@
# dumon-geolocation
Implement manager GNSS, WiFi RTT, IMU, Kalman fusion, event emitter
## Install
```bash
npm install dumon-geolocation
npx cap sync
```
## API
<docgen-index>
* [`startPositioning()`](#startpositioning)
* [`stopPositioning()`](#stoppositioning)
* [`getLatestPosition()`](#getlatestposition)
* [`addListener('onPositionUpdate', ...)`](#addlisteneronpositionupdate-)
* [Interfaces](#interfaces)
</docgen-index>
<docgen-api>
<!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
### startPositioning()
```typescript
startPositioning() => Promise<void>
```
--------------------
### stopPositioning()
```typescript
stopPositioning() => Promise<void>
```
--------------------
### getLatestPosition()
```typescript
getLatestPosition() => Promise<PositioningData>
```
**Returns:** <code>Promise&lt;<a href="#positioningdata">PositioningData</a>&gt;</code>
--------------------
### addListener('onPositionUpdate', ...)
```typescript
addListener(eventName: 'onPositionUpdate', listenerFunc: (data: PositioningData) => void) => PluginListenerHandle
```
| Param | Type |
| ------------------ | ------------------------------------------------------------------------------ |
| **`eventName`** | <code>'onPositionUpdate'</code> |
| **`listenerFunc`** | <code>(data: <a href="#positioningdata">PositioningData</a>) =&gt; void</code> |
**Returns:** <code><a href="#pluginlistenerhandle">PluginListenerHandle</a></code>
--------------------
### Interfaces
#### PositioningData
| Prop | Type |
| --------------- | ----------------------------------------------------------- |
| **`source`** | <code>'GNSS' \| 'WIFI' \| 'FUSED' \| 'MOCK'</code> |
| **`timestamp`** | <code>number</code> |
| **`latitude`** | <code>number</code> |
| **`longitude`** | <code>number</code> |
| **`accuracy`** | <code>number</code> |
| **`gnssData`** | <code><a href="#satellitestatus">SatelliteStatus</a></code> |
| **`wifiData`** | <code>WifiAp[]</code> |
| **`imuData`** | <code><a href="#imudata">ImuData</a></code> |
#### SatelliteStatus
| Prop | Type |
| ------------------------- | --------------------------------------- |
| **`satellitesInView`** | <code>number</code> |
| **`usedInFix`** | <code>number</code> |
| **`constellationCounts`** | <code>{ [key: string]: number; }</code> |
#### WifiAp
| Prop | Type |
| -------------- | ------------------- |
| **`ssid`** | <code>string</code> |
| **`bssid`** | <code>string</code> |
| **`rssi`** | <code>number</code> |
| **`distance`** | <code>number</code> |
#### ImuData
| Prop | Type |
| ------------------ | ------------------- |
| **`accelX`** | <code>number</code> |
| **`accelY`** | <code>number</code> |
| **`accelZ`** | <code>number</code> |
| **`gyroX`** | <code>number</code> |
| **`gyroY`** | <code>number</code> |
| **`gyroZ`** | <code>number</code> |
| **`speed`** | <code>number</code> |
| **`acceleration`** | <code>number</code> |
| **`directionRad`** | <code>number</code> |
#### PluginListenerHandle
| Prop | Type |
| ------------ | ----------------------------------------- |
| **`remove`** | <code>() =&gt; Promise&lt;void&gt;</code> |
</docgen-api>

View File

@ -0,0 +1,133 @@
# dumon-geolocation
Plugin Capacitor Android untuk positioning real-time berbasis GNSS multi-konstelasi, Wi-Fi RTT/RSSI, dan IMU (Accelerometer + Gyroscope), dilengkapi dengan sensor fusion (Kalman Filter) dan deteksi lokasi palsu.
## 📦 Install
```bash
npm install dumon-geolocation
npx cap sync
```
## 🚀 API
### 📡 startPositioning()
```ts
startPositioning() => Promise<void>
```
Memulai pengambilan data posisi secara real-time dari GNSS, Wi-Fi, dan IMU.
---
### 🛑 stopPositioning()
```ts
stopPositioning() => Promise<void>
```
Menghentikan semua sensor dan positioning.
---
### 📍 getLatestPosition()
```ts
getLatestPosition() => Promise<PositioningData>
```
Mengembalikan data posisi terkini yang telah difusi.
---
### 🔄 addListener('onPositionUpdate', ...)
```ts
addListener(eventName: 'onPositionUpdate', listenerFunc: (data: PositioningData) => void): PluginListenerHandle
```
Listener untuk update posisi secara berkala (real-time).
---
## 🧾 Interfaces
### PositioningData
```ts
interface PositioningData {
source: 'GNSS' | 'WIFI' | 'FUSED' | 'MOCK';
latitude: number;
longitude: number;
accuracy: number;
speed: number;
acceleration: number;
directionRad: number;
timestamp: number;
isMocked: boolean;
// Optional raw sensor data (available internally)
// imuData?: ImuData;
// gnssData?: SatelliteStatus;
// wifiData?: WifiAp[];
}
```
### ImuData
```ts
interface ImuData {
accelX: number;
accelY: number;
accelZ: number;
gyroX: number;
gyroY: number;
gyroZ: number;
speed: number;
acceleration: number;
directionRad: number;
}
```
### SatelliteStatus
```ts
interface SatelliteStatus {
satellitesInView: number;
usedInFix: number;
constellationCounts: { [key: string]: number };
}
```
### WifiAp
```ts
interface WifiAp {
ssid: string;
bssid: string;
rssi: number;
distance?: number;
}
```
### PluginListenerHandle
```ts
interface PluginListenerHandle {
remove: () => Promise<void>;
}
```
---
## Catatan
- Plugin hanya mendukung platform Android saat ini.
- Ideal digunakan bersama dengan plugin `Geolocation` bawaan Capacitor untuk fallback atau perbandingan.
- Sensor fusion berbasis Kalman Filter (versi sederhana).
- `directionRad` merujuk arah dalam radian relatif terhadap utara (azimuth).
- Output `isMocked` berguna untuk deteksi lokasi palsu.
---
Lisensi: MIT Dibuat oleh Tim Dumon

View File

@ -0,0 +1,67 @@
ext {
junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.0'
androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1'
androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1'
}
buildscript {
ext {
kotlin_version = '1.9.24'
}
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.7.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'com.android.library'
apply plugin: 'org.jetbrains.kotlin.android'
android {
namespace "com.dumon.plugin.geolocation"
compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35
defaultConfig {
minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23
targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
lintOptions {
abortOnError false
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_21
targetCompatibility JavaVersion.VERSION_21
}
kotlinOptions {
jvmTarget = '21'
}
}
repositories {
google()
mavenCentral()
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':capacitor-android')
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
implementation 'androidx.core:core-ktx:1.16.0'
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
}

View File

@ -0,0 +1,67 @@
ext {
junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.0'
androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1'
androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1'
}
buildscript {
ext {
kotlin_version = '1.9.24'
}
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.7.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'com.android.library'
apply plugin: 'org.jetbrains.kotlin.android'
android {
namespace "com.dumon.plugin.geolocation"
compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35
defaultConfig {
minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23
targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
lintOptions {
abortOnError false
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = '17'
}
}
repositories {
google()
mavenCentral()
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':capacitor-android')
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
implementation 'androidx.core:core-ktx:1.16.0'
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
}

View File

@ -0,0 +1,67 @@
ext {
junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.0'
androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1'
androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1'
}
buildscript {
ext {
kotlin_version = '1.9.24'
}
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.7.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'com.android.library'
apply plugin: 'org.jetbrains.kotlin.android'
android {
namespace "com.dumon.plugin.geolocation"
compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35
defaultConfig {
minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
lintOptions {
abortOnError false
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = '17'
}
}
repositories {
google()
mavenCentral()
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':capacitor-android')
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
implementation 'androidx.core:core-ktx:1.16.0'
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
}

View File

@ -0,0 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View File

@ -0,0 +1,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" />
</manifest>

View File

@ -0,0 +1,158 @@
package com.dumon.plugin.geolocation;
import android.util.Log;
import android.Manifest
import com.getcapacitor.*
import com.getcapacitor.annotation.CapacitorPlugin
import com.getcapacitor.annotation.Permission
import com.dumon.plugin.geolocation.gps.GpsStatusManager
import com.dumon.plugin.geolocation.imu.ImuSensorManager
import com.dumon.plugin.geolocation.wifi.WifiPositioningManager
import com.dumon.plugin.geolocation.fusion.SensorFusionManager
@CapacitorPlugin(
name = "DumonGeolocation",
permissions = [
Permission(strings = [
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.CHANGE_WIFI_STATE,
Manifest.permission.NEARBY_WIFI_DEVICES
])
]
)
class DumonGeolocation : Plugin() {
private var gpsManager: GpsStatusManager? = null
private var imuManager: ImuSensorManager? = null
private var wifiManager: WifiPositioningManager? = null
private var sensorFusionManager: SensorFusionManager? = null
private var latestLatitude = 0.0
private var latestLongitude = 0.0
private var latestAccuracy = 999.0
private var latestSource = "GNSS"
private var latestAccel = floatArrayOf(0f, 0f, 0f)
private var latestGyro = floatArrayOf(0f, 0f, 0f)
override fun load() {
gpsManager = GpsStatusManager(
context,
onSatelliteStatusUpdate = { info ->
// Optional: You can choose to emit this as another event
// notifyListeners("onGpsStatus", JSObject().put("info", info))
},
onLocationUpdate = { info ->
// Example parsing GPS location (assume GNSS is main source)
// For now, dummy parser → in real code you can extract values properly
latestLatitude += 0.0001
latestLongitude += 0.0001
latestAccuracy = 5.0
latestSource = "GNSS"
emitPositionUpdate()
}
)
imuManager = ImuSensorManager(
context,
onAccelerometerUpdate = { info ->
// You can parse accel if needed
// Example dummy update:
latestAccel = floatArrayOf(0.1f, 0.2f, 0.3f)
},
onGyroscopeUpdate = { info ->
// Example dummy update:
latestGyro = floatArrayOf(0.01f, 0.02f, 0.03f)
}
)
wifiManager = WifiPositioningManager(
context,
onWifiPositioningUpdate = { info ->
// Optional: could update fused position here
}
)
sensorFusionManager = SensorFusionManager { lat, lon ->
latestLatitude = lat
latestLongitude = lon
latestAccuracy = 3.0
latestSource = "FUSED"
emitPositionUpdate()
}
}
@PluginMethod
fun startPositioning(call: PluginCall) {
gpsManager?.start()
wifiManager?.startPeriodicScan(3000L)
imuManager?.start()
call.resolve()
}
@PluginMethod
fun stopPositioning(call: PluginCall) {
gpsManager?.stop()
wifiManager?.stopPeriodicScan()
imuManager?.stop()
call.resolve()
}
@PluginMethod
fun getLatestPosition(call: PluginCall) {
val jsObj = JSObject()
jsObj.put("latitude", latestLatitude)
jsObj.put("longitude", latestLongitude)
jsObj.put("accuracy", latestAccuracy)
jsObj.put("source", latestSource)
val imuData = JSObject()
imuData.put("accelX", latestAccel[0])
imuData.put("accelY", latestAccel[1])
imuData.put("accelZ", latestAccel[2])
imuData.put("gyroX", latestGyro[0])
imuData.put("gyroY", latestGyro[1])
imuData.put("gyroZ", latestGyro[2])
jsObj.put("imuData", imuData)
call.resolve(jsObj)
}
// Helper → Emit to JS
private fun emitPositionUpdate() {
val jsObj = JSObject()
jsObj.put("latitude", latestLatitude)
jsObj.put("longitude", latestLongitude)
jsObj.put("accuracy", latestAccuracy)
jsObj.put("source", latestSource)
val imuData = JSObject()
imuData.put("accelX", latestAccel[0])
imuData.put("accelY", latestAccel[1])
imuData.put("accelZ", latestAccel[2])
imuData.put("gyroX", latestGyro[0])
imuData.put("gyroY", latestGyro[1])
imuData.put("gyroZ", latestGyro[2])
jsObj.put("imuData", imuData)
notifyListeners("onPositionUpdate", jsObj)
}
}
// import android.util.Log;
// public class DumonGeolocation {
// public String echo(String value) {
// Log.i("Echo", value);
// return value;
// }
// }

View File

@ -0,0 +1,155 @@
package com.dumon.plugin.geolocation;
import android.Manifest
import com.getcapacitor.*
import com.getcapacitor.annotation.CapacitorPlugin
import com.getcapacitor.annotation.Permission
import com.dumon.plugin.geolocation.gps.GpsStatusManager
import com.dumon.plugin.geolocation.imu.ImuSensorManager
import com.dumon.plugin.geolocation.wifi.WifiPositioningManager
import com.dumon.plugin.geolocation.fusion.SensorFusionManager
@CapacitorPlugin(
name = "DumonGeolocation",
permissions = [
Permission(strings = [
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.CHANGE_WIFI_STATE,
Manifest.permission.NEARBY_WIFI_DEVICES
])
]
)
class DumonGeolocation : Plugin() {
private var gpsManager: GpsStatusManager? = null
private var imuManager: ImuSensorManager? = null
private var wifiManager: WifiPositioningManager? = null
private var sensorFusionManager: SensorFusionManager? = null
private var latestLatitude = 0.0
private var latestLongitude = 0.0
private var latestAccuracy = 999.0
private var latestSource = "GNSS"
private var latestAccel = floatArrayOf(0f, 0f, 0f)
private var latestGyro = floatArrayOf(0f, 0f, 0f)
override fun load() {
gpsManager = GpsStatusManager(
context,
onSatelliteStatusUpdate = { info ->
// Optional: You can choose to emit this as another event
// notifyListeners("onGpsStatus", JSObject().put("info", info))
},
onLocationUpdate = { info ->
// Example parsing GPS location (assume GNSS is main source)
// For now, dummy parser → in real code you can extract values properly
// latestLatitude += 0.0001
// latestLongitude += 0.0001
// latestAccuracy = 5.0
// latestSource = "GNSS"
latestLatitude = location.latitude
latestLongitude = location.longitude
latestAccuracy = location.accuracy.toDouble()
latestSource = "GNSS"
emitPositionUpdate()
emitPositionUpdate()
}
)
imuManager = ImuSensorManager(
context,
onAccelerometerUpdate = { info ->
// You can parse accel if needed
// Example dummy update:
latestAccel = floatArrayOf(0.1f, 0.2f, 0.3f)
},
onGyroscopeUpdate = { info ->
// Example dummy update:
latestGyro = floatArrayOf(0.01f, 0.02f, 0.03f)
}
)
wifiManager = WifiPositioningManager(
context,
onWifiPositioningUpdate = { info ->
// Optional: could update fused position here
}
)
sensorFusionManager = SensorFusionManager { lat, lon ->
latestLatitude = lat
latestLongitude = lon
latestAccuracy = 3.0
latestSource = "FUSED"
emitPositionUpdate()
}
}
@PluginMethod
fun startPositioning(call: PluginCall) {
gpsManager?.start()
wifiManager?.startPeriodicScan(3000L)
imuManager?.start()
call.resolve()
}
@PluginMethod
fun stopPositioning(call: PluginCall) {
gpsManager?.stop()
wifiManager?.stopPeriodicScan()
imuManager?.stop()
call.resolve()
}
@PluginMethod
fun getLatestPosition(call: PluginCall) {
val jsObj = JSObject()
jsObj.put("latitude", latestLatitude)
jsObj.put("longitude", latestLongitude)
jsObj.put("accuracy", latestAccuracy)
jsObj.put("source", latestSource)
val imuData = JSObject()
imuData.put("accelX", latestAccel[0])
imuData.put("accelY", latestAccel[1])
imuData.put("accelZ", latestAccel[2])
imuData.put("gyroX", latestGyro[0])
imuData.put("gyroY", latestGyro[1])
imuData.put("gyroZ", latestGyro[2])
jsObj.put("imuData", imuData)
call.resolve(jsObj)
}
// Helper → Emit to JS
private fun emitPositionUpdate() {
val jsObj = JSObject()
jsObj.put("latitude", latestLatitude)
jsObj.put("longitude", latestLongitude)
jsObj.put("accuracy", latestAccuracy)
jsObj.put("source", latestSource)
val imuData = JSObject()
imuData.put("accelX", latestAccel[0])
imuData.put("accelY", latestAccel[1])
imuData.put("accelZ", latestAccel[2])
imuData.put("gyroX", latestGyro[0])
imuData.put("gyroY", latestGyro[1])
imuData.put("gyroZ", latestGyro[2])
jsObj.put("imuData", imuData)
notifyListeners("onPositionUpdate", jsObj)
}
}

View File

@ -0,0 +1,146 @@
package com.dumon.plugin.geolocation;
import android.Manifest
import com.getcapacitor.*
import com.getcapacitor.annotation.CapacitorPlugin
import com.getcapacitor.annotation.Permission
import com.dumon.plugin.geolocation.gps.GpsStatusManager
import com.dumon.plugin.geolocation.imu.ImuSensorManager
import com.dumon.plugin.geolocation.wifi.WifiPositioningManager
import com.dumon.plugin.geolocation.fusion.SensorFusionManager
@CapacitorPlugin(
name = "DumonGeolocation",
permissions = [
Permission(strings = [
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.CHANGE_WIFI_STATE,
Manifest.permission.NEARBY_WIFI_DEVICES
])
]
)
class DumonGeolocation : Plugin() {
private var gpsManager: GpsStatusManager? = null
private var imuManager: ImuSensorManager? = null
private var wifiManager: WifiPositioningManager? = null
private var sensorFusionManager: SensorFusionManager? = null
private var latestLatitude = 0.0
private var latestLongitude = 0.0
private var latestAccuracy = 999.0
private var latestSource = "GNSS"
private var latestAccel = floatArrayOf(0f, 0f, 0f)
private var latestGyro = floatArrayOf(0f, 0f, 0f)
override fun load() {
gpsManager = GpsStatusManager(
context,
onSatelliteStatusUpdate = { info ->
// Optional: You can choose to emit this as another event
// notifyListeners("onGpsStatus", JSObject().put("info", info))
},
onLocationUpdate = { location ->
latestLatitude = location.latitude
latestLongitude = location.longitude
latestAccuracy = location.accuracy.toDouble()
latestSource = "GNSS"
emitPositionUpdate()
}
)
imuManager = ImuSensorManager(
context,
onAccelerometerUpdate = { info ->
// You can parse accel if needed
// Example dummy update:
latestAccel = floatArrayOf(0.1f, 0.2f, 0.3f)
},
onGyroscopeUpdate = { info ->
// Example dummy update:
latestGyro = floatArrayOf(0.01f, 0.02f, 0.03f)
}
)
wifiManager = WifiPositioningManager(
context,
onWifiPositioningUpdate = { info ->
// Optional: could update fused position here
}
)
sensorFusionManager = SensorFusionManager { lat, lon ->
latestLatitude = lat
latestLongitude = lon
latestAccuracy = 3.0
latestSource = "FUSED"
emitPositionUpdate()
}
}
@PluginMethod
fun startPositioning(call: PluginCall) {
gpsManager?.start()
wifiManager?.startPeriodicScan(3000L)
imuManager?.start()
call.resolve()
}
@PluginMethod
fun stopPositioning(call: PluginCall) {
gpsManager?.stop()
wifiManager?.stopPeriodicScan()
imuManager?.stop()
call.resolve()
}
@PluginMethod
fun getLatestPosition(call: PluginCall) {
val jsObj = JSObject()
jsObj.put("latitude", latestLatitude)
jsObj.put("longitude", latestLongitude)
jsObj.put("accuracy", latestAccuracy)
jsObj.put("source", latestSource)
val imuData = JSObject()
imuData.put("accelX", latestAccel[0])
imuData.put("accelY", latestAccel[1])
imuData.put("accelZ", latestAccel[2])
imuData.put("gyroX", latestGyro[0])
imuData.put("gyroY", latestGyro[1])
imuData.put("gyroZ", latestGyro[2])
jsObj.put("imuData", imuData)
call.resolve(jsObj)
}
// Helper → Emit to JS
private fun emitPositionUpdate() {
val jsObj = JSObject()
jsObj.put("latitude", latestLatitude)
jsObj.put("longitude", latestLongitude)
jsObj.put("accuracy", latestAccuracy)
jsObj.put("source", latestSource)
val imuData = JSObject()
imuData.put("accelX", latestAccel[0])
imuData.put("accelY", latestAccel[1])
imuData.put("accelZ", latestAccel[2])
imuData.put("gyroX", latestGyro[0])
imuData.put("gyroY", latestGyro[1])
imuData.put("gyroZ", latestGyro[2])
jsObj.put("imuData", imuData)
notifyListeners("onPositionUpdate", jsObj)
}
}

View File

@ -0,0 +1,148 @@
package com.dumon.plugin.geolocation;
import android.Manifest
import com.getcapacitor.*
import com.getcapacitor.annotation.CapacitorPlugin
import com.getcapacitor.annotation.Permission
import com.dumon.plugin.geolocation.gps.GpsStatusManager
import com.dumon.plugin.geolocation.imu.ImuSensorManager
import com.dumon.plugin.geolocation.wifi.WifiPositioningManager
import com.dumon.plugin.geolocation.fusion.SensorFusionManager
@CapacitorPlugin(
name = "DumonGeolocation",
permissions = [
Permission(strings = [
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.CHANGE_WIFI_STATE,
Manifest.permission.NEARBY_WIFI_DEVICES
])
]
)
class DumonGeolocation : Plugin() {
private var gpsManager: GpsStatusManager? = null
private var imuManager: ImuSensorManager? = null
private var wifiManager: WifiPositioningManager? = null
private var sensorFusionManager: SensorFusionManager? = null
private var latestLatitude = 0.0
private var latestLongitude = 0.0
private var latestAccuracy = 999.0
private var latestSource = "GNSS"
private var latestTimestamp: Long = 0L
private var latestAccel = floatArrayOf(0f, 0f, 0f)
private var latestGyro = floatArrayOf(0f, 0f, 0f)
override fun load() {
gpsManager = GpsStatusManager(
context,
onSatelliteStatusUpdate = { info ->
// Optional: You can choose to emit this as another event
// notifyListeners("onGpsStatus", JSObject().put("info", info))
},
onLocationUpdate = { location ->
latestLatitude = location.latitude
latestLongitude = location.longitude
latestAccuracy = location.accuracy.toDouble()
latestSource = "GNSS"
latestTimestamp = location.time
emitPositionUpdate()
}
)
imuManager = ImuSensorManager(
context,
onAccelerometerUpdate = { info ->
// You can parse accel if needed
// Example dummy update:
latestAccel = floatArrayOf(0.1f, 0.2f, 0.3f)
},
onGyroscopeUpdate = { info ->
// Example dummy update:
latestGyro = floatArrayOf(0.01f, 0.02f, 0.03f)
}
)
wifiManager = WifiPositioningManager(
context,
onWifiPositioningUpdate = { info ->
// Optional: could update fused position here
}
)
sensorFusionManager = SensorFusionManager { lat, lon ->
latestLatitude = lat
latestLongitude = lon
latestAccuracy = 3.0
latestSource = "FUSED"
emitPositionUpdate()
}
}
@PluginMethod
fun startPositioning(call: PluginCall) {
gpsManager?.start()
wifiManager?.startPeriodicScan(3000L)
imuManager?.start()
call.resolve()
}
@PluginMethod
fun stopPositioning(call: PluginCall) {
gpsManager?.stop()
wifiManager?.stopPeriodicScan()
imuManager?.stop()
call.resolve()
}
@PluginMethod
fun getLatestPosition(call: PluginCall) {
val jsObj = JSObject()
jsObj.put("latitude", latestLatitude)
jsObj.put("longitude", latestLongitude)
jsObj.put("accuracy", latestAccuracy)
jsObj.put("source", latestSource)
val imuData = JSObject()
imuData.put("accelX", latestAccel[0])
imuData.put("accelY", latestAccel[1])
imuData.put("accelZ", latestAccel[2])
imuData.put("gyroX", latestGyro[0])
imuData.put("gyroY", latestGyro[1])
imuData.put("gyroZ", latestGyro[2])
jsObj.put("imuData", imuData)
call.resolve(jsObj)
}
// Helper → Emit to JS
private fun emitPositionUpdate() {
val jsObj = JSObject()
jsObj.put("latitude", latestLatitude)
jsObj.put("longitude", latestLongitude)
jsObj.put("accuracy", latestAccuracy)
jsObj.put("source", latestSource)
val imuData = JSObject()
imuData.put("accelX", latestAccel[0])
imuData.put("accelY", latestAccel[1])
imuData.put("accelZ", latestAccel[2])
imuData.put("gyroX", latestGyro[0])
imuData.put("gyroY", latestGyro[1])
imuData.put("gyroZ", latestGyro[2])
jsObj.put("imuData", imuData)
notifyListeners("onPositionUpdate", jsObj)
}
}

View File

@ -0,0 +1,150 @@
package com.dumon.plugin.geolocation;
import android.Manifest
import com.getcapacitor.*
import com.getcapacitor.annotation.CapacitorPlugin
import com.getcapacitor.annotation.Permission
import com.dumon.plugin.geolocation.gps.GpsStatusManager
import com.dumon.plugin.geolocation.imu.ImuSensorManager
import com.dumon.plugin.geolocation.wifi.WifiPositioningManager
import com.dumon.plugin.geolocation.fusion.SensorFusionManager
@CapacitorPlugin(
name = "DumonGeolocation",
permissions = [
Permission(strings = [
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.CHANGE_WIFI_STATE,
Manifest.permission.NEARBY_WIFI_DEVICES
])
]
)
class DumonGeolocation : Plugin() {
private var gpsManager: GpsStatusManager? = null
private var imuManager: ImuSensorManager? = null
private var wifiManager: WifiPositioningManager? = null
private var sensorFusionManager: SensorFusionManager? = null
private var latestLatitude = 0.0
private var latestLongitude = 0.0
private var latestAccuracy = 999.0
private var latestSource = "GNSS"
private var latestTimestamp: Long = 0L
private var latestAccel = floatArrayOf(0f, 0f, 0f)
private var latestGyro = floatArrayOf(0f, 0f, 0f)
override fun load() {
gpsManager = GpsStatusManager(
context,
onSatelliteStatusUpdate = { info ->
// Optional: You can choose to emit this as another event
// notifyListeners("onGpsStatus", JSObject().put("info", info))
},
onLocationUpdate = { location ->
latestLatitude = location.latitude
latestLongitude = location.longitude
latestAccuracy = location.accuracy.toDouble()
latestSource = "GNSS"
latestTimestamp = location.time
emitPositionUpdate()
}
)
imuManager = ImuSensorManager(
context,
onAccelerometerUpdate = { info ->
// You can parse accel if needed
// Example dummy update:
latestAccel = floatArrayOf(0.1f, 0.2f, 0.3f)
},
onGyroscopeUpdate = { info ->
// Example dummy update:
latestGyro = floatArrayOf(0.01f, 0.02f, 0.03f)
}
)
wifiManager = WifiPositioningManager(
context,
onWifiPositioningUpdate = { info ->
// Optional: could update fused position here
}
)
sensorFusionManager = SensorFusionManager { lat, lon ->
latestLatitude = lat
latestLongitude = lon
latestAccuracy = 3.0
latestSource = "FUSED"
emitPositionUpdate()
}
}
@PluginMethod
fun startPositioning(call: PluginCall) {
gpsManager?.start()
wifiManager?.startPeriodicScan(3000L)
imuManager?.start()
call.resolve()
}
@PluginMethod
fun stopPositioning(call: PluginCall) {
gpsManager?.stop()
wifiManager?.stopPeriodicScan()
imuManager?.stop()
call.resolve()
}
@PluginMethod
fun getLatestPosition(call: PluginCall) {
val jsObj = JSObject()
jsObj.put("latitude", latestLatitude)
jsObj.put("longitude", latestLongitude)
jsObj.put("accuracy", latestAccuracy)
jsObj.put("source", latestSource)
jsObj.put("timestamp", latestTimestamp)
val imuData = JSObject()
imuData.put("accelX", latestAccel[0])
imuData.put("accelY", latestAccel[1])
imuData.put("accelZ", latestAccel[2])
imuData.put("gyroX", latestGyro[0])
imuData.put("gyroY", latestGyro[1])
imuData.put("gyroZ", latestGyro[2])
jsObj.put("imuData", imuData)
call.resolve(jsObj)
}
// Helper → Emit to JS
private fun emitPositionUpdate() {
val jsObj = JSObject()
jsObj.put("latitude", latestLatitude)
jsObj.put("longitude", latestLongitude)
jsObj.put("accuracy", latestAccuracy)
jsObj.put("source", latestSource)
jsObj.put("timestamp", latestTimestamp)
val imuData = JSObject()
imuData.put("accelX", latestAccel[0])
imuData.put("accelY", latestAccel[1])
imuData.put("accelZ", latestAccel[2])
imuData.put("gyroX", latestGyro[0])
imuData.put("gyroY", latestGyro[1])
imuData.put("gyroZ", latestGyro[2])
jsObj.put("imuData", imuData)
notifyListeners("onPositionUpdate", jsObj)
}
}

View File

@ -0,0 +1,183 @@
package com.dumon.plugin.geolocation;
import android.Manifest
import com.getcapacitor.*
import com.getcapacitor.annotation.CapacitorPlugin
import com.getcapacitor.annotation.Permission
import com.dumon.plugin.geolocation.gps.GpsStatusManager
import com.dumon.plugin.geolocation.gps.SatelliteStatus
import com.dumon.plugin.geolocation.imu.ImuSensorManager
import com.dumon.plugin.geolocation.wifi.WifiPositioningManager
import com.dumon.plugin.geolocation.wifi.WifiScanResult
import com.dumon.plugin.geolocation.fusion.SensorFusionManager
@CapacitorPlugin(
name = "DumonGeolocation",
permissions = [
Permission(strings = [
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.CHANGE_WIFI_STATE,
Manifest.permission.NEARBY_WIFI_DEVICES
])
]
)
class DumonGeolocation : Plugin() {
private var gpsManager: GpsStatusManager? = null
private var imuManager: ImuSensorManager? = null
private var wifiManager: WifiPositioningManager? = null
private var sensorFusionManager: SensorFusionManager? = null
private var latestLatitude = 0.0
private var latestLongitude = 0.0
private var latestAccuracy = 999.0
private var latestSource = "GNSS"
private var latestTimestamp: Long = 0L
private var latestAccel = floatArrayOf(0f, 0f, 0f)
private var latestGyro = floatArrayOf(0f, 0f, 0f)
private var latestSatelliteStatus: SatelliteStatus? = null
private var latestWifiScanResult: WifiScanResult? = null
override fun load() {
gpsManager = GpsStatusManager(
context,
onSatelliteStatusUpdate = { status ->
latestSatelliteStatus = status
},
onLocationUpdate = { location ->
latestLatitude = location.latitude
latestLongitude = location.longitude
latestAccuracy = location.accuracy.toDouble()
latestSource = "GNSS"
latestTimestamp = location.time
emitPositionUpdate()
}
)
imuManager = ImuSensorManager(
context,
onAccelerometerUpdate = { info ->
// You can parse accel if needed
// Example dummy update:
latestAccel = floatArrayOf(0.1f, 0.2f, 0.3f)
},
onGyroscopeUpdate = { info ->
// Example dummy update:
latestGyro = floatArrayOf(0.01f, 0.02f, 0.03f)
}
)
wifiManager = WifiPositioningManager(
context,
onWifiPositioningUpdate = { result ->
latestWifiScanResult = result
}
)
sensorFusionManager = SensorFusionManager { lat, lon ->
latestLatitude = lat
latestLongitude = lon
latestAccuracy = 3.0
latestSource = "FUSED"
emitPositionUpdate()
}
}
@PluginMethod
fun startPositioning(call: PluginCall) {
gpsManager?.start()
wifiManager?.startPeriodicScan(3000L)
imuManager?.start()
call.resolve()
}
@PluginMethod
fun stopPositioning(call: PluginCall) {
gpsManager?.stop()
wifiManager?.stopPeriodicScan()
imuManager?.stop()
call.resolve()
}
@PluginMethod
fun getLatestPosition(call: PluginCall) {
val jsObj = JSObject()
jsObj.put("latitude", latestLatitude)
jsObj.put("longitude", latestLongitude)
jsObj.put("accuracy", latestAccuracy)
jsObj.put("source", latestSource)
jsObj.put("timestamp", latestTimestamp)
val imuData = JSObject()
imuData.put("accelX", latestAccel[0])
imuData.put("accelY", latestAccel[1])
imuData.put("accelZ", latestAccel[2])
imuData.put("gyroX", latestGyro[0])
imuData.put("gyroY", latestGyro[1])
imuData.put("gyroZ", latestGyro[2])
jsObj.put("imuData", imuData)
call.resolve(jsObj)
}
// Helper → Emit to JS
private fun emitPositionUpdate() {
val jsObj = JSObject()
jsObj.put("source", latestSource)
jsObj.put("timestamp", latestTimestamp)
val gpsData = JSObject()
gpsData.put("latitude", latestLatitude)
gpsData.put("longitude", latestLongitude)
gpsData.put("accuracy", latestAccuracy)
latestSatelliteStatus?.let { status ->
gpsData.put("satellitesInView", status.satellitesInView)
gpsData.put("usedInFix", status.usedInFix)
val constellations = JSObject()
status.constellationCounts.forEach { (name, count) ->
constellations.put(name, count)
}
gpsData.put("constellationCounts", constellations)
}
jsObj.put("gpsData", gpsData)
val wifiData = JSObject()
latestWifiScanResult?.let { result ->
wifiData.put("apCount", result.apCount)
val apsArray = JSArray()
result.aps.forEach { ap ->
val apObj = JSObject()
apObj.put("ssid", ap.ssid)
apObj.put("bssid", ap.bssid)
apObj.put("rssi", ap.rssi)
ap.distance?.let { d -> apObj.put("distance", d) }
apsArray.put(apObj)
}
wifiData.put("aps", apsArray)
}
jsObj.put("wifiData", wifiData)
val imuData = JSObject()
imuData.put("accelX", latestAccel[0])
imuData.put("accelY", latestAccel[1])
imuData.put("accelZ", latestAccel[2])
imuData.put("gyroX", latestGyro[0])
imuData.put("gyroY", latestGyro[1])
imuData.put("gyroZ", latestGyro[2])
jsObj.put("imuData", imuData)
notifyListeners("onPositionUpdate", jsObj)
}
}

View File

@ -0,0 +1,183 @@
package com.dumon.plugin.geolocation;
import android.Manifest
import com.getcapacitor.*
import com.getcapacitor.annotation.CapacitorPlugin
import com.getcapacitor.annotation.Permission
import com.dumon.plugin.geolocation.gps.GpsStatusManager
import com.dumon.plugin.geolocation.gps.SatelliteStatus
import com.dumon.plugin.geolocation.imu.ImuSensorManager
import com.dumon.plugin.geolocation.wifi.WifiPositioningManager
import com.dumon.plugin.geolocation.wifi.WifiScanResult
import com.dumon.plugin.geolocation.fusion.SensorFusionManager
@CapacitorPlugin(
name = "DumonGeolocation",
permissions = [
Permission(strings = [
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.CHANGE_WIFI_STATE,
Manifest.permission.NEARBY_WIFI_DEVICES
])
]
)
class DumonGeolocation : Plugin() {
private var gpsManager: GpsStatusManager? = null
private var imuManager: ImuSensorManager? = null
private var wifiManager: WifiPositioningManager? = null
private var sensorFusionManager: SensorFusionManager? = null
private var latestLatitude = 0.0
private var latestLongitude = 0.0
private var latestAccuracy = 999.0
private var latestSource = "GNSS"
private var latestTimestamp: Long = 0L
private var latestAccel = floatArrayOf(0f, 0f, 0f)
private var latestGyro = floatArrayOf(0f, 0f, 0f)
private var latestSatelliteStatus: SatelliteStatus? = null
private var latestWifiScanResult: WifiScanResult? = null
override fun load() {
gpsManager = GpsStatusManager(
context,
onSatelliteStatusUpdate = { status ->
latestSatelliteStatus = status
},
onLocationUpdate = { location ->
latestLatitude = location.latitude
latestLongitude = location.longitude
latestAccuracy = location.accuracy.toDouble()
latestSource = "GNSS"
latestTimestamp = location.time
emitPositionUpdate()
}
)
imuManager = ImuSensorManager(
context,
onAccelerometerUpdate = { info ->
// You can parse accel if needed
// Example dummy update:
latestAccel = floatArrayOf(0.1f, 0.2f, 0.3f)
},
onGyroscopeUpdate = { info ->
// Example dummy update:
latestGyro = floatArrayOf(0.01f, 0.02f, 0.03f)
}
)
wifiManager = WifiPositioningManager(
context,
onWifiPositioningUpdate = { result ->
latestWifiScanResult = result
}
)
sensorFusionManager = SensorFusionManager { lat, lon ->
latestLatitude = lat
latestLongitude = lon
latestAccuracy = 3.0
latestSource = "FUSED"
emitPositionUpdate()
}
}
@PluginMethod
fun startPositioning(call: PluginCall) {
gpsManager?.start()
wifiManager?.startPeriodicScan(3000L)
imuManager?.start()
call.resolve()
}
@PluginMethod
fun stopPositioning(call: PluginCall) {
gpsManager?.stop()
wifiManager?.stopPeriodicScan()
imuManager?.stop()
call.resolve()
}
@PluginMethod
fun getLatestPosition(call: PluginCall) {
val jsObj = JSObject()
jsObj.put("latitude", latestLatitude)
jsObj.put("longitude", latestLongitude)
jsObj.put("accuracy", latestAccuracy)
jsObj.put("source", latestSource)
jsObj.put("timestamp", latestTimestamp)
val imuData = JSObject()
imuData.put("accelX", latestAccel[0])
imuData.put("accelY", latestAccel[1])
imuData.put("accelZ", latestAccel[2])
imuData.put("gyroX", latestGyro[0])
imuData.put("gyroY", latestGyro[1])
imuData.put("gyroZ", latestGyro[2])
jsObj.put("imuData", imuData)
call.resolve(jsObj)
}
// Helper → Emit to JS
private fun emitPositionUpdate() {
val jsObj = JSObject()
jsObj.put("source", latestSource)
jsObj.put("timestamp", latestTimestamp)
val gpsData = JSObject()
gpsData.put("latitude", latestLatitude)
gpsData.put("longitude", latestLongitude)
gpsData.put("accuracy", latestAccuracy)
latestSatelliteStatus?.let { status ->
gpsData.put("satellitesInView", status.satellitesInView)
gpsData.put("usedInFix", status.usedInFix)
val constellations = JSObject()
status.constellationCounts.forEach { (name, count) ->
constellations.put(name, count)
}
gpsData.put("constellationCounts", constellations)
}
jsObj.put("gpsData", gpsData)
val wifiData = JSObject()
latestWifiScanResult?.let { result ->
wifiData.put("apCount", result.apCount)
val apsArray = JSArray()
result.aps.forEach { ap ->
val apObj = JSObject()
apObj.put("ssid", ap.ssid)
apObj.put("bssid", ap.bssid)
apObj.put("rssi", ap.rssi)
ap.distance?.let { d -> apObj.put("distance", d) }
apsArray.put(apObj)
}
wifiData.put("aps", apsArray)
}
jsObj.put("wifiData", wifiData)
val imuData = JSObject()
imuData.put("accelX", latestAccel[0])
imuData.put("accelY", latestAccel[1])
imuData.put("accelZ", latestAccel[2])
imuData.put("gyroX", latestGyro[0])
imuData.put("gyroY", latestGyro[1])
imuData.put("gyroZ", latestGyro[2])
jsObj.put("imuData", imuData)
notifyListeners("onPositionUpdate", jsObj.put("timestamp", latestTimestamp.takeIf { it > 0 } ?: System.currentTimeMillis()))
}
}

View File

@ -0,0 +1,211 @@
package com.dumon.plugin.geolocation;
import android.Manifest
import com.getcapacitor.*
import com.getcapacitor.annotation.CapacitorPlugin
import com.getcapacitor.annotation.Permission
import com.dumon.plugin.geolocation.gps.GpsStatusManager
import com.dumon.plugin.geolocation.gps.SatelliteStatus
import com.dumon.plugin.geolocation.imu.ImuSensorManager
import com.dumon.plugin.geolocation.wifi.WifiPositioningManager
import com.dumon.plugin.geolocation.wifi.WifiScanResult
import com.dumon.plugin.geolocation.fusion.SensorFusionManager
@CapacitorPlugin(
name = "DumonGeolocation",
permissions = [
Permission(strings = [
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.CHANGE_WIFI_STATE,
Manifest.permission.NEARBY_WIFI_DEVICES
])
]
)
class DumonGeolocation : Plugin() {
private var gpsManager: GpsStatusManager? = null
private var imuManager: ImuSensorManager? = null
private var wifiManager: WifiPositioningManager? = null
private var sensorFusionManager: SensorFusionManager? = null
private var latestLatitude = 0.0
private var latestLongitude = 0.0
private var latestAccuracy = 999.0
private var latestSource = "GNSS"
private var latestTimestamp: Long = 0L
private var latestAccel = floatArrayOf(0f, 0f, 0f)
private var latestGyro = floatArrayOf(0f, 0f, 0f)
private var latestSatelliteStatus: SatelliteStatus? = null
private var latestWifiScanResult: WifiScanResult? = null
override fun load() {
gpsManager = GpsStatusManager(
context,
onSatelliteStatusUpdate = { status ->
latestSatelliteStatus = status
},
onLocationUpdate = { location ->
latestLatitude = location.latitude
latestLongitude = location.longitude
latestAccuracy = location.accuracy.toDouble()
latestSource = "GNSS"
latestTimestamp = location.time
emitPositionUpdate()
}
)
imuManager = ImuSensorManager(
context,
onAccelerometerUpdate = { info ->
// You can parse accel if needed
// Example dummy update:
latestAccel = floatArrayOf(0.1f, 0.2f, 0.3f)
},
onGyroscopeUpdate = { info ->
// Example dummy update:
latestGyro = floatArrayOf(0.01f, 0.02f, 0.03f)
}
)
wifiManager = WifiPositioningManager(
context,
onWifiPositioningUpdate = { result ->
latestWifiScanResult = result
}
)
sensorFusionManager = SensorFusionManager { lat, lon ->
latestLatitude = lat
latestLongitude = lon
latestAccuracy = 3.0
latestSource = "FUSED"
emitPositionUpdate()
}
}
@PluginMethod
fun startPositioning(call: PluginCall) {
gpsManager?.start()
wifiManager?.startPeriodicScan(3000L)
imuManager?.start()
call.resolve()
}
@PluginMethod
fun stopPositioning(call: PluginCall) {
gpsManager?.stop()
wifiManager?.stopPeriodicScan()
imuManager?.stop()
call.resolve()
}
@PluginMethod
fun getLatestPosition(call: PluginCall) {
val jsObj = JSObject()
jsObj.put("source", latestSource)
jsObj.put("timestamp", latestTimestamp)
val gpsData = JSObject()
gpsData.put("latitude", latestLatitude)
gpsData.put("longitude", latestLongitude)
gpsData.put("accuracy", latestAccuracy)
latestSatelliteStatus?.let { status ->
gpsData.put("satellitesInView", status.satellitesInView)
gpsData.put("usedInFix", status.usedInFix)
val constellations = JSObject()
status.constellationCounts.forEach { (name, count) ->
constellations.put(name, count)
}
gpsData.put("constellationCounts", constellations)
}
jsObj.put("gpsData", gpsData)
val wifiData = JSObject()
latestWifiScanResult?.let { result ->
wifiData.put("apCount", result.apCount)
val apsArray = JSArray()
result.aps.forEach { ap ->
val apObj = JSObject()
apObj.put("ssid", ap.ssid)
apObj.put("bssid", ap.bssid)
apObj.put("rssi", ap.rssi)
ap.distance?.let { d -> apObj.put("distance", d) }
apsArray.put(apObj)
}
wifiData.put("aps", apsArray)
}
jsObj.put("wifiData", wifiData)
val imuData = JSObject()
imuData.put("accelX", latestAccel[0])
imuData.put("accelY", latestAccel[1])
imuData.put("accelZ", latestAccel[2])
imuData.put("gyroX", latestGyro[0])
imuData.put("gyroY", latestGyro[1])
imuData.put("gyroZ", latestGyro[2])
jsObj.put("imuData", imuData)
call.resolve(jsObj)
}
// Helper → Emit to JS
private fun emitPositionUpdate() {
val jsObj = JSObject()
jsObj.put("source", latestSource)
jsObj.put("timestamp", latestTimestamp)
val gpsData = JSObject()
gpsData.put("latitude", latestLatitude)
gpsData.put("longitude", latestLongitude)
gpsData.put("accuracy", latestAccuracy)
latestSatelliteStatus?.let { status ->
gpsData.put("satellitesInView", status.satellitesInView)
gpsData.put("usedInFix", status.usedInFix)
val constellations = JSObject()
status.constellationCounts.forEach { (name, count) ->
constellations.put(name, count)
}
gpsData.put("constellationCounts", constellations)
}
jsObj.put("gpsData", gpsData)
val wifiData = JSObject()
latestWifiScanResult?.let { result ->
wifiData.put("apCount", result.apCount)
val apsArray = JSArray()
result.aps.forEach { ap ->
val apObj = JSObject()
apObj.put("ssid", ap.ssid)
apObj.put("bssid", ap.bssid)
apObj.put("rssi", ap.rssi)
ap.distance?.let { d -> apObj.put("distance", d) }
apsArray.put(apObj)
}
wifiData.put("aps", apsArray)
}
jsObj.put("wifiData", wifiData)
val imuData = JSObject()
imuData.put("accelX", latestAccel[0])
imuData.put("accelY", latestAccel[1])
imuData.put("accelZ", latestAccel[2])
imuData.put("gyroX", latestGyro[0])
imuData.put("gyroY", latestGyro[1])
imuData.put("gyroZ", latestGyro[2])
jsObj.put("imuData", imuData)
notifyListeners("onPositionUpdate", jsObj.put("timestamp", latestTimestamp.takeIf { it > 0 } ?: System.currentTimeMillis()))
}
}

View File

@ -0,0 +1,201 @@
package com.dumon.plugin.geolocation;
import android.Manifest
import com.getcapacitor.*
import com.getcapacitor.annotation.CapacitorPlugin
import com.getcapacitor.annotation.Permission
import com.dumon.plugin.geolocation.gps.GpsStatusManager
import com.dumon.plugin.geolocation.gps.SatelliteStatus
import com.dumon.plugin.geolocation.imu.ImuSensorManager
import com.dumon.plugin.geolocation.wifi.WifiPositioningManager
import com.dumon.plugin.geolocation.wifi.WifiScanResult
import com.dumon.plugin.geolocation.fusion.SensorFusionManager
@CapacitorPlugin(
name = "DumonGeolocation",
permissions = [
Permission(strings = [
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.CHANGE_WIFI_STATE,
Manifest.permission.NEARBY_WIFI_DEVICES
])
]
)
class DumonGeolocation : Plugin() {
private var gpsManager: GpsStatusManager? = null
private var imuManager: ImuSensorManager? = null
private var wifiManager: WifiPositioningManager? = null
private var sensorFusionManager: SensorFusionManager? = null
private var latestLatitude = 0.0
private var latestLongitude = 0.0
private var latestAccuracy = 999.0
private var latestSource = "GNSS"
private var latestTimestamp: Long = 0L
private var latestAccel = floatArrayOf(0f, 0f, 0f)
private var latestGyro = floatArrayOf(0f, 0f, 0f)
private var latestSatelliteStatus: SatelliteStatus? = null
private var latestWifiScanResult: WifiScanResult? = null
override fun load() {
gpsManager = GpsStatusManager(
context,
onSatelliteStatusUpdate = { status ->
latestSatelliteStatus = status
},
onLocationUpdate = { location ->
latestLatitude = location.latitude
latestLongitude = location.longitude
latestAccuracy = location.accuracy.toDouble()
latestSource = "GNSS"
latestTimestamp = location.time
emitPositionUpdate()
}
)
imuManager = ImuSensorManager(
context,
onAccelerometerUpdate = { info ->
latestAccel = floatArrayOf(0.1f, 0.2f, 0.3f)
},
onGyroscopeUpdate = { info ->
latestGyro = floatArrayOf(0.01f, 0.02f, 0.03f)
}
)
wifiManager = WifiPositioningManager(
context,
onWifiPositioningUpdate = { result ->
latestWifiScanResult = result
}
)
sensorFusionManager = SensorFusionManager { lat, lon ->
latestLatitude = lat
latestLongitude = lon
latestAccuracy = 3.0
latestSource = "FUSED"
emitPositionUpdate()
}
}
@PluginMethod
fun startPositioning(call: PluginCall) {
gpsManager?.start()
wifiManager?.startPeriodicScan(3000L)
imuManager?.start()
call.resolve()
}
@PluginMethod
fun stopPositioning(call: PluginCall) {
gpsManager?.stop()
wifiManager?.stopPeriodicScan()
imuManager?.stop()
call.resolve()
}
@PluginMethod
fun getLatestPosition(call: PluginCall) {
val jsObj = JSObject()
jsObj.put("source", latestSource)
jsObj.put("timestamp", latestTimestamp.takeIf { it > 0 } ?: System.currentTimeMillis())
val gpsData = JSObject()
gpsData.put("latitude", latestLatitude)
gpsData.put("longitude", latestLongitude)
gpsData.put("accuracy", latestAccuracy)
latestSatelliteStatus?.let { status ->
gpsData.put("satellitesInView", status.satellitesInView)
gpsData.put("usedInFix", status.usedInFix)
val constellations = JSObject()
status.constellationCounts.forEach { (name, count) ->
constellations.put(name, count)
}
gpsData.put("constellationCounts", constellations)
}
jsObj.put("gpsData", gpsData)
val wifiData = JSObject()
latestWifiScanResult?.let { result ->
wifiData.put("apCount", result.apCount)
val apsArray = JSArray()
result.aps.forEach { ap ->
val apObj = JSObject()
apObj.put("ssid", ap.ssid)
apObj.put("bssid", ap.bssid)
apObj.put("rssi", ap.rssi)
ap.distance?.let { d -> apObj.put("distance", d) }
apsArray.put(apObj)
}
wifiData.put("aps", apsArray)
}
jsObj.put("wifiData", wifiData)
val imuData = JSObject()
imuData.put("accelX", latestAccel[0])
imuData.put("accelY", latestAccel[1])
imuData.put("accelZ", latestAccel[2])
imuData.put("gyroX", latestGyro[0])
imuData.put("gyroY", latestGyro[1])
imuData.put("gyroZ", latestGyro[2])
jsObj.put("imuData", imuData)
call.resolve(jsObj)
}
private fun emitPositionUpdate() {
val jsObj = JSObject()
jsObj.put("source", latestSource)
jsObj.put("timestamp", latestTimestamp.takeIf { it > 0 } ?: System.currentTimeMillis())
val gpsData = JSObject()
gpsData.put("latitude", latestLatitude)
gpsData.put("longitude", latestLongitude)
gpsData.put("accuracy", latestAccuracy)
latestSatelliteStatus?.let { status ->
gpsData.put("satellitesInView", status.satellitesInView)
gpsData.put("usedInFix", status.usedInFix)
val constellations = JSObject()
status.constellationCounts.forEach { (name, count) ->
constellations.put(name, count)
}
gpsData.put("constellationCounts", constellations)
}
jsObj.put("gpsData", gpsData)
val wifiData = JSObject()
latestWifiScanResult?.let { result ->
wifiData.put("apCount", result.apCount)
val apsArray = JSArray()
result.aps.forEach { ap ->
val apObj = JSObject()
apObj.put("ssid", ap.ssid)
apObj.put("bssid", ap.bssid)
apObj.put("rssi", ap.rssi)
ap.distance?.let { d -> apObj.put("distance", d) }
apsArray.put(apObj)
}
wifiData.put("aps", apsArray)
}
jsObj.put("wifiData", wifiData)
val imuData = JSObject()
imuData.put("accelX", latestAccel[0])
imuData.put("accelY", latestAccel[1])
imuData.put("accelZ", latestAccel[2])
imuData.put("gyroX", latestGyro[0])
imuData.put("gyroY", latestGyro[1])
imuData.put("gyroZ", latestGyro[2])
jsObj.put("imuData", imuData)
notifyListeners("onPositionUpdate", jsObj)
}
}

View File

@ -0,0 +1,160 @@
package com.dumon.plugin.geolocation
import android.Manifest
import com.getcapacitor.*
import com.getcapacitor.annotation.CapacitorPlugin
import com.getcapacitor.annotation.Permission
import com.dumon.plugin.geolocation.gps.GpsStatusManager
import com.dumon.plugin.geolocation.gps.SatelliteStatus
import com.dumon.plugin.geolocation.imu.ImuSensorManager
import com.dumon.plugin.geolocation.wifi.WifiPositioningManager
import com.dumon.plugin.geolocation.wifi.WifiScanResult
import com.dumon.plugin.geolocation.fusion.SensorFusionManager
import org.json.JSONArray
import org.json.JSONObject
@CapacitorPlugin(
name = "DumonGeolocation",
permissions = [
Permission(strings = [
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.CHANGE_WIFI_STATE,
Manifest.permission.NEARBY_WIFI_DEVICES
])
]
)
class DumonGeolocation : Plugin() {
private var gpsManager: GpsStatusManager? = null
private var imuManager: ImuSensorManager? = null
private var wifiManager: WifiPositioningManager? = null
private var fusionManager: SensorFusionManager? = null
private var latestLatitude = 0.0
private var latestLongitude = 0.0
private var latestAccuracy = 999.0
private var latestSource = "GNSS"
private var latestTimestamp: Long = 0L
private var latestAccel = floatArrayOf(0f, 0f, 0f)
private var latestGyro = floatArrayOf(0f, 0f, 0f)
private var satelliteStatus: SatelliteStatus? = null
private var wifiScanResult: WifiScanResult? = null
override fun load() {
gpsManager = GpsStatusManager(
context,
onSatelliteStatusUpdate = { satelliteStatus = it },
onLocationUpdate = {
latestLatitude = it.latitude
latestLongitude = it.longitude
latestAccuracy = it.accuracy.toDouble()
latestSource = "GNSS"
latestTimestamp = it.time
emitPositionUpdate()
}
)
imuManager = ImuSensorManager(
context,
onAccelerometerUpdate = { latestAccel = floatArrayOf(0.1f, 0.2f, 0.3f) },
onGyroscopeUpdate = { latestGyro = floatArrayOf(0.01f, 0.02f, 0.03f) }
)
wifiManager = WifiPositioningManager(
context,
onWifiPositioningUpdate = { wifiScanResult = it }
)
fusionManager = SensorFusionManager { lat, lon ->
latestLatitude = lat
latestLongitude = lon
latestAccuracy = 3.0
latestSource = "FUSED"
latestTimestamp = System.currentTimeMillis()
emitPositionUpdate()
}
}
@PluginMethod
fun startPositioning(call: PluginCall) {
gpsManager?.start()
imuManager?.start()
wifiManager?.startPeriodicScan(3000L)
call.resolve()
}
@PluginMethod
fun stopPositioning(call: PluginCall) {
gpsManager?.stop()
imuManager?.stop()
wifiManager?.stopPeriodicScan()
call.resolve()
}
@PluginMethod
fun getLatestPosition(call: PluginCall) {
call.resolve(buildPositionData())
}
private fun emitPositionUpdate() {
notifyListeners("onPositionUpdate", buildPositionData())
}
private fun buildPositionData(): JSObject {
val obj = JSObject()
obj.put("source", latestSource)
obj.put("timestamp", latestTimestamp.takeIf { it > 0 } ?: System.currentTimeMillis())
// GPS block
val gps = JSObject().apply {
put("latitude", latestLatitude)
put("longitude", latestLongitude)
put("accuracy", latestAccuracy)
}
satelliteStatus?.let {
gps.put("satellitesInView", it.satellitesInView)
gps.put("usedInFix", it.usedInFix)
val constellations = JSObject()
it.constellationCounts.forEach { (k, v) -> constellations.put(k, v) }
gps.put("constellationCounts", constellations)
}
// Wi-Fi block
val wifi = JSObject().apply {
wifiScanResult?.let {
put("apCount", it.apCount)
val aps = JSArray()
it.aps.forEach { ap ->
val a = JSObject().apply {
put("ssid", ap.ssid)
put("bssid", ap.bssid)
put("rssi", ap.rssi)
ap.distance?.let { d -> put("distance", d) }
}
aps.put(a)
}
put("aps", aps)
}
}
// IMU block
val imu = JSObject().apply {
put("accelX", latestAccel[0])
put("accelY", latestAccel[1])
put("accelZ", latestAccel[2])
put("gyroX", latestGyro[0])
put("gyroY", latestGyro[1])
put("gyroZ", latestGyro[2])
}
obj.put("gpsData", gps)
obj.put("wifiData", wifi)
obj.put("imuData", imu)
return obj
}
}

View File

@ -0,0 +1,160 @@
package com.dumon.plugin.geolocation
import android.Manifest
import com.getcapacitor.*
import com.getcapacitor.annotation.CapacitorPlugin
import com.getcapacitor.annotation.Permission
import com.dumon.plugin.geolocation.gps.GpsStatusManager
import com.dumon.plugin.geolocation.gps.SatelliteStatus
import com.dumon.plugin.geolocation.imu.ImuSensorManager
import com.dumon.plugin.geolocation.wifi.WifiPositioningManager
import com.dumon.plugin.geolocation.wifi.WifiScanResult
import com.dumon.plugin.geolocation.fusion.SensorFusionManager
import org.json.JSONArray
import org.json.JSONObject
@CapacitorPlugin(
name = "DumonGeolocation",
permissions = [
Permission(strings = [
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.CHANGE_WIFI_STATE,
Manifest.permission.NEARBY_WIFI_DEVICES
])
]
)
class DumonGeolocation : Plugin() {
private var gpsManager: GpsStatusManager? = null
private var imuManager: ImuSensorManager? = null
private var wifiManager: WifiPositioningManager? = null
private var fusionManager: SensorFusionManager? = null
private var latestLatitude = 0.0
private var latestLongitude = 0.0
private var latestAccuracy = 999.0
private var latestSource = "GNSS"
private var latestTimestamp: Long = 0L
private var latestAccel = floatArrayOf(0f, 0f, 0f)
private var latestGyro = floatArrayOf(0f, 0f, 0f)
private var satelliteStatus: SatelliteStatus? = null
private var wifiScanResult: WifiScanResult? = null
override fun load() {
gpsManager = GpsStatusManager(
context,
onSatelliteStatusUpdate = { satelliteStatus = it },
onLocationUpdate = { location, isMocked ->
latestLatitude = location.latitude
latestLongitude = location.longitude
latestAccuracy = location.accuracy.toDouble()
latestSource = if (isMocked) "MOCK" else "GNSS"
latestTimestamp = location.time
emitPositionUpdate()
}
)
imuManager = ImuSensorManager(
context,
onAccelerometerUpdate = { latestAccel = floatArrayOf(0.1f, 0.2f, 0.3f) },
onGyroscopeUpdate = { latestGyro = floatArrayOf(0.01f, 0.02f, 0.03f) }
)
wifiManager = WifiPositioningManager(
context,
onWifiPositioningUpdate = { wifiScanResult = it }
)
fusionManager = SensorFusionManager { lat, lon ->
latestLatitude = lat
latestLongitude = lon
latestAccuracy = 3.0
latestSource = "FUSED"
latestTimestamp = System.currentTimeMillis()
emitPositionUpdate()
}
}
@PluginMethod
fun startPositioning(call: PluginCall) {
gpsManager?.start()
imuManager?.start()
wifiManager?.startPeriodicScan(3000L)
call.resolve()
}
@PluginMethod
fun stopPositioning(call: PluginCall) {
gpsManager?.stop()
imuManager?.stop()
wifiManager?.stopPeriodicScan()
call.resolve()
}
@PluginMethod
fun getLatestPosition(call: PluginCall) {
call.resolve(buildPositionData())
}
private fun emitPositionUpdate() {
notifyListeners("onPositionUpdate", buildPositionData())
}
private fun buildPositionData(): JSObject {
val obj = JSObject()
obj.put("source", latestSource)
obj.put("timestamp", latestTimestamp.takeIf { it > 0 } ?: System.currentTimeMillis())
// GPS block
val gps = JSObject().apply {
put("latitude", latestLatitude)
put("longitude", latestLongitude)
put("accuracy", latestAccuracy)
}
satelliteStatus?.let {
gps.put("satellitesInView", it.satellitesInView)
gps.put("usedInFix", it.usedInFix)
val constellations = JSObject()
it.constellationCounts.forEach { (k, v) -> constellations.put(k, v) }
gps.put("constellationCounts", constellations)
}
// Wi-Fi block
val wifi = JSObject().apply {
wifiScanResult?.let {
put("apCount", it.apCount)
val aps = JSArray()
it.aps.forEach { ap ->
val a = JSObject().apply {
put("ssid", ap.ssid)
put("bssid", ap.bssid)
put("rssi", ap.rssi)
ap.distance?.let { d -> put("distance", d) }
}
aps.put(a)
}
put("aps", aps)
}
}
// IMU block
val imu = JSObject().apply {
put("accelX", latestAccel[0])
put("accelY", latestAccel[1])
put("accelZ", latestAccel[2])
put("gyroX", latestGyro[0])
put("gyroY", latestGyro[1])
put("gyroZ", latestGyro[2])
}
obj.put("gpsData", gps)
obj.put("wifiData", wifi)
obj.put("imuData", imu)
return obj
}
}

View File

@ -0,0 +1,156 @@
package com.dumon.plugin.geolocation
import android.Manifest
import com.getcapacitor.*
import com.getcapacitor.annotation.CapacitorPlugin
import com.getcapacitor.annotation.Permission
import com.dumon.plugin.geolocation.gps.GpsStatusManager
import com.dumon.plugin.geolocation.gps.SatelliteStatus
import com.dumon.plugin.geolocation.imu.ImuData
import com.dumon.plugin.geolocation.imu.ImuSensorManager
import com.dumon.plugin.geolocation.wifi.WifiPositioningManager
import com.dumon.plugin.geolocation.wifi.WifiScanResult
import com.dumon.plugin.geolocation.fusion.SensorFusionManager
import org.json.JSONArray
import org.json.JSONObject
@CapacitorPlugin(
name = "DumonGeolocation",
permissions = [
Permission(strings = [
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.CHANGE_WIFI_STATE,
Manifest.permission.NEARBY_WIFI_DEVICES
])
]
)
class DumonGeolocation : Plugin() {
private var gpsManager: GpsStatusManager? = null
private var imuManager: ImuSensorManager? = null
private var wifiManager: WifiPositioningManager? = null
private var fusionManager: SensorFusionManager? = null
private var latestLatitude = 0.0
private var latestLongitude = 0.0
private var latestAccuracy = 999.0
private var latestSource = "GNSS"
private var latestTimestamp: Long = 0L
private var latestImu: ImuData? = null
private var satelliteStatus: SatelliteStatus? = null
private var wifiScanResult: WifiScanResult? = null
override fun load() {
gpsManager = GpsStatusManager(
context,
onSatelliteStatusUpdate = { satelliteStatus = it },
onLocationUpdate = { location, isMocked ->
latestLatitude = location.latitude
latestLongitude = location.longitude
latestAccuracy = location.accuracy.toDouble()
latestSource = if (isMocked) "MOCK" else "GNSS"
latestTimestamp = location.time
fusionManager?.updateGpsPosition(latestLatitude, latestLongitude)
emitPositionUpdate()
}
)
imuManager = ImuSensorManager(
context,
onImuUpdate = { latestImu = it }
)
wifiManager = WifiPositioningManager(
context,
onWifiPositioningUpdate = { wifiScanResult = it }
)
fusionManager = SensorFusionManager { lat, lon ->
latestLatitude = lat
latestLongitude = lon
latestAccuracy = 3.0
latestSource = "FUSED"
latestTimestamp = System.currentTimeMillis()
emitPositionUpdate()
}
}
@PluginMethod
fun startPositioning(call: PluginCall) {
gpsManager?.start()
imuManager?.start()
wifiManager?.startPeriodicScan(3000L)
call.resolve()
}
@PluginMethod
fun stopPositioning(call: PluginCall) {
gpsManager?.stop()
imuManager?.stop()
wifiManager?.stopPeriodicScan()
call.resolve()
}
@PluginMethod
fun getLatestPosition(call: PluginCall) {
call.resolve(buildPositionData())
}
private fun emitPositionUpdate() {
notifyListeners("onPositionUpdate", buildPositionData())
}
private fun buildPositionData(): JSObject {
val obj = JSObject()
obj.put("source", latestSource)
obj.put("timestamp", if (latestTimestamp > 0) latestTimestamp else System.currentTimeMillis())
obj.put("latitude", latestLatitude)
obj.put("longitude", latestLongitude)
obj.put("accuracy", latestAccuracy)
// GPS metadata
satelliteStatus?.let {
val gnss = JSObject()
gnss.put("satellitesInView", it.satellitesInView)
gnss.put("usedInFix", it.usedInFix)
val constellations = JSObject()
it.constellationCounts.forEach { (k, v) -> constellations.put(k, v) }
gnss.put("constellationCounts", constellations)
obj.put("gnssData", gnss)
}
// Wi-Fi metadata
wifiScanResult?.let {
val wifi = JSArray()
it.aps.forEach { ap ->
val a = JSObject()
a.put("ssid", ap.ssid)
a.put("bssid", ap.bssid)
a.put("rssi", ap.rssi)
ap.distance?.let { d -> a.put("distance", d) }
wifi.put(a)
}
obj.put("wifiData", wifi)
}
// IMU metadata
latestImu?.let {
val imu = JSObject()
imu.put("accelX", it.accelX)
imu.put("accelY", it.accelY)
imu.put("accelZ", it.accelZ)
imu.put("gyroX", it.gyroX)
imu.put("gyroY", it.gyroY)
imu.put("gyroZ", it.gyroZ)
imu.put("speed", it.speed)
imu.put("acceleration", it.acceleration)
imu.put("directionRad", it.directionRad)
obj.put("imuData", imu)
}
return obj
}
}

View File

@ -0,0 +1,160 @@
package com.dumon.plugin.geolocation
import android.Manifest
import com.getcapacitor.*
import com.getcapacitor.annotation.CapacitorPlugin
import com.getcapacitor.annotation.Permission
import com.dumon.plugin.geolocation.gps.GpsStatusManager
import com.dumon.plugin.geolocation.gps.SatelliteStatus
import com.dumon.plugin.geolocation.imu.ImuData
import com.dumon.plugin.geolocation.imu.ImuSensorManager
import com.dumon.plugin.geolocation.wifi.WifiPositioningManager
import com.dumon.plugin.geolocation.wifi.WifiScanResult
import com.dumon.plugin.geolocation.fusion.SensorFusionManager
import org.json.JSONArray
import org.json.JSONObject
@CapacitorPlugin(
name = "DumonGeolocation",
permissions = [
Permission(strings = [
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.CHANGE_WIFI_STATE,
Manifest.permission.NEARBY_WIFI_DEVICES
])
]
)
class DumonGeolocation : Plugin() {
private var gpsManager: GpsStatusManager? = null
private var imuManager: ImuSensorManager? = null
private var wifiManager: WifiPositioningManager? = null
private var fusionManager: SensorFusionManager? = null
private var latestLatitude = 0.0
private var latestLongitude = 0.0
private var latestAccuracy = 999.0
private var latestSource = "GNSS"
private var latestTimestamp: Long = 0L
private var latestImu: ImuData? = null
private var satelliteStatus: SatelliteStatus? = null
private var wifiScanResult: WifiScanResult? = null
private var isMockedLocation = false
override fun load() {
gpsManager = GpsStatusManager(
context,
onSatelliteStatusUpdate = { satelliteStatus = it },
onLocationUpdate = { location, isMocked ->
latestLatitude = location.latitude
latestLongitude = location.longitude
latestAccuracy = location.accuracy.toDouble()
latestSource = if (isMocked) "MOCK" else "GNSS"
isMockedLocation = isMocked // sudah tersedia dari GpsStatusManager
latestTimestamp = location.time
fusionManager?.updateGpsPosition(latestLatitude, latestLongitude)
emitPositionUpdate()
}
)
imuManager = ImuSensorManager(
context,
onImuUpdate = { latestImu = it }
)
wifiManager = WifiPositioningManager(
context,
onWifiPositioningUpdate = { wifiScanResult = it }
)
fusionManager = SensorFusionManager { lat, lon ->
latestLatitude = lat
latestLongitude = lon
latestAccuracy = 3.0
latestSource = "FUSED"
latestTimestamp = System.currentTimeMillis()
emitPositionUpdate()
}
}
@PluginMethod
fun startPositioning(call: PluginCall) {
gpsManager?.start()
imuManager?.start()
wifiManager?.startPeriodicScan(3000L)
call.resolve()
}
@PluginMethod
fun stopPositioning(call: PluginCall) {
gpsManager?.stop()
imuManager?.stop()
wifiManager?.stopPeriodicScan()
call.resolve()
}
@PluginMethod
fun getLatestPosition(call: PluginCall) {
call.resolve(buildPositionData())
}
private fun emitPositionUpdate() {
notifyListeners("onPositionUpdate", buildPositionData())
}
private fun buildPositionData(): JSObject {
val obj = JSObject()
obj.put("source", latestSource)
obj.put("timestamp", if (latestTimestamp > 0) latestTimestamp else System.currentTimeMillis())
obj.put("latitude", latestLatitude)
obj.put("longitude", latestLongitude)
obj.put("accuracy", latestAccuracy)
obj.put("isMocked", isMockedLocation)
// GPS metadata
satelliteStatus?.let {
val gnss = JSObject()
gnss.put("satellitesInView", it.satellitesInView)
gnss.put("usedInFix", it.usedInFix)
val constellations = JSObject()
it.constellationCounts.forEach { (k, v) -> constellations.put(k, v) }
gnss.put("constellationCounts", constellations)
obj.put("gnssData", gnss)
}
// Wi-Fi metadata
wifiScanResult?.let {
val wifi = JSArray()
it.aps.forEach { ap ->
val a = JSObject()
a.put("ssid", ap.ssid)
a.put("bssid", ap.bssid)
a.put("rssi", ap.rssi)
ap.distance?.let { d -> a.put("distance", d) }
wifi.put(a)
}
obj.put("wifiData", wifi)
}
// IMU metadata
latestImu?.let {
val imu = JSObject()
imu.put("accelX", it.accelX)
imu.put("accelY", it.accelY)
imu.put("accelZ", it.accelZ)
imu.put("gyroX", it.gyroX)
imu.put("gyroY", it.gyroY)
imu.put("gyroZ", it.gyroZ)
imu.put("speed", it.speed)
imu.put("acceleration", it.acceleration)
imu.put("directionRad", it.directionRad)
obj.put("imuData", imu)
}
return obj
}
}

View File

@ -0,0 +1,166 @@
package com.dumon.plugin.geolocation
import android.Manifest
import com.getcapacitor.*
import com.getcapacitor.annotation.CapacitorPlugin
import com.getcapacitor.annotation.Permission
import com.dumon.plugin.geolocation.gps.GpsStatusManager
import com.dumon.plugin.geolocation.gps.SatelliteStatus
import com.dumon.plugin.geolocation.imu.ImuData
import com.dumon.plugin.geolocation.imu.ImuSensorManager
import com.dumon.plugin.geolocation.wifi.WifiPositioningManager
import com.dumon.plugin.geolocation.wifi.WifiScanResult
import com.dumon.plugin.geolocation.fusion.SensorFusionManager
import org.json.JSONArray
import org.json.JSONObject
@CapacitorPlugin(
name = "DumonGeolocation",
permissions = [
Permission(strings = [
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.CHANGE_WIFI_STATE,
Manifest.permission.NEARBY_WIFI_DEVICES
])
]
)
class DumonGeolocation : Plugin() {
private var gpsManager: GpsStatusManager? = null
private var imuManager: ImuSensorManager? = null
private var wifiManager: WifiPositioningManager? = null
private var fusionManager: SensorFusionManager? = null
private var latestLatitude = 0.0
private var latestLongitude = 0.0
private var latestAccuracy = 999.0
private var latestSource = "GNSS"
private var latestTimestamp: Long = 0L
private var latestImu: ImuData? = null
private var satelliteStatus: SatelliteStatus? = null
private var wifiScanResult: WifiScanResult? = null
private var isMockedLocation = false
override fun load() {
gpsManager = GpsStatusManager(
context,
onSatelliteStatusUpdate = { satelliteStatus = it },
onLocationUpdate = { location, isMocked ->
latestLatitude = location.latitude
latestLongitude = location.longitude
latestAccuracy = location.accuracy.toDouble()
latestSource = if (isMocked) "MOCK" else "GNSS"
isMockedLocation = isMocked // sudah tersedia dari GpsStatusManager
latestTimestamp = location.time
fusionManager?.updateGpsPosition(latestLatitude, latestLongitude)
emitPositionUpdate()
}
)
imuManager = ImuSensorManager(
context,
onImuUpdate = {
latestImu = it
emitPositionUpdate()
}
)
wifiManager = WifiPositioningManager(
context,
onWifiPositioningUpdate = {
wifiScanResult = it
emitPositionUpdate()
}
)
fusionManager = SensorFusionManager { lat, lon ->
latestLatitude = lat
latestLongitude = lon
latestAccuracy = 3.0
latestSource = "FUSED"
latestTimestamp = System.currentTimeMillis()
emitPositionUpdate()
}
}
@PluginMethod
fun startPositioning(call: PluginCall) {
gpsManager?.start()
imuManager?.start()
wifiManager?.startPeriodicScan(3000L)
call.resolve()
}
@PluginMethod
fun stopPositioning(call: PluginCall) {
gpsManager?.stop()
imuManager?.stop()
wifiManager?.stopPeriodicScan()
call.resolve()
}
@PluginMethod
fun getLatestPosition(call: PluginCall) {
call.resolve(buildPositionData())
}
private fun emitPositionUpdate() {
notifyListeners("onPositionUpdate", buildPositionData())
}
private fun buildPositionData(): JSObject {
val obj = JSObject()
obj.put("source", latestSource)
obj.put("timestamp", if (latestTimestamp > 0) latestTimestamp else System.currentTimeMillis())
obj.put("latitude", latestLatitude)
obj.put("longitude", latestLongitude)
obj.put("accuracy", latestAccuracy)
obj.put("isMocked", isMockedLocation)
// GPS metadata
satelliteStatus?.let {
val gnss = JSObject()
gnss.put("satellitesInView", it.satellitesInView)
gnss.put("usedInFix", it.usedInFix)
val constellations = JSObject()
it.constellationCounts.forEach { (k, v) -> constellations.put(k, v) }
gnss.put("constellationCounts", constellations)
obj.put("gnssData", gnss)
}
// Wi-Fi metadata
wifiScanResult?.let {
val wifi = JSArray()
it.aps.forEach { ap ->
val a = JSObject()
a.put("ssid", ap.ssid)
a.put("bssid", ap.bssid)
a.put("rssi", ap.rssi)
ap.distance?.let { d -> a.put("distance", d) }
wifi.put(a)
}
obj.put("wifiData", wifi)
}
// IMU metadata
latestImu?.let {
val imu = JSObject()
imu.put("accelX", it.accelX)
imu.put("accelY", it.accelY)
imu.put("accelZ", it.accelZ)
imu.put("gyroX", it.gyroX)
imu.put("gyroY", it.gyroY)
imu.put("gyroZ", it.gyroZ)
imu.put("speed", it.speed)
imu.put("acceleration", it.acceleration)
imu.put("directionRad", it.directionRad)
obj.put("imuData", imu)
}
return obj
}
}

View File

@ -0,0 +1,166 @@
package com.dumon.plugin.geolocation
import android.Manifest
import com.getcapacitor.*
import com.getcapacitor.annotation.CapacitorPlugin
import com.getcapacitor.annotation.Permission
import com.dumon.plugin.geolocation.gps.GpsStatusManager
import com.dumon.plugin.geolocation.gps.SatelliteStatus
import com.dumon.plugin.geolocation.imu.ImuData
import com.dumon.plugin.geolocation.imu.ImuSensorManager
import com.dumon.plugin.geolocation.wifi.WifiPositioningManager
import com.dumon.plugin.geolocation.wifi.WifiScanResult
import com.dumon.plugin.geolocation.fusion.SensorFusionManager
import org.json.JSONArray
import org.json.JSONObject
@CapacitorPlugin(
name = "DumonGeolocation",
permissions = [
Permission(strings = [
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.CHANGE_WIFI_STATE,
Manifest.permission.NEARBY_WIFI_DEVICES
])
]
)
class DumonGeolocation : Plugin() {
private var gpsManager: GpsStatusManager? = null
private var imuManager: ImuSensorManager? = null
private var wifiManager: WifiPositioningManager? = null
private var fusionManager: SensorFusionManager? = null
private var latestLatitude = 0.0
private var latestLongitude = 0.0
private var latestAccuracy = 999.0
private var latestSource = "GNSS"
private var latestTimestamp: Long = 0L
private var latestImu: ImuData? = null
private var satelliteStatus: SatelliteStatus? = null
private var wifiScanResult: WifiScanResult? = null
private var isMockedLocation = false
override fun load() {
gpsManager = GpsStatusManager(
context,
onSatelliteStatusUpdate = { satelliteStatus = it },
onLocationUpdate = { location, isMocked ->
latestLatitude = location.latitude
latestLongitude = location.longitude
latestAccuracy = location.accuracy.toDouble()
latestSource = if (isMocked) "MOCK" else "GNSS"
isMockedLocation = isMocked // sudah tersedia dari GpsStatusManager
latestTimestamp = location.time
fusionManager?.updateGpsPosition(latestLatitude, latestLongitude)
emitPositionUpdate()
}
)
imuManager = ImuSensorManager(
context,
onImuUpdate = {
latestImu = it
emitPositionUpdate()
}
)
wifiManager = WifiPositioningManager(
context,
onWifiPositioningUpdate = {
wifiScanResult = it
emitPositionUpdate()
}
)
fusionManager = SensorFusionManager { lat, lon ->
latestLatitude = lat
latestLongitude = lon
latestAccuracy = 3.0
latestSource = "FUSED"
latestTimestamp = System.currentTimeMillis()
emitPositionUpdate()
}
}
@PluginMethod
fun startPositioning(call: PluginCall) {
gpsManager?.start()
imuManager?.start()
wifiManager?.startPeriodicScan(3000L)
call.resolve()
}
@PluginMethod
fun stopPositioning(call: PluginCall) {
gpsManager?.stop()
imuManager?.stop()
wifiManager?.stopPeriodicScan()
call.resolve()
}
@PluginMethod
fun getLatestPosition(call: PluginCall) {
call.resolve(buildPositionData())
}
private fun emitPositionUpdate() {
notifyListeners("onPositionUpdate", buildPositionData())
}
private fun buildPositionData(): JSObject {
val obj = JSObject()
obj.put("source", latestSource)
obj.put("timestamp", if (latestTimestamp > 0) latestTimestamp else System.currentTimeMillis())
obj.put("latitude", latestLatitude)
obj.put("longitude", latestLongitude)
obj.put("accuracy", latestAccuracy)
obj.put("isMocked", isMockedLocation)
// GPS metadata
satelliteStatus?.let {
val gnss = JSObject()
gnss.put("satellitesInView", it.satellitesInView)
gnss.put("usedInFix", it.usedInFix)
val constellations = JSObject()
it.constellationCounts.forEach { (k, v) -> constellations.put(k, v) }
gnss.put("constellationCounts", constellations)
obj.put("gnssData", gnss)
}
// Wi-Fi metadata
wifiScanResult?.let {
val wifi = JSArray()
it.aps.forEach { ap ->
val a = JSObject()
a.put("ssid", ap.ssid)
a.put("bssid", ap.bssid)
a.put("rssi", ap.rssi)
ap.distance?.let { d -> a.put("distance", d) }
wifi.put(a)
}
obj.put("wifiData", wifi)
}
// IMU metadata
latestImu?.let {
val imu = JSObject()
imu.put("accelX", it.accelX)
imu.put("accelY", it.accelY)
imu.put("accelZ", it.accelZ)
imu.put("gyroX", it.gyroX)
imu.put("gyroY", it.gyroY)
imu.put("gyroZ", it.gyroZ)
imu.put("speed", it.speed)
imu.put("acceleration", it.acceleration)
imu.put("directionRad", it.directionRad)
obj.put("imuData", imu)
}
return obj
}
}

View File

@ -0,0 +1,168 @@
package com.dumon.plugin.geolocation
import android.Manifest
import com.getcapacitor.*
import com.getcapacitor.annotation.CapacitorPlugin
import com.getcapacitor.annotation.Permission
import com.dumon.plugin.geolocation.gps.GpsStatusManager
import com.dumon.plugin.geolocation.gps.SatelliteStatus
import com.dumon.plugin.geolocation.imu.ImuData
import com.dumon.plugin.geolocation.imu.ImuSensorManager
import com.dumon.plugin.geolocation.wifi.WifiPositioningManager
import com.dumon.plugin.geolocation.wifi.WifiScanResult
import com.dumon.plugin.geolocation.fusion.SensorFusionManager
import org.json.JSONArray
import org.json.JSONObject
@CapacitorPlugin(
name = "DumonGeolocation",
permissions = [
Permission(strings = [
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.CHANGE_WIFI_STATE,
Manifest.permission.NEARBY_WIFI_DEVICES
])
]
)
class DumonGeolocation : Plugin() {
private var gpsManager: GpsStatusManager? = null
private var imuManager: ImuSensorManager? = null
private var wifiManager: WifiPositioningManager? = null
private var fusionManager: SensorFusionManager? = null
private var latestLatitude = 0.0
private var latestLongitude = 0.0
private var latestAccuracy = 999.0
private var latestSource = "GNSS"
private var latestTimestamp: Long = 0L
private var latestImu: ImuData? = null
private var satelliteStatus: SatelliteStatus? = null
private var wifiScanResult: WifiScanResult? = null
private var isMockedLocation = false
private var lastEmitTimestamp: Long = 0L
private val emitIntervalMs: Long = 500L
override fun load() {
gpsManager = GpsStatusManager(
context,
onSatelliteStatusUpdate = { satelliteStatus = it },
onLocationUpdate = { location, isMocked ->
latestLatitude = location.latitude
latestLongitude = location.longitude
latestAccuracy = location.accuracy.toDouble()
latestSource = if (isMocked) "MOCK" else "GNSS"
isMockedLocation = isMocked
latestTimestamp = location.time
fusionManager?.updateGpsPosition(latestLatitude, latestLongitude)
emitPositionUpdate()
}
)
imuManager = ImuSensorManager(
context,
onImuUpdate = {
latestImu = it
emitPositionUpdate()
}
)
wifiManager = WifiPositioningManager(
context,
onWifiPositioningUpdate = {
wifiScanResult = it
emitPositionUpdate()
}
)
fusionManager = SensorFusionManager { lat, lon ->
latestLatitude = lat
latestLongitude = lon
latestAccuracy = 3.0
latestSource = "FUSED"
latestTimestamp = System.currentTimeMillis()
emitPositionUpdate()
}
}
@PluginMethod
fun startPositioning(call: PluginCall) {
gpsManager?.start()
imuManager?.start()
wifiManager?.startPeriodicScan(3000L)
call.resolve()
}
@PluginMethod
fun stopPositioning(call: PluginCall) {
gpsManager?.stop()
imuManager?.stop()
wifiManager?.stopPeriodicScan()
call.resolve()
}
@PluginMethod
fun getLatestPosition(call: PluginCall) {
call.resolve(buildPositionData())
}
private fun emitPositionUpdate() {
val now = System.currentTimeMillis()
if (now - lastEmitTimestamp < emitIntervalMs) return
lastEmitTimestamp = now
notifyListeners("onPositionUpdate", buildPositionData())
}
private fun buildPositionData(): JSObject {
val obj = JSObject()
obj.put("source", latestSource)
obj.put("timestamp", if (latestTimestamp > 0) latestTimestamp else System.currentTimeMillis())
obj.put("latitude", latestLatitude)
obj.put("longitude", latestLongitude)
obj.put("accuracy", latestAccuracy)
obj.put("isMocked", isMockedLocation)
satelliteStatus?.let {
val gnss = JSObject()
gnss.put("satellitesInView", it.satellitesInView)
gnss.put("usedInFix", it.usedInFix)
val constellations = JSObject()
it.constellationCounts.forEach { (k, v) -> constellations.put(k, v) }
gnss.put("constellationCounts", constellations)
obj.put("gnssData", gnss)
}
wifiScanResult?.let {
val wifi = JSArray()
it.aps.forEach { ap ->
val a = JSObject()
a.put("ssid", ap.ssid)
a.put("bssid", ap.bssid)
a.put("rssi", ap.rssi)
ap.distance?.let { d -> a.put("distance", d) }
wifi.put(a)
}
obj.put("wifiData", wifi)
}
latestImu?.let {
val imu = JSObject()
imu.put("accelX", it.accelX)
imu.put("accelY", it.accelY)
imu.put("accelZ", it.accelZ)
imu.put("gyroX", it.gyroX)
imu.put("gyroY", it.gyroY)
imu.put("gyroZ", it.gyroZ)
imu.put("speed", it.speed)
imu.put("acceleration", it.acceleration)
imu.put("directionRad", it.directionRad)
obj.put("imuData", imu)
}
return obj
}
}

View File

@ -0,0 +1,168 @@
package com.dumon.plugin.geolocation
import android.Manifest
import com.getcapacitor.*
import com.getcapacitor.annotation.CapacitorPlugin
import com.getcapacitor.annotation.Permission
import com.dumon.plugin.geolocation.gps.GpsStatusManager
import com.dumon.plugin.geolocation.gps.SatelliteStatus
import com.dumon.plugin.geolocation.imu.ImuData
import com.dumon.plugin.geolocation.imu.ImuSensorManager
import com.dumon.plugin.geolocation.wifi.WifiPositioningManager
import com.dumon.plugin.geolocation.wifi.WifiScanResult
import com.dumon.plugin.geolocation.fusion.SensorFusionManager
import org.json.JSONArray
import org.json.JSONObject
@CapacitorPlugin(
name = "DumonGeolocation",
permissions = [
Permission(strings = [
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.CHANGE_WIFI_STATE,
Manifest.permission.NEARBY_WIFI_DEVICES
])
]
)
class DumonGeolocation : Plugin() {
private var gpsManager: GpsStatusManager? = null
private var imuManager: ImuSensorManager? = null
private var wifiManager: WifiPositioningManager? = null
private var fusionManager: SensorFusionManager? = null
private var latestLatitude = 0.0
private var latestLongitude = 0.0
private var latestAccuracy = 999.0
private var latestSource = "GNSS"
private var latestTimestamp: Long = 0L
private var latestImu: ImuData? = null
private var satelliteStatus: SatelliteStatus? = null
private var wifiScanResult: WifiScanResult? = null
private var isMockedLocation = false
private var lastEmitTimestamp: Long = 0L
private val emitIntervalMs: Long = 500L
override fun load() {
gpsManager = GpsStatusManager(
context,
onSatelliteStatusUpdate = { satelliteStatus = it },
onLocationUpdate = { location, isMocked ->
latestLatitude = location.latitude
latestLongitude = location.longitude
latestAccuracy = location.accuracy.toDouble()
latestSource = if (isMocked) "MOCK" else "GNSS"
isMockedLocation = isMocked
latestTimestamp = location.time
fusionManager?.updateGpsPosition(latestLatitude, latestLongitude)
emitPositionUpdate()
}
)
imuManager = ImuSensorManager(
context,
onImuUpdate = {
latestImu = it
emitPositionUpdate()
}
)
wifiManager = WifiPositioningManager(
context,
onWifiPositioningUpdate = {
wifiScanResult = it
emitPositionUpdate()
}
)
fusionManager = SensorFusionManager { lat, lon ->
latestLatitude = lat
latestLongitude = lon
latestAccuracy = 3.0
latestSource = "FUSED"
latestTimestamp = System.currentTimeMillis()
emitPositionUpdate()
}
}
@PluginMethod
fun startPositioning(call: PluginCall) {
gpsManager?.start()
imuManager?.start()
wifiManager?.startPeriodicScan(3000L)
call.resolve()
}
@PluginMethod
fun stopPositioning(call: PluginCall) {
gpsManager?.stop()
imuManager?.stop()
wifiManager?.stopPeriodicScan()
call.resolve()
}
@PluginMethod
fun getLatestPosition(call: PluginCall) {
call.resolve(buildPositionData())
}
private fun emitPositionUpdate() {
val now = System.currentTimeMillis()
if (now - lastEmitTimestamp < emitIntervalMs) return
lastEmitTimestamp = now
notifyListeners("onPositionUpdate", buildPositionData())
}
private fun buildPositionData(): JSObject {
val obj = JSObject()
obj.put("source", latestSource)
obj.put("timestamp", if (latestTimestamp > 0) latestTimestamp else System.currentTimeMillis())
obj.put("latitude", latestLatitude)
obj.put("longitude", latestLongitude)
obj.put("accuracy", latestAccuracy)
obj.put("isMocked", isMockedLocation)
satelliteStatus?.let {
val gnss = JSObject()
gnss.put("satellitesInView", it.satellitesInView)
gnss.put("usedInFix", it.usedInFix)
val constellations = JSObject()
it.constellationCounts.forEach { (k, v) -> constellations.put(k, v) }
gnss.put("constellationCounts", constellations)
obj.put("gnssData", gnss)
}
latestImu?.let {
val imu = JSObject()
imu.put("accelX", it.accelX)
imu.put("accelY", it.accelY)
imu.put("accelZ", it.accelZ)
imu.put("gyroX", it.gyroX)
imu.put("gyroY", it.gyroY)
imu.put("gyroZ", it.gyroZ)
imu.put("speed", it.speed)
imu.put("acceleration", it.acceleration)
imu.put("directionRad", it.directionRad)
obj.put("imuData", imu)
}
wifiScanResult?.let {
val wifi = JSArray()
it.aps.forEach { ap ->
val a = JSObject()
a.put("ssid", ap.ssid)
a.put("bssid", ap.bssid)
a.put("rssi", ap.rssi)
ap.distance?.let { d -> a.put("distance", d) }
wifi.put(a)
}
obj.put("wifiData", wifi)
}
return obj
}
}

View File

@ -0,0 +1,168 @@
package com.dumon.plugin.geolocation
import android.Manifest
import com.getcapacitor.*
import com.getcapacitor.annotation.CapacitorPlugin
import com.getcapacitor.annotation.Permission
import com.dumon.plugin.geolocation.gps.GpsStatusManager
import com.dumon.plugin.geolocation.gps.SatelliteStatus
import com.dumon.plugin.geolocation.imu.ImuData
import com.dumon.plugin.geolocation.imu.ImuSensorManager
import com.dumon.plugin.geolocation.wifi.WifiPositioningManager
import com.dumon.plugin.geolocation.wifi.WifiScanResult
import com.dumon.plugin.geolocation.fusion.SensorFusionManager
import org.json.JSONArray
import org.json.JSONObject
@CapacitorPlugin(
name = "DumonGeolocation",
permissions = [
Permission(strings = [
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.CHANGE_WIFI_STATE,
Manifest.permission.NEARBY_WIFI_DEVICES
])
]
)
class DumonGeolocation : Plugin() {
private var gpsManager: GpsStatusManager? = null
private var imuManager: ImuSensorManager? = null
private var wifiManager: WifiPositioningManager? = null
private var fusionManager: SensorFusionManager? = null
private var latestLatitude = 0.0
private var latestLongitude = 0.0
private var latestAccuracy = 999.0
private var latestSource = "GNSS"
private var latestTimestamp: Long = 0L
private var latestImu: ImuData? = null
private var satelliteStatus: SatelliteStatus? = null
private var wifiScanResult: WifiScanResult? = null
private var isMockedLocation = false
private var lastEmitTimestamp: Long = 0L
private val emitIntervalMs: Long = 500L
override fun load() {
gpsManager = GpsStatusManager(
context,
onSatelliteStatusUpdate = { satelliteStatus = it },
onLocationUpdate = { location, isMocked ->
latestLatitude = location.latitude
latestLongitude = location.longitude
latestAccuracy = location.accuracy.toDouble()
latestSource = if (isMocked) "MOCK" else "GNSS"
isMockedLocation = isMocked
latestTimestamp = location.time
fusionManager?.updateGpsPosition(latestLatitude, latestLongitude)
emitPositionUpdate()
}
)
imuManager = ImuSensorManager(
context,
onImuUpdate = {
latestImu = it
emitPositionUpdate()
}
)
wifiManager = WifiPositioningManager(
context,
onWifiPositioningUpdate = {
wifiScanResult = it
emitPositionUpdate()
}
)
fusionManager = SensorFusionManager { lat, lon ->
latestLatitude = lat
latestLongitude = lon
latestAccuracy = 3.0
latestSource = "FUSED"
latestTimestamp = System.currentTimeMillis()
emitPositionUpdate()
}
}
@PluginMethod
fun startPositioning(call: PluginCall) {
gpsManager?.start()
imuManager?.start()
wifiManager?.startPeriodicScan(3000L)
call.resolve()
}
@PluginMethod
fun stopPositioning(call: PluginCall) {
gpsManager?.stop()
imuManager?.stop()
wifiManager?.stopPeriodicScan()
call.resolve()
}
@PluginMethod
fun getLatestPosition(call: PluginCall) {
call.resolve(buildPositionData())
}
private fun emitPositionUpdate() {
val now = System.currentTimeMillis()
if (now - lastEmitTimestamp < emitIntervalMs) return
lastEmitTimestamp = now
notifyListeners("onPositionUpdate", buildPositionData())
}
private fun buildPositionData(): JSObject {
val obj = JSObject()
obj.put("source", latestSource)
obj.put("timestamp", if (latestTimestamp > 0) latestTimestamp else System.currentTimeMillis())
obj.put("latitude", latestLatitude)
obj.put("longitude", latestLongitude)
obj.put("accuracy", latestAccuracy)
obj.put("isMocked", isMockedLocation)
satelliteStatus?.let {
val gnss = JSObject()
gnss.put("satellitesInView", it.satellitesInView)
gnss.put("usedInFix", it.usedInFix)
val constellations = JSObject()
it.constellationCounts.forEach { (k, v) -> constellations.put(k, v) }
gnss.put("constellationCounts", constellations)
obj.put("gnssData", gnss)
}
latestImu?.let {
val imu = JSObject()
// imu.put("accelX", it.accelX)
// imu.put("accelY", it.accelY)
// imu.put("accelZ", it.accelZ)
// imu.put("gyroX", it.gyroX)
// imu.put("gyroY", it.gyroY)
// imu.put("gyroZ", it.gyroZ)
imu.put("speed", it.speed)
imu.put("acceleration", it.acceleration)
imu.put("directionRad", it.directionRad)
obj.put("imuData", imu)
}
wifiScanResult?.let {
val wifi = JSArray()
it.aps.forEach { ap ->
val a = JSObject()
a.put("ssid", ap.ssid)
a.put("bssid", ap.bssid)
a.put("rssi", ap.rssi)
ap.distance?.let { d -> a.put("distance", d) }
wifi.put(a)
}
obj.put("wifiData", wifi)
}
return obj
}
}

View File

@ -0,0 +1,168 @@
package com.dumon.plugin.geolocation
import android.Manifest
import com.getcapacitor.*
import com.getcapacitor.annotation.CapacitorPlugin
import com.getcapacitor.annotation.Permission
import com.dumon.plugin.geolocation.gps.GpsStatusManager
import com.dumon.plugin.geolocation.gps.SatelliteStatus
import com.dumon.plugin.geolocation.imu.ImuData
import com.dumon.plugin.geolocation.imu.ImuSensorManager
import com.dumon.plugin.geolocation.wifi.WifiPositioningManager
import com.dumon.plugin.geolocation.wifi.WifiScanResult
import com.dumon.plugin.geolocation.fusion.SensorFusionManager
import org.json.JSONArray
import org.json.JSONObject
@CapacitorPlugin(
name = "DumonGeolocation",
permissions = [
Permission(strings = [
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.CHANGE_WIFI_STATE,
Manifest.permission.NEARBY_WIFI_DEVICES
])
]
)
class DumonGeolocation : Plugin() {
private var gpsManager: GpsStatusManager? = null
private var imuManager: ImuSensorManager? = null
private var wifiManager: WifiPositioningManager? = null
private var fusionManager: SensorFusionManager? = null
private var latestLatitude = 0.0
private var latestLongitude = 0.0
private var latestAccuracy = 999.0
private var latestSource = "GNSS"
private var latestTimestamp: Long = 0L
private var latestImu: ImuData? = null
private var satelliteStatus: SatelliteStatus? = null
private var wifiScanResult: WifiScanResult? = null
private var isMockedLocation = false
private var lastEmitTimestamp: Long = 0L
private val emitIntervalMs: Long = 500L
override fun load() {
gpsManager = GpsStatusManager(
context,
onSatelliteStatusUpdate = { satelliteStatus = it },
onLocationUpdate = { location, isMocked ->
latestLatitude = location.latitude
latestLongitude = location.longitude
latestAccuracy = location.accuracy.toDouble()
latestSource = if (isMocked) "MOCK" else "GNSS"
isMockedLocation = isMocked
latestTimestamp = location.time
fusionManager?.updateGpsPosition(latestLatitude, latestLongitude)
emitPositionUpdate()
}
)
imuManager = ImuSensorManager(
context,
onImuUpdate = {
latestImu = it
emitPositionUpdate()
}
)
wifiManager = WifiPositioningManager(
context,
onWifiPositioningUpdate = {
wifiScanResult = it
emitPositionUpdate()
}
)
fusionManager = SensorFusionManager { lat, lon ->
latestLatitude = lat
latestLongitude = lon
latestAccuracy = 3.0
latestSource = "FUSED"
latestTimestamp = System.currentTimeMillis()
emitPositionUpdate()
}
}
@PluginMethod
fun startPositioning(call: PluginCall) {
gpsManager?.start()
imuManager?.start()
wifiManager?.startPeriodicScan(3000L)
call.resolve()
}
@PluginMethod
fun stopPositioning(call: PluginCall) {
gpsManager?.stop()
imuManager?.stop()
wifiManager?.stopPeriodicScan()
call.resolve()
}
@PluginMethod
fun getLatestPosition(call: PluginCall) {
call.resolve(buildPositionData())
}
private fun emitPositionUpdate() {
val now = System.currentTimeMillis()
if (now - lastEmitTimestamp < emitIntervalMs) return
lastEmitTimestamp = now
notifyListeners("onPositionUpdate", buildPositionData())
}
private fun buildPositionData(): JSObject {
val obj = JSObject()
obj.put("source", latestSource)
obj.put("timestamp", if (latestTimestamp > 0) latestTimestamp else System.currentTimeMillis())
obj.put("latitude", latestLatitude)
obj.put("longitude", latestLongitude)
obj.put("accuracy", latestAccuracy)
obj.put("isMocked", isMockedLocation)
satelliteStatus?.let {
val gnss = JSObject()
gnss.put("satellitesInView", it.satellitesInView)
gnss.put("usedInFix", it.usedInFix)
val constellations = JSObject()
it.constellationCounts.forEach { (k, v) -> constellations.put(k, v) }
gnss.put("constellationCounts", constellations)
obj.put("gnssData", gnss)
}
latestImu?.let {
val imu = JSObject()
// imu.put("accelX", it.accelX)
// imu.put("accelY", it.accelY)
// imu.put("accelZ", it.accelZ)
// imu.put("gyroX", it.gyroX)
// imu.put("gyroY", it.gyroY)
// imu.put("gyroZ", it.gyroZ)
imu.put("speed", it.speed)
imu.put("acceleration", it.acceleration)
imu.put("directionRad", it.directionRad)
obj.put("imuData", imu)
}
wifiScanResult?.let {
val wifi = JSArray()
it.aps.forEach { ap ->
val a = JSObject()
a.put("ssid", ap.ssid)
a.put("bssid", ap.bssid)
a.put("rssi", ap.rssi)
ap.distance?.let { d -> a.put("distance", d) }
wifi.put(a)
}
obj.put("wifiData", wifi)
}
return obj
}
}

View File

@ -0,0 +1,177 @@
package com.dumon.plugin.geolocation
import android.Manifest
import com.getcapacitor.*
import com.getcapacitor.annotation.CapacitorPlugin
import com.getcapacitor.annotation.Permission
import com.dumon.plugin.geolocation.gps.GpsStatusManager
import com.dumon.plugin.geolocation.gps.SatelliteStatus
import com.dumon.plugin.geolocation.imu.ImuData
import com.dumon.plugin.geolocation.imu.ImuSensorManager
import com.dumon.plugin.geolocation.wifi.WifiPositioningManager
import com.dumon.plugin.geolocation.wifi.WifiScanResult
import com.dumon.plugin.geolocation.fusion.SensorFusionManager
import org.json.JSONArray
import org.json.JSONObject
@CapacitorPlugin(
name = "DumonGeolocation",
permissions = [
Permission(strings = [
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.CHANGE_WIFI_STATE,
Manifest.permission.NEARBY_WIFI_DEVICES
])
]
)
class DumonGeolocation : Plugin() {
private var gpsManager: GpsStatusManager? = null
private var imuManager: ImuSensorManager? = null
private var wifiManager: WifiPositioningManager? = null
private var fusionManager: SensorFusionManager? = null
private var latestLatitude = 0.0
private var latestLongitude = 0.0
private var latestAccuracy = 999.0
private var latestSource = "GNSS"
private var latestTimestamp: Long = 0L
private var latestImu: ImuData? = null
private var satelliteStatus: SatelliteStatus? = null
private var wifiScanResult: WifiScanResult? = null
private var isMockedLocation = false
private var lastEmitTimestamp: Long = 0L
private val emitIntervalMs: Long = 500L
override fun load() {
gpsManager = GpsStatusManager(
context,
onSatelliteStatusUpdate = { satelliteStatus = it },
onLocationUpdate = { location, isMocked ->
latestLatitude = location.latitude
latestLongitude = location.longitude
latestAccuracy = location.accuracy.toDouble()
latestSource = if (isMocked) "MOCK" else "GNSS"
isMockedLocation = isMocked
latestTimestamp = location.time
fusionManager?.updateGpsPosition(latestLatitude, latestLongitude)
emitPositionUpdate()
}
)
imuManager = ImuSensorManager(
context,
onImuUpdate = {
latestImu = it
emitPositionUpdate()
}
)
wifiManager = WifiPositioningManager(
context,
onWifiPositioningUpdate = {
wifiScanResult = it
emitPositionUpdate()
}
)
fusionManager = SensorFusionManager { lat, lon ->
latestLatitude = lat
latestLongitude = lon
latestAccuracy = 3.0
latestSource = "FUSED"
latestTimestamp = System.currentTimeMillis()
emitPositionUpdate()
}
}
@PluginMethod
fun startPositioning(call: PluginCall) {
gpsManager?.start()
imuManager?.start()
wifiManager?.startPeriodicScan(3000L)
call.resolve()
}
@PluginMethod
fun stopPositioning(call: PluginCall) {
gpsManager?.stop()
imuManager?.stop()
wifiManager?.stopPeriodicScan()
call.resolve()
}
@PluginMethod
fun getLatestPosition(call: PluginCall) {
call.resolve(buildPositionData())
}
private fun emitPositionUpdate() {
val now = System.currentTimeMillis()
if (now - lastEmitTimestamp < emitIntervalMs) return
lastEmitTimestamp = now
notifyListeners("onPositionUpdate", buildPositionData())
}
private fun buildPositionData(): JSObject {
val obj = JSObject()
obj.put("source", latestSource)
obj.put("timestamp", if (latestTimestamp > 0) latestTimestamp else System.currentTimeMillis())
obj.put("latitude", latestLatitude)
obj.put("longitude", latestLongitude)
obj.put("accuracy", latestAccuracy)
obj.put("isMocked", isMockedLocation)
latestImu?.let {
obj.put("speed", it.speed)
obj.put("acceleration", it.acceleration)
obj.put("directionRad", it.directionRad)
}
// === Full Detail (commented out for future use) ===
/*
satelliteStatus?.let {
val gnss = JSObject()
gnss.put("satellitesInView", it.satellitesInView)
gnss.put("usedInFix", it.usedInFix)
val constellations = JSObject()
it.constellationCounts.forEach { (k, v) -> constellations.put(k, v) }
gnss.put("constellationCounts", constellations)
obj.put("gnssData", gnss)
}
latestImu?.let {
val imu = JSObject()
imu.put("accelX", it.accelX)
imu.put("accelY", it.accelY)
imu.put("accelZ", it.accelZ)
imu.put("gyroX", it.gyroX)
imu.put("gyroY", it.gyroY)
imu.put("gyroZ", it.gyroZ)
imu.put("speed", it.speed)
imu.put("acceleration", it.acceleration)
imu.put("directionRad", it.directionRad)
obj.put("imuData", imu)
}
wifiScanResult?.let {
val wifi = JSArray()
it.aps.forEach { ap ->
val a = JSObject()
a.put("ssid", ap.ssid)
a.put("bssid", ap.bssid)
a.put("rssi", ap.rssi)
ap.distance?.let { d -> a.put("distance", d) }
wifi.put(a)
}
obj.put("wifiData", wifi)
}
*/
return obj
}
}

View File

@ -0,0 +1,62 @@
package com.dumon.plugin.geolocation.fusion
import android.util.Log
class SensorFusionManager(
private val onFusedPositionUpdate: (Double, Double) -> Unit = { _, _ -> }
) {
// Kalman filter state
private var latEstimate = 0.0
private var lonEstimate = 0.0
private var latErrorEstimate = 1.0
private var lonErrorEstimate = 1.0
private val processNoiseBase = 0.01
private val measurementNoise = 3.0 // Estimasi error GPS biasa: ±3m
private var isFirstUpdate = true
private var lastUpdateTimestamp: Long = 0L
fun updateGpsPosition(lat: Double, lon: Double) {
val currentTimestamp = System.currentTimeMillis()
val dtSeconds = if (lastUpdateTimestamp > 0) {
(currentTimestamp - lastUpdateTimestamp) / 1000.0
} else {
1.0
}
lastUpdateTimestamp = currentTimestamp
val processNoise = processNoiseBase * dtSeconds
if (isFirstUpdate) {
latEstimate = lat
lonEstimate = lon
isFirstUpdate = false
} else {
// Kalman update untuk latitude
val latKalmanGain = latErrorEstimate / (latErrorEstimate + measurementNoise)
latEstimate += latKalmanGain * (lat - latEstimate)
latErrorEstimate = (1 - latKalmanGain) * latErrorEstimate + processNoise
// Kalman update untuk longitude
val lonKalmanGain = lonErrorEstimate / (lonErrorEstimate + measurementNoise)
lonEstimate += lonKalmanGain * (lon - lonEstimate)
lonErrorEstimate = (1 - lonKalmanGain) * lonErrorEstimate + processNoise
}
Log.d("SENSOR_FUSION", "Fused Lat: $latEstimate, Lon: $lonEstimate")
// Emit hasil update
onFusedPositionUpdate(latEstimate, lonEstimate)
}
fun reset() {
latEstimate = 0.0
lonEstimate = 0.0
latErrorEstimate = 1.0
lonErrorEstimate = 1.0
isFirstUpdate = true
lastUpdateTimestamp = 0L
}
}

View File

@ -0,0 +1,61 @@
package com.dumon.plugin.geolocation.fusion
import android.util.Log
class SensorFusionManager(
private val onFusedPositionUpdate: (Double, Double) -> Unit = { _, _ -> }
) {
// Kalman filter state
private var latEstimate = 0.0
private var lonEstimate = 0.0
private var latErrorEstimate = 1.0
private var lonErrorEstimate = 1.0
private val processNoiseBase = 0.01
private val measurementNoise = 3.0 // typical GPS error in meters
private var isFirstUpdate = true
private var lastUpdateTimestamp: Long = 0L
fun updateGpsPosition(lat: Double, lon: Double) {
val currentTimestamp = System.currentTimeMillis()
val dtSeconds = if (lastUpdateTimestamp > 0) {
(currentTimestamp - lastUpdateTimestamp) / 1000.0
} else {
1.0
}
lastUpdateTimestamp = currentTimestamp
val processNoise = processNoiseBase * dtSeconds
if (isFirstUpdate) {
latEstimate = lat
lonEstimate = lon
isFirstUpdate = false
} else {
// Kalman update for latitude
val latKalmanGain = latErrorEstimate / (latErrorEstimate + measurementNoise)
latEstimate += latKalmanGain * (lat - latEstimate)
latErrorEstimate = (1 - latKalmanGain) * latErrorEstimate + processNoise
// Kalman update for longitude
val lonKalmanGain = lonErrorEstimate / (lonErrorEstimate + measurementNoise)
lonEstimate += lonKalmanGain * (lon - lonEstimate)
lonErrorEstimate = (1 - lonKalmanGain) * lonErrorEstimate + processNoise
}
Log.d("SENSOR_FUSION", "Fused Lat: $latEstimate, Lon: $lonEstimate")
onFusedPositionUpdate(latEstimate, lonEstimate)
}
fun reset() {
latEstimate = 0.0
lonEstimate = 0.0
latErrorEstimate = 1.0
lonErrorEstimate = 1.0
isFirstUpdate = true
lastUpdateTimestamp = 0L
}
}

View File

@ -0,0 +1,113 @@
package com.dumon.plugin.geolocation.gps
import android.annotation.SuppressLint
import android.content.Context
import android.location.GnssStatus
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import android.os.Bundle
import android.text.format.DateFormat
import android.util.Log
class GpsStatusManager(
private val context: Context,
private val onSatelliteStatusUpdate: (String) -> Unit = {},
private val onLocationUpdate: (String) -> Unit = {}
) {
private val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
private val gnssStatusCallback = object : GnssStatus.Callback() {
override fun onSatelliteStatusChanged(status: GnssStatus) {
val satelliteCount = status.satelliteCount
var usedInFixCount = 0
val constellationCounts = mutableMapOf<Int, Int>()
for (i in 0 until satelliteCount) {
if (status.usedInFix(i)) usedInFixCount++
val constellationType = status.getConstellationType(i)
constellationCounts[constellationType] = constellationCounts.getOrDefault(constellationType, 0) + 1
}
val info = buildString {
append("Satellites in view: $satelliteCount\n")
append("Used in fix: $usedInFixCount\n")
constellationCounts.forEach { (type, count) ->
append("${getConstellationName(type)}$count\n")
}
}
Log.d("GPS_STATUS", info)
onSatelliteStatusUpdate(info)
}
}
private val locationListener = object : LocationListener {
override fun onLocationChanged(location: Location) {
val providerTag = when (location.provider) {
LocationManager.GPS_PROVIDER -> "[GPS]"
LocationManager.NETWORK_PROVIDER -> "[NET]"
else -> "[OTHER]"
}
val timestamp = DateFormat.format("HH:mm:ss", System.currentTimeMillis())
val info = "$providerTag Lat: ${location.latitude}, Lon: ${location.longitude}, Acc: ${location.accuracy} m @ $timestamp"
Log.d("GPS_LOCATION", info)
onLocationUpdate(info)
}
@Deprecated("Deprecated in Java")
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}
override fun onProviderEnabled(provider: String) {}
override fun onProviderDisabled(provider: String) {}
}
@SuppressLint("MissingPermission")
fun start() {
try {
locationManager.registerGnssStatusCallback(gnssStatusCallback, null)
// Request location updates → GPS_PROVIDER
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
1000L, // minTime → 1 detik
0f, // minDistance → 0 meter
locationListener
)
// Fallback → Network Provider
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER,
3000L, // minTime → 3 detik
10f, // minDistance → 10 meter
locationListener
)
Log.d("GPS_STATUS", "GPS + Network location tracking started")
} catch (e: SecurityException) {
Log.e("GPS_STATUS", "Missing permissions", e)
}
}
fun stop() {
locationManager.unregisterGnssStatusCallback(gnssStatusCallback)
locationManager.removeUpdates(locationListener)
Log.d("GPS_STATUS", "GPS tracking stopped")
}
private fun getConstellationName(type: Int): String {
return when (type) {
GnssStatus.CONSTELLATION_GPS -> "GPS"
GnssStatus.CONSTELLATION_SBAS -> "SBAS"
GnssStatus.CONSTELLATION_GLONASS -> "GLONASS"
GnssStatus.CONSTELLATION_QZSS -> "QZSS"
GnssStatus.CONSTELLATION_BEIDOU -> "BEIDOU"
GnssStatus.CONSTELLATION_GALILEO -> "GALILEO"
GnssStatus.CONSTELLATION_IRNSS -> "IRNSS"
else -> "Unknown ($type)"
}
}
}

View File

@ -0,0 +1,113 @@
package com.dumon.plugin.geolocation.gps
import android.annotation.SuppressLint
import android.content.Context
import android.location.GnssStatus
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import android.os.Bundle
import android.text.format.DateFormat
import android.util.Log
class GpsStatusManager(
private val context: Context,
private val onSatelliteStatusUpdate: (String) -> Unit = {},
private val onLocationUpdate: (Location) -> Unit = {}
) {
private val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
private val gnssStatusCallback = object : GnssStatus.Callback() {
override fun onSatelliteStatusChanged(status: GnssStatus) {
val satelliteCount = status.satelliteCount
var usedInFixCount = 0
val constellationCounts = mutableMapOf<Int, Int>()
for (i in 0 until satelliteCount) {
if (status.usedInFix(i)) usedInFixCount++
val constellationType = status.getConstellationType(i)
constellationCounts[constellationType] = constellationCounts.getOrDefault(constellationType, 0) + 1
}
val info = buildString {
append("Satellites in view: $satelliteCount\n")
append("Used in fix: $usedInFixCount\n")
constellationCounts.forEach { (type, count) ->
append("${getConstellationName(type)}$count\n")
}
}
Log.d("GPS_STATUS", info)
onSatelliteStatusUpdate(info)
}
}
private val locationListener = object : LocationListener {
override fun onLocationChanged(location: Location) {
val providerTag = when (location.provider) {
LocationManager.GPS_PROVIDER -> "[GPS]"
LocationManager.NETWORK_PROVIDER -> "[NET]"
else -> "[OTHER]"
}
val timestamp = DateFormat.format("HH:mm:ss", System.currentTimeMillis())
val info = "$providerTag Lat: ${location.latitude}, Lon: ${location.longitude}, Acc: ${location.accuracy} m @ $timestamp"
Log.d("GPS_LOCATION", info)
onLocationUpdate(info)
}
@Deprecated("Deprecated in Java")
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}
override fun onProviderEnabled(provider: String) {}
override fun onProviderDisabled(provider: String) {}
}
@SuppressLint("MissingPermission")
fun start() {
try {
locationManager.registerGnssStatusCallback(gnssStatusCallback, null)
// Request location updates → GPS_PROVIDER
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
1000L, // minTime → 1 detik
0f, // minDistance → 0 meter
locationListener
)
// Fallback → Network Provider
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER,
3000L, // minTime → 3 detik
10f, // minDistance → 10 meter
locationListener
)
Log.d("GPS_STATUS", "GPS + Network location tracking started")
} catch (e: SecurityException) {
Log.e("GPS_STATUS", "Missing permissions", e)
}
}
fun stop() {
locationManager.unregisterGnssStatusCallback(gnssStatusCallback)
locationManager.removeUpdates(locationListener)
Log.d("GPS_STATUS", "GPS tracking stopped")
}
private fun getConstellationName(type: Int): String {
return when (type) {
GnssStatus.CONSTELLATION_GPS -> "GPS"
GnssStatus.CONSTELLATION_SBAS -> "SBAS"
GnssStatus.CONSTELLATION_GLONASS -> "GLONASS"
GnssStatus.CONSTELLATION_QZSS -> "QZSS"
GnssStatus.CONSTELLATION_BEIDOU -> "BEIDOU"
GnssStatus.CONSTELLATION_GALILEO -> "GALILEO"
GnssStatus.CONSTELLATION_IRNSS -> "IRNSS"
else -> "Unknown ($type)"
}
}
}

View File

@ -0,0 +1,113 @@
package com.dumon.plugin.geolocation.gps
import android.annotation.SuppressLint
import android.content.Context
import android.location.GnssStatus
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import android.os.Bundle
import android.text.format.DateFormat
import android.util.Log
class GpsStatusManager(
private val context: Context,
private val onSatelliteStatusUpdate: (String) -> Unit = {},
private val onLocationUpdate: (Location) -> Unit = {}
) {
private val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
private val gnssStatusCallback = object : GnssStatus.Callback() {
override fun onSatelliteStatusChanged(status: GnssStatus) {
val satelliteCount = status.satelliteCount
var usedInFixCount = 0
val constellationCounts = mutableMapOf<Int, Int>()
for (i in 0 until satelliteCount) {
if (status.usedInFix(i)) usedInFixCount++
val constellationType = status.getConstellationType(i)
constellationCounts[constellationType] = constellationCounts.getOrDefault(constellationType, 0) + 1
}
val info = buildString {
append("Satellites in view: $satelliteCount\n")
append("Used in fix: $usedInFixCount\n")
constellationCounts.forEach { (type, count) ->
append("${getConstellationName(type)}$count\n")
}
}
Log.d("GPS_STATUS", info)
onSatelliteStatusUpdate(info)
}
}
private val locationListener = object : LocationListener {
override fun onLocationChanged(location: Location) {
val providerTag = when (location.provider) {
LocationManager.GPS_PROVIDER -> "[GPS]"
LocationManager.NETWORK_PROVIDER -> "[NET]"
else -> "[OTHER]"
}
val timestamp = DateFormat.format("HH:mm:ss", System.currentTimeMillis())
val info = "$providerTag Lat: ${location.latitude}, Lon: ${location.longitude}, Acc: ${location.accuracy} m @ $timestamp"
Log.d("GPS_LOCATION", info)
onLocationUpdate(location)
}
@Deprecated("Deprecated in Java")
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}
override fun onProviderEnabled(provider: String) {}
override fun onProviderDisabled(provider: String) {}
}
@SuppressLint("MissingPermission")
fun start() {
try {
locationManager.registerGnssStatusCallback(gnssStatusCallback, null)
// Request location updates → GPS_PROVIDER
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
1000L, // minTime → 1 detik
0f, // minDistance → 0 meter
locationListener
)
// Fallback → Network Provider
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER,
3000L, // minTime → 3 detik
10f, // minDistance → 10 meter
locationListener
)
Log.d("GPS_STATUS", "GPS + Network location tracking started")
} catch (e: SecurityException) {
Log.e("GPS_STATUS", "Missing permissions", e)
}
}
fun stop() {
locationManager.unregisterGnssStatusCallback(gnssStatusCallback)
locationManager.removeUpdates(locationListener)
Log.d("GPS_STATUS", "GPS tracking stopped")
}
private fun getConstellationName(type: Int): String {
return when (type) {
GnssStatus.CONSTELLATION_GPS -> "GPS"
GnssStatus.CONSTELLATION_SBAS -> "SBAS"
GnssStatus.CONSTELLATION_GLONASS -> "GLONASS"
GnssStatus.CONSTELLATION_QZSS -> "QZSS"
GnssStatus.CONSTELLATION_BEIDOU -> "BEIDOU"
GnssStatus.CONSTELLATION_GALILEO -> "GALILEO"
GnssStatus.CONSTELLATION_IRNSS -> "IRNSS"
else -> "Unknown ($type)"
}
}
}

View File

@ -0,0 +1,117 @@
package com.dumon.plugin.geolocation.gps
import android.annotation.SuppressLint
import android.content.Context
import android.location.GnssStatus
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import android.os.Bundle
import android.text.format.DateFormat
import android.util.Log
class GpsStatusManager(
private val context: Context,
// private val onSatelliteStatusUpdate: (String) -> Unit = {},
private val onSatelliteStatusUpdate: (SatelliteStatus) -> Unit
private val onLocationUpdate: (Location) -> Unit = {}
) {
private val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
private val gnssStatusCallback = object : GnssStatus.Callback() {
override fun onSatelliteStatusChanged(status: GnssStatus) {
val satelliteCount = status.satelliteCount
var usedInFixCount = 0
val constellationCounts = mutableMapOf<Int, Int>()
for (i in 0 until satelliteCount) {
if (status.usedInFix(i)) usedInFixCount++
val constellationType = status.getConstellationType(i)
constellationCounts[constellationType] = constellationCounts.getOrDefault(constellationType, 0) + 1
}
val statusObj = SatelliteStatus(
satelliteCount,
usedInFixCount,
constellationCounts.mapKeys { getConstellationName(it.key) }
)
onSatelliteStatusUpdate(statusObj)
}
}
private val locationListener = object : LocationListener {
override fun onLocationChanged(location: Location) {
val providerTag = when (location.provider) {
LocationManager.GPS_PROVIDER -> "[GPS]"
LocationManager.NETWORK_PROVIDER -> "[NET]"
else -> "[OTHER]"
}
val timestamp = DateFormat.format("HH:mm:ss", System.currentTimeMillis())
val info = "$providerTag Lat: ${location.latitude}, Lon: ${location.longitude}, Acc: ${location.accuracy} m @ $timestamp"
Log.d("GPS_LOCATION", info)
onLocationUpdate(location)
}
@Deprecated("Deprecated in Java")
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}
override fun onProviderEnabled(provider: String) {}
override fun onProviderDisabled(provider: String) {}
}
@SuppressLint("MissingPermission")
fun start() {
try {
locationManager.registerGnssStatusCallback(gnssStatusCallback, null)
// Request location updates → GPS_PROVIDER
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
1000L, // minTime → 1 detik
0f, // minDistance → 0 meter
locationListener
)
// Fallback → Network Provider
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER,
3000L, // minTime → 3 detik
10f, // minDistance → 10 meter
locationListener
)
Log.d("GPS_STATUS", "GPS + Network location tracking started")
} catch (e: SecurityException) {
Log.e("GPS_STATUS", "Missing permissions", e)
}
}
fun stop() {
locationManager.unregisterGnssStatusCallback(gnssStatusCallback)
locationManager.removeUpdates(locationListener)
Log.d("GPS_STATUS", "GPS tracking stopped")
}
private fun getConstellationName(type: Int): String {
return when (type) {
GnssStatus.CONSTELLATION_GPS -> "GPS"
GnssStatus.CONSTELLATION_SBAS -> "SBAS"
GnssStatus.CONSTELLATION_GLONASS -> "GLONASS"
GnssStatus.CONSTELLATION_QZSS -> "QZSS"
GnssStatus.CONSTELLATION_BEIDOU -> "BEIDOU"
GnssStatus.CONSTELLATION_GALILEO -> "GALILEO"
GnssStatus.CONSTELLATION_IRNSS -> "IRNSS"
else -> "Unknown ($type)"
}
}
}
data class SatelliteStatus(
val satellitesInView: Int,
val usedInFix: Int,
val constellationCounts: Map<String, Int>
)

View File

@ -0,0 +1,116 @@
package com.dumon.plugin.geolocation.gps
import android.annotation.SuppressLint
import android.content.Context
import android.location.GnssStatus
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import android.os.Bundle
import android.text.format.DateFormat
import android.util.Log
class GpsStatusManager(
private val context: Context,
private val onSatelliteStatusUpdate: (SatelliteStatus) -> Unit = {},
private val onLocationUpdate: (Location) -> Unit = {}
) {
private val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
private val gnssStatusCallback = object : GnssStatus.Callback() {
override fun onSatelliteStatusChanged(status: GnssStatus) {
val satelliteCount = status.satelliteCount
var usedInFixCount = 0
val constellationCounts = mutableMapOf<Int, Int>()
for (i in 0 until satelliteCount) {
if (status.usedInFix(i)) usedInFixCount++
val constellationType = status.getConstellationType(i)
constellationCounts[constellationType] = constellationCounts.getOrDefault(constellationType, 0) + 1
}
val statusObj = SatelliteStatus(
satelliteCount,
usedInFixCount,
constellationCounts.mapKeys { getConstellationName(it.key) }
)
onSatelliteStatusUpdate(statusObj)
}
}
private val locationListener = object : LocationListener {
override fun onLocationChanged(location: Location) {
val providerTag = when (location.provider) {
LocationManager.GPS_PROVIDER -> "[GPS]"
LocationManager.NETWORK_PROVIDER -> "[NET]"
else -> "[OTHER]"
}
val timestamp = DateFormat.format("HH:mm:ss", System.currentTimeMillis())
val info = "$providerTag Lat: ${location.latitude}, Lon: ${location.longitude}, Acc: ${location.accuracy} m @ $timestamp"
Log.d("GPS_LOCATION", info)
onLocationUpdate(location)
}
@Deprecated("Deprecated in Java")
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}
override fun onProviderEnabled(provider: String) {}
override fun onProviderDisabled(provider: String) {}
}
@SuppressLint("MissingPermission")
fun start() {
try {
locationManager.registerGnssStatusCallback(gnssStatusCallback, null)
// Request location updates → GPS_PROVIDER
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
1000L, // minTime → 1 detik
0f, // minDistance → 0 meter
locationListener
)
// Fallback → Network Provider
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER,
3000L, // minTime → 3 detik
10f, // minDistance → 10 meter
locationListener
)
Log.d("GPS_STATUS", "GPS + Network location tracking started")
} catch (e: SecurityException) {
Log.e("GPS_STATUS", "Missing permissions", e)
}
}
fun stop() {
locationManager.unregisterGnssStatusCallback(gnssStatusCallback)
locationManager.removeUpdates(locationListener)
Log.d("GPS_STATUS", "GPS tracking stopped")
}
private fun getConstellationName(type: Int): String {
return when (type) {
GnssStatus.CONSTELLATION_GPS -> "GPS"
GnssStatus.CONSTELLATION_SBAS -> "SBAS"
GnssStatus.CONSTELLATION_GLONASS -> "GLONASS"
GnssStatus.CONSTELLATION_QZSS -> "QZSS"
GnssStatus.CONSTELLATION_BEIDOU -> "BEIDOU"
GnssStatus.CONSTELLATION_GALILEO -> "GALILEO"
GnssStatus.CONSTELLATION_IRNSS -> "IRNSS"
else -> "Unknown ($type)"
}
}
}
data class SatelliteStatus(
val satellitesInView: Int,
val usedInFix: Int,
val constellationCounts: Map<String, Int>
)

View File

@ -0,0 +1,116 @@
package com.dumon.plugin.geolocation.gps
import android.annotation.SuppressLint
import android.content.Context
import android.location.GnssStatus
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import android.os.Bundle
import android.util.Log
class GpsStatusManager(
private val context: Context,
private val onSatelliteStatusUpdate: (SatelliteStatus) -> Unit = {},
private val onLocationUpdate: (Location) -> Unit = {}
) {
private val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
private val gnssStatusCallback = object : GnssStatus.Callback() {
override fun onSatelliteStatusChanged(status: GnssStatus) {
val satelliteCount = status.satelliteCount
var usedInFix = 0
val constellationCounts = mutableMapOf<String, Int>()
for (i in 0 until satelliteCount) {
if (status.usedInFix(i)) usedInFix++
val constellation = getConstellationName(status.getConstellationType(i))
constellationCounts[constellation] = constellationCounts.getOrDefault(constellation, 0) + 1
}
Log.d("GPS_STATUS", "Satellites in View: $satelliteCount | Used in Fix: $usedInFix")
Log.d("GPS_STATUS", "Constellation Breakdown: $constellationCounts")
val statusObj = SatelliteStatus(
satellitesInView = satelliteCount,
usedInFix = usedInFix,
constellationCounts = constellationCounts
)
onSatelliteStatusUpdate(statusObj)
}
}
private val locationListener = object : LocationListener {
override fun onLocationChanged(location: Location) {
val tag = when (location.provider) {
LocationManager.GPS_PROVIDER -> "[GPS]"
LocationManager.NETWORK_PROVIDER -> "[NET]"
else -> "[OTHER]"
}
val info = "$tag Lat=${location.latitude}, Lon=${location.longitude}, Acc=${location.accuracy}m"
Log.d("GPS_LOCATION", info)
onLocationUpdate(location)
}
@Deprecated("Deprecated in Java")
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}
override fun onProviderEnabled(provider: String) {}
override fun onProviderDisabled(provider: String) {}
}
@SuppressLint("MissingPermission")
fun start() {
try {
locationManager.registerGnssStatusCallback(gnssStatusCallback, null)
// GPS_PROVIDER updates
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
1000L, // 1 detik
0f,
locationListener
)
// NETWORK_PROVIDER fallback
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER,
3000L,
10f,
locationListener
)
Log.d("GPS_STATUS", "GPS and Network location tracking started")
} catch (e: SecurityException) {
Log.e("GPS_STATUS", "Missing location permissions", e)
}
}
fun stop() {
locationManager.unregisterGnssStatusCallback(gnssStatusCallback)
locationManager.removeUpdates(locationListener)
Log.d("GPS_STATUS", "Location tracking stopped")
}
private fun getConstellationName(type: Int): String {
return when (type) {
GnssStatus.CONSTELLATION_GPS -> "GPS"
GnssStatus.CONSTELLATION_SBAS -> "SBAS"
GnssStatus.CONSTELLATION_GLONASS -> "GLONASS"
GnssStatus.CONSTELLATION_QZSS -> "QZSS"
GnssStatus.CONSTELLATION_BEIDOU -> "BEIDOU"
GnssStatus.CONSTELLATION_GALILEO -> "GALILEO"
GnssStatus.CONSTELLATION_IRNSS -> "IRNSS"
else -> "Unknown($type)"
}
}
}
data class SatelliteStatus(
val satellitesInView: Int,
val usedInFix: Int,
val constellationCounts: Map<String, Int>
)

View File

@ -0,0 +1,129 @@
package com.dumon.plugin.geolocation.gps
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.location.GnssStatus
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import android.os.Bundle
import android.os.Build
import android.text.format.DateFormat
import android.util.Log
import androidx.core.app.ActivityCompat
class GpsStatusManager(
private val context: Context,
private val onSatelliteStatusUpdate: (SatelliteStatus) -> Unit = {},
private val onLocationUpdate: (Location, Boolean) -> Unit = { _, _ -> }
) {
private val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
private val gnssStatusCallback = object : GnssStatus.Callback() {
override fun onSatelliteStatusChanged(status: GnssStatus) {
val satelliteCount = status.satelliteCount
var usedInFixCount = 0
val constellationCounts = mutableMapOf<Int, Int>()
for (i in 0 until satelliteCount) {
if (status.usedInFix(i)) usedInFixCount++
val constellationType = status.getConstellationType(i)
constellationCounts[constellationType] = constellationCounts.getOrDefault(constellationType, 0) + 1
}
val statusObj = SatelliteStatus(
satelliteCount,
usedInFixCount,
constellationCounts.mapKeys { getConstellationName(it.key) }
)
onSatelliteStatusUpdate(statusObj)
}
}
private val locationListener = object : LocationListener {
override fun onLocationChanged(location: Location) {
val providerTag = when (location.provider) {
LocationManager.GPS_PROVIDER -> "[GPS]"
LocationManager.NETWORK_PROVIDER -> "[NET]"
else -> "[OTHER]"
}
val timestamp = DateFormat.format("HH:mm:ss", System.currentTimeMillis())
val isMocked = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
location.isMock // API 31+
} else {
location.isFromMockProvider // Deprecated, tapi fallback
}
val info = "$providerTag Lat: ${location.latitude}, Lon: ${location.longitude}, Acc: ${location.accuracy} m @ $timestamp | Mock=$isMocked"
Log.d("GPS_LOCATION", info)
onLocationUpdate(location, isMocked)
}
@Deprecated("Deprecated in Java")
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}
override fun onProviderEnabled(provider: String) {}
override fun onProviderDisabled(provider: String) {}
}
@SuppressLint("MissingPermission")
fun start() {
try {
if (ActivityCompat.checkSelfPermission(context, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(context, android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
Log.e("GPS_STATUS", "Missing location permissions")
return
}
locationManager.registerGnssStatusCallback(gnssStatusCallback, null)
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
1000L,
0f,
locationListener
)
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER,
3000L,
10f,
locationListener
)
Log.d("GPS_STATUS", "GPS + Network location tracking started")
} catch (e: SecurityException) {
Log.e("GPS_STATUS", "SecurityException", e)
}
}
fun stop() {
locationManager.unregisterGnssStatusCallback(gnssStatusCallback)
locationManager.removeUpdates(locationListener)
Log.d("GPS_STATUS", "GPS tracking stopped")
}
private fun getConstellationName(type: Int): String {
return when (type) {
GnssStatus.CONSTELLATION_GPS -> "GPS"
GnssStatus.CONSTELLATION_SBAS -> "SBAS"
GnssStatus.CONSTELLATION_GLONASS -> "GLONASS"
GnssStatus.CONSTELLATION_QZSS -> "QZSS"
GnssStatus.CONSTELLATION_BEIDOU -> "BEIDOU"
GnssStatus.CONSTELLATION_GALILEO -> "GALILEO"
GnssStatus.CONSTELLATION_IRNSS -> "IRNSS"
else -> "Unknown ($type)"
}
}
}
data class SatelliteStatus(
val satellitesInView: Int,
val usedInFix: Int,
val constellationCounts: Map<String, Int>
)

View File

@ -0,0 +1,129 @@
package com.dumon.plugin.geolocation.gps
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.location.GnssStatus
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import android.os.Build
import android.os.Bundle
import android.text.format.DateFormat
import android.util.Log
import androidx.core.app.ActivityCompat
class GpsStatusManager(
private val context: Context,
private val onSatelliteStatusUpdate: (SatelliteStatus) -> Unit = {},
private val onLocationUpdate: (Location, Boolean) -> Unit = { _, _ -> }
) {
private val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
private val gnssStatusCallback = object : GnssStatus.Callback() {
override fun onSatelliteStatusChanged(status: GnssStatus) {
val satelliteCount = status.satelliteCount
var usedInFixCount = 0
val constellationCounts = mutableMapOf<Int, Int>()
for (i in 0 until satelliteCount) {
if (status.usedInFix(i)) usedInFixCount++
val constellationType = status.getConstellationType(i)
constellationCounts[constellationType] = constellationCounts.getOrDefault(constellationType, 0) + 1
}
val statusObj = SatelliteStatus(
satellitesInView = satelliteCount,
usedInFix = usedInFixCount,
constellationCounts = constellationCounts.mapKeys { getConstellationName(it.key) }
)
onSatelliteStatusUpdate(statusObj)
}
}
private val locationListener = object : LocationListener {
override fun onLocationChanged(location: Location) {
val providerTag = when (location.provider) {
LocationManager.GPS_PROVIDER -> "[GPS]"
LocationManager.NETWORK_PROVIDER -> "[NET]"
else -> "[OTHER]"
}
val timestamp = DateFormat.format("HH:mm:ss", System.currentTimeMillis())
val isMocked = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
location.isMock // API 31+
} else {
location.isFromMockProvider // Fallback
}
val info = "$providerTag Lat: ${location.latitude}, Lon: ${location.longitude}, Acc: ${location.accuracy} m @ $timestamp | Mock=$isMocked"
Log.d("GPS_LOCATION", info)
onLocationUpdate(location, isMocked)
}
@Deprecated("Deprecated in Java")
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}
override fun onProviderEnabled(provider: String) {}
override fun onProviderDisabled(provider: String) {}
}
@SuppressLint("MissingPermission")
fun start() {
try {
if (ActivityCompat.checkSelfPermission(context, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(context, android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
Log.e("GPS_STATUS", "Missing location permissions")
return
}
locationManager.registerGnssStatusCallback(gnssStatusCallback, null)
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
1000L,
0f,
locationListener
)
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER,
3000L,
10f,
locationListener
)
Log.d("GPS_STATUS", "GPS + Network location tracking started")
} catch (e: SecurityException) {
Log.e("GPS_STATUS", "SecurityException", e)
}
}
fun stop() {
locationManager.unregisterGnssStatusCallback(gnssStatusCallback)
locationManager.removeUpdates(locationListener)
Log.d("GPS_STATUS", "GPS tracking stopped")
}
private fun getConstellationName(type: Int): String {
return when (type) {
GnssStatus.CONSTELLATION_GPS -> "GPS"
GnssStatus.CONSTELLATION_SBAS -> "SBAS"
GnssStatus.CONSTELLATION_GLONASS -> "GLONASS"
GnssStatus.CONSTELLATION_QZSS -> "QZSS"
GnssStatus.CONSTELLATION_BEIDOU -> "BEIDOU"
GnssStatus.CONSTELLATION_GALILEO -> "GALILEO"
GnssStatus.CONSTELLATION_IRNSS -> "IRNSS"
else -> "Unknown ($type)"
}
}
}
data class SatelliteStatus(
val satellitesInView: Int,
val usedInFix: Int,
val constellationCounts: Map<String, Int>
)

View File

@ -0,0 +1,56 @@
package com.dumon.plugin.geolocation.imu
import android.content.Context
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.util.Log
class ImuSensorManager(
private val context: Context,
private val onAccelerometerUpdate: (String) -> Unit = {},
private val onGyroscopeUpdate: (String) -> Unit = {}
) : SensorEventListener {
private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
private val accelerometer: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION)
private val gyroscope: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
fun start() {
accelerometer?.let {
sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_UI)
}
gyroscope?.let {
sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_UI)
}
Log.d("IMU_SENSOR", "IMU sensor tracking started")
}
fun stop() {
sensorManager.unregisterListener(this)
Log.d("IMU_SENSOR", "IMU sensor tracking stopped")
}
override fun onSensorChanged(event: SensorEvent?) {
event?.let {
when (it.sensor.type) {
Sensor.TYPE_LINEAR_ACCELERATION -> {
val info = "Accel x: %.2f, y: %.2f, z: %.2f m/s²".format(it.values[0], it.values[1], it.values[2])
Log.d("IMU_SENSOR", info)
onAccelerometerUpdate(info)
}
Sensor.TYPE_GYROSCOPE -> {
val info = "Gyro x: %.2f, y: %.2f, z: %.2f rad/s".format(it.values[0], it.values[1], it.values[2])
Log.d("IMU_SENSOR", info)
onGyroscopeUpdate(info)
}
}
}
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
// Not used for now
}
}

View File

@ -0,0 +1,54 @@
package com.dumon.plugin.geolocation.imu
import android.content.Context
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.util.Log
class ImuSensorManager(
private val context: Context,
private val onAccelerometerUpdate: (FloatArray) -> Unit,
private val onGyroscopeUpdate: (FloatArray) -> Unit
) : SensorEventListener {
private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
private val accelerometer: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION)
private val gyroscope: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
fun start() {
accelerometer?.let {
sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_UI)
}
gyroscope?.let {
sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_UI)
}
Log.d("IMU_SENSOR", "IMU sensor tracking started")
}
fun stop() {
sensorManager.unregisterListener(this)
Log.d("IMU_SENSOR", "IMU sensor tracking stopped")
}
override fun onSensorChanged(event: SensorEvent?) {
event?.let {
when (it.sensor.type) {
Sensor.TYPE_LINEAR_ACCELERATION -> {
onAccelerometerUpdate(it.values.copyOf())
Log.d("IMU_SENSOR", "Accel x: %.2f y: %.2f z: %.2f m/s²".format(it.values[0], it.values[1], it.values[2]))
}
Sensor.TYPE_GYROSCOPE -> {
onGyroscopeUpdate(it.values.copyOf())
Log.d("IMU_SENSOR", "Gyro x: %.2f y: %.2f z: %.2f rad/s".format(it.values[0], it.values[1], it.values[2]))
}
}
}
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
// Not used for now
}
}

View File

@ -0,0 +1,114 @@
package com.dumon.plugin.geolocation.imu
import android.content.Context
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.util.Log
import kotlin.math.*
class ImuSensorManager(
private val context: Context,
private val onImuUpdate: (ImuData) -> Unit
) : SensorEventListener {
private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
private val accelerometer: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION)
private val gyroscope: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
private var lastAccel = FloatArray(3) { 0f }
private var lastGyro = FloatArray(3) { 0f }
private var lastAccelTimestamp: Long = 0L
private var velocity = FloatArray(3) { 0f }
fun start() {
accelerometer?.let {
sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_UI)
}
gyroscope?.let {
sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_UI)
}
Log.d("IMU_SENSOR", "IMU sensor tracking started")
}
fun stop() {
sensorManager.unregisterListener(this)
Log.d("IMU_SENSOR", "IMU sensor tracking stopped")
}
override fun onSensorChanged(event: SensorEvent?) {
event?.let {
when (it.sensor.type) {
Sensor.TYPE_LINEAR_ACCELERATION -> handleAccelerometer(it)
Sensor.TYPE_GYROSCOPE -> handleGyroscope(it)
}
}
}
private fun handleAccelerometer(event: SensorEvent) {
val currentTime = event.timestamp
if (lastAccelTimestamp != 0L) {
val dt = (currentTime - lastAccelTimestamp) / 1_000_000_000f // convert ns to seconds
for (i in 0..2) {
velocity[i] += event.values[i] * dt
}
}
lastAccelTimestamp = currentTime
lastAccel = event.values.copyOf()
val speed = sqrt(velocity[0].pow(2) + velocity[1].pow(2) + velocity[2].pow(2))
val acceleration = sqrt(
lastAccel[0].pow(2) + lastAccel[1].pow(2) + lastAccel[2].pow(2)
)
emitCombinedImuData(speed, acceleration)
Log.d("IMU_SENSOR", "Accel x: %.2f y: %.2f z: %.2f m/s² | Speed: %.2f m/s | AccelMag: %.2f m/s²".format(
lastAccel[0], lastAccel[1], lastAccel[2], speed, acceleration
))
}
private fun handleGyroscope(event: SensorEvent) {
lastGyro = event.values.copyOf()
Log.d("IMU_SENSOR", "Gyro x: %.2f y: %.2f z: %.2f rad/s".format(
lastGyro[0], lastGyro[1], lastGyro[2]
))
}
private fun emitCombinedImuData(speed: Float, acceleration: Float) {
val directionRad = atan2(lastGyro[1], lastGyro[0])
onImuUpdate(
ImuData(
accelX = lastAccel[0],
accelY = lastAccel[1],
accelZ = lastAccel[2],
gyroX = lastGyro[0],
gyroY = lastGyro[1],
gyroZ = lastGyro[2],
speed = speed,
acceleration = acceleration,
directionRad = directionRad
)
)
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
// Not used
}
}
data class ImuData(
val accelX: Float,
val accelY: Float,
val accelZ: Float,
val gyroX: Float,
val gyroY: Float,
val gyroZ: Float,
val speed: Float,
val acceleration: Float,
val directionRad: Float
)

View File

@ -0,0 +1,114 @@
package com.dumon.plugin.geolocation.imu
import android.content.Context
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.util.Log
import kotlin.math.*
class ImuSensorManager(
private val context: Context,
private val onImuUpdate: (ImuData) -> Unit
) : SensorEventListener {
private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
private val accelerometer: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION)
private val gyroscope: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
private var lastAccel = FloatArray(3) { 0f }
private var lastGyro = FloatArray(3) { 0f }
private var lastAccelTimestamp: Long = 0L
private var velocity = FloatArray(3) { 0f }
fun start() {
accelerometer?.let {
sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_GAME)
}
gyroscope?.let {
sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_GAME)
}
Log.d("IMU_SENSOR", "IMU sensor tracking started")
}
fun stop() {
sensorManager.unregisterListener(this)
Log.d("IMU_SENSOR", "IMU sensor tracking stopped")
}
override fun onSensorChanged(event: SensorEvent?) {
event?.let {
when (it.sensor.type) {
Sensor.TYPE_LINEAR_ACCELERATION -> handleAccelerometer(it)
Sensor.TYPE_GYROSCOPE -> handleGyroscope(it)
}
}
}
private fun handleAccelerometer(event: SensorEvent) {
val currentTime = event.timestamp
if (lastAccelTimestamp != 0L) {
val dt = (currentTime - lastAccelTimestamp) / 1_000_000_000f // convert ns to seconds
for (i in 0..2) {
velocity[i] += event.values[i] * dt
}
}
lastAccelTimestamp = currentTime
lastAccel = event.values.copyOf()
val speed = sqrt(velocity[0].pow(2) + velocity[1].pow(2) + velocity[2].pow(2))
val acceleration = sqrt(
lastAccel[0].pow(2) + lastAccel[1].pow(2) + lastAccel[2].pow(2)
)
emitCombinedImuData(speed, acceleration)
Log.d("IMU_SENSOR", "Accel x: %.3f y: %.3f z: %.3f m/s² | Speed: %.3f m/s | AccelMag: %.3f m/s²".format(
lastAccel[0], lastAccel[1], lastAccel[2], speed, acceleration
))
}
private fun handleGyroscope(event: SensorEvent) {
lastGyro = event.values.copyOf()
Log.d("IMU_SENSOR", "Gyro x: %.3f y: %.3f z: %.3f rad/s".format(
lastGyro[0], lastGyro[1], lastGyro[2]
))
}
private fun emitCombinedImuData(speed: Float, acceleration: Float) {
val directionRad = atan2(lastGyro[1], lastGyro[0])
onImuUpdate(
ImuData(
accelX = lastAccel[0],
accelY = lastAccel[1],
accelZ = lastAccel[2],
gyroX = lastGyro[0],
gyroY = lastGyro[1],
gyroZ = lastGyro[2],
speed = speed,
acceleration = acceleration,
directionRad = directionRad
)
)
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
// Not used
}
}
data class ImuData(
val accelX: Float,
val accelY: Float,
val accelZ: Float,
val gyroX: Float,
val gyroY: Float,
val gyroZ: Float,
val speed: Float,
val acceleration: Float,
val directionRad: Float
)

View File

@ -0,0 +1,119 @@
package com.dumon.plugin.geolocation.imu
import android.content.Context
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.util.Log
import kotlin.math.*
class ImuSensorManager(
private val context: Context,
private val onImuUpdate: (ImuData) -> Unit
) : SensorEventListener {
private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
private val accelerometer: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION)
private val gyroscope: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
private var lastAccel = FloatArray(3) { 0f }
private var lastGyro = FloatArray(3) { 0f }
private var lastAccelTimestamp: Long = 0L
private var velocity = FloatArray(3) { 0f }
private val accelLowPass = FloatArray(3) { 0f }
private val alpha = 0.8f // Low-pass filter constant
fun start() {
accelerometer?.let {
sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_GAME)
}
gyroscope?.let {
sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_GAME)
}
Log.d("IMU_SENSOR", "IMU sensor tracking started")
}
fun stop() {
sensorManager.unregisterListener(this)
Log.d("IMU_SENSOR", "IMU sensor tracking stopped")
}
override fun onSensorChanged(event: SensorEvent?) {
event?.let {
when (it.sensor.type) {
Sensor.TYPE_LINEAR_ACCELERATION -> handleAccelerometer(it)
Sensor.TYPE_GYROSCOPE -> handleGyroscope(it)
}
}
}
private fun handleAccelerometer(event: SensorEvent) {
val currentTime = event.timestamp
for (i in 0..2) {
accelLowPass[i] = alpha * accelLowPass[i] + (1 - alpha) * event.values[i]
}
if (lastAccelTimestamp != 0L) {
val dt = (currentTime - lastAccelTimestamp) / 1_000_000_000f // convert ns to seconds
for (i in 0..2) {
velocity[i] += accelLowPass[i] * dt
}
}
lastAccelTimestamp = currentTime
lastAccel = accelLowPass.copyOf()
val speed = velocity.map { it.pow(2) }.sum().pow(0.5f).coerceIn(0f, 50f)
val acceleration = lastAccel.map { it.pow(2) }.sum().pow(0.5f).coerceIn(0f, 20f)
emitCombinedImuData(speed, acceleration)
Log.d("IMU_SENSOR", "Accel x: %.3f y: %.3f z: %.3f m/s² | Speed: %.3f m/s | AccelMag: %.3f m/s²".format(
lastAccel[0], lastAccel[1], lastAccel[2], speed, acceleration
))
}
private fun handleGyroscope(event: SensorEvent) {
lastGyro = event.values.copyOf()
Log.d("IMU_SENSOR", "Gyro x: %.3f y: %.3f z: %.3f rad/s".format(
lastGyro[0], lastGyro[1], lastGyro[2]
))
}
private fun emitCombinedImuData(speed: Float, acceleration: Float) {
val directionRad = if (speed > 0.5f) atan2(lastGyro[1], lastGyro[0]) else null
onImuUpdate(
ImuData(
accelX = lastAccel[0],
accelY = lastAccel[1],
accelZ = lastAccel[2],
gyroX = lastGyro[0],
gyroY = lastGyro[1],
gyroZ = lastGyro[2],
speed = speed,
acceleration = acceleration,
directionRad = directionRad ?: 0f
)
)
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
// Not used
}
}
data class ImuData(
val accelX: Float,
val accelY: Float,
val accelZ: Float,
val gyroX: Float,
val gyroY: Float,
val gyroZ: Float,
val speed: Float,
val acceleration: Float,
val directionRad: Float
)

View File

@ -0,0 +1,126 @@
package com.dumon.plugin.geolocation.imu
import android.content.Context
import android.hardware.*
import android.util.Log
import kotlin.math.*
class ImuSensorManager(
private val context: Context,
private val onImuUpdate: (ImuData) -> Unit
) : SensorEventListener {
private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
private val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION)
private val gyroscope = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
private val rotationVector = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
private var lastAccel = FloatArray(3) { 0f }
private var lastGyro = FloatArray(3) { 0f }
private var lastAccelTimestamp: Long = 0L
private var velocity = FloatArray(3) { 0f }
private val accelLowPass = FloatArray(3) { 0f }
private val alpha = 0.8f
private var latestDirectionRad = 0f
fun start() {
accelerometer?.let { sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_GAME) }
gyroscope?.let { sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_GAME) }
rotationVector?.let { sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_GAME) }
Log.d("IMU_SENSOR", "IMU sensor tracking started")
}
fun stop() {
sensorManager.unregisterListener(this)
Log.d("IMU_SENSOR", "IMU sensor tracking stopped")
}
override fun onSensorChanged(event: SensorEvent?) {
event?.let {
when (it.sensor.type) {
Sensor.TYPE_LINEAR_ACCELERATION -> handleAccelerometer(it)
Sensor.TYPE_GYROSCOPE -> handleGyroscope(it)
Sensor.TYPE_ROTATION_VECTOR -> handleRotationVector(it)
}
}
}
private fun handleAccelerometer(event: SensorEvent) {
val currentTime = event.timestamp
val threshold = 0.1f
for (i in 0..2) {
accelLowPass[i] = alpha * accelLowPass[i] + (1 - alpha) * event.values[i]
}
if (lastAccelTimestamp != 0L) {
val dt = (currentTime - lastAccelTimestamp) / 1_000_000_000f
for (i in 0..2) {
val acc = accelLowPass[i]
if (abs(acc) > threshold) {
velocity[i] += acc * dt
}
}
}
lastAccelTimestamp = currentTime
lastAccel = accelLowPass.copyOf()
val speed = velocity.map { it * it }.sum().pow(0.5f).coerceIn(0f, 50f)
val acceleration = lastAccel.map { it * it }.sum().pow(0.5f).coerceIn(0f, 20f)
val directionRad = if (speed > 0.3f) atan2(velocity[1], velocity[0]) else latestDirectionRad
latestDirectionRad = directionRad
emitCombinedImuData(speed, acceleration, directionRad)
Log.d("IMU_SENSOR", "Accel x: %.3f y: %.3f z: %.3f m/s² | Speed: %.3f m/s | AccelMag: %.3f m/s² | Dir: %.2f rad".format(
lastAccel[0], lastAccel[1], lastAccel[2], speed, acceleration, directionRad
))
}
private fun handleGyroscope(event: SensorEvent) {
lastGyro = event.values.copyOf()
}
private fun handleRotationVector(event: SensorEvent) {
val rotationMatrix = FloatArray(9)
SensorManager.getRotationMatrixFromVector(rotationMatrix, event.values)
val orientation = FloatArray(3)
SensorManager.getOrientation(rotationMatrix, orientation)
latestDirectionRad = orientation[0] // azimuth
}
private fun emitCombinedImuData(speed: Float, acceleration: Float, directionRad: Float) {
onImuUpdate(
ImuData(
accelX = lastAccel[0],
accelY = lastAccel[1],
accelZ = lastAccel[2],
gyroX = lastGyro[0],
gyroY = lastGyro[1],
gyroZ = lastGyro[2],
speed = speed,
acceleration = acceleration,
directionRad = directionRad
)
)
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
// Not used
}
}
data class ImuData(
val accelX: Float,
val accelY: Float,
val accelZ: Float,
val gyroX: Float,
val gyroY: Float,
val gyroZ: Float,
val speed: Float,
val acceleration: Float,
val directionRad: Float
)

View File

@ -0,0 +1,141 @@
package com.dumon.plugin.geolocation.imu
import android.content.Context
import android.hardware.*
import android.util.Log
import kotlin.math.*
class ImuSensorManager(
private val context: Context,
private val onImuUpdate: (ImuData) -> Unit
) : SensorEventListener {
private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
private val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION)
private val gyroscope = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
private val rotationVector = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
private var lastAccel = FloatArray(3) { 0f }
private var lastGyro = FloatArray(3) { 0f }
private var velocity = FloatArray(3) { 0f }
private var lastAccelTimestamp: Long = 0L
private val accelLowPass = FloatArray(3) { 0f }
private val alpha = 0.85f
private val accelerationThreshold = 0.12f
private val idleTimeoutNs = 2_000_000_000L // 2 seconds
private var lastActiveTimestamp: Long = 0L
private var latestDirectionRad = 0f
private var latestSpeed = 0f
fun start() {
accelerometer?.let { sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_GAME) }
gyroscope?.let { sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_GAME) }
rotationVector?.let { sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_GAME) }
Log.d("IMU_SENSOR", "IMU sensor tracking started")
}
fun stop() {
sensorManager.unregisterListener(this)
Log.d("IMU_SENSOR", "IMU sensor tracking stopped")
}
override fun onSensorChanged(event: SensorEvent?) {
event?.let {
when (it.sensor.type) {
Sensor.TYPE_LINEAR_ACCELERATION -> handleAccelerometer(it)
Sensor.TYPE_GYROSCOPE -> handleGyroscope(it)
Sensor.TYPE_ROTATION_VECTOR -> handleRotationVector(it)
}
}
}
private fun handleAccelerometer(event: SensorEvent) {
val currentTime = event.timestamp
for (i in 0..2) {
accelLowPass[i] = alpha * accelLowPass[i] + (1 - alpha) * event.values[i]
}
val accMag = sqrt(accelLowPass.map { it * it }.sum())
if (accMag > accelerationThreshold) {
if (lastAccelTimestamp != 0L) {
val dt = (currentTime - lastAccelTimestamp) / 1_000_000_000f
for (i in 0..2) {
velocity[i] += accelLowPass[i] * dt
}
}
lastActiveTimestamp = currentTime
} else {
// If idle too long, decay velocity to zero
if (currentTime - lastActiveTimestamp > idleTimeoutNs) {
for (i in 0..2) velocity[i] = 0f
} else {
// Apply damping to reduce drift
for (i in 0..2) velocity[i] *= 0.96f
}
}
lastAccelTimestamp = currentTime
lastAccel = accelLowPass.copyOf()
val speed = velocity.map { it * it }.sum().pow(0.5f).coerceIn(0f, 30f)
val acceleration = accMag.coerceIn(0f, 20f)
val directionRad = if (speed > 0.3f) atan2(velocity[1], velocity[0]) else latestDirectionRad
latestDirectionRad = directionRad
latestSpeed = speed
emitCombinedImuData(speed, acceleration, directionRad)
Log.d("IMU_SENSOR", "Accel x: %.3f y: %.3f z: %.3f | Speed: %.3f | AccelMag: %.3f | Dir: %.2f rad".format(
lastAccel[0], lastAccel[1], lastAccel[2], speed, acceleration, directionRad
))
}
private fun handleGyroscope(event: SensorEvent) {
lastGyro = event.values.copyOf()
}
private fun handleRotationVector(event: SensorEvent) {
val rotationMatrix = FloatArray(9)
SensorManager.getRotationMatrixFromVector(rotationMatrix, event.values)
val orientation = FloatArray(3)
SensorManager.getOrientation(rotationMatrix, orientation)
latestDirectionRad = orientation[0] // azimuth (radian)
}
private fun emitCombinedImuData(speed: Float, acceleration: Float, directionRad: Float) {
onImuUpdate(
ImuData(
accelX = lastAccel[0],
accelY = lastAccel[1],
accelZ = lastAccel[2],
gyroX = lastGyro[0],
gyroY = lastGyro[1],
gyroZ = lastGyro[2],
speed = speed,
acceleration = acceleration,
directionRad = directionRad
)
)
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
// Not used
}
}
data class ImuData(
val accelX: Float,
val accelY: Float,
val accelZ: Float,
val gyroX: Float,
val gyroY: Float,
val gyroZ: Float,
val speed: Float,
val acceleration: Float,
val directionRad: Float
)

View File

@ -0,0 +1,144 @@
package com.dumon.plugin.geolocation.imu
import android.content.Context
import android.hardware.*
import android.util.Log
import kotlin.math.*
class ImuSensorManager(
private val context: Context,
private val onImuUpdate: (ImuData) -> Unit
) : SensorEventListener {
private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
private val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION)
private val gyroscope = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
private val rotationVector = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
private var lastAccel = FloatArray(3) { 0f }
private var lastGyro = FloatArray(3) { 0f }
private var velocity = FloatArray(3) { 0f }
private var lastAccelTimestamp: Long = 0L
private val accelLowPass = FloatArray(3) { 0f }
private val alpha = 0.85f
private val accelerationThreshold = 0.12f
private val idleTimeoutNs = 2_000_000_000L // 2 seconds
private var lastActiveTimestamp: Long = 0L
private var latestDirectionRad = 0f
private var latestSpeed = 0f
fun start() {
accelerometer?.let { sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_GAME) }
gyroscope?.let { sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_GAME) }
rotationVector?.let { sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_GAME) }
Log.d("IMU_SENSOR", "IMU sensor tracking started")
}
fun stop() {
sensorManager.unregisterListener(this)
Log.d("IMU_SENSOR", "IMU sensor tracking stopped")
}
override fun onSensorChanged(event: SensorEvent?) {
event?.let {
when (it.sensor.type) {
Sensor.TYPE_LINEAR_ACCELERATION -> handleAccelerometer(it)
Sensor.TYPE_GYROSCOPE -> handleGyroscope(it)
Sensor.TYPE_ROTATION_VECTOR -> handleRotationVector(it)
}
}
}
private fun handleAccelerometer(event: SensorEvent) {
val currentTime = event.timestamp
for (i in 0..2) {
accelLowPass[i] = alpha * accelLowPass[i] + (1 - alpha) * event.values[i]
}
val accMag = sqrt(accelLowPass.map { it * it }.sum())
if (accMag > accelerationThreshold) {
if (lastAccelTimestamp != 0L) {
val dt = (currentTime - lastAccelTimestamp) / 1_000_000_000f
for (i in 0..2) {
velocity[i] += accelLowPass[i] * dt
}
}
lastActiveTimestamp = currentTime
} else {
if (currentTime - lastActiveTimestamp > idleTimeoutNs) {
for (i in 0..2) velocity[i] = 0f
} else {
for (i in 0..2) velocity[i] *= 0.96f
}
}
lastAccelTimestamp = currentTime
lastAccel = accelLowPass.copyOf()
val speed = velocity.map { it * it }.sum().pow(0.5f).coerceIn(0f, 30f)
val acceleration = accMag.coerceIn(0f, 20f)
latestSpeed = speed
emitCombinedImuData(speed, acceleration, latestDirectionRad)
Log.d("IMU_SENSOR", "Accel x: %.3f y: %.3f z: %.3f | Speed: %.3f | AccelMag: %.3f | Dir: %.2f rad".format(
lastAccel[0], lastAccel[1], lastAccel[2], speed, acceleration, latestDirectionRad
))
}
private fun handleGyroscope(event: SensorEvent) {
lastGyro = event.values.copyOf()
}
private fun handleRotationVector(event: SensorEvent) {
val rotationMatrix = FloatArray(9)
SensorManager.getRotationMatrixFromVector(rotationMatrix, event.values)
val orientation = FloatArray(3)
SensorManager.getOrientation(rotationMatrix, orientation)
val azimuth = orientation[0] // radian
// Apply smoothing to direction
val smoothFactor = 0.85f
if (abs(azimuth - latestDirectionRad) > 0.05f) {
latestDirectionRad = smoothFactor * latestDirectionRad + (1 - smoothFactor) * azimuth
}
}
private fun emitCombinedImuData(speed: Float, acceleration: Float, directionRad: Float) {
onImuUpdate(
ImuData(
accelX = lastAccel[0],
accelY = lastAccel[1],
accelZ = lastAccel[2],
gyroX = lastGyro[0],
gyroY = lastGyro[1],
gyroZ = lastGyro[2],
speed = speed,
acceleration = acceleration,
directionRad = directionRad
)
)
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
// Not used
}
}
data class ImuData(
val accelX: Float,
val accelY: Float,
val accelZ: Float,
val gyroX: Float,
val gyroY: Float,
val gyroZ: Float,
val speed: Float,
val acceleration: Float,
val directionRad: Float
)

View File

@ -0,0 +1,123 @@
package com.dumon.plugin.geolocation.wifi
import android.content.Context
import android.net.wifi.ScanResult
import android.net.wifi.WifiManager
import android.net.wifi.rtt.RangingRequest
import android.net.wifi.rtt.RangingResultCallback
import android.net.wifi.rtt.WifiRttManager
import android.os.*
import android.util.Log
import java.util.concurrent.Executors
class WifiPositioningManager(
private val context: Context,
private val onWifiPositioningUpdate: (String) -> Unit = {}
) {
private val wifiManager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
private val wifiRttManager: WifiRttManager? =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
context.applicationContext.getSystemService(Context.WIFI_RTT_RANGING_SERVICE) as? WifiRttManager
} else {
null
}
private val handler = Handler(Looper.getMainLooper())
private var isScanning = false
fun isRttSupported(): Boolean {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && wifiRttManager != null && wifiManager.isWifiEnabled
}
fun startPeriodicScan(intervalMs: Long = 3000L) {
isScanning = true
handler.post(object : Runnable {
override fun run() {
if (isScanning) {
startWifiScan()
handler.postDelayed(this, intervalMs)
}
}
})
}
fun stopPeriodicScan() {
isScanning = false
handler.removeCallbacksAndMessages(null)
}
fun startWifiScan() {
val success = wifiManager.startScan()
if (success) {
val results = wifiManager.scanResults
logScanResults(results)
if (isRttSupported()) {
startRttRanging(results)
}
} else {
Log.e("WIFI_POSITION", "Wi-Fi scan failed")
onWifiPositioningUpdate("Wi-Fi scan failed")
}
}
private fun logScanResults(results: List<ScanResult>) {
val info = buildString {
append("Wi-Fi AP count: ${results.size}\n")
results.forEach { result ->
append("SSID: ${result.SSID}, BSSID: ${result.BSSID}, RSSI: ${result.level} dBm\n")
}
}
Log.d("WIFI_POSITION", info)
onWifiPositioningUpdate(info)
}
private fun startRttRanging(results: List<ScanResult>) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P || wifiRttManager == null) {
Log.d("WIFI_POSITION", "RTT not supported or WifiRttManager is null.")
return
}
val rttCapableAps = results.filter { it.is80211mcResponder }
if (rttCapableAps.isEmpty()) {
Log.d("WIFI_POSITION", "No RTT-capable AP found.")
return
}
val rangingRequest = RangingRequest.Builder()
.addAccessPoints(rttCapableAps)
.build()
try {
wifiRttManager.startRanging(
rangingRequest,
Executors.newSingleThreadExecutor(),
object : RangingResultCallback() {
override fun onRangingFailure(code: Int) {
Log.e("WIFI_POSITION", "RTT Ranging failed: $code")
}
override fun onRangingResults(results: List<android.net.wifi.rtt.RangingResult>) {
results.forEach { result ->
if (result.status == android.net.wifi.rtt.RangingResult.STATUS_SUCCESS) {
Log.d(
"WIFI_POSITION",
"RTT Result → BSSID: ${result.macAddress}, Distance: ${result.distanceMm / 1000.0} m"
)
} else {
Log.d(
"WIFI_POSITION",
"RTT Result → BSSID: ${result.macAddress}, Status: ${result.status}"
)
}
}
}
}
)
} catch (e: SecurityException) {
Log.e("WIFI_POSITION", "SecurityException: Missing NEARBY_WIFI_DEVICES permission or not granted", e)
}
}
}

View File

@ -0,0 +1,151 @@
package com.dumon.plugin.geolocation.wifi
import android.content.Context
import android.net.wifi.ScanResult
import android.net.wifi.WifiManager
import android.net.wifi.rtt.RangingRequest
import android.net.wifi.rtt.RangingResultCallback
import android.net.wifi.rtt.WifiRttManager
import android.os.*
import android.util.Log
import java.util.concurrent.Executors
class WifiPositioningManager(
private val context: Context,
private val onWifiPositioningUpdate: (WifiScanResult) -> Unit
) {
private val wifiManager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
private val wifiRttManager: WifiRttManager? =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
context.applicationContext.getSystemService(Context.WIFI_RTT_RANGING_SERVICE) as? WifiRttManager
} else {
null
}
private val handler = Handler(Looper.getMainLooper())
private var isScanning = false
fun isRttSupported(): Boolean {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && wifiRttManager != null && wifiManager.isWifiEnabled
}
fun startPeriodicScan(intervalMs: Long = 3000L) {
isScanning = true
handler.post(object : Runnable {
override fun run() {
if (isScanning) {
startWifiScan()
handler.postDelayed(this, intervalMs)
}
}
})
}
fun stopPeriodicScan() {
isScanning = false
handler.removeCallbacksAndMessages(null)
}
fun startWifiScan() {
val success = wifiManager.startScan()
if (success) {
val results = wifiManager.scanResults
logScanResults(results)
if (isRttSupported()) {
startRttRanging(results)
}
} else {
onWifiPositioningUpdate(
WifiScanResult(
apCount = 0,
aps = emptyList()
)
)
}
}
private fun logScanResults(results: List<ScanResult>) {
val aps = results.map { result ->
WifiAp(
ssid = result.SSID,
bssid = result.BSSID,
rssi = result.level
// distance belum ada di ScanResult, hanya di RTT → akan diupdate di startRttRanging()
)
}
val wifiScanResult = WifiScanResult(
apCount = aps.size,
aps = aps
)
Log.d("WIFI_POSITION", "Wi-Fi scan → AP count: ${aps.size}")
aps.forEach {
Log.d("WIFI_POSITION", "SSID: ${it.ssid}, BSSID: ${it.bssid}, RSSI: ${it.rssi} dBm")
}
onWifiPositioningUpdate(wifiScanResult)
}
private fun startRttRanging(results: List<ScanResult>) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P || wifiRttManager == null) {
Log.d("WIFI_POSITION", "RTT not supported or WifiRttManager is null.")
return
}
val rttCapableAps = results.filter { it.is80211mcResponder }
if (rttCapableAps.isEmpty()) {
Log.d("WIFI_POSITION", "No RTT-capable AP found.")
return
}
val rangingRequest = RangingRequest.Builder()
.addAccessPoints(rttCapableAps)
.build()
try {
wifiRttManager.startRanging(
rangingRequest,
Executors.newSingleThreadExecutor(),
object : RangingResultCallback() {
override fun onRangingFailure(code: Int) {
Log.e("WIFI_POSITION", "RTT Ranging failed: $code")
}
override fun onRangingResults(results: List<android.net.wifi.rtt.RangingResult>) {
results.forEach { result ->
if (result.status == android.net.wifi.rtt.RangingResult.STATUS_SUCCESS) {
Log.d(
"WIFI_POSITION",
"RTT Result → BSSID: ${result.macAddress}, Distance: ${result.distanceMm / 1000.0} m"
)
} else {
Log.d(
"WIFI_POSITION",
"RTT Result → BSSID: ${result.macAddress}, Status: ${result.status}"
)
}
}
}
}
)
} catch (e: SecurityException) {
Log.e("WIFI_POSITION", "SecurityException: Missing NEARBY_WIFI_DEVICES permission or not granted", e)
}
}
}
data class WifiAp(
val ssid: String,
val bssid: String,
val rssi: Int,
val distance: Double? = null
)
data class WifiScanResult(
val apCount: Int,
val aps: List<WifiAp>
)

View File

@ -0,0 +1,148 @@
package com.dumon.plugin.geolocation.wifi
import android.content.Context
import android.net.wifi.ScanResult
import android.net.wifi.WifiManager
import android.net.wifi.rtt.RangingRequest
import android.net.wifi.rtt.RangingResultCallback
import android.net.wifi.rtt.WifiRttManager
import android.os.*
import android.util.Log
import java.util.concurrent.Executors
class WifiPositioningManager(
private val context: Context,
private val onWifiPositioningUpdate: (WifiScanResult) -> Unit
) {
private val wifiManager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
private val wifiRttManager: WifiRttManager? =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
context.applicationContext.getSystemService(Context.WIFI_RTT_RANGING_SERVICE) as? WifiRttManager
} else {
null
}
private val handler = Handler(Looper.getMainLooper())
private var isScanning = false
private var lastWifiScanAps: MutableList<WifiAp> = mutableListOf()
fun isRttSupported(): Boolean {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && wifiRttManager != null && wifiManager.isWifiEnabled
}
fun startPeriodicScan(intervalMs: Long = 3000L) {
isScanning = true
handler.post(object : Runnable {
override fun run() {
if (isScanning) {
startWifiScan()
handler.postDelayed(this, intervalMs)
}
}
})
}
fun stopPeriodicScan() {
isScanning = false
handler.removeCallbacksAndMessages(null)
}
fun startWifiScan() {
val success = wifiManager.startScan()
if (success) {
val results = wifiManager.scanResults
processScanResults(results)
if (isRttSupported()) {
startRttRanging(results)
}
} else {
Log.e("WIFI_POSITION", "Wi-Fi scan failed")
onWifiPositioningUpdate(WifiScanResult(0, emptyList()))
}
}
private fun processScanResults(results: List<ScanResult>) {
lastWifiScanAps = results.map { result ->
WifiAp(
ssid = result.SSID,
bssid = result.BSSID,
rssi = result.level
)
}.toMutableList()
Log.d("WIFI_POSITION", "Wi-Fi scan → AP count: ${lastWifiScanAps.size}")
lastWifiScanAps.forEach {
Log.d("WIFI_POSITION", "SSID: ${it.ssid}, BSSID: ${it.bssid}, RSSI: ${it.rssi} dBm")
}
onWifiPositioningUpdate(WifiScanResult(lastWifiScanAps.size, lastWifiScanAps))
}
private fun startRttRanging(results: List<ScanResult>) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P || wifiRttManager == null) {
Log.d("WIFI_POSITION", "RTT not supported or WifiRttManager is null.")
return
}
val rttCapableAps = results.filter { it.is80211mcResponder }
if (rttCapableAps.isEmpty()) {
Log.d("WIFI_POSITION", "No RTT-capable AP found.")
return
}
val rangingRequest = RangingRequest.Builder()
.addAccessPoints(rttCapableAps)
.build()
try {
wifiRttManager.startRanging(
rangingRequest,
Executors.newSingleThreadExecutor(),
object : RangingResultCallback() {
override fun onRangingFailure(code: Int) {
Log.e("WIFI_POSITION", "RTT Ranging failed: $code")
}
override fun onRangingResults(results: List<android.net.wifi.rtt.RangingResult>) {
results.forEach { result ->
val mac = result.macAddress.toString()
val distance = if (result.status == android.net.wifi.rtt.RangingResult.STATUS_SUCCESS) {
result.distanceMm / 1000.0
} else null
lastWifiScanAps.indexOfFirst { it.bssid == mac }.takeIf { it >= 0 }?.let { idx ->
lastWifiScanAps[idx] = lastWifiScanAps[idx].copy(distance = distance)
}
Log.d(
"WIFI_POSITION",
if (distance != null)
"RTT → ${mac}, Distance: ${distance} m"
else
"RTT → ${mac}, Status: ${result.status}"
)
}
onWifiPositioningUpdate(WifiScanResult(lastWifiScanAps.size, lastWifiScanAps))
}
}
)
} catch (e: SecurityException) {
Log.e("WIFI_POSITION", "SecurityException: Missing NEARBY_WIFI_DEVICES permission", e)
}
}
}
data class WifiAp(
val ssid: String,
val bssid: String,
val rssi: Int,
val distance: Double? = null
)
data class WifiScanResult(
val apCount: Int,
val aps: List<WifiAp>
)

View File

@ -0,0 +1,142 @@
package com.dumon.plugin.geolocation.wifi
import android.content.Context
import android.net.wifi.ScanResult
import android.net.wifi.WifiManager
import android.net.wifi.rtt.RangingRequest
import android.net.wifi.rtt.RangingResultCallback
import android.net.wifi.rtt.WifiRttManager
import android.os.*
import android.util.Log
import java.util.concurrent.Executors
class WifiPositioningManager(
private val context: Context,
private val onWifiPositioningUpdate: (WifiScanResult) -> Unit
) {
private val wifiManager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
private val wifiRttManager: WifiRttManager? =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
context.applicationContext.getSystemService(Context.WIFI_RTT_RANGING_SERVICE) as? WifiRttManager
} else null
private val handler = Handler(Looper.getMainLooper())
private var isScanning = false
private var lastScanAps: MutableList<WifiAp> = mutableListOf()
fun isRttSupported(): Boolean {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && wifiRttManager != null && wifiManager.isWifiEnabled
}
fun startPeriodicScan(intervalMs: Long = 3000L) {
isScanning = true
handler.post(object : Runnable {
override fun run() {
if (isScanning) {
startWifiScan()
handler.postDelayed(this, intervalMs)
}
}
})
}
fun stopPeriodicScan() {
isScanning = false
handler.removeCallbacksAndMessages(null)
}
fun startWifiScan() {
val success = wifiManager.startScan()
if (success) {
val results = wifiManager.scanResults
processScanResults(results)
if (isRttSupported()) {
startRttRanging(results)
}
} else {
Log.e("WIFI_POSITION", "Wi-Fi scan failed")
onWifiPositioningUpdate(WifiScanResult(0, emptyList()))
}
}
private fun processScanResults(results: List<ScanResult>) {
lastScanAps = results.map { result ->
WifiAp(
ssid = result.SSID,
bssid = result.BSSID,
rssi = result.level
)
}.toMutableList()
Log.d("WIFI_POSITION", "Scan result → ${lastScanAps.size} APs")
lastScanAps.forEach {
Log.d("WIFI_AP", "SSID=${it.ssid}, BSSID=${it.bssid}, RSSI=${it.rssi} dBm")
}
onWifiPositioningUpdate(WifiScanResult(lastScanAps.size, lastScanAps))
}
private fun startRttRanging(results: List<ScanResult>) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P || wifiRttManager == null) return
val rttCapable = results.filter { it.is80211mcResponder }
if (rttCapable.isEmpty()) {
Log.d("WIFI_RTT", "No RTT-capable AP found.")
return
}
val request = RangingRequest.Builder()
.addAccessPoints(rttCapable)
.build()
try {
wifiRttManager.startRanging(
request,
Executors.newSingleThreadExecutor(),
object : RangingResultCallback() {
override fun onRangingFailure(code: Int) {
Log.e("WIFI_RTT", "Ranging failed: $code")
}
override fun onRangingResults(results: List<android.net.wifi.rtt.RangingResult>) {
results.forEach { result ->
val bssid = result.macAddress.toString()
val distance = if (result.status == android.net.wifi.rtt.RangingResult.STATUS_SUCCESS) {
result.distanceMm / 1000.0
} else null
lastScanAps.indexOfFirst { it.bssid == bssid }
.takeIf { it >= 0 }?.let { idx ->
lastScanAps[idx] = lastScanAps[idx].copy(distance = distance)
}
Log.d(
"WIFI_RTT",
if (distance != null)
"RTT → $bssid : ${distance} m"
else
"RTT → $bssid : FAILED"
)
}
onWifiPositioningUpdate(WifiScanResult(lastScanAps.size, lastScanAps))
}
}
)
} catch (e: SecurityException) {
Log.e("WIFI_RTT", "Missing NEARBY_WIFI_DEVICES permission", e)
}
}
}
data class WifiAp(
val ssid: String,
val bssid: String,
val rssi: Int,
val distance: Double? = null
)
data class WifiScanResult(
val apCount: Int,
val aps: List<WifiAp>
)

View File

@ -0,0 +1,163 @@
package com.dumon.plugin.geolocation.wifi
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.net.wifi.ScanResult
import android.net.wifi.WifiManager
import android.net.wifi.rtt.RangingRequest
import android.net.wifi.rtt.RangingResultCallback
import android.net.wifi.rtt.WifiRttManager
import android.os.*
import android.util.Log
import androidx.core.app.ActivityCompat
import java.util.concurrent.Executors
class WifiPositioningManager(
private val context: Context,
private val onWifiPositioningUpdate: (WifiScanResult) -> Unit
) {
private val wifiManager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
private val wifiRttManager: WifiRttManager? =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
context.applicationContext.getSystemService(Context.WIFI_RTT_RANGING_SERVICE) as? WifiRttManager
} else {
null
}
private val handler = Handler(Looper.getMainLooper())
private var isScanning = false
private var lastWifiScanAps: MutableList<WifiAp> = mutableListOf()
fun isRttSupported(): Boolean {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && wifiRttManager != null && wifiManager.isWifiEnabled
}
fun startPeriodicScan(intervalMs: Long = 3000L) {
isScanning = true
handler.post(object : Runnable {
override fun run() {
if (isScanning) {
startWifiScan()
handler.postDelayed(this, intervalMs)
}
}
})
}
fun stopPeriodicScan() {
isScanning = false
handler.removeCallbacksAndMessages(null)
}
fun startWifiScan() {
if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
) {
val success = wifiManager.startScan()
if (success) {
val results = wifiManager.scanResults
processScanResults(results)
if (isRttSupported()) {
startRttRanging(results)
}
} else {
Log.e("WIFI_POSITION", "Wi-Fi scan failed")
onWifiPositioningUpdate(WifiScanResult(0, emptyList()))
}
} else {
Log.e("WIFI_POSITION", "Missing ACCESS_FINE_LOCATION permission")
onWifiPositioningUpdate(WifiScanResult(0, emptyList()))
}
}
private fun processScanResults(results: List<ScanResult>) {
lastWifiScanAps = results.map { result ->
WifiAp(
ssid = result.SSID,
bssid = result.BSSID,
rssi = result.level
)
}.toMutableList()
Log.d("WIFI_POSITION", "Wi-Fi scan → AP count: ${lastWifiScanAps.size}")
lastWifiScanAps.forEach {
Log.d("WIFI_POSITION", "SSID: ${it.ssid}, BSSID: ${it.bssid}, RSSI: ${it.rssi} dBm")
}
onWifiPositioningUpdate(WifiScanResult(lastWifiScanAps.size, lastWifiScanAps))
}
private fun startRttRanging(results: List<ScanResult>) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P || wifiRttManager == null) {
Log.d("WIFI_POSITION", "RTT not supported or WifiRttManager is null.")
return
}
val rttCapableAps = results.filter { it.is80211mcResponder }
if (rttCapableAps.isEmpty()) {
Log.d("WIFI_POSITION", "No RTT-capable AP found.")
return
}
val rangingRequest = RangingRequest.Builder()
.addAccessPoints(rttCapableAps)
.build()
try {
wifiRttManager.startRanging(
rangingRequest,
Executors.newSingleThreadExecutor(),
object : RangingResultCallback() {
override fun onRangingFailure(code: Int) {
Log.e("WIFI_POSITION", "RTT Ranging failed: $code")
}
override fun onRangingResults(results: List<android.net.wifi.rtt.RangingResult>) {
results.forEach { result ->
val mac = result.macAddress.toString()
val distance = if (result.status == android.net.wifi.rtt.RangingResult.STATUS_SUCCESS) {
result.distanceMm / 1000.0
} else {
Log.w("WIFI_POSITION", "RTT distance unavailable for ${result.macAddress}")
null
}
lastWifiScanAps.indexOfFirst { it.bssid == mac }.takeIf { it >= 0 }?.let { idx ->
lastWifiScanAps[idx] = lastWifiScanAps[idx].copy(distance = distance)
}
Log.d(
"WIFI_POSITION",
if (distance != null)
"RTT → ${mac}, Distance: ${distance} m"
else
"RTT → ${mac}, Status: ${result.status}"
)
}
onWifiPositioningUpdate(WifiScanResult(lastWifiScanAps.size, lastWifiScanAps))
}
}
)
} catch (e: SecurityException) {
Log.e("WIFI_POSITION", "SecurityException: Missing NEARBY_WIFI_DEVICES permission", e)
}
}
}
data class WifiAp(
val ssid: String,
val bssid: String,
val rssi: Int,
val distance: Double? = null
)
data class WifiScanResult(
val apCount: Int,
val aps: List<WifiAp>
)

View File

@ -0,0 +1,163 @@
package com.dumon.plugin.geolocation.wifi
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.net.wifi.ScanResult
import android.net.wifi.WifiManager
import android.net.wifi.rtt.RangingRequest
import android.net.wifi.rtt.RangingResultCallback
import android.net.wifi.rtt.WifiRttManager
import android.os.*
import android.util.Log
import androidx.core.app.ActivityCompat
import java.util.concurrent.Executors
class WifiPositioningManager(
private val context: Context,
private val onWifiPositioningUpdate: (WifiScanResult) -> Unit
) {
private val wifiManager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
private val wifiRttManager: WifiRttManager? =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
context.applicationContext.getSystemService(Context.WIFI_RTT_RANGING_SERVICE) as? WifiRttManager
} else {
null
}
private val handler = Handler(Looper.getMainLooper())
private var isScanning = false
private var lastWifiScanAps: MutableList<WifiAp> = mutableListOf()
fun isRttSupported(): Boolean {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && wifiRttManager != null && wifiManager.isWifiEnabled
}
fun startPeriodicScan(intervalMs: Long = 3000L) {
isScanning = true
handler.post(object : Runnable {
override fun run() {
if (isScanning) {
startWifiScan()
handler.postDelayed(this, intervalMs)
}
}
})
}
fun stopPeriodicScan() {
isScanning = false
handler.removeCallbacksAndMessages(null)
}
fun startWifiScan() {
if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
) {
val success = wifiManager.startScan()
if (success) {
val results = wifiManager.scanResults
processScanResults(results)
if (isRttSupported()) {
startRttRanging(results)
}
} else {
Log.e("WIFI_POSITION", "Wi-Fi scan failed")
onWifiPositioningUpdate(WifiScanResult(0, emptyList()))
}
} else {
Log.e("WIFI_POSITION", "Missing ACCESS_FINE_LOCATION permission")
onWifiPositioningUpdate(WifiScanResult(0, emptyList()))
}
}
private fun processScanResults(results: List<ScanResult>) {
lastWifiScanAps = results.map { result ->
WifiAp(
ssid = result.SSID,
bssid = result.BSSID,
rssi = result.level
)
}.toMutableList()
Log.d("WIFI_POSITION", "Wi-Fi scan → AP count: ${lastWifiScanAps.size}")
lastWifiScanAps.forEach {
Log.d("WIFI_POSITION", "SSID: ${it.ssid}, BSSID: ${it.bssid}, RSSI: ${it.rssi} dBm")
}
onWifiPositioningUpdate(WifiScanResult(lastWifiScanAps.size, lastWifiScanAps))
}
private fun startRttRanging(results: List<ScanResult>) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P || wifiRttManager == null) {
Log.d("WIFI_POSITION", "RTT not supported or WifiRttManager is null.")
return
}
val rttCapableAps = results.filter { it.is80211mcResponder }
if (rttCapableAps.isEmpty()) {
Log.d("WIFI_POSITION", "No RTT-capable AP found.")
return
}
val rangingRequest = RangingRequest.Builder()
.addAccessPoints(rttCapableAps)
.build()
try {
wifiRttManager.startRanging(
rangingRequest,
Executors.newSingleThreadExecutor(),
object : RangingResultCallback() {
override fun onRangingFailure(code: Int) {
Log.e("WIFI_POSITION", "RTT Ranging failed: $code")
}
override fun onRangingResults(results: List<android.net.wifi.rtt.RangingResult>) {
results.forEach { result ->
val mac = result.macAddress.toString()
val distance = if (result.status == android.net.wifi.rtt.RangingResult.STATUS_SUCCESS) {
result.distanceMm / 1000.0
} else {
Log.w("WIFI_POSITION", "RTT distance unavailable for ${result.macAddress}")
null
}
lastWifiScanAps.indexOfFirst { it.bssid == mac }.takeIf { it >= 0 }?.let { idx ->
lastWifiScanAps[idx] = lastWifiScanAps[idx].copy(distance = distance)
}
Log.d(
"WIFI_POSITION",
if (distance != null)
"RTT → ${mac}, Distance: ${distance} m"
else
"RTT → ${mac}, Status: ${result.status}"
)
}
onWifiPositioningUpdate(WifiScanResult(lastWifiScanAps.size, lastWifiScanAps))
}
}
)
} catch (e: SecurityException) {
Log.e("WIFI_POSITION", "SecurityException: Missing NEARBY_WIFI_DEVICES permission", e)
}
}
}
data class WifiAp(
val ssid: String,
val bssid: String,
val rssi: Int,
val distance: Double? = null
)
data class WifiScanResult(
val apCount: Int,
val aps: List<WifiAp>
)

View File

@ -0,0 +1,163 @@
package com.dumon.plugin.geolocation.wifi
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.net.wifi.ScanResult
import android.net.wifi.WifiManager
import android.net.wifi.rtt.RangingRequest
import android.net.wifi.rtt.RangingResultCallback
import android.net.wifi.rtt.WifiRttManager
import android.os.*
import android.util.Log
import androidx.core.app.ActivityCompat
import java.util.concurrent.Executors
class WifiPositioningManager(
private val context: Context,
private val onWifiPositioningUpdate: (WifiScanResult) -> Unit
) {
private val wifiManager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
private val wifiRttManager: WifiRttManager? =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
context.applicationContext.getSystemService(Context.WIFI_RTT_RANGING_SERVICE) as? WifiRttManager
} else {
null
}
private val handler = Handler(Looper.getMainLooper())
private var isScanning = false
private var lastWifiScanAps: MutableList<WifiAp> = mutableListOf()
fun isRttSupported(): Boolean {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && wifiRttManager != null && wifiManager.isWifiEnabled
}
fun startPeriodicScan(intervalMs: Long = 3000L) {
isScanning = true
handler.post(object : Runnable {
override fun run() {
if (isScanning) {
startWifiScan()
handler.postDelayed(this, intervalMs)
}
}
})
}
fun stopPeriodicScan() {
isScanning = false
handler.removeCallbacksAndMessages(null)
}
fun startWifiScan() {
if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
) {
val success = wifiManager.startScan()
if (success) {
val results = wifiManager.scanResults
processScanResults(results)
if (isRttSupported()) {
startRttRanging(results)
}
} else {
Log.e("WIFI_POSITION", "Wi-Fi scan failed")
onWifiPositioningUpdate(WifiScanResult(0, emptyList()))
}
} else {
Log.e("WIFI_POSITION", "Missing ACCESS_FINE_LOCATION permission")
onWifiPositioningUpdate(WifiScanResult(0, emptyList()))
}
}
private fun processScanResults(results: List<ScanResult>) {
lastWifiScanAps = results.map { result ->
WifiAp(
ssid = result.SSID,
bssid = result.BSSID,
rssi = result.level
)
}.toMutableList()
Log.d("WIFI_POSITION", "Wi-Fi scan → AP count: ${lastWifiScanAps.size}")
lastWifiScanAps.forEach {
Log.d("WIFI_POSITION", "SSID: ${it.ssid}, BSSID: ${it.bssid}, RSSI: ${it.rssi} dBm")
}
onWifiPositioningUpdate(WifiScanResult(lastWifiScanAps.size, lastWifiScanAps))
}
private fun startRttRanging(results: List<ScanResult>) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P || wifiRttManager == null) {
Log.d("WIFI_POSITION", "RTT not supported or WifiRttManager is null.")
return
}
val rttCapableAps = results.filter { it.is80211mcResponder }
if (rttCapableAps.isEmpty()) {
Log.d("WIFI_POSITION", "No RTT-capable AP found.")
return
}
val rangingRequest = RangingRequest.Builder()
.addAccessPoints(rttCapableAps)
.build()
try {
wifiRttManager.startRanging(
rangingRequest,
Executors.newSingleThreadExecutor(),
object : RangingResultCallback() {
override fun onRangingFailure(code: Int) {
Log.e("WIFI_POSITION", "RTT Ranging failed: $code")
}
override fun onRangingResults(results: List<android.net.wifi.rtt.RangingResult>) {
results.forEach { result ->
val mac = result.macAddress.toString()
val distance = if (result.status == android.net.wifi.rtt.RangingResult.STATUS_SUCCESS) {
result.distanceMm / 1000.0
} else {
Log.w("WIFI_POSITION", "RTT distance unavailable for ${result.macAddress}")
null
}
lastWifiScanAps.indexOfFirst { it.bssid == mac }.takeIf { it >= 0 }?.let { idx ->
lastWifiScanAps[idx] = lastWifiScanAps[idx].copy(distance = distance)
}
Log.d(
"WIFI_POSITION",
if (distance != null)
"RTT → ${mac}, Distance: ${distance} m"
else
"RTT → ${mac}, Status: ${result.status}"
)
}
onWifiPositioningUpdate(WifiScanResult(lastWifiScanAps.size, lastWifiScanAps))
}
}
)
} catch (e: SecurityException) {
Log.e("WIFI_POSITION", "SecurityException: Missing NEARBY_WIFI_DEVICES permission", e)
}
}
}
data class WifiAp(
val ssid: String,
val bssid: String,
val rssi: Int,
val distance: Double? = null
)
data class WifiScanResult(
val apCount: Int,
val aps: List<WifiAp>
)

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|navigation"
android:name=".MainActivity"
android:label="@string/title_activity_main"
android:theme="@style/AppTheme.NoActionBarLaunch"
android:launchMode="singleTask"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"></meta-data>
</provider>
</application>
<!-- Permissions -->
<uses-permission android:name="android.permission.INTERNET" />
</manifest>

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|navigation"
android:name=".MainActivity"
android:label="@string/title_activity_main"
android:theme="@style/AppTheme.NoActionBarLaunch"
android:launchMode="singleTask"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"></meta-data>
</provider>
</application>
<!-- Permissions -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" />
</manifest>

View File

@ -0,0 +1,26 @@
<!doctype html>
<html lang="en" dir="ltr" class="hydrated">
<head>
<meta charset="UTF-8" />
<title>Example Capacitor App</title>
<meta
name="viewport"
content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<meta name="format-detection" content="telephone=no" />
</head>
<body>
<main>
<h1>Capacitor Test Plugin Project</h1>
<p>
This project can be used to test out the functionality of your plugin. Nothing in the
<em>example-app/</em> folder will be published to npm when using this template, so you can create away!
</p>
<label for="echoInput">Echo Text:</label>
<input type="text" id="echoInput" name="echoInput" value="Hello World!" />
<button onclick="testEcho()">Click Me!</button>
</main>
<script src="./js/example.js" type="module"></script>
</body>
</html>

View File

@ -0,0 +1,24 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Example Capacitor App - DumonGeolocation</title>
<meta
name="viewport"
content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<meta name="format-detection" content="telephone=no" />
</head>
<body>
<h1>Capacitor DumonGeolocation Plugin Test</h1>
<hr />
<button id="startButton">Start Positioning</button>
<button id="stopButton">Stop Positioning</button>
<button id="getLatestButton">Get Latest Position</button>
<pre id="logArea" style="background-color: #f0f0f0; padding: 10px; white-space: pre-wrap;"></pre>
<script type="module" src="./js/example.js"></script>
</body>
</html>

View File

@ -0,0 +1,58 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Example Capacitor App - DumonGeolocation</title>
<meta
name="viewport"
content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<meta name="format-detection" content="telephone=no" />
<style>
body {
font-family: sans-serif;
margin: 1rem;
background-color: #ffffff;
}
h1 {
font-size: 1.2rem;
margin-bottom: 0.5rem;
}
button {
margin-right: 0.5rem;
margin-bottom: 0.5rem;
padding: 8px 16px;
font-size: 1rem;
border: none;
background-color: #3366ff;
color: white;
border-radius: 6px;
cursor: pointer;
}
pre {
background-color: #f8f8f8;
padding: 1rem;
border: 1px solid #ccc;
border-radius: 6px;
overflow-x: auto;
white-space: pre-wrap;
font-size: 0.9rem;
max-height: 70vh;
}
</style>
</head>
<body>
<h1>Capacitor DumonGeolocation Plugin Test</h1>
<button id="startButton">Start Positioning</button>
<button id="stopButton">Stop Positioning</button>
<button id="getLatestButton">Get Latest Position</button>
<pre id="logArea"></pre>
<script type="module" src="./js/example.js"></script>
</body>
</html>

View File

@ -0,0 +1,6 @@
import { DumonGeolocation } from 'dumon-geolocation';
window.testEcho = () => {
const inputValue = document.getElementById("echoInput").value;
DumonGeolocation.echo({ value: inputValue })
}

View File

@ -0,0 +1,24 @@
import { DumonGeolocation } from '@dumon/capacitor-geolocation';
async function startGeolocation() {
DumonGeolocation.addListener('onPositionUpdate', (data) => {
console.log('[onPositionUpdate]', data);
});
await DumonGeolocation.startPositioning();
}
async function stopGeolocation() {
await DumonGeolocation.stopPositioning();
}
async function getLatestPosition() {
const data = await DumonGeolocation.getLatestPosition();
console.log('[getLatestPosition]', data);
}
window.addEventListener('DOMContentLoaded', () => {
document.getElementById('startButton').addEventListener('click', startGeolocation);
document.getElementById('stopButton').addEventListener('click', stopGeolocation);
document.getElementById('getLatestButton').addEventListener('click', getLatestPosition);
});

View File

@ -0,0 +1,24 @@
import { DumonGeolocation } from 'dumon-geolocation';
async function startGeolocation() {
DumonGeolocation.addListener('onPositionUpdate', (data) => {
console.log('[onPositionUpdate]', data);
});
await DumonGeolocation.startPositioning();
}
async function stopGeolocation() {
await DumonGeolocation.stopPositioning();
}
async function getLatestPosition() {
const data = await DumonGeolocation.getLatestPosition();
console.log('[getLatestPosition]', data);
}
window.addEventListener('DOMContentLoaded', () => {
document.getElementById('startButton').addEventListener('click', startGeolocation);
document.getElementById('stopButton').addEventListener('click', stopGeolocation);
document.getElementById('getLatestButton').addEventListener('click', getLatestPosition);
});

View File

@ -0,0 +1,46 @@
import { DumonGeolocation } from 'dumon-geolocation';
const logArea = document.getElementById('logArea');
function appendLog(title, data) {
const timestamp = new Date().toLocaleTimeString();
const formatted = `[${timestamp}] ${title}\n${JSON.stringify(data, null, 2)}\n\n`;
logArea.textContent = formatted + logArea.textContent;
}
async function startGeolocation() {
DumonGeolocation.addListener('onPositionUpdate', (data) => {
appendLog('onPositionUpdate', data);
});
try {
await DumonGeolocation.startPositioning();
appendLog('startPositioning', { success: true });
} catch (err) {
appendLog('startPositioning', { error: err.message });
}
}
async function stopGeolocation() {
try {
await DumonGeolocation.stopPositioning();
appendLog('stopPositioning', { success: true });
} catch (err) {
appendLog('stopPositioning', { error: err.message });
}
}
async function getLatestPosition() {
try {
const data = await DumonGeolocation.getLatestPosition();
appendLog('getLatestPosition', data);
} catch (err) {
appendLog('getLatestPosition', { error: err.message });
}
}
window.addEventListener('DOMContentLoaded', () => {
document.getElementById('startButton').addEventListener('click', startGeolocation);
document.getElementById('stopButton').addEventListener('click', stopGeolocation);
document.getElementById('getLatestButton').addEventListener('click', getLatestPosition);
});

View File

@ -0,0 +1,46 @@
import { DumonGeolocation } from 'dumon-geolocation';
const logArea = document.getElementById('logArea');
function appendLog(title, data) {
const timestamp = new Date().toLocaleTimeString();
const formatted = `[${timestamp}] ${title}\n${JSON.stringify(data, null, 2)}\n\n`;
logArea.textContent = formatted; // + logArea.textContent;
}
async function startGeolocation() {
DumonGeolocation.addListener('onPositionUpdate', (data) => {
appendLog('onPositionUpdate', data);
});
try {
await DumonGeolocation.startPositioning();
appendLog('startPositioning', { success: true });
} catch (err) {
appendLog('startPositioning', { error: err.message });
}
}
async function stopGeolocation() {
try {
await DumonGeolocation.stopPositioning();
appendLog('stopPositioning', { success: true });
} catch (err) {
appendLog('stopPositioning', { error: err.message });
}
}
async function getLatestPosition() {
try {
const data = await DumonGeolocation.getLatestPosition();
appendLog('getLatestPosition', data);
} catch (err) {
appendLog('getLatestPosition', { error: err.message });
}
}
window.addEventListener('DOMContentLoaded', () => {
document.getElementById('startButton').addEventListener('click', startGeolocation);
document.getElementById('stopButton').addEventListener('click', stopGeolocation);
document.getElementById('getLatestButton').addEventListener('click', getLatestPosition);
});

View File

@ -0,0 +1,49 @@
import { DumonGeolocation } from 'dumon-geolocation';
const logArea = document.getElementById('logArea');
function appendLog(title, data) {
const timestamp = new Date().toLocaleTimeString();
const formatted = `[${timestamp}] ${title}\n${JSON.stringify(data, null, 2)}\n\n`;
if(data.wifiData.aps && data.wifiData.aps.length > 0){
console.log('---WiFi-data detected');
}
logArea.textContent = formatted; // + logArea.textContent;
}
async function startGeolocation() {
DumonGeolocation.addListener('onPositionUpdate', (data) => {
appendLog('onPositionUpdate', data);
});
try {
await DumonGeolocation.startPositioning();
appendLog('startPositioning', { success: true });
} catch (err) {
appendLog('startPositioning', { error: err.message });
}
}
async function stopGeolocation() {
try {
await DumonGeolocation.stopPositioning();
appendLog('stopPositioning', { success: true });
} catch (err) {
appendLog('stopPositioning', { error: err.message });
}
}
async function getLatestPosition() {
try {
const data = await DumonGeolocation.getLatestPosition();
appendLog('getLatestPosition', data);
} catch (err) {
appendLog('getLatestPosition', { error: err.message });
}
}
window.addEventListener('DOMContentLoaded', () => {
document.getElementById('startButton').addEventListener('click', startGeolocation);
document.getElementById('stopButton').addEventListener('click', stopGeolocation);
document.getElementById('getLatestButton').addEventListener('click', getLatestPosition);
});

View File

@ -0,0 +1,46 @@
import { DumonGeolocation } from 'dumon-geolocation';
const logArea = document.getElementById('logArea');
function appendLog(title, data) {
const timestamp = new Date().toLocaleTimeString();
const formatted = `[${timestamp}] ${title}\n${JSON.stringify(data, null, 2)}\n\n`;
logArea.textContent = formatted; // + logArea.textContent;
}
async function startGeolocation() {
DumonGeolocation.addListener('onPositionUpdate', (data) => {
appendLog('onPositionUpdate', data);
});
try {
await DumonGeolocation.startPositioning();
appendLog('startPositioning', { success: true });
} catch (err) {
appendLog('startPositioning', { error: err.message });
}
}
async function stopGeolocation() {
try {
await DumonGeolocation.stopPositioning();
appendLog('stopPositioning', { success: true });
} catch (err) {
appendLog('stopPositioning', { error: err.message });
}
}
async function getLatestPosition() {
try {
const data = await DumonGeolocation.getLatestPosition();
appendLog('getLatestPosition', data);
} catch (err) {
appendLog('getLatestPosition', { error: err.message });
}
}
window.addEventListener('DOMContentLoaded', () => {
document.getElementById('startButton').addEventListener('click', startGeolocation);
document.getElementById('stopButton').addEventListener('click', stopGeolocation);
document.getElementById('getLatestButton').addEventListener('click', getLatestPosition);
});

View File

@ -0,0 +1,46 @@
import { DumonGeolocation } from 'dumon-geolocation';
const logArea = document.getElementById('logArea');
function appendLog(title, data) {
const timestamp = new Date().toLocaleTimeString();
const formatted = `[${timestamp}] ${title}\n${JSON.stringify(data, null, 2)}\n\n`;
logArea.textContent = formatted; // + logArea.textContent;
}
async function startGeolocation() {
DumonGeolocation.addListener('onPositionUpdate', (data) => {
appendLog('onPositionUpdate', data);
});
try {
await DumonGeolocation.startPositioning();
appendLog('startPositioning', { success: true });
} catch (err) {
appendLog('startPositioning', { error: err.message });
}
}
async function stopGeolocation() {
try {
await DumonGeolocation.stopPositioning();
appendLog('stopPositioning', { success: true });
} catch (err) {
appendLog('stopPositioning', { error: err.message });
}
}
async function getLatestPosition() {
try {
const data = await DumonGeolocation.getLatestPosition();
appendLog('getLatestPosition', data);
} catch (err) {
appendLog('getLatestPosition', { error: err.message });
}
}
window.addEventListener('DOMContentLoaded', () => {
document.getElementById('startButton').addEventListener('click', startGeolocation);
document.getElementById('stopButton').addEventListener('click', stopGeolocation);
document.getElementById('getLatestButton').addEventListener('click', getLatestPosition);
});

View File

@ -0,0 +1,80 @@
{
"name": "dumon-geolocation",
"version": "0.0.1",
"description": "Implement manager GNSS, WiFi RTT, IMU, Kalman fusion, event emitter",
"main": "dist/plugin.cjs.js",
"module": "dist/esm/index.js",
"types": "dist/esm/index.d.ts",
"unpkg": "dist/plugin.js",
"files": [
"android/src/main/",
"android/build.gradle",
"dist/",
"ios/Sources",
"ios/Tests",
"Package.swift",
"DumonGeolocation.podspec"
],
"author": "Donald Wengki Goni",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://git.jayacita.com/wengki/dumon-geolocation.git.git"
},
"bugs": {
"url": "https://git.jayacita.com/wengki/dumon-geolocation.git/issues"
},
"keywords": [
"capacitor",
"plugin",
"native"
],
"scripts": {
"verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
"verify:ios": "xcodebuild -scheme DumonGeolocation -destination generic/platform=iOS",
"verify:android": "cd android && ./gradlew clean build test && cd ..",
"verify:web": "npm run build",
"lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
"fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --fix --format",
"eslint": "eslint . --ext ts",
"prettier": "prettier \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
"swiftlint": "node-swiftlint",
"docgen": "docgen --api DumonGeolocationPlugin --output-readme README.md --output-json dist/docs.json",
"build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs",
"clean": "rimraf ./dist",
"watch": "tsc --watch",
"prepublishOnly": "npm run build"
},
"devDependencies": {
"@capacitor/android": "^7.0.0",
"@capacitor/core": "^7.0.0",
"@capacitor/docgen": "^0.3.0",
"@capacitor/ios": "^7.0.0",
"@ionic/eslint-config": "^0.4.0",
"@ionic/prettier-config": "^4.0.0",
"@ionic/swiftlint-config": "^2.0.0",
"eslint": "^8.57.0",
"prettier": "^3.4.2",
"prettier-plugin-java": "^2.6.6",
"rimraf": "^6.0.1",
"rollup": "^4.30.1",
"swiftlint": "^2.0.0",
"typescript": "~4.1.5"
},
"peerDependencies": {
"@capacitor/core": ">=7.0.0"
},
"prettier": "@ionic/prettier-config",
"swiftlint": "@ionic/swiftlint-config",
"eslintConfig": {
"extends": "@ionic/eslint-config/recommended"
},
"capacitor": {
"ios": {
"src": "ios"
},
"android": {
"src": "android"
}
}
}

View File

@ -0,0 +1,80 @@
{
"name": "dumon-geolocation",
"version": "0.0.1",
"description": "Implement manager GNSS, WiFi RTT, IMU, Kalman fusion, event emitter",
"main": "dist/plugin.cjs.js",
"module": "dist/esm/index.js",
"types": "dist/esm/index.d.ts",
"unpkg": "dist/plugin.js",
"files": [
"android/src/main/",
"android/build.gradle",
"dist/",
"ios/Sources",
"ios/Tests",
"Package.swift",
"DumonGeolocation.podspec"
],
"author": "Donald Wengki Goni",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://git.jayacita.com/wengki/dumon-geolocation.git.git"
},
"bugs": {
"url": "https://git.jayacita.com/wengki/dumon-geolocation.git/issues"
},
"keywords": [
"capacitor",
"plugin",
"native"
],
"scripts": {
"verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
"verify:ios": "xcodebuild -scheme DumonGeolocation -destination generic/platform=iOS",
"verify:android": "cd android && ./gradlew clean build test && cd ..",
"verify:web": "npm run build",
"lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
"fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --fix --format",
"eslint": "eslint . --ext ts",
"prettier": "prettier \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
"swiftlint": "node-swiftlint",
"docgen": "docgen --api DumonGeolocationPlugin --output-readme README.md --output-json dist/docs.json",
"build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs",
"clean": "rimraf ./dist",
"watch": "tsc --watch",
"prepublishOnly": "npm run build"
},
"devDependencies": {
"@capacitor/android": "^6.0.0",
"@capacitor/core": "^6.0.0",
"@capacitor/docgen": "^0.3.0",
"@capacitor/ios": "^6.0.0",
"@ionic/eslint-config": "^0.4.0",
"@ionic/prettier-config": "^4.0.0",
"@ionic/swiftlint-config": "^2.0.0",
"eslint": "^8.57.0",
"prettier": "^3.4.2",
"prettier-plugin-java": "^2.6.6",
"rimraf": "^6.0.1",
"rollup": "^4.30.1",
"swiftlint": "^2.0.0",
"typescript": "~4.1.5"
},
"peerDependencies": {
"@capacitor/core": ">=6.0.0"
},
"prettier": "@ionic/prettier-config",
"swiftlint": "@ionic/swiftlint-config",
"eslintConfig": {
"extends": "@ionic/eslint-config/recommended"
},
"capacitor": {
"ios": {
"src": "ios"
},
"android": {
"src": "android"
}
}
}

View File

@ -0,0 +1,80 @@
{
"name": "dumon-geolocation",
"version": "0.0.1",
"description": "Implement manager GNSS, WiFi RTT, IMU, Kalman fusion, event emitter",
"main": "dist/plugin.cjs.js",
"module": "dist/esm/index.js",
"types": "dist/esm/index.d.ts",
"unpkg": "dist/plugin.js",
"files": [
"android/src/main/",
"android/build.gradle",
"dist/",
"ios/Sources",
"ios/Tests",
"Package.swift",
"DumonGeolocation.podspec"
],
"author": "Donald Wengki Goni",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://git.jayacita.com/wengki/dumon-geolocation.git.git"
},
"bugs": {
"url": "https://git.jayacita.com/wengki/dumon-geolocation.git/issues"
},
"keywords": [
"capacitor",
"plugin",
"native"
],
"scripts": {
"verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
"verify:ios": "xcodebuild -scheme DumonGeolocation -destination generic/platform=iOS",
"verify:android": "cd android && ./gradlew clean build test && cd ..",
"verify:web": "npm run build",
"lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
"fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --fix --format",
"eslint": "eslint . --ext ts",
"prettier": "prettier \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
"swiftlint": "node-swiftlint",
"docgen": "docgen --api DumonGeolocationPlugin --output-readme README.md --output-json dist/docs.json",
"build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs",
"clean": "rimraf ./dist",
"watch": "tsc --watch",
"prepublishOnly": "npm run build"
},
"devDependencies": {
"@capacitor/android": "^7.0.0",
"@capacitor/core": "^7.0.0",
"@capacitor/docgen": "^0.3.0",
"@capacitor/ios": "^7.0.0",
"@ionic/eslint-config": "^0.4.0",
"@ionic/prettier-config": "^4.0.0",
"@ionic/swiftlint-config": "^2.0.0",
"eslint": "^8.57.0",
"prettier": "^3.4.2",
"prettier-plugin-java": "^2.6.6",
"rimraf": "^6.0.1",
"rollup": "^4.30.1",
"swiftlint": "^2.0.0",
"typescript": "~4.1.5"
},
"peerDependencies": {
"@capacitor/core": ">=7.0.0"
},
"prettier": "@ionic/prettier-config",
"swiftlint": "@ionic/swiftlint-config",
"eslintConfig": {
"extends": "@ionic/eslint-config/recommended"
},
"capacitor": {
"ios": {
"src": "ios"
},
"android": {
"src": "android"
}
}
}

View File

@ -0,0 +1,3 @@
export interface DumonGeolocationPlugin {
echo(options: { value: string }): Promise<{ value: string }>;
}

View File

@ -0,0 +1,24 @@
import { PluginListenerHandle } from '@capacitor/core';
export interface PositioningData {
latitude: number;
longitude: number;
accuracy: number;
source: 'GNSS' | 'WIFI' | 'FUSED';
imuData: {
accelX: number;
accelY: number;
accelZ: number;
gyroX: number;
gyroY: number;
gyroZ: number;
};
}
export interface DumonGeolocationPlugin {
startPositioning(): Promise<void>;
stopPositioning(): Promise<void>;
getLatestPosition(): Promise<PositioningData>;
addListener(eventName: 'onPositionUpdate', listenerFunc: (data: PositioningData) => void): PluginListenerHandle;
}

View File

@ -0,0 +1,24 @@
import type { PluginListenerHandle } from '@capacitor/core';
export interface PositioningData {
latitude: number;
longitude: number;
accuracy: number;
source: 'GNSS' | 'WIFI' | 'FUSED';
imuData: {
accelX: number;
accelY: number;
accelZ: number;
gyroX: number;
gyroY: number;
gyroZ: number;
};
}
export interface DumonGeolocationPlugin {
startPositioning(): Promise<void>;
stopPositioning(): Promise<void>;
getLatestPosition(): Promise<PositioningData>;
addListener(eventName: 'onPositionUpdate', listenerFunc: (data: PositioningData) => void): PluginListenerHandle;
}

View File

@ -0,0 +1,53 @@
import type { PluginListenerHandle } from '@capacitor/core';
export interface SatelliteStatus {
satellitesInView: number;
usedInFix: number;
constellationCounts: { [key: string]: number };
}
export interface WifiAp {
ssid: string;
bssid: string;
rssi: number;
distance?: number;
}
export interface WifiScanResult {
apCount: number;
aps: WifiAp[];
}
export interface PositioningData {
source: 'GNSS' | 'WIFI' | 'FUSED';
timestamp: number;
gpsData: {
latitude: number;
longitude: number;
accuracy: number;
satellitesInView?: number;
usedInFix?: number;
constellationCounts?: { [key: string]: number };
};
wifiData: WifiScanResult;
imuData: {
accelX: number;
accelY: number;
accelZ: number;
gyroX: number;
gyroY: number;
gyroZ: number;
};
}
export interface DumonGeolocationPlugin {
startPositioning(): Promise<void>;
stopPositioning(): Promise<void>;
getLatestPosition(): Promise<PositioningData>;
addListener(eventName: 'onPositionUpdate', listenerFunc: (data: PositioningData) => void): PluginListenerHandle;
}

View File

@ -0,0 +1,56 @@
import type { PluginListenerHandle } from '@capacitor/core';
export interface SatelliteStatus {
satellitesInView: number;
usedInFix: number;
constellationCounts: { [key: string]: number };
}
export interface WifiAp {
ssid: string;
bssid: string;
rssi: number;
distance?: number;
}
export interface WifiScanResult {
apCount: number;
aps: WifiAp[];
}
export interface ImuData {
accelX: number;
accelY: number;
accelZ: number;
gyroX: number;
gyroY: number;
gyroZ: number;
}
export interface GpsData {
latitude: number;
longitude: number;
accuracy: number;
satellitesInView?: number;
usedInFix?: number;
constellationCounts?: { [key: string]: number };
}
export interface PositioningData {
source: 'GNSS' | 'WIFI' | 'FUSED';
timestamp: number;
gpsData: GpsData;
wifiData: WifiScanResult;
imuData: ImuData;
}
export interface DumonGeolocationPlugin {
startPositioning(): Promise<void>;
stopPositioning(): Promise<void>;
getLatestPosition(): Promise<PositioningData>;
addListener(
eventName: 'onPositionUpdate',
listenerFunc: (data: PositioningData) => void
): PluginListenerHandle;
}

View File

@ -0,0 +1,63 @@
import type { PluginListenerHandle } from '@capacitor/core';
export interface SatelliteStatus {
satellitesInView: number;
usedInFix: number;
constellationCounts: { [key: string]: number };
}
export interface WifiAp {
ssid: string;
bssid: string;
rssi: number;
distance?: number;
}
export interface WifiScanResult {
apCount: number;
aps: WifiAp[];
}
export interface ImuData {
accelX: number;
accelY: number;
accelZ: number;
gyroX: number;
gyroY: number;
gyroZ: number;
speed?: number;
acceleration?: number;
directionRad?: number;
}
export interface GpsData {
latitude: number;
longitude: number;
accuracy: number;
satellitesInView?: number;
usedInFix?: number;
constellationCounts?: { [key: string]: number };
}
export interface PositioningData {
source: 'GNSS' | 'WIFI' | 'FUSED' | 'MOCK';
timestamp: number;
latitude: number;
longitude: number;
accuracy: number;
gnssData?: SatelliteStatus;
wifiData?: WifiAp[];
imuData?: ImuData;
}
export interface DumonGeolocationPlugin {
startPositioning(): Promise<void>;
stopPositioning(): Promise<void>;
getLatestPosition(): Promise<PositioningData>;
addListener(
eventName: 'onPositionUpdate',
listenerFunc: (data: PositioningData) => void
): PluginListenerHandle;
}

View File

@ -0,0 +1,10 @@
import { registerPlugin } from '@capacitor/core';
import type { DumonGeolocationPlugin } from './definitions';
const DumonGeolocation = registerPlugin<DumonGeolocationPlugin>('DumonGeolocation', {
web: () => import('./web').then((m) => new m.DumonGeolocationWeb()),
});
export * from './definitions';
export { DumonGeolocation };

View File

@ -0,0 +1,10 @@
import { registerPlugin } from '@capacitor/core';
import type { DumonGeolocationPlugin } from './definitions';
const DumonGeolocation = registerPlugin<DumonGeolocationPlugin>('DumonGeolocation', {
web: () => import('./web').then((m) => new m.DumonGeolocationWeb()),
});
export * from './definitions';
export { DumonGeolocation };

View File

@ -0,0 +1,10 @@
import { registerPlugin } from '@capacitor/core';
import type { DumonGeolocationPlugin } from './definitions';
const DumonGeolocation = registerPlugin<DumonGeolocationPlugin>('DumonGeolocation', {
web: () => import('./web').then((m) => new m.DumonGeolocationWeb()),
});
export * from './definitions';
export { DumonGeolocation };

View File

@ -0,0 +1,24 @@
import { DumonGeolocation } from '@dumon/capacitor-geolocation';
async function startGeolocation() {
DumonGeolocation.addListener('onPositionUpdate', (data) => {
console.log('[onPositionUpdate]', data);
});
await DumonGeolocation.startPositioning();
}
async function stopGeolocation() {
await DumonGeolocation.stopPositioning();
}
async function getLatestPosition() {
const data = await DumonGeolocation.getLatestPosition();
console.log('[getLatestPosition]', data);
}
window.addEventListener('DOMContentLoaded', () => {
document.getElementById('startButton').addEventListener('click', startGeolocation);
document.getElementById('stopButton').addEventListener('click', stopGeolocation);
document.getElementById('getLatestButton').addEventListener('click', getLatestPosition);
});

View File

@ -0,0 +1,24 @@
import { DumonGeolocation } from '@dumon/capacitor-geolocation';
async function startGeolocation() {
DumonGeolocation.addListener('onPositionUpdate', (data) => {
console.log('[onPositionUpdate]', data);
});
await DumonGeolocation.startPositioning();
}
async function stopGeolocation() {
await DumonGeolocation.stopPositioning();
}
async function getLatestPosition() {
const data = await DumonGeolocation.getLatestPosition();
console.log('[getLatestPosition]', data);
}
window.addEventListener('DOMContentLoaded', () => {
document.getElementById('startButton').addEventListener('click', startGeolocation);
document.getElementById('stopButton').addEventListener('click', stopGeolocation);
document.getElementById('getLatestButton').addEventListener('click', getLatestPosition);
});

View File

@ -0,0 +1,24 @@
import { DumonGeolocation } from 'dumon-geolocation';
async function startGeolocation() {
DumonGeolocation.addListener('onPositionUpdate', (data) => {
console.log('[onPositionUpdate]', data);
});
await DumonGeolocation.startPositioning();
}
async function stopGeolocation() {
await DumonGeolocation.stopPositioning();
}
async function getLatestPosition() {
const data = await DumonGeolocation.getLatestPosition();
console.log('[getLatestPosition]', data);
}
window.addEventListener('DOMContentLoaded', () => {
document.getElementById('startButton').addEventListener('click', startGeolocation);
document.getElementById('stopButton').addEventListener('click', stopGeolocation);
document.getElementById('getLatestButton').addEventListener('click', getLatestPosition);
});

View File

@ -0,0 +1,10 @@
import { WebPlugin } from '@capacitor/core';
import type { DumonGeolocationPlugin } from './definitions';
export class DumonGeolocationWeb extends WebPlugin implements DumonGeolocationPlugin {
async echo(options: { value: string }): Promise<{ value: string }> {
console.log('ECHO', options);
return options;
}
}

View File

@ -0,0 +1,33 @@
import { WebPlugin } from '@capacitor/core';
import type { PositioningData, DumonGeolocationPlugin } from './definitions';
export class DumonGeolocationWeb extends WebPlugin implements DumonGeolocationPlugin {
async startPositioning(): Promise<void> {
console.log('DumonGeolocationWeb: startPositioning() called (no-op)');
}
async stopPositioning(): Promise<void> {
console.log('DumonGeolocationWeb: stopPositioning() called (no-op)');
}
async getLatestPosition(): Promise<PositioningData> {
console.log('DumonGeolocationWeb: getLatestPosition() called (no-op)');
return {
latitude: 0,
longitude: 0,
accuracy: 999,
source: 'GNSS',
imuData: {
accelX: 0,
accelY: 0,
accelZ: 0,
gyroX: 0,
gyroY: 0,
gyroZ: 0,
},
};
}
}

View File

@ -0,0 +1,33 @@
import { WebPlugin } from '@capacitor/core';
import type { PositioningData } from './definitions';
export class DumonGeolocationWeb extends WebPlugin {
async startPositioning(): Promise<void> {
console.log('DumonGeolocationWeb: startPositioning() called (no-op)');
}
async stopPositioning(): Promise<void> {
console.log('DumonGeolocationWeb: stopPositioning() called (no-op)');
}
async getLatestPosition(): Promise<PositioningData> {
console.log('DumonGeolocationWeb: getLatestPosition() called (no-op)');
return {
latitude: 0,
longitude: 0,
accuracy: 999,
source: 'GNSS',
imuData: {
accelX: 0,
accelY: 0,
accelZ: 0,
gyroX: 0,
gyroY: 0,
gyroZ: 0,
},
};
}
}

View File

@ -0,0 +1,39 @@
import { WebPlugin } from '@capacitor/core';
import type { PositioningData } from './definitions';
export class DumonGeolocationWeb extends WebPlugin {
async startPositioning(): Promise<void> {
console.log('DumonGeolocationWeb: startPositioning() called (no-op)');
}
async stopPositioning(): Promise<void> {
console.log('DumonGeolocationWeb: stopPositioning() called (no-op)');
}
async getLatestPosition(): Promise<PositioningData> {
return {
source: 'GNSS',
timestamp: Date.now(),
gpsData: {
latitude: 0,
longitude: 0,
accuracy: 999,
satellitesInView: 0,
usedInFix: 0,
constellationCounts: {}
},
wifiData: {
apCount: 0,
aps: []
},
imuData: {
accelX: 0,
accelY: 0,
accelZ: 0,
gyroX: 0,
gyroY: 0,
gyroZ: 0
}
};
}
}

View File

@ -0,0 +1,41 @@
import { WebPlugin } from '@capacitor/core';
import type { DumonGeolocationPlugin, PositioningData } from './definitions';
export class DumonGeolocationWeb extends WebPlugin implements DumonGeolocationPlugin {
async startPositioning(): Promise<void> {
console.log('DumonGeolocationWeb: startPositioning() called (no-op)');
}
async stopPositioning(): Promise<void> {
console.log('DumonGeolocationWeb: stopPositioning() called (no-op)');
}
async getLatestPosition(): Promise<PositioningData> {
console.log('DumonGeolocationWeb: getLatestPosition() called (returning dummy data)');
return {
source: 'GNSS',
timestamp: Date.now(),
gpsData: {
latitude: 0,
longitude: 0,
accuracy: 999,
satellitesInView: 0,
usedInFix: 0,
constellationCounts: {},
},
wifiData: {
apCount: 0,
aps: [],
},
imuData: {
accelX: 0,
accelY: 0,
accelZ: 0,
gyroX: 0,
gyroY: 0,
gyroZ: 0,
}
};
}
}

View File

@ -0,0 +1,41 @@
import { WebPlugin } from '@capacitor/core';
import type { DumonGeolocationPlugin, PositioningData } from './definitions';
export class DumonGeolocationWeb extends WebPlugin {
async startPositioning(): Promise<void> {
console.log('DumonGeolocationWeb: startPositioning() called (no-op)');
}
async stopPositioning(): Promise<void> {
console.log('DumonGeolocationWeb: stopPositioning() called (no-op)');
}
async getLatestPosition(): Promise<PositioningData> {
console.log('DumonGeolocationWeb: getLatestPosition() called (returning dummy data)');
return {
source: 'GNSS',
timestamp: Date.now(),
gpsData: {
latitude: 0,
longitude: 0,
accuracy: 999,
satellitesInView: 0,
usedInFix: 0,
constellationCounts: {},
},
wifiData: {
apCount: 0,
aps: [],
},
imuData: {
accelX: 0,
accelY: 0,
accelZ: 0,
gyroX: 0,
gyroY: 0,
gyroZ: 0,
}
};
}
}

View File

@ -0,0 +1,41 @@
import { WebPlugin } from '@capacitor/core';
import type { PositioningData } from './definitions';
export class DumonGeolocationWeb extends WebPlugin {
async startPositioning(): Promise<void> {
console.log('DumonGeolocationWeb: startPositioning() called (no-op)');
}
async stopPositioning(): Promise<void> {
console.log('DumonGeolocationWeb: stopPositioning() called (no-op)');
}
async getLatestPosition(): Promise<PositioningData> {
console.log('DumonGeolocationWeb: getLatestPosition() called (returning dummy data)');
return {
source: 'GNSS',
timestamp: Date.now(),
gpsData: {
latitude: 0,
longitude: 0,
accuracy: 999,
satellitesInView: 0,
usedInFix: 0,
constellationCounts: {},
},
wifiData: {
apCount: 0,
aps: [],
},
imuData: {
accelX: 0,
accelY: 0,
accelZ: 0,
gyroX: 0,
gyroY: 0,
gyroZ: 0,
}
};
}
}

View File

@ -0,0 +1,40 @@
import { WebPlugin } from '@capacitor/core';
import type { PositioningData, DumonGeolocationPlugin } from './definitions';
export class DumonGeolocationWeb extends WebPlugin implements DumonGeolocationPlugin {
async startPositioning(): Promise<void> {
console.log('DumonGeolocationWeb: startPositioning() called (no-op)');
}
async stopPositioning(): Promise<void> {
console.log('DumonGeolocationWeb: stopPositioning() called (no-op)');
}
async getLatestPosition(): Promise<PositioningData> {
console.log('DumonGeolocationWeb: getLatestPosition() called (returning dummy data)');
return {
source: 'GNSS',
timestamp: Date.now(),
latitude: 0,
longitude: 0,
accuracy: 999,
gnssData: {
satellitesInView: 0,
usedInFix: 0,
constellationCounts: {}
},
wifiData: [],
imuData: {
accelX: 0,
accelY: 0,
accelZ: 0,
gyroX: 0,
gyroY: 0,
gyroZ: 0,
speed: 0,
acceleration: 0,
directionRad: 0
}
};
}
}

View File

@ -0,0 +1,40 @@
import { WebPlugin } from '@capacitor/core';
import type { PositioningData } from './definitions';
export class DumonGeolocationWeb extends WebPlugin {
async startPositioning(): Promise<void> {
console.log('DumonGeolocationWeb: startPositioning() called (no-op)');
}
async stopPositioning(): Promise<void> {
console.log('DumonGeolocationWeb: stopPositioning() called (no-op)');
}
async getLatestPosition(): Promise<PositioningData> {
console.log('DumonGeolocationWeb: getLatestPosition() called (returning dummy data)');
return {
source: 'GNSS',
timestamp: Date.now(),
latitude: 0,
longitude: 0,
accuracy: 999,
gnssData: {
satellitesInView: 0,
usedInFix: 0,
constellationCounts: {}
},
wifiData: [],
imuData: {
accelX: 0,
accelY: 0,
accelZ: 0,
gyroX: 0,
gyroY: 0,
gyroZ: 0,
speed: 0,
acceleration: 0,
directionRad: 0
}
};
}
}

138
README.md
View File

@ -1,37 +1,133 @@
# dumon-geolocation # dumon-geolocation
Implement manager GNSS, WiFi RTT, IMU, Kalman fusion, event emitter Plugin Capacitor Android untuk positioning real-time berbasis GNSS multi-konstelasi, Wi-Fi RTT/RSSI, dan IMU (Accelerometer + Gyroscope), dilengkapi dengan sensor fusion (Kalman Filter) dan deteksi lokasi palsu.
## Install ## 📦 Install
```bash ```bash
npm install dumon-geolocation npm install dumon-geolocation
npx cap sync npx cap sync
``` ```
## API ## 🚀 API
<docgen-index> ### 📡 startPositioning()
* [`echo(...)`](#echo) ```ts
startPositioning() => Promise<void>
</docgen-index>
<docgen-api>
<!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
### echo(...)
```typescript
echo(options: { value: string; }) => Promise<{ value: string; }>
``` ```
| Param | Type | Memulai pengambilan data posisi secara real-time dari GNSS, Wi-Fi, dan IMU.
| ------------- | ------------------------------- |
| **`options`** | <code>{ value: string; }</code> |
**Returns:** <code>Promise&lt;{ value: string; }&gt;</code> ---
-------------------- ### 🛑 stopPositioning()
</docgen-api> ```ts
stopPositioning() => Promise<void>
```
Menghentikan semua sensor dan positioning.
---
### 📍 getLatestPosition()
```ts
getLatestPosition() => Promise<PositioningData>
```
Mengembalikan data posisi terkini yang telah difusi.
---
### 🔄 addListener('onPositionUpdate', ...)
```ts
addListener(eventName: 'onPositionUpdate', listenerFunc: (data: PositioningData) => void): PluginListenerHandle
```
Listener untuk update posisi secara berkala (real-time).
---
## 🧾 Interfaces
### PositioningData
```ts
interface PositioningData {
source: 'GNSS' | 'WIFI' | 'FUSED' | 'MOCK';
latitude: number;
longitude: number;
accuracy: number;
speed: number;
acceleration: number;
directionRad: number;
timestamp: number;
isMocked: boolean;
// Optional raw sensor data (available internally)
// imuData?: ImuData;
// gnssData?: SatelliteStatus;
// wifiData?: WifiAp[];
}
```
### ImuData
```ts
interface ImuData {
accelX: number;
accelY: number;
accelZ: number;
gyroX: number;
gyroY: number;
gyroZ: number;
speed: number;
acceleration: number;
directionRad: number;
}
```
### SatelliteStatus
```ts
interface SatelliteStatus {
satellitesInView: number;
usedInFix: number;
constellationCounts: { [key: string]: number };
}
```
### WifiAp
```ts
interface WifiAp {
ssid: string;
bssid: string;
rssi: number;
distance?: number;
}
```
### PluginListenerHandle
```ts
interface PluginListenerHandle {
remove: () => Promise<void>;
}
```
---
## Catatan
- Plugin hanya mendukung platform Android saat ini.
- Ideal digunakan bersama dengan plugin `Geolocation` bawaan Capacitor untuk fallback atau perbandingan.
- Sensor fusion berbasis Kalman Filter (versi sederhana).
- `directionRad` merujuk arah dalam radian relatif terhadap utara (azimuth).
- Output `isMocked` berguna untuk deteksi lokasi palsu.
---
Lisensi: MIT Dibuat oleh Tim Dumon

View File

@ -6,22 +6,27 @@ ext {
} }
buildscript { buildscript {
ext {
kotlin_version = '1.9.24'
}
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:8.7.2' classpath 'com.android.tools.build:gradle:8.7.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
} }
} }
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
apply plugin: 'org.jetbrains.kotlin.android'
android { android {
namespace "com.dumon.plugin.geolocation" namespace "com.dumon.plugin.geolocation"
compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35 compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35
defaultConfig { defaultConfig {
minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23 minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35 targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35
versionCode 1 versionCode 1
versionName "1.0" versionName "1.0"
@ -37,8 +42,11 @@ android {
abortOnError false abortOnError false
} }
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_21 sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_21 targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = '17'
} }
} }
@ -52,6 +60,7 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':capacitor-android') implementation project(':capacitor-android')
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion" implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
implementation 'androidx.core:core-ktx:1.16.0'
testImplementation "junit:junit:$junitVersion" testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"

View File

@ -1,2 +1,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" />
</manifest> </manifest>

View File

@ -0,0 +1,177 @@
package com.dumon.plugin.geolocation
import android.Manifest
import com.getcapacitor.*
import com.getcapacitor.annotation.CapacitorPlugin
import com.getcapacitor.annotation.Permission
import com.dumon.plugin.geolocation.gps.GpsStatusManager
import com.dumon.plugin.geolocation.gps.SatelliteStatus
import com.dumon.plugin.geolocation.imu.ImuData
import com.dumon.plugin.geolocation.imu.ImuSensorManager
import com.dumon.plugin.geolocation.wifi.WifiPositioningManager
import com.dumon.plugin.geolocation.wifi.WifiScanResult
import com.dumon.plugin.geolocation.fusion.SensorFusionManager
import org.json.JSONArray
import org.json.JSONObject
@CapacitorPlugin(
name = "DumonGeolocation",
permissions = [
Permission(strings = [
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.CHANGE_WIFI_STATE,
Manifest.permission.NEARBY_WIFI_DEVICES
])
]
)
class DumonGeolocation : Plugin() {
private var gpsManager: GpsStatusManager? = null
private var imuManager: ImuSensorManager? = null
private var wifiManager: WifiPositioningManager? = null
private var fusionManager: SensorFusionManager? = null
private var latestLatitude = 0.0
private var latestLongitude = 0.0
private var latestAccuracy = 999.0
private var latestSource = "GNSS"
private var latestTimestamp: Long = 0L
private var latestImu: ImuData? = null
private var satelliteStatus: SatelliteStatus? = null
private var wifiScanResult: WifiScanResult? = null
private var isMockedLocation = false
private var lastEmitTimestamp: Long = 0L
private val emitIntervalMs: Long = 500L
override fun load() {
gpsManager = GpsStatusManager(
context,
onSatelliteStatusUpdate = { satelliteStatus = it },
onLocationUpdate = { location, isMocked ->
latestLatitude = location.latitude
latestLongitude = location.longitude
latestAccuracy = location.accuracy.toDouble()
latestSource = if (isMocked) "MOCK" else "GNSS"
isMockedLocation = isMocked
latestTimestamp = location.time
fusionManager?.updateGpsPosition(latestLatitude, latestLongitude)
emitPositionUpdate()
}
)
imuManager = ImuSensorManager(
context,
onImuUpdate = {
latestImu = it
emitPositionUpdate()
}
)
wifiManager = WifiPositioningManager(
context,
onWifiPositioningUpdate = {
wifiScanResult = it
emitPositionUpdate()
}
)
fusionManager = SensorFusionManager { lat, lon ->
latestLatitude = lat
latestLongitude = lon
latestAccuracy = 3.0
latestSource = "FUSED"
latestTimestamp = System.currentTimeMillis()
emitPositionUpdate()
}
}
@PluginMethod
fun startPositioning(call: PluginCall) {
gpsManager?.start()
imuManager?.start()
wifiManager?.startPeriodicScan(3000L)
call.resolve()
}
@PluginMethod
fun stopPositioning(call: PluginCall) {
gpsManager?.stop()
imuManager?.stop()
wifiManager?.stopPeriodicScan()
call.resolve()
}
@PluginMethod
fun getLatestPosition(call: PluginCall) {
call.resolve(buildPositionData())
}
private fun emitPositionUpdate() {
val now = System.currentTimeMillis()
if (now - lastEmitTimestamp < emitIntervalMs) return
lastEmitTimestamp = now
notifyListeners("onPositionUpdate", buildPositionData())
}
private fun buildPositionData(): JSObject {
val obj = JSObject()
obj.put("source", latestSource)
obj.put("timestamp", if (latestTimestamp > 0) latestTimestamp else System.currentTimeMillis())
obj.put("latitude", latestLatitude)
obj.put("longitude", latestLongitude)
obj.put("accuracy", latestAccuracy)
obj.put("isMocked", isMockedLocation)
latestImu?.let {
obj.put("speed", it.speed)
obj.put("acceleration", it.acceleration)
obj.put("directionRad", it.directionRad)
}
// === Full Detail (commented out for future use) ===
/*
satelliteStatus?.let {
val gnss = JSObject()
gnss.put("satellitesInView", it.satellitesInView)
gnss.put("usedInFix", it.usedInFix)
val constellations = JSObject()
it.constellationCounts.forEach { (k, v) -> constellations.put(k, v) }
gnss.put("constellationCounts", constellations)
obj.put("gnssData", gnss)
}
latestImu?.let {
val imu = JSObject()
imu.put("accelX", it.accelX)
imu.put("accelY", it.accelY)
imu.put("accelZ", it.accelZ)
imu.put("gyroX", it.gyroX)
imu.put("gyroY", it.gyroY)
imu.put("gyroZ", it.gyroZ)
imu.put("speed", it.speed)
imu.put("acceleration", it.acceleration)
imu.put("directionRad", it.directionRad)
obj.put("imuData", imu)
}
wifiScanResult?.let {
val wifi = JSArray()
it.aps.forEach { ap ->
val a = JSObject()
a.put("ssid", ap.ssid)
a.put("bssid", ap.bssid)
a.put("rssi", ap.rssi)
ap.distance?.let { d -> a.put("distance", d) }
wifi.put(a)
}
obj.put("wifiData", wifi)
}
*/
return obj
}
}

View File

@ -1,22 +0,0 @@
package com.dumon.plugin.geolocation;
import com.getcapacitor.JSObject;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.CapacitorPlugin;
@CapacitorPlugin(name = "DumonGeolocation")
public class DumonGeolocationPlugin extends Plugin {
private DumonGeolocation implementation = new DumonGeolocation();
@PluginMethod
public void echo(PluginCall call) {
String value = call.getString("value");
JSObject ret = new JSObject();
ret.put("value", implementation.echo(value));
call.resolve(ret);
}
}

View File

@ -0,0 +1,61 @@
package com.dumon.plugin.geolocation.fusion
import android.util.Log
class SensorFusionManager(
private val onFusedPositionUpdate: (Double, Double) -> Unit = { _, _ -> }
) {
// Kalman filter state
private var latEstimate = 0.0
private var lonEstimate = 0.0
private var latErrorEstimate = 1.0
private var lonErrorEstimate = 1.0
private val processNoiseBase = 0.01
private val measurementNoise = 3.0 // typical GPS error in meters
private var isFirstUpdate = true
private var lastUpdateTimestamp: Long = 0L
fun updateGpsPosition(lat: Double, lon: Double) {
val currentTimestamp = System.currentTimeMillis()
val dtSeconds = if (lastUpdateTimestamp > 0) {
(currentTimestamp - lastUpdateTimestamp) / 1000.0
} else {
1.0
}
lastUpdateTimestamp = currentTimestamp
val processNoise = processNoiseBase * dtSeconds
if (isFirstUpdate) {
latEstimate = lat
lonEstimate = lon
isFirstUpdate = false
} else {
// Kalman update for latitude
val latKalmanGain = latErrorEstimate / (latErrorEstimate + measurementNoise)
latEstimate += latKalmanGain * (lat - latEstimate)
latErrorEstimate = (1 - latKalmanGain) * latErrorEstimate + processNoise
// Kalman update for longitude
val lonKalmanGain = lonErrorEstimate / (lonErrorEstimate + measurementNoise)
lonEstimate += lonKalmanGain * (lon - lonEstimate)
lonErrorEstimate = (1 - lonKalmanGain) * lonErrorEstimate + processNoise
}
Log.d("SENSOR_FUSION", "Fused Lat: $latEstimate, Lon: $lonEstimate")
onFusedPositionUpdate(latEstimate, lonEstimate)
}
fun reset() {
latEstimate = 0.0
lonEstimate = 0.0
latErrorEstimate = 1.0
lonErrorEstimate = 1.0
isFirstUpdate = true
lastUpdateTimestamp = 0L
}
}

View File

@ -0,0 +1,129 @@
package com.dumon.plugin.geolocation.gps
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.location.GnssStatus
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import android.os.Build
import android.os.Bundle
import android.text.format.DateFormat
import android.util.Log
import androidx.core.app.ActivityCompat
class GpsStatusManager(
private val context: Context,
private val onSatelliteStatusUpdate: (SatelliteStatus) -> Unit = {},
private val onLocationUpdate: (Location, Boolean) -> Unit = { _, _ -> }
) {
private val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
private val gnssStatusCallback = object : GnssStatus.Callback() {
override fun onSatelliteStatusChanged(status: GnssStatus) {
val satelliteCount = status.satelliteCount
var usedInFixCount = 0
val constellationCounts = mutableMapOf<Int, Int>()
for (i in 0 until satelliteCount) {
if (status.usedInFix(i)) usedInFixCount++
val constellationType = status.getConstellationType(i)
constellationCounts[constellationType] = constellationCounts.getOrDefault(constellationType, 0) + 1
}
val statusObj = SatelliteStatus(
satellitesInView = satelliteCount,
usedInFix = usedInFixCount,
constellationCounts = constellationCounts.mapKeys { getConstellationName(it.key) }
)
onSatelliteStatusUpdate(statusObj)
}
}
private val locationListener = object : LocationListener {
override fun onLocationChanged(location: Location) {
val providerTag = when (location.provider) {
LocationManager.GPS_PROVIDER -> "[GPS]"
LocationManager.NETWORK_PROVIDER -> "[NET]"
else -> "[OTHER]"
}
val timestamp = DateFormat.format("HH:mm:ss", System.currentTimeMillis())
val isMocked = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
location.isMock // API 31+
} else {
location.isFromMockProvider // Fallback
}
val info = "$providerTag Lat: ${location.latitude}, Lon: ${location.longitude}, Acc: ${location.accuracy} m @ $timestamp | Mock=$isMocked"
Log.d("GPS_LOCATION", info)
onLocationUpdate(location, isMocked)
}
@Deprecated("Deprecated in Java")
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}
override fun onProviderEnabled(provider: String) {}
override fun onProviderDisabled(provider: String) {}
}
@SuppressLint("MissingPermission")
fun start() {
try {
if (ActivityCompat.checkSelfPermission(context, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(context, android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
Log.e("GPS_STATUS", "Missing location permissions")
return
}
locationManager.registerGnssStatusCallback(gnssStatusCallback, null)
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
1000L,
0f,
locationListener
)
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER,
3000L,
10f,
locationListener
)
Log.d("GPS_STATUS", "GPS + Network location tracking started")
} catch (e: SecurityException) {
Log.e("GPS_STATUS", "SecurityException", e)
}
}
fun stop() {
locationManager.unregisterGnssStatusCallback(gnssStatusCallback)
locationManager.removeUpdates(locationListener)
Log.d("GPS_STATUS", "GPS tracking stopped")
}
private fun getConstellationName(type: Int): String {
return when (type) {
GnssStatus.CONSTELLATION_GPS -> "GPS"
GnssStatus.CONSTELLATION_SBAS -> "SBAS"
GnssStatus.CONSTELLATION_GLONASS -> "GLONASS"
GnssStatus.CONSTELLATION_QZSS -> "QZSS"
GnssStatus.CONSTELLATION_BEIDOU -> "BEIDOU"
GnssStatus.CONSTELLATION_GALILEO -> "GALILEO"
GnssStatus.CONSTELLATION_IRNSS -> "IRNSS"
else -> "Unknown ($type)"
}
}
}
data class SatelliteStatus(
val satellitesInView: Int,
val usedInFix: Int,
val constellationCounts: Map<String, Int>
)

View File

@ -0,0 +1,144 @@
package com.dumon.plugin.geolocation.imu
import android.content.Context
import android.hardware.*
import android.util.Log
import kotlin.math.*
class ImuSensorManager(
private val context: Context,
private val onImuUpdate: (ImuData) -> Unit
) : SensorEventListener {
private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
private val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION)
private val gyroscope = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
private val rotationVector = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
private var lastAccel = FloatArray(3) { 0f }
private var lastGyro = FloatArray(3) { 0f }
private var velocity = FloatArray(3) { 0f }
private var lastAccelTimestamp: Long = 0L
private val accelLowPass = FloatArray(3) { 0f }
private val alpha = 0.85f
private val accelerationThreshold = 0.12f
private val idleTimeoutNs = 2_000_000_000L // 2 seconds
private var lastActiveTimestamp: Long = 0L
private var latestDirectionRad = 0f
private var latestSpeed = 0f
fun start() {
accelerometer?.let { sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_GAME) }
gyroscope?.let { sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_GAME) }
rotationVector?.let { sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_GAME) }
Log.d("IMU_SENSOR", "IMU sensor tracking started")
}
fun stop() {
sensorManager.unregisterListener(this)
Log.d("IMU_SENSOR", "IMU sensor tracking stopped")
}
override fun onSensorChanged(event: SensorEvent?) {
event?.let {
when (it.sensor.type) {
Sensor.TYPE_LINEAR_ACCELERATION -> handleAccelerometer(it)
Sensor.TYPE_GYROSCOPE -> handleGyroscope(it)
Sensor.TYPE_ROTATION_VECTOR -> handleRotationVector(it)
}
}
}
private fun handleAccelerometer(event: SensorEvent) {
val currentTime = event.timestamp
for (i in 0..2) {
accelLowPass[i] = alpha * accelLowPass[i] + (1 - alpha) * event.values[i]
}
val accMag = sqrt(accelLowPass.map { it * it }.sum())
if (accMag > accelerationThreshold) {
if (lastAccelTimestamp != 0L) {
val dt = (currentTime - lastAccelTimestamp) / 1_000_000_000f
for (i in 0..2) {
velocity[i] += accelLowPass[i] * dt
}
}
lastActiveTimestamp = currentTime
} else {
if (currentTime - lastActiveTimestamp > idleTimeoutNs) {
for (i in 0..2) velocity[i] = 0f
} else {
for (i in 0..2) velocity[i] *= 0.96f
}
}
lastAccelTimestamp = currentTime
lastAccel = accelLowPass.copyOf()
val speed = velocity.map { it * it }.sum().pow(0.5f).coerceIn(0f, 30f)
val acceleration = accMag.coerceIn(0f, 20f)
latestSpeed = speed
emitCombinedImuData(speed, acceleration, latestDirectionRad)
Log.d("IMU_SENSOR", "Accel x: %.3f y: %.3f z: %.3f | Speed: %.3f | AccelMag: %.3f | Dir: %.2f rad".format(
lastAccel[0], lastAccel[1], lastAccel[2], speed, acceleration, latestDirectionRad
))
}
private fun handleGyroscope(event: SensorEvent) {
lastGyro = event.values.copyOf()
}
private fun handleRotationVector(event: SensorEvent) {
val rotationMatrix = FloatArray(9)
SensorManager.getRotationMatrixFromVector(rotationMatrix, event.values)
val orientation = FloatArray(3)
SensorManager.getOrientation(rotationMatrix, orientation)
val azimuth = orientation[0] // radian
// Apply smoothing to direction
val smoothFactor = 0.85f
if (abs(azimuth - latestDirectionRad) > 0.05f) {
latestDirectionRad = smoothFactor * latestDirectionRad + (1 - smoothFactor) * azimuth
}
}
private fun emitCombinedImuData(speed: Float, acceleration: Float, directionRad: Float) {
onImuUpdate(
ImuData(
accelX = lastAccel[0],
accelY = lastAccel[1],
accelZ = lastAccel[2],
gyroX = lastGyro[0],
gyroY = lastGyro[1],
gyroZ = lastGyro[2],
speed = speed,
acceleration = acceleration,
directionRad = directionRad
)
)
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
// Not used
}
}
data class ImuData(
val accelX: Float,
val accelY: Float,
val accelZ: Float,
val gyroX: Float,
val gyroY: Float,
val gyroZ: Float,
val speed: Float,
val acceleration: Float,
val directionRad: Float
)

View File

@ -0,0 +1,163 @@
package com.dumon.plugin.geolocation.wifi
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.net.wifi.ScanResult
import android.net.wifi.WifiManager
import android.net.wifi.rtt.RangingRequest
import android.net.wifi.rtt.RangingResultCallback
import android.net.wifi.rtt.WifiRttManager
import android.os.*
import android.util.Log
import androidx.core.app.ActivityCompat
import java.util.concurrent.Executors
class WifiPositioningManager(
private val context: Context,
private val onWifiPositioningUpdate: (WifiScanResult) -> Unit
) {
private val wifiManager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
private val wifiRttManager: WifiRttManager? =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
context.applicationContext.getSystemService(Context.WIFI_RTT_RANGING_SERVICE) as? WifiRttManager
} else {
null
}
private val handler = Handler(Looper.getMainLooper())
private var isScanning = false
private var lastWifiScanAps: MutableList<WifiAp> = mutableListOf()
fun isRttSupported(): Boolean {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && wifiRttManager != null && wifiManager.isWifiEnabled
}
fun startPeriodicScan(intervalMs: Long = 3000L) {
isScanning = true
handler.post(object : Runnable {
override fun run() {
if (isScanning) {
startWifiScan()
handler.postDelayed(this, intervalMs)
}
}
})
}
fun stopPeriodicScan() {
isScanning = false
handler.removeCallbacksAndMessages(null)
}
fun startWifiScan() {
if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
) {
val success = wifiManager.startScan()
if (success) {
val results = wifiManager.scanResults
processScanResults(results)
if (isRttSupported()) {
startRttRanging(results)
}
} else {
Log.e("WIFI_POSITION", "Wi-Fi scan failed")
onWifiPositioningUpdate(WifiScanResult(0, emptyList()))
}
} else {
Log.e("WIFI_POSITION", "Missing ACCESS_FINE_LOCATION permission")
onWifiPositioningUpdate(WifiScanResult(0, emptyList()))
}
}
private fun processScanResults(results: List<ScanResult>) {
lastWifiScanAps = results.map { result ->
WifiAp(
ssid = result.SSID,
bssid = result.BSSID,
rssi = result.level
)
}.toMutableList()
Log.d("WIFI_POSITION", "Wi-Fi scan → AP count: ${lastWifiScanAps.size}")
lastWifiScanAps.forEach {
Log.d("WIFI_POSITION", "SSID: ${it.ssid}, BSSID: ${it.bssid}, RSSI: ${it.rssi} dBm")
}
onWifiPositioningUpdate(WifiScanResult(lastWifiScanAps.size, lastWifiScanAps))
}
private fun startRttRanging(results: List<ScanResult>) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P || wifiRttManager == null) {
Log.d("WIFI_POSITION", "RTT not supported or WifiRttManager is null.")
return
}
val rttCapableAps = results.filter { it.is80211mcResponder }
if (rttCapableAps.isEmpty()) {
Log.d("WIFI_POSITION", "No RTT-capable AP found.")
return
}
val rangingRequest = RangingRequest.Builder()
.addAccessPoints(rttCapableAps)
.build()
try {
wifiRttManager.startRanging(
rangingRequest,
Executors.newSingleThreadExecutor(),
object : RangingResultCallback() {
override fun onRangingFailure(code: Int) {
Log.e("WIFI_POSITION", "RTT Ranging failed: $code")
}
override fun onRangingResults(results: List<android.net.wifi.rtt.RangingResult>) {
results.forEach { result ->
val mac = result.macAddress.toString()
val distance = if (result.status == android.net.wifi.rtt.RangingResult.STATUS_SUCCESS) {
result.distanceMm / 1000.0
} else {
Log.w("WIFI_POSITION", "RTT distance unavailable for ${result.macAddress}")
null
}
lastWifiScanAps.indexOfFirst { it.bssid == mac }.takeIf { it >= 0 }?.let { idx ->
lastWifiScanAps[idx] = lastWifiScanAps[idx].copy(distance = distance)
}
Log.d(
"WIFI_POSITION",
if (distance != null)
"RTT → ${mac}, Distance: ${distance} m"
else
"RTT → ${mac}, Status: ${result.status}"
)
}
onWifiPositioningUpdate(WifiScanResult(lastWifiScanAps.size, lastWifiScanAps))
}
}
)
} catch (e: SecurityException) {
Log.e("WIFI_POSITION", "SecurityException: Missing NEARBY_WIFI_DEVICES permission", e)
}
}
}
data class WifiAp(
val ssid: String,
val bssid: String,
val rssi: Int,
val distance: Double? = null
)
data class WifiScanResult(
val apCount: Int,
val aps: List<WifiAp>
)

View File

@ -0,0 +1,6 @@
<resources>
<string name="plugin_name">DumonGeolocation</string>
<string name="error_missing_permission">Akses lokasi belum diberikan</string>
<string name="status_tracking_started">Pelacakan dimulai</string>
<string name="status_tracking_stopped">Pelacakan dihentikan</string>
</resources>

Some files were not shown because too many files have changed in this diff Show More