This commit is contained in:
Piotr Dec 2023-08-16 02:44:36 +02:00
parent 49559993d8
commit 0a00d13706
6 changed files with 328 additions and 15 deletions

View file

@ -16,6 +16,7 @@ android {
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
buildConfigField "String", "MAPBOX_DOWNLOADS_TOKEN", "\"$MAPBOX_DOWNLOADS_TOKEN\""
}
buildTypes {
@ -33,6 +34,7 @@ android {
}
buildFeatures {
viewBinding true
buildConfig = true
}
}

View file

@ -5,8 +5,10 @@ import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothSocket
import android.util.Log
import com.mapbox.navigation.ui.maneuver.model.Maneuver
import java.io.IOException
import java.util.*
import java.util.concurrent.SynchronousQueue
@SuppressLint("MissingPermission")
@ -16,19 +18,79 @@ class Garmin(
val adapter: BluetoothAdapter
) {
private lateinit var thread: ConnectThread
private lateinit var connection: ConnectThread
private lateinit var processing: ProcessingThread
private var stateCache: State = State()
fun start() {
thread = ConnectThread()
thread.start()
connection = ConnectThread()
connection.start()
}
fun close() {
thread.close()
connection.close()
}
fun process(maneuver: Maneuver) {
processing = ProcessingThread(maneuver)
processing.start()
processing.join()
}
private inner class ProcessingThread(val maneuver: Maneuver) : Thread() {
override fun run() {
send(ManeuverMapper.apply(maneuver))
}
fun send(incoming: eu.ztsh.garmin.State) {
if (stateCache.distance != incoming.distance) {
setDistance(incoming)
}
if (stateCache.direction != incoming.direction) {
setDirection(stateCache.direction)
}
stateCache = incoming
}
private fun setDistance(state: eu.ztsh.garmin.State) {
connection.enqueue(intArrayOf(
0x03, asDigit(state.distance / 1000), asDigit(state.distance / 100), asDigit(state.distance / 10),
0x00, asDigit(state.distance), state.unit.data
))
}
private fun setDirection(direction: Direction) {
val param1 = when (direction.outAngle) {
OutAngle.LeftDown -> 0x10
OutAngle.RightDown -> 0x20
else -> direction.outType.data
}
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
}
val param3: Int = if (direction.outAngle == OutAngle.LeftDown || direction.outAngle == OutAngle.RightDown) 0x00 else direction.outAngle.data
connection.enqueue(intArrayOf(0x01, param1, param2, param3))
}
private fun asDigit(n: Int): Int {
if (n == 0) {
return 0
}
val m = n % 10
return if (m == 0) 10 else m
}
}
private inner class ConnectThread : Thread() {
private val queue: SynchronousQueue<IntArray> = SynchronousQueue()
private var current: IntArray = intArrayOf()
private val socket: BluetoothSocket? by lazy(LazyThreadSafetyMode.NONE) {
context.checkBt()
device.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"))
@ -41,10 +103,12 @@ class Garmin(
socket?.connect()
sleep(3000)
readAll()
send(intArrayOf(1, 1, 0, 16))
send(intArrayOf(3, 0, 1, 10, 0, 10, 3))
send(intArrayOf(3, 0, 0, 9, 0, 9, 3))
send(intArrayOf(0x04, 0x01))
while (true) {
val newCurrent = Optional.ofNullable(queue.poll()).orElse(current)
current = newCurrent
send(current)
sleep(900)
}
}
// Closes the client socket and causes the thread to finish.
@ -56,7 +120,15 @@ class Garmin(
}
}
fun readAll() {
fun enqueue(data: IntArray) {
queue.put(data)
}
private fun send(data: IntArray) {
sendRaw(prepareData(data))
}
private fun readAll() {
val buffer = ByteArray(64)
val istr = socket!!.inputStream
istr!!.let {
@ -66,10 +138,6 @@ class Garmin(
}
}
fun send(hex: IntArray) {
sendRaw(prepareData(hex))
}
private fun sendRaw(buff: IntArray) {
buff.forEach { socket!!.outputStream.write(it) }
socket!!.outputStream.flush()

View file

@ -14,6 +14,15 @@ import androidx.activity.result.ActivityResultCallback
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import com.mapbox.navigation.base.formatter.DistanceFormatterOptions
import com.mapbox.navigation.base.options.NavigationOptions
import com.mapbox.navigation.base.trip.model.RouteProgress
import com.mapbox.navigation.core.formatter.MapboxDistanceFormatter
import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp
import com.mapbox.navigation.core.trip.session.RouteProgressObserver
import com.mapbox.navigation.ui.maneuver.api.MapboxManeuverApi
import eu.ztsh.garmin.databinding.ActivityMainBinding
@SuppressLint("MissingPermission")
@ -21,15 +30,66 @@ class MainActivity : AppCompatActivity() {
lateinit var garmin: Garmin
private lateinit var binding : ActivityMainBinding
private lateinit var binding: ActivityMainBinding
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)
}
})
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// bluetoothInit()
if (!MapboxNavigationApp.isSetup()) {
MapboxNavigationApp.setup {
NavigationOptions.Builder(applicationContext)
.accessToken(BuildConfig.MAPBOX_DOWNLOADS_TOKEN)
.build()
}
}
MapboxNavigationApp.current()?.startTripSession()
bluetoothInit()
}
override fun onStart() {
super.onStart()
MapboxNavigationApp.current()?.registerRouteProgressObserver(routeProgressObserver)
}
override fun onStop() {
super.onStop()
MapboxNavigationApp.current()?.unregisterRouteProgressObserver(routeProgressObserver)
}
override fun onDestroy() {
super.onDestroy()
MapboxNavigationApp.current()?.stopTripSession()
maneuverApi.cancel()
}
// Define distance formatter options
private val distanceFormatter: DistanceFormatterOptions by lazy {
DistanceFormatterOptions.Builder(this).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 { garmin.process(this[0]) } }
private fun bluetoothInit() {
val bluetoothManager: BluetoothManager = getSystemService(BluetoothManager::class.java)
val bluetoothAdapter: BluetoothAdapter = bluetoothManager.adapter

View file

@ -0,0 +1,66 @@
package eu.ztsh.garmin
import com.mapbox.navigation.ui.maneuver.model.Maneuver
class ManeuverMapper {
companion object {
fun apply(maneuver: Maneuver): State {
val state = State()
maneuver.apply {
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
}
"arrive" -> {
state.flag = true
}
}
when (this.modifier) {
"right" -> {
when (this.type) {
"turn" -> state.direction.outAngle = OutAngle.Right
"roundabout" -> {
when (this.degrees) {
137.0 -> state.direction.outAngle = OutAngle.EasyRight
}
}
}
}
"left" -> {
when (this.type) {
"turn" -> state.direction.outAngle = OutAngle.Left
}
}
}
}
this.stepDistance.apply {
this.distanceRemaining?.apply {
distanceFormatter.formatDistance(distanceRemaining!!).split(" ").apply {
state.distance = this[0].toInt()
state.unit = when (this[1]) {
"m" -> Unit.Metres
"km" -> Unit.Kilometres
else -> {Unit.Any}
}
}
}
}
}
return state
}
}
}

View file

@ -0,0 +1,21 @@
package eu.ztsh.garmin
import android.util.Log
import com.mapbox.navigation.core.MapboxNavigation
import com.mapbox.navigation.core.lifecycle.MapboxNavigationObserver
class MapboxObserver : MapboxNavigationObserver {
override fun onAttached(mapboxNavigation: MapboxNavigation) {
Log.d(TAG, "Attached")
}
override fun onDetached(mapboxNavigation: MapboxNavigation) {
Log.d(TAG, "Detached")
}
companion object {
const val TAG = "MBOXOBS"
}
}

View file

@ -0,0 +1,96 @@
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: Int = 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
}
}