pax_global_header 0000666 0000000 0000000 00000000064 12576466102 0014523 g ustar 00root root 0000000 0000000 52 comment=a7067334f93f2dde86eaade55723ead1b1ce4e8c
wader-0.5.13/ 0000775 0000000 0000000 00000000000 12576466102 0012713 5 ustar 00root root 0000000 0000000 wader-0.5.13/CHANGELOG 0000664 0000000 0000000 00000020353 12576466102 0014130 0 ustar 00root root 0000000 0000000 =======================================
Wader-*.*.*
Overview of changes since Wader-0.3.6
=======================================
Releases are usually made in sync with stable bcm releases. List of changes:
* See SVN log for details.
=======================================
Wader-0.3.6
Overview of changes since Wader-0.3.5.2
=======================================
This is a new minor stable release of Wader. List of changes:
* wader-core
* DNS update mechanisms have been harmonized, see #154. On Ubuntu
hardy, wader-core does not depend on resolvconf anymore as it
causes conflicts with the stock NetworkManager.
* New device supported: Novatel MC990D
* Start and restart scripts merged into one: wader-core-ctl
* wader-gtk
* Fix PUK/PUK2 handling, see #148
* Make sure profiles work with GConf-DBus, see #144
* wader-doc
* Documentation migrated to Sphinx and revised
* wader-doc is no longer built by default, thus saving us from
maintaining many, buggy, dependencies.
=====================================
Wader-0.3.5.2
Overview of changes since Wader-0.3.5
=====================================
This is a new nano stable release of Wader. wader-0.3.5.1 was quickly
replaced by wader-0.3.5.2 as it didn't built on hardy. List of changes:
* wader-core
* Some HSO bugs introduced in 0.3.{4,5} have been fixed. This is what
you get when you don't test a release with every supported device.
* Do not execute set_network_type if network_type is None
* wader._version allows nano releases
* wader-core depends on resolvconf as its not included by default
on Hardy.
* wader-gtk
* Handle NoKeyringDaemonError exceptions, not finished. #149
=====================================
Wader-0.3.5
Overview of changes since Wader-0.3.4
=====================================
This is a new minor stable release of Wader. List of changes:
* wader-core
* DBusDevicePlugin merged into DevicePlugin
* Separate better the ModemManager and Wader exceptions
* get_radio_status return value fixed
* Some fixes for hotplugging events
* Huawei:
* get_driver_name fixed for Nozomi
* use HuaweiE620's cmd_dict rather than Huawei's
* Handle ^MODE: 0,2 in Huawei
* Handle error in ^CURC=1 command
* Do not use os.system but subprocess.call instead
* Set a registering lock to avoid multiple attempts of registration
* Add the U1900 band
* org.freedesktop.ModemManager.Modem.Simple interface implemented
(and untested)
* Device creation time has been reduced
* Fix "undefined reference to ser" while probing ports
* Unused stuff removed
* wader-gtk
* Many profiles bugs fixed
* Only ask for profile when self.profile is None
* Add Ctrl+Q accelerator to log window
* Stop throbber if device is not present
* Remove standard gtk symbols from translation
=====================================
Wader-0.3.4
Overview of changes since Wader-0.3.3
=====================================
This is a new minor stable release of Wader. List of changes:
* wader-core
* WVDIAL: Use either the 'Connected' string or the DNS info to ack that we are
connected.
* Get rid of python-axiom
* wader-gtk
* Handle gracefully errors in SetBand and SetConnectionMode
* Use translated strings for mode changes
=====================================
Wader-0.3.3
Overview of changes since Wader-0.3.2
=====================================
This is a new minor stable release of Wader. List of changes:
* wader-core
* Fix initialization routine so it bails with cards that insist on
replying SimBusy to +CPBR=? commands. Next time is necessary will be
requested again and will succeed.
* Use PDP context in HSO dialer
* Only enable/disable radio if necessary
* Some wvdial fixes make it more robust
* CLeanup CGMM response if echo was enabled
* Some ZTE love
* Fix dialup with Option GTM378, it must use the hso dialer.
* Better responsiveness in network registration state machine
* Many, many bug fixes.
* wader-gtk
* only enable PIN/PUK if what user entered is meaningful
* new wader icon
* make sure bytes are resetted
=====================================
Wader-0.3.2
Overview of changes since Wader-0.3.1
=====================================
This is a new minor stable release of Wader. List of changes:
* wader-core
* NetworkManager compatibility has been temporally disabled, our
three target distros ship with different snapshots of NM/MM and
they don't include some later patches required for compatibility.
See [0] for more info. This has the downside of firefox not realizing
that we are connected and it will insist on that we are disconnected. A
simple workaround is to check the "Work offline" checkbox in "File".
* New device added (untested): ZTE MF632
* NMPPPDDialer now listens to PropertyChanged signals
* Make sure dialers are unexported upon unsuccessful connection attempt.
* wader-core now depends on ozerocdoff.
* Many, many bug fixes.
* wader-gtk
* Problems with unsigned ints and some DNS have been fixed.
* Get rid of GtkSpinButton warning.
* Handle gracefully unsucessful connection attempts [1].
* Do not allow Set{Band,ConnectionMode} if we are connected.
[0] http://trac.warp.es/wader/ticket/133
[1] http://trac.warp.es/wader/ticket/132
=====================================
Wader-0.3.1
Overview of changes since Wader-0.3.0
=====================================
This is a new minor stable release of Wader. List of changes:
* wader-core
* Really restart the core upon upgrade (Debian/Ubuntu only)
* Plugins are only included once and at /usr/share/wader/plugins. An
upgrade routine has been added to ensure a smooth transition.
* wader-core now mimics the udis that ModemManager uses to export devices.
* wader-core enables/disables radio too now (+CFUN=0,1)
* Use o.fd.MM.Modem rather than o.fd.ModemManager for properties
* OS detection has been improved by not relying on lsb_release
* wader-core runs on python2.6
* wader-core now depends on python-messaging. A joint project between
Warp (Wader) & OpenShine (MobileManager) to create a solid SMS
encoding/decoding library.
* New models supported: Huawei E169, Huawei E180
* o.fd.DBus.Properties.GetAll implemented
* wader-gtk
* The UI has been gettex'd and there are Spanish and French translations
* The SMS/Contacts UI has been polished and some interesting
new features have been added:
* Support for multipart SMS (only sending for now)
* Support for categories (Inbox, Drafts and Sent for now)
* Support for storing SMS and sending it from SIM
* Support for searching contacts
* Support for searching SMS (not enabled unless pygtk >= 2.14.0
because of a bug on set_visible_func)
* A throbber is shown for every potentially long (IO) operations
* Log window now updates the log every second
* .desktop added for supported RPM systems
* Copyright changed to 'Wader contributors'
* Many, many bugs fixed
* doc
* User tutorial added
=====================================
Wader-0.3.0
Overview of changes since Wader-0.2.X
=====================================
This is a new major stable release of Wader. List of changes:
* wader-core
* Wader is the first project that implements the ModemManager API
apart from MM itself. It implements the whole MM API except for
the new Ericsson's MBM devices (introduced late in the MM
development cycle, will support them as soon as we get our hands
in one of them). For more info check out[0].
* Wader ditches the session bus and works exclusively on the system bus.
* Dialup support for system with NM 0.6.X, NM0.7 and NM0.7.X.
* Multiple active devices.
* Wader doesn't depends on hsolink any more.
* New devices supported: Option Icon 401, Huawei EM730V, Huawei K3520,
ZTE K3520 and ZTE K3565.
* Initial support for OSX. No dialup, no UI, only DBus functionality.
* wader-gtk
* Wader now uses gconf to store all its config settings.
* Initial support for SMS/Contacts. The UI is not that great, it will
be improved in next release.
[0] http://trac.warp.es/wader/wiki/WhatsModemManager
wader-0.5.13/CONTRIBUTING 0000664 0000000 0000000 00000005163 12576466102 0014552 0 ustar 00root root 0000000 0000000 ===========================
Guidelines for contributing
===========================
Coding guidelines
=================
Like any well behaved python project out there, we are `PEP-8`_ compliant.
All the gory details are in the link, a quick way to find out whether your
code is complaint or not is via lints, such as `pylint`_ or `pep8`_.
.. _PEP-8: http://www.python.org/dev/peps/pep-0008/
.. _pylint: http://pypi.python.org/pypi/pylint
.. _pep8: http://pypi.python.org/pypi/pep8
Documentation guidelines
========================
All the documentation must be written in `rst`_. From the API documentation
to developer documentation, everything must be written in this lightweight
format.
.. _rst: http://docutils.sourceforge.net/rst.html
Project guidelines
==================
* Testing: We have strived for a good coverage in the core, and we want not
only to keep it this way, but we want even better coverage. So every
testable feature to be added to the core must be comprehensively tested.
The `twisted tool`_ that we use for testing has a `coverage`_ switch that
is very handy to back up this bold statements.
.. _twisted tool: http://twistedmatrix.com/trac/browser/trunk/twisted/trial
.. _coverage: http://twistedmatrix.com/trac/browser/trunk/twisted/scripts/trial.py#L140
* Databases: As we had some bad experiences with ORMs in the past, and our
models are not that complex, we chose to create our own ORM layer. No, we
are not suffering from NIH syndrome, it is a very simple piece of code.
Anyway all the DB related code is contained in `wader.common.provider` and
tested in `wader.test.test_provider`. The schema and all the ORM related
methods must be there. Client code should never deal with cursors,
connections, etc.
* Portability: A great deal of effort has been invested on making this
code base as portable as possible (runs on OSX and Linux). As such, you
can not add any coupling in the code to any OS. All the troublesome methods
are abstracted out via the `wader.common.oal` module. Similarly, there is
a backend system that contains all the modules required to deal with a given
environment. For example, if NetworkManager is present we will use its
profile system, while if we operate standalone, a custom one will be used.
Most of this abstractions are shown in `wader.common.interfaces`.
* Translating: No strings should be marked for translation in the core, all
the translated strings must be UI ones. The core and the UI will communicate
via exceptions that the core will raise and the UI will catch. It is here
where a localized dialog can be shown to the user.
wader-0.5.13/LICENSE 0000664 0000000 0000000 00000035356 12576466102 0013734 0 ustar 00root root 0000000 0000000 GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
wader-0.5.13/Makefile 0000664 0000000 0000000 00000002102 12576466102 0014346 0 ustar 00root root 0000000 0000000 SHELL = /bin/bash
VERSION := $(shell python -c 'from wader.common.consts import APP_VERSION; print APP_VERSION')
SOURCES := $(shell rpmbuild --eval '%{_topdir}' 2>/dev/null)/SOURCES
WV := wader-$(VERSION)
all:
@echo Usage: make deb \[TARGET=ubuntu-lucid\] \| rpm
rpm:
@if [ ! -d $(SOURCES) ] ;\
then\
echo 'SOURCES does not exist, are you running on a non RPM based system?';\
exit 1;\
fi
touch resources/extra/networks.py
tar -jcvf $(SOURCES)/$(WV).tar.bz2 --exclude=.git --transform="s/^\./$(WV)/" .
rpmbuild -ba resources/rpm/wader.spec
deb:
@if [ ! -d /var/lib/dpkg ] ;\
then\
echo 'Debian package directory does not exist, are you running on a non Debian based system?';\
exit 1;\
fi
touch resources/extra/networks.py
@if [ -d packaging/debian/$(TARGET)/debian ] ;\
then\
PKGSOURCE=$(TARGET);\
else\
PKGSOURCE=generic;\
fi;\
tar -C packaging/debian/$$PKGSOURCE -cf - debian | tar -xf -
@if ! head -1 debian/changelog | grep -q $(VERSION) ;\
then\
echo Changelog and package version are different;\
exit 1;\
fi
dpkg-buildpackage -rfakeroot
wader-0.5.13/README 0000664 0000000 0000000 00000004646 12576466102 0013605 0 ustar 00root root 0000000 0000000 Wader is a 3G daemon accessible via DBus, written in Python and released
under the GPLv2. Wader runs on Linux and OSX.
Wader's target audience are developers. If you would like your application to
interact with a UMTS device, such as a mobile data card or a mobile phone,
then stop searching! Features:
* Built upon modern technologies such as dbus and udev
* Service invoked via DBus
* A single process handles n devices
* Dialup via NM 0.8/0.9 or Wvdial/NDIS dialup on systems with an older NM
* Extensible AT Engine
* Pluggable support for devices: Adding support for a new device is
usually a matter of copying an skeleton, changing the device IDs and
dropping the plugin in /usr/share/wader-core/plugins
* A python shell to interact with the device in runtime
History
Wader is a fork of the core of Vodafone Mobile Connect Card driver for Linux[0]
Some of its parts have been completely rewritten and the most juicy bits have
been exported over DBus to allow other applications of the Linux desktop to
use Wader. Wader is the first project (apart from ModemManager itself) that
implements ModemManager's API[1]. This means that NetworkManager 0.8 / 0.9
will be able to use wader-core to perform operations on devices.
Supported devices
See the SUPPORTED_DEVICES file to see which devices we currently know how to
handle well
Project Source Repository (patches most welcome):
https://github.com/andrewbird/wader
This software should work (in theory) with any device that follows the relevant
GSM and 3G specs. Nonetheless, every device is different and it may not work in
an untested device. Try it at your own risk. If you speak Python and feel
adventurous you could get involved by supporting a new device/distro.
LICENSE
Wader is distributed under the GPLv2. See the LICENSE file for the gory
details.
FAQ
0 .- Wader fails horribly with my OS
Wader has been tested on the following distros:
- Ubuntu 10.04 .. 12.04
- Mint 11
- Fedora 15
You can find instructions of how to add a new OS/Distro in the doc.
1 .- Wader fails horribly with my device
Chances are that your device is a cousin of one of our supported devices.
Adding support for a new device is relatively easy (as long as it behaves),
you can find instructions of how to add a new device in the doc.
[0] https://forge.vodafonebetavine.net/projects/vodafonemobilec/
[1] http://trac.warp.es/wader/wiki/WhatsModemManager
wader-0.5.13/SUPPORTED_DEVICES 0000664 0000000 0000000 00000002152 12576466102 0015365 0 ustar 00root root 0000000 0000000 ============================= Well supported devices ===========================
(we have physical samples and test regularily)
Huawei
E620 E660 E660A E870 EM730V B970 E160 E160B E172 E173 E220 E270 E272 E510
E1750 E1820 E3735 K2540 K3520 K3565 K3565rev2 K3715 K3765 K3770 K3771 K3806
K4505 K4510 K4511 K4605
ZTE
MF637U K3520-Z K3565-Z K3570-Z K3571-Z K3765-Z K3770-Z K3772-Z K3805-Z K3806-Z
K4505-Z K4510-Z
Novatel
U740 XU870 MC950 X950D MiFi2352
Option
Colt Quad Fuji FujiLite Etna GtFusion
K3760 E3730
Ericsson (and vendor variants)
F3307 F3507g F3607gw
Qualcomm (and vendor variants)
Gobi2000
========================= Less well supported devices =========================
(we don't have physical samples, either user contributed plugins or looks very
similar to a device we have and we have a user report of it working)
Huawei
E169 E1550 E1692 E180 EM770
Option
GI0335 Icon225 Icon401 GTM378 GTM380 GtMax36
Novatel
EU740 EU870D MC990D S720
ZTE and ONDA
MF180 MF181 MF190 MF620 MF626 MF628 MF632 MF636 MF651 MF668 MSA405HS MT503HS
Ericsson
MD300
SierraWireless
850 875 MC8755
LongCheer
(4GSystems XS Stick P10+)
wader-0.5.13/bin/ 0000775 0000000 0000000 00000000000 12576466102 0013463 5 ustar 00root root 0000000 0000000 wader-0.5.13/bin/wader-core-ctl 0000775 0000000 0000000 00000003561 12576466102 0016226 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
import os
from optparse import OptionParser
import sys
from time import sleep
from wader.common.consts import (APP_VERSION, DATA_DIR, PID_PATH)
sys.path.insert(0, DATA_DIR)
from twisted.python.release import sh
def stop_core():
if os.path.exists(PID_PATH):
sh("kill -9 `cat %s`" % PID_PATH)
parser = OptionParser()
parser.add_option("-v", "--version", action="store_true", dest="show_version",
default=False, help="Show version and exit")
parser.add_option("-r", "--restart", action="store_true",
dest="should_restart", default=False,
help="Restart wader-core")
parser.add_option("-s", "--start", action="store_true", dest="should_start",
default=False,
help="Start wader-core (only to be called by D-Bus service)")
parser.add_option("-t", "--stop", action="store_true", dest="should_stop",
default=False, help="Stop wader-core")
options, args = parser.parse_args()
if not (options.show_version or options.should_restart or
options.should_start or options.should_stop):
print(parser.format_help().strip())
sys.exit(0)
if options.show_version:
print "%s: version %s" % (os.path.basename(sys.argv[0]), APP_VERSION)
sys.exit(0)
if options.should_stop:
stop_core()
if options.should_restart:
try:
stop_core()
except:
print "Failed to stop core cleanly, may still be running"
sleep(1)
sh("dbus-send --system --dest=org.freedesktop.ModemManager "
"/org/freedesktop/ModemManager "
"org.freedesktop.ModemManager.EnumerateDevices")
elif options.should_start:
from twisted.scripts.twistd import run
from sys import argv
argv[1:] = [
'--python=%s' % os.path.join(DATA_DIR, 'core-tap.py'),
'--pidfile=%s' % PID_PATH,
'--reactor=glib2'
]
run()
wader-0.5.13/core-tap.py 0000664 0000000 0000000 00000002352 12576466102 0015001 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2006-2011 Vodafone España, S.A.
# Copyright (C) 2008-2009 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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.
"""tap file for Wader"""
# Make sure the very first thing we do is to set the glib loop as default
import dbus
from dbus.mainloop.glib import DBusGMainLoop
gloop = DBusGMainLoop(set_as_default=True)
import locale
# i10n stuff
locale.setlocale(locale.LC_ALL, '')
import sys
sys.path.insert(0, '/usr/share/wader-core')
from core.startup import get_wader_application
application = get_wader_application()
wader-0.5.13/core/ 0000775 0000000 0000000 00000000000 12576466102 0013643 5 ustar 00root root 0000000 0000000 wader-0.5.13/core/__init__.py 0000664 0000000 0000000 00000001546 12576466102 0015762 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2006-2008 Vodafone España, S.A.
# Copyright (C) 2008-2009 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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.
"""Wader's core"""
wader-0.5.13/core/backends/ 0000775 0000000 0000000 00000000000 12576466102 0015415 5 ustar 00root root 0000000 0000000 wader-0.5.13/core/backends/__init__.py 0000664 0000000 0000000 00000002345 12576466102 0017532 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2011 Vodafone España, S.A.
# Copyright (C) 2010 Warp Networks, S.L.
# Author: Andrew Bird
#
# 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.
from wader.common.backends import BACKEND_LIST
from core.backends.nm import nm_backend
from core.backends.plain import plain_backend
__backend = None
def get_backend():
global __backend
if __backend is not None:
return __backend
for name in BACKEND_LIST:
backend = globals()[name]
if backend.should_be_used():
__backend = backend
return __backend
wader-0.5.13/core/backends/nm.py 0000664 0000000 0000000 00000024503 12576466102 0016405 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2008-2011 Vodafone España, S.A.
# Copyright (C) 2008-2010 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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 dbus
from twisted.internet.defer import Deferred
from twisted.python import log
from zope.interface import implements
from wader.common.backends.nm import NetworkManagerBackend as \
_NetworkManagerBackend
from wader.common.consts import (WADER_PROFILES_SERVICE,
WADER_PROFILES_INTFACE,
WADER_PROFILES_OBJPATH,
MDM_INTFACE, MM_IP_METHOD_PPP)
from wader.common.interfaces import IBackend
from core.dialer import Dialer
NM_SERVICE = 'org.freedesktop.NetworkManager'
NM_OBJPATH = '/org/freedesktop/NetworkManager'
NM_INTFACE = 'org.freedesktop.NetworkManager'
NM_DEVICE = '%s.Device' % NM_INTFACE
NM08_GSM_INTFACE = '%s.Gsm' % NM_DEVICE
NM08_USER_SETTINGS = 'org.freedesktop.NetworkManagerUserSettings'
NM09_MODEM_INTFACE = '%s.Modem' % NM_DEVICE
NM08_STATE = {
'UNKNOWN': 0,
'ASLEEP': 1,
'CONNECTING': 2,
'CONNECTED': 3,
'DISCONNECTED': 4,
}
NM09_STATE = {
'UNKNOWN': 0,
'ASLEEP': 10,
'DISCONNECTED': 20,
'DISCONNECTING': 30,
'CONNECTING': 40,
'CONNECTED_LOCAL': 50,
'CONNECTED_SITE': 60,
'CONNECTED_GLOBAL': 70,
'IPCONFIG': 100
}
NM08_DEVICE_STATE_UNKNOWN = 0
NM08_DEVICE_STATE_UNMANAGED = 1
NM08_DEVICE_STATE_UNAVAILABLE = 2
NM08_DEVICE_STATE_DISCONNECTED = 3
NM08_DEVICE_STATE_PREPARE = 4
NM08_DEVICE_STATE_CONFIG = 5
NM08_DEVICE_STATE_NEED_AUTH = 6
NM08_DEVICE_STATE_IP_CONFIG = 7
NM08_DEVICE_STATE_ACTIVATED = 8
NM08_DEVICE_STATE_FAILED = 9
NM08_DEVICE_STATE = {
'UNKNOWN': NM08_DEVICE_STATE_UNKNOWN,
'UNMANAGED': NM08_DEVICE_STATE_UNMANAGED,
'UNAVAILABLE': NM08_DEVICE_STATE_UNAVAILABLE,
'DISCONNECTED': NM08_DEVICE_STATE_DISCONNECTED,
'PREPARE': NM08_DEVICE_STATE_PREPARE,
'CONFIG': NM08_DEVICE_STATE_CONFIG,
'NEED_AUTH': NM08_DEVICE_STATE_NEED_AUTH,
'IP_CONFIG': NM08_DEVICE_STATE_IP_CONFIG,
'ACTIVATED': NM08_DEVICE_STATE_ACTIVATED,
'FAILED': NM08_DEVICE_STATE_FAILED
}
NM09_DEVICE_STATE_UNKNOWN = 0
NM09_DEVICE_STATE_UNMANAGED = 10
NM09_DEVICE_STATE_UNAVAILABLE = 20
NM09_DEVICE_STATE_DISCONNECTED = 30
NM09_DEVICE_STATE_PREPARE = 40
NM09_DEVICE_STATE_CONFIG = 50
NM09_DEVICE_STATE_NEED_AUTH = 60
NM09_DEVICE_STATE_IP_CONFIG = 70
NM09_DEVICE_STATE_IP_CHECK = 80
NM09_DEVICE_STATE_SECONDARIES = 90
NM09_DEVICE_STATE_ACTIVATED = 100
NM09_DEVICE_STATE = {
'UNKNOWN': NM09_DEVICE_STATE_UNKNOWN,
'UNMANAGED': NM09_DEVICE_STATE_UNMANAGED,
'UNAVAILABLE': NM09_DEVICE_STATE_UNAVAILABLE,
'DISCONNECTED': NM09_DEVICE_STATE_DISCONNECTED,
'PREPARE': NM09_DEVICE_STATE_PREPARE,
'CONFIG': NM09_DEVICE_STATE_CONFIG,
'NEED_AUTH': NM09_DEVICE_STATE_NEED_AUTH,
'IP_CONFIG': NM09_DEVICE_STATE_IP_CONFIG,
'IP_CHECK': NM09_DEVICE_STATE_IP_CHECK,
'SECONDARIES': NM09_DEVICE_STATE_SECONDARIES,
'ACTIVATED': NM09_DEVICE_STATE_ACTIVATED
}
class NMDialer(Dialer):
"""I wrap NetworkManager's dialer"""
def __init__(self, device, opath, **kwds):
super(NMDialer, self).__init__(device, opath, **kwds)
self.int = None
self.conn_obj = None
self.iface = self._get_stats_iface()
self.state = self.NM_DISCONNECTED
self.nm_opath = None
self.connect_deferred = None
self.disconnect_deferred = None
self.sm = None
def _cleanup(self):
# enable +CREG notifications afterwards
self.device.sconn.set_netreg_notification(1)
self.sm.remove()
self.sm = None
def _get_device_opath(self):
"""
Returns the object path to use in the connection / signal
"""
obj = self.bus.get_object(NM_SERVICE, NM_OBJPATH)
interface = dbus.Interface(obj, NM_INTFACE)
for opath in interface.GetDevices():
dev = self.bus.get_object(NM_SERVICE, opath)
udi = dev.Get('org.freedesktop.NetworkManager.Device', 'Udi')
if self.device.opath == udi:
return opath
def _get_stats_iface(self):
if self.device.get_property(MDM_INTFACE,
'IpMethod') == MM_IP_METHOD_PPP:
iface = 'ppp0' # XXX: shouldn't hardcode to first PPP instance
else:
iface = self.device.get_property(MDM_INTFACE, 'Device')
return iface
def _on_properties_changed(self, changed):
if 'State' not in changed:
return
if changed['State'] == self.NM_CONNECTED and \
self.state == self.NM_DISCONNECTED:
# emit the connected signal and send back the opath
# if the deferred is present
self.state = self.NM_CONNECTED
self.Connected()
self.connect_deferred.callback(self.opath)
return
if changed['State'] == self.NM_DISCONNECTED:
if self.state == self.NM_CONNECTED:
self.Disconnected()
if self.disconnect_deferred is not None:
# could happen if we are connected and a NM_DISCONNECTED
# signal arrives without having explicitly disconnected
self.disconnect_deferred.callback(self.conn_obj)
if self.state == self.NM_DISCONNECTED:
# Occurs if the connection attempt failed
self.Disconnected()
if self.connect_deferred is not None:
msg = 'Network Manager failed to connect'
self.connect_deferred.errback(RuntimeError(msg))
self.state = self.NM_DISCONNECTED
self._cleanup()
def _setup_signals(self):
self.sm = self.bus.add_signal_receiver(self._on_properties_changed,
"PropertiesChanged",
path=self._get_device_opath(),
dbus_interface=self.NM_MODEM_INT)
def configure(self, config):
self._setup_signals()
# get the profile object and obtains its uuid
# get ProfileManager and translate the uuid to a NM object path
profiles = self.bus.get_object(WADER_PROFILES_SERVICE,
WADER_PROFILES_OBJPATH)
# get the object path of the profile being used
self.nm_opath = profiles.GetNMObjectPath(str(config.uuid),
dbus_interface=WADER_PROFILES_INTFACE)
# Disable +CREG notifications, otherwise NMDialer won't work
return self.device.sconn.set_netreg_notification(0)
def connect(self):
raise NotImplementedError("Implement in subclass")
def stop(self):
self._cleanup()
return self.disconnect()
def disconnect(self):
self.disconnect_deferred = Deferred()
self.int.DeactivateConnection(self.conn_obj)
# the deferred will be callbacked as soon as we get a
# connectivity status change
return self.disconnect_deferred
class NM08Dialer(NMDialer):
def __init__(self, device, opath, **kwds):
self.NM_CONNECTED = NM08_DEVICE_STATE_ACTIVATED
self.NM_DISCONNECTED = NM08_DEVICE_STATE_DISCONNECTED
self.NM_MODEM_INT = NM08_GSM_INTFACE
super(NM08Dialer, self).__init__(device, opath, **kwds)
def connect(self):
self.connect_deferred = Deferred()
obj = self.bus.get_object(NM_SERVICE, NM_OBJPATH)
self.int = dbus.Interface(obj, NM_INTFACE)
# NM 0.8 - applet (USER)
#
#
#
#
args = (NM08_USER_SETTINGS,
self.nm_opath, self._get_device_opath(), '/')
log.msg("Connecting with:\n%s\n%s\n%s\n%s" % args)
try:
self.conn_obj = self.int.ActivateConnection(*args)
# the deferred will be callbacked as soon as we get a
# connectivity status change
return self.connect_deferred
except dbus.DBusException, e:
log.err(e)
self._cleanup()
class NM09Dialer(NMDialer):
def __init__(self, device, opath, **kwds):
self.NM_CONNECTED = NM09_DEVICE_STATE_ACTIVATED
self.NM_DISCONNECTED = NM09_DEVICE_STATE_DISCONNECTED
self.NM_MODEM_INT = NM09_MODEM_INTFACE
super(NM09Dialer, self).__init__(device, opath, **kwds)
def connect(self):
self.connect_deferred = Deferred()
obj = self.bus.get_object(NM_SERVICE, NM_OBJPATH)
self.int = dbus.Interface(obj, NM_INTFACE)
# NM 0.9
#
#
#
args = (self.nm_opath, self._get_device_opath(), '/')
log.msg("Connecting with:\n%s\n%s\n%s" % args)
try:
self.conn_obj = self.int.ActivateConnection(*args)
# the deferred will be callbacked as soon as we get a
# connectivity status change
return self.connect_deferred
except dbus.DBusException, e:
log.err(e)
self._cleanup()
class NetworkManagerBackend(_NetworkManagerBackend):
implements(IBackend)
def get_dialer_klass(self, device):
if self._get_version() == '08':
return NM08Dialer
elif self._get_version() == '084':
return NM08Dialer
else:
return NM09Dialer
nm_backend = NetworkManagerBackend()
wader-0.5.13/core/backends/plain.py 0000664 0000000 0000000 00000044535 12576466102 0017105 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2006-2008 Vodafone España, S.A.
# Copyright (C) 2008-2009 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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.
from __future__ import with_statement
import errno
from cStringIO import StringIO
import os
import tempfile
import re
import shutil
from signal import SIGTERM, SIGKILL
from string import Template
from twisted.internet import reactor, defer, protocol, error, task
from twisted.python import log, procutils
from zope.interface import implements
from wader.common.backends.plain import PlainBackend as _PlainBackend
from wader.common.consts import (APP_NAME, FALLBACK_DNS, MDM_INTFACE,
MM_ALLOWED_AUTH_NONE, MM_ALLOWED_AUTH_PAP,
MM_ALLOWED_AUTH_CHAP,
MM_MODEM_STATE_REGISTERED,
MM_MODEM_STATE_CONNECTING,
MM_MODEM_STATE_DISCONNECTING,
MM_MODEM_STATE_CONNECTED)
from wader.common.interfaces import IBackend
from wader.common.utils import save_file, is_bogus_ip
from core.dialer import Dialer
from core.oal import get_os_object
def proc_running(pid):
try:
pid = int(pid)
except (TypeError, ValueError):
return None
if pid <= 1:
return False # No killing of process group members or all of init's
# children
try:
os.kill(pid, 0)
except OSError, err:
if err.errno == errno.ESRCH:
return False
elif err.errno == errno.EPERM:
return pid
else:
return None # Unknown error
else:
return pid
def signal_process(name, pid, signal):
pid = proc_running(pid)
if not pid:
log.msg('wvdial: "%s" process (%s) already exited' %
(name, str(pid)))
return False
log.msg('wvdial: "%s" process (%s) will be sent %s' %
(name, str(pid), signal))
try:
os.kill(pid, SIGKILL)
except OSError, err:
if err.errno == errno.ESRCH:
log.msg('wvdial: "%s" process (%s) not found' %
(name, str(pid)))
elif err.errno == errno.EPERM:
log.msg('wvdial: "%s" process (%s) permission denied' %
(name, str(pid)))
else:
log.msg('wvdial: "%s" process exit "%s"' % (name, str(err)))
return True # signal was sent
def validate_dns(dynamic, use_static, static):
if use_static:
valid_dns = [addr for addr in static if addr]
else:
# If static DNS is not set, then we should use the DNS returned by the
# network, but let's check if they're valid DNS IPs
valid_dns = [addr for addr in dynamic if not is_bogus_ip(addr)]
if len(valid_dns):
return True, valid_dns
# The DNS assigned by the network is invalid or missing, or the static
# addresses are missing, so notify the user and fallback to Google etc
return False, FALLBACK_DNS
WVDIAL_PPPD_OPTIONS = os.path.join('/etc', 'ppp', 'peers', 'wvdial')
WVDIAL_RETRIES = 3
WVTEMPLATE = """
[Dialer Defaults]
Phone = $phone
Username = $username
Password = $password
Stupid Mode = 1
Dial Command = ATDT
New PPPD = yes
Check Def Route = on
Dial Attempts = 3
Dial Timeout = 30
Auto Reconnect = off
Auto DNS = on
[Dialer connect]
Modem = $serialport
Baud = 460800
Init1 = AT
Init2 = AT
Init3 = ATQ0 V1 E0 S0=0 &C1 &D2
Init4 = AT+CGDCONT=$context,"IP","$apn"
ISDN = 0
Modem Type = Analog Modem
"""
CONNECTED_REGEXP = re.compile('Connected\.\.\.')
PPPD_PID_REGEXP = re.compile('Pid of pppd: (?P\d+)')
PPPD_IFACE_REGEXP = re.compile('Using interface (?Pppp\d+)')
MAX_ATTEMPTS_REGEXP = re.compile('Maximum Attempts Exceeded')
PPPD_DIED_REGEXP = re.compile('The PPP daemon has died')
DNS_REGEXP = re.compile(r"""
DNS\saddress
\s # beginning of the string
(?P # group named ip
(25[0-5]| # integer range 250-255 OR
2[0-4][0-9]| # integer range 200-249 OR
[01]?[0-9][0-9]?) # any number < 200
\. # matches '.'
(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?) # repeat
\. # matches '.'
(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?) # repeat
\. # matches '.'
(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?) # repeat
) # end of group
\b # end of the string
""", re.VERBOSE)
DEFAULT_TEMPLATE = """
ipparam wader
debug
noauth
name wvdial
noipdefault
nomagic
ipcp-accept-local
ipcp-accept-remote
nomp
noccp
nopredictor1
novj
novjccomp
nobsdcomp"""
PAP_TEMPLATE = DEFAULT_TEMPLATE + """
refuse-chap
refuse-mschap
refuse-mschap-v2
refuse-eap
"""
CHAP_TEMPLATE = DEFAULT_TEMPLATE + """
refuse-pap
"""
def get_wvdial_conf_file(conf, context, serial_port):
"""
Returns the path of the generated wvdial.conf
:param conf: `DialerConf` instance
:param serial_port: The port to use
:rtype: str
"""
text = _generate_wvdial_conf(conf, context, serial_port)
dirpath = tempfile.mkdtemp('', APP_NAME, '/tmp')
path = tempfile.mkstemp('wvdial.conf', APP_NAME, dirpath, True)[1]
save_file(path, text)
return path
def _generate_wvdial_conf(conf, context, sport):
"""
Generates a specially crafted wvdial.conf with `conf` and `sport`
:param conf: `DialerConf` instance
:param sport: The port to use
:rtype: str
"""
user = conf.username if conf.username else '*'
passwd = conf.password if conf.password else '*'
theapn = conf.apn
# build template
data = StringIO(WVTEMPLATE)
template = Template(data.getvalue())
data.close()
# construct number
number = '*99***%d#' % context
# return template
props = dict(serialport=sport, username=user, password=passwd,
context=context, apn=theapn, phone=number)
return template.substitute(props)
class WVDialDialer(Dialer):
"""Dialer for WvDial"""
binary = 'wvdial'
def __init__(self, device, opath, **kwds):
super(WVDialDialer, self).__init__(device, opath, **kwds)
try:
self.bin_path = procutils.which(self.binary)[0]
except IndexError:
self.bin_path = '/usr/bin/wvdial'
self.backup_path = ""
self.conf = None
self.conf_path = ""
self.dirty = False
self.proto = None
self.iconn = None
self.iface = 'ppp0'
self.should_stop = False
self.attempting_connect = False
def Connected(self):
self.device.set_status(MM_MODEM_STATE_CONNECTED)
self.attempting_connect = False
super(WVDialDialer, self).Connected()
def Disconnected(self):
if self.device.status >= MM_MODEM_STATE_REGISTERED:
self.device.set_status(MM_MODEM_STATE_REGISTERED)
self.attempting_connect = False
super(WVDialDialer, self).Disconnected()
def configure(self, config):
self.dirty = True
def get_context_id(ign):
conn_id = self.device.sconn.state_dict.get('conn_id')
try:
context = int(conn_id)
except ValueError:
raise Exception('WVDialDialer context id is "%s"' %
str(conn_id))
return context
d = self.device.sconn.set_apn(config.apn)
d.addCallback(get_context_id)
d.addCallback(lambda context: self._generate_config(config, context))
return d
def connect(self):
if self.should_stop:
self.should_stop = False
return
self.device.set_status(MM_MODEM_STATE_CONNECTING)
self.attempting_connect = True
self.proto = WVDialProtocol(self)
args = [self.binary, '-C', self.conf_path, 'connect']
self.iconn = reactor.spawnProcess(self.proto, args[0], args, env=None)
return self.proto.deferred
def stop(self):
self.should_stop = True
self.attempting_connect = False
return self.disconnect()
def disconnect(self):
if self.proto is None:
return defer.succeed(self.opath)
self.device.set_status(MM_MODEM_STATE_DISCONNECTING)
msg = 'WVdial failed to connect'
def get_pppd_pid():
pid_file = "/var/run/%s.pid" % self.iface
if not os.path.exists(pid_file):
return False
pid = None
with open(pid_file) as f:
pid = f.read()
return pid
def cleanup_pppd():
if self.attempting_connect:
self.proto.deferred.errback(RuntimeError(msg))
pppd_pid = get_pppd_pid()
if not signal_process('pppd', pppd_pid, SIGTERM):
# process was already gone
d = defer.succeed(self._cleanup())
else:
d = task.deferLater(reactor, 5,
signal_process, 'pppd', pppd_pid, SIGKILL)
d.addCallback(lambda _: self._cleanup())
return d
# tell wvdial to quit
try:
self.proto.transport.signalProcess('TERM')
except error.ProcessExitedAlready:
log.msg("wvdial: wvdial exited")
# just be damn sure that we're killing everything
wvdial_pid = proc_running(self.proto.pid)
if not wvdial_pid:
# process was already gone
d = cleanup_pppd()
else:
d = task.deferLater(reactor, 5,
signal_process, 'wvdial', wvdial_pid, SIGKILL)
d.addCallback(lambda _: cleanup_pppd())
d.addCallback(lambda _: self.opath)
return d
def _generate_config(self, conf, context):
# backup wvdial configuration
self.backup_path = self._backup_conf()
self.conf = conf
# generate auth configuration
self._generate_wvdial_ppp_options()
# generate wvdial.conf from template
port = self.device.ports.dport
self.conf_path = get_wvdial_conf_file(self.conf, context, port.path)
def _cleanup(self, ignored=None):
"""cleanup our traces"""
if not self.dirty:
return
try:
path = os.path.dirname(self.conf_path)
os.unlink(self.conf_path)
os.rmdir(path)
except (IOError, OSError):
pass
self._restore_conf()
def _generate_wvdial_ppp_options(self):
if not self.conf.refuse_chap:
wvdial_ppp_options = CHAP_TEMPLATE
elif not self.conf.refuse_pap:
wvdial_ppp_options = PAP_TEMPLATE
else:
# this could be a NOOP, but the user might have modified
# the stock /etc/ppp/peers/wvdial file, so the safest option
# is to overwrite with our known good options.
wvdial_ppp_options = DEFAULT_TEMPLATE
# There are some patched pppd implementations
# Most systems offer 'replacedefaultroute', but not Fedora
osobj = get_os_object()
if hasattr(osobj, 'get_additional_wvdial_ppp_options'):
wvdial_ppp_options += osobj.get_additional_wvdial_ppp_options()
save_file(WVDIAL_PPPD_OPTIONS, wvdial_ppp_options)
def _backup_conf(self):
path = tempfile.mkstemp('wvdial', APP_NAME)[1]
try:
shutil.copy(WVDIAL_PPPD_OPTIONS, path)
return path
except IOError:
return None
def _restore_conf(self):
if self.backup_path:
shutil.copy(self.backup_path, WVDIAL_PPPD_OPTIONS)
os.unlink(self.backup_path)
self.backup_path = None
def _set_iface(self, iface):
self.iface = iface
class WVDialProtocol(protocol.ProcessProtocol):
"""ProcessProtocol for wvdial"""
def __init__(self, dialer):
self.dialer = dialer
self.__connected = False
self.pid = None
self.retries = 0
self.deferred = defer.Deferred()
self.dns = []
def connectionMade(self):
self.transport.closeStdin()
def outReceived(self, data):
log.msg("wvdial: sysout %s" % data)
def errReceived(self, data):
"""wvdial has this bad habit of using stderr for debug"""
log.msg("wvdial: %r" % data)
self._parse_output(data)
def outConnectionLost(self):
log.msg('wvdial: wvdial closed its stdout!')
def errConnectionLost(self):
log.msg('wvdial: wvdial closed its stderr.')
def processEnded(self, status_object):
log.msg('wvdial: quitting')
if not self.__connected:
self.dialer.disconnect()
self._set_disconnected(force=True)
def processExited(self, status):
log.msg('wvdial: wvdial processExited')
def _set_connected(self):
if self.__connected:
return
valid, dns = validate_dns(self.dns, self.dialer.conf.staticdns,
[self.dialer.conf.dns1, self.dialer.conf.dns2])
if not valid:
if self.dialer.conf.staticdns:
self.dialer.InvalidDNS([])
else:
self.dialer.InvalidDNS(self.dns)
osobj = get_os_object()
osobj.add_dns_info(dns, self.dialer.iface)
self.__connected = True
self.dialer.Connected()
self.deferred.callback(self.dialer.opath)
def _set_disconnected(self, force=False):
if not self.__connected and not force:
return
osobj = get_os_object()
osobj.delete_dns_info(self.dialer.iface)
self.__connected = False
self.dialer.Disconnected()
def _extract_iface(self, data):
match = PPPD_IFACE_REGEXP.search(data)
if match:
self.dialer._set_iface(match.group('iface'))
log.msg("wvdial: dialer interface %s" % self.dialer.iface)
def _extract_dns_strings(self, data):
for match in re.finditer(DNS_REGEXP, data):
self.dns.append(match.group('ip'))
def _extract_connected(self, data):
if CONNECTED_REGEXP.search(data):
self._set_connected()
def _extract_disconnected(self, data):
# more than three attempts
max_attempts = MAX_ATTEMPTS_REGEXP.search(data)
# pppd died
pppd_died = PPPD_DIED_REGEXP.search(data)
if max_attempts or pppd_died:
self._set_disconnected()
def _extract_tries(self, data):
# force wvdial to stop after three attempts
if self.retries >= WVDIAL_RETRIES:
self.dialer.disconnect()
self._set_disconnected()
return
# extract pppd pid
match = PPPD_PID_REGEXP.search(data)
if match:
self.pid = int(match.group('pid'))
self.retries += 1
log.msg("wvdial: dialer tries %d" % self.retries)
def _parse_output(self, data):
self._extract_iface(data)
self._extract_dns_strings(data)
if not self.__connected:
self._extract_connected(data)
else:
self._extract_disconnected(data)
self._extract_tries(data)
class HSODialer(Dialer):
"""Dialer for HSO type devices"""
# Note: The interface is called HSO for historical reasons but actually
# it can be used by devices other than Option's e.g. ZTE's Icera
def __init__(self, device, opath, **kwds):
super(HSODialer, self).__init__(device, opath, **kwds)
self.iface = self.device.get_property(MDM_INTFACE, 'Device')
self.conf = None
def configure(self, config):
self.conf = config
if not config.refuse_chap:
auth = MM_ALLOWED_AUTH_CHAP
elif not config.refuse_pap:
auth = MM_ALLOWED_AUTH_PAP
else:
auth = MM_ALLOWED_AUTH_NONE
d = self.device.sconn.set_apn(config.apn)
d.addCallback(lambda _: self.device.sconn.hso_authenticate(
config.username, config.password, auth))
return d
def connect(self):
# start the connection
d = self.device.sconn.hso_connect()
# now get the IP4Config and set up device and routes
d.addCallback(lambda _: self.device.sconn.get_ip4_config())
d.addCallback(self._get_ip4_config_cb)
d.addCallback(lambda _: self.Connected())
d.addCallback(lambda _: self.opath)
return d
def _get_ip4_config_cb(self, (ip, dns1, dns2, dns3)):
valid, dns = validate_dns([dns1, dns2, dns3], self.conf.staticdns,
[self.conf.dns1, self.conf.dns2])
if not valid:
if self.conf.staticdns:
self.InvalidDNS([])
else:
self.InvalidDNS(self.dns)
osobj = get_os_object()
d = osobj.configure_iface(self.iface, ip, 'up')
d.addCallback(lambda _: osobj.add_default_route(self.iface))
d.addCallback(lambda _: osobj.add_dns_info(dns, self.iface))
return d
def disconnect(self):
d = self.device.sconn.disconnect_from_internet()
osobj = get_os_object()
osobj.delete_default_route(self.iface)
osobj.delete_dns_info(None, self.iface)
osobj.configure_iface(self.iface, '', 'down')
d.addCallback(lambda _: self.Disconnected())
return d
def stop(self):
# set internal flag in device for disconnection
self.device.sconn.state_dict['should_stop'] = True
return self.disconnect()
class PlainBackend(_PlainBackend):
"""Plain backend"""
implements(IBackend)
def get_dialer_klass(self, device):
if device.dialer in ['hso']:
return HSODialer
return WVDialDialer
plain_backend = PlainBackend()
wader-0.5.13/core/command.py 0000664 0000000 0000000 00000025105 12576466102 0015636 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2006-2008 Vodafone España, S.A.
# Copyright (C) 2008-2009 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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.
"""AT Commands related classes and help functions"""
import re
from twisted.internet import defer
from wader.common.aterrors import ERROR_REGEXP
OK_REGEXP = re.compile("\r\n(?POK)\r\n")
def build_cmd_dict(extract=OK_REGEXP, end=OK_REGEXP, error=ERROR_REGEXP):
"""Returns a dictionary ready to be used in `CMD_DICT`"""
for regexp in [extract, end, error]:
if isinstance(regexp, basestring):
regexp = re.compile(regexp)
if hasattr(regexp, 'search'):
pass
else:
raise ValueError("Don't know what to do with %r" % regexp)
return dict(extract=extract, end=end, error=error)
def get_cmd_dict_copy():
"""
Returns a copy of the `CMD_DICT` dictionary
Use this instead of importing it directly as you may forget to copy() it
"""
return CMD_DICT.copy()
CMD_DICT = {
'add_contact': build_cmd_dict(),
'cancel_ussd': build_cmd_dict(),
'change_pin': build_cmd_dict(),
'check_pin': build_cmd_dict(re.compile(r"""
\r\n
\+CPIN:\s
(?P
READY |
SIM\sPIN2? |
SIM\sPUK2?
)
\r\n""", re.X)),
'delete_contact': build_cmd_dict(),
'delete_sms': build_cmd_dict(),
'disable_echo': build_cmd_dict(),
'enable_echo': build_cmd_dict(),
'enable_radio': build_cmd_dict(),
'enable_pin': build_cmd_dict(),
'find_contacts': build_cmd_dict(re.compile(r"""
\r\n
\+CPBF:\s
(?P\d+),
"(?P[+0-9a-fA-F]+)",
(?P\d+),
\"(?P.*)\"
""", re.X)),
'get_apns': build_cmd_dict(re.compile(r"""
\r\n
\+CGDCONT:\s
(?P\d+),
"[A-Za-z0-9]*",
"(?P.*)",
"(?P.*)",
\d,\d""", re.X)),
'get_apn_range': build_cmd_dict(re.compile(r"""
\r\n
\+CGDCONT:\s*\((?P\d+)-(?P\d+)\),
(?P(?:"IP")|(?:"00490050"))(?P.*)
""", re.X)),
'get_charsets': build_cmd_dict(re.compile('"(?P.*?)",?')),
'get_contact': build_cmd_dict(re.compile(r"""
\r\n
\+CPBR:\s(?P\d+),
"(?P[+0-9a-fA-F]+)",
(?P\d+),
"(?P.*)"
(?P,.*)?
\r\n""", re.X)),
'list_contacts': build_cmd_dict(
end=re.compile('(\r\n)?\r\n(OK)\r\n'),
extract=re.compile(r"""
\r\n
\+CPBR:\s(?P\d+),
"(?P[+0-9a-fA-F]+)",
(?P\d+),
"(?P.*)"
""", re.X)),
'get_card_version': build_cmd_dict(re.compile(
'\r\n(\+C?GMR:)?(?P.*)\r\n\r\nOK\r\n')),
'get_card_model': build_cmd_dict(re.compile(
'\r\n(?P.*)\r\n\r\nOK\r\n')),
'get_charset': build_cmd_dict(re.compile(
'\r\n\+CSCS:\s"(?P.*)"\r\n')),
'get_esn': build_cmd_dict(re.compile('\r\n\+ESN:\s"(?P.*)"\r\n')),
'get_manufacturer_name': build_cmd_dict(re.compile(
'\r\n(?P.*)\r\n\r\nOK\r\n')),
'get_imei': build_cmd_dict(re.compile("\r\n(?P\d+)\r\n")),
'get_imsi': build_cmd_dict(re.compile('\r\n(?P\d+)\r\n')),
'get_netreg_status': build_cmd_dict(re.compile(r"""
\r\n
\+CREG:\s
(?P\d),(?P\d+)
(,[0-9a-fA-F]*,[0-9a-fA-F]*)?
\r\n
""", re.X)),
'get_network_info': build_cmd_dict(re.compile(r"""
\r\n
\+COPS:\s+
(\d,\d, # or followed by num,num,str,num
"(?P[^"]*)",
(?P\d)
|(?P\d)
) # end of group
\r\n""", re.X)),
'get_network_names': build_cmd_dict(re.compile(r"""
\(
(?P\d+),
"(?P[^"]*)",
"(?P[^"]*)",
"(?P\d+)",
(?P\d)
\),?""", re.X),
end=re.compile('\r\n\r\nOK\r\n')),
'get_signal_quality': build_cmd_dict(re.compile(r"""
\r\n
\+CSQ:\s(?P\d+),(?P\d+)
\r\n""", re.X)),
'get_sms_format': build_cmd_dict(
re.compile('\r\n\+CMGF:\s(?P\d)\r\n')),
'get_phonebook_size': build_cmd_dict(re.compile(r"""
\r\n
\+CPBR:\s
\(\d\-(?P\d+)\)(?:,\d+)*
\r\n""", re.X)),
'get_pin_status': build_cmd_dict(
re.compile('\r\n\+CLCK:\s(?P\d)\r\n')),
'get_radio_status': build_cmd_dict(
re.compile("\r\n\+CFUN:\s?(?P\d)\r\n")),
'get_roaming_ids': build_cmd_dict(re.compile(r"""
\r\n
\+CPOL:\s(?P\d+),
(?P\d),
"(?P\d+)"
(?P,.*)?
""", re.X)),
'list_sms': build_cmd_dict(re.compile(r"""
\r\n
\+CMGL:\s
(?P\d+),
(?P\d),,\d+
\r\n(?P\w+)""", re.X)),
'get_sms': build_cmd_dict(re.compile(r"""
\r\n
\+CMGR:\s
(?P\d),,
\d+\r\n
(?P\w+)
\r\n""", re.X)),
'get_smsc': build_cmd_dict(re.compile(
'\r\n\+CSCA:\s"(?P.*)",\d+\r\n')),
'register_with_netid': build_cmd_dict(),
'reset_settings': build_cmd_dict(),
'save_sms': build_cmd_dict(re.compile('\r\n\+CMGW:\s(?P\d+)\r\n')),
'send_at': build_cmd_dict(),
'send_sms': build_cmd_dict(re.compile(
'\r\n\+CMGS:\s(?P\d+)\r\n')),
'send_sms_from_storage': build_cmd_dict(re.compile(
'\r\n\+CMSS:\s(?P\d+)\r\n')),
'send_pin': build_cmd_dict(),
'send_puk': build_cmd_dict(),
'send_ussd': build_cmd_dict(re.compile("""
\r\n\+CUSD:\s
(?P\d)
(?:,"(?P.*)")?
(?:,(?P\d+))?
\r\n""", re.X),
re.compile('\r\n\+CUSD:\s\d(?:,".*")?(?:,\d+)?\r\n')),
'set_apn': build_cmd_dict(),
'set_charset': build_cmd_dict(),
'set_error_level': build_cmd_dict(),
'set_netreg_notification': build_cmd_dict(),
'set_network_info_format': build_cmd_dict(),
'set_sms_indication': build_cmd_dict(),
'set_sms_format': build_cmd_dict(),
'set_smsc': build_cmd_dict(),
# r'CRSM:\s(?P\d+),(?P\d+)(?:,"?(?P[0-9A-Fa-f]*)"?)?'
'sim_access_restricted': build_cmd_dict(re.compile(r"""
\r\n
\+CRSM:\s
(?P\d+),
(?P\d+)
(?:,"?
(?P[0-9A-Fa-f]*)
"?)?
\r\n""", re.X)),
}
class ATCmd(object):
"""I encapsulate all the data related to an AT command"""
def __init__(self, cmd, name=None, eol='\r\n', nolog=tuple()):
self.cmd = cmd
self.name = name
self.eol = eol
self.nolog = nolog
# Some commands like sending a sms require an special handling this
# is because we have to wait till we receive a prompt like '\r\n> '
# if splitcmd is set, the second part will be send 0.1 seconds later
self.splitcmd = None
# command's deferred
self.deferred = defer.Deferred()
self.timeout = 15 # default timeout
self.call_id = None # DelayedCall reference
def __repr__(self):
args = (self.name, self.get_cmd(), self.timeout)
return "" % args
def get_cmd(self):
"""Returns the raw AT command plus EOL"""
cmd = self.cmd + self.eol
return str(cmd)
wader-0.5.13/core/contact.py 0000664 0000000 0000000 00000011443 12576466102 0015653 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2008-2009 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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.
"""Contact related classes and utilities"""
from zope.interface import implements
from wader.common.encoding import to_u
from wader.common.interfaces import IContact
class Contact(object):
"""I am a Contact on Wader"""
implements(IContact)
def __init__(self, name, number, index=None):
super(Contact, self).__init__()
self.name = to_u(name)
self.number = to_u(number)
self.index = index
def __repr__(self):
if not self.index:
return '' % (self.name, self.number)
args = (self.name, self.number, self.index)
return '' % args
__str__ = __repr__
def __eq__(self, c):
if self.index and c.index:
return self.index == c.index
return self.name == c.name and self.number == c.number
def __ne__(self, c):
return not self.__eq__(c)
def to_csv(self):
"""See :meth:`wader.common.interfaces.IContact.to_csv`"""
name = '"%s"' % self.name
number = '"%s"' % self.number
return [name, number]
class ContactStore(object):
"""
I am a contact store
A central point to perform operations on the different contact
backends (see :class:`~wader.common.interfaces.IContactProvider`)
"""
def __init__(self):
super(ContactStore, self).__init__()
self._providers = []
def add_provider(self, provider):
"""Adds ``provider`` to the list of registered providers"""
self._providers.append(provider)
def remove_provider(self, provider):
"""Removes ``provider`` to the list of registered providers"""
self._providers.remove(provider)
def close(self):
"""Frees resources"""
while self._providers:
provider = self._providers.pop()
provider.close()
def _call_method(self, name, *args):
"""
Executes method ``name`` using ``args`` in all the registered providers
"""
ret = []
for prov in self._providers:
result = getattr(prov, name)(*args)
if isinstance(result, list):
ret.extend(getattr(prov, name)(*args))
else:
ret.append(result)
return ret
def add_contact(self, data):
""":meth:`~wader.common.interfaces.IContactProvider.add_contact`"""
return self._call_method('add_contact', data)[0]
def edit_contact(self, data):
""":meth:`~wader.common.interfaces.IContactProvider.edit_contact`"""
return self._call_method('edit_contact', data)[0]
def find_contacts_by_name(self, name):
"""
:meth:`~wader.common.interfaces.IContactProvider.find_contacts_by_name`
"""
return self._call_method('find_contacts_by_name', name)
def find_contacts_by_number(self, number):
"""
see `IContactProvider.find_contacts_by_number`
"""
# first try a full match, if succeeds return result
# otherwise try to remove 3 chars and if succeeds return result
# i.e. match '723123112' instead of '+44723123112' (UK, ES)
# otherwise try to remove 4 chars and if succeeds return result
# i.e. match '821372121' instead of '+353821372121' (IE)
# otherwise we failed
for n in [0, 3, 4]:
# finding out if a generator returns None is a bit cumbersome
# so we just consume the generator and create a list
match = list(self._find_contacts_by_number(number[n:]))
if match:
return match
return []
def _find_contacts_by_number(self, number):
return self._call_method('find_contacts_by_number', number)
def list_contacts(self):
""":meth:`~wader.common.interfaces.IContactProvider.list_contacts`"""
return self._call_method('list_contacts')
def remove_contact(self, contact):
""":meth:`~wader.common.interfaces.IContactProvider.remove_contact`"""
self._call_method('remove_contact', contact)
wader-0.5.13/core/daemon.py 0000664 0000000 0000000 00000017264 12576466102 0015472 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2006-2008 Vodafone España, S.A.
# Copyright (C) 2008-2009 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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.
"""Daemons for Wader"""
from gobject import timeout_add_seconds, source_remove
from time import time
from twisted.python import log
import wader.common.aterrors as E
from wader.common.consts import (MM_MODEM_STATE_UNKNOWN,
MM_MODEM_STATE_CONNECTED)
import wader.common.signals as S
SIG_SMS_NOTIFY_ONLINE_FREQ = 5
SIG_SMS_NOTIFY_ONLINE_POLL = 6 # interval = FREQ * POLL
SIG_REG_INFO_FREQ = 120
SIG_REG_INFO_POLL = 5
SIG_RSSI_FREQ = 15
class WaderDaemon(object):
"""
I represent a Daemon in Wader
A Daemon is an entity that performs a repetitive action, like polling
signal quality from the data card. A Daemon will emit DBus signals as
if the device itself had emitted them.
"""
def __init__(self, frequency, device):
super(WaderDaemon, self).__init__()
self.frequency = frequency
self.device = device
self.task_id = None
def __repr__(self):
return self.__class__.__name__
def start(self):
"""Starts the Daemon"""
log.msg("daemon %s started..." % self)
if self.task_id is None:
args = (self, 'function', self.frequency)
log.msg("executing %s.%s every %d seconds" % args)
# call the first directly
self.function()
# setup the poll
self.task_id = timeout_add_seconds(self.frequency, self.function)
def stop(self):
"""Stops the Daemon"""
if self.task_id is not None:
source_remove(self.task_id)
self.task_id = None
log.msg("daemon %s stopped..." % self)
def function(self):
"""
Function that will be called periodically
It *always* needs to return True, otherwise it will be
executed just once
"""
raise NotImplementedError()
class SignalQualityDaemon(WaderDaemon):
"""I emit SIG_RSSI UnsolicitedNotifications"""
def function(self):
"""Executes `get_signal_quality` periodically"""
d = self.device.sconn.get_signal_quality()
d.addCallback(self.device.sconn.emit_rssi)
return True
class NetworkRegistrationDaemon(WaderDaemon):
"""I monitor several network registration parameters"""
def function(self):
# when our next invocation will occur
self.expiry = time() + self.frequency
def is_registered_cb((status, number, name)):
return status in [1, 5] and number and name
def is_registered_eb(failure):
failure.trap(E.SerialSendFailed)
# Fake registration to stop polling
return 1
def schedule_if_necessary(registered):
def poll():
d = self.device.sconn.get_netreg_info()
d.addCallbacks(is_registered_cb, is_registered_eb)
d.addCallback(schedule_if_necessary)
return False # once only
if not registered and \
time() < (self.expiry - (SIG_REG_INFO_POLL * 2)):
timeout_add_seconds(SIG_REG_INFO_POLL, poll)
d = self.device.sconn.get_netreg_info()
d.addCallbacks(is_registered_cb, is_registered_eb)
d.addCallback(schedule_if_necessary)
return True
class SmsNotifyOnlineDaemon(WaderDaemon):
"""I monitor SMS appearing without notification"""
def __init__(self, frequency, device):
super(SmsNotifyOnlineDaemon, self).__init__(frequency, device)
self.last_status = MM_MODEM_STATE_UNKNOWN
self.last_poll = 1
def _set_smslist(self, l):
self.smslist = l
def _cmp_smslist(self, polled):
newsms = [sms for sms in polled if not sms in self.smslist]
if len(newsms):
for sms in newsms:
index = sms.to_dict()['index']
log.msg("SmsNotifyOnlineDaemon new SMS appeared %d" % index)
self.device.sconn.mal.on_sms_notification(index)
self.smslist = polled
def function(self):
if self.device.status is MM_MODEM_STATE_CONNECTED:
if self.device.status != self.last_status:
# we just got connected
self.last_poll = 0
d = self.device.sconn.mal.list_sms_raw()
d.addCallback(self._set_smslist)
else:
self.last_poll += 1
if (self.last_poll % SIG_SMS_NOTIFY_ONLINE_POLL) == 0:
d = self.device.sconn.mal.list_sms_raw()
d.addCallback(self._cmp_smslist)
self.last_status = self.device.status
return True
class WaderDaemonCollection(object):
"""
I am a collection of Daemons
I provide some methods to manage the collection.
"""
def __init__(self):
self.daemons = {}
self.running = False
def append_daemon(self, name, daemon):
"""Adds ``daemon`` to the collection identified by ``name``"""
self.daemons[name] = daemon
def has_daemon(self, name):
"""Returns True if daemon ``name`` exists"""
return name in self.daemons
def remove_daemon(self, name):
"""Removes daemon with ``name``"""
del self.daemons[name]
def start_daemons(self, arg=None):
"""Starts all daemons"""
for daemon in self.daemons.values():
daemon.start()
self.running = True
def stop_daemon(self, name):
"""Stops daemon identified by ``name``"""
try:
self.daemons[name].stop()
except KeyError:
raise
def stop_daemons(self):
"""Stops all daemons"""
for daemon in self.daemons.values():
daemon.stop()
self.running = False
def build_daemon_collection(device):
"""Returns a :class:`WaderServiceCollection` customized for ``device``"""
collection = WaderDaemonCollection()
# check capabilities
if not device.ports.has_two() or \
S.SIG_RSSI not in device.custom.device_capabilities:
# The device doesn't send unsolicited notifications about RSSI changes,
# or it has only one port which means it will never be able to send us
# unsolicited notifications, so we'll have to fake them.
interval = SIG_RSSI_FREQ
else:
# Ensure we at least update every two minutes
interval = 120
daemon = SignalQualityDaemon(interval, device)
collection.append_daemon(S.SIG_RSSI, daemon)
if S.SIG_SMS_NOTIFY_ONLINE not in device.custom.device_capabilities:
# device doesn't send unsolicited notifications whilst
# connected, so we'll have to poll
daemon = SmsNotifyOnlineDaemon(SIG_SMS_NOTIFY_ONLINE_FREQ, device)
collection.append_daemon(S.SIG_SMS_NOTIFY_ONLINE, daemon)
# daemons to be used regardless of ports or capabilities
daemon = NetworkRegistrationDaemon(SIG_REG_INFO_FREQ, device)
collection.append_daemon(S.SIG_REG_INFO, daemon)
return collection
wader-0.5.13/core/dialer.py 0000664 0000000 0000000 00000035341 12576466102 0015463 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2006-2008 Vodafone España, S.A.
# Copyright (C) 2008-2009 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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.
"""Dialer module abstracts the differences between dialers on different OSes"""
import dbus
from dbus.service import Object, BusName, method, signal
from zope.interface import implements
from twisted.python import log
from twisted.internet import reactor, task
from twisted.internet import defer
from wader.common._dbus import DBusExporterHelper
from wader.common.aterrors import CallIndexError
import wader.common.consts as consts
from wader.common.interfaces import IDialer
from wader.common.utils import convert_int_to_ip
CONFIG_DELAY = RECONNECTION_DELAY = 3
SECRETS_TIMEOUT = 3
class DialerConf(object):
"""I contain all the necessary information to connect to Internet"""
uuid = ""
apn = None
context = None
username = None
password = None
pin = None
connection = None
band = None
network_type = None
autoconnect = False
staticdns = False
dns1 = None
dns2 = None
refuse_pap = True
refuse_chap = True
def __init__(self):
super(DialerConf, self).__init__()
self.opath = None
def __repr__(self):
msg = ''
args = (self.apn, self.username, self.password)
return msg % args
def __str__(self):
return self.__repr__()
@staticmethod
def get_profile_secrets(profile):
resp = profile.GetSecrets('gsm', ['password'], False,
timeout=SECRETS_TIMEOUT)
if not resp:
# if we don't get secrets without asking, lets try asking
resp = profile.GetSecrets('gsm', ['password'], True,
timeout=SECRETS_TIMEOUT)
return resp['gsm']['passwd']
@classmethod
def from_dict(cls, settings):
"""Returns a new `:class:DialerConf` out of ``settings``"""
ret = cls()
# connection
ret.uuid = settings['connection']['uuid']
ret.autoconnect = settings['connection'].get('autoconnect', False)
# gsm
ret.apn = settings['gsm']['apn']
ret.username = settings['gsm'].get('username', '')
ret.password = settings['gsm'].get('password')
ret.band = settings['gsm'].get('band')
ret.network_type = settings['gsm'].get('network-type')
# ipv4 might not be present
if 'ipv4' in settings:
ret.staticdns = settings['ipv4'].get('ignore-auto-dns', False)
if settings['ipv4'].get('dns'):
dns1 = settings['ipv4']['dns'][0]
ret.dns1 = convert_int_to_ip(dns1)
if len(settings['ipv4']['dns']) > 1:
dns2 = settings['ipv4']['dns'][1]
ret.dns2 = convert_int_to_ip(dns2)
# ppp might not be present
if 'ppp' in settings:
# get authentication options
ret.refuse_pap = settings['ppp'].get('refuse-pap', True)
ret.refuse_chap = settings['ppp'].get('refuse-chap', True)
return ret
@classmethod
def from_dbus_path(cls, opath):
"""Returns a new `:class:DialerConf` out of ``opath``"""
profile = dbus.SystemBus().get_object(consts.WADER_PROFILES_SERVICE,
opath)
ret = DialerConf.from_dict(profile.GetSettings())
ret.opath = opath
# get the secrets
try:
ret.password = DialerConf.get_profile_secrets(profile)
except Exception, e:
log.err("Error fetching profile password, "
"setting password to ''. Reason: %s" % e)
ret.password = ''
return ret
class Dialer(Object):
"""
Base dialer class
Override me for new OSes
"""
implements(IDialer)
config = None
protocol = None
def __init__(self, device, opath, ctrl=None):
self.bus = dbus.SystemBus()
name = BusName(consts.WADER_DIALUP_SERVICE, bus=self.bus)
super(Dialer, self).__init__(bus_name=name, object_path=opath)
self.device = device
self.opath = opath
self.ctrl = ctrl
def close(self, path=None):
# remove from DBus bus
try:
self.remove_from_connection()
except LookupError:
# it's safe to ignore this exception
pass
return path
def configure(self, config):
"""
Configures ``self.device`` with ``config``
This method should perform any necessary actions to connect to
Internet like generating configuration files, modifying any necessary
files, etc.
:param config: `DialerConf` instance
"""
def connect(self):
"""Connects to Internet"""
def stop(self):
"""Stops a hung connection attempt"""
def disconnect(self):
"""Disconnects from Internet"""
@signal(dbus_interface=consts.WADER_DIALUP_INTFACE, signature='')
def Connected(self):
log.msg("emitting Connected signal")
@signal(dbus_interface=consts.WADER_DIALUP_INTFACE, signature='')
def Disconnected(self):
log.msg("emitting Disconnected signal")
@signal(dbus_interface=consts.WADER_DIALUP_INTFACE, signature='as')
def InvalidDNS(self, dns):
log.msg("emitting InvalidDNS(%s)" % dns)
class DialerManager(Object, DBusExporterHelper):
"""
I am responsible of all dial up operations
I provide a uniform API to make data calls using different
dialers on heterogeneous operating systems.
"""
def __init__(self, ctrl):
self.bus = dbus.SystemBus()
name = BusName(consts.WADER_DIALUP_SERVICE, bus=self.bus)
super(DialerManager, self).__init__(bus_name=name,
object_path=consts.WADER_DIALUP_OBJECT)
self._client_count = -1
# dict with the stablished connections, key is the object path of
# the connection and the value is the dialer being used.
self.connections = {}
# dict with the ongoing connection attempts, key is the device
# path and the value is the used dialer. The rationale of using
# the device path and not the connection opath is that the latter
# is returned when the connection is stablished, while the former
# is available from the first moment. It has the downer of only
# being able to stop one connection attempt per device.
self.connection_attempts = {}
# dict with the cached connections, key is the device path and the
# value is the used configuration. This is used to save the state
# of previous connections interrupted by a MMS connection.
self.connection_state = {}
self.ctrl = ctrl
self._connect_to_signals()
def _device_removed_cb(self, opath):
"""Executed when a device goes away"""
if opath in self.connections:
log.msg("Device %s removed! deleting dialer instance" % opath)
try:
self.deactivate_connection(opath)
except KeyError:
pass
def _connect_to_signals(self):
self.bus.add_signal_receiver(self._device_removed_cb,
"DeviceRemoved",
consts.WADER_INTFACE)
def get_dialer(self, dev_opath, opath, plain=False):
"""
Returns an instance of the dialer that will be used to connect
:param dev_opath: DBus object path of the device to use
:param opath: DBus object path of the dialer
"""
from core.backends import get_backend, plain_backend
device = self.ctrl.hm.clients[dev_opath]
if plain:
dialer_klass = plain_backend.get_dialer_klass(device)
else:
dialer_klass = get_backend().get_dialer_klass(device)
return dialer_klass(device, opath, ctrl=self.ctrl)
def get_next_opath(self):
"""Returns the next free object path"""
self._client_count += 1
return consts.WADER_DIALUP_BASE % self._client_count
def activate_connection(self, profile_opath, device_opath):
"""
Start a connection with device ``device_opath`` using ``profile_opath``
"""
conf = DialerConf.from_dbus_path(profile_opath)
# build dialer
dialer = self.get_dialer(device_opath, self.get_next_opath())
return self.do_activate_connection(conf, dialer)
def do_activate_connection(self, conf, dialer):
device_opath = dialer.device.opath
self.connection_attempts[device_opath] = dialer, conf
device = self.ctrl.hm.clients[device_opath]
def get_conn_id(ign):
conn_id = device.sconn.state_dict.get('conn_id')
if conn_id is None:
raise CallIndexError("conn_id is None")
conf.context = conn_id
return conn_id
def connect_cb(conn_opath):
# transfer the dialer from connection_attempts to connections dict
self.connections[conn_opath] = dialer, conf
if device_opath in self.connection_attempts:
self.connection_attempts.pop(device_opath)
# announce that a new connection is active
self.ConnectionChanged(conn_opath, True)
return conn_opath
d = dialer.configure(conf)
d.addCallback(get_conn_id)
d.addCallback(lambda ign: dialer.connect())
d.addCallback(connect_cb)
return d
def activate_mms_connection(self, settings, device_opath):
"""
Starts a MMS connection with device ``device_opath`` using ``settings``
"""
if device_opath in self.connection_state:
# this should never happen
log.err("activate_mms_connection: internal error, "
"device_opath is already stored")
# XXX: What exception should be used here?
return defer.fail()
# handle the case where a connection is already stablished
for conn_opath, (dialer, conf) in self.connections.items():
if dialer.device.opath == device_opath:
self.connection_state[device_opath] = dialer, conf
d = dialer.disconnect()
d.addCallback(dialer.close)
break
else:
# handle the case where a connection attempt is going on
if device_opath in self.connection_attempts:
dialer, conf = self.connection_attempts[device_opath]
self.connection_state[device_opath] = dialer, conf
d = dialer.disconnect()
d.addCallback(dialer.close)
else:
# if there was no connection/conn attempt, there's nothing to
# handle
d = defer.succeed(True)
def prepare_connection_and_activate(_):
conf = DialerConf.from_dict(settings)
# we want the plain dialer, pass True
dialer = self.get_dialer(device_opath, self.get_next_opath(), True)
return task.deferLater(reactor, RECONNECTION_DELAY,
self.do_activate_connection, conf, dialer)
d.addCallback(prepare_connection_and_activate)
return d
def deactivate_connection(self, conn_opath):
"""Stops connection of device ``device_opath``"""
if conn_opath not in self.connections:
raise KeyError("Dialup %s not handled" % conn_opath)
dialer, _ = self.connections.pop(conn_opath)
def on_disconnect(opath):
self.ConnectionChanged(conn_opath, False)
return dialer.close(opath)
d = dialer.disconnect()
d.addCallback(on_disconnect)
device_opath = dialer.device.opath
if device_opath not in self.connection_state:
return d
# there was a connection going on before, restore it
dialer, conf = self.connection_state.pop(device_opath)
return task.deferLater(reactor, RECONNECTION_DELAY,
self.do_activate_connection, conf, dialer)
def stop_connection(self, device_opath):
"""Stops connection attempt of device ``device_opath``"""
dialer, _ = self.connection_attempts.pop(device_opath)
d = dialer.stop()
d.addCallback(dialer.close)
return d
@method(consts.WADER_DIALUP_INTFACE, in_signature='oo', out_signature='o',
async_callbacks=('async_cb', 'async_eb'))
def ActivateConnection(self, profile_path, device_opath,
async_cb, async_eb):
"""See :meth:`DialerManager.activate_connection`"""
d = self.activate_connection(profile_path, device_opath)
return self.add_callbacks(d, async_cb, async_eb)
@method(consts.WADER_DIALUP_INTFACE, in_signature='a{sv}o',
out_signature='o', async_callbacks=('async_cb', 'async_eb'))
def ActivateMmsConnection(self, settings, device_opath, async_cb,
async_eb):
"""See :meth:`DialerManager.activate_mms_connection`"""
d = self.activate_mms_connection(settings, device_opath)
return self.add_callbacks(d, async_cb, async_eb)
@method(consts.WADER_DIALUP_INTFACE, in_signature='o', out_signature='',
async_callbacks=('async_cb', 'async_eb'))
def DeactivateConnection(self, device_opath, async_cb, async_eb):
"""See :meth:`DialerManager.deactivate_connection`"""
d = self.deactivate_connection(device_opath)
return self.add_callbacks_and_swallow(d, async_cb, async_eb)
@method(consts.WADER_DIALUP_INTFACE, in_signature='o', out_signature='',
async_callbacks=('async_cb', 'async_eb'))
def StopConnection(self, device_opath, async_cb, async_eb):
"""See :meth:`DialerManager.stop_connection`"""
d = self.stop_connection(device_opath)
return self.add_callbacks_and_swallow(d, async_cb, async_eb)
@signal(consts.WADER_DIALUP_INTFACE, signature='ob')
def ConnectionChanged(self, conn_opath, active):
log.msg("ConnectionChanged(%s, %s)" % (conn_opath, active))
wader-0.5.13/core/exported.py 0000664 0000000 0000000 00000105443 12576466102 0016056 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2008 Warp Networks S.L.
# Author: Pablo Martí
#
# 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.
"""
I export :class:`~wader.common.middleware.WCDMAWrapper` methods over DBus
"""
import dbus
from dbus.service import Object, BusName, method, signal
from twisted.internet import defer
from twisted.python import log
from wader.common.consts import (SMS_INTFACE, CTS_INTFACE, NET_INTFACE,
CRD_INTFACE, MDM_INTFACE, WADER_SERVICE,
HSO_INTFACE, SPL_INTFACE, USD_INTFACE,
MMS_INTFACE)
from wader.common.sms import Message
from core.mms import mms_to_dbus_data, dbus_data_to_mms
from core.contact import Contact
from wader.common._dbus import DBusExporterHelper
from wader.common.utils import (convert_ip_to_int,
convert_network_mode_to_access_technology)
# welcome to the multiple inheritance madness!
# python-dbus currently lacks an "export_as" keyword for use cases like
# us. Where we have a main object with dozens of methods that we want to
# export over several interfaces under repeated names, such as:
# org.freedesktop.ModemManager.Contacts.List
# org.freedesktop.ModemManager.SMS.List
# currently python-dbus requires you to create a new class and it will find
# the appropiated implementation through the MRO. But this leads to MH madness
# What we can do thou is rely on composition instead of MH for this one
def to_a(_list, signature='u'):
"""
Returns a :class:`dbus.Array` out of `_list`
:param signature: The dbus signature of the array
"""
return dbus.Array(sorted(_list), signature=signature)
class ModemExporter(Object, DBusExporterHelper):
"""I export the org.freedesktop.ModemManager.Modem interface"""
def __init__(self, device):
name = BusName(WADER_SERVICE, bus=dbus.SystemBus())
super(ModemExporter, self).__init__(bus_name=name,
object_path=device.opath)
self.device = device
self.sconn = device.sconn
@method(MDM_INTFACE, in_signature='s', out_signature='',
async_callbacks=('async_cb', 'async_eb'))
def Connect(self, number, async_cb, async_eb):
"""
Dials in the given number
:param number: number to dial
"""
# XXX: Use the passed number instead :/
num = "%s***%d#" % (str(number[:-1]), self.sconn.state_dict['conn_id'])
d = self.sconn.connect_to_internet(dict(number=num))
return self.add_callbacks_and_swallow(d, async_cb, async_eb)
@method(MDM_INTFACE, in_signature='', out_signature='',
async_callbacks=('async_cb', 'async_eb'))
def Disconnect(self, async_cb, async_eb):
"""Disconnects modem"""
d = self.sconn.disconnect_from_internet()
return self.add_callbacks_and_swallow(d, async_cb, async_eb)
@method(MDM_INTFACE, in_signature='b', out_signature='',
async_callbacks=('async_cb', 'async_eb'))
def Enable(self, enable, async_cb, async_eb):
"""
Performs some initial setup in the device
:param enable: whether device should be enabled or disabled
:type enable: bool
"""
d = self.sconn.enable_device(enable)
return self.add_callbacks_and_swallow(d, async_cb, async_eb)
@method(dbus.PROPERTIES_IFACE, in_signature='ss', out_signature='v')
def Get(self, interface, _property):
"""See org.freedesktop.DBus.Properties documentation"""
try:
return self.device.get_property(interface, _property)
except KeyError:
args = (interface, _property)
raise ValueError("Unknown interface %s or property %s" % args)
@method(dbus.PROPERTIES_IFACE, in_signature='s', out_signature='a{sv}')
def GetAll(self, interface_name):
"""See org.freedesktop.DBus.Properties documentation"""
if interface_name in self.device.props:
return self.device.props[interface_name]
@method(MDM_INTFACE, in_signature='', out_signature='(sss)',
async_callbacks=('async_cb', 'async_eb'))
def GetInfo(self, async_cb, async_eb):
"""
Returns the manufacturer, modem model and firmware version
:rtype: tuple
"""
d = self.sconn.get_hardware_info()
return self.add_callbacks(d, async_cb, async_eb)
@method(MDM_INTFACE, in_signature='', out_signature='(uu)',
async_callbacks=('async_cb', 'async_eb'))
def GetStats(self, async_cb, async_eb):
"""
Returns the current rx_bytes, tx_bytes of the network interface
:rtype: tuple
"""
def sanitise(response):
if response is None:
return (0, 0)
else:
return response[:2]
d = defer.succeed(self.sconn.get_stats())
d.addCallback(sanitise)
return self.add_callbacks(d, async_cb, async_eb)
@method(MDM_INTFACE, in_signature='', out_signature='(uuuu)',
async_callbacks=('async_cb', 'async_eb'))
def GetIP4Config(self, async_cb, async_eb):
"""
Requests the IP4 configuration from the device
:rtype: tuple
"""
d = self.sconn.get_ip4_config()
d.addCallback(lambda reply: map(convert_ip_to_int, reply))
return self.add_callbacks(d, async_cb, async_eb)
@method(MDM_INTFACE, in_signature='', out_signature='',
async_callbacks=('async_cb', 'async_eb'))
def FactoryReset(self, async_cb, async_eb):
"""Reset the modem to as close to factory state as possible"""
d = self.sconn.reset_settings()
return self.add_callbacks_and_swallow(d, async_cb, async_eb)
@signal(dbus_interface=MDM_INTFACE, signature='o')
def DeviceEnabled(self, opath):
log.msg("emitting DeviceEnabled('%s')" % opath)
@signal(dbus_interface=MDM_INTFACE, signature='(uuuu)')
def DialStats(self, (rx_bytes, tx_bytes, rx_rate, tx_rate)):
pass
@signal(dbus_interface=MDM_INTFACE, signature='uuu')
def StateChanged(self, old, new, reason):
log.msg("emitting StateChanged(%d, %d, %d)" % (old, new, reason))
@signal(dbus_interface=dbus.PROPERTIES_IFACE, signature='sa{sv}')
def MmPropertiesChanged(self, iface, properties):
log.msg("emitting MmPropertiesChanged: %s %s" % (iface, properties))
class SimpleExporter(ModemExporter):
"""I export the org.freedesktop.ModemManager.Modem.Simple interface"""
@method(SPL_INTFACE, in_signature='a{sv}', out_signature='',
async_callbacks=('async_cb', 'async_eb'))
def Connect(self, settings, async_cb, async_eb):
"""
Connects with the given settings
:type settings: dict
:param settings: documented in ModemManager spec
"""
d = self.sconn.connect_simple(settings)
return self.add_callbacks_and_swallow(d, async_cb, async_eb)
@method(SPL_INTFACE, in_signature='', out_signature='a{sv}',
async_callbacks=('async_cb', 'async_eb'))
def GetStatus(self, async_cb, async_eb):
"""
Get the modem status
:rtype: dict
"""
def get_simple_status_cb(status):
# by default it is converted to Int32
for name in ['signal_quality', 'band', 'network_mode']:
if name in status:
status[name] = dbus.UInt32(status[name])
return status
d = self.sconn.get_simple_status()
d.addCallback(get_simple_status_cb)
return self.add_callbacks(d, async_cb, async_eb)
class CardExporter(SimpleExporter):
"""I export the org.freedesktop.ModemManager.Modem.Gsm.Card methods"""
@method(CRD_INTFACE, in_signature='ss', out_signature='',
async_callbacks=('async_cb', 'async_eb'))
def ChangePin(self, oldpin, newpin, async_cb, async_eb):
"""
Changes PIN from ``oldpin`` to ``newpin``
:param oldpin: The old PIN
:param newpin: The new PIN
"""
d = self.sconn.change_pin(oldpin, newpin)
return self.add_callbacks_and_swallow(d, async_cb, async_eb)
@method(CRD_INTFACE, in_signature='', out_signature='s',
async_callbacks=('async_cb', 'async_eb'))
def Check(self, async_cb, async_eb):
"""
Returns the SIM authentication state
:raise ``SimPinRequired``: If PIN is required
:raise ``SimPukRequired``: If PUK is required
:raise ``SimPuk2Required``: If PUK2 is required
"""
d = self.sconn.check_pin()
return self.add_callbacks(d, async_cb, async_eb)
@method(CRD_INTFACE, in_signature='b', out_signature='',
async_callbacks=('async_cb', 'async_eb'))
def EnableEcho(self, enable, async_cb, async_eb):
"""
Enables or disables echo
Enabling echo will leave your connection unusable as this
application assumes that it will be disabled
:param enable: Whether echo should be disabled or not
"""
if enable:
d = self.sconn.enable_echo()
else:
d = self.sconn.disable_echo()
return self.add_callbacks_and_swallow(d, async_cb, async_eb)
@method(CRD_INTFACE, in_signature='sb', out_signature='',
async_callbacks=('async_cb', 'async_eb'))
def EnablePin(self, pin, enable, async_cb, async_eb):
"""
Enables or disables PIN authentication
:param pin: The PIN to use
:param enable: Whether PIN auth should be enabled or disabled
"""
d = self.sconn.enable_pin(pin, enable)
return self.add_callbacks_and_swallow(d, async_cb, async_eb)
@method(CRD_INTFACE, in_signature='', out_signature='s',
async_callbacks=('async_cb', 'async_eb'))
def GetCharset(self, async_cb, async_eb):
"""Returns active charset"""
d = self.sconn.get_charset()
return self.add_callbacks(d, async_cb, async_eb)
@method(CRD_INTFACE, in_signature='', out_signature='as',
async_callbacks=('async_cb', 'async_eb'))
def GetCharsets(self, async_cb, async_eb):
"""Returns the available charsets in SIM"""
d = self.sconn.get_charsets()
return self.add_callbacks(d, async_cb, async_eb)
@method(CRD_INTFACE, in_signature='', out_signature='s',
async_callbacks=('async_cb', 'async_eb'))
def GetImei(self, async_cb, async_eb):
"""Returns the IMEI"""
d = self.sconn.get_imei()
return self.add_callbacks(d, async_cb, async_eb)
@method(CRD_INTFACE, in_signature='', out_signature='s',
async_callbacks=('async_cb', 'async_eb'))
def GetImsi(self, async_cb, async_eb):
"""Returns the IMSI"""
d = self.sconn.get_imsi()
return self.add_callbacks(d, async_cb, async_eb)
@method(CRD_INTFACE, in_signature='', out_signature='s',
async_callbacks=('async_cb', 'async_eb'))
def GetOperatorId(self, async_cb, async_eb):
"""
Returns the ID of the network operator that issued the SIM card,
formatted as a 5 or 6-digit MCC/MNC code (ex "310410").
"""
d = self.sconn.get_operator_id()
return self.add_callbacks(d, async_cb, async_eb)
@method(CRD_INTFACE, in_signature='', out_signature='s',
async_callbacks=('async_cb', 'async_eb'))
def GetSpn(self, async_cb, async_eb):
"""Returns the SPN (Service Provider Name)."""
d = self.sconn.get_spn()
return self.add_callbacks(d, async_cb, async_eb)
@method(CRD_INTFACE, in_signature='s', out_signature='s',
async_callbacks=('async_cb', 'async_eb'))
def SendATString(self, at_str, async_cb, async_eb):
"""
Sends an arbitrary AT command
:param at_str: The AT command to be sent
"""
d = self.sconn.send_at(at_str)
return self.add_callbacks(d, async_cb, async_eb)
@method(CRD_INTFACE, in_signature='s', out_signature='',
async_callbacks=('async_cb', 'async_eb'))
def SendPin(self, pin, async_cb, async_eb):
"""
Sends ``pin`` to authenticate with SIM
:param pin: The PIN to authenticate with
"""
d = self.sconn.send_pin(pin)
# check_initted_device will check if a Enable call was
# interrupted because of PINNeededError and will continue
# if auth is successful
d.addCallback(self.sconn._check_initted_device)
return self.add_callbacks_and_swallow(d, async_cb, async_eb)
@method(CRD_INTFACE, in_signature='ss', out_signature='',
async_callbacks=('async_cb', 'async_eb'))
def SendPuk(self, puk, pin, async_cb, async_eb):
"""
Sends ``puk`` and ``pin`` to authenticate with SIM
:param puk: The PUK to authenticate with
:param pin: The PIN to authenticate with
"""
d = self.sconn.send_puk(puk, pin)
# check_initted_device will check if a Enable call was
# interrupted because of PUKNeededError and will continue
# if auth is successful
d.addCallback(self.sconn._check_initted_device)
return self.add_callbacks_and_swallow(d, async_cb, async_eb)
@method(CRD_INTFACE, in_signature='s', out_signature='',
async_callbacks=('async_cb', 'async_eb'))
def SetCharset(self, charset, async_cb, async_eb):
"""
Sets the SIM charset to ``charset``
:param charset: The character set to use
"""
d = self.sconn.set_charset(charset.encode('utf8'))
return self.add_callbacks_and_swallow(d, async_cb, async_eb)
class ContactsExporter(CardExporter):
"""
I export the org.freedesktop.ModemManager.Modem.Gsm.Contacts interface
"""
@method(CTS_INTFACE, in_signature='ss', out_signature='u',
async_callbacks=('async_cb', 'async_eb'))
def Add(self, name, number, async_cb, async_eb):
"""
Adds a contact and returns the index
:param name: The contact name
:param number: The contact number
:rtype: int
"""
d = self.sconn.add_contact(Contact(name, number))
return self.add_callbacks(d, async_cb, async_eb)
@method(CTS_INTFACE, in_signature='u', out_signature='',
async_callbacks=('async_cb', 'async_eb'))
def Delete(self, index, async_cb, async_eb):
"""
Deletes the contact at ``index``
:param index: The index of the contact to be deleted
"""
d = self.sconn.delete_contact(index)
return self.add_callbacks_and_swallow(d, async_cb, async_eb)
@method(CTS_INTFACE, in_signature='uss', out_signature='u',
async_callbacks=('async_cb', 'async_eb'))
def Edit(self, index, name, number, async_cb, async_eb):
"""
Edits the contact at ``index``
:param name: The new name of the contact to be edited
:param number: The new number of the contact to be edited
:param index: The index of the contact to be edited
"""
d = self.sconn.add_contact(Contact(name, number, index=index))
return self.add_callbacks(d, async_cb, async_eb)
@method(CTS_INTFACE, in_signature='s', out_signature='a(uss)',
async_callbacks=('async_cb', 'async_eb'))
def FindByName(self, pattern, async_cb, async_eb):
"""
Returns list of contacts whose name match ``pattern``
:param pattern: The pattern to match contacts against
:rtype: list
"""
d = self.sconn.find_contacts(pattern)
d.addCallback(lambda contacts:
[(c.index, c.name, c.number) for c in contacts])
return self.add_callbacks(d, async_cb, async_eb)
@method(CTS_INTFACE, in_signature='s', out_signature='a(uss)',
async_callbacks=('async_cb', 'async_eb'))
def FindByNumber(self, number, async_cb, async_eb):
"""
Returns list of contacts whose number match ``number``
:param number: The number to match contacts against
:rtype: list
"""
d = self.sconn.list_contacts()
d.addCallback(lambda contacts:
[(c.index, c.name, c.number) for c in contacts
if c.number.endswith(number)])
return self.add_callbacks(d, async_cb, async_eb)
@method(CTS_INTFACE, in_signature='u', out_signature='(uss)',
async_callbacks=('async_cb', 'async_eb'))
def Get(self, index, async_cb, async_eb):
"""
Returns the contact at ``index``
:param index: The index of the contact to get
:rtype: tuple
"""
d = self.sconn.get_contact(index)
d.addCallback(lambda c: (c.index, c.name, c.number))
return self.add_callbacks(d, async_cb, async_eb)
@method(CTS_INTFACE, in_signature='', out_signature='u',
async_callbacks=('async_cb', 'async_eb'))
def GetCount(self, async_cb, async_eb):
"""Returns the number of contacts in the SIM"""
d = self.sconn.list_contacts()
d.addCallback(len)
return self.add_callbacks(d, async_cb, async_eb)
@method(CTS_INTFACE, in_signature='', out_signature='i',
async_callbacks=('async_cb', 'async_eb'))
def GetPhonebookSize(self, async_cb, async_eb):
"""Returns the phonebook size"""
d = self.sconn.get_phonebook_size()
return self.add_callbacks(d, async_cb, async_eb)
@method(CTS_INTFACE, in_signature='', out_signature='a(uss)',
async_callbacks=('async_cb', 'async_eb'))
def List(self, async_cb, async_eb):
"""
Returns all the contacts in the SIM
:rtype: list of tuples
"""
d = self.sconn.list_contacts()
d.addCallback(lambda contacts:
[(c.index, c.name, c.number) for c in contacts])
return self.add_callbacks(d, async_cb, async_eb)
class NetworkExporter(ContactsExporter):
"""I export the org.freedesktop.ModemManager.Modem.Gsm.Network interface"""
@method(NET_INTFACE, in_signature='', out_signature='a(us)',
async_callbacks=('async_cb', 'async_eb'))
def GetApns(self, async_cb, async_eb):
"""Returns all the APNs stored in the system"""
d = self.sconn.get_apns()
return self.add_callbacks(d, async_cb, async_eb)
@method(NET_INTFACE, in_signature='', out_signature='u',
async_callbacks=('async_cb', 'async_eb'))
def GetBand(self, async_cb, async_eb):
"""Returns the currently used band"""
d = self.sconn.get_band()
return self.add_callbacks(d, async_cb, async_eb)
@method(NET_INTFACE, in_signature='', out_signature='u',
async_callbacks=('async_cb', 'async_eb'))
def GetNetworkMode(self, async_cb, async_eb):
"""Returns the network mode"""
d = self.sconn.get_network_mode()
return self.add_callbacks(d, async_cb, async_eb)
@method(NET_INTFACE, in_signature='', out_signature='(uss)',
async_callbacks=('async_cb', 'async_eb'))
def GetRegistrationInfo(self, async_cb, async_eb):
"""
Returns the network registration status and operator
:rtype: tuple
"""
d = self.sconn.get_netreg_info()
return self.add_callbacks(d, async_cb, async_eb)
@method(NET_INTFACE, in_signature='', out_signature='as',
async_callbacks=('async_cb', 'async_eb'))
def GetRoamingIDs(self, async_cb, async_eb):
"""Returns all the roaming IDs stored in the SIM"""
d = self.sconn.get_roaming_ids()
d.addCallback(lambda objs: [obj.netid for obj in objs])
return self.add_callbacks(d, async_cb, async_eb)
@method(NET_INTFACE, in_signature='', out_signature='u',
async_callbacks=('async_cb', 'async_eb'))
def GetSignalQuality(self, async_cb, async_eb):
"""Returns the signal quality"""
d = self.sconn.get_signal_quality()
return self.add_callbacks(d, async_cb, async_eb)
@method(NET_INTFACE, in_signature='', out_signature='aa{ss}',
async_callbacks=('async_cb', 'async_eb'))
def Scan(self, async_cb, async_eb):
"""Returns the basic information of the networks around"""
d = self.sconn.get_network_names()
def process_netnames(netobjs):
response = []
for n in netobjs:
# status should be an int, but it appeared in the
# ModemManager spec first as a string and in order
# to not break existing software (it seems that
# nm-applet in OpenSuSe uses it) we decided not to
# change it for now.
net = {'status': str(n.stat),
'operator-long': n.long_name,
'operator-short': n.short_name,
'operator-num': n.netid}
if n.rat:
# some devices won't provide this info
net['access-tech'] = str(n.rat)
response.append(net)
return response
d.addCallback(process_netnames)
return self.add_callbacks(d, async_cb, async_eb)
@method(NET_INTFACE, in_signature='u', out_signature='',
async_callbacks=('async_cb', 'async_eb'))
def SetAllowedMode(self, mode, async_cb, async_eb):
"""
Set the access technologies a device is allowed to use when connecting
:param mode: The allowed mode. Device may not support all modes.
:type mode: int
"""
d = self.sconn.set_allowed_mode(mode)
return self.add_callbacks_and_swallow(d, async_cb, async_eb)
@method(NET_INTFACE, in_signature='s', out_signature='',
async_callbacks=('async_cb', 'async_eb'))
def SetApn(self, apn, async_cb, async_eb):
"""
Sets the APN to ``apn``
:param apn: The APN to use
"""
d = self.sconn.set_apn(apn)
return self.add_callbacks_and_swallow(d, async_cb, async_eb)
@method(NET_INTFACE, in_signature='u', out_signature='',
async_callbacks=('async_cb', 'async_eb'))
def SetBand(self, band, async_cb, async_eb):
"""
Sets the band to ``band``
:param band: The band to use
:type band: int
"""
d = self.sconn.set_band(band)
return self.add_callbacks_and_swallow(d, async_cb, async_eb)
@method(NET_INTFACE, in_signature='u', out_signature='',
async_callbacks=('async_cb', 'async_eb'))
def SetNetworkMode(self, mode, async_cb, async_eb):
"""
Sets the network mode to ``mode``
:param mode: The network mode to use
:type mode: int
"""
d = self.sconn.set_network_mode(mode)
return self.add_callbacks_and_swallow(d, async_cb, async_eb)
@method(NET_INTFACE, in_signature='b', out_signature='',
async_callbacks=('async_cb', 'async_eb'))
def SetRegistrationNotification(self, active, async_cb, async_eb):
"""
Sets the network registration notifications
:param active: Enable registration notifications
:type active: bool
"""
d = self.sconn.set_netreg_notification(int(active))
return self.add_callbacks_and_swallow(d, async_cb, async_eb)
@method(NET_INTFACE, in_signature='uu', out_signature='',
async_callbacks=('async_cb', 'async_eb'))
def SetInfoFormat(self, mode, _format, async_cb, async_eb):
"""
Sets the network info format
:param mode: The network mode
:type mode: int
:param _format: The network format
:type _format: int
"""
d = self.sconn.set_network_info_format(mode, _format)
return self.add_callbacks_and_swallow(d, async_cb, async_eb)
@method(NET_INTFACE, in_signature='s', out_signature='',
async_callbacks=('async_cb', 'async_eb'))
def Register(self, netid, async_cb, async_eb):
"""
Registers with ``netid``
If netid is an empty string it will try to register with the
home network or the first provider around whose MNC matches
with one of the response of +CPOL?
:param netid: The network id to register with
:type netid: str
"""
d = self.sconn.register_with_netid(netid)
return self.add_callbacks_and_swallow(d, async_cb, async_eb)
@signal(dbus_interface=NET_INTFACE, signature='uss')
def RegistrationInfo(self, status, operator_code, operator_name):
args = (status, operator_code, operator_name)
log.msg("emitting RegistrationInfo(%d, '%s', '%s')" % args)
@signal(dbus_interface=NET_INTFACE, signature='u')
def NetworkMode(self, mode):
log.msg("emitting NetworkMode(%d)" % mode)
# we will update AccessTechnology from here
tech = convert_network_mode_to_access_technology(mode)
self.device.set_property(NET_INTFACE, 'AccessTechnology', tech)
@signal(dbus_interface=NET_INTFACE, signature='u')
def CregReceived(self, status):
log.msg("emitting CregReceived(%d)" % status)
@signal(dbus_interface=NET_INTFACE, signature='u')
def SignalQuality(self, rssi):
log.msg("emitting SignalQuality(%d)" % rssi)
class MmsExporter(NetworkExporter):
"""I export the org.freedesktop.ModemManager.Modem.Gsm.Mms interface"""
@method(MMS_INTFACE, in_signature='ua{sv}', out_signature='',
async_callbacks=('async_cb', 'async_eb'))
def Acknowledge(self, index, extra_info, async_cb, async_eb):
"""
Acknowledge reception of MMS identified by ``index``
:param index: The MMS index
:param extra_info: Dict with MMSC url, port, etc.
"""
d = self.sconn.acknowledge_mms(index, extra_info)
return self.add_callbacks_and_swallow(d, async_cb, async_eb)
@method(MMS_INTFACE, in_signature='', out_signature='a(ua{sv})',
async_callbacks=('async_cb', 'async_eb'))
def Available(self, async_cb, async_eb):
"""Lists all the available m-notification.ind"""
d = self.sconn.list_available_mms()
return self.add_callbacks(d, async_cb, async_eb)
@method(MMS_INTFACE, in_signature='ua{sv}', out_signature='(a{sv}aa{sv})',
async_callbacks=('async_cb', 'async_eb'))
def Download(self, index, extra_info, async_cb, async_eb):
"""
Retrieves the MMS identified by ``index``
:param index: The MMS index
:param extra_info: Dict with MMSC url, port, etc.
"""
d = self.sconn.download_mms(index, extra_info)
d.addCallback(mms_to_dbus_data)
return self.add_callbacks(d, async_cb, async_eb)
@method(MMS_INTFACE, in_signature='a{sv}aa{sv}a{sv}', out_signature='s',
async_callbacks=('async_cb', 'async_eb'))
def Send(self, headers, data_parts, extra_info, async_cb, async_eb):
"""
Sends a MMS and returns the Message-Id
:param headers: MMS headers
:param data_parts: data parts of the MMS
:param extra_info: Dict with MMSC url, port, etc.
"""
mms = dbus_data_to_mms(headers, data_parts)
d = self.sconn.send_mms(mms, extra_info)
d.addCallback(lambda mms: mms.headers['Message-Id'])
return self.add_callbacks(d, async_cb, async_eb)
@signal(dbus_interface=MMS_INTFACE, signature='s')
def Delivered(self, message_id):
log.msg('Emitting Delivered(%s)' % message_id)
@signal(dbus_interface=MMS_INTFACE, signature='ua{sv}')
def MMSReceived(self, index, headers):
log.msg('Emitting MMSReceived(%d, %s)' % (index, headers))
class SmsExporter(MmsExporter):
"""I export the org.freedesktop.ModemManager.Modem.Gsm.SMS interface"""
@method(SMS_INTFACE, in_signature='u', out_signature='',
async_callbacks=('async_cb', 'async_eb'))
def Delete(self, index, async_cb, async_eb):
"""
Deletes the SMS at ``index``
:param index: The SMS index
"""
d = self.sconn.delete_sms(index)
return self.add_callbacks_and_swallow(d, async_cb, async_eb)
@method(SMS_INTFACE, in_signature='u', out_signature='a{sv}',
async_callbacks=('async_cb', 'async_eb'))
def Get(self, index, async_cb, async_eb):
"""
Returns the SMS stored at ``index``
:param index: The SMS index
"""
d = self.sconn.get_sms(index)
d.addCallback(lambda sms: sms.to_dict())
return self.add_callbacks(d, async_cb, async_eb)
@method(SMS_INTFACE, in_signature='', out_signature='s',
async_callbacks=('async_cb', 'async_eb'))
def GetSmsc(self, async_cb, async_eb):
"""Returns the SMSC number stored in the SIM"""
d = self.sconn.get_smsc()
return self.add_callbacks(d, async_cb, async_eb)
@method(SMS_INTFACE, in_signature='', out_signature='u',
async_callbacks=('async_cb', 'async_eb'))
def GetFormat(self, async_cb, async_eb):
"""Returns 1 if SMS format is text and 0 if SMS format is PDU"""
d = self.sconn.get_sms_format()
return self.add_callbacks(d, async_cb, async_eb)
@method(SMS_INTFACE, in_signature='', out_signature='aa{sv}',
async_callbacks=('async_cb', 'async_eb'))
def List(self, async_cb, async_eb):
"""Returns all the SMS stored in SIM"""
d = self.sconn.list_sms()
return self.add_callbacks(d, async_cb, async_eb)
@method(SMS_INTFACE, in_signature='a{sv}', out_signature='au',
async_callbacks=('async_cb', 'async_eb'))
def Save(self, sms, async_cb, async_eb):
"""
Save a SMS ``sms`` and returns the index
:param sms: dictionary with the settings to use
:rtype: int
"""
d = self.sconn.save_sms(Message.from_dict(sms))
return self.add_callbacks(d, async_cb, async_eb)
@method(SMS_INTFACE, in_signature='a{sv}', out_signature='au',
async_callbacks=('async_cb', 'async_eb'))
def Send(self, sms, async_cb, async_eb):
"""
Sends SMS ``sms``
:param sms: dictionary with the settings to use
:rtype: list
"""
d = self.sconn.send_sms(Message.from_dict(sms))
return self.add_callbacks(d, async_cb, async_eb)
@method(SMS_INTFACE, in_signature='u', out_signature='au',
async_callbacks=('async_cb', 'async_eb'))
def SendFromStorage(self, index, async_cb, async_eb):
"""
Sends the SMS stored at ``index`` and returns the new index
:param index: The index of the stored SMS to be sent
:rtype: int
"""
d = self.sconn.send_sms_from_storage(index)
return self.add_callbacks(d, async_cb, async_eb)
@method(SMS_INTFACE, in_signature='u', out_signature='',
async_callbacks=('async_cb', 'async_eb'))
def SetFormat(self, _format, async_cb, async_eb):
"""Sets the SMS format"""
if _format not in [0, 1]:
async_eb(ValueError("Invalid SMS format %s" % repr(_format)))
d = self.sconn.set_sms_format(_format)
return self.add_callbacks_and_swallow(d, async_cb, async_eb)
@method(SMS_INTFACE, in_signature='uuuuu', out_signature='',
async_callbacks=('async_cb', 'async_eb'))
def SetIndication(self, mode, mt, bm, ds, bfr, async_cb, async_eb):
"""Sets the SMS indication"""
d = self.sconn.set_sms_indication(mode, mt, bm, ds, bfr)
return self.add_callbacks_and_swallow(d, async_cb, async_eb)
@method(SMS_INTFACE, in_signature='s', out_signature='',
async_callbacks=('async_cb', 'async_eb'))
def SetSmsc(self, smsc, async_cb, async_eb):
"""
Sets the SMSC to ``smsc``
:param smsc: The SMSC to use
"""
d = self.sconn.set_smsc(smsc)
return self.add_callbacks_and_swallow(d, async_cb, async_eb)
@signal(dbus_interface=SMS_INTFACE, signature='ub')
def SmsReceived(self, index, complete):
log.msg('Emitting SmsReceived(%d, %s)' % (index, complete))
@signal(dbus_interface=SMS_INTFACE, signature='ub')
def Completed(self, index, completed):
log.msg('emitting Complete(%d)' % index)
@signal(dbus_interface=SMS_INTFACE, signature='u')
def Delivered(self, reference):
log.msg('emitting Delivered(%d)' % reference)
class UssdExporter(SmsExporter):
"""I export the org.freedesktop.ModemManager.Modem.Gsm.Ussd interface"""
@method(USD_INTFACE, in_signature='', out_signature='',
async_callbacks=('async_cb', 'async_eb'))
def Cancel(self, async_cb, async_eb):
"""Cancels an ongoing USSD session"""
d = self.sconn.cancel_ussd()
return self.add_callbacks_and_swallow(d, async_cb, async_eb)
@method(USD_INTFACE, in_signature='s', out_signature='s',
async_callbacks=('async_cb', 'async_eb'))
def Initiate(self, command, async_cb, async_eb):
"""Sends the USSD command ``command``"""
self.device.set_property(USD_INTFACE, 'State', 'active')
d = self.sconn.send_ussd(command)
return self.add_callbacks(d, async_cb, async_eb)
@method(USD_INTFACE, in_signature='s', out_signature='s',
async_callbacks=('async_cb', 'async_eb'))
def Respond(self, reply, async_cb, async_eb):
"""Sends ``reply`` to the network"""
self.device.set_property(USD_INTFACE, 'State', 'active')
d = self.sconn.send_ussd(reply)
return self.add_callbacks(d, async_cb, async_eb)
@signal(dbus_interface=USD_INTFACE, signature='s')
def NotificationReceived(self, message):
log.msg("emitting NotificationReceived(%s)" % message)
@signal(dbus_interface=USD_INTFACE, signature='s')
def RequestReceived(self, message):
log.msg("emitting RequestReceived(%s)" % message)
class WCDMAExporter(UssdExporter):
"""I export the org.freedesktop.ModemManager.Modem* interface"""
def __str__(self):
return self.device.__remote_name__
__repr__ = __str__
class HSOExporter(WCDMAExporter):
"""I export the org.freedesktop.ModemManager.Modem.Gsm.Hso interface"""
@method(HSO_INTFACE, in_signature='ss', out_signature='',
async_callbacks=('async_cb', 'async_eb'))
def Authenticate(self, user, passwd, async_cb, async_eb):
"""
Authenticate using ``user`` and ``passwd``
:param user: The username to be used in authentication
:param passwd: The password to be used in authentication
"""
d = self.sconn.hso_authenticate(user, passwd)
return self.add_callbacks_and_swallow(d, async_cb, async_eb)
wader-0.5.13/core/hardware/ 0000775 0000000 0000000 00000000000 12576466102 0015440 5 ustar 00root root 0000000 0000000 wader-0.5.13/core/hardware/__init__.py 0000664 0000000 0000000 00000001636 12576466102 0017557 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2006-2008 Vodafone España, S.A.
# Copyright (C) 2008-2009 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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.
"""
The hardware module contains device family-specific code and utils
"""
wader-0.5.13/core/hardware/base.py 0000664 0000000 0000000 00000017663 12576466102 0016741 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2006-2008 Vodafone España, S.A.
# Copyright (C) 2008-2009 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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.
"""Base classes for the hardware module"""
from __future__ import with_statement
from contextlib import closing
import re
import serial
from time import sleep
from twisted.internet.threads import deferToThread
from twisted.python import log
from core.command import get_cmd_dict_copy
from wader.common.consts import MDM_INTFACE
from core.middleware import WCDMAWrapper
from core.plugin import PluginManager
from core.statem.auth import AuthStateMachine
from core.statem.simple import SimpleStateMachine
from core.statem.networkreg import NetworkRegistrationStateMachine
import wader.common.exceptions as ex
class WCDMACustomizer(object):
"""
I contain all the custom classes and metadata that a WCDMA device needs
:cvar wrapper_klass: Wrapper for the device
:cvar exporter_klass: DBus Exporter for the device
:cvar async_regexp: regexp to parse asynchronous notifications emited
by the device.
:cvar allowed_dict: Dictionary with the allowed modes
:cvar band_dict: Dictionary with the supported bands
:cvar conn_dict: Dictionary with the supported network modes
:cvar cmd_dict: Dictionary with commands info
:cvar device_capabilities: List with the unsolicited notifications that
this device supports
:cvar auth_klass: Class that will handle the authentication for this device
:cvar netr_klass: Class that will handle the network registration for this
device
"""
from core.exported import WCDMAExporter
wrapper_klass = WCDMAWrapper
exporter_klass = WCDMAExporter
async_regexp = None
allowed_dict = {}
band_dict = {}
conn_dict = {}
cmd_dict = get_cmd_dict_copy()
device_capabilities = []
signal_translations = {}
auth_klass = AuthStateMachine
simp_klass = SimpleStateMachine
netr_klass = NetworkRegistrationStateMachine
def build_band_dict(family_dict, supported_list):
"""Returns a new dict with just the supported bands of the family"""
band_dict = {}
for band in supported_list:
band_dict[band] = family_dict[band]
return band_dict
def check_auth_state(plugin):
codes = {
'READY': '',
'SIM PIN': 'sim-pin',
'SIM PIN2': 'sim-pin2',
'SIM PUK': 'sim-puk',
'SIM PUK2': 'sim-puk2',
'PH-NETSUB PIN': 'ph-netsub-pin',
'PH-NETSUB PUK': 'ph-netsub-puk',
'PH-SIM PIN': 'ph-sim-pin',
'PH-NET PUK': 'ph-net-puk',
'PH-NET PIN': 'ph-net-pin',
'PH-SP PIN': 'ph-sp-pin',
'PH-SP PUK': 'ph-sp-puk',
'PH-CORP PIN': 'ph-corp-pin',
'PH-CORP PUK': 'ph-corp-puk',
'PH-FSIM PIN': 'ph-fsim-pin',
'PH-FSIM PUK': 'ph-fsim-puk',
}
def do_check(port):
with closing(serial.Serial(port.path, timeout=.5)) as ser:
# some devices need to be enabled before pin can be checked
if plugin.quirks.get('needs_enable_before_pin_check', False):
ser.write('AT+CFUN=1\r\n')
lines = ser.readlines()
for i in range(15): # some devices/SIMs are slow to be available
ser.flushOutput()
ser.flushInput()
ser.write('AT+CPIN?\r\n')
lines = ser.readlines()
for line in lines:
line = line.replace('\r', '').replace('\n', '')
m = re.search('\+CPIN:\s*(?P\w+[\w -]*\w+)', line)
if m is not None:
code = m.group('code')
if code in codes:
plugin.set_property(MDM_INTFACE, 'UnlockRequired',
codes[code])
plugin.set_authtime(0)
return plugin
log.msg("check_auth_state: +CPIN? returned '%s'" % lines)
sleep(1) # can do this as we are in a separate thread
log.msg("check_auth_state: +CPIN? no match lines = %s" % str(lines))
return plugin
port = plugin.ports.get_application_port()
return deferToThread(do_check, port)
def raw_identify_device(port):
"""Returns the model of the device present at `port`"""
BAD_REPLIES = ['AT+CGMM', 'OK', '']
# as the readlines method blocks, this is executed in a parallel thread
# with deferToThread
with closing(serial.Serial(port, timeout=.5)) as ser:
ser.write('ATZ E0 V1 X4 &C1\r\n')
ser.readlines()
ser.flushOutput()
ser.flushInput()
ser.write('AT+CGMM\r\n')
lines = ser.readlines()
# clean up \r\n as pairs or singles and avoid unsolicited notifications
resp = [r for r in [l.strip('\r\n') for l in lines
if not l.startswith(('^', '_'))] if r not in BAD_REPLIES]
if resp:
log.msg("AT+CGMM response: %s" % resp)
return resp[0]
raise ValueError("Reply from modem %s was meaningless: %s"
% (port, lines))
def identify_device(plugin):
"""Returns a :class:`~core.plugin.DevicePlugin` out of `plugin`"""
if not plugin.mapping:
# only identify devices that require it
return check_auth_state(plugin)
def identify_device_cb(model):
# plugin to return
ret = None
if model in plugin.mapping:
ret = plugin.mapping[model]()
elif plugin.__remote_name__ != model:
# so we basically have a device identified by vendor & product id
# but we know nothing of this model
try:
ret = PluginManager.get_plugin_by_remote_name(model)
except ex.UnknownPluginNameError:
plugin.name = model
if ret is not None:
# we found another plugin during the process
ret.patch(plugin)
return check_auth_state(ret)
# return the original plugin, most of the time this should work
return check_auth_state(plugin)
port = plugin.ports.get_application_port()
d = deferToThread(raw_identify_device, port.path)
d.addCallback(identify_device_cb)
return d
def probe_port(port):
"""
Check whether ``port`` exists and works
:rtype: bool
"""
with closing(serial.Serial(port, timeout=.01)) as ser:
try:
ser.write('AT+CGMR\r\n')
# Huawei E620 with driver option registers three serial
# ports and the middle one wont raise any exception while
# opening it even thou its a dummy port.
return ser.readlines() != []
except (serial.SerialException, OSError), e:
log.err(e, "Error identifying device in port %s" % port)
return False
def probe_ports(ports):
"""
Obtains the data and control ports out of ``ports``
:rtype: tuple
"""
dport = cport = None
for port in ports:
if probe_port(port):
if dport is None:
# data port tends to the be the first one
dport = port
elif cport is None:
# control port the next working one
cport = port
if dport and cport:
break
return dport, cport
wader-0.5.13/core/hardware/ericsson.py 0000664 0000000 0000000 00000063622 12576466102 0017650 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2009-2011 Vodafone España, S.A.
# Author: Andrew Bird
#
# 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.
"""Common stuff for all Ericsson's cards"""
import re
from twisted.internet import defer, reactor
from twisted.internet.task import deferLater
from twisted.python import log
import wader.common.aterrors as E
from wader.common import consts
from wader.common.encoding import (pack_ucs2_bytes, from_u, check_if_ucs2,
from_ucs2)
from wader.common.utils import revert_dict
import wader.common.signals as S
from core.command import get_cmd_dict_copy, build_cmd_dict, ATCmd
from core.contact import Contact
from core.hardware.base import WCDMACustomizer
from core.middleware import WCDMAWrapper
from core.plugin import DevicePlugin
from core.sim import SIMBaseClass
CREG_MAX_RETRIES = 6
CREG_RETRY_TIMEOUT = 4
HSO_MAX_RETRIES = 10
HSO_RETRY_TIMEOUT = 1
ERICSSON_BAND_DICT = {
consts.MM_NETWORK_BAND_UNKNOWN: None,
consts.MM_NETWORK_BAND_ANY: None,
}
ERICSSON_ALLOWED_DICT = {
consts.MM_ALLOWED_MODE_ANY: 1,
consts.MM_ALLOWED_MODE_2G_ONLY: 5,
consts.MM_ALLOWED_MODE_3G_ONLY: 6,
}
ERICSSON_CONN_DICT = {
consts.MM_NETWORK_MODE_ANY: 1,
consts.MM_NETWORK_MODE_2G_ONLY: 5,
consts.MM_NETWORK_MODE_3G_ONLY: 6,
}
ERINFO_2G_GPRS, ERINFO_2G_EGPRS = 1, 2
ERINFO_3G_UMTS, ERINFO_3G_HSDPA = 1, 2
ENAP_DISCONNECTED, ENAP_CONNECTED, ENAP_CONNECTING = 0, 1, 2
ERICSSON_CMD_DICT = get_cmd_dict_copy()
# +CGDCONT: (1-10),("00490050"),,,(0-1),(0-1)
ERICSSON_CMD_DICT['get_apn_range'] = build_cmd_dict(re.compile(r"""
\r\n
\+CGDCONT:\s*\((?P\d+)-(?P\d+)\),
\((?P(?:"IP")|(?:"00490050"))\)(?P.*)
""", re.VERBOSE))
ERICSSON_CMD_DICT['get_card_model'] = build_cmd_dict('\s*(?P\S*)\r\n')
# +CIND: 5,5,0,0,1,0,1,0,1,1,0,0
ERICSSON_CMD_DICT['get_signal_quality'] = build_cmd_dict(
'\s*\+CIND:\s+[0-9]*,(?P[0-9]*),.*')
ERICSSON_CMD_DICT['get_network_info'] = build_cmd_dict(re.compile(r"""
\r\n
\+COPS:\s+
(
(?P\d) |
\d,\d, # or followed by num,num,str,num
"(?P[^"]*)",
(?P\d)
) # end of group
\s*\r\n
""", re.VERBOSE))
# +CPBR: (1-200),80,14,20,80,128
ERICSSON_CMD_DICT['get_phonebook_size'] = build_cmd_dict(re.compile(r"""
\r\n
\+CPBR:\s
\(\d\-(?P\d+)\)
(?P,.*)?
\r\n
""", re.VERBOSE))
# *ENAP: 1
# *ENAP:2,""
ERICSSON_CMD_DICT['get_enap'] = build_cmd_dict('\*ENAP:\s*(?P\d+)')
# *E2IPCFG: (,)(?:,(,)){0,3}
# D5540 (f3607gw)
# *E2IPCFG: (1,"10.49.116.194")(2,"10.49.116.195")
# (3,"10.203.65.70")(3,"88.82.13.61")
# *E2IPCFG: (1,"00310030002E00340039002E003100310036002E003100390034")
# (2,"00310030002E00340039002E003100310036002E003100390035")
# (3,"00310030002E003200300033002E00360035002E00370030")
# (3,"00380038002E00380032002E00310033002E00360031")
ERICSSON_CMD_DICT['get_ip4_config'] = build_cmd_dict(
re.compile(r"""\*E2IPCFG:\s*
(?:\((?P\d+),
"(?P(?:\d+\.\d+\.\d+\.\d+)|(?:[\dA-Fa-f]+))"\))
(?:\((?P\d+),
"(?P(?:\d+\.\d+\.\d+\.\d+)|(?:[\dA-Fa-f]+))"\))?
(?:\((?P\d+),
"(?P(?:\d+\.\d+\.\d+\.\d+)|(?:[\dA-Fa-f]+))"\))?
(?:\((?P\d+),
"(?P(?:\d+\.\d+\.\d+\.\d+)|(?:[\dA-Fa-f]+))"\))?
""", re.VERBOSE))
# Map to SimBusy for early contact accesses
E.ERROR_DICT['+CME ERROR: 264'] = E.SimBusy
def ericsson_new_indication(args, device):
indication = args.split(',')
try:
itype = int(indication[0])
value = int(indication[1])
except (ValueError, TypeError, IndexError):
return None
if itype is 2: # Signal quality
strength = value * 20
device.sconn.updatecache(strength, 'signal')
device.sconn.emit_rssi(strength)
elif itype is 5: # Service indicator
pass
elif itype is 7: # Message Received (SMS)
pass
elif itype is 8: # Call in progress indicator
pass
elif itype is 9: # roam indicator
pass
elif itype is 10: # SMS memory full
pass
else:
log.msg('Unknown indication of "+CIEV: %s"' % args)
return None
class EricssonSIMClass(SIMBaseClass):
"""Ericsson SIM Class"""
def __init__(self, sconn):
super(EricssonSIMClass, self).__init__(sconn)
def initialize(self, set_encoding=True):
self.sconn.reset_settings()
self.sconn.disable_echo()
# So that phonebook size is returned
self.sconn.send_at('AT+CPBS="SM"')
def init_callback(size):
# setup SIM storage defaults
d = self.sconn.send_at('AT+CPMS="SM","SM","SM"')
d.addCallback(lambda _: self.sconn.send_at('AT+CMER=3,0,0,1'))
d.addCallback(lambda _: size)
return d
d = super(EricssonSIMClass, self).initialize(set_encoding=set_encoding)
d.addCallback(init_callback)
return d
class EricssonWrapper(WCDMAWrapper):
"""Wrapper for all Ericsson cards"""
def __init__(self, device):
super(EricssonWrapper, self).__init__(device)
def add_contact(self, contact):
name = from_u(contact.name)
number = from_u(contact.number)
if 'UCS2' in self.device.sim.charset:
name = pack_ucs2_bytes(name)
number = pack_ucs2_bytes(number)
# common arguments for both operations (name and number)
args = [name, number]
if contact.index:
# contact.index is set, user probably wants to overwrite an
# existing contact
args.append(contact.index)
d = super(WCDMAWrapper, self).add_contact(*args)
d.addCallback(lambda _: contact.index)
return d
# contact.index is not set, this means that we need to obtain the
# first slot free on the phonebook and then add the contact
def get_next_id_cb(index):
args.append(index)
d2 = super(WCDMAWrapper, self).add_contact(*args)
# now we just fake add_contact's response and we return the index
d2.addCallback(lambda _: index)
return d2
d = self._get_next_contact_id()
d.addCallback(get_next_id_cb)
return d
def change_pin(self, oldpin, newpin):
where = "SC"
if 'UCS2' in self.device.sim.charset:
where = pack_ucs2_bytes("SC")
oldpin = pack_ucs2_bytes(oldpin)
newpin = pack_ucs2_bytes(newpin)
atstr = 'AT+CPWD="%s","%s","%s"' % (where, oldpin, newpin)
d = self.queue_at_cmd(ATCmd(atstr, name='change_pin'))
d.addCallback(lambda result: result[0].group('resp'))
return d
def enable_pin(self, pin, enable):
where = "SC"
if 'UCS2' in self.device.sim.charset:
where = pack_ucs2_bytes("SC")
pin = pack_ucs2_bytes(pin)
at_str = 'AT+CLCK="%s",%d,"%s"' % (where, int(enable), pin)
d = self.queue_at_cmd(ATCmd(at_str, name='enable_pin'))
d.addCallback(lambda result: result[0].group('resp'))
return d
def enable_radio(self, enable):
d = self.get_radio_status()
def get_radio_status_cb(status):
if status in [0, 4] and enable:
return self.send_at('AT+CFUN=1')
elif status in [1, 5, 6] and not enable:
return self.send_at('AT+CFUN=4')
d.addCallback(get_radio_status_cb)
return d
def get_apns(self):
if self.device.sim.charset != 'UCS2':
return super(EricssonWrapper, self).get_apns()
d = super(WCDMAWrapper, self).get_apns()
d.addCallback(lambda resp:
[(int(r.group('index')), from_ucs2(r.group('apn'))) for r in resp])
return d
def get_band(self):
return defer.succeed(consts.MM_NETWORK_BAND_ANY)
def get_charset(self):
d = super(EricssonWrapper, self).get_charset()
d.addCallback(from_ucs2)
return d
def get_charsets(self):
d = super(EricssonWrapper, self).get_charsets()
def get_charsets_cb(charsets):
ret = []
for charset in charsets:
if check_if_ucs2(charset):
charset = from_ucs2(charset)
ret.append(charset)
return ret
d.addCallback(get_charsets_cb)
return d
def get_network_mode(self):
ERICSSON_CONN_DICT_REV = revert_dict(ERICSSON_CONN_DICT)
def get_network_mode_cb(mode):
if mode in ERICSSON_CONN_DICT_REV:
return ERICSSON_CONN_DICT_REV[mode]
raise E.General("unknown network mode: %d" % mode)
d = self.get_radio_status()
d.addCallback(get_network_mode_cb)
return d
def get_netreg_status(self):
deferred = defer.Deferred()
self.state_dict['creg_retries'] = 0
def get_it(auxdef=None):
def get_netreg_status_cb((mode, status)):
self.state_dict['creg_retries'] += 1
if self.state_dict['creg_retries'] > CREG_MAX_RETRIES:
return auxdef.callback((mode, status))
if status == 4:
reactor.callLater(CREG_RETRY_TIMEOUT, get_it, auxdef)
else:
return auxdef.callback((mode, status))
def get_netreg_status_eb(failure):
return auxdef.errback(failure)
d = super(EricssonWrapper, self).get_netreg_status()
d.addCallbacks(get_netreg_status_cb, get_netreg_status_eb)
return auxdef
return get_it(deferred)
def get_signal_quality(self):
# On Ericsson, AT+CSQ only returns valid data in GPRS mode
# So we need to override and provide an alternative. +CIND
# returns an indication between 0-5 so let's just multiply
# that by 20 to get a RSSI between 0-100%
def get_signal_quality_cb(response):
try:
return int(response[0].group('sig')) * 20
except IndexError:
# Sometimes it won't reply to a +CIND? command
# we'll assume that we don't have RSSI right now
return 0
cmd = ATCmd('AT+CIND?', name='get_signal_quality')
d = self.queue_at_cmd(cmd)
d.addCallback(get_signal_quality_cb)
return d
def get_pin_status(self):
def ericsson_get_pin_status(facility):
"""
Checks whether the pin is enabled or disabled
"""
cmd = ATCmd('AT+CLCK="%s",2' % facility, name='get_pin_status')
return self.queue_at_cmd(cmd)
def pinreq_errback(failure):
failure.trap(E.SimPinRequired)
return 1
def aterror_eb(failure):
failure.trap(E.General)
# return the failure or wont work
return failure
facility = 'SC'
if self.device.sim.charset == 'UCS2':
facility = pack_ucs2_bytes('SC')
d = ericsson_get_pin_status(facility) # call the local one
d.addCallback(lambda response: int(response[0].group('status')))
d.addErrback(pinreq_errback)
d.addErrback(aterror_eb)
return d
def _regexp_to_contact(self, match):
"""
Returns a :class:`core.contact.Contact` out of ``match``
:type match: ``re.MatchObject``
"""
name = match.group('name')
number = match.group('number')
if self.device.sim.charset == 'UCS2':
name = from_ucs2(name)
number = from_ucs2(number)
index = int(match.group('id'))
return Contact(name, number, index=index)
def send_pin(self, pin):
"""
Sends ``pin`` to authenticate
We need to encode the PIN in the correct way, but also wait a little,
as Ericsson devices return straight away on unlock but an immediate
call to +CPIN? will still show the device as locked.
"""
from core.startup import attach_to_serial_port
d = attach_to_serial_port(self.device)
def _get_charset(_):
if hasattr(self.device, 'sim') and \
hasattr(self.device.sim, 'charset'):
return defer.succeed(self.device.sim.charset)
else:
return self.get_charset()
d.addCallback(_get_charset)
def _send_pin(charset, pin):
if 'UCS2' in charset:
pin = pack_ucs2_bytes(pin)
return super(EricssonWrapper, self).send_pin(pin)
d.addCallback(_send_pin, pin)
d.addCallback(lambda x: deferLater(reactor, 1, lambda y: y, x))
return d
def set_apn(self, apn):
if self.device.sim.charset != 'UCS2':
# no need to encode params in UCS2
return super(EricssonWrapper, self).set_apn(apn)
def process_apns(apns, the_apn):
for _index, _apn in apns:
if _apn == the_apn:
self.state_dict['conn_id'] = _index
return defer.succeed('OK')
try:
conn_id = max([idx for idx, _ in apns]) + 1
if conn_id < self.apn_range[0] or conn_id > self.apn_range[1]:
raise ValueError
except (ValueError, TypeError):
conn_id = self.apn_range[0]
self.state_dict['conn_id'] = conn_id
args = tuple([conn_id] + map(pack_ucs2_bytes, ["IP", the_apn]))
cmd = ATCmd('AT+CGDCONT=%d,"%s","%s"' % args, name='set_apn')
d = self.queue_at_cmd(cmd)
d.addCallback(lambda response: response[0].group('resp'))
return d
d = self.get_apns()
d.addCallback(process_apns, apn)
d.addErrback(log.err)
return d
def set_band(self, band):
if band == consts.MM_NETWORK_BAND_ANY:
return defer.succeed('OK')
raise KeyError("Unsupported band %d" % band)
def set_charset(self, charset):
# The oddity here is that the set command needs to have its charset
# value encoded in the current character set
if self.device.sim.charset == 'UCS2':
charset = pack_ucs2_bytes(charset)
d = super(EricssonWrapper, self).set_charset(charset)
return d
def set_allowed_mode(self, mode):
if mode not in self.custom.allowed_dict:
raise KeyError("Mode %d not found" % mode)
def set_allowed_mode_cb(ign=None):
self.device.set_property(consts.NET_INTFACE, "AllowedMode", mode)
return ign
return self.send_at("AT+CFUN=%d" % self.custom.allowed_dict[mode],
callback=set_allowed_mode_cb)
def set_network_mode(self, mode):
if mode not in self.custom.conn_dict:
raise KeyError("Mode %d not found" % mode)
return self.send_at("AT+CFUN=%d" % self.custom.conn_dict[mode])
def reset_settings(self):
cmd = ATCmd('AT&F', name='reset_settings')
return self.queue_at_cmd(cmd)
def sim_access_restricted(self, command, fileid=None, p1=None,
p2=None, p3=None, data=None, pathid=None):
d = super(EricssonWrapper, self).sim_access_restricted(
command, fileid, p1, p2, p3, data, pathid)
def cb(response):
sw1, sw2, data = response
if self.device.sim.charset == 'UCS2' and data is not None:
data = from_ucs2(data)
return (sw1, sw2, data)
d.addCallback(cb)
return d
def get_ip4_config(self):
"""
Returns the ip4 config on a NDIS device
Wrapper around _get_ip4_config that provides some error control
"""
ip_method = self.device.get_property(consts.MDM_INTFACE, 'IpMethod')
if ip_method != consts.MM_IP_METHOD_STATIC:
msg = "Cannot get IP4 config from a non static ip method"
raise E.OperationNotSupported(msg)
self.state_dict['num_of_retries'] = 0
def check_if_connected(deferred):
def inform_caller():
if self.device.status > consts.MM_MODEM_STATE_REGISTERED:
self.device.set_status(consts.MM_MODEM_STATE_REGISTERED)
deferred.errback(RuntimeError('Connection attempt failed'))
def get_ip4_eb(failure):
failure.trap(E.General, E.Unknown)
if self.state_dict.get('should_stop'):
self.state_dict.pop('should_stop')
return
self.state_dict['num_of_retries'] += 1
if self.state_dict['num_of_retries'] > HSO_MAX_RETRIES:
inform_caller()
return failure
reactor.callLater(HSO_RETRY_TIMEOUT,
check_if_connected, deferred)
# We received an unsolicited notification that we failed
if self.device.connection_attempt_failed:
inform_caller()
return
d = self._get_ip4_config()
d.addCallback(deferred.callback)
d.addErrback(get_ip4_eb)
return deferred
auxdef = defer.Deferred()
return check_if_connected(auxdef)
def _get_ip4_config(self):
"""Returns the ip4 config on a later Ericsson NDIS device"""
cmd = ATCmd('AT*E2IPCFG?', name='get_ip4_config')
d = self.queue_at_cmd(cmd)
def _get_ip4_config_cb(resp):
if not resp:
raise E.General()
ip = None
dns = []
l = list(resp[0].groups())
while len(l) > 1:
t = l.pop(0)
a = l.pop(0)
if t is None or a is None:
continue
if not '.' in a:
a = from_ucs2(a)
if t == '1':
ip = a
elif t == '3':
dns.append(a)
if ip is None:
raise E.General()
self.device.set_status(consts.MM_MODEM_STATE_CONNECTED)
# XXX: We didn't get any, but we have to return something so use
# the bogus values that 'pppd' returns sometimes.
if not len(dns):
dns = ['10.11.12.13', '10.11.12.14']
# XXX: caller expects exactly three!
while len(dns) < 3:
dns.append(dns[0])
return [ip] + dns[:3]
d.addCallback(_get_ip4_config_cb)
return d
def hso_authenticate(self, user, passwd, auth):
conn_id = self.state_dict.get('conn_id')
if conn_id is None:
raise E.CallIndexError("conn_id is None")
# Bitfield '00111': MSCHAPv2, MSCHAP, CHAP, PAP, None
if auth is None:
iauth = consts.MM_ALLOWED_AUTH_UNKNOWN
else:
iauth = auth & 0x1f # only the lowest 5 bits
if iauth == consts.MM_ALLOWED_AUTH_UNKNOWN:
iauth = consts.MM_ALLOWED_AUTH_PAP # the old default
# convert to string
try:
_auth = bin(iauth)[2:].zfill(5) # Python 2.6+
except:
_auth = ''
for i in range(5 - 1, -1, -1):
_auth += "%d" % ((iauth >> i) & 1)
if self.device.sim.charset == 'UCS2':
args = (conn_id, pack_ucs2_bytes(user),
pack_ucs2_bytes(passwd), _auth)
else:
args = (conn_id, user, passwd, _auth)
return self.send_at('AT*EIAAUW=%d,1,"%s","%s",%s' % args)
def hso_connect(self):
conn_id = self.state_dict.get('conn_id')
if conn_id is None:
raise E.CallIndexError("conn_id is None")
if self.device.status == consts.MM_MODEM_STATE_CONNECTED:
# this cannot happen
raise E.Connected("we are already connected")
if self.device.status == consts.MM_MODEM_STATE_CONNECTING:
raise E.SimBusy("we are already connecting")
# AT*E2NAP=1 used to be used to enable unsolicited interface status
# reports but not all devices support it and the existing code didn't
# even handle them either.
self.device.connection_attempt_failed = False
self.device.set_status(consts.MM_MODEM_STATE_CONNECTING)
d = self.send_at('AT*ENAP=1,%d' % conn_id)
ip_method = self.device.get_property(consts.MDM_INTFACE, 'IpMethod')
if ip_method == consts.MM_IP_METHOD_DHCP:
# XXX: it would be nice to be using ipmethod == STATIC here, but
# only later devices are capable of returning the negotiated DNS
# values, so we default to DHCP mode and let the interface be
# configured later by some other process. The most we can do is
# check if the connection is up and report it as connected (that's
# early, but actually no worse than ipmethod == PPP).
def get_enap():
cmd = ATCmd('AT*ENAP?', name='get_enap')
d = self.queue_at_cmd(cmd)
def get_enap_cb(resp):
if not resp:
raise E.General()
status = resp[0].group('status')
if status is None:
raise E.General()
status = int(status)
if status is ENAP_DISCONNECTED:
self.device.connection_attempt_failed = True
elif status is ENAP_CONNECTED:
self.device.set_status(consts.MM_MODEM_STATE_CONNECTED)
else: # ENAP_CONNECTING and spurious values
raise E.General()
return status
d.addCallback(get_enap_cb)
return d
self.state_dict['num_of_retries'] = 0
def check_if_connected(deferred):
def inform_caller():
if self.device.status > consts.MM_MODEM_STATE_REGISTERED:
self.device.set_status(
consts.MM_MODEM_STATE_REGISTERED)
deferred.errback(RuntimeError('Connection attempt failed'))
def get_enap_eb(failure):
failure.trap(E.General, E.OperationNotSupported)
if self.state_dict.get('should_stop'):
self.state_dict.pop('should_stop')
return
self.state_dict['num_of_retries'] += 1
if self.state_dict['num_of_retries'] > HSO_MAX_RETRIES:
inform_caller()
return failure
reactor.callLater(HSO_RETRY_TIMEOUT,
check_if_connected, deferred)
# We received an unsolicited notification that we failed
if self.device.connection_attempt_failed:
inform_caller()
return
d = get_enap()
d.addCallback(deferred.callback)
d.addErrback(get_enap_eb)
return deferred
auxdef = defer.Deferred()
d.addCallback(lambda x: check_if_connected(auxdef))
return d
def hso_disconnect(self):
self.device.set_status(consts.MM_MODEM_STATE_DISCONNECTING)
def disconnect_cb(ignored):
# XXX: perhaps we should check the registration status here
if self.device.status > consts.MM_MODEM_STATE_REGISTERED:
self.device.set_status(consts.MM_MODEM_STATE_REGISTERED)
d = self.send_at('AT*ENAP=0')
d.addCallback(disconnect_cb)
return d
class EricssonF3607gwWrapper(EricssonWrapper):
"""Wrapper for F3307 / F3607gw Ericsson cards"""
def find_contacts(self, pattern):
d = self.list_contacts()
d.addCallback(lambda contacts:
[c for c in contacts
if c.name.lower().startswith(pattern.lower())])
return d
class EricssonCustomizer(WCDMACustomizer):
"""Base customizer class for Ericsson cards"""
# Multiline so we catch and remove the ESTKSMENU
async_regexp = re.compile("\r\n(?P[*+][A-Z]{3,}):(?P.*)\r\n",
re.MULTILINE)
allowed_dict = ERICSSON_ALLOWED_DICT
band_dict = ERICSSON_BAND_DICT
cmd_dict = ERICSSON_CMD_DICT
conn_dict = ERICSSON_CONN_DICT
device_capabilities = [S.SIG_SMS_NOTIFY_ONLINE,
S.SIG_RSSI]
signal_translations = {
'+CIEV': (None, ericsson_new_indication),
'*ESTKDISP': (None, None),
'*ESTKSMENU': (None, None),
'*EMWI': (None, None),
'*E2NAP': (None, None),
'+PACSP0': (None, None)}
wrapper_klass = EricssonWrapper
class EricssonF3607gwCustomizer(EricssonCustomizer):
wrapper_klass = EricssonF3607gwWrapper
class EricssonDevicePlugin(DevicePlugin):
"""DevicePlugin for Ericsson"""
sim_klass = EricssonSIMClass
custom = EricssonCustomizer()
def __init__(self):
super(EricssonDevicePlugin, self).__init__()
wader-0.5.13/core/hardware/huawei.py 0000664 0000000 0000000 00000057313 12576466102 0017305 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2006-2009 Vodafone España, S.A.
# Copyright (C) 2008-2009 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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.
"""Common stuff for all Huawei cards"""
import re
from twisted.python import log
from twisted.internet import defer
from messaging.sms import is_gsm_text
from messaging.utils import encode_str, unpack_msg
from core.middleware import WCDMAWrapper
from core.command import get_cmd_dict_copy, build_cmd_dict, ATCmd
from core.contact import Contact
from wader.common import consts
from wader.common.encoding import (from_u, pack_ucs2_bytes,
unpack_ucs2_bytes_in_ts31101_80,
unpack_ucs2_bytes_in_ts31101_81,
unpack_ucs2_bytes_in_ts31101_82)
from core.hardware.base import WCDMACustomizer
from core.plugin import DevicePlugin
from core.sim import SIMBaseClass
from wader.common.utils import rssi_to_percentage
import wader.common.signals as S
import wader.common.aterrors as E
NETINFO_REGEXP = re.compile('[^a-zA-Z0-9.\-\s]*')
BADOPER_REGEXP = re.compile('FFF*')
HUAWEI_ALLOWED_DICT = {
consts.MM_ALLOWED_MODE_ANY: (2, 0),
consts.MM_ALLOWED_MODE_2G_ONLY: (13, 1),
consts.MM_ALLOWED_MODE_3G_ONLY: (14, 2),
consts.MM_ALLOWED_MODE_2G_PREFERRED: (2, 1),
consts.MM_ALLOWED_MODE_3G_PREFERRED: (2, 2),
}
HUAWEI_CONN_DICT = {
consts.MM_NETWORK_MODE_ANY: (2, 0),
consts.MM_NETWORK_MODE_2G_ONLY: (13, 1),
consts.MM_NETWORK_MODE_3G_ONLY: (14, 2),
consts.MM_NETWORK_MODE_2G_PREFERRED: (2, 1),
consts.MM_NETWORK_MODE_3G_PREFERRED: (2, 2),
}
HUAWEI_BAND_DICT = {
consts.MM_NETWORK_BAND_ANY: 0x3FFFFFFF,
consts.MM_NETWORK_BAND_DCS: 0x00000080,
consts.MM_NETWORK_BAND_EGSM: 0x00000100,
consts.MM_NETWORK_BAND_PCS: 0x00200000,
consts.MM_NETWORK_BAND_G850: 0x00080000,
consts.MM_NETWORK_BAND_U2100: 0x00400000,
consts.MM_NETWORK_BAND_U1900: 0x00800000,
consts.MM_NETWORK_BAND_U850: 0x04000000,
# XXX: check this works with bit operations and all cards before enabling
# consts.MM_NETWORK_BAND_U900: 0x0002000000000000,
}
# Hopefully this function will be exported from python-messaging in the future
def compress_7_to_8_bit(txt):
txt += '\x00'
msgl = int(len(txt) * 7 / 8)
op = [-1] * msgl
c = shift = 0
for n in range(msgl):
if shift == 6:
c += 1
shift = n % 7
lb = ord(txt[c]) >> shift
hb = (ord(txt[c + 1]) << (7 - shift) & 255)
op[n] = lb + hb
c += 1
return ''.join(map(chr, op))
def huawei_new_conn_mode(args, device):
"""Translates `args` to Wader's internal representation"""
mode_args_dict = {
'3,0': consts.MM_NETWORK_MODE_GPRS,
'3,1': consts.MM_NETWORK_MODE_GPRS,
'3,2': consts.MM_NETWORK_MODE_GPRS,
'3,3': consts.MM_NETWORK_MODE_GPRS,
'5,4': consts.MM_NETWORK_MODE_UMTS,
'5,5': consts.MM_NETWORK_MODE_HSDPA,
'5,6': consts.MM_NETWORK_MODE_HSUPA,
'5,7': consts.MM_NETWORK_MODE_HSPA,
'5,9': consts.MM_NETWORK_MODE_HSPA, # doc says HSPA+
}
try:
return mode_args_dict[args]
except KeyError:
return consts.MM_NETWORK_MODE_UNKNOWN
def huawei_new_rssi(args, device):
try:
strength = rssi_to_percentage(int(args))
except (ValueError, TypeError):
return None
device.sconn.updatecache(strength, 'signal')
device.sconn.emit_rssi(strength)
return None
HUAWEI_CMD_DICT = get_cmd_dict_copy()
HUAWEI_CMD_DICT['get_syscfg'] = build_cmd_dict(re.compile(r"""
\r\n
\^SYSCFG:
(?P\d+),
(?P\d+),
(?P[0-9A-F]*),
(?P\d),
(?P\d)
\r\n
""", re.VERBOSE))
HUAWEI_CMD_DICT['get_radio_status'] = build_cmd_dict(
end=re.compile('\r\n\+CFUN:\s?\d\r\n'),
extract=re.compile('\r\n\+CFUN:\s?(?P\d)\r\n'))
HUAWEI_CMD_DICT['check_pin'] = build_cmd_dict(re.compile(r"""
\r\n
\+CPIN:\s*
(?P
READY |
SIM\sPIN2? |
SIM\sPUK2?
)
\r\n
""", re.VERBOSE))
#+CPBR: (1-200),80,14,0,0,0
HUAWEI_CMD_DICT['get_phonebook_size'] = build_cmd_dict(re.compile(r"""
\r\n
\+CPBR:\s
\(\d+-(?P\d+)\).*
\r\n
""", re.VERBOSE))
HUAWEI_CMD_DICT['get_contact'] = build_cmd_dict(re.compile(r"""
\r\n
\^CPBR:\s(?P\d+),
"(?P\+?\d+)",
(?P\d+),
"(?P.*)",
(?P\d+)
\r\n
""", re.VERBOSE))
HUAWEI_CMD_DICT['list_contacts'] = build_cmd_dict(
end=re.compile('(\r\n)?\r\n(OK)\r\n'),
extract=re.compile(r"""
\r\n
\^CPBR:\s(?P\d+),
"(?P\+?\d+)",
(?P\d+),
"(?P.*)",
(?P\d+)
""", re.VERBOSE))
HUAWEI_CMD_DICT['list_sms'] = build_cmd_dict(re.compile(r"""
\r\n
\+CMGL:\s*
(?P\d+),
(?P\d),["\w\s]*,\d+
\r\n(?P\w+)""", re.VERBOSE))
# +CMGR: 0,"416E64726577",23^M
# 0791447758100650040C914497716247010000215032511142400444F3190D^M
# ^M
# OK^M
HUAWEI_CMD_DICT['get_sms'] = build_cmd_dict(re.compile(r"""
\r\n
\+CMGR:\s
(?P\d),["\w\s]*,
\d+\r\n
(?P\w+)
\r\n""", re.VERBOSE))
class HuaweiWCDMAWrapper(WCDMAWrapper):
"""Wrapper for all Huawei cards"""
def _get_syscfg(self):
def parse_syscfg(resp):
ret = {}
mode_a = int(resp[0].group('modea'))
mode_b = int(resp[0].group('modeb'))
band = int(resp[0].group('theband'), 16)
# keep original values
ret['roam'] = int(resp[0].group('roam'))
ret['srv'] = int(resp[0].group('srv'))
ret['modea'] = mode_a
ret['modeb'] = mode_b
ret['theband'] = band
ret['band'] = 0 # populated later on
# network mode
if mode_a == 2 and mode_b == 1:
ret['mode'] = consts.MM_NETWORK_MODE_2G_PREFERRED
elif mode_a == 2 and mode_b == 2:
ret['mode'] = consts.MM_NETWORK_MODE_3G_PREFERRED
elif mode_a == 13:
ret['mode'] = consts.MM_NETWORK_MODE_2G_ONLY
elif mode_a == 14:
ret['mode'] = consts.MM_NETWORK_MODE_3G_ONLY
elif mode_a == 2 and mode_b == 0:
ret['mode'] = consts.MM_NETWORK_MODE_ANY
# band
if band == 0x3FFFFFFF:
ret['band'] = consts.MM_NETWORK_BAND_ANY
# this band is not combinable by firmware spec
return ret
for key, value in self.custom.band_dict.items():
if key == consts.MM_NETWORK_BAND_ANY:
# ANY can't be combined
continue
if value & band:
ret['band'] |= key
return ret
d = self.send_at('AT^SYSCFG?', name='get_syscfg',
callback=parse_syscfg)
return d
def add_contact(self, contact):
"""
Adds ``contact`` to the SIM and returns the index where was stored
:rtype: int
"""
name = from_u(contact.name)
# common arguments for both operations (name and number)
args = [name, from_u(contact.number)]
if contact.index:
# contact.index is set, user probably wants to overwrite an
# existing contact
args.append(contact.index)
d = self._add_contact(*args)
d.addCallback(lambda _: contact.index)
return d
# contact.index is not set, this means that we need to obtain the
# first slot free on the phonebook and then add the contact
def get_next_id_cb(index):
args.append(index)
d2 = self._add_contact(*args)
# now we just fake add_contact's response and we return the index
d2.addCallback(lambda _: index)
return d2
d = self._get_next_contact_id()
d.addCallback(get_next_id_cb)
return d
def _add_contact(self, name, number, index):
"""
Adds a contact to the SIM card
"""
raw = 0
try: # are all ascii chars
name.encode('ascii')
except: # write in TS31.101 type 80 raw format
name = '80%sFF' % pack_ucs2_bytes(name)
raw = 1
category = 145 if number.startswith('+') else 129
args = (index, number, category, name, raw)
cmd = ATCmd('AT^CPBW=%d,"%s",%d,"%s",%d' % args, name='add_contact')
return self.queue_at_cmd(cmd)
def find_contacts(self, pattern):
d = self.list_contacts()
d.addCallback(lambda contacts:
[c for c in contacts
if c.name.lower().startswith(pattern.lower())])
return d
def get_band(self):
"""Returns the current used band"""
d = self._get_syscfg()
d.addCallback(lambda ret: ret['band'])
d.addErrback(log.err)
return d
def list_contacts(self):
"""
Returns all the contacts in the SIM
:rtype: list
"""
exceptions = (E.NotFound, E.InvalidIndex, E.General)
def not_found_eb(failure):
failure.trap(*exceptions)
return []
def get_them(ignored=None):
cmd = ATCmd('AT^CPBR=1,%d' % self.device.sim.size,
name='list_contacts', nolog=exceptions)
d = self.queue_at_cmd(cmd)
d.addCallback(
lambda matches: map(self._regexp_to_contact, matches))
d.addErrback(not_found_eb)
return d
if self.device.sim.size:
return get_them()
else:
d = self._get_next_contact_id()
d.addCallback(get_them)
return d
def _regexp_to_contact(self, match):
"""
I process a contact match and return a `Contact` object out of it
"""
if int(match.group('raw')) == 0:
# some buggy firmware appends this
name = match.group('name').rstrip('\xff')
else:
encoding = match.group('name')[:2]
hexbytes = match.group('name')[2:]
if encoding != '82':
# E220 pads '534E4E3AFFFFFFFFFFFFFFFFFF'
# K2540 pads '534E4E3AFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'
# 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'
significant = hexbytes.find('FF')
if significant != -1:
hexbytes = hexbytes[:significant + 2]
if encoding == '80': # example '80058300440586FF'
name = unpack_ucs2_bytes_in_ts31101_80(hexbytes)
elif encoding == '81': # example '810602A46563746F72FF'
name = unpack_ucs2_bytes_in_ts31101_81(hexbytes)
elif encoding == '82': # example '820505302D82D32D31'
name = unpack_ucs2_bytes_in_ts31101_82(hexbytes)
else:
name = "Unsupported encoding"
number = match.group('number')
index = int(match.group('id'))
return Contact(name, number, index=index)
def get_contact(self, index):
cmd = ATCmd('AT^CPBR=%d' % index, name='get_contact')
d = self.queue_at_cmd(cmd)
d.addCallback(lambda match: self._regexp_to_contact(match[0]))
return d
def get_network_info(self, _type=None):
# Some E220 firmwares will append an extra char to AT+COPS?
# (off-by-one) or reply as 'FFFFFFFFFF+'. The following callback
# will handle this errors.
def process_netinfo_cb(info):
operator, tech = info
m = BADOPER_REGEXP.match(operator)
# sometimes the operator will come as a FFFFFFF+
if m:
return "Unknown Network", tech
# clean extra '@', 'x1a', etc
return NETINFO_REGEXP.sub('', operator), tech
d = super(HuaweiWCDMAWrapper, self).get_network_info(_type)
d.addCallback(process_netinfo_cb)
return d
def get_network_mode(self):
"""Returns the current used network mode"""
d = self._get_syscfg()
d.addCallback(lambda ret: ret['mode'])
d.addErrback(log.err)
return d
def set_band(self, band):
"""Sets the band to ``band``"""
def get_syscfg_cb(info):
mode_a, mode_b = info['modea'], info['modeb']
roam, srv = info['roam'], info['srv']
_band = 0
if band == consts.MM_NETWORK_BAND_ANY:
# ANY cannot be combined by design
_band = 0x3FFFFFFF
else:
# the rest can be combined
for key, value in self.custom.band_dict.items():
if key == consts.MM_NETWORK_BAND_ANY:
continue
if key & band:
_band |= value
if _band == 0:
raise KeyError("Unsupported band %d" % band)
gsm_bands = (consts.MM_NETWORK_BAND_DCS |
consts.MM_NETWORK_BAND_PCS |
consts.MM_NETWORK_BAND_EGSM |
consts.MM_NETWORK_BAND_G850)
umts_bands = (consts.MM_NETWORK_BAND_U800 |
consts.MM_NETWORK_BAND_U850 |
consts.MM_NETWORK_BAND_U900 |
consts.MM_NETWORK_BAND_U1900)
if band & gsm_bands:
mode_a, mode_b = 13, 1
elif band & umts_bands:
mode_a, mode_b = 14, 2
else:
# ANY and the rest
mode_a, mode_b = 2, 0
at_str = 'AT^SYSCFG=%d,%d,%X,%d,%d'
return self.send_at(at_str % (mode_a, mode_b, _band, roam, srv))
d = self._get_syscfg()
d.addCallback(get_syscfg_cb)
return d
def set_allowed_mode(self, mode):
"""Sets the allowed mode to ``mode``"""
def get_syscfg_cb(info):
if mode not in self.custom.allowed_dict:
# NOOP
return "OK"
_mode, acqorder = self.custom.allowed_dict[mode]
band, roam, srv = info['theband'], info['roam'], info['srv']
band = 0x3FFFFFFF
at_str = 'AT^SYSCFG=%d,%d,%X,%d,%d'
def set_allowed_mode_cb(ign=None):
self.device.set_property(consts.NET_INTFACE, "AllowedMode",
mode)
return ign
return self.send_at(at_str % (_mode, acqorder, band, roam, srv),
callback=set_allowed_mode_cb)
d = self._get_syscfg()
d.addCallback(get_syscfg_cb)
d.addErrback(log.err)
return d
def set_network_mode(self, mode):
"""Sets the network mode to ``mode``"""
def get_syscfg_cb(info):
_mode, acqorder = info['modea'], info['modeb']
band, roam, srv = info['theband'], info['roam'], info['srv']
if mode not in self.custom.conn_dict:
# NOOP
return "OK"
_mode, acqorder = self.custom.conn_dict[mode]
band = 0x3FFFFFFF
at_str = 'AT^SYSCFG=%d,%d,%X,%d,%d'
return self.send_at(at_str % (_mode, acqorder, band, roam, srv))
d = self._get_syscfg()
d.addCallback(get_syscfg_cb)
d.addErrback(log.err)
return d
def set_smsc(self, smsc):
"""
Sets the SIM's smsc to `smsc`
We wrap the operation with set_charset('IRA') and set_charset('UCS2')
"""
d = self.set_charset('IRA')
d.addCallback(lambda _: super(HuaweiWCDMAWrapper, self).set_smsc(smsc))
d.addCallback(lambda _: self.set_charset('UCS2'))
return d
def _send_ussd_old_mode(self, ussd):
"""
Sends the USSD command ``ussd`` in Huawei old mode (^USSDMODE=0)
Sends GSM 7bit compressed text
Receives Hex encoded or GSM 7bit compressed text
"""
# AT+CUSD=1,"AA510C061B01",15
# (*#100#)
# +CUSD: 0,"3037373935353033333035",0
# (07795503305)
# +CUSD: 0,"C2303BEC1E97413D90140473C162A0221E9E96E741E430BD0CD452816"
# "2B4574CF692C162301748F876D7E7A069737A9A837A20980B04",15
# +CUSD: 0,"D95399CD7EB340F9771D840EDBCB0AA9CD25CB81C269393DDD2E83143"
# "0D0B43945CD53A0B09BACA0B964305053082287E9619722FBAE83C2F2"
# "32E8ED0635A94E90F6ED2EBB405076393C2F83C8E9301BA48AD162AAD"
# "808647ECB41E4323D9C6697C92071981D768FCB739742287FD7CF683A"
# "88FE06E5DF7590380F6A529D2E",0
def send_request(ussd):
if not is_gsm_text(ussd):
raise ValueError
gsm = ussd.encode("gsm0338")
ussd_enc = encode_str(compress_7_to_8_bit(gsm))
cmd = ATCmd('AT+CUSD=1,"%s",15' % ussd_enc, name='send_ussd')
return self.queue_at_cmd(cmd)
def convert_response(response):
index = response[0].group('index')
if index == '1':
self.device.set_property(
consts.USD_INTFACE, 'State', 'user-response')
else:
self.device.set_property(consts.USD_INTFACE, 'State', 'idle')
resp = response[0].group('resp')
code = response[0].group('code')
if code is not None:
code = int(code)
if ((code & 0x10) == 0x10):
log.err("We don't yet handle ISO 639 encoded USSD"
" - please report")
raise E.MalformedUssdPduError(resp)
if ((code & 0xf4) == 0xf4):
log.err("We don't yet handle 8 bit encoded USSD"
" - please report")
raise E.MalformedUssdPduError(resp)
try:
ret = unpack_msg(resp).decode("gsm0338")
if is_gsm_text(ret):
return ret
except UnicodeError:
pass
try:
return resp.decode('hex')
except TypeError:
raise E.MalformedUssdPduError(resp)
def reset_state(failure):
if self.device.get_property(consts.USD_INTFACE, 'State') != 'idle':
self.device.set_property(consts.USD_INTFACE, 'State', 'idle')
failure.raiseException() # re-raise
d = send_request(str(ussd))
d.addCallback(convert_response)
d.addErrback(reset_state)
return d
def _send_ussd_ucs2_mode(self, ussd, loose_charset_check=False):
"""
Sends the USSD command ``ussd`` in UCS2 mode regardless of the current
setting.
Many Huawei devices that implement Vodafone specified USSD translation
only function correctly in the UCS2 character set, so save the current
charset, flip to UCS2, call the ancestor, restore the charset and then
return the result
"""
def save_set_charset(old):
if old == 'UCS2':
if hasattr(self, 'old_charset'):
del self.old_charset
return defer.succeed('OK')
self.old_charset = old
return self.set_charset('UCS2')
def restore_charset(result):
if hasattr(self, 'old_charset'):
self.set_charset(self.old_charset)
del self.old_charset
return result
def restore_charset_eb(failure):
if hasattr(self, 'old_charset'):
self.set_charset(self.old_charset)
del self.old_charset
failure.raiseException() # re-raise
d = defer.Deferred()
d.addCallback(lambda _: self.get_charset())
d.addCallback(save_set_charset)
d.addErrback(restore_charset_eb)
d.addCallback(lambda _: super(HuaweiWCDMAWrapper, self).send_ussd(ussd,
loose_charset_check=loose_charset_check))
d.addCallback(restore_charset)
d.callback(True)
return d
class HuaweiWCDMACustomizer(WCDMACustomizer):
"""WCDMA Customizer class for Huawei cards"""
wrapper_klass = HuaweiWCDMAWrapper
async_regexp = re.compile(
'\r\n(?P\^[A-Z]{3,9}):\s*(?P.*?)\r\n')
allowed_dict = HUAWEI_ALLOWED_DICT
band_dict = HUAWEI_BAND_DICT
conn_dict = HUAWEI_CONN_DICT
cmd_dict = HUAWEI_CMD_DICT
device_capabilities = [S.SIG_NETWORK_MODE,
S.SIG_SMS_NOTIFY_ONLINE,
S.SIG_RSSI]
signal_translations = {
'^MODE': (S.SIG_NETWORK_MODE, huawei_new_conn_mode),
'^RSSI': (None, huawei_new_rssi),
'^DSFLOWRPT': (None, None),
'^BOOT': (None, None),
'^SRVST': (None, None),
'^SIMST': (None, None),
'^CEND': (None, None),
'^EARST': (None, None),
'^STIN': (None, None),
'^SMMEMFULL': (None, None),
'^CSNR': (None, None),
}
class HuaweiSIMClass(SIMBaseClass):
"""Huawei SIM Class"""
def __init__(self, sconn):
super(HuaweiSIMClass, self).__init__(sconn)
def setup_sms(self):
# Select SIM storage
self.sconn.send_at('AT+CPMS="SM","SM","SM"')
# Notification when a SMS arrives...
self.sconn.set_sms_indication(1, 1, 0, 1, 0)
# set PDU mode
self.sconn.set_sms_format(0)
def initialize(self, set_encoding=True):
def at_curc_eb(failure):
failure.trap(E.General)
def init_cb(size):
# enable unsolicited control commands
d = self.sconn.send_at('AT^CURC=1')
d.addErrback(at_curc_eb)
self.sconn.set_network_mode(consts.MM_NETWORK_MODE_ANY)
self.sconn.send_at('AT+COPS=3,0')
return size
d = super(HuaweiSIMClass, self).initialize(set_encoding=set_encoding)
d.addCallback(init_cb)
return d
class HuaweiWCDMADevicePlugin(DevicePlugin):
"""DevicePlugin for Huawei"""
sim_klass = HuaweiSIMClass
custom = HuaweiWCDMACustomizer()
wader-0.5.13/core/hardware/icera.py 0000664 0000000 0000000 00000032435 12576466102 0017104 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2009-2011 Vodafone España, S.A.
# Author: Andrew Bird
#
# 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.
"""
Common stuff for all ZTE's Icera based cards
"""
import re
from twisted.internet import defer, reactor
from wader.common import consts
import wader.common.aterrors as E
import wader.common.signals as S
from wader.common.utils import revert_dict, rssi_to_percentage
from core.command import get_cmd_dict_copy, build_cmd_dict, ATCmd
from core.exported import HSOExporter
from core.hardware.base import WCDMACustomizer
from core.middleware import WCDMAWrapper
from core.sim import SIMBaseClass
from core.plugin import DevicePlugin
HSO_MAX_RETRIES = 10
HSO_RETRY_TIMEOUT = 3
ICERA_ALLOWED_DICT = {
consts.MM_ALLOWED_MODE_ANY: 5,
consts.MM_ALLOWED_MODE_2G_ONLY: 0,
consts.MM_ALLOWED_MODE_3G_ONLY: 1,
consts.MM_ALLOWED_MODE_2G_PREFERRED: 2,
consts.MM_ALLOWED_MODE_3G_PREFERRED: 3,
}
ICERA_MODE_DICT = {
consts.MM_NETWORK_MODE_ANY: 5,
consts.MM_NETWORK_MODE_2G_ONLY: 0,
consts.MM_NETWORK_MODE_3G_ONLY: 1,
consts.MM_NETWORK_MODE_2G_PREFERRED: 2,
consts.MM_NETWORK_MODE_3G_PREFERRED: 3,
}
ICERA_BAND_DICT = {
}
ICERA_CMD_DICT = get_cmd_dict_copy()
# \r\n+CPBR: (1-200),80,14,0,0,0\r\n\r\nOK\r\n
ICERA_CMD_DICT['get_phonebook_size'] = build_cmd_dict(
re.compile(r"""
\r\n
\+CPBR:\s
\(\d+-(?P\d+)\).*
\r\n
""", re.VERBOSE))
# \r\n+CMGL: 1,1,"616E64726577",23\r\n
# 0791447758100650040C9144977162470100009011503195310004D436390C\r\n
ICERA_CMD_DICT['list_sms'] = build_cmd_dict(
re.compile(r"""
\r\n
\+CMGL:\s*
(?P\d+),
(?P\d),
(?P"\w*?")?,
\d+
\r\n(?P\w+)
""", re.VERBOSE))
# \r\n+CMGR: 0,"616E64726577",29\r\n
# 0791447758100650040C914497716247010000117082812392400B4AB29A3C5693D56BF2
# 18\r\n
ICERA_CMD_DICT['get_sms'] = build_cmd_dict(
re.compile(r"""
\r\n
\+CMGR:\s*
(?P\d+),
(?P.*),
\d+\r\n
(?P\w+)
\r\n
""", re.VERBOSE))
# \r\n%IPSYS: 1,2\r\n
ICERA_CMD_DICT['get_network_mode'] = build_cmd_dict(
re.compile(r"""
%IPSYS:\s
(?P\d+),
(?P\d+)
""", re.VERBOSE))
# K3805-Z :- %IPDPADDR:,,,,[,,]\r\n
# K4510-Z :- %IPDPADDR:,,,,[,,,
# ,]\r\n
ICERA_CMD_DICT['get_ip4_config'] = build_cmd_dict(
re.compile(r"""
%IPDPADDR:
\s*(?P\d+),
\s*(?P[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+),
\s*(?P[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+),
\s*(?P[0-9.]*),
\s*(?P[0-9.]*)
(?P.*|,.*)
""", re.VERBOSE))
ICERA_CMD_DICT['hso_authenticate'] = build_cmd_dict()
ICERA_CONN_DICT_REV = {
'2G-GPRS': consts.MM_NETWORK_MODE_GPRS,
'2G-EDGE': consts.MM_NETWORK_MODE_EDGE,
'3G': consts.MM_NETWORK_MODE_UMTS,
'3G-HSDPA': consts.MM_NETWORK_MODE_HSDPA,
'3G-HSUPA': consts.MM_NETWORK_MODE_HSUPA,
'3G-HSDPA-HSUPA': consts.MM_NETWORK_MODE_HSPA,
}
# Icera proprietary error
E.ERROR_DICT['+CME ERROR: 767'] = E.General # Operation failed
E.ERROR_DICT['+CME ERROR: 769'] = E.SimBusy
def icera_new_conn_mode(args, device):
try:
args = args.replace(' ', '')
# ['4', '23415', '3G-HSDPA', '-', '0']
rssi, network, tech, connected, regulation = args.split(',')
strength = rssi_to_percentage(int(rssi))
netmode = ICERA_CONN_DICT_REV.get(tech, consts.MM_NETWORK_MODE_UNKNOWN)
except (ValueError, TypeError, IndexError):
return None
device.sconn.updatecache(strength, 'signal')
device.sconn.emit_rssi(strength)
device.sconn.emit_network_mode(netmode)
return None
def icera_connection_state(args, device):
if not args:
return
args = args.replace(' ', '')
args = args.split(',')
if len(args) >= 2:
if str(args[1]) == '3':
device.connection_attempt_failed = True
return None
class IceraSIMClass(SIMBaseClass):
"""
Icera SIM Class
I perform an initial setup in the device
"""
def __init__(self, sconn):
super(IceraSIMClass, self).__init__(sconn)
def initialize(self, set_encoding=True):
self.sconn.reset_settings()
self.sconn.disable_echo()
d = super(IceraSIMClass, self).initialize(set_encoding=set_encoding)
def init_callback(size):
# setup SIM storage defaults
d = self.sconn.send_at('AT+CPMS="SM","SM","SM"')
# turn on unsolicited network notifications
d.addCallback(lambda _: self.sconn.send_at('AT%NWSTATE=1'))
d.addCallback(lambda _: size)
return d
d.addCallback(init_callback)
return d
class IceraWrapper(WCDMAWrapper):
"""Wrapper for all ZTE Icera based cards"""
def enable_radio(self, enable):
d = self.get_radio_status()
def get_radio_status_cb(status):
if status in [0, 4] and enable:
return self.send_at('AT+CFUN=1')
elif status in [1, 5, 6] and not enable:
return self.send_at('AT+CFUN=4')
d.addCallback(get_radio_status_cb)
return d
def get_band(self):
return defer.succeed(consts.MM_NETWORK_BAND_ANY)
def get_network_mode(self):
"""Returns the current network mode"""
def get_network_mode_cb(resp):
mode = int(resp[0].group('mode'))
ICERA_MODE_DICT_REV = revert_dict(self.custom.conn_dict)
if mode in ICERA_MODE_DICT_REV:
return ICERA_MODE_DICT_REV[mode]
raise KeyError("Unknown network mode %s" % mode)
d = self.send_at('AT%IPSYS?', name='get_network_mode',
callback=get_network_mode_cb)
return d
def set_band(self, band):
if band == consts.MM_NETWORK_BAND_ANY:
return defer.succeed('OK')
raise KeyError("Unsupported band %d" % band)
def set_allowed_mode(self, mode):
if mode not in self.custom.allowed_dict:
raise KeyError("Mode %s not found" % mode)
def set_allowed_mode_cb(orig=None):
self.device.set_property(consts.NET_INTFACE, "AllowedMode", mode)
return orig
return self.send_at("AT%%IPSYS=%d" % self.custom.allowed_dict[mode],
callback=set_allowed_mode_cb)
def set_network_mode(self, mode):
"""Sets the network mode to ``mode``"""
# Note: Icera devices drop signal and network acquisition on bearer
# preference set. This causes the connection sequence to take
# 7 secs instead of 2. So we test for current value and only
# set if required. Perhaps a similar thing will happen on
# band set, but as there are no bands defined for Icera at
# the moment, the band set is a no-op.
if mode not in self.custom.conn_dict:
raise KeyError("Mode %s not found" % mode)
d = self.get_network_mode()
def get_network_mode_cb(_mode):
if _mode == mode:
return defer.succeed('OK')
return self.send_at("AT%%IPSYS=%d" % self.custom.conn_dict[mode])
d.addCallback(get_network_mode_cb)
return d
def get_ip4_config(self):
"""
Returns the ip4 config on a NDIS device
Wrapper around _get_ip4_config that provides some error control
"""
ip_method = self.device.get_property(consts.MDM_INTFACE, 'IpMethod')
if ip_method != consts.MM_IP_METHOD_STATIC:
msg = "Cannot get IP4 config from a non static ip method"
raise E.OperationNotSupported(msg)
self.state_dict['num_of_retries'] = 0
def real_get_ip4_config(deferred):
def inform_caller():
if self.device.status > consts.MM_MODEM_STATE_REGISTERED:
self.device.set_status(consts.MM_MODEM_STATE_REGISTERED)
deferred.errback(RuntimeError('Connection attempt failed'))
def get_ip4_eb(failure):
failure.trap(E.General, E.OperationNotSupported)
if self.state_dict.get('should_stop'):
self.state_dict.pop('should_stop')
return
self.state_dict['num_of_retries'] += 1
if self.state_dict['num_of_retries'] > HSO_MAX_RETRIES:
inform_caller()
return failure
reactor.callLater(HSO_RETRY_TIMEOUT,
real_get_ip4_config, deferred)
# We received an unsolicited notification that we failed
if self.device.connection_attempt_failed:
inform_caller()
return
d = self._get_ip4_config()
d.addCallback(deferred.callback)
d.addErrback(get_ip4_eb)
return deferred
auxdef = defer.Deferred()
return real_get_ip4_config(auxdef)
def _get_ip4_config(self):
"""Returns the ip4 config on a Icera NDIS device"""
conn_id = self.state_dict.get('conn_id')
if conn_id is None:
raise E.CallIndexError("conn_id is None")
cmd = ATCmd('AT%%IPDPADDR=%d' % conn_id, name='get_ip4_config')
d = self.queue_at_cmd(cmd)
def _get_ip4_config_cb(resp):
if not resp:
raise E.General()
ip, dns1 = resp[0].group('ip'), resp[0].group('dns1')
# XXX: Fix dns3
dns2 = dns3 = resp[0].group('dns2')
self.device.set_status(consts.MM_MODEM_STATE_CONNECTED)
return [ip, dns1, dns2, dns3]
d.addCallback(_get_ip4_config_cb)
return d
def hso_authenticate(self, user, passwd, auth):
"""Authenticates using ``user`` and ``passwd`` on Icera NDIS devices"""
conn_id = self.state_dict.get('conn_id')
if conn_id is None:
raise E.CallIndexError("conn_id is None")
# Note: auth is now a bitfield, but icera can only support one mode so
# unless it's only NONE or CHAP, default to PAP
if auth == consts.MM_ALLOWED_AUTH_NONE:
_auth = 0 # No auth
elif auth == consts.MM_ALLOWED_AUTH_CHAP:
_auth = 2 # CHAP
else:
_auth = 1 # PAP
args = (conn_id, _auth, user, passwd)
cmd = ATCmd('AT%%IPDPCFG=%d,0,%d,"%s","%s"' % args,
name='hso_authenticate')
d = self.queue_at_cmd(cmd)
d.addCallback(lambda resp: resp[0].group('resp'))
return d
def hso_connect(self):
conn_id = self.state_dict.get('conn_id')
if conn_id is None:
raise E.CallIndexError("conn_id is None")
if self.device.status == consts.MM_MODEM_STATE_CONNECTED:
# this cannot happen
raise E.Connected("we are already connected")
if self.device.status == consts.MM_MODEM_STATE_CONNECTING:
raise E.SimBusy("we are already connecting")
self.device.connection_attempt_failed = False
self.device.set_status(consts.MM_MODEM_STATE_CONNECTING)
return self.send_at('AT%%IPDPACT=%d,1' % conn_id)
def hso_disconnect(self):
conn_id = self.state_dict.get('conn_id')
if conn_id is None:
raise E.CallIndexError("conn_id is None")
self.device.set_status(consts.MM_MODEM_STATE_DISCONNECTING)
def disconnect_cb(ignored):
# XXX: perhaps we should check the registration status here
if self.device.status > consts.MM_MODEM_STATE_REGISTERED:
self.device.set_status(consts.MM_MODEM_STATE_REGISTERED)
d = self.send_at('AT%%IPDPACT=%d,0' % conn_id)
d.addCallback(disconnect_cb)
return d
class IceraWCDMACustomizer(WCDMACustomizer):
"""WCDMA customizer for ZTE Icera based devices"""
async_regexp = re.compile("""
\r\n
(?P%[A-Z]{3,}):\s*(?P.*)
\r\n""", re.VERBOSE)
allowed_dict = ICERA_ALLOWED_DICT
band_dict = ICERA_BAND_DICT
conn_dict = ICERA_MODE_DICT
cmd_dict = ICERA_CMD_DICT
device_capabilities = [S.SIG_NETWORK_MODE,
S.SIG_SMS_NOTIFY_ONLINE,
S.SIG_RSSI]
signal_translations = {
'%NWSTATE': (None, icera_new_conn_mode),
'%IPDPACT': (None, icera_connection_state),
}
wrapper_klass = IceraWrapper
exporter_klass = HSOExporter
class IceraWCDMADevicePlugin(DevicePlugin):
"""WCDMA device plugin for ZTE Icera based devices"""
sim_klass = IceraSIMClass
custom = IceraWCDMACustomizer()
wader-0.5.13/core/hardware/longcheer.py 0000664 0000000 0000000 00000016661 12576466102 0017772 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2012 Sphere Systems Ltd
# Author: Andrew Bird
#
# 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.
"""Common stuff for all Longcheer cards and rebranded variants"""
import re
from twisted.internet import defer
# also registers gsm0338 codec
from messaging.sms import is_gsm_text
from messaging.utils import unpack_msg
from wader.common import consts
from wader.common.aterrors import MalformedUssdPduError
from wader.common.encoding import pack_ucs2_bytes
from wader.common.utils import revert_dict
from core.command import get_cmd_dict_copy, build_cmd_dict
from core.hardware.base import WCDMACustomizer
from core.middleware import WCDMAWrapper
from core.plugin import DevicePlugin
from core.sim import SIMBaseClass
LONGCHEER_ALLOWED_DICT = {
consts.MM_ALLOWED_MODE_ANY: '2', # 3g preferred
consts.MM_ALLOWED_MODE_3G_ONLY: '1',
consts.MM_ALLOWED_MODE_3G_PREFERRED: '2',
consts.MM_ALLOWED_MODE_2G_ONLY: '3',
consts.MM_ALLOWED_MODE_2G_PREFERRED: '4',
}
LONGCHEER_CONN_DICT = {
consts.MM_NETWORK_MODE_ANY: '2', # 3g preferred
consts.MM_NETWORK_MODE_3G_ONLY: '1',
consts.MM_NETWORK_MODE_3G_PREFERRED: '2',
consts.MM_NETWORK_MODE_2G_ONLY: '3',
consts.MM_NETWORK_MODE_2G_PREFERRED: '4',
}
LONGCHEER_CMD_DICT = get_cmd_dict_copy()
# +CPBR: 1,"+4917XXXXXX",145,"005400650073007400200053007400720069","",129,""
LONGCHEER_CMD_DICT['get_contact'] = build_cmd_dict(re.compile(r"""
\r\n
\+CPBR:\s(?P\d+),
"(?P[+0-9a-fA-F*#]+)",
(?P\d+),
"(?P.*?)"
(?P,\S*)?
\r\n""", re.X))
LONGCHEER_CMD_DICT['list_contacts'] = build_cmd_dict(
end=re.compile('(\r\n)?\r\n(OK)\r\n'), extract=re.compile(r"""
\r\n
\+CPBR:\s(?P\d+),
"(?P[+0-9a-fA-F*#]+)",
(?P\d+),
"(?P.*?)"
(?P,\S*)?
""", re.X))
LONGCHEER_CMD_DICT['get_network_mode'] = build_cmd_dict(
re.compile("\r\n\+MODODR:\s?(?P\d+)\r\n"))
class LongcheerSIMClass(SIMBaseClass):
"""Longcheer SIM Class"""
def __init__(self, sconn):
super(LongcheerSIMClass, self).__init__(sconn)
def initialize(self, set_encoding=True):
def init_callback(size):
# make sure we are in most promiscuous mode before registration
self.sconn.set_network_mode(consts.MM_NETWORK_MODE_ANY)
# set SMS storage default
self.sconn.send_at('AT+CPMS="SM","SM","SM"')
return(size)
d = super(LongcheerSIMClass, self).initialize(set_encoding)
d.addCallback(init_callback)
return d
class LongcheerWCDMAWrapper(WCDMAWrapper):
"""Wrapper for all Longcheer cards"""
def enable_radio(self, enable):
d = self.get_radio_status()
def get_radio_status_cb(status):
if status in [0, 4] and enable:
return self.send_at('AT+CFUN=1')
elif status in [1] and not enable:
return self.send_at('AT+CFUN=4')
d.addCallback(get_radio_status_cb)
return d
def get_band(self):
"""Returns the current used band"""
return defer.succeed(consts.MM_NETWORK_BAND_ANY)
def set_band(self, band):
"""Sets the band to ``band``"""
if band == consts.MM_NETWORK_BAND_ANY:
return defer.succeed('OK')
else:
raise KeyError("Unsupported band %d" % band)
def get_network_mode(self):
"""Returns the current network mode"""
def cb(resp):
mode = resp[0].group('mode')
return revert_dict(self.custom.conn_dict)[mode]
return self.send_at("AT+MODODR?", name='get_network_mode', callback=cb)
def set_allowed_mode(self, mode):
"""Sets the allowed mode to ``mode``"""
if mode not in self.custom.allowed_dict:
raise KeyError("Unknown network mode %d" % mode)
def cb(ign=None):
self.device.set_property(consts.NET_INTFACE, "AllowedMode", mode)
return ign
return self.send_at("AT+MODODR=%s" % self.custom.allowed_dict[mode],
callback=cb)
def set_network_mode(self, mode):
"""Sets the network mode to ``mode``"""
if mode not in self.custom.conn_dict:
raise KeyError("Unknown network mode %d" % mode)
return self.send_at("AT+MODODR=%s" % self.custom.conn_dict[mode])
def send_ussd(self, ussd):
"""
Sends the USSD command ``ussd``
Sends plain or UCS2 encoded text
Receives GSM 7bit compressed text
"""
# AT+CUSD=1,"*100#",15
# (*100#)
# or
# AT+CUSD=1,"002A0031003000300023",15
# (*100#)
# +CUSD: 1,"5079191E4E935BCDB2DBAF88818E753A3A2C2EBBD76F37FDAD90818E7"
# "53A3A2C2EBB5BD6B2DCEC3F8BC3F275394D57CC40C1BA991D26D7DD67"
# "D0B14E4787C565F70B1A1EAF5BF477EBF856D040D0F0780D6A86DDE17"
# "359AEB881A86179DA9C769BDF0A1C0899669BCB",15
# Prepaid-Menü\n
# 1 Guthabenkonto\n
# 2 Guthaben-Verfügbarkeit\n
# 3 Aufladung Guthaben/Pack-to-Go\n
# 4 Pack Manager\n
# 7 Tarifinfo\n
# 8 Hilfe
def convert_response(response):
index = response[0].group('index')
if index == '1':
self.device.set_property(
consts.USD_INTFACE, 'State', 'user-response')
else:
self.device.set_property(consts.USD_INTFACE, 'State', 'idle')
resp = response[0].group('resp')
if resp is None:
return "" # returning the Empty string is valid
try:
ret = unpack_msg(resp).decode("gsm0338")
if is_gsm_text(ret):
return ret
except UnicodeError:
pass
try:
return resp.decode('hex')
except TypeError:
raise MalformedUssdPduError(resp)
def reset_state(failure):
if self.device.get_property(consts.USD_INTFACE, 'State') != 'idle':
self.device.set_property(consts.USD_INTFACE, 'State', 'idle')
failure.raiseException() # re-raise
if 'UCS2' in self.device.sim.charset:
ussd = pack_ucs2_bytes(ussd)
d = super(WCDMAWrapper, self).send_ussd(str(ussd))
d.addCallback(convert_response)
d.addErrback(reset_state)
return d
class LongcheerWCDMACustomizer(WCDMACustomizer):
"""WCDMA customizer for Longcheer devices"""
allowed_dict = LONGCHEER_ALLOWED_DICT
cmd_dict = LONGCHEER_CMD_DICT
conn_dict = LONGCHEER_CONN_DICT
wrapper_klass = LongcheerWCDMAWrapper
class LongcheerWCDMADevicePlugin(DevicePlugin):
"""WCDMA device plugin for Longcheer devices"""
sim_klass = LongcheerSIMClass
custom = LongcheerWCDMACustomizer()
wader-0.5.13/core/hardware/novatel.py 0000664 0000000 0000000 00000022353 12576466102 0017467 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2006-2009 Vodafone España, S.A.
# Copyright (C) 2008-2009 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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.
"""Common stuff for all Novatel's cards"""
import re
from twisted.internet import defer, reactor
from wader.common import consts
from core.command import get_cmd_dict_copy, build_cmd_dict
from core.hardware.base import WCDMACustomizer
from core.middleware import WCDMAWrapper
from core.plugin import DevicePlugin
from wader.common.utils import revert_dict
from core.sim import SIMBaseClass
import wader.common.signals as S
NOVATEL_ALLOWED_DICT = {
consts.MM_ALLOWED_MODE_ANY: '0,2',
consts.MM_ALLOWED_MODE_2G_ONLY: '1,2',
consts.MM_ALLOWED_MODE_3G_ONLY: '2,2',
consts.MM_ALLOWED_MODE_3G_PREFERRED: '0,2',
}
NOVATEL_MODE_DICT = {
consts.MM_NETWORK_MODE_ANY: '0,2',
consts.MM_NETWORK_MODE_2G_ONLY: '1,2',
consts.MM_NETWORK_MODE_3G_ONLY: '2,2',
consts.MM_NETWORK_MODE_3G_PREFERRED: '0,2', # just a dup of automatic
}
NOVATEL_BAND_DICT = {
consts.MM_NETWORK_BAND_ANY: 0x3FFFFFFF,
consts.MM_NETWORK_BAND_EGSM: 0x00000100, # 900 MHz
consts.MM_NETWORK_BAND_DCS: 0x00000080, # 1800 MHz
consts.MM_NETWORK_BAND_PCS: 0x00200000, # 1900 MHz
consts.MM_NETWORK_BAND_G850: 0x00080000, # 850 MHz
consts.MM_NETWORK_BAND_U2100: 0x00400000, # WCDMA 2100 MHz
consts.MM_NETWORK_BAND_U1800: 0x01000000, # WCDMA 3GPP UMTS1800 MHz
consts.MM_NETWORK_BAND_U17IV: 0x02000000, # WCDMA 3GPP AWS 1700/2100 MHz
consts.MM_NETWORK_BAND_U800: 0x08000000, # WCDMA 3GPP UMTS800 MHz
consts.MM_NETWORK_BAND_U850: 0x04000000, # WCDMA 3GPP UMTS850 MHz
consts.MM_NETWORK_BAND_U1900: 0x00800000, # WCDMA 3GPP UMTS MHz
}
# Following band definitions found from X950D
# AT$nwband=?
# $NWBAND: bit definitions
# $NWBAND: 00 CDMA2000 Band Class 0, A-System
# $NWBAND: 01 CDMA2000 Band Class 0, B-System
# $NWBAND: 02 CDMA2000 Band Class 1, all blocks
# $NWBAND: 03 CDMA2000 Band Class 2 place holder
# $NWBAND: 04 CDMA2000 Band Class 3, A-System
# $NWBAND: 05 CDMA2000 Band Class 4, all blocks
# $NWBAND: 06 CDMA2000 Band Class 5, all blocks
# $NWBAND: 07 GSM DCS band
# $NWBAND: 08 GSM Extended GSM (E-GSM) band
# $NWBAND: 09 GSM Primary GSM (P-GSM) band
# $NWBAND: 10 CDMA2000 Band Class 6
# $NWBAND: 11 CDMA2000 Band Class 7
# $NWBAND: 12 CDMA2000 Band Class 8
# $NWBAND: 13 CDMA2000 Band Class 9
# $NWBAND: 14 CDMA2000 Band Class 10
# $NWBAND: 15 CDMA2000 Band Class 11
# $NWBAND: 16 GSM 450 band
# $NWBAND: 17 GSM 480 band
# $NWBAND: 18 GSM 750 band
# $NWBAND: 19 GSM 850 band
# $NWBAND: 20 GSM Band
# $NWBAND: 21 GSM PCS band
# $NWBAND: 22 WCDMA I IMT 2000 band
# $NWBAND: 23 WCDMA II PCS band
# $NWBAND: 24 WCDMA III 1700 band
# $NWBAND: 25 WCDMA IV 1700 band
# $NWBAND: 26 WCDMA V US850 band
# $NWBAND: 27 WCDMA VI JAPAN 800 band
# $NWBAND: 28 Reserved for BC12/BC14
# $NWBAND: 29 Reserved for BC12/BC14
# $NWBAND: 30 Reserved
# $NWBAND: 31 Reserved
# Following additional band definitions from MiFi 2352
# at$NWBAND2=?
# $NWBAND2: bit definitions
# $NWBAND2: 00 WLAN US 2400 band
# $NWBAND2: 01 WLAN ETSI 2400 band
# $NWBAND2: 02 WLAN FRANCE 2400 band
# $NWBAND2: 03 WLAN SPAIN 2400 band
# $NWBAND2: 04 WLAN JAPAN 2400 band
# $NWBAND2: 05 WLAN US 2400 band
# $NWBAND2: 06 WLAN EUROPE 5000 band
# $NWBAND2: 07 WLAN FRANCE 5000 band
# $NWBAND2: 08 WLAN SPAIN 5000 band
# $NWBAND2: 09 WLAN JAPAN 5000 band
# $NWBAND2: 10 Reserved
# $NWBAND2: 11 Reserved
# $NWBAND2: 12 Reserved
# $NWBAND2: 13 Reserved
# $NWBAND2: 14 Reserved
# $NWBAND2: 15 Reserved
# $NWBAND2: 16 WCDMA EUROPE 2600 band
# $NWBAND2: 17 WCDMA EUROPE & JAPAN 900 band
# $NWBAND2: 18 WCDMA JAPAN 1700 band
# $NWBAND2: 19 Reserved for WLAN
# $NWBAND2: 20 Reserved for WLAN
# $NWBAND2: 21 Reserved for WLAN
# $NWBAND2: 22 Reserved for WLAN
# $NWBAND2: 23 Reserved for WLAN
# $NWBAND2: 24 Band Class 16
# $NWBAND2: 25 Reserved
# $NWBAND2: 26 Reserved
# $NWBAND2: 27 Reserved
# $NWBAND2: 28 Reserved
# $NWBAND2: 29 Reserved
# $NWBAND2: 30 Persistent value from NV
# $NWBAND2: 31 Reserved
NOVATEL_CMD_DICT = get_cmd_dict_copy()
NOVATEL_CMD_DICT['get_network_mode'] = build_cmd_dict(
re.compile("\r\n\$NWRAT:\s?(?P\d,\d),\d\r\n"))
NOVATEL_CMD_DICT['get_band'] = build_cmd_dict(
re.compile("\r\n\$NWBAND:\s?(?P[0-9A-Fa-f]+)"))
class NovatelSIMClass(SIMBaseClass):
"""Novatel SIM Class"""
def __init__(self, sconn):
super(NovatelSIMClass, self).__init__(sconn)
def setup_sms(self):
# Select SIM storage
self.sconn.send_at('AT+CPMS="SM","SM","SM"')
# Notification when a SMS arrives...
self.sconn.set_sms_indication(1, 1, 0, 1, 0)
# set PDU mode
self.sconn.set_sms_format(0)
def initialize(self, set_encoding=True):
def init_callback(size):
# make sure we are in most promiscuous mode before registration
self.sconn.set_network_mode(consts.MM_NETWORK_MODE_ANY)
return(size)
d = super(NovatelSIMClass, self).initialize(set_encoding)
d.addCallback(init_callback)
return d
class NovatelWrapper(WCDMAWrapper):
"""Wrapper for all Novatel cards"""
# XXX: Needs updating for use of bands in second chunk
# of bit settings (U900 etc)
def get_band(self):
"""Returns the current used band"""
if not len(self.custom.band_dict):
return defer.succeed(consts.MM_NETWORK_BAND_ANY)
def get_band_cb(resp):
band = int(resp[0].group('band'), 16)
if band == 0x3FFFFFFF:
return consts.MM_NETWORK_BAND_ANY
ret = 0
for key, value in NOVATEL_BAND_DICT.items():
if value & band:
ret |= key
return ret
return self.send_at("AT$NWBAND?", name='get_band',
callback=get_band_cb)
def get_network_mode(self):
"""Returns the current network mode"""
def get_network_mode_cb(resp):
mode = resp[0].group('mode')
return revert_dict(self.custom.conn_dict)[mode]
return self.send_at("AT$NWRAT?", name='get_network_mode',
callback=get_network_mode_cb)
# XXX: Needs updating for use of bands in second chunk
# of bit settings (U900 etc)
def set_band(self, band):
"""Sets the band to ``band``"""
if not len(self.custom.band_dict):
if band == consts.MM_NETWORK_BAND_ANY:
return defer.succeed('')
else:
raise KeyError("Unsupported band %d" % band)
if band == consts.MM_NETWORK_BAND_ANY:
_band = 0x3FFFFFFF
else:
_band = 0
for key, value in self.custom.band_dict.items():
if key == consts.MM_NETWORK_BAND_ANY:
continue
if key & band:
_band |= value
if _band == 0:
# if we could not satisfy the request, tell someone
raise KeyError("Unsupported band %d" % band)
deferred = defer.Deferred()
d = self.send_at("AT$NWBAND=%08x" % _band)
def delay_result(result):
reactor.callLater(1, deferred.callback, True)
d.addCallback(delay_result)
return deferred
def set_allowed_mode(self, mode):
"""Sets the allowed mode to ``mode``"""
if mode not in self.custom.allowed_dict:
raise KeyError("Unknown network mode %d" % mode)
def set_allowed_mode_cb(ign=None):
self.device.set_property(consts.NET_INTFACE, "AllowedMode", mode)
return ign
return self.send_at("AT$NWRAT=%s" % self.custom.allowed_dict[mode],
callback=set_allowed_mode_cb)
def set_network_mode(self, mode):
"""Sets the network mode to ``mode``"""
if mode not in self.custom.conn_dict:
raise KeyError("Unknown network mode %d" % mode)
return self.send_at("AT$NWRAT=%s" % self.custom.conn_dict[mode])
class NovatelWCDMACustomizer(WCDMACustomizer):
"""WCDMA customizer for Novatel cards"""
async_regexp = None
allowed_dict = NOVATEL_ALLOWED_DICT
band_dict = {} # let the cards that support band switching define
# the bands they support
cmd_dict = NOVATEL_CMD_DICT
conn_dict = NOVATEL_MODE_DICT
device_capabilities = [S.SIG_SMS_NOTIFY_ONLINE]
wrapper_klass = NovatelWrapper
class NovatelWCDMADevicePlugin(DevicePlugin):
"""WCDMA device plugin for Novatel cards"""
sim_klass = NovatelSIMClass
custom = NovatelWCDMACustomizer()
wader-0.5.13/core/hardware/option.py 0000664 0000000 0000000 00000044071 12576466102 0017330 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2006-2008 Vodafone España, S.A.
# Copyright (C) 2008-2009 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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.
"""Common stuff for all Option's datacards/devices"""
import re
from twisted.internet import defer, reactor
from wader.common import consts
import wader.common.aterrors as E
import wader.common.signals as S
from wader.common.utils import rssi_to_percentage, revert_dict
from core.command import get_cmd_dict_copy, build_cmd_dict, ATCmd
from core.exported import HSOExporter
from core.hardware.base import WCDMACustomizer
from core.middleware import WCDMAWrapper
from core.plugin import DevicePlugin
from core.sim import SIMBaseClass
NUM_RETRIES = 30
RETRY_TIMEOUT = 4
HSO_MAX_RETRIES = 10
HSO_RETRY_TIMEOUT = 3
OPTION_ALLOWED_DICT = {
consts.MM_ALLOWED_MODE_ANY: 5,
consts.MM_ALLOWED_MODE_2G_PREFERRED: 2,
consts.MM_ALLOWED_MODE_3G_PREFERRED: 3,
consts.MM_ALLOWED_MODE_2G_ONLY: 0,
consts.MM_ALLOWED_MODE_3G_ONLY: 1,
}
# The MM band dictionary was modelled after the Option band dict, but there are
# now some differences
OPTION_BAND_DICT = {
consts.MM_NETWORK_BAND_ANY: 'ANY',
consts.MM_NETWORK_BAND_EGSM: 'EGSM',
consts.MM_NETWORK_BAND_DCS: 'DCS',
consts.MM_NETWORK_BAND_PCS: 'PCS',
consts.MM_NETWORK_BAND_G850: 'G850',
consts.MM_NETWORK_BAND_U2100: 'U2100',
consts.MM_NETWORK_BAND_U1800: 'U1700', # Option calls this U1700
consts.MM_NETWORK_BAND_U17IV: 'U17IV',
consts.MM_NETWORK_BAND_U800: 'U800',
consts.MM_NETWORK_BAND_U850: 'U850',
consts.MM_NETWORK_BAND_U900: 'U900',
consts.MM_NETWORK_BAND_U17IX: 'U17IX',
consts.MM_NETWORK_BAND_U1900: 'U1900',
# Option has no U2600
}
OPTION_CONN_DICT = {
consts.MM_NETWORK_MODE_2G_ONLY: 0,
consts.MM_NETWORK_MODE_3G_ONLY: 1,
consts.MM_NETWORK_MODE_2G_PREFERRED: 2,
consts.MM_NETWORK_MODE_3G_PREFERRED: 3,
consts.MM_NETWORK_MODE_ANY: 5,
}
# Option devices like to append its serial number after the IMEI, ignore it
OPTION_CMD_DICT = get_cmd_dict_copy()
OPTION_CMD_DICT['get_imei'] = build_cmd_dict(re.compile(
"\r\n(?P\d+),\S+\r\n", re.VERBOSE))
OPTION_CMD_DICT['get_sim_status'] = build_cmd_dict(re.compile(r"""
_OBLS:\s(?P\d),
(?P\d),
(?P\d)
""", re.VERBOSE))
OPTION_CMD_DICT['get_band'] = build_cmd_dict(re.compile(r"""
\r\n(?P.*):\s+(?P\d)
""", re.VERBOSE))
# AT_OPBM=?
# _OPBM: ("ANY","EGSM","DCS","PCS","G850","U2100","U1900","U1700","U17IV",
# "U850","U800","U900","U17IX")
OPTION_CMD_DICT['get_bands'] = build_cmd_dict(re.compile(r"""
"(?P[A-Za-z0-9]+)"(?:,|\))
""", re.VERBOSE))
OPTION_CMD_DICT['get_network_mode'] = build_cmd_dict(re.compile(r"""
_OPSYS:\s
(?P\d),
(?P\d)
""", re.VERBOSE))
OPTION_CMD_DICT['hso_authenticate'] = build_cmd_dict()
OPTION_CMD_DICT['get_ip4_config'] = build_cmd_dict(re.compile(r"""
\r\n
_OWANDATA:\s
(?P\d),\s
(?P.*),\s
(?P.*),\s
(?P.*),\s
(?P.*),\s
(?P.*),\s
(?P.*),\s
(?P\d+)
\r\r\n""", re.X))
def option_new_mode(args, device):
"""
Translates Option's unsolicited notifications to Wader's representation
"""
ossysi_args_dict = {
'0': consts.MM_NETWORK_MODE_GPRS,
'2': consts.MM_NETWORK_MODE_UMTS,
'3': consts.MM_NETWORK_MODE_UNKNOWN,
}
netmode = ossysi_args_dict.get(args, consts.MM_NETWORK_MODE_UNKNOWN)
device.sconn.emit_network_mode(netmode)
return None
def option_new_rssi(args, device):
try:
strength = rssi_to_percentage(int(args.split(',')[0]))
except (ValueError, TypeError, IndexError):
return None
device.sconn.updatecache(strength, 'signal')
device.sconn.emit_rssi(strength)
return None
class OptionSIMClass(SIMBaseClass):
"""
Option SIM Class
I perform an initial setup in the device and will not
return until the SIM is *really* ready
"""
def __init__(self, sconn):
super(OptionSIMClass, self).__init__(sconn)
self.num_retries = 0
def initialize(self, set_encoding=True):
deferred = defer.Deferred()
def init_callback(size):
# make sure we aren't looking for a network that isn't available
self.sconn.set_network_mode(consts.MM_NETWORK_MODE_ANY)
# setup asynchronous notifications
self.sconn.send_at('AT_OSSYS=1') # cell change notification
self.sconn.send_at('AT_OSQI=1') # signal quality notification
deferred.callback(size)
def sim_ready_cb(ignored):
d2 = super(OptionSIMClass, self).initialize(set_encoding)
d2.addCallback(init_callback)
def sim_ready_eb(failure):
deferred.errback(failure)
d = self.is_sim_ready()
d.addCallback(sim_ready_cb)
d.addErrback(sim_ready_eb)
return deferred
def is_sim_ready(self):
deferred = defer.Deferred()
def process_sim_state(auxdef):
def parse_response(resp):
status = tuple(map(int, resp[0].groups()))
if status == (1, 1, 1):
auxdef.callback(True)
else:
self.num_retries += 1
if self.num_retries < NUM_RETRIES:
reactor.callLater(RETRY_TIMEOUT,
process_sim_state, auxdef)
else:
msg = "Max number of attempts reached %d"
auxdef.errback(E.General(msg % self.num_retries))
return
self.sconn.send_at('AT_OBLS', name='get_sim_status',
callback=parse_response)
return auxdef
return process_sim_state(deferred)
class OptionWrapper(WCDMAWrapper):
"""Wrapper for all Option cards"""
def _get_band_dict(self):
"""Returns a dict with the currently selected bands"""
def callback(resp):
bands = {}
for r in resp:
name, active = r.group('name'), int(r.group('active'))
bands[name] = active
return bands
d = self.send_at('AT_OPBM?', name='get_band', callback=callback)
return d
def get_band(self):
"""Returns the current used band"""
def get_band_dict_cb(current):
if 'ANY' in current and current['ANY'] == 1:
# can't be combined by design
return consts.MM_NETWORK_BAND_ANY
ret = 0
for key, value in self.custom.band_dict.items():
for name, active in current.items():
if name == value and active:
ret |= key
return ret
d = self._get_band_dict()
d.addCallback(get_band_dict_cb)
return d
def get_bands(self):
"""
Returns the supported bands from the device, but filtered to the set
supported in the custom band_dict. This means we can remove any
awkward bands from problematic devices e.g. K3760 / U2100
"""
def callback(resp):
bands = [r.group('name') for r in resp]
ret = 0
for key, value in self.custom.band_dict.items():
if key == consts.MM_NETWORK_BAND_ANY:
# skip ANY
continue
if value in bands:
ret |= key
return ret
return self.send_at('AT_OPBM=?', name='get_bands', callback=callback)
def get_network_mode(self):
"""Returns the current network mode"""
def get_network_mode_cb(resp):
mode = int(resp[0].group('mode'))
OPTION_CONN_DICT_REV = revert_dict(OPTION_CONN_DICT)
if mode in OPTION_CONN_DICT_REV:
return OPTION_CONN_DICT_REV[mode]
raise KeyError("Unknown network mode %d" % mode)
return self.send_at('AT_OPSYS?', name='get_network_mode',
callback=get_network_mode_cb)
def get_roaming_ids(self):
# Many Option devices panic if AT+CPOL is sent while in UCS2, we will
# switch to IRA, perform the operation and switch back to UCS2
self.set_charset("IRA")
d = super(OptionWrapper, self).get_roaming_ids()
def get_roaming_ids_cb(rids):
d2 = self.set_charset("UCS2")
d2.addCallback(lambda _: rids)
return d2
d.addCallback(get_roaming_ids_cb)
return d
def set_band(self, band):
"""Sets the band set to requested ``band``"""
def get_band_dict_cb(current):
at_str = 'AT_OPBM="%s",%d'
todo = []
if band == consts.MM_NETWORK_BAND_ANY:
# enabling ANY should suffice
todo.append(self.send_at(at_str % ('ANY', 1)))
else:
supported = self.custom.band_dict.copy()
supported.pop(consts.MM_NETWORK_BAND_ANY)
# turn on the bits we want first
for key, value in supported.items():
if key & band and current[value] != 1:
# enable required band
todo.append(self.send_at(at_str % (value, 1)))
# turn off the bits we don't want
for key, value in supported.items():
if (not key & band) and current[value] != 0:
# disable unrequired band
todo.append(self.send_at(at_str % (value, 0)))
if todo:
dlist = defer.DeferredList(todo, consumeErrors=1)
dlist.addCallback(lambda l: [x[1] for x in l])
return dlist
raise KeyError("OptionWrapper: Unknown band %d" % band)
# due to Option's band API, we'll start by obtaining the current bands
d = self._get_band_dict()
d.addCallback(get_band_dict_cb)
return d
def set_allowed_mode(self, mode):
"""Sets the allowed mode to ``_mode``"""
if mode not in self.custom.allowed_dict:
raise KeyError("Unknown mode %d for set_allowed_mode" % mode)
def set_allowed_mode_cb(orig=None):
self.device.set_property(consts.NET_INTFACE, "AllowedMode", mode)
return orig
return self.send_at("AT_OPSYS=%d,2" % self.custom.allowed_dict[mode],
callback=set_allowed_mode_cb)
def set_network_mode(self, mode):
"""Sets the network mode to ``_mode``"""
if mode not in OPTION_CONN_DICT:
raise KeyError("Unknown mode %d for set_network_mode" % mode)
return self.send_at("AT_OPSYS=%d,2" % OPTION_CONN_DICT[mode])
class OptionHSOWrapper(OptionWrapper):
"""Wrapper for all Option HSO cards"""
def get_ip4_config(self):
"""
Returns the ip4 config on a HSO device
Wrapper around _get_ip4_config that provides some error control
"""
ip_method = self.device.get_property(consts.MDM_INTFACE, 'IpMethod')
if ip_method != consts.MM_IP_METHOD_STATIC:
msg = "Cannot get IP4 config from a non static ip method"
raise E.OperationNotSupported(msg)
self.state_dict['num_of_retries'] = 0
def real_get_ip4_config(deferred):
def inform_caller():
if self.device.status > consts.MM_MODEM_STATE_REGISTERED:
self.device.set_status(consts.MM_MODEM_STATE_REGISTERED)
deferred.errback(RuntimeError('Connection attempt failed'))
def get_ip4_eb(failure):
failure.trap(E.General)
if self.state_dict.get('should_stop'):
self.state_dict.pop('should_stop')
return
self.state_dict['num_of_retries'] += 1
if self.state_dict['num_of_retries'] > HSO_MAX_RETRIES:
inform_caller()
return failure
reactor.callLater(HSO_RETRY_TIMEOUT,
real_get_ip4_config, deferred)
d = self._get_ip4_config()
d.addCallback(deferred.callback)
d.addErrback(get_ip4_eb)
return deferred
auxdef = defer.Deferred()
return real_get_ip4_config(auxdef)
def _get_ip4_config(self):
"""Returns the ip4 config on a HSO device"""
conn_id = self.state_dict.get('conn_id')
if not conn_id:
raise E.CallIndexError("conn_id is None")
cmd = ATCmd('AT_OWANDATA=%d' % conn_id, name='get_ip4_config')
d = self.queue_at_cmd(cmd)
def _get_ip4_config_cb(resp):
ip, dns1 = resp[0].group('ip'), resp[0].group('dns1')
# XXX: Fix dns3
dns2 = dns3 = resp[0].group('dns2')
self.device.set_status(consts.MM_MODEM_STATE_CONNECTED)
return [ip, dns1, dns2, dns3]
d.addCallback(_get_ip4_config_cb)
return d
def hso_authenticate(self, user, passwd, auth):
"""Authenticates using ``user`` and ``passwd`` on HSO devices"""
conn_id = self.state_dict.get('conn_id')
if not conn_id:
raise E.CallIndexError("conn_id is None")
# Note: auth is now a bitfield, but option can only support one mode so
# unless it's only NONE or CHAP, default to PAP
if auth == consts.MM_ALLOWED_AUTH_NONE:
_auth = 0 # No auth
elif auth == consts.MM_ALLOWED_AUTH_CHAP:
_auth = 2 # CHAP
else:
_auth = 1 # PAP
args = (conn_id, _auth, user, passwd)
cmd = ATCmd('AT$QCPDPP=%d,%d,"%s","%s"' % args,
name='hso_authenticate')
d = self.queue_at_cmd(cmd)
d.addCallback(lambda resp: resp[0].group('resp'))
return d
def hso_connect(self):
# clean should_stop
if 'should_stop' in self.state_dict:
self.state_dict.pop('should_stop')
conn_id = self.state_dict.get('conn_id')
if not conn_id:
raise E.CallIndexError("conn_id is None")
if self.device.status == consts.MM_MODEM_STATE_CONNECTED:
# this cannot happen
raise E.Connected("we are already connected")
if self.device.status == consts.MM_MODEM_STATE_CONNECTING:
raise E.SimBusy("we are already connecting")
self.device.set_status(consts.MM_MODEM_STATE_CONNECTING)
return self.device.sconn.send_at('AT_OWANCALL=%d,1,0' % conn_id)
def hso_disconnect(self):
conn_id = self.state_dict.get('conn_id')
if not conn_id:
raise E.CallIndexError("conn_id is None")
self.device.set_status(consts.MM_MODEM_STATE_DISCONNECTING)
self.state_dict['should_stop'] = True
def disconnect_cb(ignored):
# XXX: perhaps we should check the registration status here
if self.device.status > consts.MM_MODEM_STATE_REGISTERED:
self.device.set_status(consts.MM_MODEM_STATE_REGISTERED)
d = self.device.sconn.send_at('AT_OWANCALL=%d,0,0' % conn_id)
d.addCallback(disconnect_cb)
return d
class OptionWCDMACustomizer(WCDMACustomizer):
"""Customizer for Option's cards"""
async_regexp = re.compile(r"""
\r\n
(?P_O[A-Z]{3,}):\s(?P.*)
\r\n""", re.VERBOSE)
allowed_dict = OPTION_ALLOWED_DICT
band_dict = OPTION_BAND_DICT
conn_dict = OPTION_CONN_DICT
cmd_dict = OPTION_CMD_DICT
device_capabilities = [S.SIG_NETWORK_MODE,
S.SIG_SMS_NOTIFY_ONLINE,
S.SIG_RSSI]
signal_translations = {
'_OSSYSI': (None, option_new_mode),
'_OSIGQ': (None, option_new_rssi),
}
wrapper_klass = OptionWrapper
class OptionHSOWCDMACustomizer(OptionWCDMACustomizer):
"""Customizer for HSO WCDMA devices"""
exporter_klass = HSOExporter
wrapper_klass = OptionHSOWrapper
class OptionWCDMADevicePlugin(DevicePlugin):
"""DevicePlugin for Option"""
sim_klass = OptionSIMClass
custom = OptionWCDMACustomizer()
def __init__(self):
super(OptionWCDMADevicePlugin, self).__init__()
class OptionHSOWCDMADevicePlugin(OptionWCDMADevicePlugin):
"""DevicePlugin for Option HSO devices"""
custom = OptionHSOWCDMACustomizer()
dialer = 'hso'
ipmethod = consts.MM_IP_METHOD_STATIC
def __init__(self):
super(OptionHSOWCDMADevicePlugin, self).__init__()
wader-0.5.13/core/hardware/qualcomm.py 0000664 0000000 0000000 00000007734 12576466102 0017643 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2010 Vodafone España, S.A.
# Author: Andrew Bird
#
# 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.
"""Common stuff for all Qualcomm's cards"""
from twisted.internet import defer, reactor
from twisted.internet.task import deferLater
from wader.common import consts
from core.command import get_cmd_dict_copy
from core.hardware.base import WCDMACustomizer
from core.middleware import WCDMAWrapper
from core.plugin import DevicePlugin
from core.sim import SIMBaseClass
# Gobi 2000 notes:
# Unfortunately there is only one tty port available so it has to be
# shared between data and status functions.
# There also doesn't seem to be any way of specifying the bearer
# preference for 3G only, 3G preferred, or 2G only.
QUALCOMM_CMD_DICT = get_cmd_dict_copy()
class QualcommSIMClass(SIMBaseClass):
"""Qualcomm SIM Class"""
def __init__(self, sconn):
super(QualcommSIMClass, self).__init__(sconn)
def initialize(self, set_encoding=True):
def init_callback(size):
# make sure we are in most promiscuous mode before registration
self.sconn.set_network_mode(consts.MM_NETWORK_MODE_ANY)
# set SMS storage default
self.sconn.send_at('AT+CPMS="SM","SM","SM"')
return(size)
d = super(QualcommSIMClass, self).initialize(set_encoding)
d.addCallback(init_callback)
return d
class QualcommWCDMAWrapper(WCDMAWrapper):
"""Wrapper for all Qualcomm cards"""
def enable_radio(self, enable):
d = self.get_radio_status()
def get_radio_status_cb(status):
if status in [0, 4] and enable:
self.send_at('AT+CFUN=1')
# delay here to give the device chance to wake up
return deferLater(reactor, 2, lambda: None)
elif status == 1 and not enable:
return self.send_at('AT+CFUN=4')
d.addCallback(get_radio_status_cb)
return d
def get_band(self):
"""Returns the current used band"""
return defer.succeed(consts.MM_NETWORK_BAND_ANY)
def set_band(self, band):
"""Sets the band to ``band``"""
if band == consts.MM_NETWORK_BAND_ANY:
return defer.succeed('OK')
else:
raise KeyError("Unsupported band %d" % band)
def get_network_mode(self):
"""Returns the current network mode"""
return defer.succeed(consts.MM_NETWORK_MODE_ANY)
def set_network_mode(self, mode):
"""Sets the network mode to ``mode``"""
if mode == consts.MM_NETWORK_MODE_ANY:
return defer.succeed('OK')
else:
raise KeyError("Unknown network mode %d" % mode)
def set_allowed_mode(self, mode):
"""Sets the allowed mode to ``mode``"""
if mode == consts.MM_ALLOWED_MODE_ANY:
self.device.set_property(consts.NET_INTFACE, "AllowedMode", mode)
return defer.succeed('OK')
else:
raise KeyError("Unknown allowed mode %d" % mode)
class QualcommWCDMACustomizer(WCDMACustomizer):
"""WCDMA customizer for Qualcomm devices"""
cmd_dict = QUALCOMM_CMD_DICT
wrapper_klass = QualcommWCDMAWrapper
class QualcommWCDMADevicePlugin(DevicePlugin):
"""WCDMA device plugin for Qualcomm devices"""
sim_klass = QualcommSIMClass
custom = QualcommWCDMACustomizer()
wader-0.5.13/core/hardware/sierra.py 0000664 0000000 0000000 00000010741 12576466102 0017302 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2006-2008 Vodafone España, S.A.
# Copyright (C) 2008-2009 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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.
"""Common stuff for all SierraWireless cards"""
import re
from twisted.internet import defer
from wader.common import consts
from core.command import get_cmd_dict_copy, build_cmd_dict
from core.hardware.base import WCDMACustomizer
from core.middleware import WCDMAWrapper
from core.plugin import DevicePlugin
from wader.common.utils import revert_dict
SIERRA_ALLOWED_DICT = {
consts.MM_ALLOWED_MODE_ANY: '00',
consts.MM_ALLOWED_MODE_3G_ONLY: '01',
consts.MM_ALLOWED_MODE_2G_ONLY: '02',
consts.MM_ALLOWED_MODE_3G_PREFERRED: '03',
consts.MM_ALLOWED_MODE_2G_PREFERRED: '04',
}
SIERRA_MODE_DICT = {
consts.MM_NETWORK_MODE_ANY: '00',
consts.MM_NETWORK_MODE_3G_ONLY: '01',
consts.MM_NETWORK_MODE_2G_ONLY: '02',
consts.MM_NETWORK_MODE_3G_PREFERRED: '03',
consts.MM_NETWORK_MODE_2G_PREFERRED: '04',
}
SIERRA_BAND_DICT = {
consts.MM_NETWORK_BAND_EGSM: '03', # EGSM (900MHz)
consts.MM_NETWORK_BAND_DCS: '03', # DCS (1800MHz)
consts.MM_NETWORK_BAND_PCS: '04', # PCS (1900MHz)
consts.MM_NETWORK_BAND_G850: '04', # GSM (850 MHz)
consts.MM_NETWORK_BAND_U2100: '02', # WCDMA 2100Mhz (Class I)
consts.MM_NETWORK_BAND_U800: '02', # WCDMA 3GPP UMTS800 (Class VI)
consts.MM_NETWORK_BAND_ANY: '00', # any band
}
SIERRA_CMD_DICT = get_cmd_dict_copy()
SIERRA_CMD_DICT['get_band'] = build_cmd_dict(re.compile(
"\r\n\!BAND:\s?(?P\d+)"))
SIERRA_CMD_DICT['get_network_mode'] = build_cmd_dict(re.compile(
"\r\n\!SELRAT:\s?(?P\d+)"))
class SierraWrapper(WCDMAWrapper):
"""Wrapper for all Sierra cards"""
def get_band(self):
"""Returns the current used band"""
def get_band_cb(resp):
band = resp[0].group('band')
return revert_dict(self.custom.band_dict)[band]
return self.send_at("AT!BAND?", name='get_band',
callback=get_band_cb)
def get_network_mode(self):
"""Returns the current used network mode"""
def get_network_mode_cb(resp):
mode = resp[0].group('mode')
return revert_dict(self.custom.conn_dict)[mode]
return self.send_at("AT!SELRAT?", name='get_network_mode',
callback=get_network_mode_cb)
def set_band(self, band):
"""Sets the band to ``band``"""
if band not in self.custom.band_dict:
raise KeyError("Unknown band %d" % band)
return self.send_at("AT!BAND=%s" % self.custom.band_dict[band])
def set_allowed_mode(self, mode):
"""Sets the allowed mode to ``mode``"""
if mode not in self.custom.allowed_dict:
raise KeyError("Unknown mode %d" % mode)
def set_allowed_mode_cb(ign=None):
self.device.set_property(consts.NET_INTFACE, "AllowedMode", mode)
return ign
return self.send_at("AT!SELRAT=%s" % self.custom.allowed_dict[mode],
callback=set_allowed_mode_cb)
def set_network_mode(self, mode):
"""Sets the network mode to ``mode``"""
if mode not in self.custom.conn_dict:
raise KeyError("Unknown mode %d" % mode)
return self.send_at("AT!SELRAT=%s" % self.custom.conn_dict[mode])
class SierraWirelessWCDMACustomizer(WCDMACustomizer):
"""WCDMA customizer for sierra wireless cards"""
async_regexp = None
allowed_dict = SIERRA_ALLOWED_DICT
band_dict = SIERRA_BAND_DICT
conn_dict = SIERRA_MODE_DICT
cmd_dict = SIERRA_CMD_DICT
wrapper_klass = SierraWrapper
class SierraWCDMADevicePlugin(DevicePlugin):
"""WCDMA device plugin for sierra wireless cards"""
custom = SierraWirelessWCDMACustomizer()
wader-0.5.13/core/hardware/sonyericsson.py 0000664 0000000 0000000 00000002405 12576466102 0020551 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2006-2008 Vodafone España, S.A.
# Copyright (C) 2008-2009 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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.
"""Common stuff for all SonyEricsson's cards"""
from core.hardware.base import WCDMACustomizer
SONYERICSSON_ALLOWED_DICT = {}
SONYERICSSON_CONN_DICT = {}
SONYERICSSON_BAND_DICT = {}
class SonyEricssonCustomizer(WCDMACustomizer):
"""WCDMA customizer for sonny ericsson devices"""
async_regexp = None
allowed_dict = SONYERICSSON_ALLOWED_DICT
conn_dict = SONYERICSSON_CONN_DICT
band_dict = SONYERICSSON_BAND_DICT
wader-0.5.13/core/hardware/zte.py 0000664 0000000 0000000 00000017410 12576466102 0016617 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2006-2007 Vodafone España, S.A.
# Copyright (C) 2008-2009 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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.
"""Common stuff for all zte's cards"""
import re
from twisted.internet import defer
from wader.common import consts
from core.command import get_cmd_dict_copy, build_cmd_dict
from core.hardware.base import WCDMACustomizer
from core.middleware import WCDMAWrapper
from core.plugin import DevicePlugin
from core.sim import SIMBaseClass
from wader.common.utils import revert_dict
import wader.common.signals as S
ZTE_ALLOWED_DICT = {
consts.MM_ALLOWED_MODE_ANY: (0, 0),
consts.MM_ALLOWED_MODE_2G_ONLY: (1, 0),
consts.MM_ALLOWED_MODE_3G_ONLY: (2, 0),
consts.MM_ALLOWED_MODE_2G_PREFERRED: (0, 1),
consts.MM_ALLOWED_MODE_3G_PREFERRED: (0, 2),
}
ZTE_MODE_DICT = {
consts.MM_NETWORK_MODE_ANY: (0, 0),
consts.MM_NETWORK_MODE_2G_ONLY: (1, 0),
consts.MM_NETWORK_MODE_3G_ONLY: (2, 0),
consts.MM_NETWORK_MODE_2G_PREFERRED: (0, 1),
consts.MM_NETWORK_MODE_3G_PREFERRED: (0, 2),
}
ZTE_BAND_DICT = {
consts.MM_NETWORK_BAND_ANY: 0, # any band
(consts.MM_NETWORK_BAND_U850 |
consts.MM_NETWORK_BAND_EGSM |
consts.MM_NETWORK_BAND_DCS): 1,
(consts.MM_NETWORK_BAND_U2100 |
consts.MM_NETWORK_BAND_EGSM |
consts.MM_NETWORK_BAND_DCS): 2, # Europe
(consts.MM_NETWORK_BAND_U850 |
consts.MM_NETWORK_BAND_U2100 |
consts.MM_NETWORK_BAND_EGSM |
consts.MM_NETWORK_BAND_DCS): 3,
(consts.MM_NETWORK_BAND_U850 |
consts.MM_NETWORK_BAND_U1900 |
consts.MM_NETWORK_BAND_G850 |
consts.MM_NETWORK_BAND_PCS): 4,
}
# AT+ZBANDI=0 : Automatic (Auto) - Default
# AT+ZBANDI=1 : UMTS 850 + GSM 900/1800
# AT+ZBANDI=2 : UMTS 2100 + GSM 900/1800 (Europe)
# AT+ZBANDI=3 : UMTS 850/2100 + GSM 900/1800
# AT+ZBANDI=4 : UMTS 850/1900 + GSM 850/1900
ZTE_CMD_DICT = get_cmd_dict_copy()
ZTE_CMD_DICT['get_band'] = build_cmd_dict(re.compile(r"""
\r\n
\+ZBANDI:\s?
(?P\d)
\r\n
""", re.VERBOSE))
ZTE_CMD_DICT['get_network_mode'] = build_cmd_dict(re.compile(r"""
\r\n
\+ZSNT:\s
(?P\d+),
(?P\d+),
(?P\d+)
\r\n
""", re.VERBOSE))
def zte_new_conn_mode(what, device):
zpasr = re.search(r'"(?P.*?)"(?:,"(?P.*?)")?', what)
mode = zpasr.group('mode')
modes = {
"GSM": consts.MM_NETWORK_MODE_GPRS,
"GPRS": consts.MM_NETWORK_MODE_GPRS,
"EDGE": consts.MM_NETWORK_MODE_EDGE,
"UMTS": consts.MM_NETWORK_MODE_UMTS,
"HSDPA": consts.MM_NETWORK_MODE_HSDPA,
"HSUPA": consts.MM_NETWORK_MODE_HSUPA,
}
# "No Service", "Limited Service", non-match
return modes.get(mode, consts.MM_NETWORK_MODE_UNKNOWN)
class ZTEWrapper(WCDMAWrapper):
"""Wrapper for all ZTE cards"""
def get_band(self):
"""Returns the current used band"""
if not len(self.custom.band_dict):
return defer.succeed(consts.MM_NETWORK_BAND_ANY)
def get_band_cb(resp):
band = int(resp[0].group('band'))
return revert_dict(self.custom.band_dict)[band]
return self.send_at("AT+ZBANDI?", name='get_band',
callback=get_band_cb)
def get_network_mode(self):
"""Returns the current network mode preference"""
def get_network_mode_cb(resp):
only = int(resp[0].group('only'))
order = int(resp[0].group('order'))
return revert_dict(self.custom.conn_dict)[(only, order)]
return self.send_at("AT+ZSNT?", name='get_network_mode',
callback=get_network_mode_cb)
def set_band(self, band):
"""Sets the band to ``band``"""
if not len(self.custom.band_dict):
if band == consts.MM_NETWORK_BAND_ANY:
return defer.succeed('')
else:
raise KeyError("Unsupported band %d" % band)
for key, value in self.custom.band_dict.items():
if band & key:
return self.send_at("AT+ZBANDI=%d" % value)
raise KeyError("Unsupported band %d" % band)
def set_allowed_mode(self, mode):
"""Sets the allowed mode to ``mode``"""
if mode not in self.custom.allowed_dict:
raise KeyError("Mode %s not found" % mode)
def set_allowed_mode_cb(ign=None):
self.device.set_property(consts.NET_INTFACE, "AllowedMode", mode)
return ign
return self.send_at("AT+ZSNT=%d,0,%d" % self.custom.allowed_dict[mode],
callback=set_allowed_mode_cb)
def set_network_mode(self, mode):
"""Sets the network mode to ``mode``"""
if mode not in self.custom.conn_dict:
raise KeyError("Mode %s not found" % mode)
return self.send_at("AT+ZSNT=%d,0,%d" % self.custom.conn_dict[mode])
class ZTEWCDMACustomizer(WCDMACustomizer):
"""WCDMA customizer for ZTE devices"""
async_regexp = re.compile("""
\r\n
(?P\+Z[A-Z]{3,}):\s*(?P.*)
\r\n""", re.VERBOSE)
allowed_dict = ZTE_ALLOWED_DICT
band_dict = {}
conn_dict = ZTE_MODE_DICT
cmd_dict = ZTE_CMD_DICT
device_capabilities = [S.SIG_NETWORK_MODE,
S.SIG_SMS_NOTIFY_ONLINE]
signal_translations = {
'+ZDONR': (None, None),
'+ZPASR': (S.SIG_NETWORK_MODE, zte_new_conn_mode),
'+ZUSIMR': (None, None),
'+ZPSTM': (None, None),
'+ZEND': (None, None),
}
wrapper_klass = ZTEWrapper
class ZTEWCDMASIMClass(SIMBaseClass):
"""WCDMA SIM class for ZTE devices"""
def __init__(self, sconn):
super(ZTEWCDMASIMClass, self).__init__(sconn)
def setup_sms(self):
# Select SIM storage
self.sconn.send_at('AT+CPMS="SM","SM","SM"')
# Notification when a SMS arrives...
self.sconn.set_sms_indication(2, 1, 0, 2, 0)
# XXX: We have to set +CDSI indication as original ZTE devices don't
# support +CDS mode. At some point we will have to implement
# processing of +CDSI notifications in core
# Sample notification
# '+CDSI: "SR",50'
#
# Sample retrieval
# AT+CPMS="SR";+CMGR=50;+CMGD=50;+CPMS="SM"
# +CPMS: 1,100,1,15,1,15
#
# +CMGR: ,,25
# 079144775810065006FD0C91449771624701117013908522401170139085624000
#
# +CPMS: 0,100,1,15,1,15
#
# OK
# set PDU mode
self.sconn.set_sms_format(0)
class ZTEWCDMADevicePlugin(DevicePlugin):
"""WCDMA device plugin for ZTE devices"""
sim_klass = ZTEWCDMASIMClass
custom = ZTEWCDMACustomizer()
wader-0.5.13/core/mal.py 0000664 0000000 0000000 00000040517 12576466102 0014775 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2008-2010 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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.
"""Message Assembly Layer for Wader"""
from time import mktime
from twisted.internet import reactor
from twisted.internet.defer import succeed, gatherResults, Deferred
from twisted.python import log
from messaging.sms import SmsDeliver
from messaging.sms.wap import (extract_push_notification,
is_a_wap_push_notification, is_mms_notification)
from wader.common.aterrors import (CMSError314, SimBusy, SimNotStarted,
SimFailure)
from wader.common.encoding import pack_dbus_safe_string
from wader.common.signals import SIG_MMS, SIG_SMS, SIG_SMS_COMP, SIG_SMS_DELV
from wader.common.sms import Message
from core.mms import dbus_data_to_mms
STO_INBOX, STO_DRAFTS, STO_SENT = 1, 2, 3
# XXX: What should this threshold be?
SMS_DATE_THRESHOLD = 5
MAL_RETRIES = 3
MAL_RETRY_TIMEOUT = 3
def debug(s):
# Change this to remove debugging
if 1:
print s
def should_fragment_be_assembled(sms, fragment):
"""Returns True if ``fragment`` can be assembled to ``sms``"""
if sms.completed:
# SMS is completed, no way to assemble it
return False
if sms.ref != fragment.ref:
# different sms id
return False
if sms.datetime is not None and fragment.datetime is not None:
# if datetime is present convert it to unix time
time1 = mktime(sms.datetime.timetuple())
time2 = mktime(fragment.datetime.timetuple())
if abs(time1 - time2) > SMS_DATE_THRESHOLD:
return False
if sms.cnt != fragment.cnt:
# number of parts
return False
if sms.number != fragment.number:
# different sender
return False
if sms.csca != fragment.csca:
# different SMSC
return False
debug("MAL: Assembling fragment %s with sms %s" % (fragment, sms))
return True
class CacheIncoherenceError(Exception):
"""Raised upon a cache incoherence error"""
class NotificationContainer(object):
"""
I am a WAP push notification container
I keep a list with all the wap_push notifications
and provide some operations on it
"""
def __init__(self, tx_id=None):
self.notifications = []
self.tx_id = tx_id
def add_notification(self, wap_push, notification):
self.notifications.append((wap_push, notification))
def get_last_notification(self):
"""Returns the last received notification"""
# comp function must return an int
ret = sorted(self.notifications,
lambda _, n: int(mktime(n[0].datetime.timetuple())))
# return the last element
return ret[-1][1]
class MessageAssemblyLayer(object):
"""I am a transparent layer to perform operations on concatenated SMS"""
def __init__(self, wrappee):
self.wrappee = wrappee
self.last_sms_index = 0
self.last_wap_index = 0
self.sms_map = {}
self.wap_map = {}
self.sms_pending = []
self.cached = False
def initialize(self, obj=None):
debug("MAL::initialize obj: %s" % obj)
if obj is not None:
self.wrappee = obj
# revert to initial state
self.last_sms_index = self.last_wap_index = 0
self.sms_map = {}
self.sms_pending = []
self.cached = False
# populate sms cache
return self._do_initialize()
def _do_initialize(self):
# init counter
self.wrappee.state_dict['mal_init_retries'] = 0
deferred = Deferred()
def list_sms(auxdef):
def sim_busy_eb(failure):
failure.trap(SimBusy, SimNotStarted, CMSError314)
self.wrappee.state_dict['mal_init_retries'] += 1
if self.wrappee.state_dict['mal_init_retries'] > MAL_RETRIES:
raise SimFailure("Could not initialize MAL")
reactor.callLater(MAL_RETRY_TIMEOUT, list_sms, auxdef)
d = self.list_sms()
d.addErrback(sim_busy_eb)
d.chainDeferred(auxdef)
return auxdef
return list_sms(deferred)
def _do_add_sms(self, sms, indexes=None):
"""
Adds ``sms`` to the cache using ``indexes`` if defined
It returns the logical index where it was stored
"""
#debug("MAL::_do_add_sms sms: %s indexes: %s" % (sms, indexes))
# save the real index if indexes is None
if indexes:
map(sms.real_indexes.add, indexes)
else:
sms.real_indexes.add(sms.index)
#debug("MAL::_do_add_sms sms.real_indexes %s" % sms.real_indexes)
# assign a new logical index
self.last_sms_index += 1
sms.index = self.last_sms_index
# reference the sms by this logical index
self.sms_map[self.last_sms_index] = sms
return self.last_sms_index
def _add_sms(self, sms, emit=False):
"""
Adds ``sms`` to the cache
It returns the logical index where it was stored
"""
debug("MAL::_add_sms: %s" % sms)
if not sms.cnt:
index = self._do_add_sms(sms)
debug("MAL::_add_sms single part SMS added with "
"logical index: %d" % index)
# being a single part sms, completed == True
if emit:
for signal in [SIG_SMS, SIG_SMS_COMP]:
self.wrappee.emit_signal(signal, index, True)
return index
else:
for index, value in self.sms_map.iteritems():
if should_fragment_be_assembled(value, sms):
# append the sms and emit the different signals
completed = self.sms_map[index].append_sms(sms)
debug("MAL::_add_sms multi part SMS with logical "
"index %d, completed %s" % (index, completed))
# check if we have just assembled a WAP push notification
if completed:
notification = self.sms_map[index]
if self._is_a_wap_push_notification(notification):
if self._process_wap_push_notification(index,
emit):
debug("MAL::_add_sms MMS processed OK")
# There's no need to return an index here as we
# have been called by gen_cache and MMS has a
# different index scheme than SMS.
return
# XXX: Must have been a non-MMS notification WAP
# push, there's nothing we can do with those
# presently. Leave them to be displayed as
# SMS, although poorly, so that they may be
# removed by the user if desired.
if emit:
# only emit signals in runtime, not startup
self.wrappee.emit_signal(SIG_SMS, index, completed)
if completed:
self.wrappee.emit_signal(SIG_SMS_COMP, index,
completed)
# return sms logical index
return index
# this is the first fragment of this multipart sms, add it
# to cache, emit signal and wait for the rest of fragments
# to arrive. It returns the logical index where was stored
index = self._do_add_sms(sms)
if emit:
self.wrappee.emit_signal(SIG_SMS, index, False)
debug("MAL::_add_sms first part of a multi part SMS added with "
"logical index %d" % index)
return index
def _after_ack_delete_notifications(self, _, index):
try:
container = self.wap_map.pop(index)
except KeyError:
debug("MessageAssemblyLayer::_after_ack_delete_notifications"
" NotificationContainer %d does not exist" % index)
return
indexes = []
for wap_push, _ in container.notifications:
indexes.extend(wap_push.real_indexes)
ret = map(self.wrappee.do_delete_sms, indexes)
return gatherResults(ret)
def acknowledge_mms(self, index, extra_info):
d = self.wrappee.do_acknowledge_mms(index, extra_info)
d.addCallback(self._after_ack_delete_notifications, index)
return d
def delete_sms(self, index):
"""Deletes sms identified by ``index``"""
debug("MAL::delete_sms: %d" % index)
if index in self.sms_map:
sms = self.sms_map.pop(index)
ret = map(self.wrappee.do_delete_sms, sms.real_indexes)
debug("MAL::delete_sms deleting %s" % sms.real_indexes)
return gatherResults(ret)
error = "SMS with logical index %d does not exist"
raise CacheIncoherenceError(error % index)
def download_mms(self, index, extra_info):
container = self.wap_map[index]
d = self.wrappee.do_download_mms(container.get_last_notification(),
extra_info)
return d
def get_sms(self, index):
"""Returns the sms identified by ``index``"""
if index in self.sms_map:
return succeed(self.sms_map[index])
error = "SMS with logical index %d does not exist"
raise CacheIncoherenceError(error % index)
def list_available_mms_notifications(self):
"""Returns all the lingering sms wap push notifications"""
ret = []
for index, container in self.wap_map.items():
notification = container.get_last_notification()
ret.append((index, self._clean_headers(notification)))
return succeed(ret)
def _list_sms(self):
ret = []
for sms in self.sms_map.values():
if sms.fmt != 0x04:
ret.append(sms.to_dict())
else:
# WAP Pushes are binary and may not be valid DBus strings
_sms = sms.to_dict()
_sms['text'] = pack_dbus_safe_string(_sms['text'])
ret.append(_sms)
return ret
def list_sms(self):
"""Returns all the sms"""
debug("MAL::list_sms")
def gen_cache(messages):
debug("MAL::list_sms::gen_cache")
self.sms_map = {}
for sms in messages:
self._add_sms(sms)
self.cached = True
return self._list_sms()
if self.cached:
debug("MAL::list_sms::cached path")
return succeed(self._list_sms())
d = self.wrappee.do_list_sms()
d.addCallback(gen_cache)
return d
def list_sms_raw(self):
"""Returns all the raw sms, not assembled via the mal"""
debug("MAL::list_sms_raw")
return self.wrappee.do_list_sms()
def send_mms(self, mms, extra_info):
debug("MAL::send_mms: %s" % mms)
d = self.wrappee.do_send_mms(dbus_data_to_mms(mms), extra_info)
return d
def send_sms(self, sms):
debug("MAL::send_sms: %s" % sms)
if not sms.status_request:
return self.wrappee.do_send_sms(sms)
d = self.wrappee.do_send_sms(sms)
d.addCallback(self._save_sms_reference, sms)
return d
def send_sms_from_storage(self, index):
debug("MAL::send_sms_from_storage: %d" % index)
if index in self.sms_map:
sms = self.sms_map.pop(index)
indexes = sorted(sms.real_indexes)
debug("MAL::send_sms_from_storage sending %s" % indexes)
ret = map(self.wrappee.do_send_sms_from_storage, indexes)
return gatherResults(ret)
error = "SMS with logical index %d does not exist"
raise CacheIncoherenceError(error % index)
def save_sms(self, sms):
"""Saves ``sms`` in the cache memorizing the resulting indexes"""
debug("MAL::save_sms: %s" % sms)
d = self.wrappee.do_save_sms(sms)
d.addCallback(lambda indexes: self._do_add_sms(sms, indexes))
d.addCallback(lambda logical_index: [logical_index])
return d
def _save_sms_reference(self, indexes, sms):
sms.status_references.extend(indexes)
sms.status_reference = indexes[0]
self.sms_pending.append(sms)
return [sms.status_reference]
def on_sms_delivery_report(self, pdu):
"""Executed when a SMS delivery report is received"""
data = SmsDeliver(pdu).data
sms = Message.from_dict(data)
assert sms.is_status_report(), "SMS IS NOT STATUS REPORT"
# XXX: O(N) here
for _sms in self.sms_pending:
if sms.ref in _sms.status_references:
# one confirmation received
_sms.status_references.remove(sms.ref)
# no more status references? Then we are done, remove it from
# the status_references list and emit signal
if not _sms.status_references:
self.sms_pending.remove(_sms)
return self.wrappee.emit_signal(SIG_SMS_DELV,
_sms.status_reference)
break
else:
log.err("Received status report with "
"unknown reference: %d" % sms.ref)
def on_sms_notification(self, index):
"""Executed when a SMS notification is received"""
debug("MAL::on_sms_notification: %d" % index)
d = self.wrappee.do_get_sms(index)
d.addCallback(self._add_sms, emit=True)
return d
def _is_a_wap_push_notification(self, sms):
"""Returns True if ``sms`` is a WAP push notification"""
if sms.fmt != 0x04:
return False
return is_a_wap_push_notification(sms.text)
def _process_wap_push_notification(self, index, emit):
"""
Processes WAP push notification identified by ``index``
If ``emit`` is True, it will emit a MMSReceived signal
if this the first time we see this notification.
"""
notification = extract_push_notification(self.sms_map[index].text)
if not is_mms_notification(notification):
debug("MAL::_process_wap_push_notification: is not for MMS")
return False
wap_push = self.sms_map.pop(index)
index = None
new = False
_from = notification.headers['From']
tx_id = notification.headers['Transaction-Id']
for i, container in self.wap_map.items():
if container.tx_id != tx_id:
continue
noti = container.get_last_notification()
if _from == noti.headers['From']:
index = i
break
else:
# this is the first time we see this tx_id
index = self.last_wap_index
self.last_wap_index += 1
new = True
container = self.wap_map.get(index, NotificationContainer(tx_id))
container.add_notification(wap_push, notification)
self.wap_map[index] = container
if emit and new:
# emit the signal if this is the first time we
# see this notification
notification = container.get_last_notification()
headers = self._clean_headers(notification)
self.wrappee.emit_signal(SIG_MMS, index, headers)
return True
def _clean_headers(self, notification):
"""Clean ``headers`` so its safe to send the dict via DBus"""
hdrs = notification.headers.copy()
hdrs['Content-Type'] = notification.content_type
return hdrs
wader-0.5.13/core/middleware.py 0000664 0000000 0000000 00000153651 12576466102 0016345 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2006-2011 Vodafone España, S.A.
# Copyright (C) 2008-2009 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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.
"""
Wrapper around :class:`~core.protocol.WCDMAProtocol`
It basically provides error control and more high-level operations.
N-tier folks can see this as a Business Logic class.
"""
from collections import deque
import dbus
import serial
from gobject import timeout_add_seconds, source_remove
from math import floor
from time import time
from twisted.python import log
from twisted.internet import defer, reactor, task
import wader.common.aterrors as E
from wader.common.consts import (WADER_SERVICE, MDM_INTFACE, CRD_INTFACE,
NET_INTFACE, USD_INTFACE, MM_NETWORK_BAND_ANY,
MM_NETWORK_MODE_ANY, MM_NETWORK_MODE_LAST,
MM_MODEM_STATE_DISABLED,
MM_MODEM_STATE_ENABLING,
MM_MODEM_STATE_ENABLED,
MM_MODEM_STATE_SEARCHING,
MM_MODEM_STATE_REGISTERED,
MM_MODEM_STATE_DISCONNECTING,
MM_MODEM_STATE_CONNECTING,
MM_MODEM_STATE_CONNECTED,
MM_GSM_ACCESS_TECH_GSM_COMPAT,
MM_GSM_ACCESS_TECH_GPRS,
MM_GSM_ACCESS_TECH_EDGE,
MM_GSM_ACCESS_TECH_UMTS,
MM_GSM_ACCESS_TECH_HSDPA,
MM_GSM_ACCESS_TECH_HSUPA,
MM_GSM_ACCESS_TECH_HSPA,
MM_GSM_ACCESS_TECH_HSPA_PLUS,
MM_GSM_ACCESS_TECH_LTE,
MM_IP_METHOD_PPP)
from wader.common.encoding import (from_ucs2, from_u, unpack_ucs2_bytes,
pack_ucs2_bytes, check_if_ucs2, LATIN_EX_B,
from_8bit_in_gsm_or_ts31101)
import wader.common.exceptions as ex
from wader.common.signals import SIG_CREG
from wader.common.sms import Message
from wader.common.utils import rssi_to_percentage
from core.contact import Contact
from core.mal import MessageAssemblyLayer
from core.mms import (send_m_send_req, send_m_notifyresp_ind,
get_payload)
from core.oal import get_os_object
from core.protocol import WCDMAProtocol
from core.sim import (COM_READ_BINARY, EF_AD, EF_SPN, EF_ICCID, SW_OK,
RETRY_ATTEMPTS, RETRY_TIMEOUT)
class WCDMAWrapper(WCDMAProtocol):
"""
I am a wrapper around :class:`~core.protocol.WCDMAProtocol`
Its main objective is to provide some error control on some operations
and a cleaner API to deal with its results.
"""
def __init__(self, device):
super(WCDMAWrapper, self).__init__(device)
# unfortunately some methods require me to save some state
# between runs. This dict contains 'em.
self.state_dict = {}
# message assembly layer (initted on do_enable_device)
self.mal = MessageAssemblyLayer(self)
self.signal_matchs = []
# timeout_add_seconds task ID
self.__time = 0
self.__rx_bytes = 0
self.__tx_bytes = 0
self.stats_id = None
# iface name
self.iface = None
self.apn_range = None
self.caches = {
'signal': (0, 0),
'registration': (0, (0, '', '')),
}
def maybecached(self, name, fn, cblist):
if self.caches[name][0] >= time(): # cache hasn't expired
log.msg("get '%s' (cached path)" % name, system='CACHE')
d = defer.succeed(self.caches[name][1])
else:
log.msg("get '%s' (noncached path)" % name, system='CACHE')
d = fn()
for cb in cblist:
d.addCallback(cb)
d.addCallback(self.updatecache, name)
return d
def updatecache(self, value, name):
CACHETIME = 5
self.caches[name] = (time() + CACHETIME, value)
return value
def emit_network_mode(self, value):
"""
Method to validate, convert ``value`` to dbus UInt32 and emit
"""
if value < 0 or value > MM_NETWORK_MODE_LAST:
return
self.device.exporter.NetworkMode(dbus.UInt32(value))
def emit_rssi(self, value):
"""
Method to validate, convert ``value`` to dbus UInt32 and emit
"""
if value < 0 or value > 100:
return
self.device.exporter.SignalQuality(dbus.UInt32(value))
def connect_to_signals(self):
bus = dbus.SystemBus()
device = bus.get_object(WADER_SERVICE, self.device.sconn.device.opath)
sm = device.connect_to_signal(SIG_CREG, self.on_creg_cb)
self.signal_matchs.append(sm)
def clean_signals(self):
while self.signal_matchs:
sm = self.signal_matchs.pop()
sm.remove()
def __str__(self):
return self.device.__remote_name__
def acknowledge_mms(self, index, extra_info):
"""
Acknowledges the Mms identified by ``index`` using ``extra_info``
"""
return self.mal.acknowledge_mms(index, extra_info)
def do_acknowledge_mms(self, index, extra_info):
if 'wap2' not in extra_info:
raise ValueError("Only WAP2.0 is supported at the moment")
if 'mmsc' not in extra_info:
raise ValueError("No mmsc key in %s" % extra_info)
try:
notification = self.mal.wap_map[index].get_last_notification()
except IndexError:
raise E.ExpiredNotification("Could not find "
"notification %d" % index)
d = send_m_notifyresp_ind(extra_info,
notification.headers['Transaction-Id'])
return d
def add_contact(self, contact):
"""
Adds ``contact`` to the SIM and returns the index where was stored
"""
ucs2 = 'UCS2' in self.device.sim.charset
name = pack_ucs2_bytes(contact.name) if ucs2 else from_u(contact.name)
# common arguments for both operations (name and number)
args = [name, from_u(contact.number)]
if contact.index:
# contact.index is set, user probably wants to overwrite an
# existing contact
args.append(contact.index)
d = super(WCDMAWrapper, self).add_contact(*args)
d.addCallback(lambda _: contact.index)
return d
def get_next_id_cb(index):
args.append(index)
d2 = super(WCDMAWrapper, self).add_contact(*args)
# now we just fake add_contact's response and we return the index
d2.addCallback(lambda _: index)
return d2
# contact.index is not set, this means that we need to obtain the
# first free slot on the phonebook and then add the contact
d = self._get_next_contact_id()
d.addCallback(get_next_id_cb)
return d
def cancel_ussd(self):
"""Cancels an ongoing USSD session"""
d = super(WCDMAWrapper, self).cancel_ussd()
def set_idle(result):
self.device.set_property(USD_INTFACE, 'State', 'idle')
return result[0].group('resp')
d.addCallback(set_idle)
return d
def change_pin(self, oldpin, newpin):
"""Changes PIN from ``oldpin`` to ``newpin``"""
d = super(WCDMAWrapper, self).change_pin(oldpin, newpin)
d.addCallback(lambda result: result[0].group('resp'))
return d
def check_pin(self):
"""
Returns the SIM's auth state
:raise SimPinRequired: Raised if SIM PIN is required
:raise SimPukRequired: Raised if SIM PUK is required
:raise SimPuk2Required: Raised if SIM PUK2 is required
"""
if self.device.quirks.get('needs_enable_before_pin_check', False):
# Note: this device needs to be enabled before pin can be checked
d = self.enable_radio(True)
d.addCallback(lambda x: super(WCDMAWrapper, self).check_pin())
else:
d = super(WCDMAWrapper, self).check_pin()
def process_result(resp):
result = resp[0].group('resp')
if result == 'READY':
return result
elif result == 'SIM PIN':
raise E.SimPinRequired()
elif result == 'SIM PUK':
raise E.SimPukRequired()
elif result == 'SIM PUK2':
raise E.SimPuk2Required()
else:
log.err("unknown authentication state %s" % result)
d.addCallback(process_result)
return d
def delete_contact(self, index):
"""Deletes contact at ``index``"""
d = super(WCDMAWrapper, self).delete_contact(index)
d.addCallback(lambda result: result[0].group('resp'))
return d
def delete_sms(self, index):
return self.mal.delete_sms(index)
def do_delete_sms(self, index):
"""Deletes SMS at ``index``"""
d = super(WCDMAWrapper, self).delete_sms(index)
d.addCallback(lambda result: result[0].group('resp'))
return d
def download_mms(self, index, extra_info):
"""Downloads the Mms identified by ``index``"""
return self.mal.download_mms(index, extra_info)
def do_download_mms(self, notification, extra_info):
uri = notification.headers['Content-Location']
return get_payload(uri, extra_info)
def disable_echo(self):
"""Disables echo"""
d = super(WCDMAWrapper, self).disable_echo()
d.addCallback(lambda result: result[0].group('resp'))
return d
def enable_pin(self, pin, enable):
"""
Enables or disables PIN auth with ``pin`` according to ``enable``
"""
def cache_and_return_response(response):
self.device.set_property(CRD_INTFACE, 'PinEnabled', enable)
return response[0].group('resp')
d = super(WCDMAWrapper, self).enable_pin(pin, enable)
d.addCallback(cache_and_return_response)
return d
def enable_echo(self):
"""
Enables echo
Use this with caution as it might leave Wader on an unusable state
"""
d = super(WCDMAWrapper, self).enable_echo()
d.addCallback(lambda result: result[0].group('resp'))
return d
def find_contacts(self, pattern):
"""
Returns all the `Contact` objects whose name matches ``pattern``
:rtype: list
"""
if 'UCS2' in self.device.sim.charset:
pattern = pack_ucs2_bytes(pattern)
d = super(WCDMAWrapper, self).find_contacts(pattern)
d.addCallback(lambda matches: map(self._regexp_to_contact, matches))
return d
def get_apns(self):
"""
Returns all the APNs in the SIM
:rtype: list
"""
d = super(WCDMAWrapper, self).get_apns()
d.addCallback(lambda resp:
[(int(r.group('index')), r.group('apn')) for r in resp])
return d
def get_apn_range(self):
"""
Returns the range of context IDs in the SIM
:rtype: tuple
"""
d = super(WCDMAWrapper, self).get_apn_range()
d.addCallback(lambda r: (int(r.group('lo4')), int(r.group('hi4'))))
return d
def get_band(self):
"""Returns the current band used"""
raise NotImplementedError()
def get_bands(self):
"""
Returns the available bands
:rtype: list
"""
bands = self.custom.band_dict.keys()
if MM_NETWORK_BAND_ANY in bands:
bands.pop(MM_NETWORK_BAND_ANY)
# cast it to UInt32
return defer.succeed(dbus.UInt32(sum(bands)))
def get_card_model(self):
"""Returns the card model"""
d = super(WCDMAWrapper, self).get_card_model()
d.addCallback(lambda response: response[0].group('model'))
return d
def get_card_version(self):
"""Returns the firmware version"""
d = super(WCDMAWrapper, self).get_card_version()
d.addCallback(lambda response: response[0].group('version'))
return d
def get_charset(self):
"""Returns the current charset"""
d = super(WCDMAWrapper, self).get_charset()
d.addCallback(lambda response: response[0].group('lang'))
return d
def get_charsets(self):
"""
Returns the available charsets
:rtype: list
"""
d = super(WCDMAWrapper, self).get_charsets()
d.addCallback(lambda resp: [match.group('lang') for match in resp])
return d
def get_contact(self, index):
"""Returns the contact at ``index``"""
d = super(WCDMAWrapper, self).get_contact(index)
d.addCallback(lambda match: self._regexp_to_contact(match[0]))
return d
def get_hardware_info(self):
"""Returns the manufacturer name, card model and firmware version"""
dlist = [self.get_manufacturer_name(),
self.get_card_model(),
self.get_card_version()]
return defer.gatherResults(dlist)
def sim_access_restricted(self, command, fileid=None, p1=None,
p2=None, p3=None, data=None, pathid=None):
d = super(WCDMAWrapper, self).sim_access_restricted(
command, fileid, p1, p2, p3, data, pathid)
def cb(response):
try:
sw1 = int(response[0].group('sw1'))
except (IndexError, TypeError, ValueError):
sw1 = None
try:
sw2 = int(response[0].group('sw2'))
except (IndexError, TypeError, ValueError):
sw2 = None
if (sw1 not in SW_OK) or (sw1 == 0x90 and sw2 != 0):
# Status error.
raise E.General('Bad Status response for AT+CRSM')
data = response[0].group('response')
return (sw1, sw2, data)
d.addCallback(cb)
return d
def get_iccid(self):
"""Returns ICC identification number"""
d = self.sim_access_restricted(COM_READ_BINARY, EF_ICCID, 0, 0, 10)
def get_iccid_cb(response):
sw1, sw2, data = response
if data is None:
raise E.General('ICCID not available')
if len(data) != 20:
raise E.General('ICCID length not correct')
# Parse BCD F padded string.
result = ''
i = 0
while (i + 1 < len(data)):
msd = data[i]
lsd = data[i + 1]
i += 2
if lsd in ['f', 'F']:
break
result += lsd
if msd in ['f', 'F']:
break
result += msd
return result
d.addCallback(get_iccid_cb)
return d
def get_imei(self):
"""Returns the IMEI"""
d = super(WCDMAWrapper, self).get_imei()
d.addCallback(lambda response: response[0].group('imei'))
return d
def get_imsi(self):
"""Returns the IMSI"""
d = super(WCDMAWrapper, self).get_imsi()
d.addCallback(lambda response: response[0].group('imsi'))
return d
def get_ip4_config(self):
"""Returns the IP4Config info related to IpMethod"""
raise NotImplementedError()
def get_manufacturer_name(self):
"""Returns the manufacturer name"""
d = super(WCDMAWrapper, self).get_manufacturer_name()
d.addCallback(lambda response: response[0].group('name'))
return d
def _get_netreg_info(self, status):
# Ugly but it works. The naive approach with DeferredList won't work
# as the call order is not guaranteed
resp = [status]
def get_netinfo_cb(info):
new = info[1]
cur = self.device.get_property(NET_INTFACE, 'AccessTechnology')
# Don't stamp on the value provided by a richer method
if (new == MM_GSM_ACCESS_TECH_GPRS and
cur == MM_GSM_ACCESS_TECH_EDGE) or \
(new == MM_GSM_ACCESS_TECH_UMTS and
cur in [MM_GSM_ACCESS_TECH_HSDPA,
MM_GSM_ACCESS_TECH_HSUPA,
MM_GSM_ACCESS_TECH_HSPA,
MM_GSM_ACCESS_TECH_HSPA_PLUS]):
self.device.set_property(NET_INTFACE, 'AccessTechnology', cur)
else:
self.device.set_property(NET_INTFACE, 'AccessTechnology', new)
return resp.append(info[0])
def get_netinfo_eb(failure):
failure.trap(E.NoNetwork, ex.LimitedServiceNetworkError)
resp.append('')
d = self.get_network_info('numeric')
d.addCallback(get_netinfo_cb)
d.addErrback(get_netinfo_eb)
d.addCallback(lambda _: self.get_network_info('name'))
d.addCallback(get_netinfo_cb)
d.addErrback(get_netinfo_eb)
d.addCallback(lambda _: tuple(resp))
return d
def _get_netreg_info_emit(self, _reginfo):
"""Convert type and emit RegistrationInfo signal"""
if self.device.status <= MM_MODEM_STATE_REGISTERED:
if _reginfo[0] in [1, 5]:
self.device.set_status(MM_MODEM_STATE_REGISTERED)
else:
self.device.set_status(MM_MODEM_STATE_SEARCHING)
reginfo = (dbus.UInt32(_reginfo[0]), _reginfo[1], _reginfo[2])
self.device.exporter.RegistrationInfo(*reginfo)
return reginfo
def get_netreg_info(self):
"""Get the registration status and the current operator"""
return self.maybecached('registration', self.get_netreg_status,
[lambda info: info[1], self._get_netreg_info,
self._get_netreg_info_emit])
def on_creg_cb(self, status):
"""Callback for +CREG notifications"""
d = defer.succeed(status)
d.addCallback(self._get_netreg_info)
d.addCallback(self._get_netreg_info_emit)
d.addCallback(self.updatecache, 'registration')
return d
def get_netreg_status(self):
"""Returns a tuple with the network registration status"""
d = super(WCDMAWrapper, self).get_netreg_status()
def convert_cb(resp):
# convert them to int
return int(resp[0].group('mode')), int(resp[0].group('status'))
d.addCallback(convert_cb)
return d
def get_network_info(self, _type=None):
"""
Returns the network info (a.k.a AT+COPS?)
The response will be a tuple as (OperatorName, ConnectionType) if
it returns a (None, None) that means that some error occurred while
obtaining the info. The class that requested the info should take
care of insisting before this problem. This method will convert
numeric network IDs to alphanumeric.
"""
d = super(WCDMAWrapper, self).get_network_info(_type)
def get_net_info_cb(netinfo):
"""
Returns a (Networkname, ConnType) tuple
It returns None if there's no info
"""
if not netinfo:
return None
netinfo = netinfo[0]
if netinfo.group('error'):
# this means that we've received a response like
# +COPS: 0 which means that we don't have network temporaly
# we should raise an exception here
raise E.NoNetwork()
# TS 27007 got updated as of 10.4
_map = {
'0': MM_GSM_ACCESS_TECH_GPRS, # strictly GSM
'1': MM_GSM_ACCESS_TECH_GSM_COMPAT,
'2': MM_GSM_ACCESS_TECH_UMTS, # strictly UTRAN
'3': MM_GSM_ACCESS_TECH_EDGE,
'4': MM_GSM_ACCESS_TECH_HSDPA,
'5': MM_GSM_ACCESS_TECH_HSUPA,
'6': MM_GSM_ACCESS_TECH_HSPA,
'7': MM_GSM_ACCESS_TECH_LTE,
}
conn_type = _map.get(netinfo.group('status'))
netname = netinfo.group('netname')
if netname in ['Limited Service',
pack_ucs2_bytes('Limited Service')]:
raise ex.LimitedServiceNetworkError
# netname can be in UCS2, as a string, or as a network id (int)
if check_if_ucs2(netname):
return unpack_ucs2_bytes(netname), conn_type
else:
# now can be either a string or a network id (int)
try:
netname = int(netname)
except ValueError:
# we got a string ID
return netname, conn_type
# if we have arrived here, that means that the network id
# is a five, six or seven digit integer
return str(netname), conn_type
d.addCallback(get_net_info_cb)
return d
def get_network_mode(self):
"""Returns the current network mode"""
raise NotImplementedError()
def get_network_modes(self):
"""Returns the supported network modes"""
modes = self.custom.conn_dict.copy()
if MM_NETWORK_MODE_ANY in modes:
modes.pop(MM_NETWORK_MODE_ANY)
return defer.succeed(sum(modes.keys()))
def get_network_names(self):
"""
Performs a network search
:rtype: list of :class:`NetworkOperator`
"""
d = super(WCDMAWrapper, self).get_network_names()
d.addCallback(lambda resp:
[NetworkOperator(*match.groups()) for match in resp])
return d
def _get_free_contact_ids(self):
"""Returns a deque with the not used contact ids"""
def list_contacts_cb(contacts):
if not contacts:
return deque(range(1, self.device.sim.size))
busy_ids = [contact.index for contact in contacts]
free = set(range(1, self.device.sim.size)) ^ set(busy_ids)
return deque(list(free))
def list_contacts_eb(failure):
failure.trap(E.NotFound, E.General)
return deque(range(1, self.device.sim.size))
d = self.list_contacts()
d.addCallbacks(list_contacts_cb, list_contacts_eb)
return d
def _get_next_contact_id(self):
"""
Returns the next unused contact id
Provides some error control and won't fail if sim.size
is None as the card might be a bit difficult
"""
def do_get_it():
d = self._get_free_contact_ids()
d.addCallback(lambda free: free.popleft())
return d
if self.device.sim.size and self.device.sim.size != 0:
return do_get_it()
deferred = defer.Deferred()
self.state_dict['phonebook_retries'] = 0
def get_it(auxdef=None):
def get_phonebook_size_cb(size):
self.device.sim.size = size
d = do_get_it()
d.chainDeferred(deferred)
def get_phonebook_size_eb(failure):
self.state_dict['phonebook_retries'] += 1
if self.state_dict['phonebook_retries'] > RETRY_ATTEMPTS:
raise RuntimeError("Could not obtain phonebook size")
reactor.callLater(RETRY_TIMEOUT, get_it, auxdef)
d = self.get_phonebook_size()
d.addCallback(get_phonebook_size_cb)
d.addErrback(get_phonebook_size_eb)
return auxdef
return get_it(deferred)
def get_operator_id(self):
"""
Returns the ID of the network operator that issued the SIM card,
formatted as a 5 or 6-digit MCC/MNC code (ex "310410").
:raise General: When MCC+MNC cannot be retrieved.
"""
d = defer.Deferred()
# Another way to handle global variables.
d.imsi = None
d.mnc_length = None
def get_op_id_eb(failure):
log.msg("get_operator_id FAILURE: %s" % failure.value)
failure.raiseException()
d.addErrback(get_op_id_eb)
d_mnc = self.sim_access_restricted(COM_READ_BINARY, EF_AD, 0, 0, 4)
def get_op_id_mnc_digits_cb(response):
sw1, sw2, number = response
if number is None or len(number) < 8:
raise E.General('Bad MNC length response')
number = int(number[6:8], 16)
if number in range(2, 5):
# We got MNC number of digits right.
return number
else:
raise E.General('Incorrect MNC length')
def get_op_id_mnc_digits_eb(failure):
log.msg("get_operator_id mnc_digits FAILURE %s" % failure.value)
failure.raiseException()
d_mnc.addCallback(get_op_id_mnc_digits_cb)
d_mnc.addCallback(d.callback)
d_mnc.addErrback(get_op_id_mnc_digits_eb)
d_mnc.addErrback(d.errback)
def store_mnc_length(mnc_length):
self.mnc_length = mnc_length
d_imsi = self.get_imsi()
d_imsi.addErrback(get_op_id_imsi_eb)
d_imsi.addErrback(d.errback)
return d_imsi
d.addCallback(store_mnc_length)
def get_op_id_imsi_eb(failure):
log.msg("get_operator_id imsi FAILURE %s" % failure.value)
failure.raiseException()
def store_imsi(imsi):
self.imsi = imsi
return None
d.addCallback(store_imsi)
def get_op_id_cb(response):
number = self.mnc_length # An integer.
imsi = self.imsi
if number is None or imsi is None:
raise E.General('Bad MNC length or IMSI')
length = number + 3
if len(imsi) < length:
raise E.General('IMSI length too short')
return imsi[0:length]
d.addCallback(get_op_id_cb)
return d
def get_phonebook_size(self):
"""Returns the phonebook size"""
d = super(WCDMAWrapper, self).get_phonebook_size()
d.addCallback(lambda resp: int(resp[0].group('size')))
return d
def get_pin_status(self):
"""Returns 1 if PIN auth is active and 0 if its not"""
def pinreq_errback(failure):
failure.trap(E.SimPinRequired)
return 1
def aterror_eb(failure):
failure.trap(E.General)
# return the failure or wont work
return failure
d = super(WCDMAWrapper, self).get_pin_status()
d.addCallback(lambda response: int(response[0].group('status')))
d.addErrback(pinreq_errback)
d.addErrback(aterror_eb)
return d
def get_radio_status(self):
"""Returns whether the radio is enabled or disabled"""
d = super(WCDMAWrapper, self).get_radio_status()
d.addCallback(lambda resp: int(resp[0].group('status')))
return d
def get_roaming_ids(self):
"""Returns the network ids stored in the SIM to roam"""
# a.k.a. AT+CPOL?
d = super(WCDMAWrapper, self).get_roaming_ids()
d.addCallback(lambda raw: [BasicNetworkOperator(
obj.group('netid')) for obj in raw if int(obj.group('netid'))])
return d
def get_signal_quality(self):
"""Returns the signal level quality"""
if self.device.status < MM_MODEM_STATE_ENABLED:
return defer.succeed(0)
return self.maybecached('signal',
super(WCDMAWrapper, self).get_signal_quality,
[lambda response: int(response[0].group('rssi')),
rssi_to_percentage])
def get_sms(self, index):
return self.mal.get_sms(index)
def do_get_sms(self, index):
"""
Returns a ``Message`` object representing the SMS at ``index``
"""
d = super(WCDMAWrapper, self).get_sms(index)
def get_sms_cb(rawsms):
try:
sms = Message.from_pdu(rawsms[0].group('pdu'))
sms.where = int(rawsms[0].group('where'))
sms.index = index
except IndexError:
# handle bogus CMTI notifications, see #180
return None
return sms
d.addCallback(get_sms_cb)
return d
def get_sms_format(self):
"""
Returns 1 if SMS format is text and 0 if SMS format is PDU
"""
d = super(WCDMAWrapper, self).get_sms_format()
d.addCallback(lambda response: int(response[0].group('format')))
return d
def get_smsc(self):
"""Returns the SMSC number stored in the SIM"""
d = super(WCDMAWrapper, self).get_smsc()
def get_smsc_cb(response):
try:
smsc = response[0].group('smsc')
if not smsc.startswith('+'):
if check_if_ucs2(smsc):
smsc = from_u(unpack_ucs2_bytes(smsc))
return smsc
except KeyError:
raise E.NotFound()
d.addCallback(get_smsc_cb)
return d
def get_spn(self):
"""
Returns SPN Service Provider Name from SIM.
"""
# AT+CRSM=176,28486,0,1,16
d = self.sim_access_restricted(COM_READ_BINARY, EF_SPN, 0, 1, 16)
def get_spn_cb(response):
sw1, sw2, spn = response
if spn is None:
raise E.General('SPN not returned.')
if spn:
spn = from_8bit_in_gsm_or_ts31101(spn)
return spn or ''
d.addCallback(get_spn_cb)
return d
def list_available_mms(self):
return self.mal.list_available_mms_notifications()
def list_contacts(self):
"""
Returns all the contacts in the SIM
:rtype: list
"""
def not_found_eb(failure):
failure.trap(E.NotFound, E.InvalidIndex, E.General)
return []
def get_them(ignored=None):
d = super(WCDMAWrapper, self).list_contacts()
d.addCallback(lambda matches:
map(self._regexp_to_contact, matches))
d.addErrback(not_found_eb)
return d
if self.device.sim.size:
return get_them()
else:
d = self._get_next_contact_id()
d.addCallback(get_them)
return d
def _regexp_to_contact(self, match):
"""
Returns a :class:`core.contact.Contact` out of ``match``
:type match: ``re.MatchObject``
"""
name = match.group('name')
if self.device.sim.charset == 'UCS2':
name = from_ucs2(name)
number = match.group('number')
index = int(match.group('id'))
return Contact(name, number, index=index)
def list_sms(self):
return self.mal.list_sms()
def do_list_sms(self):
"""
Returns all the SMS in the SIM card
:rtype: list
"""
d = super(WCDMAWrapper, self).list_sms()
def get_all_sms_cb(messages):
sms_list = []
for rawsms in messages:
try:
sms = Message.from_pdu(rawsms.group('pdu'))
sms.index = int(rawsms.group('id'))
sms.where = int(rawsms.group('where'))
sms_list.append(sms)
except ValueError:
log.err(ex.MalformedSMSError,
"Malformed PDU: %s" % rawsms.group('pdu'))
return sms_list
d.addCallback(get_all_sms_cb)
return d
def save_sms(self, sms):
return self.mal.save_sms(sms)
def do_save_sms(self, sms):
"""
Stores ``sms`` and returns a list of indexes
``sms`` might span several messages if it is a multipart SMS
"""
save_sms = super(WCDMAWrapper, self).save_sms
ret = [save_sms(p.pdu, p.length) for p in sms.to_pdu(store=True)]
d = defer.gatherResults(ret)
# the order is important! You need to run gatherResults and add
# the callback to its result, not the other way around!
d.addCallback(lambda response:
[int(resp[0].group('index')) for resp in response])
return d
def send_at(self, atstr, name='send_at', callback=None):
"""Sends an arbitrary AT string ``atstr``"""
d = super(WCDMAWrapper, self).send_at(atstr, name=name)
if callback is None:
d.addCallback(lambda response: response[0].group('resp'))
else:
d.addCallback(callback)
return d
def send_pin(self, pin):
"""
Sends ``pin`` to authenticate
Most devices need some time to settle after a successful auth
it is the caller's responsability to give at least 15 seconds
to the device to settle, this time varies from device to device
"""
from core.startup import attach_to_serial_port
d = attach_to_serial_port(self.device)
d.addCallback(lambda _: super(WCDMAWrapper, self).send_pin(pin))
d.addCallback(lambda response: response[0].group('resp'))
return d
def send_puk(self, puk, pin):
"""
Send ``puk`` and ``pin`` to authenticate
Most devices need some time to settle after a successful auth
it is the caller's responsability to give at least 15 seconds
to the device to settle, this time varies from device to device
"""
d = super(WCDMAWrapper, self).send_puk(puk, pin)
d.addCallback(lambda response: response[0].group('resp'))
return d
def send_mms(self, mms, extra_info):
"""Send ``mms`` and returns the Message-Id"""
return self.mal.send_mms(mms, extra_info)
def do_send_mms(self, mms, extra_info):
if 'wap2' not in extra_info:
raise ValueError("Only WAP2.0 is supported at the moment")
if 'mmsc' not in extra_info:
raise ValueError("No mmsc key in %s" % extra_info)
return send_m_send_req(extra_info, mms)
def send_sms(self, sms):
"""
Sends ``sms`` and returns the indexes
``sms`` might span several messages if it is a multipart SMS
"""
return self.mal.send_sms(sms)
def do_send_sms(self, sms):
def send_sms_cb(response):
return int(response[0].group('index'))
ret = []
for pdu in sms.to_pdu():
d = super(WCDMAWrapper, self).send_sms(pdu.pdu, pdu.length)
d.addCallback(send_sms_cb)
ret.append(d)
return defer.gatherResults(ret)
def send_sms_from_storage(self, index):
"""Sends the SMS stored at ``index`` and returns the new index"""
return self.mal.send_sms_from_storage(index)
def do_send_sms_from_storage(self, index):
d = super(WCDMAWrapper, self).send_sms_from_storage(index)
d.addCallback(lambda response: int(response[0].group('index')))
return d
def send_ussd(self, ussd, force_ascii=False, loose_charset_check=False):
"""Sends the ussd command ``ussd``"""
def convert_response(response):
index = response[0].group('index')
if index == '1':
self.device.set_property(USD_INTFACE, 'State', 'user-response')
else:
self.device.set_property(USD_INTFACE, 'State', 'idle')
resp = response[0].group('resp')
if resp is None:
return "" # returning the Empty string is valid
if not check_if_ucs2(resp, limit=LATIN_EX_B):
if 'UCS2' in self.device.sim.charset and \
not loose_charset_check:
raise E.MalformedUssdPduError(resp)
else:
return resp
else:
try:
return unpack_ucs2_bytes(resp)
except (TypeError, UnicodeDecodeError):
raise E.MalformedUssdPduError(resp)
def reset_state(failure):
if self.device.get_property(USD_INTFACE, 'State') != 'idle':
self.device.set_property(USD_INTFACE, 'State', 'idle')
failure.raiseException() # re-raise
if 'UCS2' in self.device.sim.charset and not force_ascii:
ussd = pack_ucs2_bytes(ussd)
d = super(WCDMAWrapper, self).send_ussd(str(ussd))
d.addCallback(convert_response)
d.addErrback(reset_state)
return d
def set_allowed_mode(self, mode):
raise NotImplementedError("Implement it in the device family wrapper")
def set_apn(self, apn):
"""Sets the APN to ``apn``"""
def process_apns(apns, the_apn):
for _index, _apn in apns:
if _apn == the_apn:
self.state_dict['conn_id'] = _index
return defer.succeed('OK')
try:
conn_id = max([idx for idx, _ in apns]) + 1
if conn_id < self.apn_range[0] or conn_id > self.apn_range[1]:
raise ValueError
except (ValueError, TypeError):
conn_id = self.apn_range[0]
self.state_dict['conn_id'] = conn_id
d = super(WCDMAWrapper, self).set_apn(conn_id, the_apn)
d.addCallback(lambda response: response[0].group('resp'))
return d
d = self.get_apns()
d.addCallback(process_apns, apn)
return d
def set_band(self, band):
"""Sets the device band to ``band``"""
raise NotImplementedError()
def set_charset(self, charset):
"""Sets the SIMs charset to ``charset``"""
d = super(WCDMAWrapper, self).set_charset(charset)
d.addCallback(lambda ignored: self.device.sim.set_charset(charset))
return d
def set_error_level(self, level):
"""Sets the modem's error reporting level to ``level``"""
d = super(WCDMAWrapper, self).set_error_level(level)
d.addCallback(lambda response: response[0].group('resp'))
return d
def set_network_mode(self, mode):
"""Sets the network mode to ``mode``"""
raise NotImplementedError()
def enable_radio(self, enable):
"""
Enables the radio according to ``enable``
It will not enable it if its already enabled and viceversa
"""
def check_if_necessary(status):
if (status == 1 and enable) or (status == 0 and not enable):
return defer.succeed('OK')
d = super(WCDMAWrapper, self).enable_radio(enable)
d.addCallback(lambda response: response[0].group('resp'))
return d
d = self.get_radio_status()
d.addCallback(check_if_necessary)
return d
def set_sms_format(self, _format=0):
"""Sets PDU mode or text mode in the SIM"""
d = super(WCDMAWrapper, self).set_sms_format(_format)
d.addCallback(lambda response: response[0].group('resp'))
return d
def set_smsc(self, smsc):
"""Sets the SIMS's SMSC number to ``smsc``"""
if 'UCS2' in self.device.sim.charset:
smsc = pack_ucs2_bytes(smsc)
d = super(WCDMAWrapper, self).set_smsc(smsc)
d.addCallback(lambda response: response[0].group('resp'))
return d
# some high-level methods exported over DBus
def init_properties(self):
# XXX: Implement UnlockRetries
self.device.set_property(MDM_INTFACE,
'UnlockRetries', dbus.UInt32(999))
# There's no way to query this, so we have to assume :-(
self.device.set_property(USD_INTFACE, 'State', 'idle')
d = self.get_bands()
d.addCallback(lambda bands: self.device.set_property(CRD_INTFACE,
'SupportedBands', dbus.UInt32(bands)))
d.addCallback(lambda _: self.get_network_modes())
d.addCallback(lambda modes: self.device.set_property(CRD_INTFACE,
'SupportedModes', dbus.UInt32(modes)))
d.addCallback(lambda _: self.get_pin_status())
d.addCallback(lambda active: self.device.set_property(CRD_INTFACE,
'PinEnabled', dbus.Boolean(active)))
d.addCallback(lambda _: self.get_imei())
d.addCallback(lambda imei: self.device.set_property(MDM_INTFACE,
'EquipmentIdentifier', imei))
d.addCallback(lambda _: self.get_iccid())
def iccid_eb(failure):
failure.trap(E.General)
self.device.set_property(CRD_INTFACE, 'SimIdentifier', ''),
d.addCallbacks(lambda iccid:
self.device.set_property(CRD_INTFACE,
'SimIdentifier', iccid), iccid_eb)
return d
def get_simple_status(self):
"""Returns the status for o.fd.MM.Modem.Simple.GetStatus"""
if self.device.status < MM_MODEM_STATE_ENABLED:
return defer.succeed(dict(state=self.device.status))
def get_simple_status_cb((sigstrength, netinfo, band, net_mode)):
return dict(state=self.device.status,
signal_quality=sigstrength,
operator_code=netinfo[1],
operator_name=netinfo[2],
band=band,
network_mode=net_mode)
deferred_list = []
deferred_list.append(self.get_signal_quality())
deferred_list.append(self.get_netreg_info())
deferred_list.append(self.get_band())
deferred_list.append(self.get_network_mode())
d = defer.gatherResults(deferred_list)
d.addCallback(get_simple_status_cb)
d.addErrback(log.err)
return d
def connect_simple(self, settings):
"""Connects with the given ``settings``"""
if self.device.status == MM_MODEM_STATE_CONNECTED:
# this cannot happen
raise E.Connected("we are already connected")
if self.device.status == MM_MODEM_STATE_CONNECTING:
raise E.SimBusy("we are already connecting")
def connect_eb(failure):
log.msg("connect_simple errorback")
if self.device.status >= MM_MODEM_STATE_REGISTERED:
self.device.set_status(MM_MODEM_STATE_REGISTERED)
failure.raiseException() # re-raise
simplesm = self.device.custom.simp_klass(self.device, settings)
d = simplesm.start_simple()
d.addCallback(lambda _:
self.device.set_status(MM_MODEM_STATE_CONNECTED))
d.addErrback(connect_eb)
return d
def connect_to_internet(self, settings):
# Note: this is called by:
# 1/ connect_simple via simple state machine
# 2/ directly by Connect() dbus method
self.device.set_property(NET_INTFACE, 'LastApn',
unicode(settings.get('apn', '')))
ip_method = self.device.get_property(MDM_INTFACE, 'IpMethod')
if ip_method == MM_IP_METHOD_PPP:
d = self.connect_to_internet_ppp(settings)
else:
d = self.connect_to_internet_hso(settings)
d.addCallback(self.start_traffic_monitoring)
return d
def connect_to_internet_hso(self, settings):
username = settings.get('username', '')
password = settings.get('password', '')
auth = settings.get('allowed_auth', None)
d = self.hso_authenticate(username, password, auth)
d.addCallback(lambda _: self.hso_connect())
return d
def connect_to_internet_ppp(self, settings):
"""Opens data port and dials ``settings['number']`` in"""
if self.device.status == MM_MODEM_STATE_CONNECTED:
# this cannot happen
raise E.Connected("we are already connected")
if self.device.status == MM_MODEM_STATE_CONNECTING:
raise E.SimBusy("we are already connecting")
self.device.set_status(MM_MODEM_STATE_CONNECTING)
# open the data port
port = self.device.ports.dport
# this will raise a SerialException if port is busy
port.obj = serial.Serial(port.path)
port.obj.flush()
# send ATDT and convert number to string as pyserial does
# not like to write unicode to serial ports
number = settings.get('number')
d = defer.maybeDeferred(port.obj.write,
"ATDT%s\r\n" % str(number))
# we should detect error or success here and set state
return d
def disconnect_from_internet(self):
"""Disconnects the modem"""
ip_method = self.device.get_property(MDM_INTFACE, 'IpMethod')
if ip_method == MM_IP_METHOD_PPP:
d = self.disconnect_from_internet_ppp()
else:
d = self.disconnect_from_internet_hso()
d.addCallback(self.stop_traffic_monitoring)
return d
def disconnect_from_internet_hso(self):
# NM usually issues disconnect as part of a connect sequence
if self.device.status < MM_MODEM_STATE_CONNECTED:
return defer.succeed(True)
return self.hso_disconnect()
def disconnect_from_internet_ppp(self):
"""Disconnects the modem temporarily lowering the DTR"""
# NM usually issues disconnect as part of a connect sequence
if self.device.status < MM_MODEM_STATE_CONNECTED:
return defer.succeed(True)
port = self.device.ports.dport
if not port.obj.isOpen():
raise AttributeError("Data serial port is not open")
self.device.set_status(MM_MODEM_STATE_DISCONNECTING)
# XXX: should check that we did stop the connection and set status
def restore_speed(speed):
try:
port.obj.setBaudrate(speed)
except serial.SerialException:
pass
port.obj.close()
# XXX: perhaps we should check the registration status here
if self.device.status > MM_MODEM_STATE_REGISTERED:
self.device.set_status(MM_MODEM_STATE_REGISTERED)
return True
# lower and raise baud speed
speed = port.obj.getBaudrate()
try:
port.obj.setBaudrate(0)
except serial.SerialException:
pass
# restore the speed in .1 seconds
return task.deferLater(reactor, .1, restore_speed, speed)
def get_stats(self):
"""
Returns a tuple with the connection statistics for this dialer
:return: (in_bytes, out_bytes)
"""
if self.iface is not None:
now = time()
rx_bytes, tx_bytes = get_os_object().get_iface_stats(self.iface)
# if any of these three are not 0, it means that this is at
# least the second time this method is executed, thus we
# should have cached meaningful data
if self.__rx_bytes or self.__tx_bytes or self.__time:
rx_delta = rx_bytes - self.__rx_bytes
tx_delta = tx_bytes - self.__tx_bytes
interval = now - self.__time
raw_rx_rate = int(floor(rx_delta / interval))
raw_tx_rate = int(floor(tx_delta / interval))
rx_rate = raw_rx_rate if raw_rx_rate >= 0 else 0
tx_rate = raw_tx_rate if raw_tx_rate >= 0 else 0
else:
# first time this is executed, we cannot reliably compute
# the rate. It is better to lie just once
rx_rate = tx_rate = 0
self.__rx_bytes, self.__tx_bytes = rx_bytes, tx_bytes
self.__time = now
return rx_bytes, tx_bytes, rx_rate, tx_rate
def register_with_netid(self, netid):
"""
I will try my best to register with ``netid``
If ``netid`` is an empty string, I will register with my home network
"""
netr_klass = self.device.custom.netr_klass
netsm = netr_klass(self.device.sconn, netid)
return netsm.start_netreg()
def start_traffic_monitoring(self, x=None):
ip_method = self.device.get_property(MDM_INTFACE, 'IpMethod')
if ip_method == MM_IP_METHOD_PPP:
self.iface = 'ppp0' # XXX: don't hardcode to first PPP instance
else:
self.iface = self.device.get_property(MDM_INTFACE, 'Device')
if self.stats_id is None:
self.stats_id = timeout_add_seconds(1, self._emit_dial_stats)
return x
def stop_traffic_monitoring(self, x=None):
# remove the emit stats task
if self.stats_id is not None:
source_remove(self.stats_id)
self.stats_id = None
return x
def enable_device(self, enable):
"""
I enable or disable myself according to ``enable``
If enable is True, I check the auth state of a device and will try to
initialize it. Otherwise I will disable myself
"""
if enable:
return self._do_enable_device()
else:
return self._do_disable_device()
def _do_disable_device(self):
self.clean_signals()
if self.device.status == MM_MODEM_STATE_CONNECTED:
def on_disconnect_from_internet(_):
if self.device.status >= MM_MODEM_STATE_REGISTERED:
self.device.set_status(MM_MODEM_STATE_REGISTERED)
self.device.close()
d = self.disconnect_from_internet()
d.addCallback(on_disconnect_from_internet)
d.addErrback(log.err)
return d
if self.device.status >= MM_MODEM_STATE_ENABLED:
self.device.close()
return defer.succeed(None)
def _do_enable_device(self):
if self.device.status >= MM_MODEM_STATE_ENABLED:
return defer.succeed(self.device)
if self.device.status == MM_MODEM_STATE_ENABLING:
raise E.SimBusy()
self.device.set_status(MM_MODEM_STATE_ENABLING)
def signals(resp):
self.connect_to_signals()
# XXX: This netreg notification seems to be unrelated to enable,
# perhaps it should be moved?
self.device.sconn.set_netreg_notification(1)
if self.device.status < MM_MODEM_STATE_ENABLED:
self.device.set_status(MM_MODEM_STATE_ENABLED)
return resp
def setdefaults(resp):
def apn_range_cb(result):
self.apn_range = result
return resp
def apn_range_eb(e):
self.apn_range = (1, 3)
return resp
d = self.device.sconn.get_apn_range()
d.addCallback(apn_range_cb)
d.addErrback(apn_range_eb)
return d
from core.startup import attach_to_serial_port
def process_device_and_initialize(device):
self.device = device
auth_klass = self.device.custom.auth_klass
authsm = auth_klass(self.device)
def set_status(failure):
self.device.set_status(MM_MODEM_STATE_DISABLED)
failure.raiseException() # re-raise
d = authsm.start_auth()
# if auth is ready, the device will initialize straight away
# if auth isn't ready the callback chain won't be executed and
# will just return the given exception
d.addErrback(set_status)
d.addCallback(self.device.initialize)
d.addCallback(setdefaults)
d.addCallback(signals)
return d
d = attach_to_serial_port(self.device)
d.addCallback(process_device_and_initialize)
return d
def _check_initted_device(self, result):
"""
To be executed after successful authentication over DBus
Network Manager calls this via SendPin() even when not performing an
Enable or SimpleConnect, it's just done as part of noticing a new
device has appeared. So after we have unlocked we save a timestamp so
that any subsequent initialisation can check if the requisite settling
DELAY has elapsed, but we can't continue blindly to initialisation as
used to be the case.
"""
self.device.set_property(MDM_INTFACE, 'UnlockRequired', '')
self.device.set_authtime(time())
return result
def _emit_dial_stats(self):
stats = self.get_stats()
if stats is not None:
self.device.exporter.DialStats(stats)
# make sure this is repeatedly called
return True
class BasicNetworkOperator(object):
"""A Network operator with a netid"""
def __init__(self, netid):
super(BasicNetworkOperator, self).__init__()
self.netid = from_ucs2(netid)
def __repr__(self):
return '' % self.netid
def __cmp__(self, o):
try:
a = int(self.netid)
except (AttributeError, NameError, TypeError, ValueError):
a = None
try:
b = int(o.netid)
except (AttributeError, NameError, TypeError, ValueError):
b = None
return cmp(a, b)
def __eq__(self, o):
return self.netid == o.netid
def __ne__(self, o):
return not self.__eq__(o)
class NetworkOperator(BasicNetworkOperator):
"""I represent a network operator on a mobile network"""
def __init__(self, stat, long_name, short_name, netid, rat):
super(NetworkOperator, self).__init__(netid)
self.stat = int(stat)
self.long_name = from_ucs2(long_name)
self.short_name = from_ucs2(short_name)
self.rat = int(rat)
def __repr__(self):
args = (self.long_name, self.netid)
return '' % args
wader-0.5.13/core/mms.py 0000664 0000000 0000000 00000012536 12576466102 0015020 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2008-2010 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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.
"""MMS related classes and functions"""
from array import array
from cStringIO import StringIO
import socket
import dbus
from twisted.internet import threads
from twisted.python import log
from messaging.mms.message import MMSMessage, DataPart
from wader.common.aterrors import ExpiredNotification
def remove_headers_and_convert_to_array(payload):
_, data = payload.split('\r\n\r\n')
return array("B", data)
def mms_to_dbus_data(mms):
"""Converts ``mms`` to a tuple ready to be sent via DBus"""
headers = {}
data_parts = []
# Convert headers
for key, val in mms.headers.items():
if key == 'Content-Type':
headers[key] = val[0]
else:
headers[key] = val
del headers['Date']
# Set up data
for data_part in mms.data_parts:
part = {'Content-Type': data_part.content_type,
'data': dbus.ByteArray(data_part.data)}
if data_part.headers['Content-Type'][1]:
part['parameters'] = data_part.headers['Content-Type'][1]
data_parts.append(part)
return headers, data_parts
def dbus_data_to_mms(headers, data_parts):
"""Returns a `MMSMessage` out of ``dbus_data``"""
mms = MMSMessage()
content_type = ''
for key, val in headers.items():
if key == 'Content-Type':
content_type = val
else:
mms.headers[key] = val
mms.content_type = content_type
# add data parts
for data_part in data_parts:
content_type = data_part['Content-Type']
data = array("B", data_part['data'])
parameters = data_part.get('parameters', {})
dp = DataPart()
dp.set_data(data, content_type, parameters)
# XXX: MMS message with no SMIL support
# Content-Type: application/vnd.wap.multipart.mixed
mms.add_data_part(dp)
return mms
def do_get_payload(url, extra_info):
host, port = extra_info['wap2'].split(':')
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, int(port)))
s.send("GET %s HTTP/1.0\r\n\r\n" % url)
buf = StringIO()
while True:
data = s.recv(4096)
if not data:
break
buf.write(data)
s.close()
data = buf.getvalue()
buf.close()
return data
def get_payload(uri, extra_info):
"""
Downloads ``uri`` and returns a `MMSMessage` from it
:param extra_info: dict with connection information
"""
d = threads.deferToThread(do_get_payload, uri, extra_info)
d.addCallback(remove_headers_and_convert_to_array)
d.addCallback(MMSMessage.from_data)
return d
def do_post_payload(extra_info, payload):
host, port = extra_info['wap2'].split(':')
mmsc = extra_info['mmsc']
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, int(port)))
s.send("POST %s HTTP/1.0\r\n" % mmsc)
s.send("Content-Type: application/vnd.wap.mms-message\r\n")
s.send("Content-Length: %d\r\n\r\n" % len(payload))
s.sendall(payload)
buf = StringIO()
while True:
data = s.recv(4096)
if not data:
break
buf.write(data)
s.close()
data = buf.getvalue()
buf.close()
log.msg("do_post_payload: %r" % data)
return data
def post_payload(extra_info, data):
return threads.deferToThread(do_post_payload, extra_info, data)
def send_m_notifyresp_ind(extra_info, tx_id):
mms = MMSMessage()
mms.headers['Transaction-Id'] = tx_id
mms.headers['Message-Type'] = 'm-notifyresp-ind'
mms.headers['Status'] = 'Retrieved'
def process_response(data):
if data.startswith("HTTP/1.0 404"):
# notification has expired
try:
text = data.split('\r\n')[0][13:]
except IndexError:
text = "Message not found in MMSC"
raise ExpiredNotification(text)
d = post_payload(extra_info, mms.encode())
d.addCallback(process_response)
return d
def send_m_send_req(extra_info, dbus_data):
# sanitize headers
headers = dbus_data['headers']
if 'To' not in headers:
raise ValueError("You need to provide a recipient 'To'")
if not headers['To'].endswith('/TYPE=PLMN'):
headers['To'] += '/TYPE=PLMN'
# set headers
mms = dbus_data_to_mms(dbus_data)
for key, val in headers.items():
mms.headers[key] = val
# set type the last one so is always the right type
mms.headers['Message-Type'] = 'm-send-req'
d = post_payload(extra_info, mms.encode())
d.addCallback(remove_headers_and_convert_to_array)
d.addCallback(MMSMessage.from_data)
return d
wader-0.5.13/core/modemmanager.py 0000664 0000000 0000000 00000004307 12576466102 0016655 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2008-2009 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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.
"""
A simple wrapper to access the devices on a hassle-free way
Note that it will not use async methods (for clarity's sake) and thus
it will block. If you want to use it in async environments, you'll have
to roll out your own wrapper.
"""
import dbus
from wader.common import consts
class ModemManager(object):
"""I provide an easy to use interface to interact with ModemManager"""
def __init__(self):
super(ModemManager, self).__init__()
self.bus = dbus.SystemBus()
self.mm_obj = None
self.dial_obj = None
self._opaths = []
self._init_modemmanager()
def _init_modemmanager(self):
self.mm_obj = self.bus.get_object(consts.WADER_SERVICE,
consts.WADER_OBJPATH)
self._opaths = self.mm_obj.EnumerateDevices()
if not self._opaths:
raise RuntimeError("No devices found")
def get_devices(self):
"""Returns a list with the devices present in the system"""
devices = [self.bus.get_object(consts.WADER_SERVICE, opath)
for opath in self._opaths]
return devices
def get_dial_manager(self):
"""Returns a proxy to DialerManager"""
if not self.dial_obj:
self.dial_obj = self.bus.get_object(consts.WADER_DIALUP_SERVICE,
consts.WADER_DIALUP_OBJECT)
return self.dial_obj
wader-0.5.13/core/oal.py 0000664 0000000 0000000 00000002743 12576466102 0014776 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2006-2008 Vodafone España, S.A.
# Copyright (C) 2008-2009 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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.
"""
OS Abstraction Layer
OS provides an abstraction layer so path differences between OSes/distros
won't affect Wader
"""
_os_obj = None
def get_os_object():
"""
Returns a ``OSPlugin`` instance corresponding to current OS used
If the OS is unknown it will return None
"""
global _os_obj
if _os_obj is not None:
return _os_obj
from core.plugin import PluginManager
from wader.common.interfaces import IOSPlugin
for osplugin in PluginManager.get_plugins(IOSPlugin):
if osplugin.is_valid():
osplugin.initialize()
_os_obj = osplugin
return _os_obj
return None
wader-0.5.13/core/oses/ 0000775 0000000 0000000 00000000000 12576466102 0014614 5 ustar 00root root 0000000 0000000 wader-0.5.13/core/oses/__init__.py 0000664 0000000 0000000 00000001553 12576466102 0016731 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2006-2008 Vodafone España, S.A.
# Copyright (C) 2008-2009 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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.
"""OSes base classes"""
wader-0.5.13/core/oses/bsd.py 0000664 0000000 0000000 00000003404 12576466102 0015737 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2006-2008 Vodafone España, S.A.
# Copyright (C) 2008-2009 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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.
"""BSD-based OS plugins"""
from os import uname
from core.oses.unix import UnixPlugin
class FreeBSDPlugin(UnixPlugin):
"""Plugin for FreeBSD"""
def __init__(self):
super(FreeBSDPlugin, self).__init__()
def get_iface_stats(self, iface):
# XXX: implementation missing
return 0, 0
def is_valid(self):
if 'FreeBSD' in uname()[0]:
return True
return False
def update_dns_cache(self):
raise NotImplementedError()
class OpenBSDPlugin(UnixPlugin):
"""Plugin for OpenBSD"""
def __init__(self):
super(OpenBSDPlugin, self).__init__()
def get_iface_stats(self, iface):
# XXX: implementation missing
return 0, 0
def is_valid(self):
try:
__import__("openbsd")
return True
except ImportError:
return False
def update_dns_cache(self):
raise NotImplementedError()
wader-0.5.13/core/oses/linux.py 0000664 0000000 0000000 00000047441 12576466102 0016337 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2006-2008 Vodafone España, S.A.
# Copyright (C) 2008-2009 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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.
"""Linux-based OS plugin"""
import dbus
from functools import partial
from os.path import join, exists
import re
import subprocess
from zope.interface import implements
from twisted.internet import defer, reactor, utils
from twisted.python import log, reflect
from twisted.python.procutils import which
from wader.common import consts
from wader.common.interfaces import IHardwareManager
from wader.common.utils import get_file_data, natsort
from core.hardware.base import identify_device, probe_ports
from core.plugin import PluginManager
from core.oses.unix import UnixPlugin
from core.startup import setup_and_export_device
from core.serialport import Ports
IDLE, BUSY = range(2)
ADD_THRESHOLD = 6
MODEL, VENDOR, DRIVER = "ID_MODEL_ID", "ID_VENDOR_ID", "ID_USB_DRIVER"
SUBSYSTEMS = ["tty", "usb", "net"]
REQUIRED_PROPS = [VENDOR, MODEL, DRIVER, "ID_BUS", "DEVNAME"]
BAD_DEVFILE = re.compile('^/dev/(tty\d*?|console|ptmx)$')
def get_ancestor(device):
"""
Returns the greatest ancestor with matching vid / pid
"""
def get_vidpid(device):
vendor = None
model = None
for key in device.get_property_keys():
if key == VENDOR:
try:
vendor = int(device.get_property(key), 16)
except (ValueError, TypeError):
pass
if key == MODEL:
try:
model = int(device.get_property(key), 16)
except (ValueError, TypeError):
pass
return (vendor, model)
vidpid = get_vidpid(device)
ancestor = parent = device
while parent is not None:
if get_vidpid(parent) == vidpid:
ancestor = parent
parent = parent.get_parent()
return ancestor
def is_valid_device(device):
"""Checks whether ``device`` is valid"""
if not device.get_device_file():
return False
# before checking all the properties, filter out all the /dev/tty%d
if BAD_DEVFILE.match(device.get_device_file()):
return False
# filter out /dev/usb/foo/bar/foo like too
parts = device.get_device_file().split('/')
if len(parts) > 3:
return False
# check that it has all the required properties
# otherwise we are not interested on it
props = device.get_property_keys()
for prop in REQUIRED_PROPS:
if prop not in props:
return False
return True
def get_devices(client):
devices = []
# get all the devices under the tty, usb and net subsystems
for subsystem in SUBSYSTEMS:
for device in client.query_by_subsystem(subsystem):
if is_valid_device(device):
devices.append(device)
return devices
class HardwareManager(object):
"""
I find and configure devices on Linux
I am resilient to ports assigned in unusual locations
and devices sharing ids.
"""
implements(IHardwareManager)
def __init__(self):
super(HardwareManager, self).__init__()
#: dictionary with all my configured clients
self.clients = {}
#: reference to StartupController
self.controller = None
self._waiting_deferred = None
# remember the total client count for opath generation
self._client_count = -1
gudev = reflect.namedAny("gudev")
self.gudev_client = gudev.Client(SUBSYSTEMS)
# temporary place to store hotplugged devices to process
self._hotplugged_devices = []
self._call = None
self._connect_to_signals()
def _connect_to_signals(self):
self.gudev_client.connect("uevent", self._on_uevent)
def _on_uevent(self, client, action, device):
msg = "UEVENT device: %s action: %s"
log.msg(msg % (device.get_sysfs_path(), action))
if action == 'remove':
# handle remove
for opath, plugin in self.clients.items():
if plugin.sysfs_path == device.get_sysfs_path():
self.controller.DeviceRemoved(plugin.opath)
self._unregister_client(plugin)
elif action == 'add':
# if valid, append it to the list of hotplugged devices
# for later processing
if is_valid_device(device):
self._hotplugged_devices.append(device)
if self._call is None:
# the first time we set a small delay and whenever a device
# is added we will reset the call 2 seconds
self._call = reactor.callLater(2,
self._process_hotplugged_devices)
elif self._call.active():
# XXX: this can be optimized by substracting x milliseconds
# for every device added to the reset call. However it
# introduces some more logic and perhaps should live outside.
self._call.reset(ADD_THRESHOLD)
def register_controller(self, controller):
"""
See `IHardwareManager.register_controller`
"""
self.controller = controller
def get_devices(self):
"""See :meth:`wader.common.interfaces.IHardwareManager.get_devices`"""
# If clients is an empty dict we assume that this is the first
# time get_devices is executed. If not, we just return the current
# devices. If get_devices is executed in the middle of a hotplugging
# event, the "just added" device won't be returned, but it will be
# processed in a few seconds by _process_hotplugged_devices anyway.
if self.clients:
return defer.succeed(self.clients.values())
return self._process_found_devices(get_devices(self.gudev_client))
def _process_hotplugged_devices(self):
# get DevicePlugin out of a list of gudev.Device
self._process_found_devices(self._hotplugged_devices)
self._hotplugged_devices, self._call = [], None
def _process_found_devices(self, devices=None, emit=True):
"""
Processes gudev ``devices`` and returns ``DevicePlugin``s
Find devices with a common parent and merge them, identify
the ones that need it, register and emit a signal if ``emit``
is True.
"""
deferreds = []
for device in self._setup_devices(devices):
d = identify_device(device)
d.addCallback(self._register_client, emit=emit)
deferreds.append(d)
return defer.gatherResults(deferreds)
def _setup_devices(self, devices):
"""Sets up ``devices``"""
found_devices = {}
for device in devices:
props = {}
for prop in REQUIRED_PROPS:
value = device.get_property(prop)
# values are either string or hex
try:
props[prop] = int(value, 16)
except ValueError:
props[prop] = value
if 'DEVNAME' in props:
abspath = device.get_device_file()
if props['DEVNAME'] != abspath:
# Sometimes the DEVNAME property obtained from 'gudev' is
# just a relative pathname. This seems to occur on boot
# with the device already inserted, rather than upon a
# hotplug insertion event.
if abspath.endswith(props['DEVNAME']) and exists(abspath):
props['DEVNAME'] = abspath
else:
log.msg("DEVNAME != device.get_device_file() but " +
"fixup not possible")
log.msg("'%s' != '%s'" % (props['DEVNAME'], abspath))
# if these properties are present, we should use them as the
# data and control ports
for mm_prop in ['ID_MM_PORT_TYPE_MODEM', 'ID_MM_PORT_TYPE_AUX']:
if mm_prop in device.get_property_keys():
props[mm_prop] = bool(int(device.get_property(mm_prop)))
# now find out the device parent
parent = get_ancestor(device).get_sysfs_path()
if parent in self.clients:
# this device has already been setup
return
if parent in found_devices:
# we have already found a device with the same parent, update
# the attributes
found_devices[parent].update(props)
else:
# a new parent has been found, store its sysfs_path as key
# as all the childs have the same property DEVNAME, we need to
# create a new and temporal property named DEVICES
found_devices[parent] = props
found_devices[parent]['DEVICES'] = []
if 'DEVNAME' in props:
# append the device name as usual
found_devices[parent]['DEVICES'].append(props['DEVNAME'])
# if any of this is present save them for later use
for _prop in ['ID_MM_PORT_TYPE_MODEM', 'ID_MM_PORT_TYPE_AUX']:
if props.get(_prop, False):
found_devices[parent][_prop] = props['DEVNAME']
result = []
for sysfs_path, info in found_devices.items():
device = self._get_device_from_info(sysfs_path, info)
if device:
result.append(device)
else:
log.msg("Unknown device: %s" % info)
try:
lsusb_path = which('lsusb')[0]
except IndexError:
lsusb_path = '/usr/bin/lsusb'
try:
id_vendor = info['ID_VENDOR_ID']
id_model = info['ID_MODEL_ID']
p = subprocess.Popen([lsusb_path, '-v',
'-d %x:%x' % (id_vendor, id_model)],
bufsize=-1,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE,
universal_newlines=True,
shell=False)
device_info, device_info_error = p.communicate()
log.msg('lsusb device info:%s' % device_info)
if len(device_info_error):
log.msg('lsusb device info_error:%s'
% device_info_error)
except (OSError, ValueError):
pass
from core.hardware.base import raw_identify_device
for port in info.get('DEVICES', []):
try:
log.msg('try to identify model on port "%s"' % port)
raw_identify_device(port)
except ValueError:
pass
return result
def _register_client(self, plugin, emit=False):
"""
Registers `plugin` in `self.clients` indexes by its object path
Will emit a DeviceAdded signal if emit is True
"""
log.msg("registering plugin %s with opath %s" % (plugin, plugin.opath))
self.clients[plugin.opath] = setup_and_export_device(plugin)
if emit:
self.controller.DeviceAdded(plugin.opath)
return plugin
def _unregister_client(self, client):
"""Removes client identified by ``opath``"""
try:
plugin = self.clients.pop(client.opath)
plugin.close(removed=True)
except KeyError:
log.err("_unregister_client: Could not "
"unregister %s. Is not present." % client.opath)
def _generate_opath(self):
self._client_count += 1
return "/org/freedesktop/ModemManager/Devices/%d" % self._client_count
def _get_hso_ports(self, ports):
dport = cport = None
for port in ports:
name = port.split('/')[-1]
path = join('/sys/class/tty', name, 'hsotype')
if exists(path):
what = get_file_data(path).strip().lower()
if what == 'modem':
dport = port
elif what == 'application':
cport = port
if dport and cport:
break
return dport, cport
def _get_hso_device(self, sysfs_path):
for device in self.gudev_client.query_by_subsystem("net"):
if device.get_sysfs_path().startswith(sysfs_path):
return device.get_property("INTERFACE")
raise ValueError("Cannot find hso device for device %s" % sysfs_path)
def _get_device_from_info(self, sysfs_path, info):
"""Returns a `DevicePlugin` out of ``info``"""
# order the ports before probing
ports = info['DEVICES']
natsort(ports)
query = [info.get(key) for key in [VENDOR, MODEL]]
plugin = PluginManager.get_plugin_by_vendor_product_id(*query)
if plugin:
dport = cport = None
plugin.sysfs_path = sysfs_path
plugin.opath = self._generate_opath()
set_property = partial(plugin.set_property, emit=False)
# set DBus properties (Modem interface)
set_property(consts.MDM_INTFACE, 'IpMethod',
consts.MM_IP_METHOD_PPP)
set_property(consts.MDM_INTFACE, 'MasterDevice',
'udev:%s' % sysfs_path)
# XXX: Fix CDMA
set_property(consts.MDM_INTFACE, 'Type',
consts.MM_MODEM_TYPE_REV['GSM'])
set_property(consts.MDM_INTFACE, 'Driver', info[DRIVER])
set_property(consts.MDM_INTFACE, 'Enabled', dbus.Boolean(False))
# set to unknown
set_property(consts.NET_INTFACE, 'AccessTechnology',
dbus.UInt32(0))
# set to preposterous initial number so any comparison will fail
set_property(consts.NET_INTFACE, 'AllowedMode',
dbus.UInt32(0xffff))
set_property(consts.NET_INTFACE, 'LastApn', '')
# preprobe stuff
if hasattr(plugin, 'preprobe_init'):
# this plugin requires special initialisation before probing
plugin.preprobe_init(ports, info)
# now get the ports
ports_need_probe = True
if info[DRIVER] == 'hso':
dport, cport = self._get_hso_ports(ports)
ports_need_probe = False
# if these two properties are present, use them right away and
# do not probe
if ('ID_MM_PORT_TYPE_MODEM' in info
or 'ID_MM_PORT_TYPE_AUX' in info):
try:
dport = info['ID_MM_PORT_TYPE_MODEM']
log.msg("%s: ID_MM_PORT_TYPE_MODEM" % dport)
except KeyError:
pass
try:
cport = info['ID_MM_PORT_TYPE_AUX']
log.msg("%s: ID_MM_PORT_TYPE_AUX" % cport)
except KeyError:
pass
ports_need_probe = False
if ports_need_probe:
# the ports were not hardcoded nor was an HSO device
dport, cport = probe_ports(ports)
if not dport and not cport:
# this shouldn't happen
msg = 'No data port and no control port with ports: %s'
raise RuntimeError(msg % ports)
if dport is not None:
set_property(consts.MDM_INTFACE, 'Device',
dport.split('/')[-1])
if info[DRIVER] == 'cdc_acm':
# MBM device
# XXX: Not all CDC devices support DHCP, to override see
# plugin attribute 'ipmethod'
# XXX: Also need to support Ericsson devices via 'hso' dialer
# so that we can use the plain backend. At least F3607GW
# supports a get_ip4_config() style AT command to get
# network info, else we need to implement a DHCP client
# set DBus properties (Modem.Gsm.Hso interface)
hso_device = self._get_hso_device(sysfs_path)
set_property(consts.MDM_INTFACE, 'Device', hso_device)
set_property(consts.MDM_INTFACE, 'IpMethod',
consts.MM_IP_METHOD_DHCP)
if plugin.dialer in 'hso':
# set DBus properties (Modem.Gsm.Hso interface)
hso_device = self._get_hso_device(sysfs_path)
set_property(consts.MDM_INTFACE, 'Device', hso_device)
if hasattr(plugin, 'ipmethod'):
# allows us to specify a method in a driver independent way
set_property(consts.MDM_INTFACE, 'IpMethod',
plugin.ipmethod)
if hasattr(plugin, 'conntype') and plugin.conntype:
set_property(consts.MDM_INTFACE, 'ConnType',
dbus.UInt32(plugin.conntype))
else:
set_property(consts.MDM_INTFACE, 'ConnType',
dbus.UInt32(consts.WADER_CONNTYPE_UNKNOWN))
plugin.ports = Ports(dport, cport)
return plugin
log.msg("Could not find a plugin with info %s" % info)
return None
def get_hw_manager():
try:
return HardwareManager()
except:
return None
class LinuxPlugin(UnixPlugin):
"""OSPlugin for Linux-based distros"""
dialer = None
hw_manager = get_hw_manager()
def __init__(self):
super(LinuxPlugin, self).__init__()
def is_valid(self):
raise NotImplementedError()
def add_default_route(self, iface):
"""See :meth:`wader.common.interfaces.IOSPlugin.add_default_route`"""
args = ['add', 'default', 'dev', iface]
return utils.getProcessValue('/sbin/route', args, reactor=reactor)
def delete_default_route(self, iface):
"""
See :meth:`wader.common.interfaces.IOSPlugin.delete_default_route`
"""
args = ['delete', 'default', 'dev', iface]
return utils.getProcessValue('/sbin/route', args, reactor=reactor)
def configure_iface(self, iface, ip='', action='up'):
"""See :meth:`wader.common.interfaces.IOSPlugin.configure_iface`"""
assert action in ['up', 'down']
if action == 'down':
args = [iface, action]
else:
args = [iface, ip, 'netmask', '255.255.255.255', '-arp', action]
return utils.getProcessValue('/sbin/ifconfig', args, reactor=reactor)
def get_iface_stats(self, iface):
"""See :meth:`wader.common.interfaces.IOSPlugin.get_iface_stats`"""
stats_path = "/sys/class/net/%s/statistics" % iface
rx_b = join(stats_path, 'rx_bytes')
tx_b = join(stats_path, 'tx_bytes')
try:
return map(int, [get_file_data(rx_b), get_file_data(tx_b)])
except (IOError, OSError):
return 0, 0
def get_additional_wvdial_ppp_options(self):
return ""
wader-0.5.13/core/oses/osx.py 0000664 0000000 0000000 00000012334 12576466102 0016002 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2008-2009 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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.
"""DevicePlugin for OSX"""
from functools import partial
import sys
from twisted.internet import defer
from twisted.python import log, reflect
from zope.interface import implements
from wader.common.consts import (MDM_INTFACE, MM_MODEM_TYPE_REV,
NET_INTFACE, MM_IP_METHOD_PPP)
from core.hardware.base import raw_identify_device
from wader.common.interfaces import IHardwareManager
from core.oses.unix import UnixPlugin
from core.plugin import PluginManager
from core.serialport import Ports
from core.startup import setup_and_export_device
class OSXPlugin(UnixPlugin):
"""OSPlugin for OSX"""
dialer = None
def __init__(self):
super(OSXPlugin, self).__init__()
self.hw_manager = HardwareManager()
def get_iface_stats(self, iface):
"""See :meth:`wader.common.interfaces.IOSPlugin.get_iface_stats`"""
# XXX: implementation missing
return 0, 0
def is_valid(self):
"""See :meth:`wader.common.interfaces.IOSPlugin.is_valid`"""
# XXX: Disable OSX support until we can find the provenance of the C
# module, or replace it with something else.
return False
return sys.platform == 'darwin'
def update_dns_cache(self):
# XXX: implementation missing
pass
class HardwareManager(object):
"""I find and configure devices"""
implements(IHardwareManager)
def __init__(self):
super(HardwareManager, self).__init__()
self.controller = None
self._device_count = -1
self.clients = {}
def register_controller(self, controller):
"""
See `IHardwareManager.register_controller`
"""
self.controller = controller
def get_devices(self):
"""See :meth:`wader.common.interfaces.IHardwareManager.get_devices`"""
# so pylint does not complain on Linux
osxserialports = reflect.namedAny('core.oses.osxserialports')
devs_info = [d for d in osxserialports.modems()
if 'Modem' in d['suffix']]
deferreds = []
for dev in devs_info:
port = dev['dialin'] if dev['dialin'] else dev['callout']
d = defer.maybeDeferred(raw_identify_device, port)
d.addCallback(self._get_device_from_model, dev)
deferreds.append(d)
d = defer.gatherResults(deferreds)
d.addCallback(self._check_if_devices_are_registered)
return d
def _get_device_from_model(self, model, dev_info):
plugin = PluginManager.get_plugin_by_remote_name(model)
if plugin:
device = dev_info['callout'].split('/')[-1]
set_property = partial(plugin.set_property, emit=True)
set_property(MDM_INTFACE, 'Device', device)
# XXX: Fix MasterDevice
set_property(MDM_INTFACE, 'MasterDevice',
'iokit:com.vodafone.BMC.NotImplemented')
# XXX: Fix CDMA
set_property(MDM_INTFACE, 'Type', MM_MODEM_TYPE_REV['GSM'])
set_property(MDM_INTFACE, 'Driver', 'notimplemented')
set_property(MDM_INTFACE, 'IpMethod', MM_IP_METHOD_PPP)
set_property(MDM_INTFACE, 'UnlockRequired', "")
# import here else we start the dbus too early in startup
from dbus import Boolean
set_property(MDM_INTFACE, 'Enabled', Boolean(False))
# set to unknown
set_property(NET_INTFACE, 'AccessTechnology', 0)
# set to -1 so any comparison will fail and will update it
set_property(NET_INTFACE, 'AllowedMode', -1)
plugin.opath = self._generate_opath()
plugin.ports = Ports(dev_info['callout'], dev_info['dialin'])
return plugin
def _check_if_devices_are_registered(self, devices):
for device in devices:
if device.opath not in self.clients:
self._register_client(device, emit=True)
return devices
def _register_client(self, plugin, emit=False):
"""
Registers `plugin` in `self.clients`
Will emit a DeviceAdded signal if emit is True
"""
log.msg("registering plugin %s with opath %s" % (plugin, plugin.opath))
self.clients[plugin.opath] = setup_and_export_device(plugin)
if emit:
self.controller.DeviceAdded(plugin.opath)
def _generate_opath(self):
self._device_count += 1
return '/org/freedesktop/ModemManager/Devices/%d' % self._device_count
wader-0.5.13/core/oses/unix.py 0000664 0000000 0000000 00000004467 12576466102 0016164 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2006-2008 Vodafone España, S.A.
# Copyright (C) 2008-2009 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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.
"""OSPlugin for Unix-based OSes"""
from twisted.python import log
from core.plugin import OSPlugin
from core.resolvconf import NamedManager
from wader.common.utils import get_file_data
class UnixPlugin(OSPlugin):
"""Plugin for Unix"""
def __init__(self):
super(UnixPlugin, self).__init__()
self.named_manager = NamedManager()
def add_dns_info(self, dns, iface=None):
"""See :meth:`wader.common.interfaces.IOSPlugin.add_dns_info`"""
self.named_manager.add_dns_info(dns)
try:
self.update_dns_cache()
except NotImplementedError:
klass = self.__class__.__name__
log.err("%s: update_dns_cache not implemented" % klass)
def delete_dns_info(self, dns, iface=None):
"""See :meth:`wader.common.interfaces.IOSPlugin.delete_dns_info`"""
self.named_manager.delete_dns_info(dns)
try:
self.update_dns_cache()
except NotImplementedError:
klass = self.__class__.__name__
log.err("%s: update_dns_cache not implemented" % klass)
def is_valid(self):
# DO NOT modify this unless you know what you are doing. This plugin
# is the parent class of LinuxPlugin/OSXPlugin/*BSDPlugin/etc. This
# is not a final implementation as there's no such thing as a Unix OS.
return False
def get_iface_stats(self, iface):
raise NotImplementedError()
def update_dns_cache(self):
raise NotImplementedError()
wader-0.5.13/core/plugin.py 0000664 0000000 0000000 00000026516 12576466102 0015525 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2006-2008 Vodafone España, S.A.
# Copyright (C) 2008-2009 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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.
"""Plugin system for Wader"""
# import inspect # for debugging only
from dbus import Boolean, UInt32
from pytz import timezone
from time import time
from twisted.internet import defer, reactor
from twisted.internet.task import deferLater
from twisted.python import log
from twisted.plugin import IPlugin, getPlugins
from zope.interface import implements
from wader.common.consts import (MDM_INTFACE, HSO_INTFACE, CRD_INTFACE,
NET_INTFACE, USD_INTFACE,
MM_MODEM_STATE_DISABLED,
MM_MODEM_STATE_ENABLED)
import wader.common.aterrors as at
import wader.common.exceptions as ex
import wader.common.interfaces as interfaces
from wader.common.utils import flatten_list
from core.daemon import build_daemon_collection
from core.sim import SIMBaseClass
import plugins
class DevicePlugin(object):
"""Base class for all plugins"""
implements(IPlugin, interfaces.IDevicePlugin)
__properties__ = {}
# at what speed should we talk with this device?
baudrate = 115200
# Class that will initialize the SIM, by default SIMBaseClass
sim_klass = SIMBaseClass
# Response of AT+CGMM
__remote_name__ = ""
# instance of a custom adapter class if device needs customization
custom = None
# instance of the exporter class that will export AT methods
exporter = None
# dialer
dialer = 'default'
# place for device specific hints to the core
quirks = {}
def __init__(self):
super(DevicePlugin, self).__init__()
# sim instance
self.sim = None
# serial connection reference
self.sconn = None
# device internal state
self._status = MM_MODEM_STATE_DISABLED
# time SIM authentication was done
self._authtime = None
# collection of daemons
self.daemons = None
# onyl used in devices that like to share ids, like
# huawei's exxx family. It should have at least a
# 'default' key mapping to a safe device that can be
# used to identify the rest of the family
self.mapping = {}
# object path
self.opath = None
# sysfs_path
self.sysfs_path = None
# dictionary with org.freedesktop.DBus.Properties
self.props = {MDM_INTFACE: {}, HSO_INTFACE: {}, CRD_INTFACE: {},
NET_INTFACE: {}, USD_INTFACE: {}}
self.ports = None
def __repr__(self):
args = (self.__class__.__name__, self.ports)
return "<%s %s>" % args
def get_property(self, iface, name):
return self.props[iface][name]
def get_properties(self, iface=None):
if iface is None:
return self.props
return self.props[iface]
def set_property(self, iface, name, value, emit=True):
self.props[iface][name] = value
if hasattr(self.exporter, 'MmPropertiesChanged') and emit:
self.exporter.MmPropertiesChanged(iface, {name: value})
def set_status(self, status, reason=UInt32(0)):
"""Sets internal device status to ``status``"""
# # uncomment for debugging only
# caller = inspect.stack()[1]
# try:
# log.msg("set_status called by %s" % str(caller))
# finally:
# del caller
if status >= MM_MODEM_STATE_ENABLED and \
self._status < MM_MODEM_STATE_ENABLED:
self.exporter.DeviceEnabled(self.opath)
self.set_property(MDM_INTFACE, 'Enabled',
Boolean(status >= MM_MODEM_STATE_ENABLED))
self.set_property(MDM_INTFACE, 'State', status)
# Default reason is MM_MODEM_STATE_CHANGED_REASON_UNKNOWN
self.exporter.StateChanged(self._status, status, reason)
self._status = status
@property
def status(self):
"""Returns the internal device status"""
return self._status
def set_authtime(self, authtime):
self._authtime = authtime
@property
def authtime(self):
"""
Returns the time in secs (UTC) that authentication was done
or None if it's not occurred yet
"""
return self._authtime
def close(self, remove_from_conn=False, removed=False):
"""Closes the plugin and frees all the associated resources"""
log.msg("Closing plugin %s" % self)
if remove_from_conn or removed:
try:
self.exporter.remove_from_connection()
except LookupError, e:
log.err(e)
def free_resources(ign=None):
"""Frees the resources held by :meth:`initialize`"""
if self.sconn is not None and self.sconn.transport:
self.sconn.transport.unregisterProducer()
if self.ports.cport.obj is not None:
self.ports.cport.obj.loseConnection("Bye!")
self.ports.cport.obj = None
# if our device was pulled out whilst we were connected
if self.ports.dport.obj is not None:
if self.ports.dport.obj.isOpen():
self.ports.dport.obj.close()
self.ports.dport.obj = None
if self.daemons is not None and self.daemons.running:
self.daemons.stop_daemons()
d = defer.succeed(True)
d.addCallback(self.sconn.stop_traffic_monitoring)
if not removed:
d.addCallback(lambda _: self.sconn.enable_radio(False))
d.addCallback(lambda _: self.set_status(MM_MODEM_STATE_DISABLED))
d.addCallback(free_resources)
return d
def initialize(self, init_obj=None):
"""Initializes the SIM"""
def on_init(size):
if not self.daemons:
self.daemons = build_daemon_collection(self)
self.daemons.start_daemons()
d = self.sconn.init_properties()
def set_enable(_):
if self._status <= MM_MODEM_STATE_ENABLED:
self.set_status(MM_MODEM_STATE_ENABLED)
return _
d.addCallback(set_enable)
d.addCallback(lambda _: self.sconn.mal.initialize(obj=self.sconn))
d.addCallback(lambda _: size)
return d
def initialize_sim(_):
self.sim = self.sim_klass(self.sconn)
d = self.sim.initialize()
d.addCallback(on_init)
return d
# XXX: Sometimes, right after a combination of Modem.Enable operations
# and hot pluggings, the core will not reply to the first AT
# command sent, but it will to the second. This was an old comment
# and it's not certain that this still occurs; if symptoms persist
# we'll need to re-add handling for this case.
if self.authtime is None:
log.err("Being asked to initialise before auth has been checked")
raise RuntimeError
def do_init():
if not (self.sconn and self.sconn.transport):
return defer.fail(at.SerialSendFailed())
log.msg('Enabling radio and initialising SIM')
d = self.sconn.enable_radio(True)
d.addCallback(initialize_sim)
d.addErrback(log.err)
return d
remaining = (self.authtime + self.custom.auth_klass.DELAY) - time() + 1
if remaining > 0:
log.msg('Delaying SIM initialisation for %d secs' % remaining)
d = deferLater(reactor, remaining, do_init)
else:
d = do_init()
return d
def patch(self, other):
"""Patch myself in-place with the settings of another plugin"""
if not isinstance(other, DevicePlugin):
raise ValueError("Cannot patch myself with a %s" % type(other))
self.opath = other.opath
self.sysfs_path = other.sysfs_path
self.ports = other.ports
self.props = other.props.copy()
self.baudrate = other.baudrate
class RemoteDevicePlugin(DevicePlugin):
"""
Base class from which all the RemoteDevicePlugins should inherit from
"""
implements(IPlugin, interfaces.IRemoteDevicePlugin)
class OSPlugin(object):
"""Base class from which all the OSPlugins should inherit from"""
implements(IPlugin, interfaces.IOSPlugin)
dialer = None
hw_manager = None
def __init__(self):
super(OSPlugin, self).__init__()
def get_iface_stats(self, iface):
"""
Returns ``iface`` network statistics
:rtype: tuple
"""
raise NotImplementedError()
def is_valid(self):
"""Returns True if we are on the given OS/Distro"""
raise NotImplementedError()
def initialize(self):
"""Initializes the plugin"""
pass
class PluginManager(object):
"""I manage Wader's plugins"""
@classmethod
def get_plugins(cls, interface=IPlugin, package=plugins):
"""
Returns all the plugins under ``package`` that implement ``interface``
"""
return getPlugins(interface, package)
@classmethod
def get_plugin_by_remote_name(cls, name,
interface=interfaces.IDevicePlugin):
"""
Get a plugin by its remote name
:raise UnknownPluginNameError: When we don't know about the plugin
"""
for plugin in cls.get_plugins(interface, plugins):
if not hasattr(plugin, '__remote_name__'):
continue
if plugin.__remote_name__ == name:
return plugin
if name in plugin.mapping:
return plugin.mapping[name]()
raise ex.UnknownPluginNameError(name)
@classmethod
def get_plugin_by_vendor_product_id(cls, product_id, vendor_id):
"""Get a plugin by its product and vendor ids"""
log.msg("get_plugin_by_id called with 0x%04x and 0x%04x"
% (product_id, vendor_id))
for plugin in cls.get_plugins(interfaces.IDevicePlugin):
props = flatten_list(plugin.__properties__.values())
if int(product_id) in props and int(vendor_id) in props:
if not plugin.mapping:
# regular plugin
return plugin
# device has multiple personalities...
# this will just return the default plugin for
# the mapping, we keep a reference to the mapping
# once the device is properly identified by
# core.hardware.base::identify_device
_plugin = plugin.mapping['default']()
_plugin.mapping = plugin.mapping
return _plugin
return None
wader-0.5.13/core/protocol.py 0000664 0000000 0000000 00000077507 12576466102 0016076 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2006-2008 Vodafone España, S.A.
# Copyright (C) 2008-2009 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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.
"""Twisted protocols for serial communication"""
import re
from twisted.internet import protocol, defer, reactor
from twisted.python.failure import Failure
from twisted.python import log
import wader.common.aterrors as E
import wader.common.signals as S
from core.command import ATCmd
# Standard unsolicited notifications
CALL_RECV = re.compile('\r\nRING\r\n')
STK_DEBUG = re.compile('\r\n\+STC:\s\d+\r\n')
# Standard solicited notifications
SMS_RECEIVED = re.compile('\r\n\+CMTI:\s"(?P\w{2,})",(?P\d+)\r\n')
SMS_DELIVERY = re.compile('\r\n\+CDS:\s\d+\r\n(?P[A-Za-z0-9]+)\r\n')
SPLIT_PROMPT = re.compile('\r?\r\n>\s$')
CREG_REGEXP = re.compile('\r\n\+CREG:\s*(?P\d)\r\n')
class BufferingStateMachine(object, protocol.Protocol):
"""A simple SM that handles low level communication with the device"""
def __init__(self, device):
super(BufferingStateMachine, self).__init__()
self.device = device
# a reference to the customizer class for notifications
self.custom = device.custom
# current AT command
self.cmd = None
self.state = 'idle'
# idle and wait buffers
self.idlebuf = ""
self.waitbuf = ""
# log prefix for situations where the prefix is not appended
self._prefix = ""
def _get_log_prefix(self):
try:
if not self._prefix:
port = self.device.ports.get_application_port()
self._prefix = port.obj.logPrefix()
except AttributeError:
self._prefix = ''
return self._prefix
def _timeout_eb(self):
"""Executed when a command exceeds its timeout"""
msg = "Command '%r' timed out, this is my waitbuf: %s"
e = E.SerialResponseTimeout(msg % (self.cmd, self.waitbuf))
self.notify_failure(e)
self.transition_to_idle()
def cancel_current_delayed_call(self):
"""
Cancels current :class:`~core.command.ATCmd` delayed call
"""
if self.cmd.call_id and self.cmd.call_id.active():
self.cmd.call_id.cancel()
def notify_success(self, result):
"""
Notify success to current :class:`~core.command.ATCmd`
"""
self.cancel_current_delayed_call()
try:
self.cmd.deferred.callback(result)
except Exception, e:
args = (self.cmd, result)
log.err(e, "'%r' callback failed with args '%s'" % args)
def notify_failure(self, failure):
"""Notify failure to current :class:`~core.command.ATCmd`"""
self.cancel_current_delayed_call()
self.cmd.deferred.errback(failure)
def set_cmd(self, cmd):
"""
Sets ``cmd`` as the next command to process
It also sets an initial timeout and transitions to waiting state
"""
self.cmd = cmd
# set the timeout for this command
self.cmd.call_id = reactor.callLater(cmd.timeout, self._timeout_eb)
self.set_state('waiting')
def set_state(self, new_state):
"""Sets the new state ``new_state``"""
log.msg("state change: %s -> %s" % (self.state, new_state),
system=self._get_log_prefix())
# the system line got added because no suffix was being added
# to the log in set_state
self.state = new_state
def transition_to_idle(self):
"""Transitions to idle state and cleans internal buffers"""
self.cmd = None
self.set_state('idle')
self.idlebuf = ""
self.waitbuf = ""
def send_splitcmd(self):
"""
Used to send the second part of a split command after prompt appears
"""
raise NotImplementedError()
def emit_signal(self, signal, *args, **kwds):
"""
Emits ``signal``
:param signal: The name of the signal to emit
:param args: The arguments for the signal ``signal``
:param kwds: The keywords for the signal ``signal``
"""
method = getattr(self.device.exporter, signal, None)
if method:
method(*args, **kwds)
else:
log.err("No method registered for signal %s" % signal)
def connectionLost(self, reason):
super(BufferingStateMachine, self).connectionLost(reason)
log.msg("Serial connection was lost")
self.transport = None
def dataReceived(self, data):
"""See `twisted.internet.protocol.Protocol.dataReceived`"""
# XXX: Change the following zero to one to log all data from the modem
if 0:
log.msg('dataReceived: %s' % str(data))
state = 'handle_%s' % self.state
getattr(self, state)(data)
def process_notifications(self, _buffer):
"""
Processes unsolicited notifications in ``_buffer``
:param _buffer: Buffer to scan
"""
if not self.device.custom or not self.device.custom.async_regexp:
return _buffer
custom = self.device.custom
# we have to use re.finditer as some cards like to pipeline
# several asynchronous notifications in one
for match in re.finditer(custom.async_regexp, _buffer):
name, value = match.groups()
if name in custom.signal_translations:
# we obtain the signal name and the associated function
# that will translate the device unsolicited message to
# the signal used in Wader internally
signal, func = custom.signal_translations[name]
# if we have a transform function defined, then use it
# otherwise use value as args
if func:
try:
args = func(value, self.device)
except Exception, e:
msg = "%s can not handle notification %s"
log.err(e, msg % (func, value))
args = value
if signal is not None:
self.emit_signal(signal, args)
# remove from the idlebuf the match (but only once please)
_buffer = _buffer.replace(match.group(), '', 1)
return _buffer
def process_notification_sms_received(self, _buffer):
while True:
match = SMS_RECEIVED.match(_buffer)
if not match:
break
mal = getattr(self, 'mal', None)
if mal:
index = int(match.group('id'))
mal.on_sms_notification(index)
_buffer = _buffer.replace(match.group(), '', 1)
return _buffer
def process_notification_creg_received(self, _buffer):
while True:
match = CREG_REGEXP.match(_buffer)
if not match:
break
status = int(match.group('status'))
self.emit_signal(S.SIG_CREG, status)
_buffer = _buffer.replace(match.group(), '', 1)
return _buffer
def handle_idle(self, data):
"""
Processes ``data`` in `idle` state
Being in `idle` state, there are six possible events that must be
handled:
- STK init garbage
- Call received (we're not handling it in waiting)
- A SMS arrived
- SMS notification (Not handled yet)
- Device's own unsolicited notifications
- Default: i.e. this device originated a notification that we don't
understand yet, the point is to log it and make it visible so the
user can report it to us
"""
log.msg("idle: %r" % data)
self.idlebuf += data
# most possible event:
# device's own unsolicited notifications
# signal translations stuff
self.idlebuf = self.process_notifications(self.idlebuf)
if not self.idlebuf:
return
# second most possible event:
# new SMS arrived
self.idlebuf = self.process_notification_sms_received(self.idlebuf)
if not self.idlebuf:
return
# third most possible event:
# SMS delivery report
match = SMS_DELIVERY.match(self.idlebuf)
if match:
mal = getattr(self, 'mal', None)
if mal:
pdu = match.group('pdu')
mal.on_sms_delivery_report(pdu)
# the report might be repeated, consume as many as possible
# idle: unmatched data '\r\n+CDS: 27\r\n07914306073011F006C
# 00B914306565711F90120910134454001209101344540000100\r\n\r
# \n+CDS: 27\r\n07914306073011F006C10B914306565711F90120910
# 144454001209101444540000100\r\n'
self.idlebuf = self.idlebuf.replace(match.group(), '')
if not self.idlebuf:
return
# fourth most possible event
match = STK_DEBUG.match(self.idlebuf)
if match:
self.idlebuf = self.idlebuf.replace(match.group(), '')
if not self.idlebuf:
return
# fifth most possible event
self.idlebuf = self.process_notification_creg_received(self.idlebuf)
if not self.idlebuf:
return
# sixth most possible event:
match = CALL_RECV.match(self.idlebuf)
if match:
self.emit_signal(S.SIG_CALL)
self.idlebuf = self.idlebuf.replace(match.group(), '')
if not self.idlebuf:
return
log.msg("idle: unmatched data %r" % self.idlebuf)
def handle_waiting(self, data):
"""Process ``data`` in the wait state"""
self.waitbuf += data
self.waitbuf = self.process_notifications(self.waitbuf)
if not self.waitbuf:
return
# new SMS arrived
self.waitbuf = self.process_notification_sms_received(self.waitbuf)
if not self.waitbuf:
return
# CREG arrived
self.waitbuf = self.process_notification_creg_received(self.waitbuf)
if not self.waitbuf:
return
try:
cmdinfo = self.custom.cmd_dict[self.cmd.name]
except KeyError, e:
log.err(e, 'command %s not present in my cmd dict' % self.cmd)
return self.transition_to_idle()
match = cmdinfo['end'].search(self.waitbuf)
if match: # end of response
if cmdinfo['extract']:
# There's an regex to extract info from data
response = list(re.finditer(cmdinfo['extract'], self.waitbuf))
resp_repr = str([m.groups() for m in response])
log.msg("%s: callback = %s" % (self.state, resp_repr))
self.notify_success(response)
# now clean self.waitbuf
for _m in response:
self.waitbuf = self.waitbuf.replace(_m.group(), '', 1)
# now clean end of command
endmatch = cmdinfo['end'].search(self.waitbuf)
if endmatch:
self.waitbuf = self.waitbuf.replace(endmatch.group(),
'', 1)
else:
# there's no regex in cmdinfo to extract info
log.msg("%s: no callback registered" % self.state)
self.notify_success(self.waitbuf)
self.waitbuf = self.waitbuf.replace(match.group(), '', 1)
self.transition_to_idle()
else:
# there is no end of response detected, so we have either an error
# or a split command (like send_sms, save_sms, etc.)
match = E.extract_error(self.waitbuf)
if match:
exception, error, m = match
e = exception(error)
f = Failure(e)
if not f.check(*self.cmd.nolog):
log.err(e, "waiting")
# send the failure back
self.notify_failure(f)
# remove the exception string from the waitbuf
self.waitbuf = self.waitbuf.replace(m.group(), '', 1)
self.transition_to_idle()
else:
match = SPLIT_PROMPT.search(data)
if match:
log.msg("waiting: split command prompt detected")
self.send_splitcmd()
self.waitbuf = self.waitbuf.replace(match.group(), '', 1)
else:
log.msg("waiting: unmatched data %r" % data)
class SerialProtocol(BufferingStateMachine):
"""
I define the protocol used to communicate with the SIM card
SerialProtocol communicates with the SIM synchronously, only one command
at a time. However, SerialProtocol offers an asynchronous interface
:meth:`SerialProtocol.queue_at_cmd` which accepts and queues an
:class:`~core.command.ATCmd` and returns a
:class:`~twisted.internet.defer.Deferred` that will be callbacked with
the commands response, or errback if an exception is
raised.
SerialProtocol actually is an specially tailored Finite State Machine.
After several redesigns and simplifications, this FSM has just two states:
- idle: sitting idle for user input or an unsolicited response, when a
command is received we send the command and transition to the waiting
state.
- waiting: the FSM is buffering and parsing all the SIM's response to the
command till it matches the regexp that signals the end of the command.
If the command has an associated regexp to extract information, the
buffered response will be parsed and the command's deferred will be
callbacked with the regexp as argument. There are commands that don't
have an associated regexp to extract information as we are not
interested in the "all went ok" response, only if an exception
occurred -e.g. when deleting a contact we are only interested if
something went wrong, not if all went ok.
The transition to each state is driven by regular expressions, each
command has associated a set of regular expressions that make the FSM
change states. This regexps are defined in
:obj:`core.command.CMD_DICT` although the plugin mechanism
offers the possibility of customizing the CMD_DICT through
:class:`~core.hardware.base.Customizer` if a card uses a
different AT string than the rest for that particular command.
"""
def __init__(self, device):
super(SerialProtocol, self).__init__(device)
self.queue = defer.DeferredQueue()
self.mutex = defer.DeferredLock()
self._check_queue()
def transition_to_idle(self):
"""Transitions to idle state and processes next queued `ATCmd`"""
super(SerialProtocol, self).transition_to_idle()
# release the lock and check the queue
if self.mutex.locked:
self.mutex.release()
self._check_queue()
def send_splitcmd(self):
"""
Used to send the second part of a split command after prompt appears
"""
self.transport.write(self.cmd.splitcmd)
def _process_at_cmd(self, cmd):
def _transition_and_send(_):
self.set_cmd(cmd)
msg = "%s: sending %r" % (self.state, cmd.cmd)
if self.transport is None: # Seems that the device went away
log.msg(msg + ' cancelled', system=self._get_log_prefix())
self.notify_failure(E.SerialSendFailed('port disappeared'))
self.transition_to_idle()
else:
log.msg(msg, system=self._get_log_prefix())
self.transport.write(cmd.get_cmd())
d = self.mutex.acquire()
d.addCallback(_transition_and_send)
def _check_queue(self):
# when the next element of the queue is put, _process_at_cmd will be
# callbacked with it
d = self.queue.get()
d.addCallback(self._process_at_cmd)
def queue_at_cmd(self, cmd):
"""
Queues an :class:`~core.command.ATCmd` ``cmd``
This deferred will be callbacked with the command's response
:rtype: `Deferred`
"""
self.queue.put(cmd)
return cmd.deferred
class WCDMAProtocol(SerialProtocol):
"""
A Twisted protocol to chat with WCDMA devices
I am able to speak with most WCDMA devices, if you want to customize
the command being sent for a particular command, subclass me.
"""
def __init__(self, device):
super(WCDMAProtocol, self).__init__(device)
def add_contact(self, name, number, index):
"""
Adds a contact to the SIM card
:param name: The contact name
:param number: The contact number
:param index: The contact index
"""
category = 145 if number.startswith('+') else 129
args = (index, number, category, name)
cmd = ATCmd('AT+CPBW=%d,"%s",%d,"%s"' % args, name='add_contact')
return self.queue_at_cmd(cmd)
def cancel_ussd(self):
"""Cancels an ongoing USSD session"""
cmd = ATCmd("AT+CUSD=2", name='cancel_ussd')
return self.queue_at_cmd(cmd)
def change_pin(self, oldpin, newpin):
"""
Changes ``oldpin`` to ``newpin`` in the SIM card
:type oldpin: str
:type newpin: str
:raise General: When the password is incorrect.
:raise IncorrectPassword: When the password is incorrect.
:raise InputValueError: When the PIN != \d{4}
"""
atstr = 'AT+CPWD="SC","%s","%s"' % (str(oldpin), str(newpin))
cmd = ATCmd(atstr, name='change_pin')
return self.queue_at_cmd(cmd)
def check_pin(self):
"""
Checks what's necessary to authenticate against the SIM card
:raise SimBusy: When the SIM is not ready
:raise SimNotStarted: When the SIM is not ready
:raise SimFailure: This exception is raised by Option's colt when
authentication is disabled
:rtype: str
"""
# Note: don't log these exceptions
exceptions = (E.SimFailure,)
cmd = ATCmd('AT+CPIN?', name='check_pin', nolog=exceptions)
return self.queue_at_cmd(cmd)
def delete_all_contacts(self):
"""Deletes all the contacts in SIM card, function useful for tests"""
d = self.get_used_contact_ids()
def list_contacts_ids_cb(used):
if not used:
return True
return defer.gatherResults(map(self.delete_contact, used))
d.addCallback(list_contacts_ids_cb)
return d
def delete_all_sms(self):
"""Deletes all the messages in SIM card, function useful for tests"""
d = self.get_used_sms_ids()
def delete_all_sms_cb(used):
if not used:
return True
return defer.gatherResults(map(self.delete_sms, used))
d.addCallback(delete_all_sms_cb)
return d
def delete_contact(self, index):
"""Deletes the contact specified by ``index``"""
cmd = ATCmd('AT+CPBW=%d' % index, name='delete_contact')
return self.queue_at_cmd(cmd)
def delete_sms(self, index):
"""Deletes the message specified by ``index``"""
cmd = ATCmd('AT+CMGD=%d' % index, name='delete_sms')
return self.queue_at_cmd(cmd)
def disable_echo(self):
"""Disables echo of AT cmds"""
cmd = ATCmd('ATE0', name='disable_echo')
return self.queue_at_cmd(cmd)
def enable_echo(self):
"""Enables echo of AT cmds"""
cmd = ATCmd('ATE1', name='enable_echo')
return self.queue_at_cmd(cmd)
def enable_pin(self, pin, enable):
"""
Enables pin authentication at startup
:type pin: int
:type enable: bool
:raise General: If ``pin`` is incorrect.
:raise IncorrectPassword: If ``pin`` is incorrect.
:raise ValueError: When ``pin`` != \d{4}
"""
at_str = 'AT+CLCK="SC",%d,"%s"' % (int(enable), str(pin))
cmd = ATCmd(at_str, name='enable_pin')
return self.queue_at_cmd(cmd)
def enable_radio(self, enable):
"""
Enables/disable radio stack
"""
cmd = ATCmd("AT+CFUN=%d" % int(enable), name='enable_radio')
cmd.timeout = 30
return self.queue_at_cmd(cmd)
def find_contacts(self, pattern):
"""Returns a list of contacts that match ``pattern``"""
cmd = ATCmd('AT+CPBF="%s"' % pattern, name='find_contacts')
return self.queue_at_cmd(cmd)
def get_apns(self):
"""Returns all the APNs in the SIM"""
cmd = ATCmd('AT+CGDCONT?', name='get_apns')
return self.queue_at_cmd(cmd)
def get_apn_range(self):
"""Returns the range of APN context IDs in the SIM"""
cmd = ATCmd('AT+CGDCONT=?', name='get_apn_range')
return self.queue_at_cmd(cmd)
def get_card_model(self):
"""Returns the SIM card model"""
cmd = ATCmd('AT+CGMM', name='get_card_model')
return self.queue_at_cmd(cmd)
def get_card_version(self):
"""Returns the SIM card version"""
cmd = ATCmd('AT+CGMR', name='get_card_version')
return self.queue_at_cmd(cmd)
def get_charset(self):
"""Returns the current character set name"""
cmd = ATCmd('AT+CSCS?', name='get_charset')
return self.queue_at_cmd(cmd)
def get_charsets(self):
"""Returns the available charsets"""
cmd = ATCmd('AT+CSCS=?', name='get_charsets')
return self.queue_at_cmd(cmd)
def get_contact(self, index):
"""Returns the contact at ``index``"""
cmd = ATCmd('AT+CPBR=%d' % index, name='get_contact')
return self.queue_at_cmd(cmd)
def list_contacts(self):
"""
Returns all the contacts stored in the SIM card
:raise General: When no contacts are found.
:raise NotFound: When no contacts are found.
:raise SimBusy: When the SIM is not ready.
:raise SimNotStarted: When the SIM is not ready.
:rtype: list
"""
# Note: don't log these exceptions
exceptions = (E.General, E.NotFound)
cmd = ATCmd('AT+CPBR=1,%d' % self.device.sim.size,
name='list_contacts', nolog=exceptions)
return self.queue_at_cmd(cmd)
def get_imei(self):
"""Returns the IMEI number of the SIM card"""
cmd = ATCmd('AT+CGSN', name='get_imei')
return self.queue_at_cmd(cmd)
def get_imsi(self):
"""Returns the IMSI number of the SIM card"""
cmd = ATCmd('AT+CIMI', name='get_imsi')
return self.queue_at_cmd(cmd)
def get_manufacturer_name(self):
"""Returns the manufacturer name of the SIM card"""
cmd = ATCmd('AT+GMI', name='get_manufacturer_name')
return self.queue_at_cmd(cmd)
def get_netreg_status(self):
"""Returns the network registration status"""
cmd = ATCmd('AT+CREG?', name='get_netreg_status')
return self.queue_at_cmd(cmd)
def get_network_info(self, _type=None):
"""Returns a tuple with the network info"""
if _type is 'name':
s = 'AT+COPS=3,0;+COPS?'
elif _type is 'numeric':
s = 'AT+COPS=3,2;+COPS?'
else:
s = 'AT+COPS?'
cmd = ATCmd(s, name='get_network_info')
return self.queue_at_cmd(cmd)
def get_network_names(self):
"""Returns a tuple with the network info"""
cmd = ATCmd('AT+COPS=?', name='get_network_names')
cmd.timeout = 300
return self.queue_at_cmd(cmd)
def get_phonebook_size(self):
"""
Returns the phonebook size of the SIM card
:raise General: When the SIM is not ready.
:raise SimBusy: When the SIM is not ready.
:raise CMSError500: When the SIM is not ready.
"""
cmd = ATCmd('AT+CPBR=?', name='get_phonebook_size')
cmd.timeout = 15
return self.queue_at_cmd(cmd)
def get_pin_status(self):
"""Checks whether the pin is enabled or disabled"""
cmd = ATCmd('AT+CLCK="SC",2', name='get_pin_status')
return self.queue_at_cmd(cmd)
def get_radio_status(self):
"""Returns whether the radio is enabled or disabled"""
cmd = ATCmd("AT+CFUN?", name='get_radio_status')
return self.queue_at_cmd(cmd)
def get_roaming_ids(self):
"""Returns a list with the networks we can register with"""
cmd = ATCmd('AT+CPOL?', name='get_roaming_ids')
return self.queue_at_cmd(cmd)
def get_signal_quality(self):
"""Returns a tuple with the RSSI and BER of the connection"""
cmd = ATCmd('AT+CSQ', name='get_signal_quality')
return self.queue_at_cmd(cmd)
def list_sms(self):
"""
Returns all the messages stored in the SIM card
:raise General: When no messages are found.
:raise NotFound: When no messages are found.
:rtype: list
"""
cmd = ATCmd('AT+CMGL=4', name='list_sms')
return self.queue_at_cmd(cmd)
def get_sms(self, index):
"""Returns the message stored at ``index``"""
cmd = ATCmd('AT+CMGR=%d' % index, name='get_sms')
return self.queue_at_cmd(cmd)
def get_sms_format(self):
"""Returns the message stored at ``index``"""
cmd = ATCmd('AT+CMGF?', name='get_sms_format')
return self.queue_at_cmd(cmd)
def get_smsc(self):
"""Returns the SMSC stored in the SIM"""
cmd = ATCmd('AT+CSCA?', name='get_smsc')
return self.queue_at_cmd(cmd)
def get_used_contact_ids(self):
"""Returns a list with the used contact ids"""
def errback(failure):
failure.trap(E.NotFound, E.General)
return []
d = self.list_contacts()
d.addCallback(lambda contacts: [int(c.group('id')) for c in contacts])
d.addErrback(errback)
return d
def get_used_sms_ids(self):
"""Returns a list with used SMS ids in the SIM card"""
d = self.list_sms()
def errback(failure):
failure.trap(E.NotFound, E.General)
return []
d.addCallback(lambda smslist: [int(s.group('id')) for s in smslist])
d.addErrback(errback)
return d
def register_with_netid(self, netid, mode=1, _format=2):
"""Registers with ``netid``"""
atstr = 'AT+COPS=%d,%d,"%s"' % (mode, _format, netid)
cmd = ATCmd(atstr, name='register_with_netid')
cmd.timeout = 30
return self.queue_at_cmd(cmd)
def reset_settings(self):
"""Resets the settings to factory settings"""
cmd = ATCmd('ATZ', name='reset_settings')
return self.queue_at_cmd(cmd)
def save_sms(self, pdu, pdu_len):
"""Returns the index where ``pdu`` was stored"""
cmd = ATCmd('AT+CMGW=%s' % pdu_len, name='save_sms', eol='\r')
cmd.splitcmd = '%s\x1a' % pdu
return self.queue_at_cmd(cmd)
def send_pin(self, pin):
"""
Authenticates using ``pin``
:raise General: Exception raised by Nozomi when PIN is incorrect.
:raise IncorrectPassword: Exception raised when the PIN is incorrect
"""
cmd = ATCmd('AT+CPIN="%s"' % str(pin), name='send_pin')
return self.queue_at_cmd(cmd)
def send_puk(self, puk, pin):
"""
Authenticates using ``puk`` and ``pin``
:raise General: Exception raised by Nozomi when PUK is incorrect.
:raise IncorrectPassword: Exception raised when the PUK is incorrect
"""
atstr = 'AT+CPIN="%s","%s"' % (str(puk), str(pin))
cmd = ATCmd(atstr, name='send_puk')
return self.queue_at_cmd(cmd)
def send_sms(self, pdu, pdu_len):
"""Sends the given pdu and returns the index"""
cmd = ATCmd('AT+CMGS=%d' % pdu_len, name='send_sms', eol='\r')
cmd.splitcmd = '%s\x1a' % pdu
return self.queue_at_cmd(cmd)
def send_sms_from_storage(self, index):
"""Sends the SMS stored at ``index`` and returns the new index"""
cmd = ATCmd('AT+CMSS=%d' % index, name='send_sms_from_storage')
return self.queue_at_cmd(cmd)
def send_ussd(self, ussd):
"""Sends the USSD command ``ussd``"""
dcs = 15
cmd = ATCmd('AT+CUSD=1,"%s",%d' % (ussd, dcs), name='send_ussd')
cmd.timeout = 30
return self.queue_at_cmd(cmd)
def set_apn(self, index, apn):
"""Sets the APN to ``apn`` using ``index``"""
cmd = ATCmd('AT+CGDCONT=%d,"IP","%s"' % (index, apn), name='set_apn')
return self.queue_at_cmd(cmd)
def set_charset(self, charset):
"""Sets the character set used on the SIM"""
cmd = ATCmd('AT+CSCS="%s"' % charset, name='set_charset')
return self.queue_at_cmd(cmd)
def set_error_level(self, level):
"""Sets the modem error level"""
cmd = ATCmd('AT+CMEE=%d' % level, name='set_error_level')
return self.queue_at_cmd(cmd)
def set_netreg_notification(self, val=1):
"""Sets CREG unsolicited notification"""
cmd = ATCmd('AT+CREG=%d' % val, name='set_netreg_notification')
return self.queue_at_cmd(cmd)
def set_network_info_format(self, mode=0, _format=2):
"""Sets the network information format for +COPS queries"""
cmd = ATCmd('AT+COPS=%d,%d' % (mode, _format),
name='set_network_info_format')
return self.queue_at_cmd(cmd)
def set_sms_format(self, _format=0):
"""Sets the format of the SMS"""
cmd = ATCmd('AT+CMGF=%d' % _format, name='set_sms_format')
return self.queue_at_cmd(cmd)
def set_sms_indication(self, mode=2, mt=1, bm=0, ds=0, bfr=0):
"""Sets the SMS indication mode"""
args = 'AT+CNMI=' + ','.join(map(str, [mode, mt, bm, ds, bfr]))
cmd = ATCmd(args, name='set_sms_indication')
return self.queue_at_cmd(cmd)
def set_smsc(self, number):
"""Sets the SMSC"""
cmd = ATCmd('AT+CSCA="%s"' % number, name='set_smsc')
return self.queue_at_cmd(cmd)
def send_at(self, at_str, name='send_at', timeout=None):
"""Send an arbitrary AT string to the SIM card"""
cmd = ATCmd(at_str, name=name)
if timeout:
cmd.timeout = timeout
return self.queue_at_cmd(cmd)
def sim_access_restricted(self, command, fileid=None, p1=None,
p2=None, p3=None, data=None, pathid=None):
"""
Implements CRSM AT command.
command is only mandatory argument.
:param command: command integer number.
:type command: int
"""
if fileid is None:
cmd = ATCmd('AT+CRSM=%d' % command,
name='sim_access_restricted')
elif p1 is None or p2 is None or p3 is None:
cmd = ATCmd('AT+CRSM=%d,%d' % (command, fileid),
name='sim_access_restricted')
elif data is None:
cmd = ATCmd('AT+CRSM=%d,%d,%d,%d,%d' %
(command, fileid, p1, p2, p3),
name='sim_access_restricted')
elif pathid is None:
cmd = ATCmd('AT+CRSM=%d,%d,%d,%d,%d,%s' %
(command, fileid, p1, p2, p3, data),
name='sim_access_restricted')
else:
cmd = ATCmd('AT+CRSM=%d,%d,%d,%d,%d,%s,%s' %
(command, fileid, p1, p2, p3, data, pathid),
name='sim_access_restricted')
return self.queue_at_cmd(cmd)
wader-0.5.13/core/resolvconf.py 0000664 0000000 0000000 00000004012 12576466102 0016372 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2008-2009 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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 os
from twisted.python import log
from wader.common.utils import get_file_data, save_file
class NamedManager(object):
def __init__(self, resolvconf_path='/etc/resolv.conf'):
self.resolvconf_path = resolvconf_path
self.old_contents = None
def add_dns_info(self, dns):
if self.old_contents is not None:
# just log it, don't fail
msg = "NamedManager old_contents not empty: %s"
log.err(msg % self.old_contents)
return
# backup old data
real_path = os.path.realpath(self.resolvconf_path)
self.old_contents = get_file_data(real_path)
# set new DNS data
to_write = []
if dns:
to_write.append("# Generated by Wader")
for d in dns:
to_write.append("nameserver %s" % d)
to_write.append('')
save_file(real_path, '\n'.join(to_write))
def delete_dns_info(self, dns):
if self.old_contents is None:
# just log it, don't fail
log.err("NamedManager old_contents is None")
return
real_path = os.path.realpath(self.resolvconf_path)
save_file(real_path, self.old_contents)
self.old_contents = None
wader-0.5.13/core/serialport.py 0000664 0000000 0000000 00000006145 12576466102 0016407 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2006-2008 Vodafone España, S.A.
# Copyright (C) 2008-2009 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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.
"""Logging Serial Port and related classes"""
from twisted import version as TwistedVersion
from twisted.internet.serialport import SerialPort as _SerialPort
from twisted.python import log
class Port(object):
"""I represent a serial port in Wader"""
def __init__(self, path):
self._port_path = path
self._sport_obj = None
def get_port_path(self):
return self._port_path
def set_port_path(self, path):
self._port_path = path
def get_sport_obj(self):
return self._sport_obj
def set_sport_obj(self, obj):
self._sport_obj = obj
path = property(get_port_path, set_port_path)
obj = property(get_sport_obj, set_sport_obj)
class Ports(object):
"""I am a pair of :class:`~core.serialport.Port` objects"""
def __init__(self, dport, cport):
self.dport = Port(dport)
self.cport = Port(cport)
def get_application_port(self):
"""Returns the application port"""
if self.cport.path:
return self.cport
elif self.dport.path:
return self.dport
else:
raise AttributeError("No application port")
def has_two(self):
"""
Check if there are two active ports
:rtype: bool
"""
return all([self.dport.path, self.cport.path])
def __repr__(self):
if not self.cport.path:
return "dport: %s" % (self.dport.path)
return "dport: %s cport: %s" % (self.dport.path, self.cport.path)
class SerialPort(_SerialPort, log.Logger):
"""Small wrapper over Twisted's serial port to make it loggable"""
def __init__(self, protocol, port, reactor, baudrate=115200, timeout=.1):
super(SerialPort, self).__init__(protocol, port, reactor,
baudrate=baudrate, timeout=timeout)
self._port = port
# Note: fixup a long standing bug in twisted itself
def connectionLost(self, reason):
super(SerialPort, self).connectionLost(reason)
if TwistedVersion.major < 11 or \
(TwistedVersion.major == 11 and TwistedVersion.minor < 1):
self.protocol.connectionLost(reason)
def logPrefix(self):
"""Returns the last part of the port being used"""
return self._port.split('/')[-1]
wader-0.5.13/core/shell.py 0000664 0000000 0000000 00000003111 12576466102 0015320 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2006-2008 Vodafone España, S.A.
# Copyright (C) 2008-2009 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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.
"""Module to obtain an introspection shell"""
from twisted.cred import portal, checkers
from twisted.conch import manhole, manhole_ssh
def get_manhole_factory(namespace, **passwords):
"""
Returns a ``ConchFactory`` instance configured with given settings
:param namespace: The namespace to use
:param passwords: The passwords to use
:rtype: `twisted.conch.manhole_ssh.ConchFactory`
"""
realm = manhole_ssh.TerminalRealm()
def getManhole(_):
return manhole.Manhole(namespace)
realm.chainedProtocolFactory.protocolFactory = getManhole
p = portal.Portal(realm)
checker = checkers.InMemoryUsernamePasswordDatabaseDontUse(**passwords)
p.registerChecker(checker)
return manhole_ssh.ConchFactory(p)
wader-0.5.13/core/sim.py 0000664 0000000 0000000 00000011437 12576466102 0015013 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2006-2008 Vodafone España, S.A.
# Copyright (C) 2008-2009 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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.
"""SIM startup module"""
from twisted.python import log
from twisted.internet import defer, reactor
import wader.common.aterrors as E
from wader.common.encoding import from_ucs2
RETRY_ATTEMPTS = 3
RETRY_TIMEOUT = 3
# SIM files:
# 3GPP TS 31.102 V10.21.0 (2011-064)
EF_AD = 0x6fad # 28589
EF_SPN = 0x6f46 # 28486
# ETSI TS 102 221 V8.2.0 (2009-06)
EF_ICCID = 0x2fe2 # 12285
# SIM Commands:
# ETSI TS 102 221 V8.2.0 (2009-06): Commands
# 3GPP TS 27.007 V10.4.0 (2011-06): Restricted SIM access +CRSM
COM_READ_BINARY = 0xb0 # 176
COM_READ_RECORD = 0xb2 # 178
COM_GET_RESPONSE = 0xc0 # 192
COM_UPDATE_BINARY = 0xd6 # 214
COM_UPDATE_RECORD = 0xdc # 220
COM_STATUS = 0xf2 # 242
COM_RETRIEVE_DATA = 0xcb # 203
COM_SET_DATA = 0xdb # 219
# Successfull status bytes
# ETSI TS 102 221 V8.2.0 (2009-06)
SW_OK = [0x90, 0x91, 0x92] # Response APDU structure.
class SIMBaseClass(object):
"""
I take care of initing the SIM
The actual details of initing the SIM vary from mobile to datacard, so
I am the one to subclass in case your device needs a special startup
"""
def __init__(self, sconn):
super(SIMBaseClass, self).__init__()
self.sconn = sconn
self.size = None
self.charset = 'IRA'
self.num_of_failures = 0
def set_size(self, size):
log.msg("Setting size to %d" % size)
self.size = size
def set_charset(self, charset):
try:
self.charset = from_ucs2(charset)
except (UnicodeDecodeError, TypeError):
self.charset = charset
return charset
def setup_sms(self):
# Notification when a SMS arrives...
self.sconn.set_sms_indication(2, 1, 0, 1, 0)
# set PDU mode
self.sconn.set_sms_format(0)
def initialize(self, set_encoding=True):
"""
Initializes the SIM card
This method sets up encoding, SMS format and notifications
in the SIM. It returns a deferred with the SIM size.
"""
# set up extended error reporting
self.sconn.set_error_level(1)
if set_encoding:
self._setup_encoding()
self.setup_sms()
deferred = defer.Deferred()
def get_size(auxdef):
d = self.sconn.get_phonebook_size()
def phonebook_size_cb(resp):
self.set_size(resp)
auxdef.callback(self.size)
def phonebook_size_eb(failure):
failure.trap(E.General, E.SimBusy, E.SimFailure)
self.num_of_failures += 1
if self.num_of_failures > RETRY_ATTEMPTS:
# fail gracefully for now, we'll try again
# the next time a contact operation is required
auxdef.callback(None)
return
reactor.callLater(RETRY_TIMEOUT, get_size, auxdef)
d.addCallback(phonebook_size_cb)
d.addErrback(phonebook_size_eb)
return auxdef
return get_size(deferred)
def _set_charset(self, charset):
"""
Checks whether is necessary the change and memorizes the used charset
"""
def process_charset(reply):
"""
Only set the new charset if is different from current encoding
"""
if reply != charset:
return self.sconn.set_charset(charset)
# we already have the wanted charset
self.charset = reply
return defer.succeed(True)
d = self.sconn.get_charset()
d.addCallback(process_charset)
return d
def _process_charsets(self, charsets):
for charset in ["UCS2", "IRA", "GSM"]:
if charset in charsets:
return self._set_charset(charset)
msg = "Couldn't find an appropriated charset in %s"
raise E.CharsetError(msg % charsets)
def _setup_encoding(self):
d = self.sconn.get_charsets()
d.addCallback(self._process_charsets)
return d
wader-0.5.13/core/startup.py 0000664 0000000 0000000 00000027052 12576466102 0015725 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2006-2011 Vodafone España, S.A.
# Copyright (C) 2008-2009 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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.
"""Utilities used at startup"""
import os
from os.path import join
import dbus
from dbus.service import Object, BusName, method, signal
from twisted.application.service import Application, Service
from twisted.internet import reactor, defer
from twisted.plugin import IPlugin, getPlugins
from twisted.python import log
# Logger related imports.
import glob
import time
from twisted.python import threadable
from twisted.python.log import ILogObserver, FileLogObserver
from twisted.python.logfile import BaseLogFile, LogReader
import wader.common.consts as consts
from wader.common._dbus import DBusExporterHelper
from wader.common.provider import NetworkProvider
from core.serialport import SerialPort
DELAY = 10
ATTACH_DELAY = 1
OLDLOCK = join(consts.DATA_DIR, '.setup-done')
_application = None
# Wow, this is fantastic if you need to know where to add an errorback
#defer.setDebugging(True)
class WaderLogFile(BaseLogFile):
"""
A log file that can be rotated.
A rotateLength of None disables automatic log rotation.
"""
def __init__(self, name, directory, defaultMode=None,
maxRotatedFiles=None):
"""
Create a log file rotating on length.
@param name: file name.
@type name: C{str}
@param directory: path of the log file.
@type directory: C{str}
@param defaultMode: mode used to create the file.
@type defaultMode: C{int}
@param maxRotatedFiles: if not None, max number of log files the class
creates. Warning: it removes all log files above this number.
@type maxRotatedFiles: C{int}
"""
BaseLogFile.__init__(self, name, directory, defaultMode)
self.maxRotatedFiles = maxRotatedFiles
def _openFile(self):
BaseLogFile._openFile(self)
self.lastDate = self.toDate(os.stat(self.path)[8])
def toDate(self, *args):
"""Convert a unixtime to (year, month, day) localtime tuple,
or return the current (year, month, day) localtime tuple.
This function primarily exists so you may overload it with
gmtime, or some cruft to make unit testing possible.
"""
# primarily so this can be unit tested easily
return time.localtime(*args)[:3]
def shouldRotate(self):
"""Rotate when the date has changed since last write"""
return self.toDate() > self.lastDate
def getLog(self, identifier):
"""
Given an integer, return a LogReader for an old log file.
"""
filename = "%s.%d" % (self.path, identifier)
if not os.path.exists(filename):
raise ValueError("no such logfile exists")
return LogReader(filename)
def write(self, data):
"""
Write some data to the file.
"""
BaseLogFile.write(self, data)
# Guard against a corner case where time.time()
# could potentially run backwards to yesterday.
# Primarily due to network time.
self.lastDate = max(self.lastDate, self.toDate())
def rotate(self):
"""
Rotate the file and create a new one.
If it's not possible to open new logfile, this will fail silently,
and continue logging to old logfile.
"""
if not (os.access(self.directory, os.W_OK) and \
os.access(self.path, os.W_OK)):
return
logs = self.listLogs()
logs.reverse()
for i in logs:
if self.maxRotatedFiles is not None and i >= self.maxRotatedFiles:
os.remove("%s.%d" % (self.path, i))
else:
os.rename("%s.%d" % (self.path, i),
"%s.%d" % (self.path, i + 1))
self._file.close()
os.rename(self.path, "%s.1" % self.path)
self._openFile()
def listLogs(self):
"""
Return sorted list of integers - the old logs' identifiers.
"""
result = []
for name in glob.glob("%s.*" % self.path):
try:
counter = int(name.split('.')[-1])
if counter:
result.append(counter)
except ValueError:
pass
result.sort()
return result
def __getstate__(self):
state = BaseLogFile.__getstate__(self)
del state["lastDate"]
return state
threadable.synchronize(WaderLogFile)
def _get_application():
"""
Factory function that returns an Application object.
If the object does not exist then it creates a new Application object.
(Internal use only).
"""
global _application
if _application is not None:
return _application
_application = Application(consts.APP_NAME)
logfile = WaderLogFile(consts.LOG_NAME, consts.LOG_DIR,
maxRotatedFiles=consts.LOG_NUMBER)
_application.setComponent(ILogObserver, FileLogObserver(logfile).emit)
return _application
def create_skeleton_and_do_initial_setup():
"""I perform the operations needed for the initial user setup"""
PLUGINS = join(consts.BASE_DIR, 'usr', 'share', consts.APP_SLUG_NAME,
'plugins')
if os.path.exists(OLDLOCK):
# old way to signal that the setup is complete
os.unlink(OLDLOCK)
# maybe create new networks DB
provider = NetworkProvider()
try:
if provider.is_current():
log.msg("Networks DB is current")
else:
log.msg("Networks DB creation started")
provider.populate_networks()
log.msg("Networks DB population complete")
except Exception, e:
log.err(str(e))
finally:
provider.close()
# maybe regenerate plugin cache
regenerate = False
# remove any compiled python files that are orphans
for cmpname in glob.glob(join(PLUGINS, '*.py[co]')):
pyname = cmpname[:-1]
if not os.path.exists(pyname):
regenerate = True
os.unlink(cmpname)
# check if any python is newer than the cache
if not regenerate:
try:
timestamp = os.stat(join(PLUGINS, 'dropin.cache'))[8]
for pyname in glob.glob(join(PLUGINS, '*.py')) + \
glob.glob(join(PLUGINS, '*.py[co]')):
if timestamp < os.stat(pyname)[8]:
raise ValueError
except:
regenerate = True
if regenerate:
log.msg("Plugin cache generation started")
import plugins
list(getPlugins(IPlugin, package=plugins))
log.msg("Plugin cache generation complete")
else:
log.msg("Plugin cache is current")
class WaderService(Service):
"""I am a Twisted service that starts up Wader"""
def __init__(self):
self.ctrl = None
self.prof = None
self.dial = None
def startService(self):
"""Starts the Wader service"""
log.msg("%s (%s) started" % (consts.APP_NAME, consts.APP_VERSION))
create_skeleton_and_do_initial_setup()
# check if we have an OSPlugin for this OS/Distro
from core.oal import get_os_object
if get_os_object() is None:
message = 'OS/Distro not registered'
details = """
The OS/Distro under which you are running %s is not
registered in the OS database. Check the documentation
for what you can do in order to support your OS/Distro
""" % consts.APP_NAME
raise SystemExit("%s\n%s" % (message, details))
from core.dialer import DialerManager
self.ctrl = StartupController()
self.dial = DialerManager(self.ctrl)
def get_clients(self):
"""
Helper method for SSH sessions
:rtype: dict
"""
return self.ctrl.hm.clients
class StartupController(Object, DBusExporterHelper):
"""
I manage devices in the system
Discovery, identification, hotplugging, etc.
:ivar clients: Dict with a reference to every configured device
"""
def __init__(self):
name = BusName(consts.WADER_SERVICE, bus=dbus.SystemBus())
super(StartupController, self).__init__(bus_name=name,
object_path=consts.WADER_OBJPATH)
from core.oal import get_os_object
self.hm = get_os_object().hw_manager
assert self.hm is not None, "Running Wader on an unsupported OS?"
self.hm.register_controller(self)
@method(consts.WADER_INTFACE, in_signature='', out_signature='ao',
async_callbacks=('async_cb', 'async_eb'))
def EnumerateDevices(self, async_cb, async_eb):
"""
Returns a list of object paths with all the found devices
It also includes the object paths of already handled devices
"""
d = self.hm.get_devices()
d.addCallback(lambda devs: [d.opath for d in devs])
return self.add_callbacks(d, async_cb, async_eb)
@signal(consts.WADER_INTFACE, signature='o')
def DeviceAdded(self, opath):
"""Emitted when a 3G device is added"""
log.msg("emitting DeviceAdded('%s')" % opath)
@signal(consts.WADER_INTFACE, signature='o')
def DeviceRemoved(self, opath):
"""Emitted when a 3G device is removed"""
log.msg("emitting DeviceRemoved('%s')" % opath)
def get_wader_application():
"""
Returns the application object required by twistd on startup
In the future this will be the point that will load startup plugins
and modify the application object
"""
service = WaderService()
application = _get_application()
service.setServiceParent(application)
return application
def attach_to_serial_port(device):
"""Attaches the serial port in ``device``"""
port = device.ports.get_application_port()
if port.obj is not None:
return defer.succeed(device)
d = defer.Deferred()
port.obj = SerialPort(device.sconn, port.path, reactor,
baudrate=device.baudrate)
reactor.callLater(ATTACH_DELAY, lambda: d.callback(device))
return d
def setup_and_export_device(device):
"""Sets up ``device`` and exports its methods over DBus"""
if not device.custom.wrapper_klass:
raise AttributeError("No wrapper class for device %s" % device)
wrapper_klass = device.custom.wrapper_klass
log.msg("wrapping plugin %s with class %s" % (device, wrapper_klass))
device.sconn = wrapper_klass(device)
# Use the exporter that device specifies
if not device.custom.exporter_klass:
raise AttributeError("No exporter class for device %s" % device)
exporter_klass = device.custom.exporter_klass
log.msg("exporting %s methods with class %s" % (device, exporter_klass))
exporter = exporter_klass(device)
device.exporter = exporter
device.__repr__ = device.__str__
return device
wader-0.5.13/core/statem/ 0000775 0000000 0000000 00000000000 12576466102 0015140 5 ustar 00root root 0000000 0000000 wader-0.5.13/core/statem/__init__.py 0000664 0000000 0000000 00000001602 12576466102 0017250 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2006-2008 Vodafone España, S.A.
# Copyright (C) 2008-2009 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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.
"""Different state machines used in the app"""
wader-0.5.13/core/statem/auth.py 0000664 0000000 0000000 00000016074 12576466102 0016463 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2006-2008 Vodafone España, S.A.
# Copyright (C) 2008-2009 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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.
"""Authentication state machine"""
from epsilon.modal import mode, Modal
from twisted.internet import reactor, defer
from twisted.python import log
import wader.common.aterrors as E
SIM_FAIL_DELAY = 15
MAX_NUM_SIM_ERRORS = 3
MAX_NUM_SIM_BUSY = 5
class AuthStateMachine(Modal):
"""I authenticate against a device"""
modeAttribute = 'mode'
initialMode = 'get_pin_status'
DELAY = 15
def __init__(self, device):
self.device = device
self.deferred = defer.Deferred()
# it will be set to True if AT+CPIN? == +CPIN: READY
self.auth_was_ready = False
self.num_sim_errors = 0
self.num_sim_busy = 0
log.msg("starting %s ..." % self.__class__.__name__)
def __repr__(self):
return "authentication_sm"
# keyring stuff
def notify_auth_ok(self):
"""Called when authentication was successful"""
self.deferred.callback(True)
def notify_auth_failure(self, failure):
"""Called when we faced a failure"""
log.msg("%s: notifying auth failure %s" % (self, failure))
self.deferred.errback(failure)
# states callbacks
def check_pin_cb(self, resp):
"""Callbacked with check_pin's result"""
self.auth_was_ready = True
self.notify_auth_ok()
def get_pin_status_cb(self, enabled):
"""Callbacked with get_pin_status's result"""
if int(enabled):
self.notify_auth_failure(E.SimPinRequired())
else:
self.notify_auth_ok()
def incorrect_pin_eb(self, failure):
"""Executed when PIN is incorrect"""
failure.trap(E.IncorrectPassword)
self.notify_auth_failure(failure)
def incorrect_puk_eb(self, failure):
"""Executed when the PUK is incorrect"""
failure.trap(E.IncorrectPassword, E.General)
self.notify_auth_failure(E.IncorrectPassword())
def incorrect_puk2_eb(self, failure):
"""Executed when the PUK2 is incorrect"""
failure.trap(E.IncorrectPassword, E.General)
self.notify_auth_failure(E.IncorrectPassword())
def pin_required_eb(self, failure):
"""Executed when SIM PIN is required"""
failure.trap(E.SimPinRequired, E.General)
self.notify_auth_failure(E.SimPinRequired())
def puk_required_eb(self, failure):
"""Executed when PUK/PUK2 is required"""
failure.trap(E.SimPukRequired, E.SimPuk2Required)
self.notify_auth_failure(failure)
def sim_failure_eb(self, failure):
"""Executed when there's a SIM failure, try again in a while"""
failure.trap(E.SimFailure)
log.msg("device returned SimFailure")
self.num_sim_errors += 1
if self.num_sim_errors >= MAX_NUM_SIM_ERRORS:
# we can now consider that there's something wrong with the
# device, probably there's no SIM
self.notify_auth_failure(E.SimNotInserted())
return
reactor.callLater(SIM_FAIL_DELAY, self.do_next)
def sim_busy_eb(self, failure):
"""Executed when SIM is busy, try again in a while"""
failure.trap(E.SimBusy, E.SimNotStarted, E.General)
self.num_sim_busy += 1
if self.num_sim_busy >= MAX_NUM_SIM_BUSY:
# we can now consider that there's something wrong with the
# device, probably a firmwarebug
self.notify_auth_failure(E.SimFailure())
return
reactor.callLater(SIM_FAIL_DELAY, self.do_next)
def sim_no_present_eb(self, failure):
"""Executed when there's no SIM, errback it"""
failure.trap(E.SimNotInserted)
self.notify_auth_failure(failure)
# entry point
def start_auth(self):
"""
Starts the authentication
Returns a deferred that will be callbacked if everything goes alright
:raise SimFailure: SIM unknown error
:raise SimNotInserted: SIM not inserted
:raise DeviceLockedError: Device is locked
"""
self.do_next()
return self.deferred
# states
class get_pin_status(mode):
"""
Ask the SIM what's the PIN status
The SIM can be in one of the following states:
- SIM is ready (already authenticated, or PIN disabled)
- PIN is needed
- PIN2 is needed (not handled)
- PUK/PUK2 is needed
- SIM is not inserted
- SIM's firmware error
"""
def __enter__(self):
pass
def __exit__(self):
pass
def do_next(self):
log.msg("%s: transition to get_pin_status mode...." % self)
from core.startup import attach_to_serial_port
d = attach_to_serial_port(self.device)
d.addCallback(lambda _: self.device.sconn.set_error_level(1))
d.addCallback(lambda _: self.device.sconn.check_pin())
d.addCallback(self.check_pin_cb)
d.addErrback(self.pin_required_eb)
d.addErrback(self.puk_required_eb)
d.addErrback(self.sim_failure_eb)
d.addErrback(self.sim_busy_eb)
d.addErrback(self.sim_no_present_eb)
class pin_needed_status(mode):
"""
Three things can happen:
- Auth went OK
- PIN is incorrect
- After three failed PIN auths, PUK is needed
"""
def __enter__(self):
pass
def __exit__(self):
pass
def do_next(self):
"""
Three things can happen:
- Auth went OK
- PIN is incorrect
- After three failed PIN auths, PUK is needed
"""
pass
class puk_needed_status(mode):
"""
Three things can happen:
- Auth went OK
- PUK/PIN is incorrect
- After five failed attempts, PUK2 is needed
"""
def __enter__(self):
pass
def __exit__(self):
pass
def do_next(self):
pass
class puk2_needed_status(mode):
"""
Three things can happen:
- Auth went OK
- PUK2/PIN is incorrect
- After ten failed attempts, device is locked
"""
def __enter__(self):
pass
def __exit__(self):
pass
def do_next(self):
pass
wader-0.5.13/core/statem/networkreg.py 0000664 0000000 0000000 00000027414 12576466102 0017711 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2006-2008 Vodafone España, S.A.
# Copyright (C) 2008-2009 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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.
"""Network registration state machine"""
import dbus
from epsilon.modal import mode, Modal
from twisted.python import log
from twisted.internet import defer, reactor
#import wader.common.exceptions as ex
import wader.common.aterrors as E
from wader.common.signals import SIG_CREG
from wader.common.consts import (WADER_SERVICE, STATUS_IDLE, STATUS_HOME,
STATUS_SEARCHING, STATUS_DENIED,
STATUS_UNKNOWN, STATUS_ROAMING)
REGISTER_TIMEOUT = 15
MAX_WAIT_TIMES = 6
class NetworkRegistrationStateMachine(Modal):
"""I am a network registration state machine"""
modeAttribute = 'mode'
initialMode = 'check_registration'
def __init__(self, sconn, netid=""):
self.sconn = sconn
self.netid = netid
self.deferred = defer.Deferred()
self.call_id = None
# used to track how many times we've been waiting for a new event
self.wait_counter = 0
self.registering = False
self.tried_manual_registration = False
self.signal_matchs = []
self.connect_to_signals()
log.msg("starting %s ..." % self.__class__.__name__)
def __repr__(self):
return "network_sm"
def connect_to_signals(self):
bus = dbus.SystemBus()
device = bus.get_object(WADER_SERVICE, self.sconn.device.opath)
sm = device.connect_to_signal(SIG_CREG, self.on_netreg_cb)
self.signal_matchs.append(sm)
def clean_signals(self):
while self.signal_matchs:
sm = self.signal_matchs.pop()
sm.remove()
def start_netreg(self):
"""
Starts the network registration process
Returns a deferred that will be callbacked upon success and
errbacked with a CMEError30 or a NetworkRegistrationError if fails
"""
self.do_next()
return self.deferred
def notify_success(self, ignored=True):
"""Notifies the caller that we have succeed"""
try:
self.deferred.callback(ignored)
except defer.AlreadyCalledError:
pass
self.cancel_counter()
self.clean_signals()
def notify_failure(self, failure):
"""Notifies the caller that we have failed"""
try:
self.deferred.errback(failure)
except defer.AlreadyCalledError:
pass
self.cancel_counter()
self.clean_signals()
def cancel_counter(self):
if self.call_id is not None and not self.call_id.called:
self.call_id.cancel()
self.call_id = None
def restart_counter_or_transition(self, timeout=REGISTER_TIMEOUT):
self.cancel_counter()
self.wait_counter += 1
if self.wait_counter <= MAX_WAIT_TIMES:
self.call_id = reactor.callLater(timeout,
self.check_if_registered)
elif not self.tried_manual_registration:
self.transitionTo('manual_registration')
self.do_next()
else:
# we have already tried to register manually and it failed
self.notify_failure(E.Unknown())
def register_with_netid(self, netid):
self.tried_manual_registration = True
d = self.sconn.register_with_netid(netid)
d.addCallback(lambda ign: self.check_if_registered())
def check_if_registered(self):
d = self.sconn.get_netreg_status()
d.addCallback(self.process_netreg_status)
def on_netreg_cb(self, status):
"""Callback for +CREG notifications"""
# we fake 'mode == 1' as we've already enabled it
self.process_netreg_status((1, status))
def process_netreg_status(self, info):
"""Processes get_netreg_status callback and reacts accordingly"""
_mode, status = info
if status == STATUS_IDLE:
# we are not looking for a network
# set up +CREG notification and start network search
if not _mode:
self.sconn.set_netreg_notification(1)
if not self.registering:
self.sconn.send_at('AT+COPS=0,,')
self.registering = True
self.restart_counter_or_transition()
elif status in [STATUS_SEARCHING, STATUS_UNKNOWN]:
# we are looking for a network, give it some time.
# +CREG: 4 is officially unknown, but it's been seen during
# Ericsson F3507g radio power up, and with Huawei E173 immediately
# before registration; in neither case is it fatal.
if not _mode:
self.sconn.set_netreg_notification(1)
self.restart_counter_or_transition()
elif status in [STATUS_HOME, STATUS_ROAMING]:
# We have already found our network -unless a netid was
# specified. Lets check if the contraints are satisfied
self.registering = False
self.transitionTo('check_constraints')
self.do_next()
elif status == STATUS_DENIED:
# Network registration has been denied
self.registering = False
msg = 'Net registration failed: +CREG: %d,%d' % (_mode, status)
self.notify_failure(E.NetworkNotAllowed(msg))
return
def process_netreg_info(self, info):
"""
Checks if we are registered with the supplied operator (if any)
It will transition to manual_registration if necessary
"""
status, netid, long_name = info
if self.netid == netid:
return self.notify_success()
# turns out we're registered with an operator we shouldn't be
self.transitionTo('manual_registration')
self.do_next()
def find_netid_to_register_with(self, imsi):
"""
Registers with the first netid that appears in both +COPS=? and +CPOL?
"""
# we have tried to register with our home network and it has
# failed. We'll try to register with the first netid present
# in AT+COPS=? and AT+CPOL?
def process_netnames(networks):
# Sort so that networks with longer prefixes are found first
networks.sort(reverse=True)
for n in networks:
try:
is_imsi_prefix = imsi.startswith(n.netid) \
if len(n.netid) else False
except (AttributeError, NameError, TypeError):
is_imsi_prefix = False
if n.netid == self.netid or is_imsi_prefix:
assert self.registering == False, "Registering again?"
self.register_with_netid(n.netid)
self.registering = True
def process_roaming_ids_cb(roam_operators):
for roam_operator in roam_operators:
if roam_operator in networks:
assert self.registering == False, "Registering again?"
self.register_with_netid(roam_operator.netid)
self.registering = True
break
else:
# Simplify for message
cpol = list(set([i.netid for i in roam_operators]))
netids = list(set([i.netid for i in networks]))
msg = "No match in +CPOL %s / networks %s" % (cpol, netids)
log.err(msg)
self.notify_failure(E.NoNetwork(msg))
# raise ex.NetworkRegistrationError(msg)
def process_roaming_ids_eb(failure):
# +CME ERROR 3, +CME ERROR 4
failure.trap(E.OperationNotAllowed, E.OperationNotSupported)
# Simplify for message
netids = list(set([i.netid for i in networks]))
msg = "No +CPOL list to compare with networks %s" % netids
log.err(msg)
self.notify_failure(E.NoNetwork(msg))
# raise ex.NetworkRegistrationError(msg)
d = self.sconn.get_roaming_ids()
d.addCallback(process_roaming_ids_cb)
d.addErrback(process_roaming_ids_eb)
d = self.sconn.get_network_names()
d.addCallback(process_netnames)
# states
class check_registration(mode):
"""I check +CREG to see whats the initial status"""
def __enter__(self):
log.msg("%s: check_registration entered" % self)
def __exit__(self):
log.msg("%s: check_registration exited" % self)
def do_next(self):
d = self.sconn.get_netreg_status()
d.addCallback(self.process_netreg_status)
class check_constraints(mode):
"""
We are registered with our home network or roaming
We are going to check whether it satisfies our constraints or not
"""
def __enter__(self):
log.msg("%s: check_constraints entered" % self)
def __exit__(self):
log.msg("%s: check_constraints exited" % self)
def do_next(self):
if not self.netid:
# no netid specified and we're already registered with our
# home network or roaming, this is success
self.notify_success()
return
d = self.sconn.get_netreg_info()
d.addCallback(self.process_netreg_info)
class manual_registration(mode):
"""
I start the manual registration process
This is due to a +CREG: 1,2 or because the card automatically
registered with an operator and its netid doesn't matches with the
one specified by the user
"""
def __enter__(self):
log.msg("%s: manual_registration entered" % self)
def __exit__(self):
log.msg("%s: manual_registration exited" % self)
def do_next(self):
def process_imsi_cb(imsi):
assert self.registering == False, "Registering again?"
log.msg("manual_registration self.netid = %s, imsi = %s"
% (str(self.netid), str(imsi)))
try:
is_imsi_prefix = imsi.startswith(self.netid) \
if len(self.netid) else False
except (AttributeError, NameError, TypeError):
is_imsi_prefix = False
if is_imsi_prefix:
log.msg("manual_registration self.netid is IMSI prefix")
# if we've been specified a netid, we cannot do
# much more than trying to register with it and
# if it fails return asap
self.register_with_netid(self.netid)
self.registering = True
else:
log.msg("manual_registration self.netid not IMSI prefix")
# look for a netid to register with
self.find_netid_to_register_with(imsi)
log.msg("%s: obtaining the IMSI..." % self)
d = self.sconn.get_imsi()
d.addCallback(process_imsi_cb)
wader-0.5.13/core/statem/simple.py 0000664 0000000 0000000 00000020043 12576466102 0017002 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2008-2009 Warp Networks, S.L.
# Author: Pablo Martí
#
# 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.
"""org.freedesktop.ModemManager.Modem.Simple state machine"""
from epsilon.modal import mode, Modal
from twisted.internet import defer, reactor
from twisted.python import log
import wader.common.aterrors as E
from wader.common.consts import STATUS_HOME, STATUS_ROAMING
from wader.common.utils import convert_network_mode_to_allowed_mode
# poll at 'n' second intervals for 'm' tries
INTERVAL = 3
TRIES = 30
class SimpleStateMachine(Modal):
"""I am a state machine for o.fd.ModemManager.Modem.Simple"""
modeAttribute = 'mode'
initialMode = 'begin'
def __init__(self, device, settings):
self.device = device
self.sconn = device.sconn
# XXX: Currently, as of NM 0.8, nm-applet always passes
# 'network_mode'=ANY in ConnectSimple. We have to
# remove it here or we stamp on the card setting
# done from the user's profile
if 'network_mode' in settings:
del settings['network_mode']
self.settings = settings
self.deferred = defer.Deferred()
def transition_to(self, state):
self.transitionTo(state)
self.do_next()
def start_simple(self):
"""Starts the whole process"""
self.do_next()
return self.deferred
def notify_success(self, ignored=True):
"""Notifies the caller that we have succeed"""
self.deferred.callback(ignored)
def notify_failure(self, failure):
"""Notifies the caller that we have failed"""
self.deferred.errback(failure)
class begin(mode):
def __enter__(self):
log.msg("Simple SM: begin entered")
def __exit__(self):
log.msg("Simple SM: begin exited")
def do_next(self):
self.registration_tries = TRIES
self.transition_to('check_pin')
class check_pin(mode):
"""We are going to check whether auth is ready or not"""
def __enter__(self):
log.msg("Simple SM: check_pin entered")
def __exit__(self):
log.msg("Simple SM: check_pin exited")
def do_next(self):
# check the auth state, if its ready go to next state
# otherwise try to auth with the provided pin, give it
# some seconds to settle and go to next state
def check_pin_cb(ignored, wait=False):
if not wait:
self.transition_to('register')
else:
DELAY = self.device.custom.auth_klass.DELAY
reactor.callLater(DELAY, self.transition_to, 'register')
def check_pin_eb_pin_needed(failure):
failure.trap(E.SimPinRequired)
if 'pin' not in self.settings:
self.notify_failure(E.SimPinRequired("No pin provided"))
return
d = self.sconn.send_pin(self.settings['pin'])
d.addCallback(check_pin_cb, wait=True)
d.addErrback(self.notify_failure)
d = self.sconn.check_pin()
d.addCallback(check_pin_cb)
d.addErrback(check_pin_eb_pin_needed)
class register(mode):
"""Registers with the given network id"""
def __enter__(self):
log.msg("Simple SM: register entered")
def __exit__(self):
log.msg("Simple SM: register exited")
def do_next(self):
if 'network_id' in self.settings:
netid = self.settings['network_id']
d = self.sconn.register_with_netid(netid)
d.addCallback(lambda _: self.transition_to('set_apn'))
else:
self.transition_to('set_apn')
class set_apn(mode):
def __enter__(self):
log.msg("Simple SM: set_apn entered")
def __exit__(self):
log.msg("Simple SM: set_apn exited")
def do_next(self):
if 'apn' in self.settings:
d = self.sconn.set_apn(self.settings['apn'])
d.addCallback(lambda _: self.transition_to('set_band'))
else:
self.transition_to('set_band')
class set_band(mode):
def __enter__(self):
log.msg("Simple SM: set_band entered")
def __exit__(self):
log.msg("Simple SM: set_band exited")
def do_next(self):
if 'band' in self.settings:
d = self.sconn.set_band(self.settings['band'])
d.addCallback(lambda _:
reactor.callLater(1,
self.transition_to, 'set_allowed_mode'))
else:
self.transition_to('set_allowed_mode')
class set_allowed_mode(mode):
def __enter__(self):
log.msg("Simple SM: set_allowed_mode entered")
def __exit__(self):
log.msg("Simple SM: set_allowed_mode exited")
def do_next(self):
def get_network_mode_cb(mode):
allowed = convert_network_mode_to_allowed_mode(mode)
if allowed == self.settings['allowed_mode']:
log.msg("Simple SM: set_allowed_mode is current")
self.transition_to('wait_for_registration')
else:
log.msg("Simple SM: set_allowed_mode change required")
d2 = self.sconn.set_allowed_mode(
self.settings['allowed_mode'])
# We need to wait long enough for the device to start
# switching and lose the current registration
d2.addCallback(lambda _: reactor.callLater(5,
self.transition_to, 'wait_for_registration'))
if 'allowed_mode' in self.settings:
d = self.sconn.get_network_mode()
d.addCallback(get_network_mode_cb)
else:
self.transition_to('wait_for_registration')
class wait_for_registration(mode):
def __enter__(self):
log.msg("Simple SM: wait_for_registration entered")
def __exit__(self):
log.msg("Simple SM: wait_for_registration exited")
def do_next(self):
def get_netreg_status_cb(info):
if info[1] in [STATUS_HOME, STATUS_ROAMING]:
self.transition_to('connect')
elif self.registration_tries <= 0:
self.notify_failure(E.NoNetwork("Not registered"))
else:
self.registration_tries -= 1
reactor.callLater(INTERVAL, self.do_next)
d = self.sconn.get_netreg_status()
d.addCallback(get_netreg_status_cb)
class connect(mode):
def __enter__(self):
log.msg("Simple SM: connect entered")
def __exit__(self):
log.msg("Simple SM: connect exited")
def do_next(self):
self.settings['number'] = \
"*99***%d#" % self.sconn.state_dict.get('conn_id')
d = self.sconn.connect_to_internet(self.settings)
d.addCallback(lambda _: self.transition_to('done'))
class done(mode):
def __enter__(self):
log.msg("Simple SM: done entered")
def __exit__(self):
log.msg("Simple SM: done exited")
def do_next(self):
self.notify_success()
wader-0.5.13/doc/ 0000775 0000000 0000000 00000000000 12576466102 0013460 5 ustar 00root root 0000000 0000000 wader-0.5.13/doc/Makefile 0000664 0000000 0000000 00000003446 12576466102 0015127 0 ustar 00root root 0000000 0000000 # You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d _build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html htmlhelp latex changes coverage
help:
@echo "Please use \`make ' where is one of"
@echo " html to make standalone HTML files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " changes to make an overview over all changed/added/deprecated items"
@echo " coverage to check documentation coverage for library and C API"
clean:
-rm -rf _build/*
html:
mkdir -p _build/html _build/doctrees
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html
@echo
@echo "Build finished. The HTML pages are in _build/html."
latex:
mkdir -p _build/latex _build/doctrees
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex
@echo
@echo "Build finished; the LaTeX files are in _build/latex."
@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
"run these through (pdf)latex."
htmlhelp:
mkdir -p _build/htmlhelp _build/doctrees
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) _build/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in _build/htmlhelp."
changes:
mkdir -p _build/changes _build/doctrees
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) _build/changes
@echo
@echo "The overview file is in _build/changes."
coverage:
mkdir -p _build/coverage _build/doctrees
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) _build/coverage
@echo
@echo "Coverage finished; see _build/coverage/python.txt"
wader-0.5.13/doc/_static/ 0000775 0000000 0000000 00000000000 12576466102 0015106 5 ustar 00root root 0000000 0000000 wader-0.5.13/doc/_static/wader.css 0000664 0000000 0000000 00000000026 12576466102 0016720 0 ustar 00root root 0000000 0000000 @import url(foo.css);
wader-0.5.13/doc/conf.py 0000664 0000000 0000000 00000011637 12576466102 0014767 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2009 Warp Networks, S.L.
# Author: David Francos Cuartero
#
# 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.
"""
Wader documentation build configuration file, created by
sphinx-quickstart on Mon Apr 20 09:16:21 2009.
"""
import os
import sys
sys.path.insert(0, os.path.abspath('..'))
from wader.common.consts import APP_VERSION, APP_NAME
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc',
'sphinx.ext.autosummary',
'sphinx.ext.coverage']
# Add any paths that contain templates here, relative to this directory.
templates_path = []
# The suffix of source filenames.
source_suffix = '.rst'
# The master toctree document.
master_doc = 'contents'
# General substitutions.
project = APP_NAME
copyright = 'The Wader project and contributors'
# The default replacements for |version| and |release|, also used in various
# other places throughout the built documents.
#
# The short X.Y version.
version = APP_VERSION
release = version
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
today_fmt = '%d %B, %Y'
# List of documents that shouldn't be included in the build.
#unused_docs = []
# If true, '()' will be appended to :func: etc. cross-reference text.
add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
add_module_names = False
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# Sphinx will recurse into subversion configuration folders and try to read
# any document file within. These should be ignored.
# Note: exclude_dirnames is new in Sphinx 0.5
exclude_dirnames = ['.svn', '.git']
# Options for HTML output
# -----------------------
# The style sheet to use for HTML and HTML Help pages. A file of that name
# must exist either in Sphinx' static/ path, or in one of the custom paths
# given in html_static_path.
#html_style = 'wader.css'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
#html_static_path = ["_static"]
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
html_use_smartypants = True
# Content template for the index page.
#html_index = ''
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
html_additional_pages = {}
# If false, no module index is generated.
html_use_modindex = True
# If true, the reST sources are included in the HTML build as _sources/.
html_copy_source = True
# Output file base name for HTML help builder.
htmlhelp_basename = 'Waderdoc'
html_show_sourcelink = True
# Options for LaTeX output
# ------------------------
# The paper size ('letter' or 'a4').
#latex_paper_size = 'letter'
# The font size ('10pt', '11pt' or '12pt').
#latex_font_size = '10pt'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author,
# document class [howto/manual]).
#latex_documents = []
latex_documents = [
('contents', 'wader.tex', 'Wader Documentation', 'The Wader project',
'manual'),
]
# Additional stuff for the LaTeX preamble.
#latex_preamble = ''
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_use_modindex = True
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
# If this isn't set to True, the LaTex writer can only handle six levels of
# headers.
latex_use_parts = True
wader-0.5.13/doc/contents.rst 0000664 0000000 0000000 00000001035 12576466102 0016046 0 ustar 00root root 0000000 0000000 .. _contents:
=============================
Wader documentation contents
=============================
.. toctree::
:maxdepth: 9
user/index
devel/index
glossary
Indices, glossary and tables
============================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`glossary`
* :ref:`search`
.. toctree::
:maxdepth: 9
:hidden:
:glob:
modules/core/*
modules/core/backends/*
modules/core/hardware/*
modules/core/oses/*
modules/core/statem/*
modules/wader/common/*
modules/wader/common/backends/*
wader-0.5.13/doc/devel/ 0000775 0000000 0000000 00000000000 12576466102 0014557 5 ustar 00root root 0000000 0000000 wader-0.5.13/doc/devel/add-new-device.rst 0000664 0000000 0000000 00000016573 12576466102 0020101 0 ustar 00root root 0000000 0000000 ===================================
How to add support for a new device
===================================
How to support a new device
===========================
All devices should inherit from :class:`~wader.common.middleware.WCDMAWrapper`
in order to override its base methods. It accepts two argument for
its constructor, an instance or subclass of
:class:`~wader.common.plugin.DevicePlugin` and the object path of the device
to use.
A DevicePlugin contains all the necessary information to speak with
the device: a data port, a control port (if exists), a baudrate, etc.
It also has the following attributes and members that you can
customise for your device plugin:
- ``custom``: An instance or subclass of
:class:`wader.common.hardware.base.WCDMACustomizer`. See bellow.
- ``sim_klass``: An instance or subclass of
:class:`wader.common.sim.SIMBaseClass`.
- ``baudrate``: At what speed are we going to talk with this device
(default 115200).
- ``__remote_name__``: As some devices share the same vendor and product ids,
we will issue an `AT+GMR` command right at the beginning to find out the
real device model. Set this attribute to whatever your device replies to the
`AT+GMR` command.
- ``mapping``: A dictionary that, when is not empty, means that this
particular combination of vendor and product ids is shared between several
models from the same company. As the ids are the same, the only way to
differentiate them is issuing an `AT+GMR` command to get the device model.
This dict must have an entry for each model and a `default` entry that
will be used in cases where we do not know about the card model. For more
information about the mapping dictionary, have a look at the module
:class:`wader.common.plugins.huawei_exxx`.
The object :class:`wader.common.hardware.base.WCDMACustomizer` acts as a
container for all the device-specific customizations such as:
- ``wrapper_klass``: Specifies the class that will be used to wrap the
`Device` object. Used in situations where the default commands supplied
by :class:`wader.common.middleware.WCDMAWrapper` are not enough:
(i.e. devices with "interesting" firmwares). Some of its commands may
need an special parsing or an specific workaround.
- ``exporter_klass``: Specifies the class that will export the wrapper
methods over :term:`DBus`.
- State Machines: Each device its a world on its own, and even though they
are supposed to support the relevant GSM and 3GPP standards, some devices
prefer to differ from them. The `custom` object contains references to
the state machines that the device should use, (on situations where it
applies, such as with WCDMA devices of course):
- ``auth_klass``: The state machine used to authenticate against the device,
default is :class:`wader.common.statem.auth.AuthStateMachine`.
- ``netr_klass``: The state machine used to register on the network, default
is :class:`wader.common.statem.networkreg.NetworkRegistrationStateMachine`.
- ``simp_klass``: The *simple* state machine specified by
:term:`ModemManager` v0.2. This state machine basically comprises the
previous two on a single (and simpler) one.
- ``async_regexp``: regular expression object that will match whatever pattern
of unsolicited notifications the given device sends us.
- ``signal_translations``: Dictionary of tuples, each tuple has two members:
the first is the signal id and the second is a function that will translate
the signal arguments and the signal to the internal representation that Wader
uses. You can find some sample code in the
:class:`~wader.common.hardware.huawei` module. If a notification should be
ignored, then add it as a key like the rest, but its value should be a
tuple ``(None, None)``.
- ``band_dict``: Dictionary with the different bands supported by the device.
The keys will *always* be a `MM_NETWORK_BAND_FOO` and the value is up to the
implementor. You can see the supported bands in the
:mod:`~wader.common.consts` module.
- ``conn_dict``: Dictionary with the different network modes supported by the
device. The keys will *always* be a `MM_NETWORK_MODE_FOO` and the value is
up to the implementor. You can see the supported network modes in the
:mod:`~wader.common.consts` module.
- ``cmd_dict``: Dictionary with information about how each command should be
processed. ``cmd_dict`` most of the time will be a shallow copy of the
:class:`~wader.common.command` dict with minor modifications about how a
particular command is processed on the given device.
- ``device_capabilities``: List with all the unsolicited notifications that
this device will send us. If the device sends us every RSSI change that
detects, we don't need to poll manually the device for that information.
Overview of a simple DevicePlugin
=================================
Lets have a look at the NovatelXU870 plugin:
.. literalinclude:: ../../plugins/devices/novatel_xu870.py
:lines: 18-
In an ideal world, devices have a unique vendor and product id tuple, they
conform to the relevant CDMA or WCDMA specs, and that's it. The device is
identified by its vendor and product ids and double-checked with its
`__remote_name__` attribute (the response to an `AT+GMR` command).
This vendor and product id tuple will usually use the `usb` bus, however
some devices might end up attached to the ``pci`` or `pcmcia` buses. The
last line in the plugin will create an instance of the plugin in wader's
plugin system -otherwise it will not be found!.
Overview of a *relatively* simple DevicePlugin
==============================================
Take for example the HuaweiE620 class:
.. literalinclude:: ../../plugins/devices/huawei_e620.py
:lines: 19-
The E620 plugin is identical to the XU870 except one small difference
regarding the parsing of the `get_roaming_ids` command. The E620 omits
some information that other devices do output, and the regular expression
object that parses it has to be updated. We get a new copy of the
`cmd_dict` dictionary attribute and modify it with the new regexp the
`get_roaming_ids` entry. The new `cmd_dict` is specified in its
Customizer object.
Overview of a not so simple DevicePlugin
========================================
.. literalinclude:: ../../plugins/devices/huawei_e220.py
:lines: 19-
Huawei's E220, despite sharing its manufacturer with the E620, has a couple
of minor differences that deserve some explanation. There's a bug in its
firmware that will reset the device if you ask its SMSC. The workaround is
to get once the SMSC before switching to UCS2, you'd be amazed of how long
took me to discover the fix. The second difference with the E620 is that
the E220 can have several product_ids, thus its product_id list has two
elements.
Overview of a complex DevicePlugin
==================================
.. literalinclude:: ../../plugins/devices/option_colt.py
:lines: 24-
This data card is the buggiest card we've found so far, and has proven to be
an excellent challenge for the extensibility and granularity of our plugin
system. Basically we've found the following bugs on the card's firmware:
- If PIN authentication is disabled and you issue an `AT+CPIN?`, the card
will reply with a `+CPIN: SIM PUK2`.
- Don't ask me why, but `AT+CPBR=1,250` does not work once the application
is running. I have tried replacing the command with an equivalent one
(`AT+CPBF=""`) without luck.
So we had to modify the AuthStateMachine for this particular device and its
`cmd_dict`.
wader-0.5.13/doc/devel/add-new-os.rst 0000664 0000000 0000000 00000003106 12576466102 0017247 0 ustar 00root root 0000000 0000000 How to add support for a new distro/OS
======================================
How to add support for a new distro
+++++++++++++++++++++++++++++++++++
Adding support for a new distro is relatively straightforward, it basically
boils down to:
- Inheriting from :class:`~wader.common.oses.linux.LinuxPlugin`.
- Implementing the ``is_valid`` method. This will return True if we are in
the given OS/Distro and False otherwise. Usually you will just need to
check for a well known file that your distro or OS ships with.
- Implement the ``get_timezone`` method. This method returns a string with
the timezone name (i.e. "Europe/Madrid"). Implementing this method is not
strictly necessary, and Wader can start up without it, but your SMS dates
will probably be off by some hours.
Lets have a look at the Fedora plugin:
.. literalinclude:: ../../plugins/oses/fedora.py
:lines: 21-
As we can see, the Fedora plugin just defines ``is_valid`` and provides an
implementation for ``get_timezone``.
How to add support for a new OS
+++++++++++++++++++++++++++++++
Adding support for a new OS is not as easy as the previous point. You need to
add a new os class to ``wader.common.oses`` with a working
implementation for the following methods/objects:
- ``get_iface_stats``: Accepts just one parameter, the iface name, and
returns a tuple with tx,rx bytes.
- ``is_valid``: Returns True if the plugin is valid in the context where is
being run, otherwise returns False.
- ``hw_manager``: A instance of a class that implements the
:class:`~wader.common.interfaces.IHardwareManager` interface.
wader-0.5.13/doc/devel/contacts-backend.rst 0000664 0000000 0000000 00000010372 12576466102 0020517 0 ustar 00root root 0000000 0000000 ==========================
Pluggable contacts backend
==========================
Overview
========
During the BMC/Wader merge it became obvious that a pluggable contacts backend
was going to be required in order to support all the different contact sources
that `Vodafone`_ had in mind:
* SIM contacts
* Persistent contacts (SQL)
* Web services contacts (`ZYB`_)
* Desktop contacts (Evolution, KMail, etc.)
The design envisioned a central entity that would deal with all the different
plugins in a homogeneous way (ContactStore). Every plugin would provide its
own ``Contact`` class extending :class:`~wader.common.contact.Contact`.
.. _Vodafone: http://www.vodafone.com
.. _ZYB: http://www.zyb.com
Classes overview
================
:class:`~wader.common.contact.ContactStore` is is the central point to perform
operations with the different backends. It is a simple class that performs
operations on an array of different backends. Every operation will contact
every registered backend, and it is up to the backend to decide if it should
accept or ignore the given operation.
For example, a ``list_contacts`` operation will contact every backend and
will return an iterator that will yield all the contacts present in the
backend. On the other hand a specific operation, such as ``edit_contact``
will be ignored by all the backends but the one that the target belongs to.
How to determine that a contact object belongs to a given backend? Easy,
every backend must provide a :class:`~wader.common.contact.Contact` subclass
that will identify the contact type. When a foreign contact is received
in a backend for an specific operation, such as ``edit_contact`` the backend
will just ignore the request.
Design rationale
================
The design of this feature had to take into account that the core and UI run
separatedly in different processes. While the former runs as uid 0 (root),
the latter runs as a user process. Why is this important? Because one of the
first contact plugins developed was an SQLite backend. Any database created
at core level, could then be read by any user unless we complicated the
whole design and schema with user separation at the SQL level.
Instead we chose to instantiate the plugins at the GUI level, not only the
design was simpler, but also we had less data to worry at the core level.
The following diagram illustrates the design:
.. image:: images/contacts-backend/design.png
:alt: Current design diagram
:align: center
Not implemented
================
The following functionalities need some planning and discussion, thus
they are not present in the current implementation.
Plugin manager ala gedit
++++++++++++++++++++++++
Soon it became clear that a manager interface would be required so the user
can control what plugins are active at a given time, and also as a mean to
configure plugins that require some config.
Every row in the interface represents a plugin found in the system, they
can be enabled by ticking the checkbox next to it. In addition, there is
a configure button at the bottom of the interface, by clicking this button
a new interface specific to every plugin is shown. In the case of ZYB it
would contain widgets to be able to register online or enter an existing
username and password. Not all the plugins require a further screen to
configure them, right now ZYB is the only one.
.. image:: images/contacts-backend/gedit.png
:alt: Gedit plugin manager interface
:align: center
Widgets to edit contacts
++++++++++++++++++++++++
There are certain backends, such as ZYB, that can handle way more data than
what other backends usually deal with (i.e. name, email and phone). ZYB can
configure middle name, family name, time zone, country, and more. Instead
of providing a base contact class with all this attributes, we chose to have
a simple contact class that is extended by subclasses with all this new
attributes. Similarly, every plugin module can provide widgets in a well
known location -i.e. ``plugin.widgets``- to edit this augmented contact
classes. ``plugin.widgets`` could well be a dictionary with the following
keys: add, config, edit, read. Every one of them has as its value a class
tailored for the situation that implements an interface for the task. When
needed, the class will be instantiated, used and then disposed.
wader-0.5.13/doc/devel/images/ 0000775 0000000 0000000 00000000000 12576466102 0016024 5 ustar 00root root 0000000 0000000 wader-0.5.13/doc/devel/images/contacts-backend/ 0000775 0000000 0000000 00000000000 12576466102 0021227 5 ustar 00root root 0000000 0000000 wader-0.5.13/doc/devel/images/contacts-backend/design.dia 0000664 0000000 0000000 00000003650 12576466102 0023163 0 ustar 00root root 0000000 0000000 \Yo8~WITݤ@ZaŠ-VN}߾C2İBGpȼ{4'~ 0{'ӛoy>{+10Qnz3!oG&$|6W-c{]&D쏗!ޘM~LhzUnQl<>z\`G6㘳MynQ&b(5~o[%Q8}YyUsO%d@®lH-l"?teŗ"E|QGQYxɛ$H7,\/Dt,H@v{=NOޝ5R}OFOԕI_i'8UCq6/7LZ(u.}'lMYlpHv*&Up)DV<ظj#9l"o[:Ucg1>DOM"}6wU&yE6)DbP:1p
J
24&Q#s+I