14 June 2025 - Dumon Geolocation Plugin for Capacitor - Android
This commit is contained in:
parent
89b0bb514c
commit
6d7e1f25ce
129
.history/README_20250614152201.md
Normal file
129
.history/README_20250614152201.md
Normal file
@ -0,0 +1,129 @@
|
||||
# dumon-geolocation
|
||||
|
||||
Implement manager GNSS, Wi‑Fi 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<<a href="#positioningdata">PositioningData</a>></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>) => 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>() => Promise<void></code> |
|
||||
|
||||
</docgen-api>
|
||||
133
.history/README_20250614152812.md
Normal file
133
.history/README_20250614152812.md
Normal 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
|
||||
67
.history/android/build_20250614102811.gradle
Normal file
67
.history/android/build_20250614102811.gradle
Normal 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"
|
||||
}
|
||||
67
.history/android/build_20250614102909.gradle
Normal file
67
.history/android/build_20250614102909.gradle
Normal 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"
|
||||
}
|
||||
67
.history/android/build_20250614115958.gradle
Normal file
67
.history/android/build_20250614115958.gradle
Normal 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"
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
</manifest>
|
||||
10
.history/android/src/main/AndroidManifest_20250614102035.xml
Normal file
10
.history/android/src/main/AndroidManifest_20250614102035.xml
Normal 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>
|
||||
@ -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;
|
||||
// }
|
||||
// }
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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()))
|
||||
}
|
||||
}
|
||||
@ -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()))
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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)"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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)"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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)"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
)
|
||||
@ -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>
|
||||
)
|
||||
@ -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>
|
||||
)
|
||||
@ -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>
|
||||
)
|
||||
@ -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>
|
||||
)
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
)
|
||||
@ -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
|
||||
)
|
||||
@ -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
|
||||
)
|
||||
@ -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
|
||||
)
|
||||
@ -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
|
||||
)
|
||||
@ -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
|
||||
)
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
)
|
||||
@ -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>
|
||||
)
|
||||
@ -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>
|
||||
)
|
||||
@ -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>
|
||||
)
|
||||
@ -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>
|
||||
)
|
||||
@ -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>
|
||||
)
|
||||
@ -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>
|
||||
@ -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>
|
||||
26
.history/example-app/src/index_20250115225119.html
Normal file
26
.history/example-app/src/index_20250115225119.html
Normal 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>
|
||||
24
.history/example-app/src/index_20250614103708.html
Normal file
24
.history/example-app/src/index_20250614103708.html
Normal 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>
|
||||
58
.history/example-app/src/index_20250614121429.html
Normal file
58
.history/example-app/src/index_20250614121429.html
Normal 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>
|
||||
6
.history/example-app/src/js/example_20250614100113.js
Normal file
6
.history/example-app/src/js/example_20250614100113.js
Normal file
@ -0,0 +1,6 @@
|
||||
import { DumonGeolocation } from 'dumon-geolocation';
|
||||
|
||||
window.testEcho = () => {
|
||||
const inputValue = document.getElementById("echoInput").value;
|
||||
DumonGeolocation.echo({ value: inputValue })
|
||||
}
|
||||
24
.history/example-app/src/js/example_20250614103747.js
Normal file
24
.history/example-app/src/js/example_20250614103747.js
Normal 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);
|
||||
});
|
||||
24
.history/example-app/src/js/example_20250614105523.js
Normal file
24
.history/example-app/src/js/example_20250614105523.js
Normal 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);
|
||||
});
|
||||
46
.history/example-app/src/js/example_20250614121455.js
Normal file
46
.history/example-app/src/js/example_20250614121455.js
Normal 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);
|
||||
});
|
||||
46
.history/example-app/src/js/example_20250614123723.js
Normal file
46
.history/example-app/src/js/example_20250614123723.js
Normal 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);
|
||||
});
|
||||
49
.history/example-app/src/js/example_20250614123846.js
Normal file
49
.history/example-app/src/js/example_20250614123846.js
Normal 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);
|
||||
});
|
||||
46
.history/example-app/src/js/example_20250614152308.js
Normal file
46
.history/example-app/src/js/example_20250614152308.js
Normal 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);
|
||||
});
|
||||
46
.history/example-app/src/js/example_20250614152312.js
Normal file
46
.history/example-app/src/js/example_20250614152312.js
Normal 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);
|
||||
});
|
||||
80
.history/package_20250614100016.json
Normal file
80
.history/package_20250614100016.json
Normal file
@ -0,0 +1,80 @@
|
||||
{
|
||||
"name": "dumon-geolocation",
|
||||
"version": "0.0.1",
|
||||
"description": "Implement manager GNSS, Wi‑Fi 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
80
.history/package_20250614102416.json
Normal file
80
.history/package_20250614102416.json
Normal file
@ -0,0 +1,80 @@
|
||||
{
|
||||
"name": "dumon-geolocation",
|
||||
"version": "0.0.1",
|
||||
"description": "Implement manager GNSS, Wi‑Fi 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
80
.history/package_20250614102435.json
Normal file
80
.history/package_20250614102435.json
Normal file
@ -0,0 +1,80 @@
|
||||
{
|
||||
"name": "dumon-geolocation",
|
||||
"version": "0.0.1",
|
||||
"description": "Implement manager GNSS, Wi‑Fi 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
3
.history/src/definitions_20250614100016.ts
Normal file
3
.history/src/definitions_20250614100016.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export interface DumonGeolocationPlugin {
|
||||
echo(options: { value: string }): Promise<{ value: string }>;
|
||||
}
|
||||
24
.history/src/definitions_20250614103117.ts
Normal file
24
.history/src/definitions_20250614103117.ts
Normal 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;
|
||||
}
|
||||
24
.history/src/definitions_20250614103144.ts
Normal file
24
.history/src/definitions_20250614103144.ts
Normal 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;
|
||||
}
|
||||
53
.history/src/definitions_20250614113628.ts
Normal file
53
.history/src/definitions_20250614113628.ts
Normal 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;
|
||||
}
|
||||
56
.history/src/definitions_20250614121305.ts
Normal file
56
.history/src/definitions_20250614121305.ts
Normal 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;
|
||||
}
|
||||
63
.history/src/definitions_20250614135647.ts
Normal file
63
.history/src/definitions_20250614135647.ts
Normal 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;
|
||||
}
|
||||
10
.history/src/index_20250614100016.ts
Normal file
10
.history/src/index_20250614100016.ts
Normal 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 };
|
||||
10
.history/src/index_20250614103213.ts
Normal file
10
.history/src/index_20250614103213.ts
Normal 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 };
|
||||
10
.history/src/index_20250614121335.ts
Normal file
10
.history/src/index_20250614121335.ts
Normal 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 };
|
||||
24
.history/src/js/example_20250614103439.js
Normal file
24
.history/src/js/example_20250614103439.js
Normal 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);
|
||||
});
|
||||
24
.history/src/js/example_20250614103448.js
Normal file
24
.history/src/js/example_20250614103448.js
Normal 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);
|
||||
});
|
||||
24
.history/src/js/example_20250614105124.js
Normal file
24
.history/src/js/example_20250614105124.js
Normal 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);
|
||||
});
|
||||
10
.history/src/web_20250614100016.ts
Normal file
10
.history/src/web_20250614100016.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
33
.history/src/web_20250614103423.ts
Normal file
33
.history/src/web_20250614103423.ts
Normal 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,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
33
.history/src/web_20250614103640.ts
Normal file
33
.history/src/web_20250614103640.ts
Normal 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,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
39
.history/src/web_20250614114331.ts
Normal file
39
.history/src/web_20250614114331.ts
Normal 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
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
41
.history/src/web_20250614121358.ts
Normal file
41
.history/src/web_20250614121358.ts
Normal 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,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
41
.history/src/web_20250614121612.ts
Normal file
41
.history/src/web_20250614121612.ts
Normal 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,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
41
.history/src/web_20250614121624.ts
Normal file
41
.history/src/web_20250614121624.ts
Normal 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,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
40
.history/src/web_20250614135757.ts
Normal file
40
.history/src/web_20250614135757.ts
Normal 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
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
40
.history/src/web_20250614135839.ts
Normal file
40
.history/src/web_20250614135839.ts
Normal 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
138
README.md
@ -1,37 +1,133 @@
|
||||
# dumon-geolocation
|
||||
|
||||
Implement manager GNSS, Wi‑Fi 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
|
||||
npm install dumon-geolocation
|
||||
npx cap sync
|
||||
```
|
||||
|
||||
## API
|
||||
## 🚀 API
|
||||
|
||||
<docgen-index>
|
||||
### 📡 startPositioning()
|
||||
|
||||
* [`echo(...)`](#echo)
|
||||
|
||||
</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; }>
|
||||
```ts
|
||||
startPositioning() => Promise<void>
|
||||
```
|
||||
|
||||
| Param | Type |
|
||||
| ------------- | ------------------------------- |
|
||||
| **`options`** | <code>{ value: string; }</code> |
|
||||
Memulai pengambilan data posisi secara real-time dari GNSS, Wi-Fi, dan IMU.
|
||||
|
||||
**Returns:** <code>Promise<{ value: string; }></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
|
||||
@ -6,22 +6,27 @@ ext {
|
||||
}
|
||||
|
||||
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
|
||||
minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
|
||||
targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
@ -37,8 +42,11 @@ android {
|
||||
abortOnError false
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_21
|
||||
targetCompatibility JavaVersion.VERSION_21
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '17'
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,6 +60,7 @@ 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"
|
||||
|
||||
@ -1,2 +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>
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
)
|
||||
@ -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
|
||||
)
|
||||
@ -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>
|
||||
)
|
||||
6
android/src/main/res/values/strings.xml
Normal file
6
android/src/main/res/values/strings.xml
Normal 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
Loading…
x
Reference in New Issue
Block a user