kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
add beginnings of radio interface service
rodzic
5c9696588e
commit
559795b796
|
|
@ -9,7 +9,7 @@ Questions? kevinh@geeksville.com
|
|||
Once this project is public, I'll happily let collaborators have access to the crash logs/analytics.
|
||||
|
||||
* analytics is currently on, before beta is over I'll make it optional
|
||||
* on dev devices "adb shell setprop debug.firebase.analytics.app com.geeeksville.mesh"
|
||||
* on dev devices "adb shell setprop debug.firebase.analytics.app com.geeksville.mesh"
|
||||
* To see analytics: https://console.firebase.google.com/u/0/project/meshutil/analytics/app/android:com.geeksville.mesh/overview
|
||||
* To see crash logs: https://console.firebase.google.com/u/0/project/meshutil/crashlytics/app/android:com.geeksville.mesh/issues?state=open&time=last-seven-days&type=crash
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,6 @@ class ExampleInstrumentedTest {
|
|||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.geeksville.com.geeeksville.mesh", appContext.packageName)
|
||||
assertEquals("com.geeksville.com.geeksville.mesh", appContext.packageName)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,15 +16,13 @@
|
|||
This permission is required to allow the application to send
|
||||
events and properties to Mixpanel.
|
||||
-->
|
||||
<uses-permission
|
||||
android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<!--
|
||||
This permission is optional but recommended so we can be smart
|
||||
about when to send data.
|
||||
-->
|
||||
<uses-permission
|
||||
android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.bluetooth_le"
|
||||
|
|
@ -39,7 +37,8 @@
|
|||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
|
||||
<meta-data android:name="com.mixpanel.android.MPConfig.DisableViewCrawler"
|
||||
<meta-data
|
||||
android:name="com.mixpanel.android.MPConfig.DisableViewCrawler"
|
||||
android:value="true" />
|
||||
|
||||
<!-- we need bind job service for oreo -->
|
||||
|
|
@ -49,11 +48,18 @@
|
|||
android:exported="false"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||
|
||||
<!-- This is the public API for doing mesh radio operations from android apps -->
|
||||
<service
|
||||
android:name="com.geeksville.mesh.MeshService"
|
||||
android:enabled="true"
|
||||
android:exported="true" />
|
||||
|
||||
<!-- This is a private service which just does direct communication to the radio -->
|
||||
<service
|
||||
android:name="com.geeksville.mesh.RadioInterfaceService"
|
||||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name="com.geeksville.mesh.MainActivity"
|
||||
android:label="@string/app_name"
|
||||
|
|
@ -67,7 +73,7 @@
|
|||
|
||||
<receiver android:name="com.geeksville.mesh.BootCompleteReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
</application>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// com.geeksville.com.geeeksville.mesh.IMeshService.aidl
|
||||
// com.geeksville.mesh.IMeshService.aidl
|
||||
package com.geeksville.mesh;
|
||||
|
||||
// Declare any non-default types here with import statements
|
||||
|
|
@ -24,7 +24,7 @@ interface IMeshService {
|
|||
*/
|
||||
boolean isConnected();
|
||||
|
||||
// see com.geeksville.com.geeeksville.mesh broadcast intents
|
||||
// see com.geeksville.com.geeksville.mesh broadcast intents
|
||||
// RECEIVED_OPAQUE for data received from other nodes
|
||||
// NODE_CHANGE for new IDs appearing or disappearing
|
||||
// CONNECTION_CHANGED for losing/gaining connection to the packet radio
|
||||
|
|
|
|||
|
|
@ -1,42 +1,45 @@
|
|||
package com.geeksville.mesh
|
||||
|
||||
import android.app.Service
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.IBinder
|
||||
import com.geeksville.android.Logging
|
||||
|
||||
|
||||
/**
|
||||
* Handles all the communication with android apps. Also keeps an internal model
|
||||
* of the network state.
|
||||
*
|
||||
* Note: this service will go away once all clients are unbound from it.
|
||||
*/
|
||||
class MeshService : Service(), Logging {
|
||||
|
||||
companion object {
|
||||
const val prefix = "com.geeksville.mesh"
|
||||
}
|
||||
|
||||
/*
|
||||
see com.geeksville.mesh broadcast intents
|
||||
// RECEIVED_OPAQUE for data received from other nodes
|
||||
// NODE_CHANGE for new IDs appearing or disappearing
|
||||
// CONNECTION_CHANGED for losing/gaining connection to the packet radio
|
||||
*/
|
||||
|
||||
/**
|
||||
* The RECEIVED_OPAQUE:
|
||||
* Payload will be the raw bytes which were contained within a MeshPacket.Opaque field
|
||||
* Sender will be a user ID string
|
||||
*/
|
||||
fun broadcastReceivedOpaque(senderId: String, payload: ByteArray) {
|
||||
val intent = Intent("$prefix.RECEIVED_OPAQUE")
|
||||
intent.putExtra("$prefix.Sender", senderId)
|
||||
intent.putExtra("$prefix.Payload", payload)
|
||||
intent.putExtra(EXTRA_SENDER, senderId)
|
||||
intent.putExtra(EXTRA_PAYLOAD, payload)
|
||||
sendBroadcast(intent)
|
||||
}
|
||||
|
||||
fun broadcastNodeChange(nodeId: String, isOnline: Boolean) {
|
||||
val intent = Intent("$prefix.NODE_CHANGE")
|
||||
intent.putExtra("$prefix.Id", nodeId)
|
||||
intent.putExtra("$prefix.Online", isOnline)
|
||||
sendBroadcast(intent)
|
||||
}
|
||||
|
||||
fun broadcastConnectionChanged(isConnected: Boolean) {
|
||||
val intent = Intent("$prefix.CONNECTION_CHANGED")
|
||||
intent.putExtra("$prefix.Connected", isConnected)
|
||||
intent.putExtra(EXTRA_ID, nodeId)
|
||||
intent.putExtra(EXTRA_ONLINE, isOnline)
|
||||
sendBroadcast(intent)
|
||||
}
|
||||
|
||||
|
|
@ -45,6 +48,30 @@ class MeshService : Service(), Logging {
|
|||
return binder
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
val filter = IntentFilter(RadioInterfaceService.RECEIVE_FROMRADIO_ACTION)
|
||||
registerReceiver(radioInterfaceReceiver, filter)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
unregisterReceiver(radioInterfaceReceiver)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives messages from our BT radio service and processes them to update our model
|
||||
* and send to clients as needed.
|
||||
*/
|
||||
private val radioInterfaceReceiver = object : BroadcastReceiver() {
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
val proto = MeshProtos.FromRadio.parseFrom(intent.getByteArrayExtra(EXTRA_PAYLOAD)!!)
|
||||
TODO("FIXME - update model and send messages as needed")
|
||||
}
|
||||
}
|
||||
|
||||
private val binder = object : IMeshService.Stub() {
|
||||
override fun setOwner(myId: String, longName: String, shortName: String) {
|
||||
error("TODO setOwner $myId : $longName : $shortName")
|
||||
|
|
|
|||
|
|
@ -2,4 +2,7 @@ package com.geeksville.mesh
|
|||
|
||||
import com.geeksville.android.GeeksvilleApplication
|
||||
|
||||
class MeshUtilApplication : GeeksvilleApplication(null, "58e72ccc361883ea502510baa46580e3")
|
||||
const val prefix = "com.geeksville.mesh"
|
||||
|
||||
class MeshUtilApplication : GeeksvilleApplication(null, "58e72ccc361883ea502510baa46580e3") {
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
package com.geeksville.mesh
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.core.app.JobIntentService
|
||||
import com.geeksville.android.Logging
|
||||
|
||||
const val EXTRA_CONNECTED = "$prefix.Connected"
|
||||
const val EXTRA_PAYLOAD = "$prefix.Payload"
|
||||
const val EXTRA_SENDER = "$prefix.Sender"
|
||||
const val EXTRA_ID = "$prefix.Id"
|
||||
const val EXTRA_ONLINE = "$prefix.Online"
|
||||
|
||||
/**
|
||||
* Handles the bluetooth link with a mesh radio device. Does not cache any device state,
|
||||
* just does bluetooth comms etc...
|
||||
*
|
||||
* This service is not exposed outside of this process.
|
||||
*
|
||||
* Note - this class intentionally dumb. It doesn't understand protobuf framing etc...
|
||||
* It is designed to be simple so it can be stubbed out with a simulated version as needed.
|
||||
*/
|
||||
class RadioInterfaceService : JobIntentService(), Logging {
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Unique job ID for this service. Must be the same for all work.
|
||||
*/
|
||||
private const val JOB_ID = 1001
|
||||
|
||||
/**
|
||||
* The SEND_TORADIO
|
||||
* Payload will be the raw bytes which were contained within a MeshProtos.ToRadio protobuf
|
||||
*/
|
||||
const val SEND_TORADIO_ACTION = "$prefix.SEND_TORADIO"
|
||||
|
||||
/**
|
||||
* The RECEIVED_FROMRADIO
|
||||
* Payload will be the raw bytes which were contained within a MeshProtos.FromRadio protobuf
|
||||
*/
|
||||
const val RECEIVE_FROMRADIO_ACTION = "$prefix.RECEIVE_FROMRADIO"
|
||||
|
||||
|
||||
/**
|
||||
* Convenience method for enqueuing work in to this service.
|
||||
*/
|
||||
fun enqueueWork(context: Context, work: Intent) {
|
||||
enqueueWork(
|
||||
context,
|
||||
RadioInterfaceService::class.java, JOB_ID, work
|
||||
)
|
||||
}
|
||||
|
||||
/// Helper function to send a packet to the radio
|
||||
fun sendToRadio(context: Context, a: ByteArray) {
|
||||
val i = Intent(SEND_TORADIO_ACTION)
|
||||
i.putExtra(EXTRA_PAYLOAD, a)
|
||||
enqueueWork(context, i)
|
||||
}
|
||||
}
|
||||
|
||||
private fun broadcastReceivedFromRadio(payload: ByteArray) {
|
||||
val intent = Intent(RECEIVE_FROMRADIO_ACTION)
|
||||
intent.putExtra("$prefix.Payload", payload)
|
||||
sendBroadcast(intent)
|
||||
}
|
||||
|
||||
fun broadcastConnectionChanged(isConnected: Boolean) {
|
||||
val intent = Intent("$prefix.CONNECTION_CHANGED")
|
||||
intent.putExtra(EXTRA_CONNECTED, isConnected)
|
||||
sendBroadcast(intent)
|
||||
}
|
||||
|
||||
/// Send a packet/command out the radio link
|
||||
private fun sendToRadio(p: ByteArray) {
|
||||
info("Simulating sending to radio size=$p.size")
|
||||
}
|
||||
|
||||
// Handle an incoming packet from the radio, broadcasts it as an android intent
|
||||
private fun handleFromRadio(p: ByteArray) {
|
||||
broadcastReceivedFromRadio(p)
|
||||
}
|
||||
|
||||
override fun onHandleWork(intent: Intent) { // We have received work to do. The system or framework is already
|
||||
// holding a wake lock for us at this point, so we can just go.
|
||||
debug("Executing work: $intent")
|
||||
when (intent.action) {
|
||||
SEND_TORADIO_ACTION -> sendToRadio(intent.getByteArrayExtra(EXTRA_PAYLOAD)!!)
|
||||
else -> TODO("Unhandled case")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -10,10 +10,7 @@ import android.bluetooth.le.ScanResult
|
|||
import android.bluetooth.le.ScanSettings
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Handler
|
||||
import android.os.ParcelUuid
|
||||
import android.os.SystemClock
|
||||
import android.widget.Toast
|
||||
import androidx.core.app.JobIntentService
|
||||
import com.geeksville.android.Logging
|
||||
import java.util.*
|
||||
|
|
@ -184,19 +181,7 @@ class SoftwareUpdateService : JobIntentService(), Logging {
|
|||
connectToTestDevice() // FIXME, pass in as an intent arg instead
|
||||
startUpdate()
|
||||
}
|
||||
else -> logAssert(false)
|
||||
}
|
||||
|
||||
debug(
|
||||
"Completed service @ " + SystemClock.elapsedRealtime()
|
||||
)
|
||||
}
|
||||
|
||||
val mHandler = Handler()
|
||||
// Helper for showing tests
|
||||
fun toast(text: CharSequence?) {
|
||||
mHandler.post {
|
||||
Toast.makeText(this@SoftwareUpdateService, text, Toast.LENGTH_SHORT).show()
|
||||
else -> TODO("Unhandled case")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -204,10 +189,10 @@ class SoftwareUpdateService : JobIntentService(), Logging {
|
|||
/**
|
||||
* Unique job ID for this service. Must be the same for all work.
|
||||
*/
|
||||
const val JOB_ID = 1000
|
||||
private const val JOB_ID = 1000
|
||||
|
||||
val scanDevicesIntent = Intent("com.geeksville.com.geeeksville.mesh.SCAN_DEVICES")
|
||||
val startUpdateIntent = Intent("com.geeksville.com.geeeksville.mesh.START_UPDATE")
|
||||
val scanDevicesIntent = Intent("$prefix.SCAN_DEVICES")
|
||||
val startUpdateIntent = Intent("$prefix.START_UPDATE")
|
||||
|
||||
private const val SCAN_PERIOD: Long = 10000
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ syntax = "proto3";
|
|||
|
||||
package mesh;
|
||||
|
||||
option java_package = "com.geeksville.com.geeeksville.mesh";
|
||||
option java_package = "com.geeksville.mesh";
|
||||
option java_outer_classname = "MeshProtos";
|
||||
|
||||
/**
|
||||
|
|
@ -42,40 +42,40 @@ node number, or 0xff for broadcast.
|
|||
|
||||
// a gps position
|
||||
message Position {
|
||||
double latitude = 1;
|
||||
double longitude = 2;
|
||||
int32 altitude = 3;
|
||||
double latitude = 1;
|
||||
double longitude = 2;
|
||||
int32 altitude = 3;
|
||||
}
|
||||
|
||||
// Times are typically not sent over the mesh, but they will be added to any Packet (chain of SubPacket)
|
||||
// sent to the phone (so the phone can know exact time of reception)
|
||||
message Time {
|
||||
uint64 msecs = 1; // msecs since 1970
|
||||
uint64 msecs = 1; // msecs since 1970
|
||||
}
|
||||
|
||||
// A message sent from a device outside of the mesh, in a form the mesh does not understand
|
||||
// i.e. a Signal app level message.
|
||||
message Opaque {
|
||||
bytes payload = 1;
|
||||
bytes payload = 1;
|
||||
}
|
||||
|
||||
// a simple text message, which even the little micros in the mesh can understand and show on their screen
|
||||
message Text {
|
||||
string text = 1;
|
||||
string text = 1;
|
||||
}
|
||||
|
||||
// Sent from the phone over bluetooth to set the user id for the owner of this node.
|
||||
// Also sent from nodes to each other when a new node signs on (so all clients can have this info)
|
||||
message User {
|
||||
string id = 1; // a globally unique ID string for this user. In the case of Signal that would mean +16504442323
|
||||
string long_name = 2; // A full name for this user, i.e. "Kevin Hester"
|
||||
string short_name = 3; // A VERY short name, ideally two characters. Suitable for a tiny OLED screen
|
||||
string id = 1; // a globally unique ID string for this user. In the case of Signal that would mean +16504442323
|
||||
string long_name = 2; // A full name for this user, i.e. "Kevin Hester"
|
||||
string short_name = 3; // A VERY short name, ideally two characters. Suitable for a tiny OLED screen
|
||||
}
|
||||
|
||||
// Broadcast when a newly powered mesh node wants to find a node num it can use (see document for more
|
||||
// details)
|
||||
message WantNodeNum {
|
||||
// No payload, just its existence is sufficent (desired node num will be in the from field)
|
||||
// No payload, just its existence is sufficent (desired node num will be in the from field)
|
||||
}
|
||||
|
||||
// Sent to a node which has requested a nodenum when it is told it can't have it
|
||||
|
|
@ -84,35 +84,34 @@ message DenyNodeNum {
|
|||
|
||||
// A single packet might have a series of SubPacket included
|
||||
message SubPacket {
|
||||
oneof variant {
|
||||
Position position = 1;
|
||||
Time time = 2;
|
||||
Text text = 3;
|
||||
Opaque opaque = 4;
|
||||
User user = 5;
|
||||
WantNodeNum want_node = 6;
|
||||
DenyNodeNum deny_node = 7;
|
||||
}
|
||||
oneof variant {
|
||||
Position position = 1;
|
||||
Time time = 2;
|
||||
Text text = 3;
|
||||
Opaque opaque = 4;
|
||||
User user = 5;
|
||||
WantNodeNum want_node = 6;
|
||||
DenyNodeNum deny_node = 7;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// A packet sent over our mesh.
|
||||
// NOTE: this raw payload does not include the from and to addresses, which are stripped off
|
||||
// and passed into the mesh library code separately.
|
||||
message MeshPayload {
|
||||
repeated SubPacket subPackets = 3;
|
||||
repeated SubPacket subPackets = 3;
|
||||
}
|
||||
|
||||
// A full packet sent/received over the mesh
|
||||
message MeshPacket {
|
||||
int32 from = 1;
|
||||
int32 to = 2;
|
||||
MeshPayload payload = 3;
|
||||
int32 from = 1;
|
||||
int32 to = 2;
|
||||
MeshPayload payload = 3;
|
||||
}
|
||||
|
||||
// Full settings (center freq, spread factor, preshared secret key etc...) needed to configure a radio
|
||||
message RadioConfig {
|
||||
// FIXME
|
||||
// FIXME
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -139,11 +138,11 @@ SET_CONFIG (switches device to a new set of radio params and preshared key, drop
|
|||
|
||||
// Full information about a node on the mesh
|
||||
message NodeInfo {
|
||||
int32 num = 1; // the node number
|
||||
User user = 2;
|
||||
int32 battery_level = 3; // 0-100
|
||||
Position position = 4;
|
||||
Time last_seen = 5;
|
||||
int32 num = 1; // the node number
|
||||
User user = 2;
|
||||
int32 battery_level = 3; // 0-100
|
||||
Position position = 4;
|
||||
Time last_seen = 5;
|
||||
}
|
||||
|
||||
// packets from the radio to the phone will appear on the fromRadio characteristic. It will support
|
||||
|
|
@ -151,27 +150,27 @@ message NodeInfo {
|
|||
// it will sit in that descriptor until consumed by the phone, at which point the next item in the FIFO
|
||||
// will be populated. FIXME
|
||||
message FromRadio {
|
||||
oneof variant {
|
||||
MeshPacket packet = 1;
|
||||
NodeInfo node_info = 2;
|
||||
}
|
||||
oneof variant {
|
||||
MeshPacket packet = 1;
|
||||
NodeInfo node_info = 2;
|
||||
}
|
||||
}
|
||||
|
||||
// packets/commands to the radio will be written (reliably) to the toRadio characteristic. Once the
|
||||
// write completes the phone can assume it is handled.
|
||||
message ToRadio {
|
||||
|
||||
// If sent to the radio, the radio will send the phone its full node DB (NodeInfo records)
|
||||
// Used to populate network info the first time the phone connects to the radio
|
||||
message WantNodes {
|
||||
// Empty
|
||||
}
|
||||
// If sent to the radio, the radio will send the phone its full node DB (NodeInfo records)
|
||||
// Used to populate network info the first time the phone connects to the radio
|
||||
message WantNodes {
|
||||
// Empty
|
||||
}
|
||||
|
||||
oneof variant {
|
||||
MeshPacket packet = 1; // send this packet on the mesh
|
||||
RadioConfig set_radio = 2; // set the radio provisioning for this node
|
||||
User set_owner = 3; // Set the owner for this node
|
||||
WantNodes want_nodes = 4; // phone wants radio to send full node db to the phone
|
||||
}
|
||||
oneof variant {
|
||||
MeshPacket packet = 1; // send this packet on the mesh
|
||||
RadioConfig set_radio = 2; // set the radio provisioning for this node
|
||||
User set_owner = 3; // Set the owner for this node
|
||||
WantNodes want_nodes = 4; // phone wants radio to send full node db to the phone
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Ładowanie…
Reference in New Issue