-
Notifications
You must be signed in to change notification settings - Fork 23
[WIP] Ofono bridge #83
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: devel
Are you sure you want to change the base?
Changes from 17 commits
8b385ae
e1a63fb
f43ea67
1994095
0413ddf
ff3d603
b5d29b0
67c2baa
0966d0e
12d27f9
41ff44c
e1f101b
f085e7b
2a084a6
08dc9e8
da2fb47
1650127
0cf96f2
8e50fd7
01e72d3
1b5c9e5
248a30a
54d548f
49a1ee1
becaef5
85e74ce
2a40a20
47350dc
917f7f6
44d6f44
b04f185
9887af1
85cf72c
377c146
1d29e7f
f92f021
378593e
c787dc3
ec17696
886a243
801fb09
3a3500b
65e450e
bb9e456
5af4c3c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| KERNEL=="ttyAMA0", ENV{OFONO_DRIVER}="sim900" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,9 @@ | ||
| from helpers import setup_logger | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wait, why? I think you need to revert this particular change, which reverses our "setup_logger" integration work
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (as in, revert the logger-> logging replacements, not the from_string addition) |
||
| logger = setup_logger(__name__, "warning") | ||
|
|
||
| import vobject | ||
|
|
||
| from address_book import Contact | ||
| from helpers import setup_logger | ||
|
|
||
| logger = setup_logger(__name__) | ||
|
|
||
|
|
||
| class VCardContactConverter(object): | ||
|
|
@@ -52,3 +52,9 @@ def from_vcards(contact_card_files): | |
| contacts += VCardContactConverter.parse_vcard_file(file_path) | ||
| logger.info("finished : {} contacts loaded", len(contacts)) | ||
| return [VCardContactConverter.to_zpui_contact(c) for c in contacts] | ||
|
|
||
| @classmethod | ||
| def from_string(cls, vcard_string): | ||
| # type: (str) -> list | ||
| # Returns a list of ZPUI contacts from a string in vcard format | ||
| return [c for c in vobject.readComponents(vcard_string, ignoreUnreadable=True)] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| import errno | ||
| import logging | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unnecessary import |
||
| import os | ||
| from time import sleep | ||
|
|
||
| import pydbus | ||
| from dbus.mainloop.glib import DBusGMainLoop | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
| from gi.repository import GLib | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same for
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It appears the gi module is easily installable once I do
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Package created ( |
||
|
|
||
| from helpers import Singleton, setup_logger | ||
|
|
||
| logger = setup_logger(__name__, 'debug') | ||
| logging.basicConfig(level=logging.DEBUG) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unused statement - we already have setup_logger |
||
|
|
||
|
|
||
| class OfonoBridge(object): | ||
| """ | ||
| Generic util class to bridge between ZPUI and ofono backend through D-Bus | ||
| """ | ||
|
|
||
| def __init__(self): | ||
| self.modem_path = '/sim900_0' | ||
| self._check_default_modem() | ||
|
|
||
| def _check_default_modem(self): | ||
| bus = pydbus.SystemBus() | ||
| manager = bus.get('org.ofono', '/') | ||
| modem_path = manager.GetModems()[0][0] | ||
| if modem_path != self.modem_path: | ||
| raise ValueError("Default modem should be '{}', was '{}'".format(self.modem_path, modem_path)) | ||
|
|
||
| def start(self): | ||
| self.power_on() | ||
| self._init_messages() | ||
| self._listen_messages() | ||
|
|
||
| @property | ||
| def _bus(self): | ||
| """SystemBus().get() returns a snapshot of the current exposed methods. Having it as a property ensures we always | ||
| have the latest snapshot (as opposed to storing it on start/after-init)""" | ||
| return pydbus.SystemBus().get('org.ofono', self.modem_path) | ||
|
|
||
| @property | ||
| def message_manager(self): | ||
| return self._get_dbus_interface('MessageManager') | ||
|
|
||
| def _get_dbus_interface(self, name): | ||
| full_name = name if name.startswith('org.ofono') else 'org.ofono.{}'.format(name) | ||
| if full_name in self._bus.GetProperties()['Interfaces']: | ||
| return self._bus[full_name] | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And if full_name is not found, it silently returns None?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right we should raise an error |
||
| raise Exception("Interface '{}' wasn't found on ofono D-Bus".format(full_name)) | ||
|
|
||
| def power_on(self): | ||
| if self._bus.GetProperties()["Powered"]: | ||
| logger.info("Modem already powered up !") | ||
| else: | ||
| logger.info("Powering up modem...") | ||
| try: | ||
| self._bus.SetProperty("Powered", pydbus.Variant('b', True)) | ||
| sleep(2) # Let the modem some time to initialize | ||
| except Exception as e: | ||
| logger.error("Couldn't power up the modem !") | ||
| logger.exception(e) | ||
|
|
||
| def power_off(self): | ||
| self._bus.SetProperty("Powered", pydbus.Variant('b', False)) | ||
|
|
||
| def send_sms(self, to, content): # todo : untested | ||
| self.message_manager.SendMessage(to, content) | ||
| logger.info("Sending message to '{}'".format(to)) | ||
| ConversationManager().on_new_message_sent(to, content) | ||
|
|
||
| @staticmethod | ||
| def on_message_received(message, details, path=None, interface=None): # todo : untested | ||
| logger.info("Got message with path {}".format(path)) | ||
| ConversationManager().on_new_message_received(message, details) | ||
|
|
||
| def _listen_messages(self): | ||
| logger.info("Connecting to dbus callbacks") | ||
| self.message_manager.IncomingMessage.connect(self.on_message_received) | ||
| self.message_manager.ImmediateMessage.connect(self.on_message_received) | ||
|
|
||
| def _init_messages(self): | ||
| self.message_manager.SetProperty("UseDeliveryReports", pydbus.Variant('b', True)) | ||
|
|
||
|
|
||
| class ConversationManager(Singleton): | ||
| """ | ||
| Singleton dedicated to conversations. Logs every message sent and received in a flat file | ||
| for any given phone number | ||
| """ | ||
|
|
||
| def __init__(self): | ||
| super(ConversationManager, self).__init__() | ||
| self.folder = os.path.expanduser("~/.phone/sms/") # todo: store as a constant somewhere | ||
| self._create_folder() | ||
|
|
||
| def _create_folder(self): | ||
| if not os.path.exists(self.folder): | ||
| try: | ||
| os.makedirs(self.folder) | ||
| except OSError as exc: # os.makedirs(exist_ok=True) does not exist for python <= 3.2 | ||
| if exc.errno == errno.EEXIST and os.path.isdir(self.folder): | ||
| pass | ||
| else: | ||
| raise | ||
|
|
||
| def on_new_message_sent(self, to, content): | ||
| logger.info("Sent message to '{}'".format(to)) | ||
| self._write_log(to, self._format_log(content, from_me=True)) | ||
|
|
||
| def on_new_message_received(self, content, details): | ||
| origin = details['Sender'] | ||
| logger.info("Received message from'{}'".format(origin)) | ||
| self._write_log(origin, self._format_log(content, from_me=True)) | ||
|
|
||
| def _write_log(self, phone_number, log): | ||
| with open(self._get_log_path(phone_number), 'a+') as log_file: | ||
| log_file.write(log) | ||
|
|
||
| def _get_log_path(self, phone_number): | ||
| file_name = "{}.txt".format(phone_number) | ||
| return os.path.join(self.folder, file_name) | ||
|
|
||
| @staticmethod | ||
| def _format_log(content, from_me=True): | ||
| start_char = '>' if from_me else '<' | ||
| return "{prefix}\t{msg}\n".format(prefix=start_char, msg=content) | ||
|
|
||
|
|
||
| def main(): | ||
| DBusGMainLoop(set_as_default=True) # has to be called first | ||
| ofono = OfonoBridge() | ||
| try: | ||
| ofono.start() | ||
| mainloop = GLib.MainLoop() # todo : own thread | ||
| mainloop.run() | ||
| except KeyboardInterrupt: | ||
| logger.info("Caught CTRL-C : exiting without powering off...") | ||
| except Exception as e: | ||
| logger.error("Error while starting ofono bridge ! Powering off...") | ||
| logger.exception(e) | ||
| ofono.power_off() | ||
|
|
||
|
|
||
| if __name__ == '__main__': | ||
| main() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| pydbus | ||
| luma.oled | ||
| python-nmap | ||
| smspdu | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll be shipping this file in our own Debian package, most likely