mirror of
https://github.com/markqvist/Sideband.git
synced 2025-12-01 12:45:30 -05:00
Use local version of able
This commit is contained in:
parent
2e44d49d6b
commit
9b6a51a03e
67 changed files with 5305 additions and 0 deletions
1
libs/able/testapps/bletest/.gitignore
vendored
Normal file
1
libs/able/testapps/bletest/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
server
|
||||
193
libs/able/testapps/bletest/bletestapp.kv
Normal file
193
libs/able/testapps/bletest/bletestapp.kv
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
#:kivy 1.1.0
|
||||
#: import Factory kivy.factory.Factory
|
||||
#: import findall re.findall
|
||||
|
||||
<Caption@Label>:
|
||||
padding_left: '4sp'
|
||||
halign: 'left'
|
||||
text_size: self.size
|
||||
valign: 'middle'
|
||||
|
||||
<Value@Label>:
|
||||
padding_left: '4sp'
|
||||
halign: 'left'
|
||||
text_size: self.size
|
||||
valign: 'middle'
|
||||
|
||||
<ConnectByMACDialog@Popup>:
|
||||
title: 'Connect by MAC address'
|
||||
size_hint: None, None
|
||||
size: '400sp', '120sp'
|
||||
BoxLayout:
|
||||
orientation: 'vertical'
|
||||
pos: self.pos
|
||||
size: root.size
|
||||
|
||||
TextInput:
|
||||
size_hint_y: .5
|
||||
hint_text: 'Device address'
|
||||
input_filter: lambda value, _ : ''.join(findall('[0-9a-fA-F:]+', value)).upper()
|
||||
multiline: False
|
||||
text: app.device_address
|
||||
on_text: app.device_address = self.text
|
||||
|
||||
BoxLayout:
|
||||
orientation: 'horizontal'
|
||||
size_hint_y: .5
|
||||
Button:
|
||||
text: 'Connect'
|
||||
on_press: root.dismiss(), app.connect_by_mac_address()
|
||||
Button:
|
||||
text: 'Cancel'
|
||||
on_press: root.dismiss()
|
||||
|
||||
<MainLayout>:
|
||||
padding: '10sp'
|
||||
BoxLayout:
|
||||
orientation: 'horizontal'
|
||||
GridLayout:
|
||||
cols: 2
|
||||
padding: '0sp'
|
||||
spacing: '0sp'
|
||||
orientation: 'lr-tb'
|
||||
|
||||
Caption:
|
||||
text: 'Adapter:'
|
||||
Value:
|
||||
text: app.adapter_state
|
||||
|
||||
Caption:
|
||||
text: 'State:'
|
||||
Value:
|
||||
text: app.state
|
||||
halign: 'left'
|
||||
valign: 'middle'
|
||||
text_size: self.size
|
||||
|
||||
Caption:
|
||||
text: 'Read test:'
|
||||
Value:
|
||||
text: app.test_string
|
||||
|
||||
Caption:
|
||||
text: 'Notifications count:'
|
||||
Value:
|
||||
text: app.notification_value
|
||||
|
||||
Caption:
|
||||
text: 'N packets sended:'
|
||||
Value:
|
||||
text: app.increment_count_value
|
||||
|
||||
Caption:
|
||||
text: 'N packets delivered:'
|
||||
Value:
|
||||
text: app.counter_value
|
||||
|
||||
Caption:
|
||||
text: 'Total transmission time:'
|
||||
Value:
|
||||
text: app.counter_total_time
|
||||
|
||||
BoxLayout:
|
||||
spacing: '20sp'
|
||||
orientation: 'vertical'
|
||||
|
||||
BoxLayout:
|
||||
orientation: 'horizontal'
|
||||
size_hint_y: .3
|
||||
|
||||
Button:
|
||||
text: 'Scan and connect'
|
||||
on_press: app.start_scan()
|
||||
|
||||
Button:
|
||||
text: 'Connect by MAC address'
|
||||
on_press: Factory.ConnectByMACDialog().open()
|
||||
|
||||
BoxLayout:
|
||||
id: queue_box
|
||||
orientation: 'vertical'
|
||||
size_hint_y: .15
|
||||
|
||||
BoxLayout:
|
||||
orientation: 'horizontal'
|
||||
Caption:
|
||||
text: 'Enable GATT autoconnect:'
|
||||
CheckBox:
|
||||
id: timeout_checkbox
|
||||
active: app.autoconnect
|
||||
on_active: app.autoconnect = self.active
|
||||
|
||||
BoxLayout:
|
||||
orientation: 'horizontal'
|
||||
size_hint_y: .2
|
||||
spacing: 10
|
||||
Button:
|
||||
disabled: app.state != 'connected'
|
||||
text: 'Read RSSI'
|
||||
on_press: app.read_rssi()
|
||||
Caption:
|
||||
text: 'RSSI Value:'
|
||||
Value:
|
||||
text: app.rssi
|
||||
|
||||
ToggleButton:
|
||||
disabled: app.state != 'connected'
|
||||
text: "Enable notifications"
|
||||
size_hint_y: .2
|
||||
on_state: app.enable_notifications(self.state == 'down')
|
||||
BoxLayout:
|
||||
id: queue_box
|
||||
orientation: 'vertical'
|
||||
disabled: app.state != 'connected'
|
||||
|
||||
BoxLayout:
|
||||
orientation: 'horizontal'
|
||||
Caption:
|
||||
text: 'Enable BLE queue timeout:'
|
||||
CheckBox:
|
||||
id: timeout_checkbox
|
||||
active: app.queue_timeout_enabled
|
||||
on_active: app.queue_timeout_enabled = self.active
|
||||
BoxLayout:
|
||||
orientation: 'horizontal'
|
||||
Caption:
|
||||
text: 'BLE queue timeout (ms):'
|
||||
TextInput:
|
||||
disabled: queue_box.disabled or not timeout_checkbox.active
|
||||
input_filter: 'int'
|
||||
multiline: False
|
||||
text: app.queue_timeout
|
||||
on_text: app.queue_timeout = self.text
|
||||
BoxLayout:
|
||||
Button:
|
||||
text: 'Apply queue settings'
|
||||
on_press: app.set_queue_settings()
|
||||
|
||||
BoxLayout:
|
||||
disabled: app.state != 'connected'
|
||||
orientation: 'vertical'
|
||||
BoxLayout:
|
||||
orientation: 'horizontal'
|
||||
Caption:
|
||||
text: 'Transmission interval (ms):'
|
||||
TextInput:
|
||||
input_filter: 'int'
|
||||
multiline: False
|
||||
text: app.incremental_interval
|
||||
on_text: app.incremental_interval = self.text
|
||||
BoxLayout:
|
||||
orientation: 'horizontal'
|
||||
Caption:
|
||||
text: 'Packet count limit:'
|
||||
TextInput:
|
||||
input_filter: 'int'
|
||||
multiline: False
|
||||
text: app.counter_max
|
||||
on_text: app.counter_max = self.text
|
||||
padding_bottom: '100sp'
|
||||
ToggleButton:
|
||||
width: self.texture_size[0] + 50
|
||||
text: "Enable transmission"
|
||||
on_state: app.enable_counter(self.state == 'down')
|
||||
17
libs/able/testapps/bletest/buildozer.spec
Normal file
17
libs/able/testapps/bletest/buildozer.spec
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
[app]
|
||||
title = BLE functions test
|
||||
version = 1.0
|
||||
package.name = kivy_ble_test
|
||||
package.domain = org.kivy
|
||||
source.dir = .
|
||||
source.include_exts = py,png,jpg,kv,atlas
|
||||
android.permissions = BLUETOOTH, BLUETOOTH_ADMIN, ACCESS_FINE_LOCATION
|
||||
requirements = python3,kivy,android,able_recipe
|
||||
|
||||
# (str) Android's logcat filters to use
|
||||
android.logcat_filters = *:S python:D
|
||||
|
||||
[buildozer]
|
||||
warn_on_root = 1
|
||||
# (int) Log level (0 = error only, 1 = info, 2 = debug (with command output))
|
||||
log_level = 2
|
||||
245
libs/able/testapps/bletest/main.py
Normal file
245
libs/able/testapps/bletest/main.py
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
"""Connect to "KivyBLETest" server and test various BLE functions
|
||||
"""
|
||||
import time
|
||||
|
||||
from able import AdapterState, GATT_SUCCESS, BluetoothDispatcher
|
||||
from kivy.app import App
|
||||
from kivy.clock import Clock
|
||||
from kivy.config import Config
|
||||
from kivy.properties import BooleanProperty, StringProperty
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.storage.jsonstore import JsonStore
|
||||
|
||||
Config.set('kivy', 'log_level', 'debug')
|
||||
Config.set('kivy', 'log_enable', '1')
|
||||
|
||||
|
||||
class MainLayout(BoxLayout):
|
||||
pass
|
||||
|
||||
|
||||
class BLETestApp(App):
|
||||
ble = BluetoothDispatcher()
|
||||
adapter_state = StringProperty('')
|
||||
state = StringProperty('')
|
||||
test_string = StringProperty('')
|
||||
rssi = StringProperty('')
|
||||
notification_value = StringProperty('')
|
||||
counter_value = StringProperty('')
|
||||
increment_count_value = StringProperty('')
|
||||
incremental_interval = StringProperty('100')
|
||||
counter_max = StringProperty('128')
|
||||
counter_value = StringProperty('')
|
||||
counter_state = StringProperty('')
|
||||
counter_total_time = StringProperty('')
|
||||
queue_timeout_enabled = BooleanProperty(True)
|
||||
queue_timeout = StringProperty('1000')
|
||||
device_name = StringProperty('KivyBLETest')
|
||||
device_address = StringProperty('')
|
||||
autoconnect = BooleanProperty(False)
|
||||
|
||||
store = JsonStore('bletestapp.json')
|
||||
|
||||
uids = {
|
||||
'string': '0d01',
|
||||
'counter_reset': '0d02',
|
||||
'counter_increment': '0d03',
|
||||
'counter_read': '0d04',
|
||||
'notifications': '0d05'
|
||||
}
|
||||
|
||||
def build(self):
|
||||
if self.store.exists('device'):
|
||||
self.device_address = self.store.get('device')['address']
|
||||
else:
|
||||
self.device_address = ''
|
||||
return MainLayout()
|
||||
|
||||
def on_pause(self):
|
||||
return True
|
||||
|
||||
def on_resume(self):
|
||||
pass
|
||||
|
||||
def init(self):
|
||||
self.set_queue_settings()
|
||||
self.ble.bind(on_device=self.on_device)
|
||||
self.ble.bind(on_scan_started=self.on_scan_started)
|
||||
self.ble.bind(on_scan_completed=self.on_scan_completed)
|
||||
self.ble.bind(on_bluetooth_adapter_state_change=self.on_bluetooth_adapter_state_change)
|
||||
self.ble.bind(
|
||||
on_connection_state_change=self.on_connection_state_change)
|
||||
self.ble.bind(on_services=self.on_services)
|
||||
self.ble.bind(on_characteristic_read=self.on_characteristic_read)
|
||||
self.ble.bind(on_characteristic_changed=self.on_characteristic_changed)
|
||||
self.ble.bind(on_rssi_updated=self.on_rssi_updated)
|
||||
|
||||
def start_scan(self):
|
||||
if not self.state:
|
||||
self.init()
|
||||
self.state = 'scan_start'
|
||||
self.ble.close_gatt()
|
||||
self.ble.start_scan()
|
||||
|
||||
def connect_by_mac_address(self):
|
||||
self.store.put('device', address=self.device_address)
|
||||
if not self.state:
|
||||
self.init()
|
||||
self.state = 'try_connect'
|
||||
self.ble.close_gatt()
|
||||
try:
|
||||
self.ble.connect_by_device_address(
|
||||
self.device_address,
|
||||
autoconnect=self.autoconnect,
|
||||
)
|
||||
except ValueError as exc:
|
||||
self.state = str(exc)
|
||||
|
||||
def on_scan_started(self, ble, success):
|
||||
self.state = 'scan' if success else 'scan_error'
|
||||
|
||||
def on_device(self, ble, device, rssi, advertisement):
|
||||
if self.state != 'scan':
|
||||
return
|
||||
if device.getName() == self.device_name:
|
||||
self.device = device
|
||||
self.state = 'found'
|
||||
self.ble.stop_scan()
|
||||
|
||||
def on_scan_completed(self, ble):
|
||||
if self.device:
|
||||
self.ble.connect_gatt(
|
||||
self.device,
|
||||
autoconnect=self.autoconnect,
|
||||
)
|
||||
|
||||
def on_connection_state_change(self, ble, status, state):
|
||||
if status == GATT_SUCCESS:
|
||||
if state:
|
||||
self.ble.discover_services()
|
||||
else:
|
||||
self.state = 'disconnected'
|
||||
else:
|
||||
self.state = 'connection_error'
|
||||
|
||||
def on_services(self, ble, status, services):
|
||||
if status != GATT_SUCCESS:
|
||||
self.state = 'services_error'
|
||||
return
|
||||
self.state = 'connected'
|
||||
self.services = services
|
||||
self.read_test_string(ble)
|
||||
self.characteristics = {
|
||||
'counter_increment': self.services.search(
|
||||
self.uids['counter_increment']),
|
||||
'counter_reset': self.services.search(
|
||||
self.uids['counter_reset']),
|
||||
}
|
||||
|
||||
def on_bluetooth_adapter_state_change(self, ble, state):
|
||||
self.adapter_state = AdapterState(state).name
|
||||
|
||||
def read_rssi(self):
|
||||
self.rssi = '...'
|
||||
result = self.ble.update_rssi()
|
||||
|
||||
def on_rssi_updated(self, ble, rssi, status):
|
||||
self.rssi = str(rssi) if status == GATT_SUCCESS else f"Bad status: {status}"
|
||||
|
||||
def read_test_string(self, ble):
|
||||
characteristic = self.services.search(self.uids['string'])
|
||||
if characteristic:
|
||||
ble.read_characteristic(characteristic)
|
||||
else:
|
||||
self.test_string = 'not found'
|
||||
|
||||
def read_remote_counter(self):
|
||||
characteristic = self.services.search(self.uids['counter_read'])
|
||||
if characteristic:
|
||||
self.ble.read_characteristic(characteristic)
|
||||
else:
|
||||
self.counter_value = 'error'
|
||||
|
||||
def enable_notifications(self, enable):
|
||||
if enable:
|
||||
self.notification_value = '0'
|
||||
characteristic = self.services.search(self.uids['notifications'])
|
||||
if characteristic:
|
||||
self.ble.enable_notifications(characteristic, enable)
|
||||
else:
|
||||
self.notification_value = 'error'
|
||||
|
||||
def enable_counter(self, enable):
|
||||
if enable:
|
||||
self.counter_state = 'init'
|
||||
interval = int(self.incremental_interval) * .001
|
||||
Clock.schedule_interval(self.counter_next, interval)
|
||||
else:
|
||||
Clock.unschedule(self.counter_next)
|
||||
if self.counter_state != 'stop':
|
||||
self.counter_state = 'stop'
|
||||
self.read_remote_counter()
|
||||
|
||||
def counter_next(self, dt):
|
||||
if self.counter_state == 'init':
|
||||
self.counter_started_time = time.time()
|
||||
self.counter_total_time = ''
|
||||
self.reset_remote_counter()
|
||||
self.increment_remote_counter()
|
||||
elif self.counter_state == 'enabled':
|
||||
if int(self.increment_count_value) < int(self.counter_max):
|
||||
self.increment_remote_counter()
|
||||
else:
|
||||
self.enable_counter(False)
|
||||
|
||||
def reset_remote_counter(self):
|
||||
self.increment_count_value = '0'
|
||||
self.counter_value = ''
|
||||
self.ble.write_characteristic(self.characteristics['counter_reset'], [])
|
||||
self.counter_state = 'enabled'
|
||||
|
||||
def on_characteristic_read(self, ble, characteristic, status):
|
||||
uuid = characteristic.getUuid().toString()
|
||||
if self.uids['string'] in uuid:
|
||||
self.update_string_value(characteristic, status)
|
||||
elif self.uids['counter_read'] in uuid:
|
||||
self.counter_total_time = str(
|
||||
time.time() - self.counter_started_time)
|
||||
self.update_counter_value(characteristic, status)
|
||||
|
||||
def update_string_value(self, characteristic, status):
|
||||
result = 'ERROR'
|
||||
if status == GATT_SUCCESS:
|
||||
value = characteristic.getStringValue(0)
|
||||
if value == 'test':
|
||||
result = 'OK'
|
||||
self.test_string = result
|
||||
|
||||
def increment_remote_counter(self):
|
||||
characteristic = self.characteristics['counter_increment']
|
||||
self.ble.write_characteristic(characteristic, [])
|
||||
prev_value = int(self.increment_count_value)
|
||||
self.increment_count_value = str(prev_value + 1)
|
||||
|
||||
def update_counter_value(self, characteristic, status):
|
||||
if status == GATT_SUCCESS:
|
||||
self.counter_value = characteristic.getStringValue(0)
|
||||
else:
|
||||
self.counter_value = 'ERROR'
|
||||
|
||||
def set_queue_settings(self):
|
||||
self.ble.set_queue_timeout(None if not self.queue_timeout_enabled
|
||||
else int(self.queue_timeout) * .001)
|
||||
|
||||
def on_characteristic_changed(self, ble, characteristic):
|
||||
uuid = characteristic.getUuid().toString()
|
||||
if self.uids['notifications'] in uuid:
|
||||
prev_value = self.notification_value
|
||||
value = int(characteristic.getStringValue(0))
|
||||
if (prev_value == 'error') or (value != int(prev_value) + 1):
|
||||
value = 'error'
|
||||
self.notification_value = str(value)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
BLETestApp().run()
|
||||
95
libs/able/testapps/bletest/server.go
Normal file
95
libs/able/testapps/bletest/server.go
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
// +build
|
||||
|
||||
// based on https://github.com/paypal/gatt/blob/master/examples/server.go
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
"github.com/paypal/gatt"
|
||||
"github.com/paypal/gatt/linux/cmd"
|
||||
)
|
||||
|
||||
|
||||
var DefaultServerOptions = []gatt.Option{
|
||||
gatt.LnxMaxConnections(1),
|
||||
gatt.LnxDeviceID(-1, false),
|
||||
gatt.LnxSetAdvertisingParameters(&cmd.LESetAdvertisingParameters{
|
||||
AdvertisingIntervalMin: 0x04ff,
|
||||
AdvertisingIntervalMax: 0x04ff,
|
||||
AdvertisingChannelMap: 0x7,
|
||||
}),
|
||||
}
|
||||
|
||||
|
||||
func NewTestPythonService() *gatt.Service {
|
||||
n := 0
|
||||
s := gatt.NewService(gatt.MustParseUUID("16fe0d00-c111-11e3-b8c8-0002a5d5c51b"))
|
||||
|
||||
s.AddCharacteristic(gatt.MustParseUUID("16fe0d01-c111-11e3-b8c8-0002a5d5c51b")).HandleReadFunc(
|
||||
func(rsp gatt.ResponseWriter, req *gatt.ReadRequest) {
|
||||
n = 0
|
||||
log.Println("Echo")
|
||||
fmt.Fprintf(rsp, "test")
|
||||
})
|
||||
s.AddCharacteristic(gatt.MustParseUUID("16fe0d02-c111-11e3-b8c8-0002a5d5c51b")).HandleWriteFunc(
|
||||
func(r gatt.Request, data []byte) (status byte) {
|
||||
n = 0
|
||||
log.Println("Reset counter")
|
||||
return gatt.StatusSuccess
|
||||
})
|
||||
s.AddCharacteristic(gatt.MustParseUUID("16fe0d03-c111-11e3-b8c8-0002a5d5c51b")).HandleWriteFunc(
|
||||
func(r gatt.Request, data []byte) (status byte) {
|
||||
n++
|
||||
log.Println("Increment counter")
|
||||
return gatt.StatusSuccess
|
||||
})
|
||||
s.AddCharacteristic(gatt.MustParseUUID("16fe0d04-c111-11e3-b8c8-0002a5d5c51b")).HandleReadFunc(
|
||||
func(rsp gatt.ResponseWriter, req *gatt.ReadRequest) {
|
||||
log.Println("Response counter: ", n)
|
||||
fmt.Fprintf(rsp, "%d", n)
|
||||
})
|
||||
|
||||
s.AddCharacteristic(gatt.MustParseUUID("16fe0d05-c111-11e3-b8c8-0002a5d5c51b")).HandleNotifyFunc(
|
||||
func(r gatt.Request, n gatt.Notifier) {
|
||||
log.Println("Notifications enabled")
|
||||
cnt := 1
|
||||
for !n.Done() {
|
||||
fmt.Fprintf(n, "%d", cnt)
|
||||
cnt++
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
log.Println("Notifications disabled")
|
||||
})
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
|
||||
func main() {
|
||||
d, err := gatt.NewDevice(DefaultServerOptions...)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to open device, err: %s", err)
|
||||
}
|
||||
|
||||
d.Handle(
|
||||
gatt.CentralConnected(func(c gatt.Central) { fmt.Println("Connect: ", c.ID()) }),
|
||||
gatt.CentralDisconnected(func(c gatt.Central) { fmt.Println("Disconnect: ", c.ID()) }),
|
||||
)
|
||||
|
||||
onStateChanged := func(d gatt.Device, s gatt.State) {
|
||||
fmt.Printf("State: %s\n", s)
|
||||
switch s {
|
||||
case gatt.StatePoweredOn:
|
||||
s1 := NewTestPythonService()
|
||||
d.AddService(s1)
|
||||
d.AdvertiseNameAndServices("KivyBLETest", []gatt.UUID{s1.UUID()})
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
d.Init(onStateChanged)
|
||||
select {}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue