Skip to content

Commit 41a5ad5

Browse files
author
Alex R
authored
Siglo service (#30)
* add daemon flag * add siglo service calling siglo with daemon flag * add daemon module * disable the buffering of STDOUT and STDERR * save last paired device * filter SMS from all notifications * add some fluff * try to load defaults when daemon starts * change var names * add insensitive Pair Device button to menu * delete style.css * add reliability and error reporting improvements * add some todo's for pair device sensitivity * make pair button available once device is sucessfully scanned * pair switch ui working * stay connected with pair button * working pair button (needs better error handling) * start siglo service on pair, stop on unpair * more daemon improvements * always daemon-reload * try to actually send a text through * fail sort of gracefully on restart without enabling bluetooth * add 3 space buffer before message * add a beta label to the new pair device check item
1 parent 6356a45 commit 41a5ad5

12 files changed

+261
-36
lines changed

.vscode/settings.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"python.formatting.provider": "black"
3+
}

data/meson.build

+3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ install_data(join_paths('icons', 'org.gnome.siglo.svg'),
1818
install_dir: join_paths(get_option('datadir'), 'icons')
1919
)
2020

21+
install_data('siglo.service', install_dir: '/etc/systemd/user/')
22+
2123
appstream_file = i18n.merge_file(
2224
input: 'org.gnome.siglo.appdata.xml.in',
2325
output: 'org.gnome.siglo.appdata.xml',
@@ -43,3 +45,4 @@ if compile_schemas.found()
4345
args: ['--strict', '--dry-run', meson.current_source_dir()]
4446
)
4547
endif
48+

data/siglo.service

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[Unit]
2+
Description=siglo service
3+
[Service]
4+
ExecStart=siglo --daemon
5+
Environment=PYTHONUNBUFFERED=1

src/ble_dfu.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -262,4 +262,4 @@ def step_nine(self):
262262
def get_init_bin_array(self):
263263
# Open the DAT file and create array of its contents
264264
init_bin_array = array("B", open(self.datfile_path, "rb").read())
265-
return init_bin_array
265+
return init_bin_array

src/bluetooth.py

+64-13
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from os import sync
12
import gatt
23
import datetime
34
import struct
@@ -26,39 +27,60 @@ def get_current_time():
2627

2728

2829
def get_default_adapter():
29-
""" https://stackoverflow.com/a/49017827 """
30+
"""https://stackoverflow.com/a/49017827"""
3031
import dbus
32+
3133
bus = dbus.SystemBus()
3234
try:
33-
manager = dbus.Interface(bus.get_object('org.bluez', '/'),
34-
'org.freedesktop.DBus.ObjectManager')
35+
manager = dbus.Interface(
36+
bus.get_object("org.bluez", "/"), "org.freedesktop.DBus.ObjectManager"
37+
)
3538
except dbus.exceptions.DBusException:
3639
raise BluetoothDisabled
3740

3841
for path, ifaces in manager.GetManagedObjects().items():
39-
if ifaces.get('org.bluez.Adapter1') is None:
42+
if ifaces.get("org.bluez.Adapter1") is None:
4043
continue
41-
return path.split('/')[-1]
44+
return path.split("/")[-1]
4245
raise NoAdapterFound
4346

4447

4548
class InfiniTimeManager(gatt.DeviceManager):
4649
def __init__(self):
4750
self.conf = config()
4851
self.device_set = set()
49-
self.adapter_name = get_default_adapter()
5052
self.alias = None
51-
self.scan_result = False
53+
if not self.conf.get_property("paired"):
54+
self.scan_result = False
55+
self.adapter_name = get_default_adapter()
56+
self.conf.set_property("adapter", self.adapter_name)
57+
else:
58+
self.scan_result = True
59+
self.adapter_name = self.conf.get_property("adapter")
5260
self.mac_address = None
5361
super().__init__(self.adapter_name)
5462

5563
def get_scan_result(self):
64+
if self.conf.get_property("paired"):
65+
self.scan_result = True
5666
return self.scan_result
5767

68+
def get_device_set(self):
69+
if self.conf.get_property("paired"):
70+
self.device_set.add(self.conf.get_property("last_paired_device"))
71+
return self.device_set
72+
73+
def get_adapter_name(self):
74+
if self.conf.get_property("paired"):
75+
return self.conf.get_property("adapter")
76+
return get_default_adapter()
77+
5878
def set_mac_address(self, mac_address):
5979
self.mac_address = mac_address
6080

6181
def get_mac_address(self):
82+
if self.conf.get_property("paired"):
83+
self.mac_address = self.conf.get_property("last_paired_device")
6284
return self.mac_address
6385

6486
def set_timeout(self, timeout):
@@ -81,7 +103,12 @@ def scan_for_infinitime(self):
81103

82104

83105
class InfiniTimeDevice(gatt.Device):
84-
def connect(self):
106+
def __init__(self, mac_address, manager):
107+
self.conf = config()
108+
super().__init__(mac_address, manager)
109+
110+
def connect(self, sync_time):
111+
self.sync_time = sync_time
85112
self.successful_connection = True
86113
super().connect()
87114

@@ -99,20 +126,44 @@ def disconnect_succeeded(self):
99126
print("[%s] Disconnected" % (self.mac_address))
100127

101128
def characteristic_write_value_succeeded(self, characteristic):
102-
self.disconnect()
129+
if not self.conf.get_property("paired"):
130+
self.disconnect()
103131

