mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
refactor: introduce NodeEntity protobuf-based database entity (#1250)
This commit is contained in:
parent
2433cbc00a
commit
396195a1b8
12 changed files with 1029 additions and 151 deletions
|
|
@ -0,0 +1,642 @@
|
|||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 10,
|
||||
"identityHash": "d3d6934fdce32237a4c8f2cb24455a59",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "MyNodeInfo",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`myNodeNum` INTEGER NOT NULL, `hasGPS` INTEGER NOT NULL, `model` TEXT, `firmwareVersion` TEXT, `couldUpdate` INTEGER NOT NULL, `shouldUpdate` INTEGER NOT NULL, `currentPacketId` INTEGER NOT NULL, `messageTimeoutMsec` INTEGER NOT NULL, `minAppVersion` INTEGER NOT NULL, `maxChannels` INTEGER NOT NULL, `hasWifi` INTEGER NOT NULL, `channelUtilization` REAL NOT NULL, `airUtilTx` REAL NOT NULL, PRIMARY KEY(`myNodeNum`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "myNodeNum",
|
||||
"columnName": "myNodeNum",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasGPS",
|
||||
"columnName": "hasGPS",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "model",
|
||||
"columnName": "model",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "firmwareVersion",
|
||||
"columnName": "firmwareVersion",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "couldUpdate",
|
||||
"columnName": "couldUpdate",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "shouldUpdate",
|
||||
"columnName": "shouldUpdate",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "currentPacketId",
|
||||
"columnName": "currentPacketId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "messageTimeoutMsec",
|
||||
"columnName": "messageTimeoutMsec",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "minAppVersion",
|
||||
"columnName": "minAppVersion",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "maxChannels",
|
||||
"columnName": "maxChannels",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasWifi",
|
||||
"columnName": "hasWifi",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "channelUtilization",
|
||||
"columnName": "channelUtilization",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "airUtilTx",
|
||||
"columnName": "airUtilTx",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"myNodeNum"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "NodeInfo",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`num` INTEGER NOT NULL, `snr` REAL NOT NULL, `rssi` INTEGER NOT NULL, `lastHeard` INTEGER NOT NULL, `channel` INTEGER NOT NULL, `hopsAway` INTEGER NOT NULL DEFAULT 0, `user_id` TEXT, `user_longName` TEXT, `user_shortName` TEXT, `user_hwModel` TEXT, `user_isLicensed` INTEGER, `user_role` INTEGER DEFAULT 0, `position_latitude` REAL, `position_longitude` REAL, `position_altitude` INTEGER, `position_time` INTEGER, `position_satellitesInView` INTEGER, `position_groundSpeed` INTEGER, `position_groundTrack` INTEGER, `position_precisionBits` INTEGER, `devMetrics_time` INTEGER, `devMetrics_batteryLevel` INTEGER, `devMetrics_voltage` REAL, `devMetrics_channelUtilization` REAL, `devMetrics_airUtilTx` REAL, `devMetrics_uptimeSeconds` INTEGER, `envMetrics_time` INTEGER, `envMetrics_temperature` REAL, `envMetrics_relativeHumidity` REAL, `envMetrics_barometricPressure` REAL, `envMetrics_gasResistance` REAL, `envMetrics_voltage` REAL, `envMetrics_current` REAL, `envMetrics_iaq` INTEGER, PRIMARY KEY(`num`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "num",
|
||||
"columnName": "num",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "snr",
|
||||
"columnName": "snr",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "rssi",
|
||||
"columnName": "rssi",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastHeard",
|
||||
"columnName": "lastHeard",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "channel",
|
||||
"columnName": "channel",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hopsAway",
|
||||
"columnName": "hopsAway",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "user.id",
|
||||
"columnName": "user_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "user.longName",
|
||||
"columnName": "user_longName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "user.shortName",
|
||||
"columnName": "user_shortName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "user.hwModel",
|
||||
"columnName": "user_hwModel",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "user.isLicensed",
|
||||
"columnName": "user_isLicensed",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "user.role",
|
||||
"columnName": "user_role",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "position.latitude",
|
||||
"columnName": "position_latitude",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "position.longitude",
|
||||
"columnName": "position_longitude",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "position.altitude",
|
||||
"columnName": "position_altitude",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "position.time",
|
||||
"columnName": "position_time",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "position.satellitesInView",
|
||||
"columnName": "position_satellitesInView",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "position.groundSpeed",
|
||||
"columnName": "position_groundSpeed",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "position.groundTrack",
|
||||
"columnName": "position_groundTrack",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "position.precisionBits",
|
||||
"columnName": "position_precisionBits",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "deviceMetrics.time",
|
||||
"columnName": "devMetrics_time",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "deviceMetrics.batteryLevel",
|
||||
"columnName": "devMetrics_batteryLevel",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "deviceMetrics.voltage",
|
||||
"columnName": "devMetrics_voltage",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "deviceMetrics.channelUtilization",
|
||||
"columnName": "devMetrics_channelUtilization",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "deviceMetrics.airUtilTx",
|
||||
"columnName": "devMetrics_airUtilTx",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "deviceMetrics.uptimeSeconds",
|
||||
"columnName": "devMetrics_uptimeSeconds",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "environmentMetrics.time",
|
||||
"columnName": "envMetrics_time",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "environmentMetrics.temperature",
|
||||
"columnName": "envMetrics_temperature",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "environmentMetrics.relativeHumidity",
|
||||
"columnName": "envMetrics_relativeHumidity",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "environmentMetrics.barometricPressure",
|
||||
"columnName": "envMetrics_barometricPressure",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "environmentMetrics.gasResistance",
|
||||
"columnName": "envMetrics_gasResistance",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "environmentMetrics.voltage",
|
||||
"columnName": "envMetrics_voltage",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "environmentMetrics.current",
|
||||
"columnName": "envMetrics_current",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "environmentMetrics.iaq",
|
||||
"columnName": "envMetrics_iaq",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"num"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "nodes",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`num` INTEGER NOT NULL, `user` BLOB NOT NULL, `long_name` TEXT, `short_name` TEXT, `position` BLOB NOT NULL, `latitude` REAL NOT NULL, `longitude` REAL NOT NULL, `snr` REAL NOT NULL, `rssi` INTEGER NOT NULL, `last_heard` INTEGER NOT NULL, `device_metrics` BLOB NOT NULL, `channel` INTEGER NOT NULL, `via_mqtt` INTEGER NOT NULL, `hops_away` INTEGER NOT NULL, `is_favorite` INTEGER NOT NULL, `environment_metrics` BLOB NOT NULL, `power_metrics` BLOB NOT NULL, `paxcounter` BLOB NOT NULL, PRIMARY KEY(`num`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "num",
|
||||
"columnName": "num",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "user",
|
||||
"columnName": "user",
|
||||
"affinity": "BLOB",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "longName",
|
||||
"columnName": "long_name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "shortName",
|
||||
"columnName": "short_name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "position",
|
||||
"columnName": "position",
|
||||
"affinity": "BLOB",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "latitude",
|
||||
"columnName": "latitude",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "longitude",
|
||||
"columnName": "longitude",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "snr",
|
||||
"columnName": "snr",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "rssi",
|
||||
"columnName": "rssi",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastHeard",
|
||||
"columnName": "last_heard",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "deviceTelemetry",
|
||||
"columnName": "device_metrics",
|
||||
"affinity": "BLOB",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "channel",
|
||||
"columnName": "channel",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "viaMqtt",
|
||||
"columnName": "via_mqtt",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hopsAway",
|
||||
"columnName": "hops_away",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isFavorite",
|
||||
"columnName": "is_favorite",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "environmentTelemetry",
|
||||
"columnName": "environment_metrics",
|
||||
"affinity": "BLOB",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "powerTelemetry",
|
||||
"columnName": "power_metrics",
|
||||
"affinity": "BLOB",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "paxcounter",
|
||||
"columnName": "paxcounter",
|
||||
"affinity": "BLOB",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"num"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "packet",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `myNodeNum` INTEGER NOT NULL DEFAULT 0, `port_num` INTEGER NOT NULL, `contact_key` TEXT NOT NULL, `received_time` INTEGER NOT NULL, `read` INTEGER NOT NULL DEFAULT 1, `data` TEXT NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "uuid",
|
||||
"columnName": "uuid",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "myNodeNum",
|
||||
"columnName": "myNodeNum",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "port_num",
|
||||
"columnName": "port_num",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "contact_key",
|
||||
"columnName": "contact_key",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "received_time",
|
||||
"columnName": "received_time",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "read",
|
||||
"columnName": "read",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "1"
|
||||
},
|
||||
{
|
||||
"fieldPath": "data",
|
||||
"columnName": "data",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"uuid"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_packet_myNodeNum",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"myNodeNum"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_packet_myNodeNum` ON `${TABLE_NAME}` (`myNodeNum`)"
|
||||
},
|
||||
{
|
||||
"name": "index_packet_port_num",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"port_num"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_packet_port_num` ON `${TABLE_NAME}` (`port_num`)"
|
||||
},
|
||||
{
|
||||
"name": "index_packet_contact_key",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"contact_key"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_packet_contact_key` ON `${TABLE_NAME}` (`contact_key`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "contact_settings",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`contact_key` TEXT NOT NULL, `muteUntil` INTEGER NOT NULL, PRIMARY KEY(`contact_key`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "contact_key",
|
||||
"columnName": "contact_key",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "muteUntil",
|
||||
"columnName": "muteUntil",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"contact_key"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "log",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `type` TEXT NOT NULL, `received_date` INTEGER NOT NULL, `message` TEXT NOT NULL, PRIMARY KEY(`uuid`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "uuid",
|
||||
"columnName": "uuid",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "message_type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "received_date",
|
||||
"columnName": "received_date",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "raw_message",
|
||||
"columnName": "message",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"uuid"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "quick_chat",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `message` TEXT NOT NULL, `mode` TEXT NOT NULL, `position` INTEGER NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "uuid",
|
||||
"columnName": "uuid",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "message",
|
||||
"columnName": "message",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "mode",
|
||||
"columnName": "mode",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "position",
|
||||
"columnName": "position",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"uuid"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd3d6934fdce32237a4c8f2cb24455a59')"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
|
|||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.geeksville.mesh.database.MeshtasticDatabase
|
||||
import com.geeksville.mesh.database.dao.NodeInfoDao
|
||||
import com.geeksville.mesh.database.entity.NodeEntity
|
||||
import com.geeksville.mesh.model.NodeSortOption
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
|
@ -17,24 +18,25 @@ import org.junit.Test
|
|||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class NodeDBTest {
|
||||
class NodeInfoDaoTest {
|
||||
private lateinit var database: MeshtasticDatabase
|
||||
private lateinit var nodeInfoDao: NodeInfoDao
|
||||
|
||||
private val ourNodeInfo = NodeInfo(
|
||||
private val ourNode = NodeEntity(
|
||||
num = 8,
|
||||
user = MeshUser(
|
||||
"+16508765308".format(8),
|
||||
"Kevin Mester",
|
||||
"KLO",
|
||||
MeshProtos.HardwareModel.ANDROID_SIM,
|
||||
false
|
||||
),
|
||||
position = Position(30.267153, -97.743057, 35, 123), // Austin
|
||||
user = user {
|
||||
id = "+16508765308".format(8)
|
||||
longName = "Kevin Mester"
|
||||
shortName = "KLO"
|
||||
hwModel = MeshProtos.HardwareModel.ANDROID_SIM
|
||||
isLicensed = false
|
||||
},
|
||||
longName = "Kevin Mester", shortName = "KLO",
|
||||
latitude = 30.267153, longitude = -97.743057 // Austin
|
||||
)
|
||||
|
||||
private val myNodeInfo: MyNodeInfo = MyNodeInfo(
|
||||
myNodeNum = ourNodeInfo.num,
|
||||
myNodeNum = ourNode.num,
|
||||
hasGPS = false,
|
||||
model = null,
|
||||
firmwareVersion = null,
|
||||
|
|
@ -50,28 +52,30 @@ class NodeDBTest {
|
|||
)
|
||||
|
||||
private val testPositions = arrayOf(
|
||||
Position(32.776665, -96.796989, 35, 123), // Dallas
|
||||
Position(32.960758, -96.733521, 35, 456), // Richardson
|
||||
Position(32.912901, -96.781776, 35, 789), // North Dallas
|
||||
Position(29.760427, -95.369804, 35, 123), // Houston
|
||||
Position(33.748997, -84.387985, 35, 456), // Atlanta
|
||||
Position(34.052235, -118.243683, 35, 789), // Los Angeles
|
||||
Position(40.712776, -74.005974, 35, 123), // New York City
|
||||
Position(41.878113, -87.629799, 35, 456), // Chicago
|
||||
Position(39.952583, -75.165222, 35, 789), // Philadelphia
|
||||
0.0 to 0.0,
|
||||
32.776665 to -96.796989, // Dallas
|
||||
32.960758 to -96.733521, // Richardson
|
||||
32.912901 to -96.781776, // North Dallas
|
||||
29.760427 to -95.369804, // Houston
|
||||
33.748997 to -84.387985, // Atlanta
|
||||
34.052235 to -118.243683, // Los Angeles
|
||||
40.712776 to -74.005974, // New York City
|
||||
41.878113 to -87.629799, // Chicago
|
||||
39.952583 to -75.165222, // Philadelphia
|
||||
)
|
||||
|
||||
private val testNodes = listOf(ourNodeInfo) + testPositions.mapIndexed { index, it ->
|
||||
NodeInfo(
|
||||
private val testNodes = listOf(ourNode) + testPositions.mapIndexed { index, pos ->
|
||||
NodeEntity(
|
||||
num = 9 + index,
|
||||
user = MeshUser(
|
||||
"+165087653%02d".format(9 + index),
|
||||
"Kevin Mester$index",
|
||||
"KM$index",
|
||||
if (index == 2) MeshProtos.HardwareModel.UNSET else MeshProtos.HardwareModel.ANDROID_SIM,
|
||||
false
|
||||
),
|
||||
position = it,
|
||||
user = user {
|
||||
id = "+165087653%02d".format(9 + index)
|
||||
longName = "Kevin Mester$index"
|
||||
shortName = "KM$index"
|
||||
hwModel = MeshProtos.HardwareModel.ANDROID_SIM
|
||||
isLicensed = false
|
||||
},
|
||||
longName = "Kevin Mester$index", shortName = if (index == 2) null else "KM$index",
|
||||
latitude = pos.first, longitude = pos.second,
|
||||
lastHeard = 9 + index,
|
||||
)
|
||||
}
|
||||
|
|
@ -95,7 +99,7 @@ class NodeDBTest {
|
|||
|
||||
/**
|
||||
* Retrieves a list of nodes based on [sort], [filter] and [includeUnknown] parameters.
|
||||
* The list excludes [ourNodeInfo] (our NodeInfo) to ensure consistency in the results.
|
||||
* The list excludes [ourNode] to ensure consistency in the results.
|
||||
*/
|
||||
private suspend fun getNodes(
|
||||
sort: NodeSortOption = NodeSortOption.LAST_HEARD,
|
||||
|
|
@ -105,19 +109,18 @@ class NodeDBTest {
|
|||
sort = sort.sqlValue,
|
||||
filter = filter,
|
||||
includeUnknown = includeUnknown,
|
||||
unknownHwModel = MeshProtos.HardwareModel.UNSET
|
||||
).first().filter { it != ourNodeInfo }
|
||||
).first().filter { it != ourNode }
|
||||
|
||||
@Test // node list size
|
||||
fun testNodeListSize() = runBlocking {
|
||||
val nodes = nodeInfoDao.nodeDBbyNum().first()
|
||||
assertEquals(10, nodes.size)
|
||||
assertEquals(11, nodes.size)
|
||||
}
|
||||
|
||||
@Test // nodeDBbyNum() re-orders our node at the top of the list
|
||||
fun testOurNodeInfoIsFirst() = runBlocking {
|
||||
val nodes = nodeInfoDao.nodeDBbyNum().first()
|
||||
assertEquals(ourNodeInfo, nodes.values.first())
|
||||
assertEquals(ourNode, nodes.values.first())
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -130,14 +133,14 @@ class NodeDBTest {
|
|||
@Test
|
||||
fun testSortByAlpha() = runBlocking {
|
||||
val nodes = getNodes(sort = NodeSortOption.ALPHABETICAL)
|
||||
val sortedNodes = nodes.sortedBy { it.user?.longName?.uppercase() }
|
||||
val sortedNodes = nodes.sortedBy { it.user.longName.uppercase() }
|
||||
assertEquals(sortedNodes, nodes)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSortByDistance() = runBlocking {
|
||||
val nodes = getNodes(sort = NodeSortOption.DISTANCE)
|
||||
val sortedNodes = nodes.sortedBy { it.distance(ourNodeInfo) }
|
||||
val sortedNodes = nodes.sortedBy { it.distance(ourNode) }
|
||||
assertEquals(sortedNodes, nodes)
|
||||
}
|
||||
|
||||
|
|
@ -151,7 +154,7 @@ class NodeDBTest {
|
|||
@Test
|
||||
fun testSortByViaMqtt() = runBlocking {
|
||||
val nodes = getNodes(sort = NodeSortOption.VIA_MQTT)
|
||||
val sortedNodes = nodes.sortedBy { it.user?.longName?.contains("(MQTT)") == true }
|
||||
val sortedNodes = nodes.sortedBy { it.user.longName.contains("(MQTT)") }
|
||||
assertEquals(sortedNodes, nodes)
|
||||
}
|
||||
|
||||
|
|
@ -159,7 +162,7 @@ class NodeDBTest {
|
|||
fun testIncludeUnknownIsFalse() = runBlocking {
|
||||
val nodes = getNodes(includeUnknown = false)
|
||||
val containsUnsetNode = nodes.any { node ->
|
||||
node.user?.hwModel == MeshProtos.HardwareModel.UNSET
|
||||
node.user.hwModel == MeshProtos.HardwareModel.UNSET
|
||||
}
|
||||
assertFalse(containsUnsetNode)
|
||||
}
|
||||
|
|
@ -167,9 +170,7 @@ class NodeDBTest {
|
|||
@Test
|
||||
fun testIncludeUnknownIsTrue() = runBlocking {
|
||||
val nodes = getNodes(includeUnknown = true)
|
||||
val containsUnsetNode = nodes.any { node ->
|
||||
node.user?.hwModel == MeshProtos.HardwareModel.UNSET
|
||||
}
|
||||
val containsUnsetNode = nodes.any { it.shortName == null }
|
||||
assertTrue(containsUnsetNode)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,14 @@ package com.geeksville.mesh.database
|
|||
|
||||
import androidx.room.TypeConverter
|
||||
import com.geeksville.mesh.DataPacket
|
||||
import com.geeksville.mesh.MeshProtos.MeshPacket
|
||||
import com.google.protobuf.TextFormat
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.PaxcountProtos
|
||||
import com.geeksville.mesh.TelemetryProtos
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.google.protobuf.InvalidProtocolBufferException
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
class Converters {
|
||||
class Converters : Logging {
|
||||
@TypeConverter
|
||||
fun dataFromString(value: String): DataPacket {
|
||||
val json = Json { isLenient = true }
|
||||
|
|
@ -20,14 +23,62 @@ class Converters {
|
|||
}
|
||||
|
||||
@TypeConverter
|
||||
fun protoFromString(value: String): MeshPacket {
|
||||
val builder = MeshPacket.newBuilder()
|
||||
TextFormat.getParser().merge(value, builder)
|
||||
return builder.build()
|
||||
fun bytesToUser(bytes: ByteArray): MeshProtos.User {
|
||||
return try {
|
||||
MeshProtos.User.parseFrom(bytes)
|
||||
} catch (ex: InvalidProtocolBufferException) {
|
||||
errormsg("bytesToUser TypeConverter error:", ex)
|
||||
MeshProtos.User.getDefaultInstance()
|
||||
}
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun protoToString(value: MeshPacket): String {
|
||||
return value.toString()
|
||||
fun userToBytes(value: MeshProtos.User): ByteArray? {
|
||||
return value.toByteArray()
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun bytesToPosition(bytes: ByteArray): MeshProtos.Position {
|
||||
return try {
|
||||
MeshProtos.Position.parseFrom(bytes)
|
||||
} catch (ex: InvalidProtocolBufferException) {
|
||||
errormsg("bytesToPosition TypeConverter error:", ex)
|
||||
MeshProtos.Position.getDefaultInstance()
|
||||
}
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun positionToBytes(value: MeshProtos.Position): ByteArray? {
|
||||
return value.toByteArray()
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun bytesToTelemetry(bytes: ByteArray): TelemetryProtos.Telemetry {
|
||||
return try {
|
||||
TelemetryProtos.Telemetry.parseFrom(bytes)
|
||||
} catch (ex: InvalidProtocolBufferException) {
|
||||
errormsg("bytesToTelemetry TypeConverter error:", ex)
|
||||
TelemetryProtos.Telemetry.getDefaultInstance()
|
||||
}
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun telemetryToBytes(value: TelemetryProtos.Telemetry): ByteArray? {
|
||||
return value.toByteArray()
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun bytesToPaxcounter(bytes: ByteArray): PaxcountProtos.Paxcount {
|
||||
return try {
|
||||
PaxcountProtos.Paxcount.parseFrom(bytes)
|
||||
} catch (ex: InvalidProtocolBufferException) {
|
||||
errormsg("bytesToPaxcounter TypeConverter error:", ex)
|
||||
PaxcountProtos.Paxcount.getDefaultInstance()
|
||||
}
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun paxCounterToBytes(value: PaxcountProtos.Paxcount): ByteArray? {
|
||||
return value.toByteArray()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import com.geeksville.mesh.database.dao.NodeInfoDao
|
|||
import com.geeksville.mesh.database.dao.QuickChatActionDao
|
||||
import com.geeksville.mesh.database.entity.ContactSettings
|
||||
import com.geeksville.mesh.database.entity.MeshLog
|
||||
import com.geeksville.mesh.database.entity.NodeEntity
|
||||
import com.geeksville.mesh.database.entity.Packet
|
||||
import com.geeksville.mesh.database.entity.QuickChatAction
|
||||
|
||||
|
|
@ -21,6 +22,7 @@ import com.geeksville.mesh.database.entity.QuickChatAction
|
|||
entities = [
|
||||
MyNodeInfo::class,
|
||||
NodeInfo::class,
|
||||
NodeEntity::class,
|
||||
Packet::class,
|
||||
ContactSettings::class,
|
||||
MeshLog::class,
|
||||
|
|
@ -33,8 +35,9 @@ import com.geeksville.mesh.database.entity.QuickChatAction
|
|||
AutoMigration (from = 6, to = 7),
|
||||
AutoMigration (from = 7, to = 8),
|
||||
AutoMigration (from = 8, to = 9),
|
||||
AutoMigration (from = 9, to = 10),
|
||||
],
|
||||
version = 9,
|
||||
version = 10,
|
||||
exportSchema = true,
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
|
|
|
|||
|
|
@ -6,9 +6,8 @@ import androidx.room.MapColumn
|
|||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.Upsert
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.MyNodeInfo
|
||||
import com.geeksville.mesh.NodeInfo
|
||||
import com.geeksville.mesh.database.entity.NodeEntity
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
|
|
@ -23,62 +22,70 @@ interface NodeInfoDao {
|
|||
@Query("DELETE FROM MyNodeInfo")
|
||||
fun clearMyNodeInfo()
|
||||
|
||||
@Query("SELECT * FROM NodeInfo ORDER BY CASE WHEN num = (SELECT myNodeNum FROM MyNodeInfo LIMIT 1) THEN 0 ELSE 1 END, lastHeard DESC")
|
||||
fun nodeDBbyNum(): Flow<Map<@MapColumn(columnName = "num") Int, NodeInfo>>
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM nodes
|
||||
ORDER BY CASE
|
||||
WHEN num = (SELECT myNodeNum FROM MyNodeInfo LIMIT 1) THEN 0
|
||||
ELSE 1
|
||||
END,
|
||||
last_heard DESC
|
||||
"""
|
||||
)
|
||||
fun nodeDBbyNum(): Flow<Map<@MapColumn(columnName = "num") Int, NodeEntity>>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
WITH OurNode AS (
|
||||
SELECT position_latitude, position_longitude
|
||||
FROM NodeInfo
|
||||
SELECT latitude, longitude
|
||||
FROM nodes
|
||||
WHERE num = (SELECT myNodeNum FROM MyNodeInfo LIMIT 1)
|
||||
)
|
||||
SELECT * FROM NodeInfo
|
||||
WHERE (:includeUnknown = 1 OR user_hwModel != :unknownHwModel)
|
||||
SELECT * FROM nodes
|
||||
WHERE (:includeUnknown = 1 OR short_name IS NOT NULL)
|
||||
AND (:filter = ''
|
||||
OR (user_longName LIKE '%' || :filter || '%'
|
||||
OR user_shortName LIKE '%' || :filter || '%'))
|
||||
OR (long_name LIKE '%' || :filter || '%'
|
||||
OR short_name LIKE '%' || :filter || '%'))
|
||||
ORDER BY CASE
|
||||
WHEN num = (SELECT myNodeNum FROM MyNodeInfo LIMIT 1) THEN 0
|
||||
ELSE 1
|
||||
END,
|
||||
CASE
|
||||
WHEN :sort = 'last_heard' THEN lastHeard * -1
|
||||
WHEN :sort = 'alpha' THEN UPPER(user_longName)
|
||||
WHEN :sort = 'last_heard' THEN last_heard * -1
|
||||
WHEN :sort = 'alpha' THEN UPPER(long_name)
|
||||
WHEN :sort = 'distance' THEN
|
||||
CASE
|
||||
WHEN position_latitude IS NULL OR position_longitude IS NULL OR
|
||||
(position_latitude = 0 AND position_longitude = 0) THEN 999999999
|
||||
WHEN latitude IS NULL OR longitude IS NULL OR
|
||||
(latitude = 0.0 AND longitude = 0.0) THEN 999999999
|
||||
ELSE
|
||||
(position_latitude - (SELECT position_latitude FROM OurNode)) *
|
||||
(position_latitude - (SELECT position_latitude FROM OurNode)) +
|
||||
(position_longitude - (SELECT position_longitude FROM OurNode)) *
|
||||
(position_longitude - (SELECT position_longitude FROM OurNode))
|
||||
(latitude - (SELECT latitude FROM OurNode)) *
|
||||
(latitude - (SELECT latitude FROM OurNode)) +
|
||||
(longitude - (SELECT longitude FROM OurNode)) *
|
||||
(longitude - (SELECT longitude FROM OurNode))
|
||||
END
|
||||
WHEN :sort = 'hops_away' THEN hopsAway
|
||||
WHEN :sort = 'hops_away' THEN hops_away
|
||||
WHEN :sort = 'channel' THEN channel
|
||||
WHEN :sort = 'via_mqtt' THEN user_longName LIKE '%(MQTT)' -- viaMqtt
|
||||
WHEN :sort = 'via_mqtt' THEN long_name LIKE '%(MQTT)' -- viaMqtt
|
||||
ELSE 0
|
||||
END ASC,
|
||||
lastHeard DESC
|
||||
last_heard DESC
|
||||
"""
|
||||
)
|
||||
fun getNodes(
|
||||
sort: String,
|
||||
filter: String,
|
||||
includeUnknown: Boolean,
|
||||
unknownHwModel: MeshProtos.HardwareModel
|
||||
): Flow<List<NodeInfo>>
|
||||
): Flow<List<NodeEntity>>
|
||||
|
||||
@Upsert
|
||||
fun upsert(node: NodeInfo)
|
||||
fun upsert(node: NodeEntity)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun putAll(nodes: List<NodeInfo>)
|
||||
fun putAll(nodes: List<NodeEntity>)
|
||||
|
||||
@Query("DELETE FROM NodeInfo")
|
||||
@Query("DELETE FROM nodes")
|
||||
fun clearNodeInfo()
|
||||
|
||||
@Query("DELETE FROM NodeInfo WHERE num=:num")
|
||||
@Query("DELETE FROM nodes WHERE num=:num")
|
||||
fun deleteNode(num: Int)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,156 @@
|
|||
package com.geeksville.mesh.database.entity
|
||||
|
||||
import android.graphics.Color
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.geeksville.mesh.DeviceMetrics
|
||||
import com.geeksville.mesh.EnvironmentMetrics
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.MeshUser
|
||||
import com.geeksville.mesh.NodeInfo
|
||||
import com.geeksville.mesh.PaxcountProtos
|
||||
import com.geeksville.mesh.Position
|
||||
import com.geeksville.mesh.TelemetryProtos
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.util.latLongToMeter
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@Entity(tableName = "nodes")
|
||||
data class NodeEntity(
|
||||
|
||||
@PrimaryKey(autoGenerate = false)
|
||||
val num: Int, // This is immutable, and used as a key
|
||||
|
||||
@ColumnInfo(typeAffinity = ColumnInfo.BLOB)
|
||||
var user: MeshProtos.User = MeshProtos.User.getDefaultInstance(),
|
||||
@ColumnInfo(name = "long_name") var longName: String? = null,
|
||||
@ColumnInfo(name = "short_name") var shortName: String? = null, // used in includeUnknown filter
|
||||
|
||||
@ColumnInfo(typeAffinity = ColumnInfo.BLOB)
|
||||
var position: MeshProtos.Position = MeshProtos.Position.getDefaultInstance(),
|
||||
var latitude: Double = 0.0,
|
||||
var longitude: Double = 0.0,
|
||||
|
||||
var snr: Float = Float.MAX_VALUE,
|
||||
var rssi: Int = Int.MAX_VALUE,
|
||||
|
||||
@ColumnInfo(name = "last_heard")
|
||||
var lastHeard: Int = 0, // the last time we've seen this node in secs since 1970
|
||||
|
||||
@ColumnInfo(name = "device_metrics", typeAffinity = ColumnInfo.BLOB)
|
||||
var deviceTelemetry: TelemetryProtos.Telemetry = TelemetryProtos.Telemetry.getDefaultInstance(),
|
||||
|
||||
var channel: Int = 0,
|
||||
|
||||
@ColumnInfo(name = "via_mqtt")
|
||||
var viaMqtt: Boolean = false,
|
||||
|
||||
@ColumnInfo(name = "hops_away")
|
||||
var hopsAway: Int = 0,
|
||||
|
||||
@ColumnInfo(name = "is_favorite")
|
||||
var isFavorite: Boolean = false,
|
||||
|
||||
@ColumnInfo(name = "environment_metrics", typeAffinity = ColumnInfo.BLOB)
|
||||
var environmentTelemetry: TelemetryProtos.Telemetry = TelemetryProtos.Telemetry.getDefaultInstance(),
|
||||
|
||||
@ColumnInfo(name = "power_metrics", typeAffinity = ColumnInfo.BLOB)
|
||||
var powerTelemetry: TelemetryProtos.Telemetry = TelemetryProtos.Telemetry.getDefaultInstance(),
|
||||
|
||||
@ColumnInfo(typeAffinity = ColumnInfo.BLOB)
|
||||
var paxcounter: PaxcountProtos.Paxcount = PaxcountProtos.Paxcount.getDefaultInstance(),
|
||||
) {
|
||||
val deviceMetrics: TelemetryProtos.DeviceMetrics
|
||||
get() = deviceTelemetry.deviceMetrics
|
||||
|
||||
val environmentMetrics: TelemetryProtos.EnvironmentMetrics
|
||||
get() = environmentTelemetry.environmentMetrics
|
||||
|
||||
val powerMetrics: TelemetryProtos.PowerMetrics
|
||||
get() = powerTelemetry.powerMetrics
|
||||
|
||||
val colors: Pair<Int, Int>
|
||||
get() { // returns foreground and background @ColorInt for each 'num'
|
||||
val r = (num and 0xFF0000) shr 16
|
||||
val g = (num and 0x00FF00) shr 8
|
||||
val b = num and 0x0000FF
|
||||
val brightness = ((r * 0.299) + (g * 0.587) + (b * 0.114)) / 255
|
||||
return (if (brightness > 0.5) Color.BLACK else Color.WHITE) to Color.rgb(r, g, b)
|
||||
}
|
||||
|
||||
val batteryLevel get() = deviceMetrics.batteryLevel
|
||||
val voltage get() = deviceMetrics.voltage
|
||||
val batteryStr get() = if (batteryLevel in 1..100) "$batteryLevel%" else ""
|
||||
|
||||
fun setPosition(p: MeshProtos.Position, defaultTime: Int = currentTime()) {
|
||||
position = p.copy { time = if (p.time != 0) p.time else defaultTime }
|
||||
latitude = degD(p.latitudeI)
|
||||
longitude = degD(p.longitudeI)
|
||||
}
|
||||
|
||||
// @return distance in meters to some other node (or null if unknown)
|
||||
fun distance(o: NodeEntity) = latLongToMeter(latitude, longitude, o.latitude, o.longitude)
|
||||
|
||||
/**
|
||||
* true if the device was heard from recently
|
||||
*/
|
||||
val isOnline: Boolean
|
||||
get() {
|
||||
val now = System.currentTimeMillis() / 1000
|
||||
val timeout = 15 * 60
|
||||
return (now - lastHeard <= timeout)
|
||||
}
|
||||
|
||||
companion object {
|
||||
/// Convert to a double representation of degrees
|
||||
fun degD(i: Int) = i * 1e-7
|
||||
fun degI(d: Double) = (d * 1e7).toInt()
|
||||
|
||||
fun currentTime() = (System.currentTimeMillis() / 1000).toInt()
|
||||
}
|
||||
}
|
||||
|
||||
fun NodeEntity.toNodeInfo() = NodeInfo(
|
||||
num = num,
|
||||
user = MeshUser(
|
||||
id = user.id,
|
||||
longName = user.longName,
|
||||
shortName = user.shortName,
|
||||
hwModel = user.hwModel,
|
||||
role = user.roleValue,
|
||||
).takeIf { user.id.isNotEmpty() },
|
||||
position = Position(
|
||||
latitude = latitude,
|
||||
longitude = longitude,
|
||||
altitude = position.altitude,
|
||||
time = position.time,
|
||||
satellitesInView = position.satsInView,
|
||||
groundSpeed = position.groundSpeed,
|
||||
groundTrack = position.groundTrack,
|
||||
precisionBits = position.precisionBits,
|
||||
).takeIf { it.isValid() },
|
||||
snr = snr,
|
||||
rssi = rssi,
|
||||
lastHeard = lastHeard,
|
||||
deviceMetrics = DeviceMetrics(
|
||||
time = deviceTelemetry.time,
|
||||
batteryLevel = deviceMetrics.batteryLevel,
|
||||
voltage = deviceMetrics.voltage,
|
||||
channelUtilization = deviceMetrics.channelUtilization,
|
||||
airUtilTx = deviceMetrics.airUtilTx,
|
||||
uptimeSeconds = deviceMetrics.uptimeSeconds,
|
||||
),
|
||||
channel = channel,
|
||||
environmentMetrics = EnvironmentMetrics(
|
||||
time = environmentTelemetry.time,
|
||||
temperature = environmentMetrics.temperature,
|
||||
relativeHumidity = environmentMetrics.relativeHumidity,
|
||||
barometricPressure = environmentMetrics.barometricPressure,
|
||||
gasResistance = environmentMetrics.gasResistance,
|
||||
voltage = environmentMetrics.voltage,
|
||||
current = environmentMetrics.current,
|
||||
iaq = environmentMetrics.iaq,
|
||||
),
|
||||
hopsAway = hopsAway,
|
||||
)
|
||||
|
|
@ -2,12 +2,12 @@ package com.geeksville.mesh.model
|
|||
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.coroutineScope
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.MyNodeInfo
|
||||
import com.geeksville.mesh.NodeInfo
|
||||
import com.geeksville.mesh.database.dao.NodeInfoDao
|
||||
import com.geeksville.mesh.database.entity.NodeEntity
|
||||
import com.geeksville.mesh.database.entity.toNodeInfo
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
|
|
@ -33,12 +33,12 @@ class NodeDB @Inject constructor(
|
|||
private val _myId = MutableStateFlow<String?>(null)
|
||||
val myId: StateFlow<String?> get() = _myId
|
||||
|
||||
// A map from nodeNum to NodeInfo
|
||||
private val _nodeDBbyNum = MutableStateFlow<Map<Int, NodeInfo>>(mapOf())
|
||||
val nodeDBbyNum: StateFlow<Map<Int, NodeInfo>> get() = _nodeDBbyNum
|
||||
// A map from nodeNum to NodeEntity
|
||||
private val _nodeDBbyNum = MutableStateFlow<Map<Int, NodeEntity>>(mapOf())
|
||||
val nodeDBbyNum: StateFlow<Map<Int, NodeEntity>> get() = _nodeDBbyNum
|
||||
|
||||
fun getUser(userId: String?) = userId?.let { id ->
|
||||
nodeDBbyNum.value.values.find { it.user?.id == id }?.user
|
||||
nodeDBbyNum.value.values.find { it.user.id == id }?.user
|
||||
}
|
||||
|
||||
init {
|
||||
|
|
@ -47,7 +47,7 @@ class NodeDB @Inject constructor(
|
|||
|
||||
nodeInfoDao.nodeDBbyNum().onEach {
|
||||
_nodeDBbyNum.value = it
|
||||
val ourNodeInfo = it.values.firstOrNull()
|
||||
val ourNodeInfo = it.values.firstOrNull()?.toNodeInfo()
|
||||
_ourNodeInfo.value = ourNodeInfo
|
||||
_myId.value = ourNodeInfo?.user?.id
|
||||
}.launchIn(processLifecycle.coroutineScope)
|
||||
|
|
@ -61,16 +61,13 @@ class NodeDB @Inject constructor(
|
|||
sort = sort.sqlValue,
|
||||
filter = filter,
|
||||
includeUnknown = includeUnknown,
|
||||
unknownHwModel = MeshProtos.HardwareModel.UNSET
|
||||
)
|
||||
|
||||
fun myNodeInfoFlow(): Flow<MyNodeInfo?> = nodeInfoDao.getMyNodeInfo()
|
||||
|
||||
suspend fun upsert(node: NodeInfo) = withContext(Dispatchers.IO) {
|
||||
suspend fun upsert(node: NodeEntity) = withContext(Dispatchers.IO) {
|
||||
nodeInfoDao.upsert(node)
|
||||
}
|
||||
|
||||
suspend fun installNodeDB(mi: MyNodeInfo, nodes: List<NodeInfo>) = withContext(Dispatchers.IO) {
|
||||
suspend fun installNodeDB(mi: MyNodeInfo, nodes: List<NodeEntity>) = withContext(Dispatchers.IO) {
|
||||
nodeInfoDao.clearMyNodeInfo()
|
||||
nodeInfoDao.setMyNodeInfo(mi) // set MyNodeInfo first
|
||||
nodeInfoDao.clearNodeInfo()
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import com.geeksville.mesh.Portnums
|
|||
import com.geeksville.mesh.Position
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.config
|
||||
import com.geeksville.mesh.database.entity.toNodeInfo
|
||||
import com.geeksville.mesh.deviceProfile
|
||||
import com.geeksville.mesh.moduleConfig
|
||||
import com.geeksville.mesh.repository.datastore.RadioConfigRepository
|
||||
|
|
@ -85,7 +86,7 @@ class RadioConfigViewModel @Inject constructor(
|
|||
init {
|
||||
combine(_destNum, radioConfigRepository.nodeDBbyNum) { destNum, nodes ->
|
||||
nodes[destNum] ?: nodes.values.firstOrNull()
|
||||
}.onEach { _destNode.value = it }.launchIn(viewModelScope)
|
||||
}.onEach { _destNode.value = it?.toNodeInfo() }.launchIn(viewModelScope)
|
||||
|
||||
radioConfigRepository.deviceProfileFlow.onEach {
|
||||
_currentDeviceProfile.value = it
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import com.geeksville.mesh.database.PacketRepository
|
|||
import com.geeksville.mesh.database.QuickChatActionRepository
|
||||
import com.geeksville.mesh.database.entity.Packet
|
||||
import com.geeksville.mesh.database.entity.QuickChatAction
|
||||
import com.geeksville.mesh.database.entity.toNodeInfo
|
||||
import com.geeksville.mesh.repository.datastore.RadioConfigRepository
|
||||
import com.geeksville.mesh.repository.radio.RadioInterfaceService
|
||||
import com.geeksville.mesh.service.MeshService
|
||||
|
|
@ -230,7 +231,7 @@ class UIViewModel @Inject constructor(
|
|||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
val nodeList: StateFlow<List<NodeInfo>> = nodesUiState.flatMapLatest { state ->
|
||||
nodeDB.getNodes(state.sort, state.filter, state.includeUnknown)
|
||||
}.stateIn(
|
||||
}.mapLatest { list -> list.map { it.toNodeInfo() } }.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = Eagerly,
|
||||
initialValue = emptyList(),
|
||||
|
|
@ -239,14 +240,16 @@ class UIViewModel @Inject constructor(
|
|||
// hardware info about our local device (can be null)
|
||||
val myNodeInfo: StateFlow<MyNodeInfo?> get() = nodeDB.myNodeInfo
|
||||
val ourNodeInfo: StateFlow<NodeInfo?> get() = nodeDB.ourNodeInfo
|
||||
val nodesByNum get() = nodeDB.nodeDBbyNum.value // FIXME only used in MapFragment
|
||||
|
||||
fun getUser(userId: String?) = nodeDB.getUser(userId) ?: MeshUser(
|
||||
userId ?: DataPacket.ID_LOCAL,
|
||||
app.getString(R.string.unknown_username),
|
||||
app.getString(R.string.unknown_node_short_name),
|
||||
MeshProtos.HardwareModel.UNSET,
|
||||
)
|
||||
// FIXME only used in MapFragment
|
||||
val initialNodes get() = nodeDB.nodeDBbyNum.value.values.map { it.toNodeInfo() }
|
||||
|
||||
fun getUser(userId: String?) = nodeDB.getUser(userId) ?: user {
|
||||
id = userId.orEmpty()
|
||||
longName = app.getString(R.string.unknown_username)
|
||||
shortName = app.getString(R.string.unknown_node_short_name)
|
||||
hwModel = MeshProtos.HardwareModel.UNSET
|
||||
}
|
||||
|
||||
private val _snackbarText = MutableLiveData<Any?>(null)
|
||||
val snackbarText: LiveData<Any?> get() = _snackbarText
|
||||
|
|
@ -330,7 +333,7 @@ class UIViewModel @Inject constructor(
|
|||
Message(
|
||||
uuid = it.uuid,
|
||||
receivedTime = it.received_time,
|
||||
user = getUser(it.data.from),
|
||||
user = MeshUser(getUser(it.data.from)), // FIXME convert to proto User
|
||||
text = it.data.text.orEmpty(),
|
||||
time = it.data.time,
|
||||
read = it.read,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.geeksville.mesh.MeshProtos.MeshPacket
|
|||
import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig
|
||||
import com.geeksville.mesh.MyNodeInfo
|
||||
import com.geeksville.mesh.NodeInfo
|
||||
import com.geeksville.mesh.database.entity.NodeEntity
|
||||
import com.geeksville.mesh.deviceProfile
|
||||
import com.geeksville.mesh.model.NodeDB
|
||||
import com.geeksville.mesh.model.getChannelUrl
|
||||
|
|
@ -54,10 +55,10 @@ class RadioConfigRepository @Inject constructor(
|
|||
/**
|
||||
* Flow representing the [NodeInfo] database.
|
||||
*/
|
||||
val nodeDBbyNum: StateFlow<Map<Int, NodeInfo>> get() = nodeDB.nodeDBbyNum
|
||||
val nodeDBbyNum: StateFlow<Map<Int, NodeEntity>> get() = nodeDB.nodeDBbyNum
|
||||
|
||||
suspend fun upsert(node: NodeInfo) = nodeDB.upsert(node)
|
||||
suspend fun installNodeDB(mi: MyNodeInfo, nodes: List<NodeInfo>) {
|
||||
suspend fun upsert(node: NodeEntity) = nodeDB.upsert(node)
|
||||
suspend fun installNodeDB(mi: MyNodeInfo, nodes: List<NodeEntity>) {
|
||||
nodeDB.installNodeDB(mi, nodes)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,9 @@ import com.geeksville.mesh.android.hasLocationPermission
|
|||
import com.geeksville.mesh.database.MeshLogRepository
|
||||
import com.geeksville.mesh.database.PacketRepository
|
||||
import com.geeksville.mesh.database.entity.MeshLog
|
||||
import com.geeksville.mesh.database.entity.NodeEntity
|
||||
import com.geeksville.mesh.database.entity.Packet
|
||||
import com.geeksville.mesh.database.entity.toNodeInfo
|
||||
import com.geeksville.mesh.model.DeviceVersion
|
||||
import com.geeksville.mesh.repository.datastore.RadioConfigRepository
|
||||
import com.geeksville.mesh.repository.location.LocationRepository
|
||||
|
|
@ -347,7 +349,7 @@ class MeshService : Service(), Logging {
|
|||
|
||||
// The database of active nodes, index is the node user ID string
|
||||
// NOTE: some NodeInfos might be in only nodeDBbyNodeNum (because we don't yet know an ID).
|
||||
private val nodeDBbyID get() = nodeDBbyNodeNum.mapKeys { it.value.user?.id }
|
||||
private val nodeDBbyID get() = nodeDBbyNodeNum.mapKeys { it.value.user.id }
|
||||
|
||||
///
|
||||
/// END OF MODEL
|
||||
|
|
@ -368,22 +370,23 @@ class MeshService : Service(), Logging {
|
|||
if (n == DataPacket.NODENUM_BROADCAST) DataPacket.ID_BROADCAST
|
||||
else nodeDBbyNodeNum[n]?.user?.id ?: DataPacket.nodeNumToDefaultId(n)
|
||||
|
||||
private fun defaultUser(num: Int) = MeshUser(
|
||||
id = DataPacket.nodeNumToDefaultId(num),
|
||||
longName = getString(R.string.unknown_username),
|
||||
shortName = getString(R.string.unknown_node_short_name),
|
||||
hwModel = MeshProtos.HardwareModel.UNSET,
|
||||
)
|
||||
private fun defaultUser(num: Int) = user {
|
||||
val userId = DataPacket.nodeNumToDefaultId(num)
|
||||
id = userId
|
||||
longName = "Meshtastic ${userId.takeLast(n = 4)}"
|
||||
shortName = userId.takeLast(n = 4)
|
||||
hwModel = MeshProtos.HardwareModel.UNSET
|
||||
}
|
||||
|
||||
// given a nodeNum, return a db entry - creating if necessary
|
||||
private fun getOrCreateNodeInfo(n: Int) = nodeDBbyNodeNum[n] ?: NodeInfo(n, defaultUser(n))
|
||||
private fun getOrCreateNodeInfo(n: Int) = nodeDBbyNodeNum[n] ?: NodeEntity(n, defaultUser(n))
|
||||
|
||||
private val hexIdRegex = """\!([0-9A-Fa-f]+)""".toRegex()
|
||||
private val rangeTestRegex = Regex("seq (\\d{1,10})")
|
||||
|
||||
/// Map a userid to a node/ node num, or throw an exception if not found
|
||||
/// We prefer to find nodes based on their assigned IDs, but if no ID has been assigned to a node, we can also find it based on node number
|
||||
private fun toNodeInfo(id: String): NodeInfo {
|
||||
private fun toNodeInfo(id: String): NodeEntity {
|
||||
// If this is a valid hexaddr will be !null
|
||||
val hexStr = hexIdRegex.matchEntire(id)?.groups?.get(1)?.value
|
||||
|
||||
|
|
@ -421,22 +424,19 @@ class MeshService : Service(), Logging {
|
|||
private inline fun updateNodeInfo(
|
||||
nodeNum: Int,
|
||||
withBroadcast: Boolean = true,
|
||||
crossinline updateFn: (NodeInfo) -> Unit,
|
||||
crossinline updateFn: (NodeEntity) -> Unit,
|
||||
) {
|
||||
val info = getOrCreateNodeInfo(nodeNum)
|
||||
updateFn(info)
|
||||
|
||||
// This might have been the first time we know an ID for this node, so also update the by ID map
|
||||
val userId = info.user?.id.orEmpty()
|
||||
if (userId.isNotEmpty()) {
|
||||
if (info.user.id.isNotEmpty()) {
|
||||
if (haveNodeDB) serviceScope.handledLaunch {
|
||||
radioConfigRepository.upsert(info)
|
||||
}
|
||||
}
|
||||
|
||||
// parcelable is busted
|
||||
if (withBroadcast)
|
||||
serviceBroadcasts.broadcastNodeChange(info)
|
||||
serviceBroadcasts.broadcastNodeChange(info.toNodeInfo())
|
||||
}
|
||||
|
||||
/// My node num
|
||||
|
|
@ -488,6 +488,12 @@ class MeshService : Service(), Logging {
|
|||
decoded = MeshProtos.Data.newBuilder().also {
|
||||
initFn(it)
|
||||
}.build()
|
||||
if (decoded.portnum in setOf(Portnums.PortNum.TEXT_MESSAGE_APP, Portnums.PortNum.ADMIN_APP)) {
|
||||
nodeDBbyNodeNum[to]?.user?.publicKey?.let { publicKey ->
|
||||
pkiEncrypted = !publicKey.isEmpty
|
||||
this.publicKey = publicKey
|
||||
}
|
||||
}
|
||||
|
||||
return build()
|
||||
}
|
||||
|
|
@ -642,7 +648,8 @@ class MeshService : Service(), Logging {
|
|||
// Handle new telemetry info
|
||||
Portnums.PortNum.TELEMETRY_APP_VALUE -> {
|
||||
val u = TelemetryProtos.Telemetry.parseFrom(data.payload)
|
||||
handleReceivedTelemetry(packet.from, u, dataPacket.time)
|
||||
.copy { if (time == 0) time = (dataPacket.time / 1000L).toInt() }
|
||||
handleReceivedTelemetry(packet.from, u)
|
||||
}
|
||||
|
||||
Portnums.PortNum.ROUTING_APP_VALUE -> {
|
||||
|
|
@ -665,6 +672,12 @@ class MeshService : Service(), Logging {
|
|||
shouldBroadcast = false
|
||||
}
|
||||
|
||||
Portnums.PortNum.PAXCOUNTER_APP_VALUE -> {
|
||||
val p = PaxcountProtos.Paxcount.parseFrom(data.payload)
|
||||
handleReceivedPaxcounter(packet.from, p)
|
||||
shouldBroadcast = false
|
||||
}
|
||||
|
||||
Portnums.PortNum.STORE_FORWARD_APP_VALUE -> {
|
||||
val u = StoreAndForwardProtos.StoreAndForward.parseFrom(data.payload)
|
||||
handleReceivedStoreAndForward(dataPacket, u)
|
||||
|
|
@ -742,7 +755,9 @@ class MeshService : Service(), Logging {
|
|||
/// Update our DB of users based on someone sending out a User subpacket
|
||||
private fun handleReceivedUser(fromNum: Int, p: MeshProtos.User, channel: Int = 0) {
|
||||
updateNodeInfo(fromNum) {
|
||||
it.user = MeshUser(p)
|
||||
it.user = p
|
||||
it.longName = p.longName
|
||||
it.shortName = p.shortName
|
||||
it.channel = channel
|
||||
}
|
||||
}
|
||||
|
|
@ -762,8 +777,8 @@ class MeshService : Service(), Logging {
|
|||
debug("Ignoring nop position update for the local node")
|
||||
} else {
|
||||
updateNodeInfo(fromNum) {
|
||||
debug("update position: ${it.user?.longName?.toPIIString()} with ${p.toPIIString()}")
|
||||
it.position = Position(p, (defaultTime / 1000L).toInt())
|
||||
debug("update position: ${it.longName?.toPIIString()} with ${p.toPIIString()}")
|
||||
it.setPosition(p, (defaultTime / 1000L).toInt())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -772,18 +787,20 @@ class MeshService : Service(), Logging {
|
|||
private fun handleReceivedTelemetry(
|
||||
fromNum: Int,
|
||||
t: TelemetryProtos.Telemetry,
|
||||
defaultTime: Long = System.currentTimeMillis()
|
||||
) {
|
||||
updateNodeInfo(fromNum) {
|
||||
if (t.hasDeviceMetrics()) it.deviceMetrics = DeviceMetrics(
|
||||
t.deviceMetrics, if (t.time != 0) t.time else (defaultTime / 1000L).toInt()
|
||||
)
|
||||
if (t.hasEnvironmentMetrics()) it.environmentMetrics = EnvironmentMetrics(
|
||||
t.environmentMetrics, if (t.time != 0) t.time else (defaultTime / 1000L).toInt()
|
||||
)
|
||||
when {
|
||||
t.hasDeviceMetrics() -> it.deviceTelemetry = t
|
||||
t.hasEnvironmentMetrics() -> it.environmentTelemetry = t
|
||||
t.hasPowerMetrics() -> it.powerTelemetry = t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleReceivedPaxcounter(fromNum: Int, p: PaxcountProtos.Paxcount) {
|
||||
updateNodeInfo(fromNum) { it.paxcounter = p }
|
||||
}
|
||||
|
||||
private fun handleReceivedStoreAndForward(
|
||||
dataPacket: DataPacket,
|
||||
s: StoreAndForwardProtos.StoreAndForward,
|
||||
|
|
@ -1311,27 +1328,21 @@ class MeshService : Service(), Logging {
|
|||
radioConfigRepository.setStatusMessage("Channels (${ch.index + 1} / $maxChannels)")
|
||||
}
|
||||
|
||||
private fun MeshProtos.NodeInfo.toEntity() = NodeInfo(
|
||||
private fun MeshProtos.NodeInfo.toEntity() = NodeEntity(
|
||||
num = num,
|
||||
user = if (hasUser()) {
|
||||
MeshUser(user.copy { if (viaMqtt) longName = "$longName (MQTT)" })
|
||||
} else {
|
||||
defaultUser(num)
|
||||
},
|
||||
position = if (hasPosition()) {
|
||||
Position(position)
|
||||
} else {
|
||||
null
|
||||
},
|
||||
snr = snr,
|
||||
user = if (hasUser()) user else defaultUser(num)
|
||||
.copy { if (viaMqtt) longName = "$longName (MQTT)" },
|
||||
longName = user.longName,
|
||||
shortName = user.shortName.takeIf { hasUser() },
|
||||
position = position,
|
||||
latitude = position.latitudeI * 1e-7,
|
||||
longitude = position.longitudeI * 1e-7,
|
||||
lastHeard = lastHeard,
|
||||
deviceMetrics = if(hasDeviceMetrics()) {
|
||||
DeviceMetrics(deviceMetrics)
|
||||
} else {
|
||||
null
|
||||
},
|
||||
deviceTelemetry = telemetry { deviceMetrics = deviceMetrics },
|
||||
channel = channel,
|
||||
viaMqtt = viaMqtt,
|
||||
hopsAway = hopsAway,
|
||||
isFavorite = isFavorite,
|
||||
)
|
||||
|
||||
private fun handleNodeInfo(info: MeshProtos.NodeInfo) {
|
||||
|
|
@ -1589,7 +1600,7 @@ class MeshService : Service(), Logging {
|
|||
private fun setOwner(packetId: Int, user: MeshProtos.User) = with(user) {
|
||||
val dest = nodeDBbyID[id]
|
||||
?: throw Exception("Can't set user without a NodeInfo") // this shouldn't happen
|
||||
val old = dest.user!!
|
||||
val old = dest.user
|
||||
if (longName == old.longName && shortName == old.shortName && isLicensed == old.isLicensed) {
|
||||
debug("Ignoring nop owner change")
|
||||
} else {
|
||||
|
|
@ -1665,7 +1676,12 @@ class MeshService : Service(), Logging {
|
|||
override fun getPacketId() = toRemoteExceptions { generatePacketId() }
|
||||
|
||||
override fun setOwner(user: MeshUser) = toRemoteExceptions {
|
||||
setOwner(generatePacketId(), user.toProto())
|
||||
setOwner(generatePacketId(), user {
|
||||
id = user.id
|
||||
longName = user.longName
|
||||
shortName = user.shortName
|
||||
isLicensed = user.isLicensed
|
||||
})
|
||||
}
|
||||
|
||||
override fun setRemoteOwner(id: Int, payload: ByteArray) = toRemoteExceptions {
|
||||
|
|
@ -1813,7 +1829,7 @@ class MeshService : Service(), Logging {
|
|||
}
|
||||
|
||||
override fun getNodes(): MutableList<NodeInfo> = toRemoteExceptions {
|
||||
val r = nodeDBbyNodeNum.values.toMutableList()
|
||||
val r = nodeDBbyNodeNum.values.map { it.toNodeInfo() }.toMutableList()
|
||||
info("in getOnline, count=${r.size}")
|
||||
// return arrayOf("+16508675309")
|
||||
r
|
||||
|
|
|
|||
|
|
@ -467,7 +467,7 @@ fun MapView(
|
|||
}
|
||||
|
||||
fun MapView.zoomToNodes() {
|
||||
val nodeMarkers = onNodesChanged(model.nodesByNum.values)
|
||||
val nodeMarkers = onNodesChanged(model.initialNodes)
|
||||
if (nodeMarkers.isNotEmpty()) {
|
||||
val box = BoundingBox.fromGeoPoints(nodeMarkers.map { it.position })
|
||||
val center = GeoPoint(box.centerLatitude, box.centerLongitude)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue