kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
commit
a35714186d
|
@ -24,7 +24,7 @@ The app is also distributed for Amazon Fire devices via the Amazon appstore: [![
|
|||
If you would like to develop this application we'd love your help! These build instructions are brief
|
||||
and should be improved, please send a PR if you can.
|
||||
|
||||
* Use Android Studio 4.0 RC 1 to build/debug (other versions might work but no promises)
|
||||
* Use Android Studio 4.1.2 to build/debug (other versions might work but no promises)
|
||||
* Use "git submodule update --init --recursive" to pull in the various submodules we depend on
|
||||
* There are a few config files which you'll need to copy from templates included in the project.
|
||||
Run the following commands to do so:
|
||||
|
|
|
@ -113,7 +113,7 @@ dependencies {
|
|||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'androidx.core:core-ktx:1.3.2'
|
||||
implementation 'androidx.fragment:fragment-ktx:1.3.0'
|
||||
implementation 'androidx.fragment:fragment-ktx:1.3.1'
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||
|
|
|
@ -37,6 +37,7 @@ import androidx.fragment.app.FragmentManager
|
|||
import androidx.fragment.app.FragmentTransaction
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import com.geeksville.android.BindFailedException
|
||||
import com.geeksville.android.GeeksvilleApplication
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.android.ServiceClient
|
||||
|
@ -308,11 +309,7 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
|
||||
if (deniedPermissions.isNotEmpty()) {
|
||||
errormsg("Denied permissions: ${deniedPermissions.joinToString(",")}")
|
||||
Toast.makeText(
|
||||
this,
|
||||
getString(R.string.permission_missing),
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
showToast(R.string.permission_missing)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -700,40 +697,52 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
}
|
||||
}
|
||||
|
||||
private fun showToast(msgId: Int) {
|
||||
Toast.makeText(
|
||||
this,
|
||||
msgId,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
|
||||
private fun showToast(msg: String) {
|
||||
Toast.makeText(
|
||||
this,
|
||||
msg,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
|
||||
private fun perhapsChangeChannel() {
|
||||
// If the is opening a channel URL, handle it now
|
||||
requestedChannelUrl?.let { url ->
|
||||
try {
|
||||
val channels = ChannelSet(url)
|
||||
val primary = channels.primaryChannel
|
||||
requestedChannelUrl = null
|
||||
if (primary == null)
|
||||
showToast(R.string.channel_invalid)
|
||||
else {
|
||||
requestedChannelUrl = null
|
||||
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.new_channel_rcvd)
|
||||
.setMessage(getString(R.string.do_you_want_switch).format(primary.name))
|
||||
.setNeutralButton(R.string.cancel) { _, _ ->
|
||||
// Do nothing
|
||||
}
|
||||
.setPositiveButton(R.string.accept) { _, _ ->
|
||||
debug("Setting channel from URL")
|
||||
try {
|
||||
model.setChannels(channels)
|
||||
} catch (ex: RemoteException) {
|
||||
errormsg("Couldn't change channel ${ex.message}")
|
||||
Toast.makeText(
|
||||
this,
|
||||
"Couldn't change channel, because radio is not yet connected. Please try again.",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.new_channel_rcvd)
|
||||
.setMessage(getString(R.string.do_you_want_switch).format(primary.name))
|
||||
.setNeutralButton(R.string.cancel) { _, _ ->
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
.show()
|
||||
.setPositiveButton(R.string.accept) { _, _ ->
|
||||
debug("Setting channel from URL")
|
||||
try {
|
||||
model.setChannels(channels)
|
||||
} catch (ex: RemoteException) {
|
||||
errormsg("Couldn't change channel ${ex.message}")
|
||||
showToast(R.string.cant_change_no_radio)
|
||||
}
|
||||
}
|
||||
.show()
|
||||
}
|
||||
} catch (ex: InvalidProtocolBufferException) {
|
||||
Toast.makeText(
|
||||
this,
|
||||
R.string.channel_invalid,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
showToast(R.string.channel_invalid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -958,8 +967,14 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
}
|
||||
}
|
||||
|
||||
bindMeshService()
|
||||
|
||||
try {
|
||||
bindMeshService()
|
||||
}
|
||||
catch(ex: BindFailedException) {
|
||||
// App is probably shutting down, ignore
|
||||
errormsg("Bind of MeshService failed")
|
||||
}
|
||||
|
||||
val bonded = RadioInterfaceService.getBondedDeviceAddress(this) != null
|
||||
if (!bonded && usbDevice == null) // we will handle USB later
|
||||
showSettingsPage()
|
||||
|
@ -1058,7 +1073,7 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
try {
|
||||
val packageInfo: PackageInfo = packageManager.getPackageInfo(packageName, 0)
|
||||
val versionName = packageInfo.versionName
|
||||
Toast.makeText(applicationContext, versionName, Toast.LENGTH_LONG).show()
|
||||
showToast(versionName)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
errormsg("Can not find the version: ${e.message}")
|
||||
}
|
||||
|
|
|
@ -14,12 +14,27 @@ import kotlinx.serialization.Serializable
|
|||
|
||||
@Serializable
|
||||
@Parcelize
|
||||
data class MeshUser(val id: String, val longName: String, val shortName: String) :
|
||||
data class MeshUser(
|
||||
val id: String,
|
||||
val longName: String,
|
||||
val shortName: String,
|
||||
val hwModel: MeshProtos.HardwareModel
|
||||
) :
|
||||
Parcelable {
|
||||
|
||||
override fun toString(): String {
|
||||
return "MeshUser(id=${id.anonymize}, longName=${longName.anonymize}, shortName=${shortName.anonymize})"
|
||||
return "MeshUser(id=${id.anonymize}, longName=${longName.anonymize}, shortName=${shortName.anonymize}, hwModel=${hwModelString})"
|
||||
}
|
||||
|
||||
/** a string version of the hardware model, converted into pretty lowercase and changing _ to -, and p to dot
|
||||
* or null if unset
|
||||
* */
|
||||
val hwModelString: String?
|
||||
get() =
|
||||
if (hwModel == MeshProtos.HardwareModel.UNSET)
|
||||
null
|
||||
else
|
||||
hwModel.name.replace('_', '-').replace('p', '.').toLowerCase()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
|
@ -95,8 +110,8 @@ data class NodeInfo(
|
|||
get() {
|
||||
return position?.takeIf {
|
||||
(it.latitude <= 90.0 && it.latitude >= -90) && // If GPS gives a crap position don't crash our app
|
||||
it.latitude != 0.0 &&
|
||||
it.longitude != 0.0
|
||||
it.latitude != 0.0 &&
|
||||
it.longitude != 0.0
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -47,9 +47,11 @@ data class ChannelSet(
|
|||
/**
|
||||
* Return the primary channel info
|
||||
*/
|
||||
val primaryChannel: Channel get() {
|
||||
return Channel(protobuf.getSettings(0))
|
||||
}
|
||||
val primaryChannel: Channel? get() =
|
||||
if(protobuf.settingsCount > 0)
|
||||
Channel(protobuf.getSettings(0))
|
||||
else
|
||||
null
|
||||
|
||||
/// Return an URL that represents the current channel values
|
||||
/// @param upperCasePrefix - portions of the URL can be upper case to make for more efficient QR codes
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.geeksville.mesh.model
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.MeshUser
|
||||
import com.geeksville.mesh.NodeInfo
|
||||
import com.geeksville.mesh.Position
|
||||
|
@ -25,7 +26,8 @@ class NodeDB(private val ui: UIViewModel) {
|
|||
MeshUser(
|
||||
"+16508765308".format(8),
|
||||
"Kevin MesterNoLoc",
|
||||
"KLO"
|
||||
"KLO",
|
||||
MeshProtos.HardwareModel.ANDROID_SIM
|
||||
),
|
||||
null
|
||||
)
|
||||
|
@ -36,7 +38,8 @@ class NodeDB(private val ui: UIViewModel) {
|
|||
MeshUser(
|
||||
"+165087653%02d".format(9 + index),
|
||||
"Kevin Mester$index",
|
||||
"KM$index"
|
||||
"KM$index",
|
||||
MeshProtos.HardwareModel.ANDROID_SIM
|
||||
),
|
||||
it
|
||||
)
|
||||
|
|
|
@ -373,10 +373,10 @@ class MeshService : Service(), Logging {
|
|||
}
|
||||
}
|
||||
|
||||
private fun installNewNodeDB(newMyNodeInfo: MyNodeInfo, nodes: Array<NodeInfo>) {
|
||||
private fun installNewNodeDB(ni: MyNodeInfo, nodes: Array<NodeInfo>) {
|
||||
discardNodeDB() // Get rid of any old state
|
||||
|
||||
myNodeInfo = newMyNodeInfo
|
||||
myNodeInfo = ni
|
||||
|
||||
// put our node array into our two different map representations
|
||||
nodeDBbyNodeNum.putAll(nodes.map { Pair(it.num, it) })
|
||||
|
@ -542,7 +542,7 @@ class MeshService : Service(), Logging {
|
|||
|
||||
debug("Sending channels to device")
|
||||
asChannels.forEach {
|
||||
setChannel(it)
|
||||
setChannel(it)
|
||||
}
|
||||
|
||||
channels = asChannels.toTypedArray()
|
||||
|
@ -725,7 +725,8 @@ class MeshService : Service(), Logging {
|
|||
|
||||
// Handle new style routing info
|
||||
Portnums.PortNum.ROUTING_APP_VALUE -> {
|
||||
shouldBroadcast = true // We always send acks to other apps, because they might care about the messages they sent
|
||||
shouldBroadcast =
|
||||
true // We always send acks to other apps, because they might care about the messages they sent
|
||||
val u = MeshProtos.Routing.parseFrom(data.payload)
|
||||
if (u.errorReasonValue == MeshProtos.Routing.Error.NONE_VALUE)
|
||||
handleAckNak(true, data.requestId)
|
||||
|
@ -778,12 +779,13 @@ class MeshService : Service(), Logging {
|
|||
channels[ch.index] = ch
|
||||
debug("Admin: Received channel ${ch.index}")
|
||||
if (ch.index + 1 < mi.maxChannels) {
|
||||
if(ch.hasSettings()) {
|
||||
|
||||
// Stop once we get to the first disabled entry
|
||||
if (/* ch.hasSettings() || */ ch.role != ChannelProtos.Channel.Role.DISABLED) {
|
||||
// Not done yet, request next channel
|
||||
requestChannel(ch.index + 1)
|
||||
}
|
||||
else {
|
||||
debug("We've received the primary channel, allowing rest of app to start...")
|
||||
} else {
|
||||
debug("We've received the last channel, allowing rest of app to start...")
|
||||
onHasSettings()
|
||||
}
|
||||
} else {
|
||||
|
@ -806,7 +808,8 @@ class MeshService : Service(), Logging {
|
|||
it.user = MeshUser(
|
||||
if (p.id.isNotEmpty()) p.id else oldId, // If the new update doesn't contain an ID keep our old value
|
||||
p.longName,
|
||||
p.shortName
|
||||
p.shortName,
|
||||
p.hwModel
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1194,7 +1197,8 @@ class MeshService : Service(), Logging {
|
|||
MeshUser(
|
||||
info.user.id,
|
||||
info.user.longName,
|
||||
info.user.shortName
|
||||
info.user.shortName,
|
||||
info.user.hwModel
|
||||
)
|
||||
|
||||
if (info.hasPosition()) {
|
||||
|
@ -1221,6 +1225,77 @@ class MeshService : Service(), Logging {
|
|||
}
|
||||
|
||||
|
||||
private var rawMyNodeInfo: MeshProtos.MyNodeInfo? = null
|
||||
|
||||
/** Regenerate the myNodeInfo model. We call this twice. Once after we receive myNodeInfo from the device
|
||||
* and again after we have the node DB (which might allow us a better notion of our HwModel.
|
||||
*/
|
||||
private fun regenMyNodeInfo() {
|
||||
val myInfo = rawMyNodeInfo
|
||||
if (myInfo != null) {
|
||||
val a = RadioInterfaceService.getBondedDeviceAddress(this)
|
||||
val isBluetoothInterface = a != null && a.startsWith("x")
|
||||
|
||||
var hwModelStr = myInfo.hwModelDeprecated
|
||||
if (hwModelStr.isEmpty()) {
|
||||
val nodeNum =
|
||||
myInfo.myNodeNum // Note: can't use the normal property because myNodeInfo not yet setup
|
||||
val ni = nodeDBbyNodeNum[nodeNum] // can't use toNodeInfo because too early
|
||||
val asStr = ni?.user?.hwModelString
|
||||
if (asStr != null)
|
||||
hwModelStr = asStr
|
||||
}
|
||||
val mi = with(myInfo) {
|
||||
MyNodeInfo(
|
||||
myNodeNum,
|
||||
hasGps,
|
||||
hwModelStr,
|
||||
firmwareVersion,
|
||||
firmwareUpdateFilename != null,
|
||||
isBluetoothInterface && com.geeksville.mesh.service.SoftwareUpdateService.shouldUpdate(
|
||||
this@MeshService,
|
||||
DeviceVersion(firmwareVersion)
|
||||
),
|
||||
currentPacketId.toLong() and 0xffffffffL,
|
||||
if (messageTimeoutMsec == 0) 5 * 60 * 1000 else messageTimeoutMsec, // constants from current device code
|
||||
minAppVersion,
|
||||
maxChannels
|
||||
)
|
||||
}
|
||||
|
||||
newMyNodeInfo = mi
|
||||
setFirmwareUpdateFilename(mi)
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendAnalytics() {
|
||||
val myInfo = rawMyNodeInfo
|
||||
val mi = myNodeInfo
|
||||
if (myInfo != null && mi != null) {
|
||||
/// Track types of devices and firmware versions in use
|
||||
GeeksvilleApplication.analytics.setUserInfo(
|
||||
// DataPair("region", mi.region),
|
||||
DataPair("firmware", mi.firmwareVersion),
|
||||
DataPair("has_gps", mi.hasGPS),
|
||||
DataPair("hw_model", mi.model),
|
||||
DataPair("dev_error_count", myInfo.errorCount)
|
||||
)
|
||||
|
||||
if (myInfo.errorCode.number != 0) {
|
||||
GeeksvilleApplication.analytics.track(
|
||||
"dev_error",
|
||||
DataPair("code", myInfo.errorCode.number),
|
||||
DataPair("address", myInfo.errorAddress),
|
||||
|
||||
// We also include this info, because it is required to correctly decode address from the map file
|
||||
DataPair("firmware", mi.firmwareVersion),
|
||||
DataPair("hw_model", mi.model)
|
||||
// DataPair("region", mi.region)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the nodeinfo (called from either new API version or the old one)
|
||||
*/
|
||||
|
@ -1233,62 +1308,18 @@ class MeshService : Service(), Logging {
|
|||
)
|
||||
insertPacket(packetToSave)
|
||||
|
||||
setFirmwareUpdateFilename(myInfo)
|
||||
|
||||
val a = RadioInterfaceService.getBondedDeviceAddress(this)
|
||||
val isBluetoothInterface = a != null && a.startsWith("x")
|
||||
|
||||
val mi = with(myInfo) {
|
||||
MyNodeInfo(
|
||||
myNodeNum,
|
||||
hasGps,
|
||||
hwModelDeprecated,
|
||||
firmwareVersion,
|
||||
firmwareUpdateFilename != null,
|
||||
isBluetoothInterface && SoftwareUpdateService.shouldUpdate(
|
||||
this@MeshService,
|
||||
DeviceVersion(firmwareVersion)
|
||||
),
|
||||
currentPacketId.toLong() and 0xffffffffL,
|
||||
if (messageTimeoutMsec == 0) 5 * 60 * 1000 else messageTimeoutMsec, // constants from current device code
|
||||
minAppVersion,
|
||||
maxChannels
|
||||
)
|
||||
}
|
||||
|
||||
newMyNodeInfo = mi
|
||||
rawMyNodeInfo = myInfo
|
||||
regenMyNodeInfo()
|
||||
|
||||
// We'll need to get a new set of channels and settings now
|
||||
radioConfig = null
|
||||
|
||||
// prefill the channel array with null channels
|
||||
channels = Array(mi.maxChannels) {
|
||||
channels = Array(myInfo.maxChannels) {
|
||||
val b = ChannelProtos.Channel.newBuilder()
|
||||
b.index = it
|
||||
b.build()
|
||||
}
|
||||
|
||||
/// Track types of devices and firmware versions in use
|
||||
GeeksvilleApplication.analytics.setUserInfo(
|
||||
// DataPair("region", mi.region),
|
||||
DataPair("firmware", mi.firmwareVersion),
|
||||
DataPair("has_gps", mi.hasGPS),
|
||||
DataPair("hw_model", mi.model),
|
||||
DataPair("dev_error_count", myInfo.errorCount)
|
||||
)
|
||||
|
||||
if (myInfo.errorCode.number != 0) {
|
||||
GeeksvilleApplication.analytics.track(
|
||||
"dev_error",
|
||||
DataPair("code", myInfo.errorCode.number),
|
||||
DataPair("address", myInfo.errorAddress),
|
||||
|
||||
// We also include this info, because it is required to correctly decode address from the map file
|
||||
DataPair("firmware", mi.firmwareVersion),
|
||||
DataPair("hw_model", mi.model)
|
||||
// DataPair("region", mi.region)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1373,12 +1404,18 @@ class MeshService : Service(), Logging {
|
|||
else {
|
||||
discardNodeDB()
|
||||
debug("Installing new node DB")
|
||||
myNodeInfo = newMyNodeInfo
|
||||
myNodeInfo = newMyNodeInfo// Install myNodeInfo as current
|
||||
|
||||
newNodes.forEach(::installNodeInfo)
|
||||
newNodes.clear() // Just to save RAM ;-)
|
||||
|
||||
haveNodeDB = true // we now have nodes from real hardware
|
||||
|
||||
regenMyNodeInfo() // we have a node db now, so can possibly find a better hwmodel
|
||||
myNodeInfo = newMyNodeInfo // we might have just updated myNodeInfo
|
||||
|
||||
sendAnalytics()
|
||||
|
||||
requestRadioConfig()
|
||||
}
|
||||
} else
|
||||
|
@ -1550,12 +1587,12 @@ class MeshService : Service(), Logging {
|
|||
/***
|
||||
* Return the filename we will install on the device
|
||||
*/
|
||||
private fun setFirmwareUpdateFilename(info: MeshProtos.MyNodeInfo) {
|
||||
private fun setFirmwareUpdateFilename(info: MyNodeInfo) {
|
||||
firmwareUpdateFilename = try {
|
||||
if (info.region != null && info.firmwareVersion != null && info.hwModelDeprecated != null)
|
||||
if (info.firmwareVersion != null && info.model != null)
|
||||
SoftwareUpdateService.getUpdateFilename(
|
||||
this,
|
||||
info.hwModelDeprecated
|
||||
info.model
|
||||
)
|
||||
else
|
||||
null
|
||||
|
@ -1667,7 +1704,7 @@ class MeshService : Service(), Logging {
|
|||
|
||||
info("sendData dest=${p.to}, id=${p.id} <- ${p.bytes!!.size} bytes (connectionState=$connectionState)")
|
||||
|
||||
if(p.dataType == 0)
|
||||
if (p.dataType == 0)
|
||||
throw Exception("Port numbers must be non-zero!") // we are now more strict
|
||||
|
||||
// Keep a record of datapackets, so GUIs can show proper chat history
|
||||
|
@ -1723,7 +1760,7 @@ class MeshService : Service(), Logging {
|
|||
channelSet.toByteArray()
|
||||
}
|
||||
|
||||
override fun setChannels(payload: ByteArray?) {
|
||||
override fun setChannels(payload: ByteArray?) = toRemoteExceptions {
|
||||
val parsed = AppOnlyProtos.ChannelSet.parseFrom(payload)
|
||||
channelSet = parsed
|
||||
}
|
||||
|
|
|
@ -38,12 +38,22 @@ class MeshServiceLocationCallback(
|
|||
MeshService.info("got phone location")
|
||||
if (location.isAccurateForMesh) { // if within 200 meters, or accuracy is unknown
|
||||
|
||||
// Do we want to broadcast this position globally, or are we just telling the local node what its current position is (
|
||||
val shouldBroadcast = isAllowedToSend()
|
||||
val destinationNumber = if (shouldBroadcast) DataPacket.NODENUM_BROADCAST else getNodeNum()
|
||||
try {
|
||||
// Do we want to broadcast this position globally, or are we just telling the local node what its current position is (
|
||||
val shouldBroadcast = isAllowedToSend()
|
||||
val destinationNumber =
|
||||
if (shouldBroadcast) DataPacket.NODENUM_BROADCAST else getNodeNum()
|
||||
|
||||
// Note: we never want this message sent as a reliable message, because it is low value and we'll be sending one again later anyways
|
||||
sendPosition(location, destinationNumber, wantResponse = false)
|
||||
// Note: we never want this message sent as a reliable message, because it is low value and we'll be sending one again later anyways
|
||||
sendPosition(location, destinationNumber, wantResponse = false)
|
||||
|
||||
} catch (ex: RemoteException) { // Really a RadioNotConnected exception, but it has changed into this type via remoting
|
||||
MeshService.warn("Lost connection to radio, stopping location requests")
|
||||
onSendPositionFailed()
|
||||
} catch (ex: BLEException) { // Really a RadioNotConnected exception, but it has changed into this type via remoting
|
||||
MeshService.warn("BLE exception, stopping location requests $ex")
|
||||
onSendPositionFailed()
|
||||
}
|
||||
} else {
|
||||
MeshService.warn("accuracy ${location.accuracy} is too poor to use")
|
||||
}
|
||||
|
@ -51,21 +61,13 @@ class MeshServiceLocationCallback(
|
|||
}
|
||||
|
||||
private fun sendPosition(location: Location, destinationNumber: Int, wantResponse: Boolean) {
|
||||
try {
|
||||
onSendPosition(
|
||||
location.latitude,
|
||||
location.longitude,
|
||||
location.altitude.toInt(),
|
||||
destinationNumber,
|
||||
wantResponse // wantResponse?
|
||||
)
|
||||
} catch (ex: RemoteException) { // Really a RadioNotConnected exception, but it has changed into this type via remoting
|
||||
MeshService.warn("Lost connection to radio, stopping location requests")
|
||||
onSendPositionFailed()
|
||||
} catch (ex: BLEException) { // Really a RadioNotConnected exception, but it has changed into this type via remoting
|
||||
MeshService.warn("BLE exception, stopping location requests $ex")
|
||||
onSendPositionFailed()
|
||||
}
|
||||
onSendPosition(
|
||||
location.latitude,
|
||||
location.longitude,
|
||||
location.altitude.toInt(),
|
||||
destinationNumber,
|
||||
wantResponse // wantResponse?
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -142,6 +142,7 @@ class MockInterface(private val service: RadioInterfaceService) : Logging, IRadi
|
|||
id = DataPacket.nodeNumToDefaultId(numIn)
|
||||
longName = "Sim " + num.toHexString()
|
||||
shortName = getInitials(longName)
|
||||
hwModel = MeshProtos.HardwareModel.ANDROID_SIM
|
||||
}.build()
|
||||
position = MeshProtos.Position.newBuilder().apply {
|
||||
latitudeI = Position.degI(lat)
|
||||
|
@ -160,9 +161,8 @@ class MockInterface(private val service: RadioInterfaceService) : Logging, IRadi
|
|||
MeshProtos.FromRadio.newBuilder().apply {
|
||||
myInfo = MeshProtos.MyNodeInfo.newBuilder().apply {
|
||||
myNodeNum = MY_NODE
|
||||
hwModelDeprecated = "Sim"
|
||||
messageTimeoutMsec = 5 * 60 * 1000
|
||||
firmwareVersion = service.getString(R.string.cur_firmware_version)
|
||||
firmwareVersion = "1.2.8" // Pretend to be running an older 1.2 version
|
||||
numBands = 13
|
||||
maxChannels = 8
|
||||
}.build()
|
||||
|
|
|
@ -79,11 +79,10 @@ class ChannelFragment : ScreenFragment("Channel"), Logging {
|
|||
/// Pull the latest data from the model (discarding any user edits)
|
||||
private fun setGUIfromModel() {
|
||||
val channels = model.channels.value
|
||||
val channel = channels?.primaryChannel
|
||||
|
||||
binding.editableCheckbox.isChecked = false // start locked
|
||||
if (channels != null) {
|
||||
val channel = channels.primaryChannel
|
||||
|
||||
if (channel != null) {
|
||||
binding.qrView.visibility = View.VISIBLE
|
||||
binding.channelNameEdit.visibility = View.VISIBLE
|
||||
binding.channelNameEdit.setText(channel.humanName)
|
||||
|
@ -156,8 +155,8 @@ class ChannelFragment : ScreenFragment("Channel"), Logging {
|
|||
val checked = binding.editableCheckbox.isChecked
|
||||
if (checked) {
|
||||
// User just unlocked for editing - remove the # goo around the channel name
|
||||
model.channels.value?.let { channels ->
|
||||
binding.channelNameEdit.setText(channels.primaryChannel.name)
|
||||
model.channels.value?.primaryChannel?.let { ch ->
|
||||
binding.channelNameEdit.setText(ch.name)
|
||||
}
|
||||
} else {
|
||||
// User just locked it, we should warn and then apply changes to radio
|
||||
|
@ -169,8 +168,7 @@ class ChannelFragment : ScreenFragment("Channel"), Logging {
|
|||
}
|
||||
.setPositiveButton(getString(R.string.accept)) { _, _ ->
|
||||
// Generate a new channel with only the changes the user can change in the GUI
|
||||
model.channels.value?.let { old ->
|
||||
val oldPrimary = old.primaryChannel
|
||||
model.channels.value?.primaryChannel?.let { oldPrimary ->
|
||||
val newSettings = oldPrimary.settings.toBuilder()
|
||||
newSettings.name = binding.channelNameEdit.text.toString().trim()
|
||||
|
||||
|
|
|
@ -610,7 +610,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
|||
statusText.text = getString(R.string.must_set_region)
|
||||
|
||||
connected == MeshService.ConnectionState.CONNECTED -> {
|
||||
val fwStr = info?.firmwareString ?: ""
|
||||
val fwStr = info?.firmwareString ?: "unknown"
|
||||
statusText.text = getString(R.string.connected_to).format(fwStr)
|
||||
}
|
||||
connected == MeshService.ConnectionState.DISCONNECTED ->
|
||||
|
|
|
@ -94,6 +94,7 @@
|
|||
<string name="okay">Okay</string>
|
||||
<string name="must_set_region">You must set a region!</string>
|
||||
<string name="region">Region</string>
|
||||
<string name="cant_change_no_radio">Couldn\'t change channel, because radio is not yet connected. Please try again.</string>
|
||||
<string name="sample_coords">55.332244 34.442211</string>
|
||||
<string name="save_messages">Save messages as csv...</string>
|
||||
</resources>
|
||||
|
|
2
design
2
design
|
@ -1 +1 @@
|
|||
Subproject commit d0339f0297c629f1bd6873b4abccfecb98443538
|
||||
Subproject commit a81074152157fa54b0d02ccbbd6a6357cc3cedcf
|
|
@ -1 +1 @@
|
|||
Subproject commit 6da250358ed13e3c58fd4fa2a123b01b3826d4bf
|
||||
Subproject commit 158f6f2dd5dfe81833ed035d54045d7b34394e51
|
Ładowanie…
Reference in New Issue