update protobufs

This commit is contained in:
andrekir 2022-05-26 16:23:47 -03:00
parent 60f6e0a96f
commit 48fd657d8a
15 changed files with 140 additions and 150 deletions

View file

@ -80,12 +80,12 @@ interface IMeshService {
List<DataPacket> getOldMessages(); List<DataPacket> getOldMessages();
/// This method is only intended for use in our GUI, so the user can set radio options /// This method is only intended for use in our GUI, so the user can set radio options
/// It returns a RadioConfig protobuf. /// It returns a DeviceConfig protobuf.
byte []getRadioConfig(); byte []getDeviceConfig();
/// This method is only intended for use in our GUI, so the user can set radio options /// This method is only intended for use in our GUI, so the user can set radio options
/// It sets a RadioConfig protobuf /// It sets a DeviceConfig protobuf
void setRadioConfig(in byte []payload); void setDeviceConfig(in byte []payload);
/// This method is only intended for use in our GUI, so the user can set radio options /// This method is only intended for use in our GUI, so the user can set radio options
/// It returns a ChannelSet protobuf. /// It returns a ChannelSet protobuf.

View file

@ -218,9 +218,6 @@ class MainActivity : BaseActivity(), Logging,
/** Ask the user to grant Bluetooth scan/discovery permission */ /** Ask the user to grant Bluetooth scan/discovery permission */
fun requestScanPermission() = requestPermission(getScanPermissions(), true) fun requestScanPermission() = requestPermission(getScanPermissions(), true)
/** Ask the user to grant camera permission */
fun requestCameraPermission() = requestPermission(getCameraPermissions())
/** Ask the user to grant foreground location permission */ /** Ask the user to grant foreground location permission */
fun requestLocationPermission() = requestPermission(getLocationPermissions()) fun requestLocationPermission() = requestPermission(getLocationPermissions())
@ -644,7 +641,7 @@ class MainActivity : BaseActivity(), Logging,
model.setConnectionState(newConnection) model.setConnectionState(newConnection)
debug("Getting latest radioconfig from service") debug("Getting latest DeviceConfig from service")
try { try {
val info: MyNodeInfo? = service.myNodeInfo // this can be null val info: MyNodeInfo? = service.myNodeInfo // this can be null
model.setMyNodeInfo(info) model.setMyNodeInfo(info)
@ -662,9 +659,9 @@ class MainActivity : BaseActivity(), Logging,
if (curVer < MeshService.minFirmwareVersion) if (curVer < MeshService.minFirmwareVersion)
showAlert(R.string.firmware_too_old, R.string.firmware_old) showAlert(R.string.firmware_too_old, R.string.firmware_old)
else { else {
// If our app is too old/new, we probably don't understand the new radioconfig messages, so we don't read them until here // If our app is too old/new, we probably don't understand the new DeviceConfig messages, so we don't read them until here
model.setRadioConfig(RadioConfigProtos.RadioConfig.parseFrom(service.radioConfig)) model.setDeviceConfig(ConfigProtos.Config.parseFrom(service.deviceConfig))
model.setChannels(ChannelSet(AppOnlyProtos.ChannelSet.parseFrom(service.channels))) model.setChannels(ChannelSet(AppOnlyProtos.ChannelSet.parseFrom(service.channels)))

View file

@ -22,7 +22,7 @@ data class Channel(val settings: ChannelProtos.ChannelSettings) {
// TH=he unsecured channel that devices ship with // TH=he unsecured channel that devices ship with
val default = Channel( val default = Channel(
ChannelProtos.ChannelSettings.newBuilder() ChannelProtos.ChannelSettings.newBuilder()
.setModemConfig(ChannelProtos.ChannelSettings.ModemConfig.LongFast) // .setModemConfig(ChannelProtos.ChannelSettings.ModemConfig.LongFast)
.setPsk(ByteString.copyFrom(defaultPSK)) .setPsk(ByteString.copyFrom(defaultPSK))
.build() .build()
) )
@ -30,26 +30,20 @@ data class Channel(val settings: ChannelProtos.ChannelSettings) {
/// Return the name of our channel as a human readable string. If empty string, assume "Default" per mesh.proto spec /// Return the name of our channel as a human readable string. If empty string, assume "Default" per mesh.proto spec
val name: String val name: String
get() = if (settings.name.isEmpty()) { get() = settings.name.ifEmpty { "Placeholder" /*
// We have a new style 'empty' channel name. Use the same logic from the device to convert that to a human readable name when (settings.modemConfig) {
if (settings.bandwidth != 0) ConfigProtos.Config.LoRaConfig.ModemPreset.ShortFast -> "ShortFast"
"Unset" ConfigProtos.Config.LoRaConfig.ModemPreset.ShortSlow -> "ShortSlow"
else when (settings.modemConfig) { ConfigProtos.Config.LoRaConfig.ModemPreset.MidFast -> "MidFast"
ChannelProtos.ChannelSettings.ModemConfig.ShortFast -> "ShortFast" ConfigProtos.Config.LoRaConfig.ModemPreset.MidSlow -> "MidSlow"
ChannelProtos.ChannelSettings.ModemConfig.ShortSlow -> "ShortSlow" ConfigProtos.Config.LoRaConfig.ModemPreset.LongFast -> "LongFast"
ChannelProtos.ChannelSettings.ModemConfig.MidFast -> "MidFast" ConfigProtos.Config.LoRaConfig.ModemPreset.LongSlow -> "LongSlow"
ChannelProtos.ChannelSettings.ModemConfig.MidSlow -> "MidSlow" ConfigProtos.Config.LoRaConfig.ModemPreset.VLongSlow -> "VLongSlow"
ChannelProtos.ChannelSettings.ModemConfig.LongFast -> "LongFast"
ChannelProtos.ChannelSettings.ModemConfig.LongSlow -> "LongSlow"
ChannelProtos.ChannelSettings.ModemConfig.VLongSlow -> "VLongSlow"
else -> "Invalid" else -> "Invalid"
} }*/
} else }
settings.name
val modemConfig: ChannelProtos.ChannelSettings.ModemConfig get() = settings.modemConfig val psk: ByteString
val psk
get() = if (settings.psk.size() != 1) get() = if (settings.psk.size() != 1)
settings.psk // A standard PSK settings.psk // A standard PSK
else { else {
@ -86,4 +80,4 @@ data class Channel(val settings: ChannelProtos.ChannelSettings) {
&& name == o.name && name == o.name
} }
fun xorHash(b: ByteArray) = b.fold(0, { acc, x -> acc xor (x.toInt() and 0xff) }) fun xorHash(b: ByteArray) = b.fold(0) { acc, x -> acc xor (x.toInt() and 0xff) }

View file

@ -1,25 +1,25 @@
package com.geeksville.mesh.model package com.geeksville.mesh.model
import com.geeksville.mesh.ChannelProtos import com.geeksville.mesh.ConfigProtos
import com.geeksville.mesh.R import com.geeksville.mesh.R
enum class ChannelOption( enum class ChannelOption(
val modemConfig: ChannelProtos.ChannelSettings.ModemConfig, val modemPreset: ConfigProtos.Config.LoRaConfig.ModemPreset,
val configRes: Int, val configRes: Int,
val minBroadcastPeriodSecs: Int val minBroadcastPeriodSecs: Int
) { ) {
SHORT_FAST(ChannelProtos.ChannelSettings.ModemConfig.ShortFast,R.string.modem_config_short, 30), SHORT_FAST(ConfigProtos.Config.LoRaConfig.ModemPreset.ShortFast, R.string.modem_config_short, 30),
SHORT_SLOW(ChannelProtos.ChannelSettings.ModemConfig.ShortSlow, R.string.modem_config_slow_short, 30), SHORT_SLOW(ConfigProtos.Config.LoRaConfig.ModemPreset.ShortSlow, R.string.modem_config_slow_short, 30),
MED_FAST(ChannelProtos.ChannelSettings.ModemConfig.MidFast,R.string.modem_config_medium, 60), MED_FAST(ConfigProtos.Config.LoRaConfig.ModemPreset.MidFast, R.string.modem_config_medium, 60),
MED_SLOW(ChannelProtos.ChannelSettings.ModemConfig.MidSlow,R.string.modem_config_slow_medium, 60), MED_SLOW(ConfigProtos.Config.LoRaConfig.ModemPreset.MidSlow, R.string.modem_config_slow_medium, 60),
LONG_FAST(ChannelProtos.ChannelSettings.ModemConfig.LongFast, R.string.modem_config_long, 240), LONG_FAST(ConfigProtos.Config.LoRaConfig.ModemPreset.LongFast, R.string.modem_config_long, 240),
LONG_SLOW(ChannelProtos.ChannelSettings.ModemConfig.LongSlow, R.string.modem_config_slow_long, 375), LONG_SLOW(ConfigProtos.Config.LoRaConfig.ModemPreset.LongSlow, R.string.modem_config_slow_long, 375),
VERY_LONG(ChannelProtos.ChannelSettings.ModemConfig.VLongSlow, R.string.modem_config_very_long, 375); VERY_LONG(ConfigProtos.Config.LoRaConfig.ModemPreset.VLongSlow, R.string.modem_config_very_long, 375);
companion object { companion object {
fun fromConfig(modemConfig: ChannelProtos.ChannelSettings.ModemConfig?): ChannelOption? { fun fromConfig(modemPreset: ConfigProtos.Config.LoRaConfig.ModemPreset?): ChannelOption? {
for (option in values()) { for (option in values()) {
if (option.modemConfig == modemConfig) if (option.modemPreset == modemPreset)
return option return option
} }
return null return null

View file

@ -22,8 +22,6 @@ data class ChannelSet(
private fun urlToChannels(url: Uri): AppOnlyProtos.ChannelSet { private fun urlToChannels(url: Uri): AppOnlyProtos.ChannelSet {
val urlStr = url.toString() val urlStr = url.toString()
// We no longer support the super old (about 0.8ish? verison of the URLs that don't use the # separator - I doubt
// anyone is still using that old format
val pathRegex = Regex("$prefix(.*)", RegexOption.IGNORE_CASE) val pathRegex = Regex("$prefix(.*)", RegexOption.IGNORE_CASE)
val (base64) = pathRegex.find(urlStr)?.destructured val (base64) = pathRegex.find(urlStr)?.destructured
?: throw MalformedURLException("Not a meshtastic URL: ${urlStr.take(40)}") ?: throw MalformedURLException("Not a meshtastic URL: ${urlStr.take(40)}")
@ -51,13 +49,13 @@ data class ChannelSet(
/// Return an URL that represents the current channel values /// 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 /// @param upperCasePrefix - portions of the URL can be upper case to make for more efficient QR codes
fun getChannelUrl(upperCasePrefix: Boolean = false): Uri { fun getChannelUrl(upperCasePrefix: Boolean = false): Uri {
// If we have a valid radio config use it, othterwise use whatever we have saved in the prefs // If we have a valid radio config use it, otherwise use whatever we have saved in the prefs
val channelBytes = protobuf.toByteArray() ?: ByteArray(0) // if unset just use empty val channelBytes = protobuf.toByteArray() ?: ByteArray(0) // if unset just use empty
val enc = Base64.encodeToString(channelBytes, base64Flags) val enc = Base64.encodeToString(channelBytes, base64Flags)
val p = if (upperCasePrefix) val p = if (upperCasePrefix)
prefix.toUpperCase() prefix.uppercase()
else else
prefix prefix
return Uri.parse("$p$enc") return Uri.parse("$p$enc")

View file

@ -43,8 +43,8 @@ fun getInitials(nameIn: String): String {
val words = name.split(Regex("\\s+")).filter { it.isNotEmpty() } val words = name.split(Regex("\\s+")).filter { it.isNotEmpty() }
val initials = when (words.size) { val initials = when (words.size) {
in 0..minchars - 1 -> { in 0 until minchars -> {
val nm = if (name.length >= 1) val nm = if (name.isNotEmpty())
name.first() + name.drop(1).filterNot { c -> c.lowercase() in "aeiou" } name.first() + name.drop(1).filterNot { c -> c.lowercase() in "aeiou" }
else else
"" ""
@ -101,8 +101,8 @@ class UIViewModel @Inject constructor(
} }
/// various radio settings (including the channel) /// various radio settings (including the channel)
private val _radioConfig = MutableLiveData<RadioConfigProtos.RadioConfig?>() private val _deviceConfig = MutableLiveData<ConfigProtos.Config?>()
val radioConfig: LiveData<RadioConfigProtos.RadioConfig?> get() = _radioConfig val deviceConfig: LiveData<ConfigProtos.Config?> get() = _deviceConfig
private val _channels = MutableLiveData<ChannelSet?>() private val _channels = MutableLiveData<ChannelSet?>()
val channels: LiveData<ChannelSet?> get() = _channels val channels: LiveData<ChannelSet?> get() = _channels
@ -123,7 +123,7 @@ class UIViewModel @Inject constructor(
var positionBroadcastSecs: Int? var positionBroadcastSecs: Int?
get() { get() {
_radioConfig.value?.preferences?.let { _deviceConfig.value?.position?.let {
if (it.positionBroadcastSecs > 0) return it.positionBroadcastSecs if (it.positionBroadcastSecs > 0) return it.positionBroadcastSecs
// These default values are borrowed from the device code. // These default values are borrowed from the device code.
return 15 * 60 return 15 * 60
@ -131,50 +131,50 @@ class UIViewModel @Inject constructor(
return null return null
} }
set(value) { set(value) {
val config = _radioConfig.value val config = _deviceConfig.value
if (value != null && config != null) { if (value != null && config != null) {
val builder = config.toBuilder() val builder = config.toBuilder()
builder.preferencesBuilder.positionBroadcastSecs = value builder.positionBuilder.positionBroadcastSecs = value
setRadioConfig(builder.build()) setDeviceConfig(builder.build())
} }
} }
var lsSleepSecs: Int? var lsSleepSecs: Int?
get() = _radioConfig.value?.preferences?.lsSecs get() = _deviceConfig.value?.power?.lsSecs
set(value) { set(value) {
val config = _radioConfig.value val config = _deviceConfig.value
if (value != null && config != null) { if (value != null && config != null) {
val builder = config.toBuilder() val builder = config.toBuilder()
builder.preferencesBuilder.lsSecs = value builder.powerBuilder.lsSecs = value
setRadioConfig(builder.build()) setDeviceConfig(builder.build())
} }
} }
var locationShareDisabled: Boolean var gpsDisabled: Boolean
get() = _radioConfig.value?.preferences?.locationShareDisabled ?: false get() = _deviceConfig.value?.position?.gpsDisabled ?: false
set(value) { set(value) {
val config = _radioConfig.value val config = _deviceConfig.value
if (config != null) { if (config != null) {
val builder = config.toBuilder() val builder = config.toBuilder()
builder.preferencesBuilder.locationShareDisabled = value builder.positionBuilder.gpsDisabled = value
setRadioConfig(builder.build()) setDeviceConfig(builder.build())
} }
} }
var isPowerSaving: Boolean? var isPowerSaving: Boolean?
get() = _radioConfig.value?.preferences?.isPowerSaving get() = _deviceConfig.value?.power?.isPowerSaving
set(value) { set(value) {
val config = _radioConfig.value val config = _deviceConfig.value
if (value != null && config != null) { if (value != null && config != null) {
val builder = config.toBuilder() val builder = config.toBuilder()
builder.preferencesBuilder.isPowerSaving = value builder.powerBuilder.isPowerSaving = value
setRadioConfig(builder.build()) setDeviceConfig(builder.build())
} }
} }
var region: RadioConfigProtos.RegionCode var region: ConfigProtos.Config.LoRaConfig.RegionCode
get() = meshService?.region?.let { RadioConfigProtos.RegionCode.forNumber(it) } get() = meshService?.region?.let { ConfigProtos.Config.LoRaConfig.RegionCode.forNumber(it) }
?: RadioConfigProtos.RegionCode.Unset ?: ConfigProtos.Config.LoRaConfig.RegionCode.Unset
set(value) { set(value) {
meshService?.region = value.number meshService?.region = value.number
} }
@ -216,10 +216,10 @@ class UIViewModel @Inject constructor(
val primaryChannel: Channel? get() = _channels.value?.primaryChannel val primaryChannel: Channel? get() = _channels.value?.primaryChannel
// Set the radio config (also updates our saved copy in preferences) // Set the radio config (also updates our saved copy in preferences)
fun setRadioConfig(c: RadioConfigProtos.RadioConfig) { fun setDeviceConfig(c: ConfigProtos.Config) {
debug("Setting new radio config!") debug("Setting new radio config!")
meshService?.radioConfig = c.toByteArray() meshService?.deviceConfig = c.toByteArray()
_radioConfig.value = _deviceConfig.value =
c // Must be done after calling the service, so we will will properly throw if the service failed (and therefore not cache invalid new settings) c // Must be done after calling the service, so we will will properly throw if the service failed (and therefore not cache invalid new settings)
} }

View file

@ -60,13 +60,13 @@ class MockInterface(private val service: RadioInterfaceService) : Logging, IRadi
private fun handleAdminPacket(pr: MeshProtos.ToRadio, d: AdminProtos.AdminMessage) { private fun handleAdminPacket(pr: MeshProtos.ToRadio, d: AdminProtos.AdminMessage) {
when { when {
d.getRadioRequest -> d.getConfigRequest == AdminProtos.AdminMessage.ConfigType.LORA_CONFIG ->
sendAdmin(pr.packet.to, pr.packet.from, pr.packet.id) { sendAdmin(pr.packet.to, pr.packet.from, pr.packet.id) {
getRadioResponse = RadioConfigProtos.RadioConfig.newBuilder().apply { getConfigResponse = ConfigProtos.Config.newBuilder().apply {
preferences = lora =
RadioConfigProtos.RadioConfig.UserPreferences.newBuilder().apply { ConfigProtos.Config.LoRaConfig.newBuilder().apply {
region = RadioConfigProtos.RegionCode.TW region = ConfigProtos.Config.LoRaConfig.RegionCode.TW
// FIXME set critical times? // FIXME set critical times?
}.build() }.build()
}.build() }.build()

View file

@ -83,7 +83,7 @@ class MeshService : Service(), Logging {
class NodeNumNotFoundException(id: Int) : NodeNotFoundException("NodeNum not found $id") class NodeNumNotFoundException(id: Int) : NodeNotFoundException("NodeNum not found $id")
class IdNotFoundException(id: String) : NodeNotFoundException("ID not found $id") class IdNotFoundException(id: String) : NodeNotFoundException("ID not found $id")
class NoRadioConfigException(message: String = "No radio settings received (is our app too old?)") : class NoDeviceConfigException(message: String = "No radio settings received (is our app too old?)") :
RadioNotConnectedException(message) RadioNotConnectedException(message)
/** We treat software update as similar to loss of comms to the regular bluetooth service (so things like sendPosition for background GPS ignores the problem */ /** We treat software update as similar to loss of comms to the regular bluetooth service (so things like sendPosition for background GPS ignores the problem */
@ -128,7 +128,7 @@ class MeshService : Service(), Logging {
private var locationFlow: Job? = null private var locationFlow: Job? = null
// If we've ever read a valid region code from our device it will be here // If we've ever read a valid region code from our device it will be here
var curRegionValue = RadioConfigProtos.RegionCode.Unset_VALUE var curRegionValue = ConfigProtos.Config.LoRaConfig.RegionCode.Unset_VALUE
private fun getSenderName(packet: DataPacket?): String { private fun getSenderName(packet: DataPacket?): String {
val name = nodeDBbyID[packet?.from]?.user?.longName val name = nodeDBbyID[packet?.from]?.user?.longName
@ -350,7 +350,7 @@ class MeshService : Service(), Logging {
var myNodeInfo: MyNodeInfo? = null var myNodeInfo: MyNodeInfo? = null
private var radioConfig: RadioConfigProtos.RadioConfig? = null private var deviceConfig: ConfigProtos.Config? = null
private var channels = fixupChannelList(listOf()) private var channels = fixupChannelList(listOf())
@ -725,9 +725,9 @@ class MeshService : Service(), Logging {
// For the time being we only care about admin messages from our local node // For the time being we only care about admin messages from our local node
if (fromNodeNum == myNodeNum) { if (fromNodeNum == myNodeNum) {
when (a.variantCase) { when (a.variantCase) {
AdminProtos.AdminMessage.VariantCase.GET_RADIO_RESPONSE -> { AdminProtos.AdminMessage.VariantCase.GET_CONFIG_RESPONSE -> {
debug("Admin: received radioConfig") debug("Admin: received deviceConfig")
radioConfig = a.getRadioResponse deviceConfig = a.getConfigResponse
requestChannel(0) // Now start reading channels requestChannel(0) // Now start reading channels
} }
@ -983,7 +983,7 @@ class MeshService : Service(), Logging {
sleepTimeout = serviceScope.handledLaunch { sleepTimeout = serviceScope.handledLaunch {
try { try {
// If we have a valid timeout, wait that long (+30 seconds) otherwise, just wait 30 seconds // If we have a valid timeout, wait that long (+30 seconds) otherwise, just wait 30 seconds
val timeout = (radioConfig?.preferences?.lsSecs ?: 0) + 30 val timeout = (deviceConfig?.power?.lsSecs ?: 0) + 30
debug("Waiting for sleeping device, timeout=$timeout secs") debug("Waiting for sleeping device, timeout=$timeout secs")
delay(timeout * 1000L) delay(timeout * 1000L)
@ -1075,7 +1075,7 @@ class MeshService : Service(), Logging {
private fun onRadioConnectionState(state: RadioServiceConnectionState) { private fun onRadioConnectionState(state: RadioServiceConnectionState) {
// sleep now disabled by default on ESP32, permanent is true unless isPowerSaving enabled // sleep now disabled by default on ESP32, permanent is true unless isPowerSaving enabled
val lsEnabled = radioConfig?.preferences?.isPowerSaving ?: false val lsEnabled = deviceConfig?.power?.isPowerSaving ?: false
val connected = state.isConnected val connected = state.isConnected
val permanent = state.isPermanent || !lsEnabled val permanent = state.isPermanent || !lsEnabled
onConnectionChanged( onConnectionChanged(
@ -1105,7 +1105,7 @@ class MeshService : Service(), Logging {
MeshProtos.FromRadio.NODE_INFO_FIELD_NUMBER -> handleNodeInfo(proto.nodeInfo) MeshProtos.FromRadio.NODE_INFO_FIELD_NUMBER -> handleNodeInfo(proto.nodeInfo)
// MeshProtos.FromRadio.RADIO_FIELD_NUMBER -> handleRadioConfig(proto.radio) // MeshProtos.FromRadio.RADIO_FIELD_NUMBER -> handleDeviceConfig(proto.radio)
else -> errormsg("Unexpected FromRadio variant") else -> errormsg("Unexpected FromRadio variant")
} }
@ -1249,7 +1249,7 @@ class MeshService : Service(), Logging {
regenMyNodeInfo() regenMyNodeInfo()
// We'll need to get a new set of channels and settings now // We'll need to get a new set of channels and settings now
radioConfig = null deviceConfig = null
// prefill the channel array with null channels // prefill the channel array with null channels
channels = fixupChannelList(listOf<ChannelProtos.Channel>()) channels = fixupChannelList(listOf<ChannelProtos.Channel>())
@ -1272,21 +1272,21 @@ class MeshService : Service(), Logging {
private fun setRegionOnDevice() { private fun setRegionOnDevice() {
val curConfigRegion = val curConfigRegion =
radioConfig?.preferences?.region ?: RadioConfigProtos.RegionCode.Unset deviceConfig?.lora?.region ?: ConfigProtos.Config.LoRaConfig.RegionCode.Unset
if (curConfigRegion.number != curRegionValue && curRegionValue != RadioConfigProtos.RegionCode.Unset_VALUE) if (curConfigRegion.number != curRegionValue && curRegionValue != ConfigProtos.Config.LoRaConfig.RegionCode.Unset_VALUE)
if (deviceVersion >= minFirmwareVersion) { if (deviceVersion >= minFirmwareVersion) {
info("Telling device to upgrade region") info("Telling device to upgrade region")
// Tell the device to set the new region field (old devices will simply ignore this) // Tell the device to set the new region field (old devices will simply ignore this)
radioConfig?.let { currentConfig -> deviceConfig?.let { currentConfig ->
val newConfig = currentConfig.toBuilder() val newConfig = currentConfig.toBuilder()
val newPrefs = currentConfig.preferences.toBuilder() val newPrefs = currentConfig.lora.toBuilder()
newPrefs.regionValue = curRegionValue newPrefs.regionValue = curRegionValue
newConfig.preferences = newPrefs.build() newConfig.lora = newPrefs.build()
sendRadioConfig(newConfig.build()) sendDeviceConfig(newConfig.build())
} }
} else } else
warn("Device is too old to understand region changes") warn("Device is too old to understand region changes")
@ -1301,8 +1301,8 @@ class MeshService : Service(), Logging {
// Try to pull our region code from the new preferences field // Try to pull our region code from the new preferences field
// FIXME - do not check net - figuring out why board is rebooting // FIXME - do not check net - figuring out why board is rebooting
val curConfigRegion = val curConfigRegion =
radioConfig?.preferences?.region ?: RadioConfigProtos.RegionCode.Unset deviceConfig?.lora?.region ?: ConfigProtos.Config.LoRaConfig.RegionCode.Unset
if (curConfigRegion != RadioConfigProtos.RegionCode.Unset) { if (curConfigRegion != ConfigProtos.Config.LoRaConfig.RegionCode.Unset) {
info("Using device region $curConfigRegion (code ${curConfigRegion.number})") info("Using device region $curConfigRegion (code ${curConfigRegion.number})")
curRegionValue = curConfigRegion.number curRegionValue = curConfigRegion.number
} }
@ -1357,15 +1357,15 @@ class MeshService : Service(), Logging {
info("Device firmware is too old, faking config so firmware update can occur") info("Device firmware is too old, faking config so firmware update can occur")
onHasSettings() onHasSettings()
} else } else
requestRadioConfig() requestDeviceConfig()
} }
} else } else
warn("Ignoring stale config complete") warn("Ignoring stale config complete")
} }
private fun requestRadioConfig() { private fun requestDeviceConfig() {
sendToRadio(newMeshPacketTo(myNodeNum).buildAdminPacket(wantResponse = true) { sendToRadio(newMeshPacketTo(myNodeNum).buildAdminPacket(wantResponse = true) {
getRadioRequest = true getConfigRequest = AdminProtos.AdminMessage.ConfigType.DEVICE_CONFIG
}) })
} }
@ -1440,22 +1440,22 @@ class MeshService : Service(), Logging {
/** Send our current radio config to the device /** Send our current radio config to the device
*/ */
private fun sendRadioConfig(c: RadioConfigProtos.RadioConfig) { private fun sendDeviceConfig(c: ConfigProtos.Config) {
// send the packet into the mesh // send the packet into the mesh
sendToRadio(newMeshPacketTo(myNodeNum).buildAdminPacket { sendToRadio(newMeshPacketTo(myNodeNum).buildAdminPacket {
setRadio = c setConfig = c
}) })
// Update our cached copy // Update our cached copy
this@MeshService.radioConfig = c this@MeshService.deviceConfig = c
} }
/** Set our radio config /** Set our radio config
*/ */
private fun setRadioConfig(payload: ByteArray) { private fun setDeviceConfig(payload: ByteArray) {
val parsed = RadioConfigProtos.RadioConfig.parseFrom(payload) val parsed = ConfigProtos.Config.parseFrom(payload)
sendRadioConfig(parsed) sendDeviceConfig(parsed)
} }
/** /**
@ -1690,13 +1690,13 @@ class MeshService : Service(), Logging {
} }
} }
override fun getRadioConfig(): ByteArray = toRemoteExceptions { override fun getDeviceConfig(): ByteArray = toRemoteExceptions {
this@MeshService.radioConfig?.toByteArray() this@MeshService.deviceConfig?.toByteArray()
?: throw NoRadioConfigException() ?: throw NoDeviceConfigException()
} }
override fun setRadioConfig(payload: ByteArray) = toRemoteExceptions { override fun setDeviceConfig(payload: ByteArray) = toRemoteExceptions {
this@MeshService.setRadioConfig(payload) this@MeshService.setDeviceConfig(payload)
} }
override fun getChannels(): ByteArray = toRemoteExceptions { override fun getChannels(): ByteArray = toRemoteExceptions {

View file

@ -3,7 +3,7 @@ package com.geeksville.mesh.service
import com.geeksville.mesh.DataPacket import com.geeksville.mesh.DataPacket
import com.geeksville.mesh.MyNodeInfo import com.geeksville.mesh.MyNodeInfo
import com.geeksville.mesh.NodeInfo import com.geeksville.mesh.NodeInfo
import com.geeksville.mesh.RadioConfigProtos import com.geeksville.mesh.ConfigProtos
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
/// Our saved preferences as stored on disk /// Our saved preferences as stored on disk
@ -12,7 +12,7 @@ data class MeshServiceSettingsData(
val nodeDB: Array<NodeInfo>, val nodeDB: Array<NodeInfo>,
val myInfo: MyNodeInfo, val myInfo: MyNodeInfo,
val messages: Array<DataPacket>, val messages: Array<DataPacket>,
val regionCode: Int = RadioConfigProtos.RegionCode.Unset_VALUE val regionCode: Int = ConfigProtos.Config.LoRaConfig.RegionCode.Unset_VALUE
) { ) {
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true

View file

@ -37,18 +37,18 @@ class AdvancedSettingsFragment : ScreenFragment("Advanced Settings"), Logging {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
model.radioConfig.observe(viewLifecycleOwner) { model.deviceConfig.observe(viewLifecycleOwner) {
binding.positionBroadcastPeriodEditText.setText(model.positionBroadcastSecs.toString()) binding.positionBroadcastPeriodEditText.setText(model.positionBroadcastSecs.toString())
binding.lsSleepEditText.setText(model.lsSleepSecs.toString()) binding.lsSleepEditText.setText(model.lsSleepSecs.toString())
binding.positionBroadcastPeriodView.isEnabled = !model.locationShareDisabled binding.positionBroadcastPeriodView.isEnabled = !model.gpsDisabled
binding.positionBroadcastSwitch.isChecked = !model.locationShareDisabled binding.positionBroadcastSwitch.isChecked = !model.gpsDisabled
binding.lsSleepView.isEnabled = model.isPowerSaving ?: false binding.lsSleepView.isEnabled = model.isPowerSaving ?: false
binding.lsSleepSwitch.isChecked = model.isPowerSaving ?: false binding.lsSleepSwitch.isChecked = model.isPowerSaving ?: false
} }
model.connectionState.observe(viewLifecycleOwner) { connectionState -> model.connectionState.observe(viewLifecycleOwner) { connectionState ->
val connected = connectionState == MeshService.ConnectionState.CONNECTED val connected = connectionState == MeshService.ConnectionState.CONNECTED
binding.positionBroadcastPeriodView.isEnabled = connected && !model.locationShareDisabled binding.positionBroadcastPeriodView.isEnabled = connected && !model.gpsDisabled
binding.lsSleepView.isEnabled = connected && model.isPowerSaving ?: false binding.lsSleepView.isEnabled = connected && model.isPowerSaving ?: false
binding.positionBroadcastSwitch.isEnabled = connected binding.positionBroadcastSwitch.isEnabled = connected
binding.lsSleepSwitch.isEnabled = connected binding.lsSleepSwitch.isEnabled = connected
@ -58,7 +58,7 @@ class AdvancedSettingsFragment : ScreenFragment("Advanced Settings"), Logging {
val textEdit = binding.positionBroadcastPeriodEditText val textEdit = binding.positionBroadcastPeriodEditText
val n = textEdit.text.toString().toIntOrNull() val n = textEdit.text.toString().toIntOrNull()
val minBroadcastPeriodSecs = val minBroadcastPeriodSecs =
ChannelOption.fromConfig(model.primaryChannel?.modemConfig)?.minBroadcastPeriodSecs ChannelOption.fromConfig(model.deviceConfig.value?.lora?.modemPreset)?.minBroadcastPeriodSecs
?: ChannelOption.defaultMinBroadcastPeriod ?: ChannelOption.defaultMinBroadcastPeriod
if (n != null && n < MAX_INT_DEVICE && (n == 0 || n >= minBroadcastPeriodSecs)) { if (n != null && n < MAX_INT_DEVICE && (n == 0 || n >= minBroadcastPeriodSecs)) {
@ -81,7 +81,7 @@ class AdvancedSettingsFragment : ScreenFragment("Advanced Settings"), Logging {
binding.positionBroadcastSwitch.setOnCheckedChangeListener { view, isChecked -> binding.positionBroadcastSwitch.setOnCheckedChangeListener { view, isChecked ->
if (view.isPressed) { if (view.isPressed) {
model.locationShareDisabled = !isChecked model.gpsDisabled = !isChecked
debug("User changed locationShare to $isChecked") debug("User changed locationShare to $isChecked")
} }
} }

View file

@ -23,6 +23,7 @@ import com.geeksville.android.hideKeyboard
import com.geeksville.android.isGooglePlayAvailable import com.geeksville.android.isGooglePlayAvailable
import com.geeksville.mesh.AppOnlyProtos import com.geeksville.mesh.AppOnlyProtos
import com.geeksville.mesh.ChannelProtos import com.geeksville.mesh.ChannelProtos
import com.geeksville.mesh.ConfigProtos
import com.geeksville.mesh.R import com.geeksville.mesh.R
import com.geeksville.mesh.android.hasCameraPermission import com.geeksville.mesh.android.hasCameraPermission
import com.geeksville.mesh.databinding.ChannelFragmentBinding import com.geeksville.mesh.databinding.ChannelFragmentBinding
@ -107,15 +108,15 @@ class ChannelFragment : ScreenFragment("Channel"), Logging {
binding.channelNameEdit.setText(channel.humanName) binding.channelNameEdit.setText(channel.humanName)
// For now, we only let the user edit/save channels while the radio is awake - because the service // For now, we only let the user edit/save channels while the radio is awake - because the service
// doesn't cache radioconfig writes. // doesn't cache DeviceConfig writes.
binding.editableCheckbox.isEnabled = connected binding.editableCheckbox.isEnabled = connected
val bitmap = channels.qrCode val bitmap = channels.qrCode
if (bitmap != null) if (bitmap != null)
binding.qrView.setImageBitmap(bitmap) binding.qrView.setImageBitmap(bitmap)
val modemConfig = channel.modemConfig val modemPreset = model.deviceConfig.value?.lora?.modemPreset
val channelOption = ChannelOption.fromConfig(modemConfig) val channelOption = ChannelOption.fromConfig(modemPreset)
binding.filledExposedDropdown.setText( binding.filledExposedDropdown.setText(
getString( getString(
channelOption?.configRes ?: R.string.modem_config_unrecognized channelOption?.configRes ?: R.string.modem_config_unrecognized
@ -128,12 +129,12 @@ class ChannelFragment : ScreenFragment("Channel"), Logging {
binding.editableCheckbox.isEnabled = false binding.editableCheckbox.isEnabled = false
} }
val modemConfigs = ChannelOption.values() val modemPresets = ChannelOption.values()
val modemConfigList = modemConfigs.map { getString(it.configRes) } val modemPresetList = modemPresets.map { getString(it.configRes) }
val adapter = ArrayAdapter( val adapter = ArrayAdapter(
requireContext(), requireContext(),
R.layout.dropdown_menu_popup_item, R.layout.dropdown_menu_popup_item,
modemConfigList modemPresetList
) )
binding.filledExposedDropdown.setAdapter(adapter) binding.filledExposedDropdown.setAdapter(adapter)
@ -174,10 +175,10 @@ class ChannelFragment : ScreenFragment("Channel"), Logging {
private fun installSettings(newChannel: ChannelProtos.ChannelSettings) { private fun installSettings(newChannel: ChannelProtos.ChannelSettings) {
val newSet = val newSet =
ChannelSet(AppOnlyProtos.ChannelSet.newBuilder().addSettings(newChannel).build()) ChannelSet(AppOnlyProtos.ChannelSet.newBuilder().addSettings(newChannel).build())
// Try to change the radio, if it fails, tell the user why and throw away their redits // Try to change the radio, if it fails, tell the user why and throw away their edits
try { try {
model.setChannels(newSet) model.setChannels(newSet)
// Since we are writing to radioconfig, that will trigger the rest of the GUI update (QR code etc) // Since we are writing to DeviceConfig, that will trigger the rest of the GUI update (QR code etc)
} catch (ex: RemoteException) { } catch (ex: RemoteException) {
errormsg("ignoring channel problem", ex) errormsg("ignoring channel problem", ex)
@ -282,7 +283,7 @@ class ChannelFragment : ScreenFragment("Channel"), Logging {
if (checked) { if (checked) {
// User just unlocked for editing - remove the # goo around the channel name // User just unlocked for editing - remove the # goo around the channel name
model.channels.value?.primaryChannel?.let { ch -> model.channels.value?.primaryChannel?.let { ch ->
// Note: We are careful to show the emptystring here if the user was on a default channel, so the user knows they should it for any changes // Note: We are careful to show the empty string here if the user was on a default channel, so the user knows they should it for any changes
originalName = ch.settings.name originalName = ch.settings.name
binding.channelNameEdit.setText(originalName) binding.channelNameEdit.setText(originalName)
} }
@ -303,18 +304,20 @@ class ChannelFragment : ScreenFragment("Channel"), Logging {
// Find the new modem config // Find the new modem config
val selectedChannelOptionString = val selectedChannelOptionString =
binding.filledExposedDropdown.editableText.toString() binding.filledExposedDropdown.editableText.toString()
var modemConfig = getModemConfig(selectedChannelOptionString) var modemPreset = getModemPreset(selectedChannelOptionString)
if (modemConfig == ChannelProtos.ChannelSettings.ModemConfig.UNRECOGNIZED) // Huh? didn't find it - keep same // if (modemPreset == ConfigProtos.Config.LoRaConfig.ModemPreset.UNRECOGNIZED) // Huh? didn't find it - keep same
modemConfig = oldPrimary.settings.modemConfig // modemPreset = oldPrimary.settings.modemConfig -> TODO add from LoraConfig.ModemPreset?
// Generate a new AES256 key if the user changes channel name or the name is non-default and the settings changed // Generate a new AES256 key if the user changes channel name or the name is non-default and the settings changed
if (newName != originalName || (newName.isNotEmpty() && modemConfig != oldPrimary.settings.modemConfig)) { // if (newName != originalName || (newName.isNotEmpty() && modemConfig != oldPrimary.settings.modemConfig)) {
if (newName != originalName || newName.isNotEmpty()) {
// Install a new customized channel // Install a new customized channel
debug("ASSIGNING NEW AES256 KEY") debug("ASSIGNING NEW AES256 KEY")
val random = SecureRandom() val random = SecureRandom()
val bytes = ByteArray(32) val bytes = ByteArray(32)
random.nextBytes(bytes) random.nextBytes(bytes)
newSettings.name = newName newSettings.name = newName.take(11) // proto max_size:12
newSettings.psk = ByteString.copyFrom(bytes) newSettings.psk = ByteString.copyFrom(bytes)
} else { } else {
debug("Switching back to default channel") debug("Switching back to default channel")
@ -322,7 +325,7 @@ class ChannelFragment : ScreenFragment("Channel"), Logging {
} }
// No matter what apply the speed selection from the user // No matter what apply the speed selection from the user
newSettings.modemConfig = modemConfig // newSettings.modemConfig = modemPreset -> TODO add from LoraConfig.ModemPreset?
installSettings(newSettings.build()) installSettings(newSettings.build())
} }
@ -348,12 +351,12 @@ class ChannelFragment : ScreenFragment("Channel"), Logging {
} }
} }
private fun getModemConfig(selectedChannelOptionString: String): ChannelProtos.ChannelSettings.ModemConfig { private fun getModemPreset(selectedChannelOptionString: String): ConfigProtos.Config.LoRaConfig.ModemPreset {
for (item in ChannelOption.values()) { for (item in ChannelOption.values()) {
if (getString(item.configRes) == selectedChannelOptionString) if (getString(item.configRes) == selectedChannelOptionString)
return item.modemConfig return item.modemPreset
} }
return ChannelProtos.ChannelSettings.ModemConfig.UNRECOGNIZED return ConfigProtos.Config.LoRaConfig.ModemPreset.UNRECOGNIZED
} }
private val requestPermissionAndScanLauncher = private val requestPermissionAndScanLauncher =

View file

@ -87,7 +87,7 @@ class ContactsFragment : ScreenFragment("Messages"), Logging {
val nodes = model.nodeDB.nodes.value!! val nodes = model.nodeDB.nodes.value!!
val node = nodes[if (isLocal) contact.to else contact.from] val node = nodes[if (isLocal) contact.to else contact.from]
//grab channel names from RadioConfig //grab channel names from DeviceConfig
val channels = model.channels.value val channels = model.channels.value
val primaryChannel = channels?.primaryChannel val primaryChannel = channels?.primaryChannel

View file

@ -296,9 +296,6 @@ class MessagesFragment : Fragment(), Logging {
val connected = connectionState == MeshService.ConnectionState.CONNECTED val connected = connectionState == MeshService.ConnectionState.CONNECTED
binding.textInputLayout.isEnabled = connected binding.textInputLayout.isEnabled = connected
binding.sendButton.isEnabled = connected binding.sendButton.isEnabled = connected
// Just being connected is enough to allow sending texts I think
// && model.nodeDB.myId.value != null && model.radioConfig.value != null
} }
} }

View file

@ -32,7 +32,7 @@ import com.geeksville.android.hideKeyboard
import com.geeksville.android.isGooglePlayAvailable import com.geeksville.android.isGooglePlayAvailable
import com.geeksville.mesh.MainActivity import com.geeksville.mesh.MainActivity
import com.geeksville.mesh.R import com.geeksville.mesh.R
import com.geeksville.mesh.RadioConfigProtos import com.geeksville.mesh.ConfigProtos
import com.geeksville.mesh.android.* import com.geeksville.mesh.android.*
import com.geeksville.mesh.databinding.SettingsFragmentBinding import com.geeksville.mesh.databinding.SettingsFragmentBinding
import com.geeksville.mesh.model.BluetoothViewModel import com.geeksville.mesh.model.BluetoothViewModel
@ -659,7 +659,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
// update the region selection from the device // update the region selection from the device
val region = model.region val region = model.region
val spinner = binding.regionSpinner val spinner = binding.regionSpinner
val unsetIndex = regions.indexOf(RadioConfigProtos.RegionCode.Unset.name) val unsetIndex = regions.indexOf(ConfigProtos.Config.LoRaConfig.RegionCode.Unset.name)
spinner.onItemSelectedListener = null spinner.onItemSelectedListener = null
debug("current region is $region") debug("current region is $region")
@ -673,7 +673,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
spinner.isEnabled = true spinner.isEnabled = true
// If actively connected possibly let the user update firmware // If actively connected possibly let the user update firmware
refreshUpdateButton(region != RadioConfigProtos.RegionCode.Unset) refreshUpdateButton(region != ConfigProtos.Config.LoRaConfig.RegionCode.Unset)
// Update the status string (highest priority messages first) // Update the status string (highest priority messages first)
val info = model.myNodeInfo.value val info = model.myNodeInfo.value
@ -683,7 +683,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
(permissionsWarning != null) -> (permissionsWarning != null) ->
statusText.text = permissionsWarning statusText.text = permissionsWarning
region == RadioConfigProtos.RegionCode.Unset -> region == ConfigProtos.Config.LoRaConfig.RegionCode.Unset ->
statusText.text = getString(R.string.must_set_region) statusText.text = getString(R.string.must_set_region)
connected == MeshService.ConnectionState.CONNECTED -> { connected == MeshService.ConnectionState.CONNECTED -> {
@ -705,7 +705,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
id: Long id: Long
) { ) {
val item = parent.getItemAtPosition(position) as String? val item = parent.getItemAtPosition(position) as String?
val asProto = item!!.let { RadioConfigProtos.RegionCode.valueOf(it) } val asProto = item!!.let { ConfigProtos.Config.LoRaConfig.RegionCode.valueOf(it) }
exceptionToSnackbar(requireView()) { exceptionToSnackbar(requireView()) {
model.region = asProto model.region = asProto
} }
@ -718,8 +718,8 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
} }
/// the sorted list of region names like arrayOf("US", "CN", "EU488") /// the sorted list of region names like arrayOf("US", "CN", "EU488")
private val regions = RadioConfigProtos.RegionCode.values().filter { private val regions = ConfigProtos.Config.LoRaConfig.RegionCode.values().filter {
it != RadioConfigProtos.RegionCode.UNRECOGNIZED it != ConfigProtos.Config.LoRaConfig.RegionCode.UNRECOGNIZED
}.map { }.map {
it.name it.name
}.sorted() }.sorted()
@ -746,10 +746,10 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
updateDevicesButtons(scanModel.devices.value) updateDevicesButtons(scanModel.devices.value)
} }
model.radioConfig.observe(viewLifecycleOwner) { model.deviceConfig.observe(viewLifecycleOwner) {
binding.provideLocationCheckbox.isEnabled = binding.provideLocationCheckbox.isEnabled =
isGooglePlayAvailable(requireContext()) && !model.locationShareDisabled isGooglePlayAvailable(requireContext()) && !model.gpsDisabled
if (model.locationShareDisabled) { if (model.gpsDisabled) {
model.provideLocation.value = false model.provideLocation.value = false
binding.provideLocationCheckbox.isChecked = false binding.provideLocationCheckbox.isChecked = false
} }

View file

@ -13,6 +13,8 @@
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:layout_marginEnd="64dp" android:layout_marginEnd="64dp"
android:hint="@string/channel_name" android:hint="@string/channel_name"
app:counterEnabled="true"
app:counterMaxLength="11"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
@ -23,7 +25,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:digits="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890- " android:digits="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890- "
android:imeOptions="actionDone" android:imeOptions="actionDone"
android:maxLength="15"
android:singleLine="true" android:singleLine="true"
android:text="@string/unset" /> android:text="@string/unset" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>