Merge branch 'mapbox' into mapbox-sdkv3

This commit is contained in:
Piotr Dec 2024-07-30 18:21:02 +02:00
commit c67f9f9726
Signed by: stawros
GPG key ID: F89F27AD8F881A91
14 changed files with 962 additions and 202 deletions

View file

@ -6,6 +6,11 @@ import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothSocket
import android.util.Log
import com.mapbox.navigation.tripdata.maneuver.model.Maneuver
import com.mapbox.navigation.core.trip.session.LocationMatcherResult
import com.mapbox.navigation.core.trip.session.NavigationSessionState
import eu.ztsh.garmin.data.DataCache
import eu.ztsh.garmin.data.GarminMapper
import eu.ztsh.garmin.data.MapboxMapper
import java.io.IOException
import java.util.*
import java.util.concurrent.SynchronousQueue
@ -19,7 +24,7 @@ class Garmin(
private lateinit var connection: ConnectThread
private lateinit var processing: ProcessingThread
private var stateCache: State = State()
private val cache = DataCache()
fun start() {
connection = ConnectThread()
@ -31,28 +36,42 @@ class Garmin(
}
fun process(maneuver: Maneuver) {
processing = ProcessingThread(maneuver)
processing = ManeuverProcessingThread(maneuver)
processing.start()
processing.join()
}
fun process(location: LocationMatcherResult) {
processing = LocationProcessingThread(location)
processing.start()
processing.join()
}
fun process(navigationSessionState: NavigationSessionState) {
cache.update(navigationSessionState)
}
private inner class ManeuverProcessingThread(val maneuver: Maneuver) : ProcessingThread() {
private inner class ProcessingThread(val maneuver: Maneuver) : Thread() {
override fun run() {
send(ManeuverMapper.apply(maneuver))
if (cache.hasChanged(maneuver)) {
cache.update(maneuver)
send(MapboxMapper.apply(maneuver))
}
}
fun send(incoming: eu.ztsh.garmin.State) {
if (stateCache.distance != incoming.distance) {
setDistance(incoming)
}
private inner class LocationProcessingThread(val location: LocationMatcherResult) : ProcessingThread() {
override fun run() {
if (cache.hasChanged(location)) {
cache.update(location)
send(MapboxMapper.apply(location))
}
if (stateCache.direction != incoming.direction) {
setDirection(stateCache.direction)
}
stateCache = incoming
}
}
private fun setDistance(state: eu.ztsh.garmin.State) {
connection.enqueue(intArrayOf(
0x03,
@ -65,20 +84,16 @@ class Garmin(
))
}
private fun setDirection(direction: Direction) {
val param1 = when (direction.outAngle) {
OutAngle.LeftDown -> 0x10
OutAngle.RightDown -> 0x20
else -> direction.outType.data
private open inner class ProcessingThread : Thread() {
fun send(incoming: eu.ztsh.garmin.data.State) {
if (cache.hasChanged(incoming.distance)) {
connection.enqueue(GarminMapper.setDistance(incoming))
}
val param2: Int = if (direction.outType == OutType.RightRoundabout
|| direction.outType == OutType.LeftRoundabout) {
if (direction.roundabout == OutAngle.AsDirection) direction.outAngle.data else direction.roundabout.data
} else {
0x00
if (cache.hasChanged(incoming.direction)) {
connection.enqueue(GarminMapper.setDirection(incoming))
}
val param3: Int = if (direction.outAngle == OutAngle.LeftDown || direction.outAngle == OutAngle.RightDown) 0x00 else direction.outAngle.data
connection.enqueue(intArrayOf(0x01, param1, param2, param3))
cache.update(incoming)
}
private fun asDigit(input: Double): Int {
@ -111,10 +126,13 @@ class Garmin(
sleep(3000)
readAll()
while (true) {
val newCurrent = Optional.ofNullable(queue.poll()).orElse(current)
current = newCurrent
val newCurrent = queue.poll()
if (newCurrent == null) {
sleep(500)
} else {
current = newCurrent
}
send(current)
sleep(900)
}
} catch (e: IOException) {
Log.d(TAG, "Not connected", e)

View file

@ -51,6 +51,7 @@ class MainActivity : AppCompatActivity() {
mapControl.initNavigation()
}
)
private val mapboxToolbox = MapboxToolbox(lifecycle, this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -66,14 +67,26 @@ class MainActivity : AppCompatActivity() {
.build()
)
}
mapboxToolbox.onCreate()
bluetoothInit()
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
override fun onStart() {
super.onStart()
mapboxToolbox.onStart()
}
override fun onStop() {
super.onStop()
mapboxToolbox.onStop()
}
override fun onDestroy() {
super.onDestroy()
mapControl.onDestroy()
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
mapboxToolbox.onDestroy()
}
private fun bluetoothInit() {

View file

@ -0,0 +1,96 @@
package eu.ztsh.garmin
import android.annotation.SuppressLint
import android.location.Location
import android.util.Log
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import com.mapbox.navigation.base.formatter.DistanceFormatterOptions
import com.mapbox.navigation.base.options.NavigationOptions
import com.mapbox.navigation.core.formatter.MapboxDistanceFormatter
import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp
import com.mapbox.navigation.core.trip.session.LocationMatcherResult
import com.mapbox.navigation.core.trip.session.LocationObserver
import com.mapbox.navigation.core.trip.session.NavigationSessionState
import com.mapbox.navigation.core.trip.session.NavigationSessionStateObserver
import com.mapbox.navigation.core.trip.session.RouteProgressObserver
import com.mapbox.navigation.ui.maneuver.api.MapboxManeuverApi
@SuppressLint("MissingPermission")
class MapboxToolbox(lifecycle: Lifecycle, private val context: MainActivity) {
private val mapboxObserver = MapboxObserver()
init {
lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) {
MapboxNavigationApp.attach(owner)
MapboxNavigationApp.registerObserver(mapboxObserver)
}
override fun onPause(owner: LifecycleOwner) {
MapboxNavigationApp.detach(owner)
MapboxNavigationApp.unregisterObserver(mapboxObserver)
}
})
}
fun onCreate() {
if (!MapboxNavigationApp.isSetup()) {
MapboxNavigationApp.setup {
NavigationOptions.Builder(context)
.accessToken(BuildConfig.MAPBOX_DOWNLOADS_TOKEN)
.build()
}
}
MapboxNavigationApp.current()?.startTripSession()
}
fun onStart() {
MapboxNavigationApp.current()?.registerRouteProgressObserver(routeProgressObserver)
MapboxNavigationApp.current()?.registerLocationObserver(locationObserver)
MapboxNavigationApp.current()?.registerNavigationSessionStateObserver(navigationStateObserver)
}
fun onStop() {
MapboxNavigationApp.current()?.unregisterRouteProgressObserver(routeProgressObserver)
MapboxNavigationApp.current()?.unregisterLocationObserver(locationObserver)
MapboxNavigationApp.current()?.unregisterNavigationSessionStateObserver(navigationStateObserver)
}
fun onDestroy() {
MapboxNavigationApp.current()?.stopTripSession()
maneuverApi.cancel()
}
// Define distance formatter options
private val distanceFormatter: DistanceFormatterOptions by lazy {
DistanceFormatterOptions.Builder(context).build()
}
// Create an instance of the Maneuver API
private val maneuverApi: MapboxManeuverApi by lazy {
MapboxManeuverApi(MapboxDistanceFormatter(distanceFormatter))
}
private val routeProgressObserver =
RouteProgressObserver { routeProgress ->
maneuverApi.getManeuvers(routeProgress).value?.apply {
context.garmin.process(
this[0]
)
}
}
private val locationObserver = object : LocationObserver {
override fun onNewLocationMatcherResult(locationMatcherResult: LocationMatcherResult) {
context.garmin.process(locationMatcherResult)
}
override fun onNewRawLocation(rawLocation: Location) {
}
}
private val navigationStateObserver = NavigationSessionStateObserver { context.garmin.process(it) }
}

View file

@ -1,96 +0,0 @@
package eu.ztsh.garmin
enum class OutType(val data: Int) {
Off(0x00),
Lane(0x01),
LongerLane(0x02),
LeftRoundabout(0x04),
RightRoundabout(0x08),
Flag(0x40),
ArrowOnly(0x80);
}
enum class OutAngle(val data: Int) {
Down(0x01),
SharpRight(0x02),
Right(0x04),
EasyRight(0x08),
Straight(0x10),
EasyLeft(0x20),
Left(0x40),
SharpLeft(0x80),
LeftDown(0x81),
RightDown(0x82),
AsDirection(0x00)
}
enum class Unit(val data: Int) {
Any(0),
Metres(1),
Kilometres(3),
Miles(5),
Foot(8)
}
enum class Lane(val data: Int) {
DotsRight(0x01),
OuterRight(0x02),
MiddleRight(0x04),
InnerRight(0x08),
InnerLeft(0x10),
MiddleLeft(0x20),
OuterLeft(0x40),
DotsLeft(0x80)
}
class State {
var lineArrows: Int = 0
var lineOutlines: Int = 0
var direction = Direction()
var distance: Double = 0.0
var unit: Unit = Unit.Any
var speed: Int = 0
var limit: Int = 0
var hours: Int = 0
var minutes: Int = 0
var traffic: Boolean = false
var flag: Boolean = false
var control: Boolean = false
}
class Direction {
var outAngle: OutAngle = OutAngle.AsDirection
var outType: OutType = OutType.Lane
var roundabout: OutAngle = OutAngle.AsDirection
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Direction
if (outAngle != other.outAngle) return false
if (outType != other.outType) return false
if (roundabout != other.roundabout) return false
return true
}
override fun hashCode(): Int {
var result = outAngle.hashCode()
result = 31 * result + outType.hashCode()
result = 31 * result + roundabout.hashCode()
return result
}
}

View file

@ -0,0 +1,88 @@
package eu.ztsh.garmin.data
import com.mapbox.navigation.core.trip.session.LocationMatcherResult
import com.mapbox.navigation.core.trip.session.NavigationSessionState
import com.mapbox.navigation.ui.maneuver.model.Maneuver
class DataCache {
private val stateCache: State = State()
private var maneuverCache: Maneuver? = null
private var locationCache: LocationMatcherResult? = null
private var session: NavigationSessionState? = null
// state
fun hasChanged(lanes: Lanes?): Boolean {
return stateCache.lineArrows == null || stateCache.lineArrows != lanes
}
fun hasChanged(outlines: Outlines?): Boolean {
return stateCache.lineOutlines == null || stateCache.lineOutlines != outlines
}
fun hasChanged(distance: Distance?): Boolean {
return stateCache.distance == null || stateCache.distance != distance
}
fun hasChanged(direction: Direction?): Boolean {
return stateCache.direction == null || stateCache.direction != direction
}
fun hasChanged(speed: Speed?): Boolean {
return stateCache.speed == null || stateCache.speed != speed
}
fun hasChanged(arrival: Arrival?): Boolean {
return stateCache.arrival == null || stateCache.arrival != arrival
}
// Merge states
fun update(state: State) {
if (state.lineArrows != null) {
stateCache.lineArrows = state.lineArrows
}
if (state.lineOutlines != null) {
state.lineOutlines = state.lineOutlines
}
if (state.direction != null) {
stateCache.direction = state.direction
}
if (state.distance != null) {
stateCache.distance = state.distance
}
if (state.speed != null) {
stateCache.speed = state.speed
}
if (state.arrival != null) {
stateCache.arrival = state.arrival
}
}
// maneuver
fun hasChanged(maneuver: Maneuver): Boolean {
return maneuverCache == null || maneuverCache!! != maneuver
}
fun update(maneuver: Maneuver) {
maneuverCache = maneuver
}
// location
fun hasChanged(locationMatcherResult: LocationMatcherResult): Boolean {
return locationCache == null || locationCache!! != locationMatcherResult
}
fun update(locationMatcherResult: LocationMatcherResult) {
locationCache = locationMatcherResult
}
// session
fun isActive(): Boolean {
return session != null && session is NavigationSessionState.ActiveGuidance
}
fun update(sessionState: NavigationSessionState) {
session = sessionState
}
}

View file

@ -0,0 +1,151 @@
package eu.ztsh.garmin.data
class GarminMapper {
companion object {
fun setLines(state: State): IntArray {
return intArrayOf(0x02, state.lineOutlines.sumOf { it.value }, state.lineArrows.sumOf { it.value })
}
fun setDirection(state: State): IntArray {
return setDirection(state.direction.angle, state.direction.out, state.direction.roundabout)
}
fun setDistance(state: State): IntArray {
return setDistance(state.distance, state.unit)
}
fun setSpeed(state: State): IntArray {
return setSpeed(state.speed, state.limit, state.speed > state.limit)
}
fun setSpeedFreeRide(state: State): Pair<IntArray, IntArray> {
return Pair(setDistance(state.speed), setSpeed(state.limit, limitWarning = state.speed > state.limit))
}
fun setTime(hours: Int, minutes: Int, traffic: Boolean = false, flag: Boolean = false): IntArray {
val trafficChar = asChar(traffic)
val flagChar = asChar(flag)
return if (hours > 99) {
intArrayOf(
0x05,
trafficChar,
asDigit(hours / 1000),
asDigit(hours / 100),
0x00,
asDigit(hours / 10),
asDigit(hours),
0xff,
flagChar
)
} else {
intArrayOf(
0x05,
trafficChar,
asDigit(hours / 10),
asDigit(hours),
0xff,
asDigit(minutes / 10),
asDigit(minutes),
0x00,
flagChar
)
}
}
fun setSpeedControl(state: State): IntArray {
return intArrayOf(0x04, if (state.control) 0x01 else 0x02)
}
fun setCompass(state: State): IntArray {
// TODO: Implement
return setDirection(OutAngle.Straight, OutType.ArrowOnly)
}
fun cleanDistance(): IntArray {
return intArrayOf(0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
}
private fun setDirection(
angle: OutAngle,
out: OutType = OutType.Lane,
roundabout: OutAngle = OutAngle.AsDirection
): IntArray {
val param1: Int = when (angle) {
OutAngle.LeftDown -> 0x10
OutAngle.RightDown -> 0x20
else -> out.value
}
val param2: Int = if (out == OutType.RightRoundabout
|| out == OutType.LeftRoundabout
) {
if (roundabout == OutAngle.AsDirection) angle.value else roundabout.value
} else {
0x00
}
val param3: Int =
if (angle == OutAngle.LeftDown || angle == OutAngle.RightDown) 0x00 else angle.value
return intArrayOf(0x01, param1, param2, param3)
}
private fun setDistance(distance: Int, unit: Unit = Unit.Any): IntArray {
return intArrayOf(
0x03, asDigit(distance / 1000), asDigit(distance / 100), asDigit(distance / 10),
0x00, asDigit(distance), unit.value
)
}
private fun setSpeed(
speed: Int,
limit: Int = 0,
limitWarning: Boolean = false,
acc: Boolean = false
): IntArray {
// TODO: car connection
val accChar = asChar(acc)
val limitWarningChar = asChar(limitWarning)
return if (limit > 0) {
intArrayOf(
0x06,
asDigit(speed / 100),
asDigit(speed / 10),
asDigit(speed),
0xff,
asDigit(limit / 100),
asDigit(limit / 10),
asDigit(limit),
limitWarningChar,
accChar
)
} else {
intArrayOf(
0x06,
0x00,
0x00,
0x00,
0x00,
asDigit(speed / 100),
asDigit(speed / 10),
asDigit(speed),
limitWarningChar,
accChar
)
}
}
private fun asDigit(n: Int): Int {
if (n == 0) {
return 0
}
val m = n % 10
return if (m == 0) 10 else m
}
private fun asChar(boolean: Boolean): Int {
return if (boolean) 0xff else 0x00
}
}
}

View file

@ -1,8 +1,9 @@
package eu.ztsh.garmin
package eu.ztsh.garmin.data
import com.mapbox.navigation.tripdata.maneuver.model.Maneuver
import com.mapbox.navigation.core.trip.session.LocationMatcherResult
class ManeuverMapper {
class MapboxMapper {
companion object {
@ -12,13 +13,8 @@ class ManeuverMapper {
this.primary.apply {
state.direction = Direction()
when (this.type) {
"turn" -> {
state.direction.outType = OutType.Lane
state.direction.roundabout = OutAngle.AsDirection
}
"roundabout" -> {
state.direction.outType = OutType.RightRoundabout
state.direction!!.out = OutType.RightRoundabout
}
"arrive" -> {
@ -28,11 +24,11 @@ class ManeuverMapper {
when (this.modifier) {
"right" -> {
when (this.type) {
"turn" -> state.direction.outAngle = OutAngle.Right
"turn" -> state.direction!!.angle = OutAngle.Right
"roundabout" -> {
when (this.degrees) {
137.0 -> state.direction.outAngle = OutAngle.EasyRight
180.0 -> state.direction.outAngle = OutAngle.Straight
137.0 -> state.direction!!.angle = OutAngle.EasyRight
180.0 -> state.direction!!.angle = OutAngle.Straight
}
}
}
@ -40,7 +36,7 @@ class ManeuverMapper {
"left" -> {
when (this.type) {
"turn" -> state.direction.outAngle = OutAngle.Left
"turn" -> state.direction!!.angle = OutAngle.Left
}
}
}
@ -48,14 +44,14 @@ class ManeuverMapper {
this.stepDistance.apply {
this.distanceRemaining?.apply {
distanceFormatter.formatDistance(distanceRemaining!!).split(" ").apply {
state.distance = this[0].replace(',', '.').toDouble()
state.unit = when (this[1]) {
"m" -> Unit.Metres
"km" -> Unit.Kilometres
else -> {
Unit.Any
state.distance = Distance(
this[0].replace(',', '.').toDouble().toInt(),
when (this[1]) {
"m" -> Unit.Metres
"km" -> Unit.Kilometres
else -> Unit.Any
}
}
)
}
}
@ -69,6 +65,12 @@ class ManeuverMapper {
return state
}
fun apply(locationMatcherResult: LocationMatcherResult): State {
val state = State()
// TODO: speed, limit, location?, bearing
return state
}
}
}

View file

@ -0,0 +1,141 @@
package eu.ztsh.garmin.data
enum class OutType(val value: Int) {
Off(0x00),
Lane(0x01),
LongerLane(0x02),
LeftRoundabout(0x04),
RightRoundabout(0x08),
Flag(0x40),
ArrowOnly(0x80);
}
enum class OutAngle(val value: Int) {
Down(0x01),
SharpRight(0x02),
Right(0x04),
EasyRight(0x08),
Straight(0x10),
EasyLeft(0x20),
Left(0x40),
SharpLeft(0x80),
LeftDown(0x81),
RightDown(0x82),
AsDirection(0x00)
}
enum class Unit(val value: Int) {
Any(0),
Metres(1),
Kilometres(3),
Miles(5),
Foot(8)
}
enum class Lane(val value: Int) {
DotsRight(0x01),
OuterRight(0x02),
MiddleRight(0x04),
InnerRight(0x08),
InnerLeft(0x10),
MiddleLeft(0x20),
OuterLeft(0x40),
DotsLeft(0x80)
}
open class Arrows(val lanes: List<Lane>) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Arrows
return lanes == other.lanes
}
override fun hashCode(): Int {
return lanes.hashCode()
}
}
class Lanes(lanes: List<Lane>) : Arrows(lanes)
class Outlines(lanes: List<Lane>) : Arrows(lanes)
class Distance(val distance: Int, val unit: Unit) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Distance
if (distance != other.distance) return false
if (unit != other.unit) return false
return true
}
override fun hashCode(): Int {
var result = distance
result = 31 * result + unit.hashCode()
return result
}
}
class Speed(val speed: Int, val limit: Int)
class Arrival(val hours: Int, val minutes: Int)
class State {
var lineArrows: Lanes? = null
var lineOutlines: Outlines? = null
var direction : Direction? = null
var distance: Distance? = null
var speed: Speed? = null
var arrival: Arrival? = null
// TODO: Bearing
// TODO: support
var traffic: Boolean? = null
var flag: Boolean? = null
var control: Boolean? = null
}
class Direction {
var angle: OutAngle = OutAngle.AsDirection
var out: OutType = OutType.Lane
var roundabout: OutAngle = OutAngle.AsDirection
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Direction
if (angle != other.angle) return false
if (out != other.out) return false
if (roundabout != other.roundabout) return false
return true
}
override fun hashCode(): Int {
var result = angle.hashCode()
result = 31 * result + out.hashCode()
result = 31 * result + roundabout.hashCode()
return result
}
}