104132
def services_resolved(self):
105133
super().services_resolved()
106-
serv = next(
134+
self.serv = next(
107135
s for s in self.services if s.uuid == "00001805-0000-1000-8000-00805f9b34fb"
108136
)
109-
char = next(
137+
self.char = next(
110138
c
111-
for c in serv.characteristics
139+
for c in self.serv.characteristics
112140
if c.uuid == "00002a2b-0000-1000-8000-00805f9b34fb"
113141
)
114142

115-
char.write_value(get_current_time())
143+
self.alert_service = next(
144+
a for a in self.services if a.uuid == "00001811-0000-1000-8000-00805f9b34fb"
145+
)
146+
147+
self.new_alert_characteristic = next(
148+
n
149+
for n in self.alert_service.characteristics
150+
if n.uuid == "00002a46-0000-1000-8000-00805f9b34fb"
151+
)
152+
153+
if self.sync_time:
154+
self.char.write_value(get_current_time())
155+
156+
def send_notification(self, alert_dict):
157+
message = alert_dict["message"]
158+
arr = bytearray(message, "utf-8")
159+
# self.new_alert_characteristic.write_value(
160+
# b" Reports of my death were greatly exaggerated. So how was your day?"
161+
# )
162+
self.new_alert_characteristic.write_value(arr)
163+
164+
165+
class BluetoothDisabled(Exception):
166+
pass
116167

117168

118169
class BluetoothDisabled(Exception):

src/config.py

+13-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
import configparser
22
import xdg.BaseDirectory
3+
import distutils
4+
import distutils.util
35
from pathlib import Path
46

57

68
class config:
79
# Class constants
8-
default_config = {"mode": "singleton", "deploy_type": "quick"}
10+
default_config = {
11+
"mode": "singleton",
12+
"deploy_type": "quick",
13+
"last_paired_device": "None",
14+
"paired": "False",
15+
"adapter": "None"
16+
}
917
config_dir = xdg.BaseDirectory.xdg_config_home
1018
config_file = config_dir + "/siglo.ini"
1119

@@ -33,13 +41,14 @@ def file_valid(self):
3341
def get_property(self, key):
3442
config = configparser.ConfigParser()
3543
config.read(self.config_file)
36-
return config["settings"][key]
44+
prop = config["settings"][key]
45+
if key == "paired":
46+
prop = bool(distutils.util.strtobool(prop))
47+
return prop
3748

3849
def set_property(self, key, val):
3950
config = configparser.ConfigParser()
4051
config.read(self.config_file)
4152
config["settings"][key] = val
4253
with open(self.config_file, "w") as f:
4354
config.write(f)
44-
45-

src/daemon.py

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import gatt
2+
3+
import gi.repository.GLib as glib
4+
import dbus
5+
from dbus.mainloop.glib import DBusGMainLoop
6+
from .bluetooth import InfiniTimeManager, InfiniTimeDevice, NoAdapterFound
7+
from .config import config
8+
9+
10+
class daemon:
11+
def __init__(self):
12+
self.conf = config()
13+
self.manager = InfiniTimeManager()
14+
self.device = InfiniTimeDevice(manager=self.manager, mac_address=self.conf.get_property("last_paired_device"))
15+
self.device.connect(sync_time=False)
16+
17+
def scan_for_notifications(self):
18+
DBusGMainLoop(set_as_default=True)
19+
bus = dbus.SessionBus()
20+
bus.add_match_string_non_blocking(
21+
"eavesdrop=true, interface='org.freedesktop.Notifications', member='Notify'"
22+
)
23+
bus.add_message_filter(self.notifications)
24+
mainloop = glib.MainLoop()
25+
mainloop.run()
26+
27+
def notifications(self, bus, message):
28+
alert_dict = {}
29+
for arg in message.get_args_list():
30+
if isinstance(arg, dbus.Dictionary):
31+
if arg["desktop-entry"] == "sm.puri.Chatty":
32+
alert_dict["category"] = "SMS"
33+
alert_dict["sender"] = message.get_args_list()[3]
34+
alert_dict["message"] = " " + message.get_args_list()[4]
35+
alert_dict_empty = not alert_dict
36+
if len(alert_dict) > 0:
37+
self.device.send_notification(alert_dict)

src/main.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,4 @@ def gtk_style():
4646

4747
gtk_style()
4848
app = Application()
49-
return app.run(sys.argv)
49+
return app.run(sys.argv)

src/meson.build

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ configure_file(
2727

2828
siglo_sources = [
2929
'__init__.py',
30+
'daemon.py',
3031
'quick_deploy.py',
3132
'main.py',
3233
'config.py',

src/siglo.in

+20-7
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import os
55
import sys
66
import signal
77
import gettext
8+
import argparse
89

910
VERSION = '@VERSION@'
1011
pkgdatadir = '@pkgdatadir@'
@@ -14,12 +15,24 @@ sys.path.insert(1, pkgdatadir)
1415
signal.signal(signal.SIGINT, signal.SIG_DFL)
1516
gettext.install('siglo', localedir)
1617

17-
if __name__ == '__main__':
18-
import gi
18+
def main():
19+
p = argparse.ArgumentParser(description="app to sync InfiniTime watch")
20+
p.add_argument('--daemon', '-d', required=False, action='store_true', help="run as a service")
21+
args = p.parse_args()
22+
23+
if args.daemon:
24+
from siglo import daemon
25+
d = daemon.daemon()
26+
d.scan_for_notifications()
27+
else:
28+
import gi
1929

20-
from gi.repository import Gio
21-
resource = Gio.Resource.load(os.path.join(pkgdatadir, 'siglo.gresource'))
22-
resource._register()
30+
from gi.repository import Gio
31+
resource = Gio.Resource.load(os.path.join(pkgdatadir, 'siglo.gresource'))
32+
resource._register()
2333

24-
from siglo import main
25-
sys.exit(main.main(VERSION))
34+
from siglo import main
35+
sys.exit(main.main(VERSION))
36+
37+
if __name__ == '__main__':
38+
main()

0 commit comments

Comments
 (0)