add beginnings of radio interface service

pull/8/head
geeksville 2020-01-24 17:05:55 -08:00
rodzic 5c9696588e
commit 559795b796
9 zmienionych plików z 203 dodań i 90 usunięć

Wyświetl plik

@ -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

Wyświetl plik

@ -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)
}
}

Wyświetl plik

@ -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>

Wyświetl plik

@ -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

Wyświetl plik

@ -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")

Wyświetl plik

@ -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") {
}

Wyświetl plik

@ -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")
}
}
}

Wyświetl plik

@ -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

Wyświetl plik

@ -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
}
}