-
-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for Async operations on Worker (#38)
* Add support for Async operations on Worker
- Loading branch information
Showing
5 changed files
with
353 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
#!/usr/bin/env python | ||
# -*- coding: UTF-8 -*- | ||
# vim: expandtab sw=4 ts=4 sts=4: | ||
# | ||
# Copyright © 2003 - 2018 Michal Čihař <michal@cihar.com> | ||
# | ||
# This file is part of python-gammu <https://wammu.eu/python-gammu/> | ||
# | ||
# This program is free software; you can redistribute it and/or modify | ||
# it under the terms of the GNU General Public License as published by | ||
# the Free Software Foundation; either version 2 of the License, or | ||
# (at your option) any later version. | ||
# | ||
# This program is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
# GNU General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU General Public License along | ||
# with this program; if not, write to the Free Software Foundation, Inc., | ||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
''' | ||
python-gammu - Phone communication libary | ||
Gammu asynchronous wrapper example with asyncio. This allows your application to care | ||
only about handling received data and not about phone communication | ||
details. | ||
''' | ||
|
||
import sys | ||
import gammu | ||
import gammu.asyncworker | ||
import asyncio | ||
|
||
|
||
async def send_message_async(state_machine, number, message): | ||
smsinfo = { | ||
'Class': -1, | ||
'Unicode': False, | ||
'Entries': [ | ||
{ | ||
'ID': 'ConcatenatedTextLong', | ||
'Buffer': message | ||
} | ||
]} | ||
# Encode messages | ||
encoded = gammu.EncodeSMS(smsinfo) | ||
# Send messages | ||
for message in encoded: | ||
# Fill in numbers | ||
message['SMSC'] = {'Location': 1} | ||
message['Number'] = number | ||
# Actually send the message | ||
await state_machine.send_sms_async(message) | ||
|
||
async def get_network_info(worker): | ||
info = await worker.get_network_info_async() | ||
print('NetworkName:',info['NetworkName']) | ||
print(' State:',info['State']) | ||
print(' NetworkCode:',info['NetworkCode']) | ||
print(' CID:',info['CID']) | ||
print(' LAC:',info['LAC']) | ||
|
||
async def get_info(worker): | ||
print('Phone infomation:') | ||
manufacturer = await worker.get_manufacturer_async() | ||
print(('{0:<15}: {1}'.format('Manufacturer', manufacturer))) | ||
model = await worker.get_model_async() | ||
print(('{0:<15}: {1} ({2})'.format('Model', model[0], model[1]))) | ||
imei = await worker.get_imei_async() | ||
print(('{0:<15}: {1}'.format('IMEI', imei))) | ||
firmware = await worker.get_firmware_async() | ||
print(('{0:<15}: {1}'.format('Firmware', firmware[0]))) | ||
|
||
async def main(): | ||
|
||
gammu.SetDebugFile(sys.stderr) | ||
gammu.SetDebugLevel('textall') | ||
|
||
config = dict(Device="/dev/ttyS6", Connection="at") | ||
worker = gammu.asyncworker.GammuAsyncWorker() | ||
worker.configure(config) | ||
|
||
try: | ||
await worker.init_async() | ||
|
||
await get_info(worker) | ||
await get_network_info(worker) | ||
|
||
await send_message_async(worker, '6700', 'BAL') | ||
|
||
# Just a busy waiting for event | ||
# We need to keep communication with phone to get notifications | ||
print('Press Ctrl+C to interrupt') | ||
while 1: | ||
try: | ||
signal = await worker.get_signal_quality_async() | ||
print('Signal is at {0:d}%'.format(signal['SignalPercent'])) | ||
except Exception as e: | ||
print('Exception reading signal: {0}'.format(e)) | ||
|
||
await asyncio.sleep(10); | ||
|
||
except Exception as e: | ||
print('Exception:') | ||
print(e) | ||
|
||
print("Terminate Start") | ||
await worker.terminate_async() | ||
print("Terminate Done") | ||
|
||
if __name__ == '__main__': | ||
event_loop = asyncio.get_event_loop() | ||
try: | ||
event_loop.run_until_complete(main()) | ||
finally: | ||
event_loop.close() | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,6 +33,7 @@ | |
__all__ = [ | ||
'data', | ||
'worker', | ||
'asyncworker', | ||
'smsd', | ||
'exception', | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
"""Async extensions for gammu.""" | ||
import asyncio | ||
import gammu | ||
import gammu.worker | ||
|
||
class GammuAsyncThread(gammu.worker.GammuThread): | ||
"""Thread for phone communication.""" | ||
|
||
def __init__(self, queue, config, callback): | ||
"""Initialize thread.""" | ||
super().__init__(queue, config, callback) | ||
|
||
def _do_command(self, future, cmd, params, percentage=100): | ||
"""Execute single command on phone.""" | ||
func = getattr(self._sm, cmd) | ||
result = None | ||
try: | ||
if params is None: | ||
result = func() | ||
elif isinstance(params, dict): | ||
result = func(**params) | ||
else: | ||
result = func(*params) | ||
except gammu.GSMError as info: | ||
errcode = info.args[0]["Code"] | ||
error = gammu.ErrorNumbers[errcode] | ||
self._callback(future, result, error, percentage) | ||
except Exception as exception: # pylint: disable=broad-except | ||
self._callback(future, None, exception, percentage) | ||
else: | ||
self._callback(future, result, None, percentage) | ||
|
||
|
||
class GammuAsyncWorker(gammu.worker.GammuWorker): | ||
"""Extend gammu worker class for async operations.""" | ||
|
||
def worker_callback(self, name, result, error, percents): | ||
"""Execute command from the thread worker.""" | ||
future = None | ||
if name == "Init" and self._init_future is not None: | ||
future = self._init_future | ||
elif name == "Terminate" and self._terminate_future is not None: | ||
# Set _kill to true on the base class to avoid waiting for termination | ||
self._thread._kill = True # pylint: disable=protected-access | ||
future = self._terminate_future | ||
elif hasattr(name, "set_result"): | ||
future = name | ||
|
||
if future is not None: | ||
if error is None: | ||
self._loop.call_soon_threadsafe(future.set_result, result) | ||
else: | ||
exception = error | ||
if not isinstance(error, Exception): | ||
exception = gammu.GSMError(error) | ||
self._loop.call_soon_threadsafe(future.set_exception, exception) | ||
|
||
def __init__(self): | ||
"""Initialize the worker class. | ||
@param callback: See L{GammuThread.__init__} for description. | ||
""" | ||
super().__init__(self.worker_callback) | ||
self._loop = asyncio.get_event_loop() | ||
self._init_future = None | ||
self._terminate_future = None | ||
self._thread = None | ||
|
||
async def init_async(self): | ||
"""Connect to phone.""" | ||
self._init_future = self._loop.create_future() | ||
|
||
self._thread = GammuAsyncThread(self._queue, self._config, self._callback) | ||
self._thread.start() | ||
|
||
await self._init_future | ||
self._init_future = None | ||
|
||
async def get_imei_async(self): | ||
"""Get the IMEI of the device.""" | ||
future = self._loop.create_future() | ||
self.enqueue(future, commands=[("GetIMEI", ())]) | ||
return await future | ||
|
||
async def get_network_info_async(self): | ||
"""Get the network info in the device.""" | ||
future = self._loop.create_future() | ||
self.enqueue(future, commands=[("GetNetworkInfo", ())]) | ||
return await future | ||
|
||
async def get_manufacturer_async(self): | ||
"""Get the manufacturer of the device.""" | ||
future = self._loop.create_future() | ||
self.enqueue(future, commands=[("GetManufacturer", ())]) | ||
return await future | ||
|
||
async def get_model_async(self): | ||
"""Get the model of the device.""" | ||
future = self._loop.create_future() | ||
self.enqueue(future, commands=[("GetModel", ())]) | ||
return await future | ||
|
||
async def get_firmware_async(self): | ||
"""Get the firmware version of the device.""" | ||
future = self._loop.create_future() | ||
self.enqueue(future, commands=[("GetFirmware", ())]) | ||
return await future | ||
|
||
async def get_signal_quality_async(self): | ||
"""Get signal quality from phone.""" | ||
future = self._loop.create_future() | ||
self.enqueue(future, commands=[("GetSignalQuality", ())]) | ||
result = await future | ||
return result | ||
|
||
async def send_sms_async(self, message): | ||
"""Send sms message via the phone.""" | ||
future = self._loop.create_future() | ||
self.enqueue(future, commands=[("SendSMS", [message])]) | ||
result = await future | ||
return result | ||
|
||
async def set_incoming_callback_async(self, callback): | ||
"""Set the callback to call from phone.""" | ||
future = self._loop.create_future() | ||
self.enqueue(future, commands=[("SetIncomingCallback", [callback])]) | ||
result = await future | ||
return result | ||
|
||
async def set_incoming_sms_async(self): | ||
"""Activate SMS notifications from phone.""" | ||
future = self._loop.create_future() | ||
self.enqueue(future, commands=[("SetIncomingSMS", ())]) | ||
result = await future | ||
return result | ||
|
||
async def terminate_async(self): | ||
"""Terminate phone communication.""" | ||
self._terminate_future = self._loop.create_future() | ||
self.enqueue("Terminate") | ||
await self._terminate_future | ||
|
||
while self._thread.is_alive(): | ||
await asyncio.sleep(5) | ||
self._thread = None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
# -*- coding: UTF-8 -*- | ||
# vim: expandtab sw=4 ts=4 sts=4: | ||
# | ||
# Copyright © 2003 - 2018 Michal Čihař <michal@cihar.com> | ||
# | ||
# This file is part of python-gammu <https://wammu.eu/python-gammu/> | ||
# | ||
# This program is free software; you can redistribute it and/or modify | ||
# it under the terms of the GNU General Public License as published by | ||
# the Free Software Foundation; either version 2 of the License, or | ||
# (at your option) any later version. | ||
# | ||
# This program is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
# GNU General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU General Public License along | ||
# with this program; if not, write to the Free Software Foundation, Inc., | ||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
# | ||
import asyncio | ||
import gammu.asyncworker | ||
from .test_dummy import DummyTest | ||
|
||
|
||
WORKER_EXPECT = [ | ||
('Init', None), | ||
('GetIMEI', '999999999999999'), | ||
('GetManufacturer', 'Gammu'), | ||
('GetNetworkInfo', | ||
{'CID': 'FACE', | ||
'GPRS': 'Attached', | ||
'LAC': 'B00B', | ||
'NetworkCode': '999 99', | ||
'NetworkName': '', | ||
'PacketCID': 'DEAD', | ||
'PacketLAC': 'BEEF', | ||
'PacketState': 'HomeNetwork', | ||
'State': 'HomeNetwork'}), | ||
('GetModel', ('unknown', 'Dummy')), | ||
#('GetFirmware', ('1.41.0', '20150101', 1.41)), # Mock is returning different values between the local workstation on the CI build | ||
('GetSignalQuality', {'BitErrorRate': 0, 'SignalPercent': 42, 'SignalStrength': 42}), | ||
('SendSMS', 255), | ||
('SetIncomingCallback', None), | ||
('SetIncomingSMS',None), | ||
('Terminate', None) | ||
] | ||
|
||
def async_test(coro): | ||
def wrapper(*args, **kwargs): | ||
loop = asyncio.new_event_loop() | ||
return loop.run_until_complete(coro(*args, **kwargs)) | ||
return wrapper | ||
|
||
class AsyncWorkerDummyTest(DummyTest): | ||
results = [] | ||
|
||
def callback(self, name, result, error, percents): | ||
self.results.append((name, result, error, percents)) | ||
|
||
@async_test | ||
async def test_worker_async(self): | ||
self.results = [] | ||
worker = gammu.asyncworker.GammuAsyncWorker() | ||
worker.configure(self.get_statemachine().GetConfig()) | ||
self.results.append(('Init', await worker.init_async())) | ||
self.results.append(('GetIMEI', await worker.get_imei_async())) | ||
self.results.append(('GetManufacturer', await worker.get_manufacturer_async())) | ||
self.results.append(('GetNetworkInfo', await worker.get_network_info_async())) | ||
self.results.append(('GetModel', await worker.get_model_async())) | ||
#self.results.append(('GetFirmware', await worker.get_firmware_async())) | ||
self.results.append(('GetSignalQuality', await worker.get_signal_quality_async())) | ||
message = { | ||
'Text': 'python-gammu testing message', | ||
'SMSC': {'Location': 1}, | ||
'Number': '555-555-1234', | ||
} | ||
self.results.append(('SendSMS', await worker.send_sms_async(message))) | ||
with self.assertRaises(TypeError): | ||
await worker.send_sms_async(42) | ||
with self.assertRaises(Exception): | ||
await worker.send_sms_async(dict(42)) | ||
self.results.append(('SetIncomingCallback', await worker.set_incoming_callback_async(self.callback))) | ||
self.results.append(('SetIncomingSMS', await worker.set_incoming_sms_async())) | ||
self.results.append(('Terminate', await worker.terminate_async())) | ||
self.maxDiff = None | ||
self.assertEqual(WORKER_EXPECT, self.results) | ||
|