pax_global_header00006660000000000000000000000064117452541300014514gustar00rootroot0000000000000052 comment=61ea74b9cc6157be9da33cfdf416bc7aaa679063 fso-frameworkd-0.10.1/000077500000000000000000000000001174525413000145215ustar00rootroot00000000000000fso-frameworkd-0.10.1/.gitignore000066400000000000000000000000201174525413000165010ustar00rootroot00000000000000*.pyc *.pyo *~ fso-frameworkd-0.10.1/AUTHORS000066400000000000000000000005461174525413000155760ustar00rootroot00000000000000=== MAIN AUTHORS === * Michael 'Mickey' Lauer * Jan 'Shoragan' Lübbe * Daniel 'Alphaone' Willmann * Guillaume 'Charlie' Chéreau === SPECIAL THANKS === * Michael 'Emdete' Dietrich for showing me that this thing is possible and giving me a major kick-off! fso-frameworkd-0.10.1/COPYING000066400000000000000000000431031174525413000155550ustar00rootroot00000000000000 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. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. fso-frameworkd-0.10.1/ChangeLog000066400000000000000000006732751174525413000163170ustar00rootroot000000000000002012-04-23 Simon Busch Bump version to 0.10.1 Add systemd service file from meta-fso Update relevant components for a 0.10.0 release 2012-03-21 Denis 'GNUtoo' Carikli om-gta04 oeventsd rules.yaml: Handle GPS resource 2012-03-07 Denis 'GNUtoo' Carikli oeventsd: add fso Resource State trigger 2012-02-09 Jose Luis Perez Diez Switch to python-dbus-1.0.0 api v2 Clean debugin helpers 2012-01-04 Felix Huber ogsmd: bug fixes, start work for conference, toggle and 3rd incoming call fix #2305: GSM connection hangs up when during a call a second is not answered ophoned: improve BT connect responsiveness 2012-01-01 Simon Busch Update oevents configuration of crespo (Nexus S) machine Drop oevents configuration for not existing herring machine 2011-12-21 Klaus Kurzmann disable TEST resource for *all* devices 2011-12-21 Simon Busch Add rules.yaml for herring device known as Nexus S 2011-12-18 Klaus Kurzmann add rules.yaml for om-gta04 2011-10-20 Klaus Kurzmann add config for om-gta04 2011-10-16 Denis 'GNUtoo' Carikli Add rules.yaml for the crespo machine Note that since we have no telephony yet only screen brightness is handles. The power button release that made suspend the phone was not copied from the default rules.yaml because it suspended the phone when you released the power button: After suspending the phone(for instance with apm -s) the phone goes in early suspend, and then for resuming it you press the power button(fsousaged listens for the power button press) but then when you release the button it goes back into suspend because of that rule in rules.yaml. Add a frameworkd.conf for the crespo machine 2011-10-12 Denis 'GNUtoo' Carikli ogpsd: add support for 2.6.39 kernel paths 2011-09-06 Felix Huber ophoned: disable gsm functions until used by app otimed: fix for non-existing /etc/localtime testing: improve start time 2011-09-05 Sylvain 'GarthPS' Paré rules.yaml: added a rule for the powerbutton on palm devices 2011-08-22 Sebastian Krzyszkowiak conf: update oeventsd's rules.yaml for Nokia N900 to support LEDs again 2011-06-21 Simon Busch Revert "Add configuration file for the palmpre2 (it's same as the palmpre)" This reverts commit 962479a215bda648c9ddbf7aa652ee16851eda5a. We will fix this in OE and not here! Add configuration file for the palmpre2 (it's same as the palmpre) 2011-06-07 Denis 'GNUtoo' Carikli Add rules.yaml for the nexusone machine Theses rules were copied from the htcleo machine and left intact. Add a frameworkd.conf for the nexusone machine 2011-05-30 Sebastian Krzyszkowiak [ogsmd] Update networks.tab with up-to-date data for Poland Don't know if this file is still used somewhere, but just in case... ;) 2011-04-06 Klaus Kurzmann add a rules.yaml for htcleo add a frameworkd.conf for htcleo 2011-04-05 Simon Busch oevents: update configuration for palmpre machine 2011-02-25 Simon Busch Change script called when backlight changes in rules.yaml for palmpre machine Add rules.yaml config file for palmpre machine oeventsd: add fso BacklightPower trigger 2011-02-22 Marco Trevisan (Treviño) Revert back Daniele's fixes for limit This fixes my bad removal in commit 5cc3ef0, that removed the fix made by Daniele Ricci in 5e5970b48 2011-02-20 Simon Busch Add configuration for palmpre machine 2011-02-06 Marco Trevisan (Treviño) opimd: add more query operator support Now you can also query with more operators using: field >= val with {">=field", "val"} field <= val with {"<=field", "val"} field != val with {"!field", "val"} 2011-02-01 Felix Huber ogsmd: check len of name and number for phonebook 2011-01-23 Felix Huber oevents: add aux key rule 2011-01-22 Felix Huber ogsmd: final fix for #435 in ti calypso sleep mode ogsmd: fix power down of ti calypso 2011-01-18 Martin Jansa opreferences: merge schema/phone.yaml config with frameworkd-config-shr * hopefully fixes KeyError: 'message-volume' issue 2011-01-17 Martin Jansa add init.d script from OpenEmbedded and install it by default setup.py: install new yaml configs and don't install ogsmd config file by default * machine specific rules.yaml and frameworkd.conf are not installed by setup.py (ie OE recipe can choose which one to install) * ogsmd files can also be installed by recipe if needed (ogsmd is used on target device) opreferences: merge config with frameworkd-config-shr frameworkd.conf: add eten-m800 config from OE repo add machine specific rules.yaml files from frameworkd-config-shr add machine specific frameworkd.conf files from frameworkd-config-shr move default frameworkd.conf from conf/example to etc directory 2010-12-30 Michael 'Mickey' Lauer config: align milliseconds in logger output; patch by ott814, thanks. 2010-11-11 Sebastian Krzyszkowiak Revert "oeventsd: workaround buggy kernel to get full vibration power." This reverts commit 72ad63c5ce9f2d25a119612065d86bd317bf80ed. Merge branch 'master' of git.freesmartphone.org:framework 2010-11-11 Marco Trevisan (Treviño) opimd: Added '_limit_start' query option. This query allows to set the SQL LIMIT x,y function in the proper way, now you can limit the results both setting the "end limit" (like before, using "_limit") and the start value to show (using the new "_start_limit" parameter). Useful for paginating results, reducing GUI lookups. 2010-11-04 Denis 'GNUtoo' Carikli ogpsd: msm.py: activate/desactivate gps by launching/killing the "gps" program I used subprocess for launching and killing the gps program. Here are the limitations: * playing too fast with gps's on/off in shr-settings can crash (and so reboot) the phone * The gps program must not be launched manually(not within frameworkd) Note that if we use shell=True for launching the gps program, the .kill() kills the shell instead of the gps program. Thanks a lot Yhg1s in #python on Freenode for help on debugging that 2010-11-04 Sebastian Krzyszkowiak Merge branch 'master' of git.freesmartphone.org:framework 2010-10-31 Daniele Ricci opimd: install database upgrade script 2010-10-30 Daniele Ricci Merge branch 'master' of git.freesmartphone.org:framework opimd: fixed passing columns descriptions to get_full_result 2010-10-27 Daniele Ricci opimd: insert database metadata if creating new database in order to prevent automatic database upgrade 2010-10-26 Daniele Ricci opimd: delete MessageSent=1 as well in upgrade script 2010-10-19 Daniele Ricci PIM: database upgrader for version 2.0 to 2.1 Db upgrader skeleton opimd: some small fixes PIM.Messages.QueryThreads: JOIN is enough 2010-10-15 Daniele Ricci PIM.Messages: final fixes for messages New switch Message(Un)read and MessageSent both converted to New PIM.Messages: added TotalCount attribute in QueryThreads PIM.Messages: MessageRead changed to MessageUnread PIM.Messages: query for retrieving threads 2010-09-13 Felix Huber ogpsd,ogsmd: fix missing global decl for sysfs path variables 2010-09-12 Felix Huber ophoned: optimise bluetooth headset funtions ogpsd: automatic adjust sysfs node to kernel version ogsmd ti_calypso: automatic adjust sysfs node to kernel version 2010-09-11 Denis 'GNUtoo' Carikli ogpsd: add initial support for htcdream's gps Htcdream's gps support is higly incomplete: *First NMEADevice has some parsing problem: 2010.09.11 12:13:35.500 ogpsd ERROR Line: ['GPVTG,nan,T,,M,0.0,N,0.0,K,A', '4'] *the user has to launch "gps" from the android-rpc recipe. That will be fixed in a future commit. Despite of the parsing problem,it seem to work, display the location etc... But it makes the logs grow In order to activate the htcdream's gps support, frameworkd.conf should have the following lines,after the ogsmd section: [ogpsd] device = GTA02Device channel = SerialChannel path = /dev/ttySAC1 2010-07-13 Martin Jansa ogsmd ti_calipsy: update sysfs node for 2.6.32+ kernels on freerunner ogpsd: update sysfs node for 2.6.32+ kernels on freerunner 2010-07-07 Sebastian Krzyszkowiak oeventsd: workaround buggy kernel to get full vibration power. This patch should be disabled as soon as fixed kernel arrives. 2010-06-23 Tom 'TAsn' Hacohen opimd: Updated TODO. 2010-06-05 Tom 'TAsn' Hacohen Added following name changes of ogsmd (i.e ogsmd restarts). #557 Thanks to Paul Fertser. 2010-05-28 Tom 'TAsn' Hacohen tools: Added opimd_fix_db to fix broken opimd database files. 2010-05-27 Antonio Ospite ogsmd: freescale_neptune use "CallAndNetwork" channel for DeviceMediator This resembles more the behaviour of the original firmware which sends, for instance, +CFUN on mux1. 2010-05-21 Antonio Ospite ogsmd: freecale_neptune, cleanup channels init Send AT commands only to the appropriate channels. Also reorganize the order of the channel classes in the code to reflect the respective dlci line order. ogsmd: freescale_neptune, make network registration more reliable On gen1 +CFUN={1,0} is very slow to sync up with the network and this can confuse ogsmd, by sleeping in DeviceSetAntennaPower and before NetworkRegister we give the modem the time to emit the responses to +CFUN and +COPS in a clearer order. We do the symmetric in NetworkUnregister. ogsmd: freescale_neptune, enable proper logging for URCs ogsmd: move _freescale_neptune_modemOn to __new__ By moving _freescale_neptune_modemOn() from __init__() to __new__() we can return None if the modem can't be initialized. Note that now _freescale_neptune_modemOn() is a static method. ogsmd: freescale_neptune, enable modem on gen2 phones '+EPOM=1,0' is needed on gen2 for most commands to work (+CPIN for instance), add also some other commands seen when tracing original firmware, and also '+CRC=1' which seems to work fine. ogsmd: device.py, detect modem creation failure Some devices may be in a certain status that makes impossible to initialize the modem, in these cases we should not instanciate the actual Modem class and return None, so we need to detect that. ogsmd: freescale_neptune, handle URCs on the Misc channel ogsmd: freescale_neptune, implement plusCLIP URC ogsmd: freescale_neptune, fix init for gen2 EzX phones Gen2 EzX phones need to open a different set of dlci lines to make BP finish its initialization. By using the union of the gen1 and gen2 dlci lines sets, and ignoring errors about invalid lines for a given generation, we can make init work on both with the same code. 2010-05-20 Sebastian Krzyszkowiak schema: remove ring-volume and message-volume params. They didn't work and leaded to confusion of users. 2010-05-11 Michael 'Mickey' Lauer processguard: attempt to fix zombie at process shutdown 2010-05-07 Tom 'TAsn' Hacohen opimd: changed Sender to Peer in messages. opimd: register signals through the bus, and not a proxy. 2010-05-04 Tom 'TAsn' Hacohen opimd: Updated TODO. 2010-05-03 Tom 'TAsn' Hacohen opimd: added sanity tests for not allowing addition of fields starting with < or >. Merge branch 'master' of ssh://git.freesmartphone.org/framework opimd: Added support for < and > which mean lesser than and bigger than respectively. 2010-05-02 Sebastian Krzyszkowiak opimd: Messages: support message reports with fsogsmd 2010-04-28 Tom 'TAsn' Hacohen opimd: Updated TODO. opimd: Changed Recipient/Sender to Peer in messages. opimd: Updated TODO. opimd: moved the dbus import to the start. opimd: Renamed @contacts to @Contacts and added support for _retrieve_full_contact. 2010-04-27 Tom 'TAsn' Hacohen opimd: GetMultipleResults returns all when the number of requested results is negative. 2010-04-26 Tom 'TAsn' Hacohen opimd: Updated TODO. oevents: Fixed "oneshot" vibration. Changed vibration strength to 90. 2010-04-25 Sebastian Krzyszkowiak opimd: Calls: fix path returned by MissedCall signal (...again :P) 2010-04-25 Tom 'TAsn' Hacohen opimd: Updated TODO. opimd: Fixed MissedCall signal. 2010-04-24 Tom 'TAsn' Hacohen opimd: remove obsolete backend_manager. opimd: Updated TODO. opimd: updated TODO. 2010-04-23 Tom 'TAsn' Hacohen Merge branch 'master' of ssh://git.freesmartphone.org/framework opimd: Fixed time parser. I assumed the date was day/month/year (I only had info with the date 10/04/10) While it actually was year/month/day. 2010-04-21 Klaus Kurzmann opreferencesd: adjust to API and rename Notify signal to Changed NOTE: this breaks applications listening to Notify !!! But is needed to make it work with glib signals (as used by libfso-glib). 2010-04-20 Tom 'TAsn' Hacohen opimd: Fixed a crash on Update. 2010-04-19 Tom 'TAsn' Hacohen opimd: Changed @ContactId to @contacts for future abstraction. Added a bit more infrastructure for future extensions to this temporary method. opimd: Updated TODO. opimd: Added support for list/array instead of , delimited list in GetMultipleFields. 2010-04-18 Tom 'TAsn' Hacohen opimd: restricted usage of @ in the start of fields, this is reserved for joining domains. opimd: Added a file I forgot to commit. opimd: remove debug print. opimd: Added _resolve_phonenumber query flag that resolves the phonenumber to a @ContactId. Also added infrastructure for complex domain joins. Merge branch 'master' of ssh://git.freesmartphone.org/framework Fixed a major bug with fsogsmd timestamp parsing with west to greenwich timezones. (i.e "negative" timezones). 2010-04-17 Michael 'Mickey' Lauer ophoned: add missing file headers 2010-04-15 Antonio Ospite This allows BP init to complete successfully on EZX phones without having to rely on ezxd. 2010-04-15 Michael 'Mickey' Lauer oeventsd: give call status a 2nd chance to match in lowercase NOTE: This should fix interoperability with fsogsmd ophoned: use lower case comparison for call status This should fix interoperability with fsogsmd 2010-04-13 Sebastian Krzyszkowiak opimd: Messages: fix compatibility with Python 2.5 opimd: db_handler: fix compatibility with Python 2.5 2010-04-12 Klaus Kurzmann opimd: make call logging work with fsogsmd too In fsogsmd the status in CallStatus signals is uppercase. 2010-04-10 Tom 'TAsn' Hacohen opimd: fixed a typo from the last commit Added fsogsmd support for IncomingTextMessage 2010-03-22 Tom 'TAsn' Hacohen oeventsd: Fixed vibration to work with fsodeviced instead of odeviced 2010-03-19 Tom 'TAsn' Hacohen opimd: Added more error handling in the case we get IncomingStoredMessage although we specifically asked for no sim buffering. * Delete the corrosponding sim contact * Reset SimBuffering to False again. 2010-03-18 Tom 'TAsn' Hacohen opimd: GetMultipleFields now support getting all fields of a certain type opimd: used the wrong variable in the if, and that caused a wacky behavior with fields with more than 2 values. opimd: Fixed a couple of typos in GetMultipleFields 2010-03-15 Sebastian Krzyszkowiak opimd: Contacts: uncomment default field types. It shouldn't be commented at all, as it's necessary to have sane defaults. 2010-03-10 Tom 'TAsn' Hacohen opimd: We now restrict the addition of empty values in a better manner. opimd_conversion_script: Added safety checks that old opimd values are valid (not empty). 2010-03-09 Felix Huber Fixed Implement HFP pbook retrieval, call indication and bluetooth autostart 2010-03-09 Tom 'TAsn' Hacohen Applied timestamps issue patch (in some cases importing data would fail without this patch) from Justus Winter. 2010-03-08 Tom 'TAsn' Hacohen Revert "Implement HFP pbook retrieval, call indication and bluetooth autostart" The changes in this commit don't even pass syntax checking, I don't have the time or will to understand this commit and fix it properly. Leaving this for the original author. This reverts commit 4d4641684459fd64552b4fa305615717dda3f961. Added more instructions to the convert script. Merge branch 'opimd-redesign' 2010-03-07 Felix Huber Implement HFP pbook retrieval, call indication and bluetooth autostart 2010-03-01 Tom 'TAsn' Hacohen Updated TODO Moved opimd convert script to the tools directory, and added it to setup.py 2010-02-26 Klaus Kurzmann ogsmd: add missing "serial" parameter to OpenSession call in ti_calypso/modem 2010-02-21 Tom Hacohen Fixed a typo. Don't send duplicated results when each part of the query matches a contact more than once Fixed a small issue with error handling with regex in db 2010-02-20 Tom Hacohen Fixed a lot of FIXMEs and Added QueryFailed dbus failure reason Updated TODO Started sending EntryId as well as Path, first step towards dropping it Adedd a couple of more exceptions (realetd to last commit). FIxed * We don't allow to query system fields, but we should catch the exceptions that makes, Add log messages when doing that and when trying to add reserved field Fixed * Don't let removing default must have fields Fixed typo Updated TODO Fixed a typo Fixed a bug with querying for entries with multiple fields Change list to dict in the python way of doing stuff 2010-02-19 Tom Hacohen Added indexing on the date type in messages and calls, makes retrieving calls and messages a lot faster Fixed a bug causing Types not to be listed in the Types.List dbus method Add a default set of fields to the contacts domain Fixed bug in the conversion script that caused types not to get converted Fixed a bug with generating values for non generic fields Made the conversion script use opimd's modules. It's nicer, easier and safer. We don't need tasklets in type_manager, removed that Changed pimd_generic so some of it's functions will be usable from the convert script Cleaned pimd_generic's init function just in case someone decides to call it Changed default tables per domain Add some kind of handling when phoneutils is not present (the behavior is mostly undefined when comparing numbers in the db) Updated TODO Adedd a log message concerning libphone-utils Updated TODO Phoneutils is not a must in the convert_db.py script anymore, but it is strongly advised for creating indexed databases. Fixed hardcoded path in conversion script Updated both opimd and script to start using sqlite collation functions and indexes on phonenumber values Updated TODO 2010-02-18 Tom Hacohen Added a workaround for a missing feature, should fix it though Fixed a bug with the query method Now adding must have fields, atm you can deleted them if you want, but they'll be added after a reboot automatically again Updated TODO: Don't allow removing default needed fields Unread messages count works New missed calls count works Updated TODO Fixed a typo that caused conversion/opimd not to work Added real type support (not only strings). Made database creation more generic Restricted adding reserved fields completely Added specific python types per opimd type Changed the script to work with the new type enforcement - i.e make sure fields are of a certain type Updated TODO Added support for the _at_least_one query option Fixed the path in the convert script Handle case when someone already converted the db Fixed a few issues Added convert_db.py that converts from the old db to the new 2010-02-17 Tom Hacohen Fixed format string in Nacked log error Added a cast to int in path_to_id function Fixed a bug in get single entry single field Made pimd_messages more type safe and fixed a couple of bugs Messages: Getting incoming message is now a bit cleaner and works. created get_content in the generic domain Added missed call handling to phonelog Fixed a bug with sortby causing duplicate entries on retrieval Forgot to enable fso tracking in calls - Fixed Fixed a couple of bugs in the calls backend Fixed a bug with GetSingleEntrySingleField Fixed a bug preventing running only one subsystem without the frameworkd subsystem Fixed a bug causing fields tables not to be created for domains other than contacts Moved loading fields to initialization instead of the ugly initialize everywhere Fixed a bug when listing domain fields Moved RESERVED_DEFAULT_FILEDS handling to pimd_generic.py Added a bit of restrictions concerning adding default fields 2010-02-17 Michael 'Mickey' Lauer frameworkd: add new command line option -n / --noframework to not start the special framework subsystem This is necessary, if you want to run several frameworkd processes with different sets of subsystems in parallel 2010-02-17 Tom Hacohen Added phonelogging capabilities to the calls backend I did not test it very well, but should work (code from old opimd). Set SimBuffersSms to False (i.e don't buffer) this fixes the issue of sms getting saved on sim and not in sqlite Added loading fields at generic domain init fixed a couple of bugs 2010-02-16 Tom Hacohen Fixed a typo Added support for handling incoming messages from ogsmd Added regex support Added everything needed for (non-optimized) number normalization Updated TODO Made db creation generic for all the domains, should improve but basic structure is there and everything works get_table_name and get_table_name_from_type are now generic and implemented in the parent object (db_handler) Fixed a bug that didn't let use two fields of the same type for a specific contact Renamed *_numbers tables to *_phonenumber (to match the type) removed the hackish list_types Added the internal list_types function and fixed a typo that would have caused bugs in the future Update TODO Fixed spacing (vs tabs) in contacts + messages daemons Fixed a typo in messages Passing lists in query means multiple options, i.e if Phone = [123, 12] the query will return all the contacts that their phone number is either 123 or 12 Added the entry_id (used for linking in the db) type Updated TODO Fixed pimd_calls.py Fixed pimd_dates Fixed pimd_tasks.py Fixed pimd_notes.py Removed all backends Dropped folders system from messages Fixed contact info Did most of pimd_messages.py Added opimd TODO Made contacts and db_handler more generic Fix the remains of the contacts-domain specific code in db_handler.py Fix all of the TBDs Did a bit of renaming (internal stuff) Moved ContactsDbHandler to pimd_contacts.py Removed supported domains list from db_handler (old backend relic) Made the db_handler more generic, each domain needs to implement the bare minimum now Fixed a bug that caused empty queries (i.e get all) not to work. Added contact information, and removed the Entry entities Adding and removing fields now also moves old values (from deleted/added fields) to the correct tables 2010-02-15 Tom Hacohen Fixed a small indent issue, and moved the generic functions to their generic location Fully working querying/adding/removing/updating 2010-02-12 Tom Hacohen opim: added a file I forgot to add in the previous commit opimd: Initial import of the redesign. It's still a terrible mess, not everything works (iirc only adding and deleting contacts works) This commit only includes cleanup and features for the contacts domain. Fields pickle file is now stored in the db 2010-02-11 Tom Hacohen opimd: Changed _limit to _pre_limit which limits before sorting, and changed _limit to limit after everything is done. 2010-01-28 Klaus Kurzmann setup.py: install the remove-tel script tools: add remove-tel script needed to convert old contacts to type based opimd 2010-01-26 Sebastian Krzyszkowiak opimd: update TODO 2010-01-25 Sebastian Krzyszkowiak Merge branch 'master' of git.freesmartphone.org:framework into dos/opimd-tracking 2010-01-16 Michael 'Mickey' Lauer cli-framework: do not introspect as this is starting all services on demand otherwise 2009-12-13 Sebastian Krzyszkowiak opimd: Contacts: add Nickname to default field types 2009-12-10 Sebastian Krzyszkowiak opimd: fix quering with non-str values 2009-12-09 Sebastian Krzyszkowiak opimd: update TODO opimd: rename Field dbus methods due to problems with duplicated names ***Breaks API!*** opimd: Generic: fix typo in ListFieldsWithType Merge remote branch 'origin/dos/opimd-tracking' into dos/opimd-tracking opimd: SIM-Messages-FSO: remove usage of phone_number_to_tel_uri opimd: SIM-Contacts-FSO: remove usage of phone_number_to_tel_uri opimd: ogsmd Calls: remove usage of phone_number_to_tel_uri 2009-12-06 Sebastian Krzyszkowiak opimd: Tasks: add ListFieldsWithType dbus method opimd: Dates: add ListFieldsWithType dbus method opimd: Calls: add ListFieldsWithType dbus method opimd: Notes: add ListFieldsWithType dbus method opimd: Messages: add ListFieldsWithType dbus method opimd: Contacts: add ListFieldsWithType dbus method opimd: Generic: implement list_fields_with_type method opimd: update TODO opimd: Calls: add default field types opimd: TypeManager: add number and integer types opimd: Messages: fix copy'n'paste error opimd: Messages: add default field types opimd: Type Manager: add boolean and timezone types opimd: Tasks: implement org.freesmartphone.PIM.Fields dbus methods opimd: generic: take care if FieldTypes isn't None on loading opimd: Notes: implement org.freesmartphone.PIM.Fields dbus methods opimd: Dates: implement org.freesmartphone.PIM.Fields dbus methods opimd: Calls: implement org.freesmartphone.PIM.Fields dbus methods opimd: Messages: implement org.freesmartphone.PIM.Fields dbus methods opimd: contacts: implement org.freesmartphone.PIM.Fields dbus methods opimd: generic: save fields modifications; don't fail on listing when list is None opimd: generic: implement list_fields, add_new_field and remove_field functions opimd: helpers: fix typo in dbus error opimd: contacts: define default field types opimd: generic: support loading, storing and fallbacking to default field types opimd: TypeManager: add objectpath type 2009-12-05 Sebastian Krzyszkowiak opimd: implement number normalizing using phoneutils with new, TypeManager way opimd: be aware of different field types Changes nothing yet :P opimd: remove all old handling of 'tel:' URI opimd: TypeManager: add few more default types opimd: introduce Type Manager opimd: update TODO 2009-11-28 Michael 'Mickey' Lauer ogsmd: const.py: speed up parsing networks.tab by assuming the file is really in UTF-8 2009-11-27 Sebastian Krzyszkowiak opimd: Dates: fix copy'n'paste errors in new signals opimd: Notes: fix copy'n'paste error in DeletedNote opimd: Tasks: few references to Notes remained, kill'em! opimd: Tasks: implement DeletedTask and UpgradedTask signals opimd: Tasks: fix references to Notes domain remaining there after copy'n'paste opimd: Notes: implement DeletedNote and UpdatedNote signals opimd: Dates: implement UpdatedDate and DeletedDate opimd: Calls: implement DeletedCall and UpdatedCall signals opimd: Messages: implement DeletedMessage and UpdatedMessage signals opimd: Contacts: fix dbus interfaces in new signals opimd: Contacts: implement DeletedContact and UpdatedContact signals opimd: fix error when using Python 2.4 2009-11-22 Sebastian Krzyszkowiak opimd: update TODO 2009-11-17 Michael 'Mickey' Lauer testing: add 5 seconds blocking delay to all operations, so that we can actually see whether client operations are being carried out sync. or async. 2009-10-20 Tom Hacohen opimd: Numbers are sometimes sent as 'unicode', changed the checks to conform to that. This solves the numbers not resolving issue. There's probably a bug somewhere else, though in the meanwhile this solves the issue. 2009-10-20 Sebastian Krzyszkowiak Merge branch 'master' of git.freesmartphone.org:framework into dos/opimd-tracking 2009-10-14 Baruch Even ogpsd: Set NAV2 parameters to define the static threshold to reduce movement noise when the GPS is stationary. ogpsd: Correct the comment about usage of SBAS We set mode to 1 which means we use SBAS but not if its in test-mode. 2009-10-10 Sebastian Krzyszkowiak opimd: update TODO opimd: init phone-utils earlier. Fixes wrong comparition values for phone numbers in contacts loaded at boot. 2009-10-09 Sebastian Krzyszkowiak opimd: helpers: little cosmetic fix 2009-10-06 Michael 'Mickey' Lauer cli-framework: add gsmrtc as FSO2 is (re)using the RTC interface for the Modem Clock 2009-10-06 Sebastian Krzyszkowiak opimd: SQLite-Tasks: change floats into ints opimd: SQLite-Notes: change floats into ints opimd: SQLite-Calls: change floats into ints opimd: ogsmd-Calls: change floats into ints opimd: SIM-Messages-FSO: change floats into ints opimd: SQLite-Messages: change floats into ints 2009-10-03 Sebastian Krzyszkowiak opimd: update TODO opimd: SQLite-*: use FLOAT type in database for Timestamps and Duration fields 2009-09-27 Guillaume Anciaux opimd: VCard-Contacts: fix storing vcard entries Signed-off-by: Sebastian Krzyszkowiak 2009-09-26 Sebastian Krzyszkowiak Merge commit 'origin/dos/opimd-tracking' into dos/opimd-tracking opimd: VCard-Contacts: fix logger levels opimd: VCard-Contacts: fix local variable 'value' reference before assignment opimd: VCard-Contacts: write in log about missing python-vobject if importing fails opimd: VCard-Contacts: few fixes (EMail=>E-mail, use the same file for reading and storing) 2009-09-26 Guillaume Anciaux opimd: add VCard-Contacts backend Signed-off-by: Sebastian Krzyszkowiak 2009-09-26 Sebastian Krzyszkowiak opimd: GenericDomain: fix unicode problems in make_comp_value 2009-09-16 Heinervdm opimd: Dates: fix copy and paste error Signed-off-by: Sebastian Krzyszkowiak 2009-09-13 Sebastian Krzyszkowiak opimd: SIM-Messages-FSO: comment out not-really-well-thought (read as: why the hell is it there?) code Merge branch 'master' of git@git.freesmartphone.org:framework into dos/opimd-tracking 2009-09-13 Michael 'Mickey' Lauer ogsmd: [TI CALYPSO]: on closing, drop out of muxing mode and power off the modem (AT@POFF) 2009-09-13 Sebastian Krzyszkowiak Merge commit 'origin/dos/opimd-tracking' into dos/opimd-tracking 2009-09-13 Michael 'Mickey' Lauer ogsmd: fix SMS timezone being parsed incorrectly for places where GMT<0 Fixes FSO #476. Patch courtesy "dkogan", thanks a lot! 2009-09-13 Sebastian Krzyszkowiak opimd: GenericDomain: move new get_full_content function out of comment ;x opimd: SIM-Messages-FSO: fix CSM handling opimd: GenericDomain add get_full_content function (returns also parser fields) 2009-09-12 Sebastian Krzyszkowiak Merge branch 'dos/opimd-tracking' 2009-09-12 Michael 'Mickey' Lauer cli-framework: fix devrtc and add timealarm 2009-09-12 Sebastian Krzyszkowiak opimd: SQLite-*: add isolation_level=None when connecting to sqlite file. Thanks TAsn! Closes #477 2009-09-09 Sebastian Krzyszkowiak opimd: Messages: fix AddIncoming 2009-09-07 Sebastian Krzyszkowiak opimd: Messages: implement AddIncoming dbus method It can be used by apps to "deliver" message to user, without writing special opimd backend/handler for that. odeviced: implement WakeupTimeChanged signal in RTC 2009-09-07 Michael 'Mickey' Lauer odeviced: kernel26: do not bail out, if we can't query the RTC 2009-09-07 Sebastian Krzyszkowiak opimd: Tasks: fix Update opimd: Tasks: fix Update opimd: Tasks: fix UnfinishedTasks opimd: Calls: fix possible traceback on deleting opimd: Messages: fix dbus signature in GetMultipleResults 2009-09-06 Sebastian Krzyszkowiak Merge branch 'dos/opimd-tracking' of git@git.freesmartphone.org:framework into dos/opimd-tracking opimd: Message: fix deleting messages without MessageRead and Direction fields (most often messages from SIM backend) opimd: update TODO 2009-09-05 Sebastian Krzyszkowiak Merge branch 'master' of git@git.freesmartphone.org:framework into dos/opimd-tracking 2009-09-05 Michael 'Mickey' Lauer otimed: alarm: catch up with API change in kernel26_rtc odeviced: kernel26: always use ioctls for talking to the realtime clock Warning: API change: This commit brings the exported interface in line with the FSO specs odeviced: add list of available scenarios in org.freesmartphone.Device.Audio odeviced: fix org.freesmartphone.Device.Audio.GetInfo() implementation 2009-09-04 Michael Kurz ogsmd: substitute empty provider string with information from network.tab. Fixes FSO #466 Some providers are too new for the modem, hence we get something like: +COPS: (2," "," ","26201"),(1,"o2 - de","o2 - de","26207"),(1,"E-Plus","E-Plus","26203"),(1,"Vodafone.de","Vodafone","26202") This fixes it by checking our (hopefully more current) provider database. 2009-09-03 Sebastian Krzyszkowiak Merge branch 'master' of git@git.freesmartphone.org:framework into dos/opimd-tracking 2009-09-03 Michael 'Mickey' Lauer ogsmd: ignore empty alphanumerical provider strings (happening for new providers) NOTE: Clients should use the SIM issuer instead, if registered to the home network. For this you have to call org.freesmartphone.GSM.SIM.GetSimIssuer() though, hence for layering purposes, we're not allowed to do it transparently in GetStatus(). Fixes FSO #465 2009-09-01 Sebastian Krzyszkowiak opimd: Tasks: implement UnfinishedTasks signal and GetUnfinishedTasks method opimd: Calls: fix syntax opimd: Notes: implement NewTag and TagRemoved signals opimd: Messages: implement GetUnreadMessages method opimd: Calls: implement GetMewMissedCalls method 2009-08-31 Sebastian Krzyszkowiak opimd: implement disabling handlers opimd: backend manager: add Synchronize method opimd: fix possible incorrect query results when using _at_least_one mode opimd: fix returning multiple fields with the same data after merging opimd: Messages: fix copy'n'paste issues opimd: Messages: move to GenericDomain Merge branch 'master' of git@git.freesmartphone.org:framework into dos/opimd-tracking opimd: fix merging 2009-08-29 Sebastian Krzyszkowiak opimd: SIM-Messages-FSO: start preparing for GenericDomain migration opimd: SIM-Messages-FSO: another attempt to fix deleting opimd: SIM-Messages-FSO: fix deleting messages opimd: Messages: backport last changes from GenericDomain opimd: kill duplicates! We don't like them! 2009-08-27 Sebastian Krzyszkowiak opimd: cast Timestamp to float when loading from database opimd: support less or equal and greater or equal Usage is the same like with _lt_ and _gt_ (_le_ and _ge_) opimd: Dates: remove custom matcher opimd: fix sorting non-string values conf: enable merging in opimd Contacts domain opimd: disable merging by default opimd: little optimalizantion in merging and getting field value from entry opimd: add possibility to sort case sensitive To use that, query with _sortcasesens set to non-false value. Default is False. 2009-08-26 Sebastian Krzyszkowiak opimd: implement _initialized in backends 2009-08-26 Michael 'Mickey' Lauer ogsmd: add KPN, thanks dos1 ogsmd: add provider 'Telfort' to gprs. Thanks pander, closes FSO #394 ogsmd: add number type 161 as reported by shishz Closes FSO #469 2009-08-26 Sebastian Krzyszkowiak opimd: now *really* place entries without field which is used for sorting always on end ;) opimd: place entries without field which is used for sorting always on end opimd: implement sorting _sortby field in query specifies field name to use when sorting. To reverse results, use non-false value of _sortdesc Merge branch 'dos/opimd-tracking' opimd: add new mode in quering: at least one To use it, set _at_least_one field to non-false value. In this mode returned entries match to at least one field in query. opimd: Calls, Notes: check entry id when deleting to prevent tracebacks opimd: remove entries from cache when disabling backend opimd: backend manager: send dbus signal after initializing all backends on startup opimd: add experimental support of "greater than" and "lesser than" queries Field names to use when quering: _lt_Field, _float_lt_Field, _gt_Field, _float_gt_Field - compare after casting to float _int_lt_Field, _int_gt_Field - compare after casting to int 2009-08-25 Eric Olson config: improve finding installpath when symlinked (ipkg-link etc.) to other locations 2009-08-25 Mirko Vogt patterns: chose the first libc you find, this makes is works with uclibc. Fixes FSO #338 2009-08-25 Sebastian Krzyszkowiak Merge branch 'master' into dos/opimd-tracking opimd: little updates in docs directory opimd: backend manager: add GetInitialized method ATM it always returns False, but it'll change when backends learns how to use it. opimd: handle correctly blank fields 2009-08-24 Sebastian Krzyszkowiak opimd: handle comp_value correctly opimd: move making comp_value into function opimd: Don't create comp_value when not needed opimd: Fix updating multiple fields 2009-08-23 Sebastian Krzyszkowiak opimd: SQLite-*: fix traceback when editing 2009-08-22 Sebastian Krzyszkowiak opimd: SQLite-*: support editing multiple values of one field opimd: SQLite-Notes: support editing multiple values of one field opimd: Notes: rename GetTags to GetUsedTags 2009-08-21 Sebastian Krzyszkowiak opimd: Notes: implement GetTags method opimd: catch up with changes in dbus errors opimd: Calls: fix copy'n'paste error opimd: Generic: use re.escape instead of str.replace opimd: Todos: rename to Tasks 2009-08-20 Sebastian Krzyszkowiak opimd: SQLite-Todos: add new backend conf: add default backends for new domains opimd: SQLite-Notes: add new backend opimd: SQLite-Dates: fix typo when updating entry opimd: Dates: fix quering in Dates domain. (more love is still needed) opimd: add Todos domain opimd: Dates: fix traceback when adding entry opimd: don't fail when can't import phoneutils opimd: Calls: fix copy'n'paste error in Update and Delete opimd: accidentaly commited too lot. Fixing. opimd: use libphone-utils to normalize phone numbers, fix quering multiple values and fix entry paths 2009-08-19 Sebastian Krzyszkowiak opimd: Notes: add new domain opimd: Calls: few cosmetic fixes opimd: Calls: fix register_missed_call opimd: Calls: fix copy'n'paste error opimd: SQLite-Calls: fix function names opimd: Calls: implement missing stuff opimd: Calls: base Calls domain on GenericDomain (not everything ported yet!) opimd: Contacts: cosmetic changes in few comments opimd: Query manager: fix missing import 2009-08-15 Sebastian Krzyszkowiak opimd: SIM-Messages-FSO: try to fix deleting messages 2009-08-14 Sebastian Krzyszkowiak Merge branch 'master' into dos/opimd-tracking opimd: SQLite-Contacts: fix typo in Department field, which may cause problems when editing 2009-08-10 Sebastian Krzyszkowiak opimd: Dates: raise correct dbus error (InvalidData) opimd: helpers: add InvalidData dbus error opimd: Dates: fix syntax error 2009-08-10 Thomas Zimmermann opimd: include Dates domain and SQLite-Dates backend when loading Signed-off-by: Sebastian Krzyszkowiak 2009-08-10 Sebastian Krzyszkowiak opimd: Dates: don't reference do Contacts in code anymore 2009-08-10 Thomas Zimmermann opimd: Dates: fixed indentiation error and removed unused method GetDatesOfDayByTimestamp Signed-off-by: Sebastian Krzyszkowiak opimd: add Dates domain and SQLite-Dates backend Signed-off-by: Sebastian Krzyszkowiak 2009-08-08 Sebastian Krzyszkowiak opimd: SIM-Contacts-FSO: update function names opimd: Generic: fix dbus signatures opimd: Contacts: fix dbus signature in GetContactPath opimd: Contacts: inherit from GenericDomain, general fixes opimd: Generic domain: general fixes opimd: Query manager: fix variable name opimd: SQLite-Contacts: update function names opimd: CSV-Contacts: Update function names 2009-08-07 Sebastian Krzyszkowiak opimd: Contacts: get rid of SingleQueryHandler - use one from query_manager Merge branch 'master' of git@git.freesmartphone.org:framework into dos/opimd-tracking Conflicts: framework/subsystems/opimd/pimd_contacts.py opimd: fix posible division by zero when matching entries. Should fix delivery reports. 2009-08-07 Michael 'Mickey' Lauer ogsmd: remove duplicated function ogsmd: fix GetCallForwarding unhandled exception in response callback. Closes #462 Thanks to 'Michael' (please include your E-Mail address, if you want proper credit ;) docs: add (longer) ussd session 2009-08-03 Sebastian Krzyszkowiak Merge branch 'master' of git@git.freesmartphone.org:framework into dos/opimd-tracking opimd: SIM-*-FSO: cast _backend_entry_id to int when deleting and updating opimd: use GenericEntry in Contact class and refactor some opimd structures (experimental!) Merge branch 'master' of git@git.freesmartphone.org:framework into dos/opimd-tracking 2009-08-03 Jan Luebbe ogsmd: [TI CALYPSO] verify that we got a complete monitor response from the modem 2009-08-01 Sebastian Krzyszkowiak opimd: fix imports and move QueryMatcher class into query_manager.py opimd: GenericDomain: implement EntryAdded signal in queries Merge branch 'master' of git@git.freesmartphone.org:framework into dos/opimd-tracking opimd: Queries: implement (Contact|Message|Call)Added signal 2009-07-31 Sebastian Krzyszkowiak opimd: GenericDomain: implement merging opimd: GenericDomain: few cosmetic changes opimd: add GenericDomain opimd: helpers: fix typo in NoMoreCalls dbus exception opimd: Calls: fix typo in GetSingleCallSingleField method 2009-07-29 Sebastian Krzyszkowiak opimd: make merging a little faster and fix bug with duplicates opimd: ogsmd-Calls: fix error when loading entries 2009-07-27 Sebastian Krzyszkowiak opimd: SIM-Messages-FSO: implement deleting messages 2009-07-25 Sebastian Krzyszkowiak opimd: move factory from __init__.py to opimd.py opimd: Messages: add forgotten import opimd: Calls: use regexps instead of difflib opimd: Messages: use regexps instead of difflib Use regular expressions instead of difflib in Contacts domain 2009-07-21 Sebastian Krzyszkowiak opimd: SIM-Messages-FSO: fix fail when opimd starts before ogsmd 2009-07-19 Sebastian Krzyszkowiak opimd: SQLite-*: support multiple fields with the same name ogsmd: abstract modem: fix traceback on error opimd: little cleanup, copyrights, pypimd -> opimd opimd: CSV-Contacts: support multiple fields with the same name opimd: SIM-Messages-FSO: fix possible error when installing signal handlers opimd: SQLite-Contacts: add indexes to database opimd: SQLite-Messages: add indexes to database opimd: SQLite-Calls: order by id desc as default opimd: SQLite-Calls: remove too much indexes 2009-07-18 Sebastian Krzyszkowiak Merge branch 'master' of git@git.freesmartphone.org:framework oeventsd: add And function to parser 2009-07-15 Michael 'Mickey' Lauer * Merge mickey/full-csd-handling branch into master. This adds a new configuration entry 'data-call-handler' in section [ogsmd] where a handler binary can be specified that gets called whenever a data call has been setup. This handler will be called with params , where direction is "incoming" or "outgoing" (depending on whether you are the data call initiator or peer), and port is a device node that can be used for communicating. Once the binary exits, the call is hung up. * Post-release version bump Merge commit 'origin/mickey/full-csd-handling' 2009-07-12 Sebastian Krzyszkowiak networks.tab: update information about CenterNet network (Poland) opimd: ogsmd-Calls: handle calls on multiple lines opimd: docs: update api overview oeventsd: fso_triggers: add IncomingUssd trigger oeventsd: fso_triggers: add UnreadMessages and UnreadMessagesTrigger opimd: Messages: implement UnreadMessages signal oeventsd: fso_triggers: add NewMissedCalls and NewMissedCallsTrigger opimd: Calls: implement NewMissedCalls signal and make Update a bit faster opimd: cosmetic changes in _limit handling opimd: SQLite-Messages: fix poor typo... opimd: add new property PIMB_IS_HANDLER opimd: SQLite-Messages: add missing PIMB_CAN_UPD_ENTRY_WITH_NEW_FIELD to properties 2009-07-11 Sebastian Krzyszkowiak opimd: remove unnecessary transformations. Should speed up a litle quering. 2009-07-10 Sebastian Krzyszkowiak opimd: make queries with _limit higher than amount of reslts working opimd: SQLite-Calls: fix tracebacks introduces by latest commits opimd: SQLite-Calls: fix typos opimd: SQLite-Calls: provide some fields as float and some as int opimd: Calls: use Peer field instead of Caller and Recipient networks.tab: update information about Play network (Poland, 260-06) config: specify default Calls backend for opimd (SQLite-Calls) opimd: SQLite-Calls: add more indexes opimd: SQLite-Calls: add IF NOT EXISTS when adding index to database opimd: add dbus errors related to calls 2009-07-09 Sebastian Krzyszkowiak opimd: SQLite-Calls: add index on Direction field opimd: SQLite-*, remove added and updated fields, DELETE from db instead of setting deleted field to 1. opimd: don't return private fields in GetContent Merge branch 'master' of git@git.freesmartphone.org:framework opimd: don't list empty fields in content 2009-07-09 Jan Luebbe celldb: skip broken cell ids add tool to dump the binary cell db celldb: update from cellhunter 2009-07-09 Sebastian Krzyszkowiak opimd: SIM-Messages-FSO: fix syntax error opimd: SIM-Messages-FSO: add error handling when handling timestamp opimd: Backend Manager: don't allow setting backend without ADD property as default opimd: ogsmd-Calls: handle calls with hidden peer number 2009-07-09 Michael 'Mickey' Lauer this is milestone 5.5 2009-07-09 Sebastian Krzyszkowiak opimd: SIM-Messages-FSO: handle timestamps 2009-07-08 Sebastian Krzyszkowiak opimd: SQLite-Messages: fix typo opimd: ogsmd-Calls: store Timezone data opimd: register new domain and backends opimd: Add Calls domain, SQLite-Calls backend and ogsmd-Calls backend (handler) opimd: SQLite-Messages: remove Date field and add Timestamp and Timezone fields Merge branch 'master' of git@git.freesmartphone.org:framework 2009-07-08 Michael 'Mickey' Lauer etc: new ringtones 2009-07-08 Sebastian Krzyszkowiak opimd: Messages: comment check_new_message. It doesn't work now, only slows down everything. 2009-07-08 Tommy B oevents: fso_actions: call updatepending when untriggering occupyresource 2009-07-08 Sebastian Krzyszkowiak opimd: SIM-Messages-FSO: add log message when nacking message 2009-07-04 Sebastian Krzyszkowiak ogpsd: fix typo in log messages 2009-07-03 Michael 'Mickey' Lauer resource: enable, if ousaged is not present 2009-07-02 Sebastian Krzyszkowiak opimd: SIM-Messages-FSO: check if status-report-request is 1 when handling receipt 2009-07-01 Michael 'Mickey' Lauer ousaged: mark as DEPRECATED resource: try to actually call fsousaged before giving up (if you're lucky, it might start on demand due to dbus system activation) * use dbuscache 2009-06-29 Michael 'Mickey' Lauer ogsmd: [TI CALYPSO] lower threshold for waking Calypso up from deep sleep 2009-06-29 Sebastian Krzyszkowiak opimd: SIM-Messages-FSO: mark merged message as unread after incoming CSM opimd: SIM-Messages-FSO: handle incoming split messages and indicate missing parts 2009-06-28 Sebastian Krzyszkowiak opimd: SIM-Messages-FSO: use more fields when quering for delivery report handling opimd: SIM-Messages-FSO: install signal handlers and SetSimBuffersSms on AuthStatus, not ReadyStatus opimd: SIM-Messages-FSO: change True and False to 1 and 0 in field values opimd: SIM-Messages-FSO: add SMS-complete_message and SMS-combined_message on incoming split message opimd: SIM-Messages-FSO: clean SMS-message-reference field on delivery report opimd: SIM-Messages-FSO: export part of split message in field in incoming message opimd: SIM-Messages-FSO: export parts of split messages in fields of stored messages 2009-06-27 Sebastian Krzyszkowiak opimd: Contacts: allow disabling merging from config 2009-06-27 Michael 'Mickey' Lauer oeventsd: fix syntax error in actions; don't bail out, if opreferences is not existing 2009-06-27 Sudharshan 'Sup3rkiddo' S otimed.alarm: Fix a little typo. Clears the timer properly 2009-06-27 Sebastian Krzyszkowiak opimd: SIM-Messages-FSO: Nack message when registering failed opimd: SIM-Messages-FSO: handle message receipts (delivery reports) opimd: SIM-Messages-FSO: add Source field with value 'SMS' 2009-06-26 Michael 'Mickey' Lauer odeviced: kernel26: remove APM power class supply. We do not support APM. 2009-06-26 Sebastian Krzyszkowiak Merge branch 'master' of git@git.freesmartphone.org:framework 2009-06-26 Michael 'Mickey' Lauer oeventsd: fso-actions: don't store values acquired by oprofile as variants 2009-06-26 Sebastian Krzyszkowiak opimd: SQLite-Contacts: fix accidentaly wrapped line opimd: SQLite-*: handle database errors opimd: SQLite-*: don't store special _ fields opimd: SIM-Messages-FSO: rename Text field to Content opimd: Messages: allow disabling merging in config opimd: SQLite-Messages: fix updating MessageRead, MessageSent and Processing fields 2009-06-25 Sebastian Krzyszkowiak opimd: Messages: rename GetMessageURI to GetMessagePath and GetMessageURIs to GetMessagePaths opimd: don't fail when some queried field is not string opimd: implement MessageUpdated and MessageDeleted signals opimd: implement NewContact, ContactUpdated and ContactDeleted signals 2009-06-24 Sebastian Krzyszkowiak opimd: SIM-Messages-FSO: change _sms_... fields to SMS-... opimd: don't fail on registering item, when some other was deleted previously opimd: BackendManager: don't fail when registering or loading entries from some backend fails opimd: SIM-Messages-FSO: disable SIM SMS buffering after installing signal handlers 2009-06-23 Sebastian Krzyszkowiak opimd: don't change paths when deleting contact/message opimd: SQLite-Messages: fix adding new messages to database opimd: use better way for installing ReadyStatus signal handler opimd: install GSM signal handlers even if ogsmd isn't present when initing opimd opimd: update docs opimd: SQLite-Messages: change read and sent to MessageRead and MessageSent, and add Processing field opimd: Messages: implement Update method 2009-06-21 Michael 'Mickey' Lauer oeventsd: fso_actions.py: fix typo in connect_to_signal 2009-06-21 Sebastian Krzyszkowiak opimd: Messages: fix possible duplicating incoming messages opimd: SIM-Messages-FSO: SMS fields now starts with _sms_*, not _backend_*. Only SIM ID stays _backend_entry_id opimd: Messages: add stub for single_message_matches opimd: Messages: fix tracebacks in few dbus methods conf: specify default backend for opimd Messages domain 2009-06-20 Sebastian Krzyszkowiak opimd: Messages: Implement handling incoming messages opimd: introduce PIMB_NEEDS_SYNC property and use it instead of try...except: blocks [opimd] Messages: implement Delete method and support it in SQLite backend [opimd] add SQLite-Messages backend [opimd] Messages: fix possible division by zero [opimd] Contacts: fix possible division by zero 2009-06-18 Michael 'Mickey' Lauer resource: fix checkedasyncmethod to properly raise errors 2009-06-17 Michael 'Mickey' Lauer oeventsd: fso_actions: add state logic to the OccupyResource action frameworkd: don't fail if loophole can't be launched resource: add exception logger objectquery: use dbuscache.dbusInterfaceForObjectWithInterface 2009-06-12 Michael 'Mickey' Lauer frameworkd.conf: make it work on newer dbus versions that are non-permissive (aka: paranoid) by default 2009-06-12 Luca Capello Improve frameworkd configuration example and dbus configuration [PATCH 1/5] Gypsy belongs to the freedesktop tree in D-Bus frameworkd.conf [PATCH 2/5] conf/example/frameworkd.conf: adjust log_level use [PATCH 3/5] conf/example/frameworkd.conf: simplify log_to documentation [PATCH 4/5] conf/example/frameworkd.conf: adjust disable use [PATCH 5/5] conf/example/frameworkd.conf: fix English typos 2009-06-09 Jan Luebbe idlenotifier: finally fix #416 Revert idlenotifier to 516f48762ab7afa4e4702a0ba01bc0a004ae4c60 2009-05-29 Michael 'Mickey' Lauer ogsmd: [ABSTRACT] fix reporting access technology in status signal ogsmd: [QUALCOMM MSM] fix typo in ppp options. NOTE: Given a recent kernel, ppp works fine w/ UMTS for the HTC Raphael and friends. 2009-05-28 Sebastian Krzyszkowiak opimd: SIM_Messages_FSO: fix crash when last part of split message isn't in inbox 2009-05-28 Michael 'Mickey' Lauer docs: [QUALCOMM MSM] add +gkti urc 2009-05-23 Alain2210 ogsmd: [FREESCALE NEPTUNE] enable charging and fix a buglet in the modem's channel 2009-05-23 Michael 'Mickey' Lauer odeviced: audio.py: fix sending dbus signals for AlsaPlayer. Thanks Toaster for spotting. 2009-05-23 Alain2210 oeventsd: add KeypadEvent trigger to fso_triggers, add Shutdown action to fso_actions ogsmd: [FREESCALE NEPTUNE] fix GetAuthStatus for modems which do not support +CPIN? 2009-05-21 Sebastian Krzyszkowiak opimd: SQLite_Contacts: provide _backend_entry_id as int opimd: SIM_Contacts_FSO: provide _backend_entry_id as int opimd: SIM_Contacts_FSO: indicate possibly truncated field values 2009-05-21 Daniel Willmann ogsmd: Fix copy and paste error to also support CSM with 16 bit IDs 2009-05-21 Sebastian Krzyszkowiak opimd: SIM_*_FSO: fix possible tracebacks 2009-05-20 Michael 'Mickey' Lauer dbuscache: use python2.5-compatible syntax 2009-05-19 Sebastian Krzyszkowiak opimd: SIM_Messages_FSO: init automatically on SIM ready opimd: SIM_Contacts_FSO: load entries automatically after SIM goes ready opimd: Backend manager: init backends paralelly on opimd startup 2009-05-18 Michael 'Mickey' Lauer ogsmd: [FREESCALE NEPTUNE] honor custom +EKEV URC and send org.freesmartphone.GSM.Device.KeypadEvent() ogsmd: add signal org.freesmartphone.GSM.Device.KeypadEvent( s, b ) dbuscache: commit missing hunk 2009-05-17 Michael 'Mickey' Lauer ogsmd: channel.py: relaunch processing the queue after handling a timeout. Closes FSO #414. ousaged: trigger idlenotifier's busy state after waking up. Closes FSO #410 ophoned|otimed: use dbuscache dbuscachepy: use lazy lookup by default. NOTE: This means you can never assume that a dbus object is actually present by the time you want to call it. You need to be prepared for errors. 2009-05-17 Sebastian Krzyszkowiak opimd: SIM_Messages_FSO: combine spliten messages opimd: Messages: implement merging Messages from different backends and duplicates opimd: Contacts: remove unnecesary variable 2009-05-16 Michael 'Mickey' Lauer ogsmd: [TI CALYPSO] reorder suspend/resume commands make sure SMS/CB notifications are disabled first, and reenabled last 2009-05-16 Sebastian Krzyszkowiak opimd: SIM_Messages_FSO: some initial work 2009-05-16 Michael 'Mickey' Lauer ogsmd: repair damage done by 9c746079d020f483257c64200cd0e18ca8586f13. should fix #404 2009-05-15 Sebastian Krzyszkowiak opimd: Contacts: use lower TRESHOLD to have better contact name resolving opimd: Messages: fix inconsistences with Contacts backend opimd: implement adding new fields when updating existing contact 2009-05-13 Daniel Willmann oeventsd: Fix SetLed rule SetLed needs to follow name owner changes since they aren't there at the time oeventsd parses the rule. This commit adds an attribute follow_name_owner_changes to the dbuscache.dbusInterfaceForObjectWithInterface method. Merge branch 'master' of ssh://git@git.freesmartphone.org/framework ogsmd: Handle the case where the address has zero length Comparing with [] doesn't work for arrays len() == 0 is better. 2009-05-13 Sebastian Krzyszkowiak opimd: SIM_Contacts_FSO: cosmetical fix opimd: SIM_Contacts_FSO: fix adding new contacts opimd: SIM_Contacts_FSO: implement adding new contacts on SIM 2009-05-13 Michael 'Mickey' Lauer rules: fix requesting the CPU resource during calls odeviced: kernel26: check whether the battery is actually present before evaluating the capacity This should fix the infamous shutdown when you use a GTA01 battery in GTA02. 2009-05-12 Michael 'Mickey' Lauer ogsmd: CSD handling with external call handler part 2/3 done (outgoing) Limitation: the state machine does not know about the additional state and will raise an execption, on everything call handling while the call handler is active. To end a call while the csd handler is still active, you can only use org.freesmartphone.GSM.Call.ReleaseAll() ogsmd: CSD handling with external call handler part 1/2 done (incoming) 2009-05-11 Sebastian Krzyszkowiak opimd: SIM_Contacts_FSO: another try to fix Update() and Delete() 2009-05-10 Jan Luebbe otimed: handle the case when we have no GSM time zone and are in a multi zone country 2009-05-08 Sebastian Krzyszkowiak opimd: SIM_Contacts_FSO: fix Update() and Delete() opimd: Contacts: call upd_contact only once for every used backend opimd: SIM_Contacts_FSO: don't block whole frameworkd when deleting or updating SIM contact 2009-05-08 Sudharshan 'Sup3rkiddo' S otimed: Fix a couple of tiny typos 2009-05-08 Michael 'Mickey' Lauer odeviced: idlenotifier: do not reset all config timeouts when re(enabling) one 2009-05-08 Sebastian Krzyszkowiak opimd: Contacts: implement GetUsedBackends() 2009-05-07 Sebastian Krzyszkowiak opimd: helpers: add IncorectDomain error opimd: Contacts: implement merging Contacts, Messages: rename enumerate_(contacts|messages) to enumerate_items opimd: backend manager: raise correct errors and Init on Enable() 2009-05-07 Michael 'Mickey' Lauer oeventsd: action.py: restore python2.5-compatibility, thanks Paul Fertser for spotting 2009-05-06 Sebastian Krzyszkowiak Merge branch 'master' of git@git.freesmartphone.org:framework opimd: implement GetDomains() 2009-05-06 Michael 'Mickey' Lauer Revert "odeviced: fix idlenotifier" for now This reverts commit 516f48762ab7afa4e4702a0ba01bc0a004ae4c60 which -- for a reason yet unknown -- exposes FSO #416. 2009-05-05 Michael 'Mickey' Lauer oeventsd: fix regression introduced by cleanup 2009-05-05 Sebastian Krzyszkowiak opimd: implement GetBackends() opimd: again few useful methods - Enable, Disable, SetAsDefault 2009-05-05 Joachim Ott oeventsd: use subprocess.call to get rid of zombies (when using xset) 2009-05-05 Daniel Willmann ogsmd: Handle the case where AT+CLIR? only returns OK o.f.GSM.Network.GetCallingIdentification now returns "unknown". 2009-05-05 Michael 'Mickey' Lauer oeventsd: use dbus cache and misc. cleanups patterns: add a dbus cache for object connections 2009-05-05 Sebastian Krzyszkowiak opimd: add some useful dbus methods regarding backends 2009-05-05 Michael 'Mickey' Lauer resource.py: get rid of callback factory and rewrite for improved clarity. This does NOT fix #416 though. loophole: deal with varying line endings inject controller object into loophole; from there we should be able to gather all interesting references patterns: add loophole object to prepare for post-mortem/live interactive debugging next step: hand out references to interesting objects to network interpreter 2009-05-04 Sebastian Krzyszkowiak opimd: SQLite_Contacts: implement adding new fields to contact on Update() 2009-05-02 Sebastian Krzyszkowiak opimd: SIM_Contacts_FSO: implement Update() opimd: SQLite_Contacts: implement Update() Revert "opimd: Contacts: ignore special _backend fields when merging" This reverts d1fa4df754461417dd2a26d134c03a1851d86f9d opimd: Contacts: get rid of unwanted print command 2009-05-01 Sebastian Krzyszkowiak opimd: Contacts: implement Update(); allow backends to use sync() after deleting or updating; CVS_Contacts: use sync(). opimd: don't init entries from backends disabled in config opimd: add Init() method to Source opimd: init all entries on startup opimd: Contacts: ignore special _backend fields when merging opimd: Contacts: return successful merge when trying to add contact, which already exists It should close "Don't duplicate contacts when calling InitAllEntries twice" ticket on my paper-trac :) opimd: SQLite_Contacts: fix backend properties to be more accurate ATM opimd: SIM_Contacts_FSO: implement deleting contacts and remove requesting/releasing GSM resource Merge branch 'master' of git@git.freesmartphone.org:framework opimd: CSV_Contacts: fix deleting opimd: Contacts: fix importing fields many times from the same backend 2009-05-01 Michael 'Mickey' Lauer [TI CALYPSO] document +CCLK oddity 2009-05-01 Sebastian Krzyszkowiak opimd: add some stub for updating contacts opimd: CSV_Contacts: support deleting and updating contacts 2009-05-01 Michael 'Mickey' Lauer resource: ran into the same logic error as last time. We can only check the resource status at the time the command is actually being performed. 2009-04-30 Michael 'Mickey' Lauer resource: do not enqueue suspend or resume commands, if a resource is disabled this should bring the resource handling back on track ogsmd: do not try to suspend or resume a modem that doesn't exist (read: has never been used before) resource: use SynchronizedAsyncWorker as worker queue asyncworker: add synchronized version of AsyncWorkerQueue 2009-04-30 Sebastian Krzyszkowiak opimd: fix possible ID conflict after deleting contact. opimd: SIM_Contacts_FSO: fix unicode handling 2009-04-29 Sebastian Krzyszkowiak opimd: SQLite_Contacts: set _backend_entry_id in add_entry too. Fixes problem with deleting new contacts. 2009-04-29 Sebastian Krzyszkowiak opimd: add support for deleting contacts in SQLite backend. Worked by Heinervdm too. opimd: add Delete() method in Contacts domain 2009-04-29 Jan Luebbe otimed: handle the case where no network code is present correctly 2009-04-29 Michael 'Mickey' Lauer ogsmd: Implement org.freesmartphone.GSM.Device.GetRTC() -> (i) 2009-04-28 Jan Luebbe resource: fix callback name asyncworker: catch and log exceptions thrown by the work units 2009-04-27 Michael 'Mickey' Lauer ogsmd: call stateAntennaOn, when the antenna is on. Fixes FSO #398 docs: gnufiish proprietary commands 2009-04-27 Jan Luebbe ogsmd [TI CALYPSO]: switch %CUNS back to 2 (no mixed solisticated/unsolisticated messages) %CUNS=0 exposed a bug in the %EM commands, which were losing some output lines. 2009-04-26 Sebastian Krzyszkowiak opimd: fix custom fields handling in SQLite-Contacts backend Revert "opimd: don't fail when default backend isn't specified. Use some, which supports specified domain." That isn't correct way to do this. This reverts commit a100a6b73023abb4ed385cf0e3615c1f303f6cc4. 2009-04-25 Sebastian Krzyszkowiak opimd: don't fail when default backend isn't specified. Use some, which supports specified domain. 2009-04-25 Michael 'Mickey' Lauer ousaged: Serialize resources with an asynchronous worker queue. This should ease fixing FSO #404. 2009-04-25 Sebastian Krzyszkowiak opimd: convert sqlite backend from windows to unix format opimd: add sqlite contacts backend by Heinervdm and modify it to work 2009-04-23 Michael 'Mickey' Lauer ogsmd: secure sending the first line of multiline messages against errors. This should improve #332 2009-04-22 Cameron Frazier opreferences: add a signal for when a whole profile has been changed 2009-04-22 Michael 'Mickey' Lauer ogsmd: [FREESCALE NEPTUNE] revert last change causing an endless loop in sending +EMGL=4 docs: document +CFUN resetting +CLVL ogsmd: [TI CALYPSO] send +CLVL=255 after the antenna has been powered on, otherwise it gets reset. closes FSO 2009-04-22 Sascha Wessel ogpsd: Fix race in ogpsd/ousaged when using Gypsy Start() method This addresses some of the issues listed in (FSO #251). 2009-04-22 Daniel Willmann ogsmd: Use proper base class when calling responseFromChannel This fixes bug #390 and also fixes a potential problem in ListCalls. 2009-04-21 Michael 'Mickey' Lauer ogsmd: add note where to handle data calls in calling.py 2009-04-20 Jan Luebbe otimed: bugfixes otimed: more work otimed: zone handing rewrite 2009-04-19 Michael 'Mickey' Lauer ogsmd: do not bail out if call mode is != VOICE ogsmd: [TI CALYPSO] report call mode (voice, data, ...) in org.freesmartphone.GSM.CallStatus odeviced: audio: introduce AlsaPlayer using aplay for wav playback. This should improve playback latency, if e.g. a .wav is selected as ringtone. patterns/processguard: introduce hadpid, so we have a handle to check for after a process ended frameworkd.conf: document ti_calypso_muxer entry 2009-04-19 Angus Ainslie otimed: apply patch from Angus to handle CTZV messages 2009-04-19 Michael 'Mickey' Lauer refactor killall into framework.patterns.utilities module 2009-04-18 Michael 'Mickey' Lauer odevice: audio: refactor audio player to support multiple player engines (covering disjunct formats) ogsmd: channel: add freeze() and thaw(), preparing for data call and pdp channel handover 2009-04-17 Sebastian Krzyszkowiak odeviced: fix USB Host mode with latest kernels. Patch by Paul Fertser. Signed-off-by: Paul Fertser 2009-04-16 Sebastian Krzyszkowiak opimd: fix inconsistence with Messages and Contacts API Since now opimd Messages should work :) opimd: fix incorrect variable name opimd: remove unnecessary syslog line 2009-04-16 Daniel Willmann dtest: Add DBus interface and object caching 2009-04-15 Jan Luebbe odeviced: fix idlenotifier idlenotifier: more debugging and error recovery in callback This should help us to solve FSO bug #333. setup.py: ship cell db otimed: make ntp server configurable 2009-04-14 Michael 'Mickey' Lauer * [ogsmd] Support handling setup and tethering of PDP connections on our own. * [ogsmd] [QUALCOMM MSM]: Handle setup the PDP connection. 2009-04-13 Daniel Willmann dtest: Make dbus test case actually work We need to import dbus... README now explains rough goals, usage, ... ogsmd, otimed: Catch up with API changes (operator code is string now) This commit follows the API change that happened in the specs repository in commit 7547d409977666eebb5117e4fc837ec6f19a4553. Warning, this might result in your apps breaking! Especially apps like cell monitors might be affected ogsmd: GSM default codec now raises UnicodeError on errors ogsmd: Fix typo in logger string ogsmd: Fix otimed GSMZoneSource Convert mcc and mnc in GSM.Data.GetNetworkInfo lookup to int. Fixes GSM time zone changes which got broken by commit edbba5e161133c28cc41c96afb8d9ea98c46d3e0 2009-04-13 Sebastian Krzyszkowiak schema: change *-vibration and *-loop items type from int to bool 2009-04-13 Michael 'Mickey' Lauer ogsmd: [TI CALYPSO]: set default speaker volume to maximum. Closes FSO #398 2009-04-13 Daniel Willmann dtest: Add more infrastructure for high level DBus testing ogsmd: Send out o.f.GSM.Network.Status after resume When we resume we now immediately query the network status and send a Signal. This fixes FSO #248. 2009-04-12 Daniel Willmann Add preliminary dtest infrastructurAdd preliminary dtest infrastructure ogsmd: Fix logic to show the correct operator name This fixes a logic error in finding out the best operator name. Before the result from the SIM query would always be overwritten with the result from the network.tab. Fixes a problem that was also exposed in FSO #375. 2009-04-09 Michael 'Mickey' Lauer ogsmd: abstract: fix copy'n'paste bug in DeviceGetSpeakerVolume() 2009-04-09 Jan Luebbe update network data ogsmd/networkdb: each network must have an MNC persist: having no persist data for a subsystem is not an error 2009-04-09 Daniel Willmann odeviced: Fix time and format problems in the RTC interface This fixes FSO bug #374 and brings the RTC interface in line with the spec. Also fixes the time offset some people (probably everyone not in UTC) experienced between calls of SetWakeupTime and GetWakeupTime. See http://docs.python.org/library/time.html for the different conversions "seconds since the epoch" <-> "{local,UTC} time". 2009-04-08 Daniel Willmann ogsmd: Convert MCC and MNC to int when comparing This fixes http://trac.freesmartphone.org/ticket/375 2009-04-04 Daniel Willmann ogsmd: Acknowledge status report messages right away Status reports need to be acknowledged with AT+CNMA=1, but we want to do that every time. There's nothing to be gained from deferring this to the DBus client. 2009-04-01 Jan Luebbe add make_cell_db.py and rebuild cell db add cell database ogsmd: add support for cell location 2009-03-30 Jan Luebbe tests/sms: test encoding performace ogsmd/convert: rewrite 7-bit handling for performance Inspired by libgsmd_sms.c 2009-03-28 Michael 'Mickey' Lauer docs: document TI Calypso SAT commands docs: document some more qualcomm commands, remove some standard ones ogsmd: [QUALCOMM MSM] parser.py: ERROR and OK are always formatted o.k. ogsmd: fix DeviceGetInfo prefixes to allow free format. It now works on modems that skip the prefixes for these commands. ogsmd: [QUALCOMM MSM] Add 'QualcommGsmViolationParser' that covers up Qualcomm's v250.ter AT format violations. ogsmd: parser.py: remove ThrowStuffAwayParser which is no longer necessary with the new parser ogsmd: parser.py: remove AlwaysUnsolicitedParser which is no longer necessary with the new parser 2009-03-26 Michael 'Mickey' Lauer docs: add 'commands' and 'urcs' describing proprietary commands/URCs ogsmd: [QUALCOMM MSM] parse HTC proprietary signal strength quality URC oeventsd: Refactor RingToneAction and MessageToneAction into a common UserAlertAction. Add enabling/disabling the vibrator for both as per 'ring-vibration' respectively 'message-vibration' attribute in the profile. odeviced: kernel26: implement org.freesmartphone.Device.LED.BlinkSeconds( i, i, i ) -> () opreferencesd: add schema entries for ring-vibration and message-vibration 2009-03-25 Michael 'Mickey' Lauer ogsmd: [FREESCALE NEPTUNE] fix call handling to report outgoing calls properly misc other fixes 2009-03-25 Jan Luebbe opreferencesd: enforce types in SetValue ophoned: use prefrences to configure the headset 2009-03-21 Paul Fertser ophoned: Retain compatibility with Python 2.5 2009-03-20 Michael 'Mickey' Lauer ogsmd: Do not assume a range of 0-255 for +CLVL, rather query the modem first. If the respective query is not supported, _then_ assume 0-255. [QUALCOMM MSM] uses a range of 0-5. ogsmd: fix prefixmap for several free-format commands (spotted on HTC Touch Pro) 2009-03-19 Michael 'Mickey' Lauer ogsmd: [QUALCOMM MSM] document more quirks ogsmd: start documenting Qualcomm MSM modem quirks 2009-03-19 Jan Luebbe oeventsd: change BTHeadsetConnectedIs(state) to BTHeadsetIsConnected() oeventsd: we need enabled/disable from Rule (not from Filter) oeventsd: fix logging oeventsd/fso_triggers: use a more specific logger oeventsd: forward enabled/disable to subfilters oeventsd: add support for the bluetooth headset odeviced: work around ASoC DAPM problem by flipping "Capture Left Mixer" ophoned: more work, improve logging ophoned: more fixes ophoned/gsm: fix status/state confusion ophoned: more work ophoned/headset: more work ophoned/headset: continue implementing ophoned: add headset manager helpers: add some simple helper functions cli-framework: add phone interface 2009-03-19 Michael 'Mickey' Lauer docs: document more neptune quirks ChangeLog: unify my email address update ogsmd/lessons file ogsmd: [FREESCALE NEPTUNE] add 'fixed' dialling phonebook. Thanks Alain for spotting. ogsmd: add fixed dialling (FD) phonebook to possible phonebooks ogsmd: Implement org.freesmartphone.GSM.Device.GetPowerStatus() for modems that support this call (e.g. FREESCALE NEPTUNE) to get the power status. Note that sysfs power supply classes are preferred though... 2009-03-18 Michael 'Mickey' Lauer ogsmd: [FREESCALE NEPTUNE] response to +COPS? can be corrupt, take this into account ogsmd: [FREESCALE NEPTUNE] use more multiplexing channels to catch all unsolicited responses 2009-03-15 Michael 'Mickey' Lauer ogsmd: Fix debug commands with new parser 2009-03-13 Michael 'Mickey' Lauer ogsmd: [FREESCALE NEPTUNE] fix bunch of problems * catch up with unsolicited response parsing * catch up with timeout changes * change channel mapping to accomodate for +CMT on /dev/mux2 * fix incoming SMS parsing (needs a slightly different PDU format) * fix roaming indication * remove ThrowAway parser ogsmd: strip " from response for +CPIN? (which some modems include in their reponse) ogsmd: fix timeout reporting 2009-03-09 Daniel Willmann ogsmd: Make sure we keep valid prefixes over continuation lines This is important when submitting commands with PDU. After receiving the "\r\n> " continuation the parser would reset the valid prefixes so in case of AT+CMGW=23 > +CMGW=1 OK the +CMGW=1 would be considered unsolicited. This fixes FSO #372. 2009-03-06 Michael 'Mickey' Lauer ogsmd: split DeviceGetFeatures() into individual commands, since not all are always supported ogsmd: DeviceGetInfo: According to GSM 07.07, it's legal to not answer quoting the prefixes for these four informational requests, hence we allow all prefixes. NOTE: Yes, this opens a slight possibility of unsolicited creeping unnoticed into. To fix this properly, we would need to enhance the prefixmap to also specify something like: [ "+CGMR", "+CGMM", "+CGMI", "+CGSN", "plaintext" ], with "plaintext" being everything else that does _not_ look like a response. ogsmd: [CINTERION MC75] add power control and request paths from fso-abyss NOTE: On mc75, we always use fso-abyss. There's no configuration option. ogsmd: [TI CALYPSO] fix power control (only significant when using fso-abyss, otherwise gsm0710muxd will handle that) config: dump milliseconds also to find out where our time gets spent 2009-03-06 Daniel Willmann ogsmd: Decode userdata even if we encounter errors This will retry to decode UCS-2 userdata with the replace policy if decoding with strict didn't work. The errors property will still have "Userdata corrupt" set, but the userdata will actually contain whatever we were able to decode. Problematic characters are replaced by the unicode replace character (\ufffd). Fixes FSO #291. 2009-03-06 Michael 'Mickey' Lauer ogsmd: query our network database if the provider's name is not known. This can happen for new providers and old modems (guess what... :). Fixes FSO #355 and OM #2241. Fixed also some bugs in querying the network database introduced by the last format revamp. 2009-03-06 Alain2210 ogsmd: [FREESCALE NEPTUNE] add customized versions of DeviceSetAntennaPower and SimGetAuthStatus to cope with the modem's GSM 07.07 violations. 2009-03-06 Michael 'Mickey' Lauer ogsmd: port +CNMA to the new timeout handling (basically the per-command timeout no longer exists) ogsmd: fix SMS sending with new parser. Thanks Daniel for spotting! ogsmd: override autoprefix for +CUSD, which is kinda special 2009-03-05 Michael 'Mickey' Lauer ogsmd: parser.py: flag +CGMR as solicited message that has a PDU. Closes FSO #369, which was actually a valid bug. 2009-03-04 Michael 'Mickey' Lauer ogsmd: channel.py: cache autogenerated prefixes since re.findall is an expensive call otimed: fix wrong interface path in alarm.py. Patch by lupan, thanks! ogsmd: teach parse about 'PDU' special prefix that does accept everything that starts with {0123456789ABCDEF} ogsmd: use more strict RE for AUTOPREFIX matching ogsmd: [TI CALYPSO] the AlwaysUnsolicitedParser is no longer necessary now that the StateBasedLowLevelParser is good enough. ogsmd: support allowing all prefixes for commands -- this is necessary for the few commands that do not need to answer with the prefix, e.g. +CIMI is allowed to answer directly with the SIM's IMSI ogsmd: fix new parser for solicited messages with PDUs (+CMGL, what else?) ogsmd: improve AUTOPREFIX handling. We can compute them for combined AT commands now as well (AT+FOO;+BAR;...) ogsmd: [TI CALYPSO] send %CUNS=0 on startup. It now sends unsolicited responses directly once they're generated, which leads to potentially lots of mixed solicited/unsolicited responses. This should be a nice test-case for our new handling. ogsmd: Rewritten timeout handling and unsolicited message handling. We now maintain a list of valid prefixes for every command in the Queue. Once the parser processes a complete line, it checks whether this line contains expected results. With this, it's safe to operate modems that mix unsolicited and solicited responses. It's also a safeguard against race conditions when talking to the modem over buffered lines. NOTE: Experimental and uncomplete, needs testing. Right now, we compute the list of valid prefixes automatically. For some commands, this is not sufficient (standard violations or mixed combined commands), so we need to give an explicit prefixlist. ogsmd: remove per-command timeouts ogsmd: don't deactivate a pdp object that is not yet existing ogsmd: start revamping timeout handling; also use gio priorities to enforce reading before writing 2009-03-03 Daniel Willmann ogsmd: Introduce types guess-submit and guess-deliver These will first try to decode the message as sms-submit/-deliver message and if that fails they will try to decode as sms-deliver/-status-report. This helps when you encounter SIMs on which sms-deliver-reports have been stored. ogsmd: raise an error on incorrect MTI fields We can't really recover from this error since the MTI defines the rest of the message format. ogsmd: Add "message-class" property to SMS classes 2009-03-02 Daniel Willmann ogsmd: Add org.freesmartphone.SMS.{Ack,Nack}Message() These are used to give positive/negative acknoledgements about an IncomingMessage. If an IncomingMessage is not Acked in a given time it will be sent to SIM storage. ogsmd: Change signature of org.fs.Sms.IncomingMessageReceipt Since an sms-status-report can have userdata this signal now has the same format as IncomingMessage. No API breakage since this feature wasn't really part of the API until now. tests: Add test cases for sms-submit-report and sms-status-report Additionally, rename the PDUs to clarify their content. ogsmd: Remove cruft from sms-submit-report, add sms-status-report ogsmd: Add support for RP-ERROR and RP-ACK in sms-submit-reports This is controlled by a boolean variable at creation. SMSSubmitReport(True) generates a submit-report for RP-ACK and SMSSubmitReport(False) generates a submit-report for RP-ERROR ogsmd: Add support for sms-submit-report Switch key and value of TP_FCS so we ease the common lookup 2009-03-01 Tim Niemeyer ogpsd: Fix 2D/3D fix reporting in NMEA 2009-03-01 Jan Luebbe ogpsd: reenable ephemeris and reorder aiding upload according to docs ogpsd: fix typo which mangled the ephemeris data somewhat 2009-03-01 Daniel Willmann ogsmd: Add SMS properties getting and setting This more or less completes the different property keys 2009-03-01 Jan Luebbe ogpsd: handle unknown position and valid almanac correctly (closes: #265) We were sending (X=0, Y=0, Z=0) as the current ECEF position. 2009-02-28 Jan Luebbe otimed: the hardware clock is in utc by default 2009-02-28 Daniel Willmann ogsmd: Add stubs for remaining sms classes ogsmd: Change naming of sms address Follow name changes in modems/abstract/mediator.py and modems/abstract/unsolicited.py so sending and receiving messages will work again with the new stack. Use the new class names directly. tests: Add a test to measure the time decoding an sms-deliver message Also fix the SMS generation test tests: Add more testcases for sms property defaults ogsmd: Factor out the different message types in individual classes With individual parse and pdu methods for each message type it's now a lot easier to follow the construction of a message. tests: Make the SMS tests fine granular ogsmd: Do not request message delivery reports by default This will be a per-message option 2009-02-28 Paul Fertser Preliminary support for SMS message delivery reports I couldn't find enough reliable information for that type of PDU, therefore support is incomplete for now. 2009-02-28 Michael 'Mickey' Lauer odeviced: idlenotifier: take timeouts from defaults, if not existing in config This fixes restarting the dimming on releasing the 'Display' resource, if no custom timeout for idle_dim is set in the configuration. 2009-02-26 Michael 'Mickey' Lauer introduce new way for a subsystem to scan for plugins the default is to scan the filesystem, looking at each and every file. we now provide a way to use the configuration file, so every plugin needs to be mentioned as a (if necessary, empty) section. the example configuration has been adjusted. ousaged: bring back ResourceInUse exception 2009-02-24 Daniel Willmann ogsmd: Follow renaming of properties attribute in SMS Fixes bugs 359 and 358 which are duplicates. 2009-02-21 Michael 'Mickey' Lauer tools: add cell broadcast to cli-framework ogsmd: [CINTERON MC75] catch up with changes in ogsmd oeventsd: use QueuedDBusAction only for triggers that really need it; RingTone for now 2009-02-19 Michael 'Mickey' Lauer ogsmd: [TI CALYPSO] use fso-abyss ogsmd: [CINTERION MC75] remove low level initialization; not necessary with good muxers ;) otapi: CbGetCellBroadcastSubscriptions: remove shadowing the error module ogsmd: fix ListProviders for systems that do not return the access technology. Closes FSO #329 2009-02-18 Michael 'Mickey' Lauer ogsmd: [TI CALYPSO] fso-abyss features autoconfiguration now, take this into account. odeviced: fix bug in Display resource preventing the display to be lit when you request the resource while it's already past the state it's trying to prevent falling into. This should fix FSO bugs #336, #343, and #352. 2009-02-16 Michael 'Mickey' Lauer start documenting cinterion specifics oeventsd: add org.freesmartphone.Events.ReloadRules() 2009-02-16 Timo Juhani Lindfors apply workaround for http://bugzilla.gnome.org/show_bug.cgi?id=571890 that causes frameworkd to print ppp error: "TypeError: 'NoneType' object is not callable\n" every time a GPRS connection is started. 2009-02-13 Daniel Willmann ogsmd: Fix binary SMS with null bytes Since binary SMS are very likely to contain null bytes we use a bytearray in the properties to convey the data. This still doesn't fix the problem if we receive UCS2 messages which include null bytes (which is possible, but less likely). Fixes FSO #345. ogsmd: Fix FSO bug #218 (@ instead of padding) 2009-02-13 Michael 'Mickey' Lauer tools: add dump-netlink ogsmd: helpers: secure killall against races 2009-02-12 Michael 'Mickey' Lauer odeviced: fix UsbHost mode sysfs path regression ogsmd: add timeout for COPN (NOTE: timeouts will soon be deprecated in favour of a global timeout...) ogsmd: Implement org.freesmartphone.GSM.SIM.GetProviderList() -> a{is} ogsmd: [TI CALYPSO] use AlwaysUnsolicitedParser on all channels. Closes FSO #334 ogsmd: parser: add AlwaysUnsolicitedParser, a low level parser that treats certain responses always as unsolicited (based on prefix matching); useful for modems which do not support deferring unsolicited responses between sending a query and returning the (solicited) response -- such as the TI Calypso with regards to +CRING, +CLIP, and %CPI. odeviced: Fix oscillating power LED. On some devices, peripherals are connected directly to the battery, which means they can _not_ drag power from USB. Hence, the battery needs to be recharged frequently, even while it is full. To cover up for this, we do not send the 'charging' signal if the last status was full. start document for 'lessons learned' to guide us for version 2.x ogsmd: [TI CALYPSO] remove bogus argument in data patch factory, thanks max_posedon and Paul Fertser! Closes FSO #354 ogsmd: wrap os.kill in try/except as the process might have vanished on its own in the meantime. 2009-02-08 Michael 'Mickey' Lauer ogsmd: send plain AT as first command on channel init docs: document TI Calypso muxer quirks 2009-02-06 Michael 'Mickey' Lauer ogsmd: [TI CALYPSO] Add support for the next-generation 07.10 multiplexer from FSO. Set 'ti_calypso_muxer' to 'fso-abyss'. Default value is gsm0710muxd for the time being. 2009-02-06 Guillaume Chereau tests: add tests from storing messages into the SIM 2009-02-04 Michael 'Mickey' Lauer fix typo in processguard. thanks lindi mention ms5 tools: cli-framework: add omuxerd oeventsd: fso-actions: don't break if preferences daemon doesn't run odeviced: kernel26: do not send capacity changes for AC adapters 2009-02-03 Jan Luebbe otimed: don't fail when requesting a timestamp 2009-02-02 Joachim Breitner typo in shebang line: /user/bin/env 2009-01-30 Michael 'Mickey' Lauer odeviced: move checking for pyrtc below creating the logger. thanks beniwtv! otimed: fix traceback on bad GPS time; thanks beniwtv for spotting! 2009-01-30 Jan Luebbe otimed: Add config options for time and zone sources. 2009-01-29 Jan Luebbe cli-framework: complete the integer types odeviced: remove org.freesmartphone.Device.RealTimeClock.Suspend 2009-01-29 Michael 'Mickey' Lauer odeviced: fix bug in realtimeclock. closes FSO #330 tools: add devrtc to cli-framework add null object oeventsd: RingToneAction: listen for audio profile changes. Closes FSO #325 2009-01-29 Jan Luebbe cli-framework: handle more dbus types ogpsd: repair ogpsd for GTA01 2009-01-28 Michael 'Mickey' Lauer opimd: raise proper dbus errors, remove some redundancy (more to come), styling fixes add fso team as test data 2009-01-28 Timo Juhani Lindfors config: do not send timestamps to syslog since syslog already adds time This patch makes frameworkd not log timestamps when it is logging to syslog. Syslog already adds timestamps so extra timestamp is just making the log lines longer for no good reason. 2009-01-28 Michael 'Mickey' Lauer tools: add some opimd objects to cli-framework opimd: move csv-contacts to where it belongs. adjust setup.py accordingly to install it. opimd: fix object paths appearing. This has been broken since http://git.freesmartphone.org/?p=framework.git;a=commit;h=8551825260f31baea9b8b3900d92e4e98d4c6a17 track busmap in config for simple access to the proper bus connection from each and every module 2009-01-27 Jan Luebbe ogsmd: [TI CALYPSO] don't convert RXLEV to percentages (closes: #323) 2009-01-27 Michael 'Mickey' Lauer otimed: alarm trigger is now calling org.freesmartphone.Notification.Alarm() on the root object opimd: do not mess with system bus registration, the controller is doing that for you 2009-01-27 Jan Luebbe ogpsd: reenable workaround for the kernel that will be release with MS5 2009-01-27 Michael 'Mickey' Lauer ogpsd: Check in all resource functions if a channel is None and warn Under certain circumstances, i.e. with testdevices we don't have a channel. Also remove stray shutdownChannel from commit 0be860bbc94f497a877555fbb4c9c95107634ef3 2009-01-27 Jan Luebbe ousaged: add some more debug messages oeventsd: support setting environment variables for the Command action (closes: #242) Thanks to Peter Strapp for the idea and a patch. 2009-01-24 Michael 'Mickey' Lauer kobject.py: kobject notifications might as well be longer than 512 bytes. Read larger buffer. Spotted by beniwtv, thanks a bunch! resource: add resource.checkedsignal decorator. In contrast to resource.queuedsignal, this will throw a signal away, if the corresponding resource is not enabled. Feel free to change signals with short validity periods from queuedsignal to checkedsignal. rename checkedsignal to queuedsignal 2009-01-23 Michael 'Mickey' Lauer odeviced: powercontrol_neo: use bind/unbind way of controlling ar6k power to get the maximum power saving. This obsoletes wireless.pyx and -- at this point of time -- we no longer require Cython. 2009-01-23 Jan Luebbe ogpsd: handle old and new power control paths 2009-01-21 Michael 'Mickey' Lauer resource: fix error in delayed sending signals cli-framework: learn how to convert doubles cli-framework: set busname also for interfaces. Thanks beniwtv for spotting tools: submit correct busname in ListObjectsByInterface to dbus-hlid ogsmd: [TI CALYPSO] txlev is not a signal strength, but a power level indicator ogsmd: [TI CALYPSO] convert signal strenghts to percentage in org.freesmartphone.GSM.Monitor.Get{Serving|Neighbour}CellInformation rename cell_type_ind to ctype ogsmd: channel: remove unicode warning; it's quite normal we get unicode via dbus otimed: fix NameError; Jan, please review! resource: fix typo ogspd: Wrap dbus signals with resource.checkedsignal ogsmd: Wrap dbus signals with resource.checkedsignal decorator resource: Added a 'checkedsignal' decorator that will enqueue a signal when the resource is not 'enabled'. Signals will get sent after the resource has reached the 'enabled' status. Resource: Fixed bug in checkedmethod and checkedsyncmethod decorators. Previously we only got org.freesmartphone.Resource.NotEnabled when the status was unequal 'disabled'. This is wrong, since we also can't use a resource during the state transitions, i.e. 'enabling', 'disabling', 'suspending', and 'resuming'. This should fix some race conditions, or at least, make it more clear why you can't access the device. 2009-01-20 Jan Luebbe cli-framework: add support for bluez 4.x otimed: set the system time directly (closes: #318) otimed: add otimed to dbus config 2009-01-19 Michael 'Mickey' Lauer ogsmd: [TI CALYPSO] bypass serial abstraction while reading/writing; use faster posix read/write. ogsmd: channel: refactor actual reading and writing from/to the source into seperate method (to be able to override in descendents) 2009-01-17 Michael 'Mickey' Lauer ogspd: according to Werner Almesberger, you need to turn on gpsd in order to turn it off *sigh* ogpsd: fix disabling the device when it was never enabled before. Daniel, please review this! 2009-01-16 Guillaume Chereau tests/sim: wait for ReadyStatus signal when a SIMBusy error is raised Otherwise the test was failing every time the SIM was not ready at startup. The solution here assumes that after we receive a ReadyStatus, then we can't get an other SIMBusy error for the next call. 2009-01-16 Michael 'Mickey' Lauer patterns/tasklet: use timeout_add_seconds to save energy 2009-01-16 Guillaume Chereau patterns/tasklet: fix bug in WaitDBus w/ timeouts 2009-01-16 Michael 'Mickey' Lauer ogspd: another bogus indent opgsd: fix bogus indention ogpsd: don't try to shutdown a channel that doesn't exist odeviced: add missing variable in constructor 2009-01-15 Michael 'Mickey' Lauer ogsmd: const: add GSM number types seen "in the field" resource: on shutdown leave resources alone, if requested ousaged: Add config option 'sync_resources_with_lifecycle' for specifying whether all Resources should be disabled on "startup", "shutdown", "always" (default), or "never". resource: status at default is "unknown" oeventsd: Serialize dbus requests with a Queue. This (and removing a race in the RingToneAction) should fix the problems with neverending vibration and audio ringtone on short calls (FSO ticket #205) tasklet: what do we do if the generator vanishes? oeventsd: add QueuedDBusAction and use it for all dbus methods triggered by actions oeventsd: substitute super calls with the classic method of calling parent constructors super is considered harmful, at least in the present implementation. DO NOT USE IT, especially not not with multiple inheritance. Trust me, it's broken! ogsmd: add auto-online mode for pdp connections 2009-01-14 Jan Luebbe ogpsd: add support for debugging via UDP 2009-01-14 Michael 'Mickey' Lauer ousaged: fix resource policy 'enabled'. Had been broken since months, but apparantly no one notice :) ousaged: move errors to appropriate modules. 2009-01-12 Daniel Willmann ogsmd: Make RegisterWithProvider charset aware ogsmd: Safeguard if modem replies to +CGM{L,R} with unsolicited messages This will prevent Tracebacks like in http://trac.freesmartphone.org/ticket/314, but the underlying actual problem should still be fixed. tests: Add a test case for invalid Service Center timestamp parsing ogsmd: [SMS] Don't fail when service center timestamps are invalid If parsing the scts field fails the date is substituted for a dummy value and the error property is marked accordingly. ogsmd: [SMS] Add an error property. The goal is that SMS parsing will not result in any Exceptions, but instead the error property will indicate if parsing a short message failed for some reason. ogsmd: Fixed getting of alphabet property. The previous commit broke all SMS functionality, this fixes the issue. A test case is now included that will recognize this failure. ogsmd: Add new properties to SMS messages This adds support for setting and getting alphabet (encoding) and PID over sms.properties 2009-01-10 Michael 'Mickey' Lauer ogsmd: [TI CALYPSO] fix bogus GSM wakeups introduced by f734551d42315bac6bc63aa5c5e2d793f16245ea ogsmd: channel.py: remove potential races * create answer timeout always (even if no one is listening) * feed value of response watch to parser (which should be more accurate than the whether there is something in the queue or not) ogsmd: [TI CALYPSO] honor +CRING, if -- for some reason -- we didn't get %CPI before. 2009-01-10 Jan Luebbe odeviced: readd fcntl import which got lost during the conversion to KObjectDispatcher ogpsd: use framework.config like ogsmd.device 2009-01-09 Michael 'Mickey' Lauer ogsmd: [TI CALYPSO]: only reinit UnsolicitedCommand channel. Kill reenabling timer if we're suspending before the timer hits. ogsmd: [TI CALYPSO] resend the DSP command on every call setup. NOTE: I'm not convinced and there is no evidence that this is necessary, but I'm sick of discussing this over and over again, hence it goes in. This closes #253 for good. odeviced: powercontrol_neo: use wmiconfig to enable/disable wlan0 add org.freesmartphone.Framework.GetVersion() 2009-01-08 Julien 'Ainulindale' Cassignol Corrected a trailing \n causing problems with the writing of the contacts backend. 2009-01-08 Michael 'Mickey' Lauer ogsmd: gather context status from pdp object, not from network this makes it inline with the signal and the actual logical state closing #289, as all parts should be in place now. 2009-01-07 Paul Fertser Add information about Calypso's flow control handling It was discovered that Calypso doesn't respond to the flow control as it should, i.e. it doesn't stop output immediately and instead transfers the rest of the message, which may lead to UART buffer overflows. See mwester's description at http://docs.openmoko.org/trac/ticket/1376 . 2009-01-07 Michael 'Mickey' Lauer ogsmd: move dbus error module one level up ogsmd: Implemented org.freesmartphone.GSM.Monitoring.* as stub erroring out with org.freesmartphone.GSM.UnsupportedCommand. ogsmd: [TI CALPYSO] Implemented the new monitoring API: org.freesmartphone.GSM.Monitoring.Get{Serving|Neighbour}CellInformation ogsmd: add stubs for monitoring API org.freesmartphone.GSM.Monitor.Get{Serving|Neighbour}CellInformation 2009-01-06 Michael 'Mickey' Lauer We now register one dbus connection per subsystem to prevent objects appearing on all bus names. If you previously only used the bus name 'org.freesmartphone.frameworkd', you have to adjust your code. 2009-01-06 Daniel Willmann ogsmd: Move SMS tests into its own unittest under tests/ 2009-01-06 Michael 'Mickey' Lauer odeviced|ousaged: use repr() in helper output odeviced: input/idlenotifier: don't use config from controller, use singleton 2009-01-03 Michael 'Mickey' Lauer ogsmd: pdp: subprocess handling is now using the common ProcessGuard odeviced: kernel26: fix parameters in KObjectDispatcher callback odeviced: kernel26: Use common KObjectDispatcher Add the new module kobject patterns that wraps uevent and rtnetlink notifications. This will be used to substitute route some polling in pdp and other modules. NOTE: To assist netlink parsing, we include the cxutil and cxnet packages from python-connexion in the framework. For more details, see http://www.radlinux.org/connexion/wiki Add cxnet and cxutil as per 0.4.6 SVN rev 1681 2009-01-02 Michael 'Mickey' Lauer odeviced: kernel26: remove deprecated API in PowerSupply, add some new calls cli-framework: use dbus-hlid oeventsd: Add an OccupyResource action plus some corresponding rules. NOTE: See rules file for one testing rule which is still outcommented. framework: objectquery: remove ListObjects from interface, this will now be handled by the new dbus-hlid project. otimed: use lazy dbus object binding, so that ogsmd has not to be present at subsystem launch time otimed: alarm: use "hardcoded" path to default real time clock also: do not introspect and follow name owner changes, so that odeviced has not to be present at subsystem launch time odeviced: register real time clock also under incremental object path 2008-12-31 Michael 'Mickey' Lauer cli-framework: convert dbus.Int64 2008-12-30 Michael 'Mickey' Lauer kobject: parse a bit more of netlink move services.py and netlink.py to patterns directory persist: Use cPickle instead of Python-based implementation odeviced: Register objects under /org/freesmartphone/Device/Display/ also with an numerical incremental alias -- e.g. the first display can now also be accessed via /org/freesmartphone/Device/Display/0. rules: Adjust the default backlight brightness rule to use display "0". NOTE: This should be the last missing step to support Openmoko kernel versions 2.6.24 and also >= 2.6.27 for GTA01 and GTA02. 2008-12-29 Michael 'Mickey' Lauer First step towards integrating Vala/C subsystems with frameworkd: Support external subsystems. If you add a section with an 'external' attribute, then the section will be treated as an external subsystem to launch with the path given as attribute: [mysubsystem] external=/foo/bar TODO: Add 'relaunch' attribute. NOTE: You can also "override" internal subsystems by giving the 'external' attribute. 2008-12-28 Michael 'Mickey' Lauer add generic process guard class 2008-12-27 Michael 'Mickey' Lauer ogsmd: [TI CALYPSO] Add TI-Calypso specific configuration option "ti_calypso_dsp_mode" to configure DSP audio enhancements. Documentation for this option has been added to the sample configuration file. (Some lowlevel details can be found in framework/subsystems/ogsmd/modems/ti_calypso/channel.py) This closes FSO ticket #274 and #253. ogmsd: Refactor Modem.dataOptions() into "pppd-configuration" attribute in modem data storage. 2008-12-25 Michael 'Mickey' Lauer add netlink constants 2008-12-25 Daniel Willmann ogsmd: Fix "NameError: global name 'currentModem' is not defined" NetworkStatus signals should work again 2008-12-24 Daniel Willmann ogsmd: Add charset support to more methods and signals PdpGetNetworkStatus and NetworksStatus signals ogsmd: Catch up with latest act spec changes (make AcT mandatory) Update ChangeLog ogsmd: Charsets are now per modem Some modems encode *all* strings in ucs2 if you change the charset. This allows us to specify the different charsets used for different commands. All commands sending or receiving strings as parameters now need to encode and decode these strings with the corresponding charset. ogsmd: Make ucs2hexToUnicode into a codec (gsm_ucs2) ogsmd: Add support for AcT and remove duplicate plusCREG Modems that support more than just GPRS will have an additional number identifying the network access type in +COPS, +CREG and +CGREG. 2008-12-22 Daniel Willmann ogsmd: [ERICSSON] Use enable_wwan node found in thinkpad devices ogsmd: Fail gracefully if the serial device doesn't exist yet Thinkpad WWAN cards can be enabled through a /sys file. It takes some time from enabling the device to the device nodes actually appearing. This makes the VirtualChannel fail gracefully when the device does not exist so it will retry. Also increased the channel open retries from 5 to 7. 2008-12-21 Michael 'Mickey' Lauer whitespace 2008-12-21 Daniel Willmann frameworkd.conf: Document ogpsd options more 2008-12-20 Michael 'Mickey' Lauer ogsmd: [QUALCOMM MSM] new modem abstraction based on singleline freescale neptune: more quirks document some more freescale neptune quirks ogsmd: [FREESCALE NEPTUNE] send the magic SIM initialization commands, so that the GSM 07.07 commands for reading the SMS message book work ootb. Override one channel's modemStateSimUnlocked() as well to send the mandatory org.freesmartphone.GSM.SIM.ReadyStatus( True ) on time. ogsmd: [ABSTRACT] strip " in response to +CPIN since some modems return this as a string ogsmd: [FREESCALE NEPTUNE] override numberToPhonebookTuple Storing international numbers works on this modem now. ogsmd: move numberToPhonebookTuple from gsm.const into modems.abstract.modem We need to be able to override it per-modem (Freescale Neptune *cough*) 2008-12-19 Michael 'Mickey' Lauer add preliminary netlink message dispatcher ogsmd: [TI CALYPSO] disable %CGEV unsolicited response codes 2008-12-19 Jan Luebbe otimed: allow time travel to the past 2008-12-19 Michael 'Mickey' Lauer ousaged: Check resume reason and launch emergency shutdown, if we woke up due to low battery. NOTE: This is only supported on Openmoko devices now. As there is no standard infrastructure for that in the kernel, we have to deal with device-specific solutions in lowlevel.py ousaged: check if we woke up due to battery low; trigger emergency shutdown if so freescale neptune: add new quirk in handling number types 2008-12-19 Daniel Willmann ogsmd: Add support for sms-submit-reports and return timestamps When sending an SMS an sms-submit-report is returned which includes a timestamp. This is the time when the Short Message Service Center received the short message. The DBus calls for sending SMS now also return a timestamp in string format (WARNING, API breakage!) 2008-12-19 Jan Luebbe otimed: use network database to install a time zone file 2008-12-18 Jan Luebbe ogsmd: reformat mobile_network_code into networks.tab This adds a new DBus interface "org.freesmartphone.GSM.Data" on the server object, which can be used to query the network database. otimed: accept only valid time from ogpsd 2008-12-18 Daniel Willmann ogsmd: Add support for different SMS message types This patch also (hopefully!) fixes the SMS PDU breakage that has been around in master for some time. SMS types are now sms-{deliver,submit} and sms-{deliver,submit}-report 2008-12-18 Stefan Schmidt ogpsd: Add EtenDevice for the glofiish devices. This patch also introduces a baudrate config option in ogpsd.serialchannel. 2008-12-18 Michael 'Mickey' Lauer ogsmd: pdp: make singleton handling more consistent with CallHandler ogsmd: pdp: do not use ATZ in connect script, do not query for CPIN ogsmd: [ABSTRACT] start handling +CGEV (neccessary to detect gprs connection teardown) ogsmd: [TI CALYPSO] remove %CGREG unsolicited code, we're not using it ousaged: gathering resume reason now works for Openmoko devices do not error out, if /etc directory can't be found 2008-12-17 Michael 'Mickey' Lauer ousaged: add lowlevel resume reason helpers 2008-12-17 Jan Luebbe otimed: fix blocking otimed without connectivity 2008-12-17 Michael 'Mickey' Lauer ogsmd: [TI CALYPSO] use kernel-version agnostic sysfs path odeviced: kernel26: Don't dump exception if we can't register to the netlink object (multiple times) It might have been successful the first time. This code will be changed soon anyways, we're going to introduce a kobject multiplexer in the framework services. odeviced: accelerometer: use sysfs path agnostic to kernel version ogpsd: om: use kernel-agnostic sysfs node path odeviced: powercontrol_neo: use kernel-agnostic sysfs path remove super() calls and limit scanning to the one directory where we expect the device nodes 2008-12-17 Jan Luebbe otimed: add simple timesync to NTP and GPS 2008-12-16 Daniel Willmann ogsmd: First working ericsson F3507g modem driver SIM access is working, PDP fails at the moment because the connect script sends an ATZ which the F3507g responds to with ERROR. It seems that changing CSCS has an effect on *all* strings, even CSCS itself and COPS. This will need to be worked around later. Merge commit 'origin/stefan-F3507g' 2008-12-16 Michael 'Mickey' Lauer oeventsd: spawn processes asynchronous in CommandAction. Closes FSO #235 odeviced: audio: try harder to escape spaces in filenames 2008-12-15 Sascha Wessel ogpsd: UBXDevice::parse: improve handling of corrupted ubx packets 2008-12-15 Daniel Willmann ogsmd: Remove stray "if s:" in state_inline_multipleR Commit 93d20c6f52f77644cc47da698e5802b63927693c didn't remove all occurences of s (which is now handled in self.lineCompleted by checking self.haveCommand. 2008-12-15 Michael 'Mickey' Lauer odeviced: audio: encode filename in " to fix FSO bug #297. 2008-12-12 Michael 'Mickey' Lauer tools: add gsmphone to cli-framework ogsmd: Implemented org.freesmartphone.GSM.Phone.[Start|Stop]AutoRegister() and its signal org.freesmartphone.GSM.Phone.ServiceStatus() This is a convenient way to ensure that ogsmd always tries to unlock the SIM and register with a provider, if necessary. (Corresponding functionality for Gprs is under discussion). 2008-12-11 Michael 'Mickey' Lauer ogsmd: [TI CALYPSO] Apply reinit commands with a delay after resuming. This should give us a chance for draining the incoming buffers and lead to a faster and more reliable resume due to an incoming call. See the code comments for more details. ogsmd: channel/parser: fix an embarrasing bug in the low-level AT parser that has been here from day one. Basically we were always assuming that 'haveCommand' [which is used to decide whether a response is solicited or unsolicited] was vaild for the whole bunch of bytes that were fed to the parser. This is wrong! (Of course, since we may retrieve lots of lines in one row that can refer to a number of commands). This may also have been the cause for the hard lock upon sending +CGACT=0 (which may actually have been a hang in the parser) that triggered an intermediate response concatenated to the OK response: \r\nOK\r\n\r\nNO CARRIER\r\n I'm actually very surprised that this could've been undetected for so long... ogsmd: pdp: release context at the end of shutting down ppp, so that the next ppp invocation will find the virtual channel in command mode ogsmd: implement org.freesmartphone.GSM.Network.TimeZoneReport() Thanks Dieter S., Jörg R. and all who contributed to the puzzle :) ogsmd: add parser for strange modems ogsmd: ppp has a serious shutdown problem :/ working around it ogsmd: start reworking ppp connection setup/shutdown WIP; it's broken atm. :D ogsmd: implement org.freesmartphone.GSM.PDP.GetNetworkStatus() ogsmd: add org.freesmartphone.GSM.PDP.NetworkStatus() onetworkd: s/logging/logger/ 2008-12-08 Michael 'Mickey' Lauer ogsmd: disable frame depth inspection per default ogsmd: remove old low level AT parser, state-based one is default now 2008-12-07 Michael 'Mickey' Lauer update TODO 2008-12-07 Stefan Schmidt ogsmd: Use right daemon name in header ogsmd: [ericsson_F3507g] Add skeleton driver * Modem used on a MiniPCIe card * Featuring HSPA and GPS 2008-12-06 Michael 'Mickey' Lauer ogsmd: [FREESCALE NEPTUNE] Query our MNO database to show the provider's alphanumerical name in addition to the numerical code (which is the only thing we get on this modem...) setup.py: ship the new file ogsmd: add database for mobile network codes. This becomes handy in case the modem does not properly support enquiring the alphanumeric operator names from the network (Freescale Neptune comes to mind...) ogsmd: Implement org.freesmartphone.GSM.SIM.GetIssuer() -> s NOTE: Clients can now show the mobile virtual network operator (MVNO) instead of the actual network operator, if they want to. This is a result for FSO ticket #271. ogsmd: Add 'cid' and 'lac' to result for org.freesmartphone.GSM.Network.GetStatus() ogsmd: fix traceback on releasing resource ogsmd: [TI CALYPSO] disable +COLP=1 to prevent falling into FSO #211. 2008-12-05 Michael 'Mickey' Lauer odeviced: implement org.freesmartphone.Device.LED.SetNetworking(s,s)->() * add parameter validation to .SetBlinking 2008-12-04 Michael 'Mickey' Lauer ogsmd: implement org.freesmartphone.GSM.Device.[Get|Set]MicrophoneMuted() odeviced: [kernel26] turn off LED on shut down ogsmd: Implement org.freesmartphone.GSM.Device.[Get|Set]SpeakerVolume odeviced: [kernel26] turn off LED on shut down resource: disable resource on object shutdown give subsystems and objects a chance to properly shut down 2008-12-03 Michael 'Mickey' Lauer fix SIGTERM handler in daemon support code 2008-12-02 Michael 'Mickey' Lauer ogsmd: unset gsm-standard hooks on modems that offer special call progress URCs ousaged: refactor resource classes into seperate module ousaged: Preliminary implementation of org.freesmartphone.Usage.[Shutdown|Reboot]() -> () Send signal org.freesmartphone.Usage.SystemAction() before suspending, after resuming, before shutting down, before rebooting. This fixes FSO ticket #287. ousaged: org.freesmartphone.Usage.Suspend() now returns a value instead of timing out (call did not return before the actual suspend was triggered). As an inherent result, the suspend now occurs asynchronous. This fixes FSO ticket #215. ogsmd: improve killall function, patch courtesy sdhillon. closes FSO ticket #260 odeviced: Add two new resources: 'CPU' and 'Display': Requesting the CPU resource will prevent the idle notifier from falling into the 'suspend' state, requesting the Display resource will prevent the idle notifier from falling into the 'idle_dim' (and following) state(s). With these two resource, it now takes only one additional rule to prevent the system from suspending while on a call or a musicplayer is playing. ogsmd: connection sharing now works 2008-11-30 Michael 'Mickey' Lauer ogsmd: start with support for the CINTERION MC75(i) ogsmd: timeouts are now per-modem ogsmd: refactor modem creation logic into modem factory method 2008-11-29 Michael 'Mickey' Lauer ogsmd: cosmetics 2008-11-29 Daniel Willmann ogsmd: Add modem for Option UMTS cards ogsmd: Fix sierra modem The channel now needs to be passed the modem object 2008-11-28 Michael 'Mickey' Lauer ogsmd: Revamped callhandling. Refactored the state-based call handler from TI Calypso (and Freescale Neptune) into a generic class that will be used from all modem abstractions. Ported singleline to use the new call handler. More tests needed for TI Calypso and Freescale Neptune. MAJOR BUGFIX: Stop importing modules by full name, instead append the subsystem to sys.path, by that we workaround modules getting imported twice, once as (e.g. and . Apparantly, if you are using __import__, you bypass the usual Python module import checks that should prevent modules from getting imported twice with different namespaces. D'oh. Anyways, this fixes a lot of potential problems with our classmethods, singletons, etc. It should also reduce memory consumption and lookup time a bit. 2008-11-28 Guillaume Chereau ogsmd: fix a bug : string.translate fails with unicode text ogsmd: use 'serial' conf parameter to find the serial line in single line modem ogsmd: Fix a bug when using singleline channel This patch add the methods `setIntermediateResponseCallback` and `handleUnsolicitedResponse` into AbstractModemChannel. 2008-11-28 Michael 'Mickey' Lauer ogsmd: major refactoring of call handling into abstract callHandler. WARNING: Work in progress -- this might destabilize callhandling for a while 2008-11-27 Michael 'Mickey' Lauer Merge commit 'origin/stabilization/milestone4' Conflicts: framework/subsystems/ogsmd/helpers.py ogsmd: use proper function to kill a process. This fixes FSO ticket #260 Merge commit 'origin/stabilization/milestone4' ogsmd: [FREESCALE NEPTUNE]: prepare +CESS handler and fix +CSSU handler Why's this special for the neptune? Merge commit 'origin/stabilization/milestone4' tests: org.freesmartphone.GSM.CB.[Get|Set]CellBroadcastSubscriptions done Merge commit 'origin/stabilization/milestone4' ogsmd: [FREESCALE NEPTUNE] add custom mediators for org.freesmartphone.GSM.CB.[Get|Set]CellBroadcastSubscriptions document some new neptune quirks ogsmd: improve standard compliance in org.freesmartphone.GSM.SIM.GetAuthCodeRequired() ogsmd: [FREESCALE NEPTUNE] fix PDU patterns for +CGML and +CGMR This fixes org.freesmartphone.GSM.SIM.RetrieveMessagebook and org.freesmartphone.GSM.SIM.RetrieveMessage Merge commit 'origin/stabilization/milestone4' tests: org.freesmartphone.[Get|Set]CallingIdentification() done Merge commit 'origin/stabilization/milestone4' Conflicts: framework/subsystems/ogsmd/device.py framework/subsystems/ogsmd/modems/abstract/mediator.py tests: org.freesmartphone.GSM.Network.GetNetworkCountryCode() done otapi: fix bogus error namespaces ogsmd: fix unimplemented method org.freesmartphone.GSM.Network.GetNetworkCountryCode() -> ss Merge commit 'origin/stabilization/milestone4' tests: org.freesmartphone.Retrieve[Phonebook|Messagebook] done tests: org.freesmartphone.GSM.Network.[ListProviders|RegisterWithProvider] done cosmetics ogsmd: bring SINGLELINE and MUXED4LINE up to speed again 2008-11-26 Michael 'Mickey' Lauer Merge commit 'origin/stabilization/milestone4' tests: org.freesmartphone.GSM.Network.Status() done tests: add to README tests: add org.freesmartphone.Device.Audio.SoundStatus() Merge commit 'origin/stabilization/milestone4' tests: start with org.freesmartphone.GSM.Network.* 2008-11-25 Michael 'Mickey' Lauer Merge commit 'origin/stabilization/milestone4' ogsmd: also report MCC and MNC in org.freesmartphone.GSM.Network.GetStatus() / org.freesmartphone.GSM.Network.Status WARNING: soft API change ogsmd: (readd) server.py Merge commit 'origin/stabilization/milestone4' tests: org.freesmartphone.[GetSet]ServiceCenterNumber() Merge commit 'origin/stabilization/milestone4' tests: org.freesmartphone.GSM.SIM: * RetrieveEntry * StoreEntry * DeleteEntry Merge commit 'origin/stabilization/milestone4' tests: start with phonebook tests ogsmd: [FREESCALE NEPTUNE] catch up modem abstraction with current state of affairs, use PDU mode Merge commit 'origin/stabilization/milestone4' Conflicts: framework/subsystems/odeviced/audio.py odeviced: start with tests for org.freesmartphone.Device.Audio() odeviced: fix error namespace for org.freesmartphone.Device.Audio odeviced: org.freesmartphone.Device.Audio.PlaySound: don't crash if extension is missing in filename odeviced: fix a couple of problems in the Audio problem and support new features such as a loop parameter and an optional length override (time in seconds). NOTE: Some codecs (i.e. machine emulators such as siddec) can not find out when a song ends, hence it will play forever until you stop it (or give it a length override, which is now possible). WARNING: API breakage in org.freesmartphone.Device.Audio.PlaySound() oeventsd: catch up with API breakage in odeviced/audio rules: override length for SID ring and message tunes This fixes FSO ticket #247 and the message notification tone only working for the first time in ms4(.1) NOTE: More stress-tests for the audio API necessary Conflicts: framework/subsystems/oeventsd/fso_actions.py tests: start with org.freesmartphone.GSM.SIM.* Merge commit 'origin/stabilization/milestone4' tests: org.freesmartphone.GSM.[Get|Set]AntennaPower done tests: org.freesmartphone.GSM.GetFeatures() done ogsmd: [FREESCALE NEPTUNE] create the MiscChannel on /dev/mux6 for now ogsmd: improve result for org.freesmartphone.GSM.Device.GetFeatures() Merge commit 'origin/stabilization/milestone4' ogsmd: [FREESCALE NEPTUNE] implement org.freesmartphone.GSM.Device.GetInfo tests: finally start with full test coverage for the FSO API compliance (yes, this is just a small start, but it's now underway) 2008-11-23 Michael 'Mickey' Lauer ogsmd: Added reinit method to abstract modem class, * added hook for handling a HUP in the low level channel class, * added reinitializing the TI Calypso modem class whenever a HUP on any MUXer channel has been detected (which means that the gsm0710muxd has probably been killed). NOTE: We now survive a killall gsm0710muxd transparently! TODO: How to send this to the upper layers? (via a signal in the org.freesmartphone.GSM.Device space?) onetworkd: simple connection sharing seems to work now! 2008-11-22 Michael 'Mickey' Lauer onetworkd: connection sharing convenience (WIP, there is no such thing as a Network subsystem...) onetworkd: just playing around... Merge commit 'origin/stabilization/milestone4' ogsmd: remove watchForHUP in GSM channel on close(). This fixes FSO ticket #240 ousaged: add FIXME odeviced: fix a couple of problems in the Audio problem and support new features such as a loop parameter and an optional length override (time in seconds). NOTE: Some codecs (i.e. machine emulators such as siddec) can not find out when a song ends, hence it will play forever until you stop it (or give it a length override, which is now possible). WARNING: API breakage in org.freesmartphone.Device.Audio.PlaySound() oeventsd: catch up with API breakage in odeviced/audio rules: override length for SID ring and message tunes This fixes FSO ticket #247 and the message notification tone only working for the first time in ms4(.1) NOTE: More stress-tests for the audio API necessary 2008-11-21 Michael 'Mickey' Lauer ogsmd: improve suspend/resume UART handling -- closes #31 WARNING: this needs testing! 2008-11-20 Daniel Willmann Merge branch 'stabilization/milestone4' preferences: Make message tone silent as well in silent profile Merge branch 'stabilization/milestone4' ogsmd: Make CPI handling more robust Sometimes for a MO call we don't get %CPI with message type 9, but only with message type 3. This patch adds the direction attribute to all CPI signals and also listens to message type 3. 2008-11-20 Michael 'Mickey' Lauer update ChangeLog, change default incoming SMS notification tone 2008-11-20 Tobias Gruetzmacher Add support for audio file options. Options can be appended to audio file names, seperated via semicolons. As an example, the ringtone now plays a different tune from the defaul SID file. 2008-11-19 Michael 'Mickey' Lauer fix resource not being able to report errors correctly Conflicts: framework/resource.py fix resource not being able to report errors correctly enable testing subsystem testing: add SetResourceBehaviour method add testing interface to cli-framework testing: add new subsystem solely for testing purposes ogsmd: add documentation for call type 128 update ChangeLog, change default incoming SMS notification tone 2008-11-19 Tobias Gruetzmacher Add support for audio file options. Options can be appended to audio file names, seperated via semicolons. As an example, the ringtone now plays a different tune from the defaul SID file. 2008-11-19 Julien 'Ainulindale' Cassignol Merge branch 'master' of git@git.freesmartphone.org:framework oeventsd/ExternalDBusAction : proper handling of the bus argument. 2008-11-19 Michael 'Mickey' Lauer ogsmd: split server and device object implementation into two files 2008-11-19 Julien 'Ainulindale' Cassignol Added an oeventsd action to call DBus methods. Syntax is : ExternalDBusAction("system"|"session", service, device, interface, method, *arguments). Session bus is untested and shouldn't work as is (FIXME). Thanks shoragan for the step-by-step thing ;-). 2008-11-18 Julien 'Ainulindale' Cassignol Timeout settings are now persistent. Timeout settings are now persistent. 2008-11-18 Daniel Willmann Merge branch 'stabilization/milestone4' 2008-11-17 Daniel Willmann ogsmd: Avoid AttributeError: 'AbstractSMS' object has no attribute 'ud' This makes sure that the ud attribute is always present. Merge branch 'stabilization/milestone4' Conflicts: framework/subsystems/ogsmd/gsm/const.py ogsmd: Increase SIMAUTH timeout to 15 seconds ogsmd: Increase default timeout to 10 seconds It seems that especially if the calypso is busy the default timeout of 5 seconds wasn't enough and timeouts fired quite often with the response arriving shortly after. Increasing the timeout will hopefully only catch real timeouts. 2008-11-16 Michael 'Mickey' Lauer ogsmd: [TI CALYPSO] report GSM | GPRS cipher status 2008-11-15 Daniel Willmann Merge branch 'stabilization/milestone4' odeviced: Fix o.f.Device.Input.Event pressed and released handling We need subsecond accuracy in the timestamp to accurately detect when one second is over. ogsmd: Rename featuremap to properties, add type 2008-11-14 Michael 'Mickey' Lauer Merge commit 'origin/stabilization/milestone4' [odeviced] Added kernel26 option called 'fb_blank' (default=1). If you suffer from the Om bug WSOD (white screen of death) during "normal" operation, try setting this option to 0. 2008-11-14 Daniel Willmann ogsmd: Increase default timeout to 10 seconds It seems that especially if the calypso is busy the default timeout of 5 seconds wasn't enough and timeouts fired quite often with the response arriving shortly after. Increasing the timeout will hopefully only catch real timeouts. ogsmd: Rename AbstractSMS -> SMS, make decodeSMS a class method 2008-11-14 Michael 'Mickey' Lauer ogsmd: implement org.freesmartphone.GSM.Network.GetCountryCode() -> ss 2008-11-14 Daniel Willmann ogsmd: Move {en,de}codePDUNumber to PDUAddress.{pdu,decode} This moves the functions from convert.py to (class) methods of PDUAddress which is much cleaner. ogsmd: Make PDUAddress parsing/generation more robust This adds support for numbers that have *#abd "digits" in them. ogsmd: sms.py: Add some more PDUs and add ACKPDUs for testing ogsmd: Change repr method to __repr__ in SMS and CB classes Add some more output in __repr__ ogsmd: Ensure that self.ud is set if we receive a binary SMS/CB ogsmd: Introduce SMSError and use it SMSError will be raised if SMS parsing/generation fails. Currently only used if the PDU is malformed (which shouldn't happen, but does - see http://trac.freesmartphone.org/ticket/227) ogsmd: Add support for supplying PDUs in a file on stand-alone operation Put PDUs separated by newlines in two files, one for MO and one for MT messages then call sms.py with the filenames for MO and MT PDUs as parameter. 2008-11-13 Jan Luebbe ogsmd: add another %CCCN example Merge branch 'stabilization/milestone4' ogsmd: detect forwarding for incoming calls ogsmd: fix SendUssdRequest to use UCS2 encoding 2008-11-13 Michael 'Mickey' Lauer ogsmd: [TI CALYPSO] prepare for sending org.freesmartphone.GSM.CipherIndication ogsmd: [TI CALYPSO] use callbacks for sending +CNMI and %CBHZ ogsmd: add support for callbacks in addition to static strings odeviced: use gobject.timeout_add instead of timeout_add_seconds, since we need the granularity here. This fixes the power button (hence suspend) seemingly not working always. odeviced: use gobject.timeout_add instead of timeout_add_seconds, since we need the granularity here. This fixes the power button (hence suspend) seemingly not working always. ogsmd: [TI CALYPSO] use callbacks for sending +CNMI and %CBHZ ogsmd: add support for callbacks in addition to static strings ogsmd: [TI CALYPSO] send %CBHZ if told to listen for home zone channel broadcasts NOTE: Does not survive a suspend/resume cycle yet. Need to enhance the channel's suspend/resume command handling a bit beforehand (allow sending a callable in addition to a static string). 2008-11-12 Daniel Willmann Merge branch 'stabilization/milestone4' ogsmd: Unify timeout for all CPIN commands (thanks quickdev!) 2008-11-10 Daniel Willmann Merge branch 'stabilization/milestone4' ogsmd: Increase {Get,Set}AntennaPower timeout Merge branch 'stabilization/milestone4' ogsmd: Make GetMessagebookInfo return integers (thanks quickdev!) Merge branch 'stabilization/milestone4' ogsmd: Adjust timeout values for GetAuthStatus and SmsSendMessage 2008-11-10 Michael 'Mickey' Lauer Merge commit 'origin/stabilization/milestone4' 2008-11-10 Julien 'Ainulindale' Cassignol Forgot I was using python. else if -> elif Implemented org.freesmartphone.odeviced.IdleNotifier.GetTimeouts() => a{si} and SetTimeout(s,i). Forgot I was using python. else if -> elif 2008-11-10 Michael 'Mickey' Lauer ogsmd: [TI CALYPSO] do not assert when the callHandler gets instanciated twice (need to remove that as singleton anyways, now that we can shutdown and restart) This closes FSO ticket #185 2008-11-09 Julien 'Ainulindale' Cassignol Implemented org.freesmartphone.odeviced.IdleNotifier.GetTimeouts() => a{si} and SetTimeout(s,i). 2008-11-09 Daniel Willmann Merge branch 'stabilization/milestone4' 2008-11-08 Daniel Willmann resource.py: Allow method calls in any state except disabled This is a workaround at the moment for Bug #209 since the problems that could occur with this workaround are less annyoing than the "DBus calls fail right after wakeup from GSM". Merge branch 'stabilization/milestone4' ogsmd: Properly encode and decode the address in gsm default alphabet If the address is alphanumeric it's encoded in the 7-bit GSM default alphabet so handle encoding/decoding properly. 2008-11-07 Daniel Willmann Merge branch 'stabilization/milestone4' Fix a Traceback on channel close when ti_calypso_deep_sleep = never We now have the case that there is no self.delegate.recampingTimeout set so instead check for self.delegate.checkForRecamping. 2008-11-02 Michael 'Mickey' Lauer docs: update calypso docs with some new (hard to believe) issues Merge commit 'origin/stabilization/milestone4' ogsmd: [TI CALYPSO]: shutdown modem on close() call odeviced: initial status for every LED is 'off' use cProfile to profile and import http://www.gnome.org/~johan/lsprofcalltree.py, that way we can view our profile output in kCacheGrind * remove option parsing out of controller, into frameworkd * add command line option for profiling with the python-hotshot profiler odeviced: enhance linux input system support 2008-11-01 Michael 'Mickey' Lauer change README 2008-10-31 Michael 'Mickey' Lauer Merge commit 'origin/stabilization/milestone4' ogsmd: Fixed bogus logic and sent-on-wrong-channel bugs in org.freesmartphone.GSM.Network.SetCallingIdentification. Merge commit 'origin/stabilization/milestone4' ogsmd: org.freesmartphone.GSM.Network.GetStatus(): make sure network provider is always shown in alphanumerical format. Thanks Alastair Johnson for spotting. Merge commit 'origin/stabilization/milestone4' ogsmd: [TI CALYPSO] add TI-Calypso specific modem option called 'ti_calypso_deep_sleep'. Valid values are: 'never', 'adaptive' (default), and 'always'. See Om bug #1024. 2008-10-30 Michael 'Mickey' Lauer Merge commit 'origin/stabilization/milestone4' rules: change Suspend rule to only fire on a button release when the button has not been held for one second or longer odeviced: input: deliver duration (i.e. number of seconds the key was held) also for the 'release' signal. Note: This is a minor API enhancement which should be backwards-compatible since the docs told that the duration field was previously only used for the 'held' signal. 2008-10-29 Michael 'Mickey' Lauer odeviced: audio: fallback to NullPlayer if GStreamerPlayer can't be launched odeviced: audio: fallback to NullPlayer if GStreamerPlayer can't be launched 2008-10-29 Daniel Willmann Merge branch 'stabilization/milestone4' oeventsd: Fix MessageToneAction This follows a change in {Audio,Vibrator}Action to tasklets ogsmd: Use PDUAddress.guess for org.freesmartphone.GSM.SMS.SendMessage oeventsd: Fix IncomingMessage trigger (thanks quickdev) This follows the org.freesmartphone.GSM.SIM API change from IncomingMessage to IncomingStoredMessage Merge commit 'origin/stabilization/milestone4' oeventsd: Fix SetLeds action that got broken earlier 2008-10-29 Guillaume Chereau tests: added oevents tests oeventsd: added RemoveRule DBus method oeventsd: Abort rule creation if there is a parsing error Before this, if a function call in the rule file fails, then the value returned by the function is None. Instead we raise an exception, and let the parser aborts the rule (with big error message) 2008-10-28 Daniel Willmann Merge commit 'origin/stabilization/milestone4' Conflicts: framework/subsystems/ogsmd/modems/abstract/mediator.py (changed dir -> direction since dir is a python builtin) ogsmd: Correctly match on SMS category in RetrieveMessage ogsmd: Fix TI calypso recamping logic (#190) The timer is now removed if we disable GSM resource. 2008-10-28 Jan Luebbe Merge branch 'stabilization/milestone4' persist: readd logging 2008-10-28 Michael 'Mickey' Lauer ogsmd: 'dir' is a builtin in Python, don't shadow it 2008-10-27 Jan Luebbe Merge branch 'stabilization/milestone4' persist: add support for the pickle format and use it per default 2008-10-27 Daniel Willmann ogsmd: Use PDUAddress.guess for org.freesmartphone.GSM.SMS.SendMessage 2008-10-27 Jan Luebbe Merge branch 'stabilization/milestone4' oeventsd: handle suspend as a rule 2008-10-27 Daniel Willmann oeventsd: Fix IncomingMessage trigger (thanks quickdev) This follows the org.freesmartphone.GSM.SIM API change from IncomingMessage to IncomingStoredMessage 2008-10-27 Guillaume Chereau oeventsd: using yaml CLoader This make things faster, but there is a problem : it is not possible to tell yaml to automatically interpret the functions anymore, so we have to do it manually after each call to yaml.load, and iterate the structure looking for functions. It is really not a very good solution. 2008-10-26 Michael 'Mickey' Lauer odeviced: improve WARNING wording odeviced: make UsbHost no ResourceAwarePowerControl for now If it's a ResourceAwarePowerControl and there is no ousaged running on startup, then it will break USBeth by automagically switching to host mode (which may not be what you want...) * [odeviced] Support .ogg in GStreamerPlayer and code more defensively (checking whether decoders are available before trying to instanciate them) ousaged: readd os import and ~lart pychecker. Thanks quickdev 2008-10-24 Michael 'Mickey' Lauer oeventsd: call init on base class, even if this does nothing Resource System: work around the fact that the dbus-python bindings do not really support handling methods for one object by two different classes, not even when it's just an ancestor. 2008-10-23 Daniel Willmann ogsmd: [TI Calypso] Return False in _reactivateDeepSleep This function is added as a timer from _checkRecampingBug in order to reenable deep sleep. Not returning False leads to many _reactivateDeepSleep being scheduled over time (if you're suffering from Bug #1024). ogpsd: Log info for the DBus Signals ogsmd: Introduce PDUAddress.guess for guessing number types and use it PDUAddress.guess() will try to figure out what kind on address (international number, national number, alphanumeric) is called and set the numbertype appropriately. ogsmd: Fix typo in const.py ogpsd: Fix shutdown logic of the SerialChannel ogpsd: Save the path in FileChannel constructor After introducing initializeChannel and shutdownChannel methods we need to save the path in the constructor so we can access it in initializeChannel. This functionality should probably go to the base class instead. ogpsd: Remove dbus imports from nmea.py cli-framework: Crop readline history to 1000 lines ogpsd: Clean up the GPS time calculation (Thanks Jan) The previous time calculation code was a mess and in the end only worked because two errors cancelled each other out. 2008-10-23 Guillaume Chereau tests: added test for opimd ContactQuery.GetMultipleResults opimd: fix a few things * return DBus path instead of uri * GetMultipleResults actually returns a list tests: updated opimd test Add checking that added contact appears into the query for the same name opimd: fix a few problems in pimd_contacts * use DBus path intead of uri * disable query.check_new_contact 2008-10-22 Michael 'Mickey' Lauer cli-framework: honor boolean type (thanks Frederik Sdun) 2008-10-22 Guillaume Chereau oeventsd: Several actions can share the leds This is a little bit experimental, each led object keeps track of 'who' is using it and can then solve conflicts (for example if one action turns a led off and an other one turns it on, the led will be on) The drawback is that it only works with 'while' type rules. 2008-10-21 Michael 'Mickey' Lauer * [ousaged] Remove dummy resources encapsulating odeviced objects * [odeviced] Add ResourceAwarePowerControl class and use it * [odeviced] Add powercontrol_ibm.py for IBM ACPI Resources use interface, so that we can run cli-framework as non-root resource.py: use own bus object odeviced: binding to the kobject socket only works as root 2008-10-21 Daniel Willmann ogpsd: Don't send ephemeris since this seriously screws up the GPS chip ogpsd: Fix aiding data time generation bug (time.time honors DST flag) ogpsd: Report height above mean sea level, not above ellipsoid NMEA height is reported as height above mean sea level, this fixes #188 2008-10-21 Guillaume Chereau tests: Added ogsmd2.py 2008-10-21 Daniel Willmann oeventsd: Split arguments in DbusTrigger This way we can just match on the first argument of a DBus signal with HasAttr(arg0, "foo") 2008-10-21 Guillaume Chereau tests: Added some tests for gsm this patch also add a way to request manual operations during a test, using the 'operator' object. Fix a few typos in dbus-1/system.d/frameworkd.conf 2008-10-20 Guillaume Chereau opimd: Created 'Backend' and 'Domain' classes It has to be done because we don't want to register the backends and the domains at import time. So instead we register only the classes at import time using the metaclasses, and then we can register all the domains and the backends. I also renamed the modules to remove the '-' characters, cause we have to import the modules manually. Compute the rootdir in config module This is because otherwise many subsystems have to reinvent a way to get the data directories. With this patch, they can all use directories : "%s/%s" % (rootdir, subsystem_name) opimd: update example frameworkd.conf 2008-10-19 Michael 'Mickey' Lauer oeventsd: do not bail out if preferences subsystem is not there * add command line option '-d' that triggers daemonizing mode * prepare for controlled shutdown ogsmd: [TI CALYPSO]: s/logging/logger/ ogsmd: move 'assert' to logger.warning, if unknown GSM number type found 2008-10-18 Michael 'Mickey' Lauer ogsmd: send +CSMS=1 only after the SIM has been unlocked ousage: fix potential errors detected by pychecker 2008-10-17 Michael 'Mickey' Lauer ousaged: improve error specs, clean up, bump version ogsmd: [TI CALYPSO]: lower recamping detect factor to 0.3 :/ 2008-10-17 Guillaume Chereau opimd: settings merged with framework settings oeventsd: DBusTrigger can be created from the rules file 2008-10-16 Michael 'Mickey' Lauer ogsmd: add some Calypso proprietary errors tools: cli-framework: improve prettyprinter to cover more cases 2008-10-16 Guillaume Chereau tests: Add opimd test suites cli-framework: Add support for pim service opimd: Added Source.InitAllEntries DBus method This method has to be called by the client before trying to access the contacts. I think we could implement it as a service ('PIM'). opimd: Fix transition bugs This patch makes the pimd subsystem not crash on startup. It disables automatic loading of the contacts on startup, because this should be done on user request. opimd: Initial import Copied all the files from Soeren gsoc project Also eplaced all the tabs by spaces to follow the style used by other subsystems. The opimd subsystem is *not* working now, so we sohuld disable it in framework.conf file. 2008-10-15 Michael 'Mickey' Lauer ousaged: readd parameter checking which seems to got lost on the way (tsss...) 2008-10-15 Guillaume Chereau tests: Added 'request' decorator to the tests This can be used to specify that a test can only be run under specific conditions (like if the sim card is present.) The current test condition can be specify in the file 'tests.conf' I also created a README file for the tests and started a sim test suite. 2008-10-14 Daniel Willmann resource.py: Don't put a "disabled" resource into "suspended" state. If we call ok_callback() in Suspend() when encountering a disabled resource its next status will be "suspended" which is not what we want. Instead call dbus_ok() directly. Revert "Fixed suspend/resume to take into account truly enabled GPS." This reverts commit 9c578aff0511d548701b5be995b4215544d77f70 as the problem arises from a bug in the resource system where disabled resources will be resumed. 2008-10-14 Julien 'Ainulindale' Cassignol Fixed suspend/resume to take into account truly enabled GPS. Slight modification of rules.yaml to set brightness to 90 on incoming calls. 2008-10-14 Michael 'Mickey' Lauer odeviced: open framebuffer "just in time" for blanking/unblanking ioctl rules: add emergency shutdown rule odeviced: signal lowercase consistency change for idlenotifier and kernel26 2008-10-14 Guillaume Chereau opreferences: log error instead of info when service not found tasklet: Added WaitDBusName tasklet This is a special tasklet that blocks until a given DBusName is available on the system bus 2008-10-13 Guillaume Chereau oeventsd: Added 'AddRule' dbus method for dynamic creation of new rules 2008-10-13 Michael 'Mickey' Lauer ogsmd: increase valid modem timeout for COPS to 30 seconds 2008-10-13 Guillaume Chereau cli-framework: Added events interface oeventsd: Added a 'Test' Trigger This is useful for testing the rules. oeventsd: Added the 'SetProfile' Action oeventsd: Access preferences service via DBus, instead of using direct python access oeventsd: Don't start the AudioAction in the RingToneAction if the volume is set to 0 2008-10-12 Daniel Willmann oeventsd: Call update in the idle loop This makes sure dependent subsystems are loaded before we try to access them. Also add missing etc yaml files. *Please* add new config files to the build system! 2008-10-11 Michael 'Mickey' Lauer add LICENSE 2008-10-10 Michael 'Mickey' Lauer odeviced: input: stop reading from touchscreen for now (to reduce CPU usage) add linux input subsystem support (from pyglet) odeviced: refactor brightness handling odeviced: [PATCH] use /dev/fb0 ioctl based screen blanking (from Harald Welte) 2008-10-09 Daniel Willmann ogpsd: Update ChangeLog * Mention gllin support for GTA01 and SBAS for GTA02 2008-10-09 Michael 'Mickey' Lauer move ListObjectsByInterface from org.freesmartphone.Objects to org.freesmartphone.Framework Warning: API Change, please adjust. oeventsd: fix super() call in SetAudioScenarioAction. Should fix FSO #174 ogsmd: validate parameters for org.freesmartphone.GSM.Call.Initiate 2008-10-08 Guillaume Chereau tests: started the test framework using python unittest tasklet: fix bug 2008-10-08 Michael 'Mickey' Lauer cli-framework uses pprint to display its results now ogsmd: raise org.freesmartphone.GSM.SIM.NotReady for CME Error 100 ogsmd: add dbus error org.freesmartphone.GSM.SIM.NotReady 2008-10-07 Daniel Willmann resource: If resource is disabled don't suspend or resume 2008-10-07 Michael 'Mickey' Lauer ogsmd: [TI CALYPSO]: change recamping threshold to trigger earlier ogsmd: [TI CALYPSO]: fix ReleaseALL during outgoing call. Thanks HDR for reporting! 2008-10-07 Guillaume Chereau cli-framework: Added preferences interface oeventsd: Using the preferences service to get the list of enabled rules I added a small example in the rules file so that we can see how it works. opreferencesd: Added list type I also removed the dictionary key access (we can only get a full dict if the value is a dict now). This is because this feature was only here for the pim system, but it is going to use its own backend instead so we don't need this anymore. 2008-10-07 Daniel Willmann ogsmd: Fix bug where sending/storing SMS with local number fails This happend on all numbers not in international (starting with '+') format 2008-10-07 Guillaume Chereau oeventsd: fixed bug #169 2008-10-06 Daniel Willmann cli-framework: Add gsmsms, gsmdebug and ubxdebug interfaces ogsmd: org.freesmartphone.GSM.SMS.SendMessage This method sends an SMS directly without having to save it on SIM first. ogsmd: Issue Device{G,S}etSimBuffersSms on unsolicited channel +CMT will go the the same channel where CNMI was set so we need to set it in the unsolicited channel. This also fixes a logic bug where GetSimBuffersSms would return the opposite of what it was set to. ogsmd: Add support for IncomingMessage in non-buffered mode This also renames the .SIM.IncomingMessage signal to .SIM.IncomingStoredMessage Warning: API breakage in org.freesmartphone.GSM.SIM! ogpsd: Enable SBAS by default and poll for HUI packet 2008-10-06 Michael 'Mickey' Lauer ogsmd: implement org.freesmartphone.GSM.SIM.ListPhonebooks() -> as ogsmd: fix bug in calling GetPhonebookInfo from within RetrievePhonebook. Thanks quickdev! 2008-10-06 Daniel Willmann ogpsd: Add a GllinChannel that will start and stop gllin ogpsd: Add resource management to GPSChannel as well This adds support for a GPSChannel to react to resource enable, disable, suspend and resume events. 2008-10-06 Michael 'Mickey' Lauer [ogsmd] change SIM phonebook API to support multiple phonebooks Warning: This is an API breakage in org.freesmartphone.GSM.SIM ! 2008-10-06 Guillaume Chereau oeventsd: Added the CallListContains trigger oeventsd: Clean the code 2008-10-06 Michael 'Mickey' Lauer ogsmd: [TI CALYPSO] work around deep sleep recaming bug as documented in http://docs.openmoko.org/trac/ticket/1024 NOTE: Might play with the heuristics to fine-tune 2008-10-06 Jan Luebbe persist: write to a tempfile first, then rename it persist: use the CLoader/CDumper from libyaml 2008-10-05 Michael 'Mickey' Lauer ogsmd: [TI CALYPSO] prepare for checking recamping bug and set AEC + Noise Reduction to -6db 2008-10-04 Daniel Willmann ogsmd: Replace timeouts of individual commands with command classes ogpsd: GPS epoch is Oct 6th not Oct 5th 1980 With this fix the time should be calculated correctly for the GPS receiver. Also slightly increase the time we wait after power on so the first configure packets don't go astray. ogpsd: Clear sendbuffer after sending stuff (emdete) Before we would resend everything that has ever been sent again and again. Big thanks to emdete for discovering! ogpsd: Make Gypsy .Server methods callable if the resource is disabled Without this fix standard Gypsy programs wont work 2008-10-04 Jan Luebbe persist: fix loading empty file 2008-10-04 Michael 'Mickey' Lauer ogsmd: fix enabling direct SMS sending. Todo: add unsolicited handler for CMT 2008-10-03 Guillaume Chereau oeventsd: Added the 'while' rule (rule that can be undo) 2008-10-03 Jan Luebbe ogpsd.ubx: use the correct dbus signature ogpsd.ubx: add simple debug interface 2008-10-03 Michael 'Mickey' Lauer ogsmd: classes derived from AbstractModemChannel now need the parent 'modem' to be supplied in init (as keyword argument). ogsmd: implement org.freesmartphone.GSM.Device methods: * GetSimBuffersSms() -> (b) * SetSimBuffersSms(b) -> () Note: This is only working for TI Calypso atm., please catch up in the other modem abstractions resource handling: if ousaged is not present at runtime, start with the resource enabled, otherwise we have no chance to turn it on. 2008-10-02 Daniel Willmann ogpsd.gta02: Request AID-HUI packet for warmstart AID-HUI contains information about the leap seconds which we very much like to know. If we play back a correct AID-HUI packet we get valid UTC time instantly. ogpsd.gta02: Make sending aiding data more robust We now send the AID-INI packet even if we don't have a position in order to still set the time. Persistent data gets saved at the end of shutdown cli-framework: Add frameworkiface object 2008-10-02 Jan Luebbe ousaged: make setPolicy a tasklet 2008-09-30 Guillaume Chereau tasklet: Added simple messages mechanism between tasklets 2008-09-30 Michael 'Mickey' Lauer ogsmd: send org.freesmartphone.GSM.SIM.MemoryFull on corresponding incoming CMS ERROR 322 2008-09-30 Guillaume Chereau tasklet: Cleaned code a little 2008-09-30 Daniel Willmann ogsmd: sms.py: Don't encode the last 7 bit if they are padding This fixes a bug where the end of the message had a '@' character appended to it. This would always happen when the message was of such a length that the last bit of the character was encoded in a new byte. The remaining 7bit would then also get decoded (all zero -> @). 2008-09-30 Michael 'Mickey' Lauer ogsmd: Supress org.freesmartphone.GSM.SIM.NotFound as a possible response to org.freesmartphone.GSM.SIM.RetrieveMessagebook -- instead send an empty list. 2008-09-30 Daniel Willmann ogpsd: GTA02Device: Sleep after turning on the chip Make sure the GPS is ready to accept commands after powering it on 2008-09-30 Guillaume Chereau tasklet: Added the WaitFileReady tasklet 2008-09-30 Michael 'Mickey' Lauer ogsmd: implement closing the modem. 2008-09-30 Daniel Willmann ogpsd: Send ConnectionStatusChanged signal 2008-09-29 Jan Luebbe changelog: comment about ousaged api change ousaged: be explicit about resource availability ogsmd: allow ogsmd to be reenabled 2008-09-29 Daniel Willmann ogpsd.ubx: Sanitize handling of non UBX data ogpsd: Only run through the dbus methods if the resource is enabled This is achieved through the resource.checkedmethod decorator 2008-09-29 Michael 'Mickey' Lauer add specialized checked method decorator for sync dbus methods 2008-09-29 Daniel Willmann ogsmd: Split the info for concatenated short messages in multiple keys There's now csm_id, csm_num and csm_seq for the id of the csm, the total number of messages for that csm and the sequence of this message. The features will only have simple types as values (no lists or maps) to make life easier especially for the C guys. WARNING: The final names for the features are not decided upon yet and are bound to change! 2008-09-29 Michael 'Mickey' Lauer ogsmd: add resource.checkedmethod for every exported dbus method on /org/freesmartphone/GSM/Device add special decorator checking for resource availability readd Michele Simionato's amazing decorator module 2008-09-29 Daniel Willmann ogsmd: Change regex for SMS PDU header This fixes a bug where RetrieveMessage would not be able to return the SMS if the destination address or origination address is not stored as a contact on the SIM card. ogsmd: Assign the features correctly ogsmd.sms: Return timezone as string for now ogsmd.sms: Added support for advanced message features This adds a parameter a{sv} to the DBus methods RetrieveMessage{,book} and StoreMessage. AbstractSMS now has a property featureMap that exposes the intersting fields for the user. 2008-09-28 Daniel Willmann ogpsd: Implement org.freedesktop.Gypsy.Device.{Start,Stop} methods The Start and Stop methods now keep track of which dbus user started the device and listens to the nameownerchanged signal, releasing the resource if the last user is gone. 2008-09-28 Jan Luebbe ousaged: drop unneeded boolean result for RequestResource 2008-09-28 Daniel Willmann ogpsd.om: Reorder shutdown sequence and force GPS power off at start ousaged: Get rid of the asserts and raise DBusExceptions instead ousaged: Call correct method to register a resource 2008-09-28 Michael 'Mickey' Lauer supply dbus error in dbus-error callback for resource handling 2008-09-28 Daniel Willmann ogpsd: Add file I missed in a355a0f8c6374ded40fe5446cadfb0646c3a3172 2008-09-28 Jan Luebbe ousaged: use the Resource object via an interface ousaged: check if the resource exists 2008-09-28 Daniel Willmann ogpsd: Switch ogpsd to the new resource handling in ousaged (fixes #158) 2008-09-28 Michael 'Mickey' Lauer ogsmd: fix GPRS connections with given user. Closes FSO #140 Patch by lindi, thanks a lot! 2008-09-28 Jan Luebbe tasklet: add debug helper for unstarted tasklets 2008-09-28 Daniel Willmann ousaged: Call start() method on the tasklet Before that the nameownerchanged signals were lost 2008-09-28 Michael 'Mickey' Lauer ogsmd: Resource-handling now working with enable/suspend/resume add state transition tracking for Resources ogsmd: startup on enabling resource now complete ogsmd: start catching up with new way of dealing with Resources 2008-09-28 Daniel Willmann ogsmd: Raise timeout for SimSendStoredMessage to 10 seconds (fixes #156) Also use of const.TIMEOUT instead of hardcoding the timeout value for SimSendAuthCode ogpsd.ubx: Send the speed in knots to be compatible with the gypsy API (fixes Bug #97) Thanks rwhitby for discovering! 2008-09-27 Jan Luebbe ousaged: annouce new resources ousaged: report time used for enabling and disabling a resource ousaged: reorder imports and get rid of unused helpers module 2008-09-27 Michael 'Mickey' Lauer rules: Add IdleState DBus trigger and SetDisplayBrightness action We are handling the display dimming solely through the rules now. (Yes, the rules files is device specific nowadays...) support configurable global debug log destination 2008-09-27 Jan Luebbe conf: add readme file for the persist directory and ship it 2008-09-26 Jan Luebbe ogpsd: use the persist module for storing aiding data 2008-09-26 Michael 'Mickey' Lauer resource.py: be prepared that OUsaged can be disabled 2008-09-26 Jan Luebbe ogsmd.sierra: switch to PDU mode for SMS 2008-09-26 Guillaume Chereau tests: updated ousaged test suite ousaged: Enable and Disable are now asynchronous, using tasklet module to make things simpler. tasklet: Modifications to the tasklet module * added the tasklet decorator * generator function need to be explicitely specified as a keyword argument in Tasklet.__init__ * added Tasklet.start_dbus method, that connect to dbus compliant callback methods * added the WaitFunc tasklet (useful to connect a tasklet to a function that take callback arguments) * added the Sleep tasklet (useful for testing) * suppressed test/tasklet.py, we should all use patterns/tasklet.py ! 2008-09-26 Jan Luebbe frameworkd: add simple persistance module 2008-09-25 Daniel Willmann ogsmd: Strip padding from cell broadcast messages and add a test PDU ogsmd: Add cell broadcast support for PDU mode This introduces cell broadcast support for PDU mode. It hasn't been tested on a real device yet, so if you have the chance please do! 2008-09-25 Jan Luebbe otimed: fix typo 2008-09-25 Daniel Willmann ogpsd: Only update ECEF position information if our accuracy is better than 1km Before we just updated this info regardless so it could happen that it got overwritten with invalid information after powering on the GPS but before we even send the AID-INI packet. ogpsd: Don't ask the GPS to send AID-DATA if the aiding file doesn't even exist 2008-09-25 Matt Brown ogpsd: only sending aiding data when valid dictionaries are present On a cold boot (no aiding.dat file available) the dictionaries that would have been unpickled from it are set to empty dictionaries instead. The code then attempts to lookup entries within these dictionaries to feed aiding data to the chip, but the dictionaries are empty resulting in a key error. I have attached a patch which fixes this by only attempting to send each component of the aiding data is a non-empty dictionary is present. Patch by Matt Brown, thanks! 2008-09-25 Jan Luebbe otimed: add basic alarm system 2008-09-25 Daniel Willmann ogsmd: Add a PDU to test characters on the extended GSM alphabet table 2008-09-25 Guillaume Chereau [ousaged] Added support for resources suspend and resume The code is a little bit tricky, it may be improved by using the tasklet class 2008-09-25 Daniel Willmann ogsmd: Fallback to UCS2 if encoding a message in the GSM alphabet fails ogsmd: Fix UD length for SMS including GSM extension alphabet characters. Characters like "\", "[", ... are encoded as two septets so we need to use the length of the encoded user data. ogsmd: Fix encoding SMS with characters in the GSM extension table We tried to catch KeyError instead of ValueError so we missed the exceptions 2008-09-24 hadara ogsmd: Fix bug in bcd_decode when an address is empty When you have SMS messages with empty sender address the bcd_decode() function will fail since it expects string to be at least 1 char long. This patch fixes it. Thanks to hadara@bsd.ee! This fixes FSO Ticket #145 2008-09-24 Daniel Willmann ogsmd: Fix calculating the timezone for the SCTS and VP field ogsmd: Fix SimRetrieveMessage and remove some unneeded code. Patch by dv_mlist@verizon.net, thanks! This closes FSO Ticket #153 2008-09-24 Guillaume Chereau Added the resource module. Every subsystem that wants to register as a resource should subclass Resource, and reimplement the _enable, _disable, _suspend and _resume methods. it should also make sure to call Resource.__init__ with a keyword 'name' parameter. I updated ogsmd to behave this way, but we still need to implement the methods. [tests] Updated tasklet module In fact I should totaly remove it since we have a tasklet module in the framework now. Print the traceback if an exception occures during a subsytem init. Controller.object method raise a KeyError exception if the object is not present. It makes more sens, since most of the time an object not present is an error. 2008-09-24 Daniel Willmann ogsmd: Use UCS2 as default character set This solves internationalization issues for contact names. 2008-09-24 Michael 'Mickey' Lauer tools: add usage and gps interfaces to cli-framework. closes fso #131 patch by fso@mattb.net.nz, thanks! 2008-09-23 Daniel Willmann ogsmd: Add missing character "ü" to GSMALPHABET Thanks Ainundale for reporting! 2008-09-23 Jan Luebbe ogsmd: finish DCS handling and add some UCS2 SMS for testing 2008-09-23 Daniel Willmann ogsmd: Calculate user data length correctly when not using GSM default alphabet Encoding of UCS2 SMS now works as well, but dcs and dcs_* fields must be set correctly) 2008-09-23 Michael 'Mickey' Lauer ogsmd: send correct dbus error if number is not an emergency number 2008-09-23 Daniel Willmann ogsmd: Fix encoding when we don't have a 7-bit charset 2008-09-23 Jan Luebbe ogsmd: Fix UCS2 decoding 2008-09-23 Daniel Willmann ogsmd: Use DCS alphabet to decode the message properly. 2008-09-23 Jan Luebbe ogsmd: decode DCS field 2008-09-23 Daniel Willmann ogsmd: Make GSM default alphabet a codec to prepare for UCS2 handling. Note: This currently breaks decoding/encoding SMS. 2008-09-22 Michael 'Mickey' Lauer ogsmd: add org.freesmartphone.GSM.Call.Emergency(number) -> () ogsmd: catch up org.freesmartphone.GSM.SIM.GetMessagebookInfo() docs: ogsmd: some more comments ogsmd: [TI CALYPSO]: tweak PDP timeouts and settings 2008-09-20 Michael 'Mickey' Lauer ogsmd: add database with gprs settings ogsmd: add FIXME for missing state, report internal error in log as well 2008-09-09 Jan Luebbe odeviced: fix SetState oeventsd: switch to gsmhandset while the state is outgoing 2008-09-07 Michael 'Mickey' Lauer ogsmd: revert revertion of a24dccc1127c619435ca405ef9e0e73ae61da7ed instead, fix textToUnicode() to deal with incoming strings that are already unicode. We emit a warning though until the transition to PDU is finished. 2008-09-07 Daniel Willmann ogsmd: Change the charset of gsm/const.py to UTF 8 This makes the definition of the GSM alphabet easier/work in all cases. Revert "ogsmd: Change the charset of gsm/const.py to UTF 8" since it makes problems with other unicode conversion stuff This reverts commit a24dccc1127c619435ca405ef9e0e73ae61da7ed. ogsmd: Change the charset of gsm/const.py to UTF 8 This makes the definition of the GSM alphabet easier/work in all cases. ogsmd: Add support for GSM default alphabet and use it Previously we just de/encoded according to ASCII, this should fix lots of issues with special characters not being displayed properly 2008-09-06 Michael 'Mickey' Lauer ogsmd: try to circumvent race condition for sending SIM init commands Needs testing on other modems 2008-09-05 Michael 'Mickey' Lauer ousaged: add UsbHost resource odeviced: add power control object for switching to Usb Host. tools/cli-framework: add convenience function for getting proxies implementing an interface bail out if our dbus bus name is already owned on startup 2008-09-05 Daniel Willmann ogsmd: Enable PDU mode and use it in all SMS functions ogsmd: sms.py: Fix some PDU generation bugs with optional fields 2008-09-05 Sascha Wessel ogpsd: Reset gpsfixstatus when stopping the GPS. From: Sascha Wessel Subject: [PATCH] reset-ubx-gpsfixstatus-and-buffer.patch Hi, This patch fixes the following issue: * zhone shows 90.0/0.0 as location (Debian bug #494927) Greetings, Sascha 2008-09-05 Daniel Willmann ogpsd: Fix warmstart codepath so it sends initial data 2008-09-05 Sascha Wessel ogpsd: UBXDevice uses one of the private variables from GPSDevice (Patch by Sascha Wessel) 2008-09-04 Michael 'Mickey' Lauer ogsmd: repair ppp connections without user and password ogsmd: start honoring user and password in PDP fix setup to automatically install all examples add example for launching scripts on incoming call 2008-09-04 Daniel Willmann ogsmd: Fix various functions in convert.py and padding/udl calculation in PDU generation convert.py: * encodePDUNumber is new and supports alphanumeric addresses as well * bcd_encode works with odd numbers of digits * pack_sevenbit now correctly handles padding sms.py (PDU generation): * Use encodePDUNumber * Fix value of the validity period field * Calculate userdata length and padding correctly ogsmd: Test MT and MO PDUs and fix some sms. instead of self. errors ogsmd: Use and test the PDU generation code ogsmd: Add functions to encode SMS back to PDUs This is still buggy, pack_sevenbit doesn't work if UDH are present. ogsmd: Add a direction attribute for the SMS object MT: Mobile terminated, MO: Mobile originated 2008-09-04 Michael 'Mickey' Lauer ogsmd: [TI CALYPSO]: enable noise reducation and echo cancelling ogsmd: remove strength field in Status() callback and result, _if_ * we are successfully registered and * signal strength has been reported as 0 This is actually a race condition in some modems. 2008-09-03 Michael 'Mickey' Lauer oeventsd: SMS notification: * add IncomingMessage trigger * add MessageTone action * add rule oeventsd: preliminary headphone mixer autoswitch * add InputEvent trigger * add Command action * add rule for gta02 2008-09-03 Daniel Willmann ogpsd: Add a dummy GPSDevice that reports a static location 2008-09-03 Sascha Wessel examples: Add examples how to use ogpsd DBus API (thanks Sascha Wessel!) ogpsd_signals.py listens for gypsy signals ogpsd_methods.py calls gypsy methods once and exits ogpsd_resource.py requests the GPS resource from ousaged (power on while running, see /sys/devices/platform/s3c2440-i2c/i2c-adapter/i2c-0/0-0073/neo1973-pm-gps.0/pwron) 2008-09-03 Daniel Willmann ogpsd: Hide private member variables in GPSDevice to prevent subclasses from overwriting them. 2008-09-03 Michael 'Mickey' Lauer oeventsd: refactor subsystem: * separate generic actions and triggers from specific ones * remove unnecessary duplicated import statements (please be more careful on that) 2008-09-03 Daniel Willmann ogpsd: ubx.py: Only update time when we have valid UTC bit set. Also explain in the comment some more about GPS time, leap seconds and what we could do to speed up first somewhat accurate UTC time signal. ogpsd: Whitespace fixes in nmea.py 2008-09-03 Sascha Wessel ogpsd: Fix various bugs (patch by Sascha Wessel) From: Sascha Wessel To: standards and reference implementations Subject: Re: ogpsd questions Hi, I started to fix some of the problems while keeping the gypsy api intact. Moreover ogpsd should behave much more like the gypsy c implementation with the attached patch applied. Summary: * Fixed timestamps * Added _updateTime calls to nmea.py * Added _reset method to gpsdevice.py and call it when deconfiguring devices * Fixed _updatePosition: keep old values * Fixed _updateAccuracy: keep old values * Fixed _updateCourse: keep old values * Fixed initialization: get rid of error messages like: dbus.exceptions.DBusException: org.freedesktop.DBus.Python.IndexError: Traceback (most recent call last): File "/var/lib/python-support/python2.5/dbus/service.py", line 702, in _message_cb retval = candidate_method(self, *args, **keywords) File "/var/lib/python-support/python2.5/framework/subsystems/ogpsd/gpsdevice.py", line 127, in GetTime return self.time[0] IndexError: list index out of range Please consider applying the attached patch to the repository. Greetings, Sascha 2008-09-03 Michael 'Mickey' Lauer oeventsd: remove circular reference to fix importing 2008-09-02 Michael 'Mickey' Lauer oeventsd: add 'blink' action to LedAction, adjust rules file 2008-09-01 Michael 'Mickey' Lauer enable global log_level in [frameworkd] section ogsmd: fix wrong dbus namespace for IncomingUssd signal. Thanks quickdev! 2008-09-01 Guillaume Chereau [oeventsd] Use metaclass to create rules function from Action and Trigger class Every Action or Trigger class that has a 'function_name' attribute will automatically be accessible from the rule file using that function name. So that we don't need to create the function in parser.py. 2008-08-28 Michael 'Mickey' Lauer remove LOG compatibility support. We are now fully converted to Python Logging. Closes FSO #26 ophoned: remove remaining LOG statements. We are now LOG-free ousaged: convert to Python logging odeviced and ogsmd: remove remaining LOG statements. Using python logging now ogsmd: pdp: convert to python logging examples: fix gsm-log-data odeviced: audio: simplify GStreamer pipeline creation ogsmd: add valid emergency numbers document additional configuration keys odeviced: kernel26: fix power supply class logic, if not present Yay, this now works even if battery is hot-swapped -- even if you change from a TH (which has no power supply support yet) to a CC (which has). 2008-08-27 Jan Luebbe ousaged: add more information to the ResourceChanged signal 2008-08-27 Michael 'Mickey' Lauer oeventsd: add triggers and rules for power management (NOTE: after MS3 we need to separate the device specific stuff from the generic stuff here!) odeviced: use kobject uevent netlink socket to implement polling-free power status notifications use introspection to implement org.freesmartphone.framework.ListObjectsByInterface(). Closes FSO #2 NOTE: need to cleanup all references to saving the interface in the controller add Charlie's fantastic tasklet module to patterns 2008-08-24 Michael 'Mickey' Lauer oevents: Push/Pull Scenario idea didn't work out, we need to save state between rule invocations for that. Went back to fixed SetScenario (same as we did in oeventd for ms2) for now until we can discuss more. ogsmd: increase robustness. closes #23 2008-08-23 Michael 'Mickey' Lauer oevents: bring back AudioScenarioAction (first try) FIXME: leads to a audio scenario stack underflow if a call never reaches 'active' odeviced: fix import breakage introduced by me 2008-08-22 Guillaume Chereau [oeventsd] Added trigger for time change events * Added the Time(hour, minute) trigger in the rule file * Added the Debug(msg) action [otimed] Creation of the subsystem 2008-08-21 Guillaume Chereau [Conf file] corrected a typo Merge branch 'master' of ssh://git@git.freesmartphone.org/framework [oeventsd] Some typo in the parser functions (thanks to Julien Cassignol) 2008-08-20 Michael 'Mickey' Lauer odeviced: audio: load default scenario on startup. closes FSO ticket #83 odeviced: org.freesmartphone.Audio: add PushScenario( scenario ) and PullScenario() -> previous oeventsd|opreferencesd: add versions, notify on init Please increment versions as per style guide * stylize accessor method (we don't use 'get' as prefix) for controller and return None if not found * increase ringtone event robustness (if preferences object not found) ogsmd: change channel representation in debug messages 2008-08-19 Michael 'Mickey' Lauer compute install prefix 2008-08-19 Daniel Willmann odeviced: fix import statement again :-) thanks Ainulindale Add missing config files to setup.py so they get installed. Thanks, Ainulindale! ogpsd: Apply warmstart.patch (thanks quickdev!) This allows us to save almanac, ephemeris, etc. before powering off the gps chip and load the data back afterwards, thus greatly reducing time to first fix. 2008-08-19 Rod Whitby framework/ogpsd: Improve nmea parser to support GTA01 gllin output A significantly modified nmea parser tested against the output of gllin on the GTA01, and functional enough to provide sufficient signals for TangoGPS to do sensible things. 2008-08-18 Daniel Willmann ogsmd: Introduce a new class PDUAddress that represents the GSM address (number) fields and use it. ogsmd: Add regex and consts for using SMS PDU mode ogsmd: Fix an off-by-one bug with GSM address field parsing ogsmd: Add two more test PDUs from OM bug 1728 ogsmd: Move PDU conversion functions to convert.py Return a datetime object when parsing the time Rename functions for consistency ogsmd: Add padding value to correctly decode alphanumeric addresses. ogsmd: Redo the phonenumber function ogsmd: Add constants describing PDU fields ogsmd: Start work on SMS PDU mode 2008-08-18 Guillaume Chereau Added a 'get_object' class method to Controller So that the subsystems can retrieve any registered DBus object without making a DBUs call. [oeventsd] Can connect to a DBus object signal before it has been created So that we don't need to call the Init method. Thanks to Mickey for the advice. 2008-08-17 Michael 'Mickey' Lauer fix Jan's email address license whitespace changes odeviced: simplify idlenotifier state machine and fix '0' specifying that a state will never be reached (except programmatically) 2008-08-14 Daniel Willmann ogpsd: Some minor (cosmetic) modifications to File- and UDPChannel 2008-08-14 Alon ogpsd: Apply gpschannel_add_udpchannel_ane_filechannel.patch This is a good step forward in the quest to support GTA01 (gllin) 2008-08-14 Guillaume Chereau [oeventsd] Forgot to add a file in previous commit [oeventsd] Added RingTone action that takes the preferences into account It only considers the ring-tone (not the ring-volume yet) I had to rely on a smal hack to retrieve opreferences service without using DBus I hope Mickey will add some sort of 'get_object' method so that we can retrieve any registered object without using DBus 2008-08-12 Michael 'Mickey' Lauer ogsmd: s/NewMessage/IncomingMessage/ 2008-08-11 Guillaume Chereau Added opreferencesd phone configuration files into setup.py [opreferncesd] Added phone configurations 2008-08-10 Michael 'Mickey' Lauer ogsmd: fix stupid bug in ReleaseAll(). fixes ticket #76 ogsmd: add org.freesmartphone.GSM.PDP.GetContextStatus() -> s 2008-08-09 Michael 'Mickey' Lauer odeviced: fix empty 'ignoreinput' in configuration for input and idlenotifier. Patch by pjz. Closes ticket #77 -- thanks! ogsmd: implement basic USSD support 2008-08-09 Joachim Breitner ogsmd: Convert ntype to int in SimRetrieveEntry like in SimRetrievePhonebook Accept number type 185 I had a number type of 185 when traveling to Argentinia, and receiving a SMS from the operator, telling me „SMS reception is for free“. The sending number was 78273. 2008-08-08 Michael 'Mickey' Lauer oeventsd: use public config module and use getValue submitting a default value. ogsmd: add result code for org.freesmartphone.GSM.SIM.SendStoredMessage() thus making it fso-compliant. Thanks #openmoko-cdevel for spotting! 2008-08-08 Daniel Willmann frameworkd.conf: Follow oeventd -> oeventsd change in dbus config 2008-08-07 Daniel Willmann ogsmd: Append the subscriber numbers as name, number tuple. ogsmd: Use right method _rightHandSide in SimGetSimInfo 2008-08-06 Daniel Willmann ousaged: Make the Bluetooth device actually (de-)activate bluetooth 2008-08-06 Guillaume Chereau Added the new style oeventsd module This is quite a lot of changes, zhone should be updated to call the Events.Init method. I hope I didn't break too many things :) Emmit an error if no config file can be found. 2008-08-05 Guillaume Chereau Removed README.opreferencesd 2008-08-05 Daniel Willmann ogpsd: Fix org.freedesktop.Gypsy.Time 2008-08-04 Daniel Willmann ogpsd: implement preliminary ACK handling for CFG messages ogpsd: Log non-UBX data with priority info instead of debug We disable NMEA sentences now. ogpsd: Use Gypsy TimeChanged signal 2008-08-04 Michael 'Mickey' Lauer ogsmd: add FIXME in parser.py 2008-08-04 Daniel Willmann ogsmd: Use self.curline instead of curline. Patch by torindel@gmail.com, thanks. This fixes #62 2008-08-04 Michael 'Mickey' Lauer ogsmd: fix parsing outgoing SMS with empty phone numbers (stored on SIM). Patch by wiedi@frubar.net, thanks! Closes ticket #67 ogsmd: [TI CALYPSO]: fix relaunching in lowlevel init 2008-08-03 Daniel Willmann ogsmd: Make the low level parser quote aware (torindel@gmail.com) This addresses bug #62 ogsmd: Bump timeout to 7 seconds when sending PIN It seems like some SIMs are quite slow to acknowledge the PIN so some time out with the default value of 5 seconds. 2008-08-03 Michael 'Mickey' Lauer * check configuration file format version * allow both disabling a whole subsystem as well as just one module (per config) odeviced: Unify configuration nodes to use the same name we are using for logging, i.e. [subsystem.module]. NOTE: Needs to be done for other subsystems that use individual modules as well 2008-08-03 Daniel Willmann Revert "WIP" This reverts commit 566f11c487b6ae43edfdb3edc60a92d5bfb17f7e. Guess why :-) WIP 2008-08-03 Michael 'Mickey' Lauer ogsmd: emit 'mode' property for GetStatus() as well as the Status() signal. 2008-08-02 Michael 'Mickey' Lauer ogsmd: [FREESCALE NEPTUNE]: use modem data storage to keep track of roaming status ogsmd: add generic mode-wide data storage pool in modem. This will be used for inter-channel communication ogsmd: improve robustness for GetInfo Note: should use individual commands for maximum effect ogsmd: [FREESCALE NEPTUNE]: fix GetStatus() 2008-08-01 Daniel Willmann ogsmd: Introduce safesplit and start using it safesplit is like split, but doesn't split anything in between quotation signs 2008-08-01 Michael 'Mickey' Lauer add dbus method to configure logging destination at runtime remove private controller API that wasn't to be used add dbus calls to configure debug logger levels at runtime 2008-08-01 Daniel Willmann ogpsd: Use NEEDS_BUSNAMES to claim org.freedesktop.Gypsy subsystem.py: Register extra busnames if requested by the subsystem This looks for NEEDS_BUSNAMES in every file that also has a factory method and tries to claim the busnames listed in there. controller.py: Be honest about the busname that we failed to claim ogpsd: Correct UBX packet definition and improve parsing code Use repr() when logging raw data (emdete) 2008-08-01 Michael 'Mickey' Lauer refactor controller. create subsystem object. Warning: Further refactoring ahead. Might be unstable for a couple of days. 2008-07-31 Michael 'Mickey' Lauer one layer down install /etc files into proper location 2008-07-31 Daniel Willmann Revert "Use absolute path for the config files in setup.py" This reverts commit 253906f561fa8d8dbb24a492bbe18abb4f9d6387 which breaks OE installation as we need to install into relative paths. 2008-07-31 John Lee odeviced: accelerometer: make it read from input dev on demand so it doesn't impact the system performance. * no idle function added * stop sending signal * only read when method called now I need to make it work with gesture. 2008-07-31 Guillaume Chereau Use absolute path for the config files in setup.py Should fix ticket #57 [opreferencesd] Removed opreferencesd/test directory We have a test directory in the root directory now 2008-07-30 Guillaume Chereau Merge branch 'master' of ssh://git@git.freesmartphone.org/framework Added test for opreferencesd 2008-07-29 Michael 'Mickey' Lauer ogsmd: [TI CALYPSO]: silence home zone cell broadcasts while in suspend This should (hopefully) be the last reason for spurious wakeups... 2008-07-29 Daniel Willmann ogpsd: Disable NMEA sentences when starting and enable them again on shutdown 2008-07-29 Michael 'Mickey' Lauer ogsmd: [TI CALYPSO]: fix sending global SIM readyness status. The gory details: PHB is phonebook, SMS is messagebook. RDY is supposed to be sent, after PHB and SMS both being 1, however it's not sent on all devices. EONS is completely undocumented. Due to RDY being unreliable, we wait for PHB and SMS sending availability and then synthesize a global SimReady signal. odeviced: accelerometer: bring back MockAccelerometer and use logging 2008-07-29 Daniel Willmann ogsmd: Fix SimRetrievePhonebook logic SimRetrievePhonebook now fails if SimGetPhonebookInfo returns an error. Previoulsy it would just sit there forever and eventually dbus would time the request out. ogsmd: Fix a bug where there's a HUP on one of the channels ogsmd will try to time.sleep and fail since time is not imported ogpsd: Don't split names that contain a comma in RetrievePhonebook and RetrieveEntry This resulted in a nasty segfault, preventing the phone functionality to work properly. Fixes #53 2008-07-28 Michael 'Mickey' Lauer odeviced: first sketch at audio scenario API 2008-07-28 Daniel Willmann ogpsd: Implement the gypsy control interface. Navit now works with the gypsy source 2008-07-28 Michael 'Mickey' Lauer ogsmd: [TI CALYPSO]: don't mix + and % commands in one command. should fix the bogus-resume-without-reason bug 2008-07-27 Daniel Willmann ogpsd: Make use of the python logging module ogpsd: Some cosmetic changes, don't import unused modules Allow ogpsd to claim the bus name org.freedesktop.Gypsy 2008-07-27 Michael 'Mickey' Lauer ogsmd: [TI CALYPSO] s/log.info/log.debug/ for channel init 2008-07-26 Michael 'Mickey' Lauer odeviced: fix module being found if installed 2008-07-25 Michael 'Mickey' Lauer ogsmd: read minimum/maximum phonebook indices before accessing 2008-07-25 John Lee [odeviced] Added accelerometer support for om-gta02. path /org/freesmartphone/Device/Accelerometer interface org.freesmartphone.Device.Accelerometer signal 'Event' says x, y, z. 100/400 Hz, so be careful. method 'Value' retrieves x, y, z method 'SetSampleRate' sets the sample rate of the accelerometer method 'GetSampleRate' gets the sample rate of the accelerometer 2008-07-25 Michael 'Mickey' Lauer odeviced: cleanup some plugins and bump version number odeviced: SetState now works for all states, no special case handling necessary. Note though that applications may depend on state transitions, so be careful with setting non-neighbour states. odeviced: use child loggers odeviced: convert input plugin to use the generic asyncworker odeviced: the audio module now uses the generic asyncworker 2008-07-24 Michael 'Mickey' Lauer start python/dbus/glib pattern library which will become standalone later on. first element: AsyncWorker which will be used in odeviced/Input and odeviced/Audio plugins. fix a bunch of logger calls mention logging conversion in ChangeLog ogsmd: numberToPhonebookTuple() now handles unicode strings as well ogsmd: fix dumb typo ogsmd: decode text to string before handing it to the modem ogsmd: add unicodeToString() ogsmd: reenable some of the supressed debug log output and fix repr() issues ogsmd: [TI CALYPSO]: yank unsolicited messages from non-unsolicited channels 2008-07-24 Jan Luebbe ogsmd: [SIERRA] fix the channel to use the correct parent class 2008-07-24 Guillaume Chereau Use of python logging module 2008-07-23 Guillaume Chereau Added Activate method to Phone.Call Added an 'Incoming' signal 2008-07-22 Michael 'Mickey' Lauer ogsmd: more tweaks to home zone support ogsmd: [SIERRA] catch up with new style for init sequences ogsmd: finished O2/Genion (who else?) home zone support: * [METHOD] org.freesmartphone.GSM.HZ.GetHomeZoneStatus() -> s:name * [SIGNAL] org.freesmartphone.GSM.HZ.HomeZoneStatus( s:name ) 2008-07-22 Jan Luebbe ogsmd: add some message book examples from the sierra wireless modem and allow number type 208 ogsmd: [SIERRA] add support for the Sierra Wireless UMTS modem 2008-07-22 Michael 'Mickey' Lauer ogsmd: [TI CALYPSO]: restore init commands for unsolicited channel (fixes a cut'n'paste error) 2008-07-21 Michael 'Mickey' Lauer ogsmd: implement org.freesmartphone.GSM.SIM.SendStoredMessage(i:index) -> () ogsmd: make sure SimReady() and AuthStatus() signals are sent whenever the status can change. NB: Fix channel initialization from copy'n'paste error. ogsmd: even more robustness ogsmd: make GetHomeZones() a bit more robust against empty answers from SIM ogsmd: send auth code signal also after delivering a PUK ogsmd: some more ISUP release causes ogsmd: [FREESCALE NEPTUNE]: Neptune does not implement CREG. ogsmd: implement org.freesmartphone.GSM.SIM.GetAuthCodeRequired() -> b and org.freesmartphone.GSM.SIM.SetAuthCodeRequired( b:enable, s:pin ) -> () 2008-07-21 Daniel Willmann ogpsd: Try to claim original Gypsy busname org.freedesktop.Gypsy With this ogpsd works with the standard gypsy libraries. ogpsd: Implemented most of the Gypsy interface 2008-07-20 Michael 'Mickey' Lauer ogsmd: add abstract modem channel implementic some basic stuff helpful for derived channels ogsmd: add org.freesmartphone.GSM.SIM.{Messagebook|Phonebook}Ready signals and associate Get methods. ogsmd: add ucs2_hex_to_string converter 2008-07-19 Michael 'Mickey' Lauer add some new things to TODO and ti_calypso specific modem information odeviced: implemented org/freesmartphone/Device/Audio serving interface org.freesmartphone.Device.Audio: * PlaySound( s ) * StopSound( s ) * StopAllSounds() 2008-07-18 Daniel Willmann ogpsd: Disable sending of time messages and only request them on demand ogpsd: Implement Gypsy DBUS Signals and Methods Accuracy, Position There's also org.freesmartphone.GPS.{GetTime,TimeChanged} as Gypsy doesn't seem to provide any appropriate interface 2008-07-18 Jan Luebbe ousaged: control ogpsd via ousaged ogpsd: rename interface to org.freesmartphone.GPS ogpsd: GTA02Device uses UBX, configure after poweron 2008-07-18 Daniel Willmann ogpsd: Enable important NAV messages ogpsd: Implement message handling 2008-07-18 Michael 'Mickey' Lauer odeviced: add first sketch of audio object complying to org.freesmartphone.Device.Audio 2008-07-18 Jan Luebbe ogsmd: fix two typos in SimDeleteEntry 2008-07-17 Daniel Willmann opgsd: Support sending of variable length messages 2008-07-17 Michael 'Mickey' Lauer ogsmd: [TI CALYPSO]: set sidetone to minimum odeviced: (input) read button/switches configuration from file, no longer hardcoding buttons for the specific device 2008-07-17 Daniel Willmann ogpsd: Implement parsing of variable length messages 2008-07-17 Michael 'Mickey' Lauer odeviced: allow specifying whether holding an input entity needs to be reported ogsmd: [TI CALYPSO]: reorder init messages, need to add callbacks for when auth status changed successfully and registration status changed enhance config parser and fail with a clear error message if we can't claim one of the bus names 2008-07-17 Daniel Willmann ogpsd: Implement more UBX messages 2008-07-17 Guillaume Chereau Minor changes More tests ophoned works better now 2008-07-16 Michael 'Mickey' Lauer ogsmd: use +COPS with two parameters, works on more platforms ogsmd: rename openezx modem to freescale_neptune ogsmd: [OPENEZX] more call handling tweaks ogsmd: [OPENEZX] use ThrowStuffAwayParser for MiscChannel until we find out how to supress unsolicited responses within request/response processes ogsmd: implement ThrowStuffAwayParser that just does that :D ogsmd: allow channels to install their own parser ogsmd: [OPENEZX] add basic call handling ogsmd: allow unsolicited message to also begin with * 'R'(ING), * 'C'(ONNECT), and * 'N'(O CARRIER) in ogsmd.gsm.DelegateChannel. 2008-07-16 Jan Luebbe ogsmd: fix number type parsing 2008-07-16 Michael 'Mickey' Lauer ogsmd: start with ezx samples add directory for implementation docs ogsmd: add 'name' parameter as result in org.freesmartphone.GSM.SIM.GetHomeZones() -> a(siii) 2008-07-16 Guillaume Chereau Merge branch 'master' of ssh://git@git.freesmartphone.org/framework Moved test.py into test directory 2008-07-16 Michael 'Mickey' Lauer ogsmd: add (C) indicating the original source of the channel initialisation and overall concept 2008-07-15 Michael 'Mickey' Lauer ogsmd: CallCancelTest now works use option parser for tests ogsmd: fix some bugs found by adding more testing code: * CallListCalls needs to derive from MiscMediator * [TI CALYPSO] ReleaseAll was not implemented ogsmd: Improve stability of GetStatus() tests: add test for ogsmd. NOTE: should later be enhance to feature different other subsystems as well etc. 2008-07-15 Guillaume Chereau More tests Merge branch 'master' of ssh://git@git.freesmartphone.org/framework Some minor changes 2008-07-15 Michael 'Mickey' Lauer ogsmd: update ChangeLog ogsmd: make the new parser more forgiving wrt. spec violations ogsmd: [TI CALYPSO]: enable cell broadcasts for home zone ogsmd: add org.freesmartphone.GSM.SIM.GetHomeZones() -> a(iii), retrieves home zone coordinates in Gauss-Krueger format 2008-07-15 Daniel Willmann ogpsd: Configure GPS to output NAV-POSLLH, NAV-POSUTM and NAV-TIMEUTC Changed message identificaion to class-id pairs because the numbers used for IDs are not unique. 2008-07-14 Michael 'Mickey' Lauer dbus configuration: fix framework object path. patch by Sudarshan S, thanks. ogsmd: increase +CFUN timeout a bit ogsmd: more tweaks to the new parser, still disabled by default though ogsmd: add first bits of PDU handling, i.e. packed 7 bit decoding (necessary for CB PDU mode) ogsmd: implement the first bits of incoming cell broadcasts: org.freesmartphone.GSM.PDP.CB.IncomingMessage( channel, data ) Warning: This only works with the new experimental parser which you need to explicitly enable atm. (in parser.py) 2008-07-14 Guillaume Chereau Added global test script 2008-07-14 Daniel Willmann ogpsd: Add more message formats, allow sending of messages 2008-07-13 Jan Luebbe ogsmd: fix SimpleLowlevelAtParser to wrap a unsolicited response line in a list This makes it compatible to StateBasedLowlevelAtParser 2008-07-13 Daniel Willmann ogpsd: Add all the remaining UBX IDs 2008-07-13 Michael 'Mickey' Lauer ogsmd: org.freesmartphone.GSM.SIM.RetrieveMessagebook: validate parameters ogsmd: ok, so my basic low-level parser is out of its depth now that we want to deal with multiline unsolicited responses (+CBM and friends)... So, here's an experimental state-based parser for lowlevel AT parsing. Disable by default until I have more time to test. Until then, +CBM will not be completed. ogsmd: [TI CALYPSO]: revert last change with was completely bogus ogsmd: [TI CALYPSO]: fix suspend/resume commands for channel 2008-07-12 Michael 'Mickey' Lauer update ChangeLog ogsmd: implement org.freesmartphone.GSM.SIM.Send{Generic|Restricted}SimCommand(...) ogsmd: repair damage done by previous commit ogsmd: refactor unsolicited response delegation capability into dedicated channel class in gsm core. ogsmd: * rename Test API to Debug API and enhance it (List virtual channels + Inject arbitrary strings into a virtual channel) * implement org.freesmartphone.GSM.CB.{Get|Set}CellBroadcastSubscriptions 2008-07-11 Michael 'Mickey' Lauer ogsmd: implement org.freesmartphone.GSM.SIM.GetInfo() -> a{sv}. ogsmd: [MOTOROLA EZX] add more docs ogsmd: [OPENEZX] add unsolicited channel for /dev/mux0 and parse +CIEV as well as the proprietary +EOPER 2008-07-11 Daniel Willmann ogpsd: Add a GTA02Device which works on GTA02. SetPower method will set the power via odeviced. ogpsd: Use frameworkd.conf for GPS device configuration 2008-07-10 Daniel Willmann ogpsd: Port UBX parsing code from ruby over to python. Support for AID-ALM and AID-EPH packets and generic UBX parsing is done. 2008-07-10 Michael 'Mickey' Lauer ogsmd: [TI CALYPSO] add support for org.freesmartphone.GSM.HoldActive(), i.e. holding a call without activating any waiting or other held call. See fso ticket #15 odeviced: fix LOG_ERROR typo in input.py. Patch by Tim Niemeyer. ogsmd: add transferring a call (call deflection service) via org.freesmartphone.GSM.Call.Transfer(s:number) -> () NOTE: does not work at all on TI Calypso, possibly/hopefully other modems ;) add ogpsd section to framework dbus configuration ogsmd: implement org.freesmartphone.GSM.Call.SendDtmf(tones) -> () ogsmd: [TI CALYPSO] fix lowlevel channel init if modem is not responding ogsmd: implement org.freesmartphone.GSM.ListCalls 2008-07-09 Michael 'Mickey' Lauer tools: restore cli-framework to not showing signals. We will have to rewrite the interactive console to make it safe. tools: spice up the interactive console clean up update ChangeLog ogsmd: SIM.GetMessagebookInfo: don't try to parse a result if we got an error [OPENEZX] remove low level init and add custom mediator for SIM.SendAuthCode ogsmd: refactor ogsmd.gsm.channel and fully document 2008-07-09 Guillaume Chereau Open log before importing controller 2008-07-08 Michael 'Mickey' Lauer ogsmd: add skeleton for Motorola EZX modem support, see http://www.openezx.org ogsmd: refactor MUXing knowledge out of gsm.VirtualChannel into the specific modems as factory function 'pathfactory'. ogsmd: implement org.freesmartphone.GSM.Network.{Set|Get}CallingIdentification ogsmd: implement remaining bits of org.freesmartphone.GSM.PDP: * ListAvailableGprsClasses() -> as * GetCurrentGprsClass() -> s * SetCurrentGprsClass(s) 2008-07-06 Michael 'Mickey' Lauer ophoned: [TI CALYPSO] use more detailed GPRS event reporting 2008-07-04 Daniel Willmann ogpsd: Recover from errors in nmea parser ogpsd: Initial support for parsing UBX packets ogpsd: Fix parsing of NMEA sentences. We now send out PositionChanged and AccuracyChanged signals. Warning, the parser still makes some weird assumptions like we always have a fix... 2008-07-03 Michael 'Mickey' Lauer ogsmd: implement org.freesmartphone.GSM.Network.EnableCallForwarding( reason, class, number, timeout ) -> () ogsmd: implement org.freesmartphone.GSM.Network.DisableCallForwarding( reason, class ) -> () opreferencesd: fix typo 2008-07-03 Daniel Willmann ogpsd: Commit first version. Not quite working yet 2008-07-03 Michael 'Mickey' Lauer ogsmd: implement org.freesmartphone.Network.GetCallForwarding() tools: fix more bus names in cli-framework tools: catch up with renamed subsystem 2008-07-03 Guillaume Chereau Created ophoned service Added new ophoned service 2008-07-03 Michael 'Mickey' Lauer add introspection parser (taken from D-Feet) in preparation of FSO ticket #2 2008-07-02 Michael 'Mickey' Lauer move cli-framework into tools directory explain how to configure the IdleNotifier ophoned: rename subsystem into ogsmd. See ChangeLog for reasoning. 2008-07-01 Jan Luebbe odeviced: also change PowerSupplyApm to export GetEnergyPercentage instead of GetChargingPercentage oeventd: convert the ledAction to a VibratorReceiver 2008-06-30 Michael 'Mickey' Lauer ophoned: fix misc. errors due to testing by someone else than the author ;) Thanks Charlie! * add dbus error org.freesmartphone.GSM.SIM.InvalidIndex * Invalid Parameters in StoreMessage now recognized * teach parser to deal with '+EXT ERROR' specs * fix bogus AT command and typo in SimGetPhonebookInfo install examples into proper directory on target 2008-06-30 Jan Luebbe Fix copy&pase error in documentation 2008-06-30 Michael 'Mickey' Lauer examples/gsm-log-data.py: add automatic call answering to example examples/gsm-log-data.py: make it work if your SIM card is not PIN protected add first usage example ophoned: don't send raw signal quality, convert it to percent 2008-06-30 Guillaume Chereau Added profiles conf file Configuration files synchronised after setting attribute Some bugs fixed 2008-06-29 Michael 'Mickey' Lauer ophoned: implement org.freesmartphone.GSM.Network.GetSignalStrength ophoned: enable proprietary unsolicited signal strength code on TI Calypso and send org.freesmartphone.GSM.Network.SignalStrength accordingly ophoned: change signature of org.freesmartphone.GSM.Network.[Get]Status to conform to OTAPI 2008-06-28 Michael 'Mickey' Lauer ophoned: org.freesmartphone.GSM.PDP.[Dea|A]ctivateContext() now works, honoring APN only for now. ophoned: establish ppp context with modem specific ppp options, correctly catch subprocess exit 2008-06-27 Michael 'Mickey' Lauer rename console to cli-framework and adjust setup.py ophoned: add preliminary handling of org.freesmartphone.PDP.{ActivateContext|DeactivateContext} ophoned: refactor abstract call handling and pdp handling into own modem-specific module document bus name changes in ChangeLog and adjust oeventd to match it rename framework to console 2008-06-26 Jan Luebbe ophoned: remove duplicated class NetworkRegisterWithProvider ophoned: running the lowlevel init stub is always successful framework: rename the command line interface to work around import errors framework: add a generic interactive console controller: use a specific dbus interface to work with our current dbus security policy 2008-06-26 Michael 'Mickey' Lauer framework: remove last bits of TI Calypso specific channel handling out VirtualChannel and put it into CalypsoModemChannel. VirtualChannel is now completely generic (*phew*) 2008-06-26 Jan Luebbe ophoned: add support for remote hold notification 2008-06-26 Michael 'Mickey' Lauer ophoned: prepare for adding the new dbus API: org.freesmartphone.PDP.* 2008-06-25 Michael 'Mickey' Lauer odeviced: fix bug in info module 2008-06-25 Guillaume Chereau Merged preferences service into framework 2008-06-24 Michael 'Mickey' Lauer odeviced: add info object controller: move quering objects out of odeviced and unconditionally into the framework 2008-06-23 Michael 'Mickey' Lauer README++ add ChangeLog for user-visible changes odeviced/main: API change: List -> ListObjectsByInterface odeviced/kernel26: change GetChargingPercentage to GetEnergyPercentage. Patch by Sudharshan S, thanks! controller: allow choosing which subsystems to launch via command line odeviced/main: add GetCpuInfo method, add wildcard support to List kernel26: cosmetics controller: show registered object paths sorted and with the interface they implement 2008-06-22 Jan Luebbe Use the config file to select the modemtype Allow overriding config options form the commandline 2008-06-22 Michael 'Mickey' Lauer register one busname for every subsystem, fail gracefully, if one module raises an exception during factory() allow uninstalled startup 2008-06-22 Jan Luebbe Use timeout_add_seconds from gobject 2008-06-22 Michael 'Mickey' Lauer remove more path hacks remove some more sys.path hacks 2008-06-22 Michael 'Mickey' Lauer fix compiling the wireless python extension module 2008-06-21 Daniel Willmann python-ophoned: Fix SimUnlock method invocation 2008-06-21 Michael 'Mickey' Lauer fix bus names and object paths for ophoned ophoned can be imported now, need to adjust busnames, object paths, and interfaces 2008-06-21 Jan Luebbe Get rid of the modules subdirectories Integrate some of old-opreferencesd Integrate some of old-ophoned 2008-06-21 Michael 'Mickey' Lauer log registered objects at end of startup simplify logic to find subsystem directory 2008-06-21 Jan Luebbe Integrate some of old-ousaged Use __import__ correctly 2008-06-21 Michael 'Mickey' Lauer stop sys.path pollution 2008-06-21 Jan Luebbe Integrate some of old-oeventd Don't use the environment for the current directory 2008-06-21 Michael 'Mickey' Lauer fix setup to autoinclude all paths 2008-06-21 Jan Luebbe Fix my email address 2008-06-21 Michael 'Mickey' Lauer change controller logic to detect subsystems and their modules continue reorg first batch of repository reorganization after merging individual device daemons into frameworkd 2008-06-20 Jan Luebbe Merge branch 'ousaged' Merge branch 'opreferencesd' Merge branch 'ophoned' Merge branch 'oeventd' Merge branch 'odeviced' Move everything to old-ousaged Move everything to old-opreferencesd Move everything to old-ophoned Move everything to old-oeventd Move everything to old-odeviced Initial commit 2008-06-19 Michael 'Mickey' Lauer python-ophoned: fix time between IDLE_PRELOCK and LOCK 2008-06-17 Michael 'Mickey' Lauer Merge branch 'master' of git@git.freesmartphone.org:python-odeviced python-odeviced: increase default timeouts for idlenotifier 2008-06-17 Guillaume Chereau Added a function to generate the DBUS API from the sources Added support for dictionary parameters 2008-06-17 Michael 'Mickey' Lauer python-oeventd: revamp gstreamer control to work around increasing amount of latency 2008-06-16 Jan Luebbe python-odeviced: Allow the idlenotifier to be reset via dbus 2008-06-16 Guillaume Chereau Added TODO file Created a small test application using gtk 2008-06-16 Michael 'Mickey' Lauer python-ophoned: add suspend/resume to ti_calypso specific modem abstraction 2008-06-15 Michael 'Mickey' Lauer python-ophoned: add basic infrastructure for suspending and resuming the phone server, this includes individually suspending/resuming every Channel instance, hence it is highly modem device specific (think unsolicited responses) 2008-06-14 Michael 'Mickey' Lauer python-odeviced: hardcode AUX and POWER button for Milestone 1. Read from configuration afterwards Merge branch 'master' of git@git.freesmartphone.org:python-odeviced python-odeviced: add first version if input module This module gives dbus signals for certain key presses. Use this for tracking things like headphone insertion, charger insertion, AUX key held for n seconds, ... 2008-06-12 Daniel Willmann odeviced: Removed spurious /tmp in apm path, add support for sysfs power_supply capacity node Merge branch 'master' of ssh://git@git.freesmartphone.org/python-ophoned python-ophoned: Reorder org.freesmartphone.GSM.Network.Status parameters to reflect correct usage 2008-06-12 Michael 'Mickey' Lauer python-oeventd: use different hardcoded ringtone for upcoming milestone 1 release python-oeventd: construct pipeline manually instead of using playbin 2008-06-12 Jan Luebbe python-ophoned: fix parameter names for ti_calypso 2008-06-12 Guillaume Chereau Initial import 2008-06-11 Jan Luebbe python-ophoned: use the correct module when installed python-ophoned: ship the modem abstractions 2008-06-11 Michael 'Mickey' Lauer python-ophoned: add todo 2008-06-10 Michael 'Mickey' Lauer python-ophoned: fix numbering in state machine, make ti_calypso the default (for now) python-ophoned: revamp call handling to use a full fledged state machine Q: Do we get a dedicated %CPI for hold events or do we need to rely solely on CCWA/CCLD? 2008-06-07 Jan Luebbe Fix signal setup if ophoned is not yet ready 2008-06-06 Michael 'Mickey' Lauer python-ophoned: fix setup python-ophoned: implement basic multiparty handling (no conference yet). needs mucho testing! 2008-06-05 Michael 'Mickey' Lauer python-ophoned: implement TI Calypso specific call handling 2008-06-04 Michael 'Mickey' Lauer python-ophoned: add skeleton for TI Calypso modem python-ophoned: add skeleton for 'singleline' modem abstraction python-ophoned: refactor code and prepare for different modem types, including, but overriding the standard behaviour 2008-06-03 Jan Luebbe Simple support for ringing Handle audio scenarios and vibrator 2008-06-03 Michael 'Mickey' Lauer python-ophoned: strip " from CLIP to peer in CallStatus signal 2008-06-03 Jan Luebbe Fix some leftovers from odeviced Fix some leftovers from odeviced 2008-06-01 Michael 'Mickey' Lauer python-ophoned: indicate incoming SMS 2008-05-30 Michael 'Mickey' Lauer ophoned: refactor yield support into own mixin class AbstractYieldSupport 2008-05-28 Michael 'Mickey' Lauer python-ophoned: cancelling a command now works, cancelling outgoing calls work, add yield-magic stub for flow-control stuff 2008-05-27 Michael 'Mickey' Lauer python-ophoned: add CLIP reporting, fix remote hangup timeout, add more InternalErrors instead of asserting/traceback on server side 2008-05-26 Jan Luebbe Ship the modules Extend manager and receiver python-ophoned: Use the interface name from the specs 2008-05-23 Michael 'Mickey' Lauer python-ophoned: incoming calls OK, rejecting calls OK, hanging up OK. NOTE: ignoring index-parameter for now, not dealing with multiple calls. 2008-05-22 Michael 'Mickey' Lauer python-ophoned: remove first idea of Call abstraction, implement 2nd idea. outgoing calls looking good now. 2008-05-21 Michael 'Mickey' Lauer python-ophoned: add more error specifications, prepare for command cancelling 2008-05-20 Jan Luebbe Create basic classes 2008-05-19 Jan Luebbe Clean up leftovers from ousaged 2008-05-19 Michael 'Mickey' Lauer python-ophoned: startup fix python-ophoned: [TI CALYPOS] refactor wakeup logic into VirtualChannel base class (where it belongs). python-ophoned: [TI CALYPSO] no longer hammer the modem to keep it from falling asleep, but rather keep a global communication timestamp and send a wakeup-character if necessary. 2008-05-18 Michael 'Mickey' Lauer python-ophoned: implement missing methods in org.freesmartphone.GSM.SIM python-ophoned: start with implementing call handling - POC only for now 2008-05-17 Michael 'Mickey' Lauer python-ophoned: handle unsolicited message '+CREG', query modem for additional data, then send dbus signal python-ophoned: org.freesmartphone.GSM.Device.GetFeatures() implemented. python-ophoned: fix parser to handle continuation lines ('\r\n> ') necessary for storing and sending SMS org.freesmartphone.GSM.SIM.StoreMessage() now working. Merge branch 'master' of git@git.freesmartphone.org:python-ophoned python-ophoned: start with org.freesmartphone.SIM.StoreMessage. Not working yet, need to improve my parser to handle continuation lines :D 2008-05-16 Jan Luebbe Adapt setup.py from odeviced Clean up sysfs filenames before using them as D-Bus paths Import the correct controller Install the modules directory Fix incorretly named script Correctly declare the encoding 2008-05-16 Michael 'Mickey' Lauer python-ophoned: org.freesmartphone.GSM.SIM.RetrieveMessagebook done. python-ophoned: org.freesmartphone.GSM.SIM.{Get,Set}ServiceCenterNumber() done. python-ophoned: add convenience-method for retrieving the whole SIM phonebook at once :D python-ophoned: add SPECIAL THANKS python-ophoned: more goodies * use seconds as timeout metric * start using regular expressions for parsing * add more of org.freesmartphone.GSM.Network 2008-05-15 Michael 'Mickey' Lauer python-ophoned: add test commands and fix decoding for phonebook entries != ASCII 2008-05-15 Jan Luebbe Support ODeviceD as a backend Use the name instead of the sysfs file for powercontrol-neo Add D-Bus object to list available objects by interface Publish the objects under the uniqe sysfs name Give modules access to the controller object (for communication between modules) 2008-05-15 Michael 'Mickey' Lauer python-ophoned: org.freesmartphone.SIM.RetrieveEntry and .StoreEntry done python-ophoned: org.freesmartphone.GSM.Network.GetStatus done, .ListProviders started 2008-05-14 Michael 'Mickey' Lauer python-ophoned: QueuedVirtualChannel: allow per-request timeout override the channel timeout python-ophoned: org.freesmartphone.GSM.SIM.SendAuthCode and org.freesmartphone.GSM.SIM.AuthStatus implemented. python-ophoned: add TODO python-ophoned: org.freesmartphone.GSM.SIM.GetAuthStatus() done 2008-05-13 Michael 'Mickey' Lauer python-ophoned: remove modem.py python-ophoned: remove old gsm implementation python-ophoned: org.freesmartphone.GSM.Device.SetAntennaPower() done. python-ophoned: refactor keepalive channel, add delegate object for unsolicited messages, always allocate three virtuall channels from MUXer python-ophoned: After massive refactoring, org.freesmartphone.GSM.Device.GetInfo() works again *phew* ;) 2008-05-12 Michael 'Mickey' Lauer python-ophoned: mess around with wakeup, create KeepAliveChannel 2008-05-11 Michael 'Mickey' Lauer python-ophoned: add first mediator class that handles transferring AT responses to Dbus responses 2008-05-10 Michael 'Mickey' Lauer python-ophoned: hook new experimental gsm stuff into ophoned 2008-05-10 Jan Luebbe Start oeventd by reusing ousaged Fix copy and paste problem 2008-05-10 Michael 'Mickey' Lauer python-ophoned: add an incredibly slow lowlevel AT command line parser 2008-05-09 Jan Luebbe Refactor interface Add simple selftest 2008-05-08 Jan Luebbe Add resource policy API (disabled, auto, enabled) 2008-05-07 Michael 'Mickey' Lauer python-ophoned: more fun with serial stuff. NOTE: nc/pty behave differently than the device :/ python-ophoned: wrap head around pygsm and prototype something to learn more about pySerial 2008-05-06 Jan Luebbe Import code 2008-05-06 Michael 'Mickey' Lauer add readme add README add readme 2008-05-05 Michael 'Mickey' Lauer python-ophoned: first combinated function GetInfo now works (minus error and timeout handling) python-ophoned/pygsm: add DumbParser to attention class python-ophoned/pygsm: remove bogus executable bits, add TODO file Revert "Revert "python-ophoned: merge with upstream"" This reverts commit 25ed3a7a26250201c878b70a32d4f589122ac0a6. Revert "python-ophoned: merge with upstream" This reverts commit c9d948a1e05e71da6cdb5eb4a794979998364e9c. python-ophoned: merge with upstream python-ophoned: first call working in asynchronous way * add keepModemAlive to workaround OM bug #1380 * TODO: enhance pygsm to a) allow callbacks on timeout and b) get status back from activate() calls 2008-05-03 Michael 'Mickey' Lauer initial commit of python-ophoned as per freesmartphone.org SVN of rev 295 initial commit of python-odeviced as per freesmartphone.org SVN of rev 295 fso-frameworkd-0.10.1/INSTALL000066400000000000000000000004171174525413000155540ustar00rootroot00000000000000This package is using the Python distutils build system. You need to have the Python development packages and also cython installed. Usually, typing 'python setup.py install' does everything necessary. See http://docs.python.org/inst/inst.html for further information. fso-frameworkd-0.10.1/NEWS000066400000000000000000000000001174525413000152060ustar00rootroot00000000000000fso-frameworkd-0.10.1/README000066400000000000000000000004251174525413000154020ustar00rootroot00000000000000This is the reference implementation of the freesmartphone.org framework APIs See http://www.freesmartphone.org for a general overview See http://trac.freesmartphone.org for tracking issues and project status See http://docs.freesmartphone.org for the official documentation fso-frameworkd-0.10.1/data/000077500000000000000000000000001174525413000154325ustar00rootroot00000000000000fso-frameworkd-0.10.1/data/frameworkd.service000066400000000000000000000002721174525413000211560ustar00rootroot00000000000000[Unit] Description=FSO1 daemon used for opimd After=dbus.socket [Service] Type=dbus BusName=org.freesmartphone.frameworkd ExecStart=/usr/bin/frameworkd [Install] WantedBy=basic.target fso-frameworkd-0.10.1/docs/000077500000000000000000000000001174525413000154515ustar00rootroot00000000000000fso-frameworkd-0.10.1/docs/README000066400000000000000000000002221174525413000163250ustar00rootroot00000000000000Random bits of implementation documentation. The real deal can be found in form of our DBus API specifications at http://www.freesmartphone.org fso-frameworkd-0.10.1/docs/TODO000066400000000000000000000022771174525413000161510ustar00rootroot00000000000000== controller / general == * introduce dbus subsystem or dbus object inter-dependencies and honor the dependency graph on startup * add dbus-introspectiond == odeviced == * add more device-specific modules * get more characteristics from the Display object == oeventd == * put metadata for parser into actions, triggers, and filters, so a) we no longer need to specify the factories in the parser one-by-one, and b) they can be treated as plugins (just toss a new one into a directory and it will be found on next startup) == ousaged == == ogsmd == * make dbus_object a singleton? * multipart SMSes (SMS PDU mode) on the API level * improve yield support * make parsing more straightforward (and automated) * add more regexps * revamp parser to take a number of expected prefixes. This should lead to much more reliable detection of unsolicited responses * move gprs status into org.freesmartphone.GSM.Network.Status? * how to find out when the SIM is truly ready? [Calypso has a signal, others not] * port to vala * ... == opreferencesd == * Add support for tree keys (like : foo/spam) * Add support for dictionaries * in the test dir, add a profile configuration file, that defines the possible profiles fso-frameworkd-0.10.1/docs/ogsmd/000077500000000000000000000000001174525413000165625ustar00rootroot00000000000000fso-frameworkd-0.10.1/docs/ogsmd/cinteron/000077500000000000000000000000001174525413000204035ustar00rootroot00000000000000fso-frameworkd-0.10.1/docs/ogsmd/cinteron/modem000066400000000000000000000002751174525413000214330ustar00rootroot00000000000000* Supports multiple multiplexing modes. Mode 4 extends 07.10 frames. * Default is lowest mode; to activate mode 4, send muxer.TestCommand( "\x04TEMUXVERSION4" ) and wait for the reply. fso-frameworkd-0.10.1/docs/ogsmd/freescale_neptune/000077500000000000000000000000001174525413000222515ustar00rootroot00000000000000fso-frameworkd-0.10.1/docs/ogsmd/freescale_neptune/callreceive.sample000066400000000000000000000054131174525413000257350ustar00rootroot00000000000000-- incoming call -- (20080702.2038.12: got 14 bytes from /dev/mux0: '\r\n+CIEV: 6,1\r\n') (20080702.2038.12: got 14 bytes from /dev/mux1: '\r\n+CIEV: 6,1\r\n') (20080702.2038.12: got 14 bytes from /dev/mux2: '\r\n+CIEV: 6,1\r\n') (20080702.2038.12: got 14 bytes from /dev/mux3: '\r\n+CIEV: 6,1\r\n') (20080702.2038.12: got 14 bytes from /dev/mux4: '\r\n+CIEV: 6,1\r\n') (20080702.2038.12: got 14 bytes from /dev/mux5: '\r\n+CIEV: 6,1\r\n') (20080702.2038.12: got 14 bytes from /dev/mux6: '\r\n+CIEV: 6,1\r\n') (20080702.2038.12: got 14 bytes from /dev/mux7: '\r\n+CIEV: 6,1\r\n') (20080702.2038.13: got 51 bytes from /dev/mux0: '\r\n+CLIN: 0\r\nRING: 1\r\n\r\n+CLIP: "+496968098690",145\r\n') (20080702.2038.18: got 41 bytes from /dev/mux0: '\r\nRING: 1\r\n\r\n+CLIP: "+496968098690",145\r\n') (20080702.2038.23: got 41 bytes from /dev/mux0: '\r\nRING: 1\r\n\r\n+CLIP: "+496968098690",145\r\n') -- hanging up at the other end -- (20080702.2038.26: got 14 bytes from /dev/mux0: '\r\n+CIEV: 6,0\r\n') (20080702.2038.26: got 14 bytes from /dev/mux1: '\r\n+CIEV: 6,0\r\n') (20080702.2038.26: got 14 bytes from /dev/mux2: '\r\n+CIEV: 6,0\r\n') (20080702.2038.26: got 14 bytes from /dev/mux3: '\r\n+CIEV: 6,0\r\n') (20080702.2038.26: got 14 bytes from /dev/mux4: '\r\n+CIEV: 6,0\r\n') (20080702.2038.26: got 14 bytes from /dev/mux5: '\r\n+CIEV: 6,0\r\n') (20080702.2038.26: got 14 bytes from /dev/mux6: '\r\n+CIEV: 6,0\r\n') (20080702.2038.26: got 14 bytes from /dev/mux7: '\r\n+CIEV: 6,0\r\n') (20080702.2038.26: got 16 bytes from /dev/mux0: '\r\n+CESS: 3, 14\r\n') (20080702.2038.27: got 31 bytes from /dev/mux0: '\r\nNO CARRIER: 1\r\n\r\n+CIEV: 5,0\r\n') (20080702.2038.27: got 14 bytes from /dev/mux1: '\r\n+CIEV: 5,0\r\n') (20080702.2038.27: got 14 bytes from /dev/mux2: '\r\n+CIEV: 5,0\r\n') (20080702.2038.27: got 14 bytes from /dev/mux3: '\r\n+CIEV: 5,0\r\n') (20080702.2038.27: got 14 bytes from /dev/mux4: '\r\n+CIEV: 5,0\r\n') (20080702.2038.27: got 14 bytes from /dev/mux5: '\r\n+CIEV: 5,0\r\n') (20080702.2038.27: got 14 bytes from /dev/mux6: '\r\n+CIEV: 5,0\r\n') (20080702.2038.27: got 14 bytes from /dev/mux7: '\r\n+CIEV: 5,0\r\n') (20080702.2038.27: got 14 bytes from /dev/mux1: '\r\n+CIEV: 3,0\r\n') (20080702.2038.27: got 14 bytes from /dev/mux2: '\r\n+CIEV: 3,0\r\n') (20080702.2038.27: got 14 bytes from /dev/mux3: '\r\n+CIEV: 3,0\r\n') (20080702.2038.27: got 14 bytes from /dev/mux4: '\r\n+CIEV: 3,0\r\n') (20080702.2038.27: got 14 bytes from /dev/mux5: '\r\n+CIEV: 3,0\r\n') (20080702.2038.27: got 14 bytes from /dev/mux6: '\r\n+CIEV: 3,0\r\n') (20080702.2038.27: got 14 bytes from /dev/mux7: '\r\n+CIEV: 3,0\r\n') (20080702.2038.27: got 14 bytes from /dev/mux0: '\r\n+CIEV: 1,5\r\n') fso-frameworkd-0.10.1/docs/ogsmd/freescale_neptune/cfun.sample000066400000000000000000000004001174525413000244010ustar00rootroot00000000000000at+cfun=0 OK at+cfun=1 OK +CMSM: 0 +CMPN: 1,2 +EMSR: 0 at+cpin=1,"7728" OK +CMSM: 3 +EHOZ: 0,"" +ESATP: 81 +EMSR: 0 +ESMT: 1 +EFLEX: 99,FE +EFLEX: 100,FF +EFLEX: 101,FF +EFLEX: 102,FF +EFLEX: 103,FB +EFLEX: 104,F7 +EFLEX: 105,FF +ESCR fso-frameworkd-0.10.1/docs/ogsmd/freescale_neptune/modem000066400000000000000000000040461174525413000233010ustar00rootroot00000000000000== Motorola EZX modem specific notes == === Behaviour === * +CFUN=0;+CFUN=1 does _not_ reset the PIN lock; sending these commands too often / too fast confuses the modem. * Giving a valid PIN to unlock starts autoregister (likewise, for an unlocked SIM +CFUN=1 will autoregister) * Modem needs +EPMS? and +EGML=4 before +CGML (and friends) will work. Said commands will only work once after BP initialization though. Afterwards they will return +CMS ERROR 500 (unknown error). * Unregistering is generally frowned upon -- if you query the operator too early after unregistering, you'll get garbage: 2008.07.02 22:19:38 ogsmd DEBUG : got 31 bytes from: '\r\n+COPS: 0,2,"\x10*Zd\x12\x04I\xdc"\r\n\r\nOK\r\n' Without antenna off/on, it doesn't seem to be possible to reregister again * After +CFUN=1 (previously +CFUN=0), the modem sends quite a bunch of URCs, e.g. +CMSM: 3 and +EMSR: 0, +EFLEX, and much more. * To enable USB charging, you need to issue +USBSTAT=255,1 === GSM standards violations === * Phonenumber format needs '+' in addition to ntype = 145 * +CPIN: PIN entry is +CPIN=%d,"%s". %d being 1 for PIN1 * +CRC: Incoming call alert always RING: %d. +CRC setting is not honored. * +CMGL: format violation, missing a ',' (PDU header) * +CMGR: format violation, missing a ',' (PDU header) * +CSCB: format violation, not accepting any parameters but '0' (all) and '1' (none) * +CMT: format violation, missing a ',' (PDU header) === Proprietary commands and URCs === * +CCTP: 1, "+49xxxxxxxx" indicates an outgoing call (on line 1?) * +CLIN: 0 indicates an incoming call on line 0 * +CMSM: 3 means "SIM unlocked"; +CMSM: 0 means "SIM locked". If the SIM slot is empty, it will not appear at all after +CFUN=1 (before +ESCR) * +EHOZ: 0,"" (homezone indication, homezone name) * +ESATP: 81 (SIM Application Toolkit P?) * +EMSR: 0 ? * +ESMT: 1 ? * +EFLEX: 99,FE ? * +ESCR ? (Sim Card Ready?) * See http://wiki.openezx.org/AT_commands -- we might add more commands here as well, if ogsmd is using them. fso-frameworkd-0.10.1/docs/ogsmd/freescale_neptune/pin.sample000066400000000000000000000025001174525413000242370ustar00rootroot00000000000000giving command +CPIN=1,"1234" (20080702.2036.37: got 6 bytes from /dev/mux0: '\r\nOK\r\n') (20080702.2036.43: got 13 bytes from /dev/mux0: ' PIN"\r\n\r\nOK\r\n') (20080702.2036.56: got 2 bytes from /dev/mux0: '\r\n') (20080702.2036.58: got 12 bytes from /dev/mux4: '\r\n+CMSM: 3\r\n') (20080702.2036.58: got 15 bytes from /dev/mux4: '\r\n+EHOZ: 0,""\r\n') (20080702.2036.58: got 14 bytes from /dev/mux4: '\r\n+ESATP: 81\r\n') (20080702.2036.58: got 12 bytes from /dev/mux4: '\r\n+EMSR: 0\r\n') (20080702.2036.59: got 12 bytes from /dev/mux0: '\n+EOPER: 0\r\n') (20080702.2036.59: got 11 bytes from /dev/mux0: 'CIEV: 1,0\r\n') (20080702.2036.59: got 137 bytes from /dev/mux4: '\r\n+ESMT: 1\r\n\r\n+EFLEX: 99,FE\r\n\r\n+EFLEX: 100,FF\r\n\r\n+EFLEX: 101,FF\r\n\r\n+EFLEX: 102,FF\r\n\r\n+EFLEX: 103,FB\r\n\r\n+EFLEX: 104,F7\r\n\r\n+EFLEX: 105,FF\r\n') (20080702.2036.59: got 9 bytes from /dev/mux4: '\r\n+ESCR\r\n') (20080702.2037.03: got 7 bytes from /dev/mux0: ': 1,0\r\n') (20080702.2037.03: got 47 bytes from /dev/mux0: 'CIEV: 1,5\r\n\r\n+CIEV: 2,1\r\n\r\n+EOPER: 5,"262-07"\r\n') (20080702.2037.03: got 12 bytes from /dev/mux0: '+CIEV: 1,5\r\n') (20080702.2037.03: got 12 bytes from /dev/mux0: '+CIEV: 7,0\r\n') (20080702.2037.04: got 11 bytes from /dev/mux0: 'CIEV: 1,5\r\n') (20080702.2037.07: got 11 bytes from /dev/mux0: 'CIEV: 1,5\r\n') fso-frameworkd-0.10.1/docs/ogsmd/gnufiish/000077500000000000000000000000001174525413000203765ustar00rootroot00000000000000fso-frameworkd-0.10.1/docs/ogsmd/gnufiish/commands000066400000000000000000000013231174525413000221210ustar00rootroot00000000000000&C=? &D=? &F=? &W=? *EDEBUGMUX *EDEBUGMUX=? *EIAAUR= *EIAAUR=? *EIAAUW= *EIAAUW=? *EIAC= *EIAC=? *EIAC? *EIACSR= *EIA CSR=? *EIACSW= *EIACSW=? *EIAD= *EIAD=? *EIADNSV6R= *EIADNSV6R=? *EIADNSV6W= *EPPSD=? *EIAIPCPR= *EIAIPCPR=? *EIAIPCPW= *EIAIPCPW=? *EIALCPR= *EIALCPR=? *EIALCPW= *EIALCPW=? *EIAPSR= *EIAPSR=? *EIAPSSR= *EIAPSSR=? *EIAPSSW= *EIAPSSW=? *EIAPSW= *EIAPSW=? *EIAR= *EIAR=? *EIARUTD= *EIARUTD=? *EIARUTR= *EIARUTR=? *EIARUTW= *EIARUTW=? *EIAW= *EIAW=? *EINA *EINA=? *ELIN= *ELIN=? *ELIN? *EPPSD *ESPSD *ESPSD=? *ETUP *ETUP=? *EUPLINK *EVA *EVD *EVH *EPEE *EPDR *EACS *ECAM *EIBA *EKSE *EPBR *EPBW *EPEE *EPHD *EREG *ESCB *ESIM *ESOM *STKC *STKE *STKR +BLDN ZZZZ *EPEV *EPDR *EREG *CPI *EC" *STKI *STK *STKN fso-frameworkd-0.10.1/docs/ogsmd/lessons000066400000000000000000000037131174525413000201770ustar00rootroot00000000000000======================================================== = Lessons learned from the implementation of ogsmd 1.x = ======================================================== === Things to keep === * General architecture of - modem class, creating a bunch of - channels, optionally delegating to - unsolicited handlers. - mediators encapsulating the dbus command specifics. * Separation of responsibilities. === Things to change === * Delegate channel i/o to a dedicated transport delegate in order to simplify talking to a simulator or a multiplexer library. * On systems with optional multiplexing, do not use PTYs, but rather directly talk (using the multiplexer as a library). * Per-command timeouts do not make sense; rather maintain a reasonable channel timeout (say 90 seconds or so) that gets reset on every incoming data. When we are actually waiting for an answer and it triggers, try to escalate resetting the parser and/or (transparently) relaunching the last command. [done in frameworkd] * Per-command retries _might_ make sense. * Dragging an error callback around for every command does not make sense, if the error refers to channel errors (such as a timeout), as opposed to an actual command response error (which makes sense). * If there's no reliable way to detect unsolicited responses (such as a physical or a virtual RING line) _and_ the modem does not guarantee delaying them between a request and a response, then the low level parser needs to be fed a list of valid prefixes for every command. [done in frameworkd] * It _might_ make sense to define every AT command as a struct, including: * a list of valid prefixes, * a regular expression that groups values into elements, * a mapping of values to types. This could reduce quite some boilerplate code. * Try to treat channels equally. Send commands in a round-robin fashion (except those for which there is per-channel state) to improve latency and throughput. fso-frameworkd-0.10.1/docs/ogsmd/qualcomm_msm/000077500000000000000000000000001174525413000212545ustar00rootroot00000000000000fso-frameworkd-0.10.1/docs/ogsmd/qualcomm_msm/commands000066400000000000000000000027761174525413000230140ustar00rootroot00000000000000Qualcomm MSM proprietary commands: ================================= +CGDSCONT extension to +cgdcont +CGTFT +CGEQREQ extension to +cgqreq +CGEQMIN extension to +cgqmin +CGSMS +CSAS +CRES +CSMP +FDD +FAR +FCL +FIT +ES +ESA +CVHU +HSDPA +SIMVOLT +TTYMODE +EONS +pref_UARFCN +HTCmaskW1 +VKK_SIM_EXPIRED +ISIMS +ISIMA +ISIMR +QEXTONE +HTCAGPS +CUS_SBM +CUS_EM +MEMFULL +CPIN send PIN1/PUK1 +CPINC get pin counter +CPIN2 send PIN2/PUK2 +CMEC +CKPD +CGCMOD +CGAATT +BANDSET manually select GSM band +COPSLAC +ALS alternate line select +ODEN +HTCPDPIDLE +CHZ +CPPP +CALLREJECT send 'busy' signal without dropping other calls +RXD +EQ +MAXPWR +L1OPT +ENCSQ +HTCCNIV enable extended operator name report URC +QBANDCAP +PHSM +HTCNV +HTCENS +CPLS +HTCCTZR extension to +CTZR +CDIP +SIMTYPE +CPBC +CSDN +GIMG +GEVL +RADIOVER +CCSQ +DMODE $QCCNMI $QCCLR $QCDMG $QCDMR $QCDNSP $QCDNSS $QCTER $QCSLOT $QCPINSTAT $QCPDPP $QCPDPLT $QCPWRDN $QCSIMSTAT $QCDGEN $BREW $FTM $GSM $GPRS $AMR $3G_RESEL_PARA $2G_RESEL_PARA $3G_RESEL_STATUS $2G_RESEL_STATUS $WCDMA $L3_RRC_SIGNAL $PRACH_RACH_INFO $3G_DCH_STATUS_A $3G_DCH_STATUS_M $3G_DCH_STATUS_D $3G_NEIGHBOR_ST $3G_NEIGHBOR_ST_2 $3G_BLER_STATUS $3G_DL_TF_STATUS $3G_DL_TF_STATUS_2 $3G_DL_TF_STATUS_3 $3G_UL_TF_STATUS $3G_DL_RLC_AM_ST $3G_UL_RLC_AM_ST $HSDPA_CQI_STATUS $3G_AGC_STATUS $3G_RESEL_EVENT $3G_DL_TF_COMB_ST $3G_DL_TF_COMB_ST_2 $3G_DL_TF_COMB_ST_3 $HSUPA_STATUS $VTS $QCSYSMODE fso-frameworkd-0.10.1/docs/ogsmd/qualcomm_msm/modem000066400000000000000000000007261174525413000223050ustar00rootroot00000000000000== Qualcomm MSM modem specific notes == === Behaviour === * Modem starts out with V0, ATZ resets to V0, be careful with your parser. * Modem responds with +CME ERROR 10 (SIM not inserted), if you query for PIN before +CFUN=1 * Range for +CLVL is 0-5 === GSM standards violations === * Even after setting V1, +CME ERRORS are not correctly terminated with \r\n, but only \r === Proprietary commands and URCs === * +PB_READY: indicating the SIM phonebook is ready fso-frameworkd-0.10.1/docs/ogsmd/qualcomm_msm/urcs000066400000000000000000000004631174525413000221560ustar00rootroot00000000000000Qualcomm MSM Proprietary URCs ============================= @HTCCSQ: 0010,1 signal strength (0-4) @HTCSID: 4 disconnect reason [WCDMA] Current RRC Status = 0 +GTKI: D03D810301250082028182050C42415345204469656E7374658F0901534D5320496E666F8F0C0253707261636820496E666F8F0B034D756C74696D65646961 fso-frameworkd-0.10.1/docs/ogsmd/samples000066400000000000000000000012061174525413000201500ustar00rootroot00000000000000== Sample responsess == === +CNUM === * +CNUM: ,"0775753xxxxx",129 * +CNUM: "LINE 1",+xxyyzz",145 === +CRSM === * +CRSM: 144,0,1107919471060040340C8494710600010001000524440008929E0000EA6001686F6 D65FFFFFFFFFFFFFFFF0200000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF030000000 0000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF040005224B0008940800159BA40363697479F FFFFFFFFFFFFFFFFFFF * +CRSM: 148,4,00000000000000000000000000000000000000000000 === +CBM === * +CBM: 88 001000DD001133DAED46ABD56AB5186CD668341A8Da46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46tA3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D100 fso-frameworkd-0.10.1/docs/ogsmd/ti_calypso/000077500000000000000000000000001174525413000207305ustar00rootroot00000000000000fso-frameworkd-0.10.1/docs/ogsmd/ti_calypso/commands000066400000000000000000000022021174525413000224500ustar00rootroot00000000000000AT%CSQ enable signal strength URC (%CSQ: ) AT%ALS alternate line select AT%ATR AT%BAND manual GSM band select AT%CACM AT%CAOC AT%CCBS AT%STDR AT%CGAATT AT%CGMM AT%CGREG AT%CNAP AT%CPI AT%COLR AT%CPRIM AT%CTV AT%CUNS AT%NRG AT%SATC configuration for SAT; %SATC=, AT%SATE send SAT envelope command; %SATE=, response will be %SATE: AT%SATR send SAT command response; $SATR= AT%SATT terminate SAT command or session; %SATT= (0=user stopped redialing, 1 end of redialing, 2 user ends session) AT%SNCNT AT%VER AT%CGCLASS AT%CGPCO AT%CGPPP AT%EM AT%EMET AT%EMETS AT%CBHZ AT%CPHS AT%CPNUMS AT%CPALS AT%CPVWI AT%CPOPN AT%CPCFU AT%CPINF AT%CPMB AT%CPRI AT%DATA AT%DINF AT%CLCC AT%DBGINFO AT%VTS AT%CHPL AT%CREG AT%CTZV AT%CNIV AT%PVRF AT%CWUP AT%DAR AT%CSCN AT%RDL AT%RDLB AT%CSTAT AT%CPRSM AT%CHLD AT%SIMIND AT%SECP AT%SECS AT%CSSN AT%CSSD AT%COPS AT%CPMBW AT%CUST AT%SATCC AT%COPN AT%CGEREP AT%CUSCFG AT%CUSDR AT%CPBS AT%PBCF AT%SIMEF AT%EFRSLT AT%CMGMDU AT%CMGL AT%CMGR AT@ST AT@AUL AT@POFF AT@RST AT@SC AT@BAND fso-frameworkd-0.10.1/docs/ogsmd/ti_calypso/modem000066400000000000000000000055631174525413000217650ustar00rootroot00000000000000== TI Calypso specific notes == === MUX Level === * Before entering the multiplex mode, you have to set echo off (ATE0), otherwise you won't get any 07.10 packets in return * No response to test commands === AT Level === * On channel init, we better send \x1a first -- just in case something crashed while we were inside a multiline request command * +CCLK? violates 07.07, it doesn't use %02d, but %d for the individual components. Moreover, +CCLK needs the UTC offset, but doesn't honor it. * +CFUN=1 resets previous +CPIN="abcd" (even with 2nd parameter?) * +CFUN=1 resets +CLVL, so it needs to be sent afterwards * +CFUN=1 always "succeeds" even if it answers with ERROR (SIM PIN Required) * I always receive CME ERROR 32 on +COPS=? until +CREG=1 * +CREG=%d influences both the unsolicited response as well as the solicited response code * Phonebook indices start with 1 (not 0) * If SIM PIN not given, SIM file reading commands (phonebook, messagebook, crm) deliver the slightly misleading * CMS ERROR 310 (=SIM NOT INSERTED), or (yes, that can happen as well, if the PHB is not ready yet) * OK (=empty response) :( Readyness of the PHB is available if CFUN=1 and SMS auth given * If SMS phone number available in messagebook, address name will be automatically inserted * +CIEV supports 'signal' and 'smsfull' and has to be explicitly enabled with: +CMER=2,0,0,1,0 and +CIND=5,1 * +CBM Cell broadcasts only appear on the virtual channel that issued +CNMI the last time. * %CBHZ=1 needs to be issued, otherwise the Calypso will swallow everything but the first Cell Broadcast message. * WARNING: %CBHZ=1 seems to have a very negative effect on power consumption and (believe it or not) voice quality :/ * Cancelling certain commands (ATH for a start) will render a virtual channel unusable. Calypso moves into a CMS ERROR 512 state in that case. You will have to restart the modem once this happens. * Sending +COLP=1 seems to have a negative effect on this issue. DO NOT USE +COLP=1. * Shutting down an ongoing GPRS connection can take ages. Need to better interface with pppd here. * We should issue ATH here (first check whether there is no call ongoing though). * +CLIR is virtual channel-specific (yes, hard to believe), so you need to issue it on the same channel where you issue ATD. * Deep Sleep Mode is broken. If you suffer from permament recamping (+CREG: 0, then +CREG: 1,...), then you need to issue the (completely undocumented) command AT%SLEEP=2 (disable deep sleep). AT%SLEEP=4 enables deep sleep. * Sometimes on outgoing calls %CPI with message type of 9 (setup MO call) wont get sent. For outgoing call progress detection one should rather rely on %CPI with message type 3 (call proceed) * Calypso only checks for flow-control before sending an entire message. === Proprietary commands === * Too many to list here, please see http://wiki.openmoko.org fso-frameworkd-0.10.1/docs/ogsmd/ussd-session000066400000000000000000000042761174525413000211550ustar00rootroot00000000000000root@om-gta02 ~ $ /etc/init.d/frameworkd stop Stopping freesmartphone.org framework daemon: stopped process in pidfile '/var/run/frameworkd.pid' (pid 1412) (done) root@om-gta02 ~ $ mickeyterm -c Using **pending_return in dbus_connection_send_with_reply_setup() without pending_setup is deprecated and strongly discouraged Using **pending_return in dbus_connection_send_with_reply_setup() without pending_setup is deprecated and strongly discouraged Using **pending_return in dbus_connection_send_with_reply_setup() without pending_setup is deprecated and strongly discouraged Using **pending_return in dbus_connection_send_with_reply_setup() without pending_setup is deprecated and strongly discouraged Using **pending_return in dbus_connection_send_with_reply_setup() without pending_setup is deprecated and strongly discouraged <----------- Mickey's Term V2.9.4 @ /dev/pts/1 -----------> AT-Command Interpreter ready OK OK OK OK a OK atd*100# OK +CUSD: 1,"o2 LOOP Men~ 1 Guthabenkonto 2 Guthaben-Verf~gbarkeit 3 Aufladung Guthaben/Pack-to-Go 4 Pack Manager 5 Bonuskonto 6 Bonus-Verf~gbarkeit 7 Tarifinfo 8 Hilfe",15 atd1 OK +CUSD: 0,"Ihr o2 LOOP Guthaben betr{gt: 8.64 EUR. Kostenlos bei o2 Prepaid! Individuelle Tarifberatung und pers|nlicher Service - 017955282",15 OK (at__man beachte die ausbleibende OK meldung nach dem ersten menue, und die "1" im CSUD-reply) ERROR (at_numn mit timeout) EXT: I ERROR atd*100# OK +CUSD: 1,"o2 LOOP Men~ 1 Guthabenkonto 2 Guthaben-Verf~gbarkeit 3 Aufladung Guthaben/Pack-to-Go 4 Pack Manager 5 Bonuskonto 6 Bonus-Verf~gbarkeit 7 Tarifinfo 8 Hilfe",15 +CME ERROR: 100 at__ok, das waren so 30 .. 45sec, dann ckommt CME ERROR ERROR atd*102# OK +CUSD: 0,"Ihr o2 LOOP Guthaben ist bis zum 15.12.09 verf~gbar. ",15 OK atd*104# OK +CUSD: 1,"Packmanager 1 Packstatus 2 Pack buchen 3 Pack abbestellen ",15 atd1 OK +CUSD: 1,"Statusanzeige f~r welches Pack? 1 Internet Pack L 0 Abbruch ",15 atd1 OK +CUSD: 1,"Ihr Pack Internet Pack L ist aktiv und l{uft am 15.07.2009 aus. Das Pack kostet monatlich 25,00 Euro. 1 Detailinfo ",15 atd1 OK +CUSD: 0,"Sie haben noch 5091,13 MB Internet zur Verf~gung. Kostenlos bei o2 Prepaid! Individuelle Tarifberatung und pers|nlicher Service - 017955282",15 OK fso-frameworkd-0.10.1/etc/000077500000000000000000000000001174525413000152745ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/a780/000077500000000000000000000000001174525413000157535ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/a780/frameworkd.conf000066400000000000000000000033521174525413000207660ustar00rootroot00000000000000[frameworkd] # indicates this configuration version, do not change version = 1 # the default log_level, if not specified per subsystem or module # available log levels are: DEBUG, INFO, WARNING, ERROR, CRITICAL log_level = INFO # the global log_destination. Uncomment to enable # available destinations are: stderr, file, syslog log_to = file # if logging to a file, specify the destination log_destination = /var/log/frameworkd.log # persistence format, one of "pickle", "yaml" persist_format = pickle rootdir = ../etc/freesmartphone:/etc/freesmartphone:/usr/etc/freesmartphone # specify how subsystems scan for their plugins, # either "auto" (via filesystem scan) or "config" (via config section check) # the default is "auto" (slow). scantype = auto [input] # keys (copied from Openmoko, need to adjust) report1 = AUX,key,169,1 report2 = POWER,key,116,1 report3 = USB,key,356,0 report4 = HEADSET,switch,2,0 [ogsmd] # A780 has Freescale Neptune LTE modemtype = freescale_neptune [opreferencesd] rootdir = ../etc/freesmartphone/opreferences:/etc/freesmartphone/opreferences:/usr/etc/freesmartphone/opreferences [oeventsd] rules_file = ../etc/freesmartphone/oevents/rules.yaml:/etc/freesmartphone/oevents/rules.yaml:/usr/etc/freesmartphone/oevents/rules.yaml [opimd] contacts_default_backend = SQLite-Contacts messages_default_backend = SQLite-Messages calls_default_backend = SQLite-Calls dates_default_backend = SQLite-Dates notes_default_backend = SQLite-Notes tasks_default_backend = SQLite-Tasks messages_default_folder = Unfiled messages_trash_folder = Trash sim_messages_default_folder = SMS contacts_merging_enabled = 1 rootdir = ../etc/freesmartphone/opim:/etc/freesmartphone/opim:/usr/etc/freesmartphone/opim [ousaged] disable = 1 [testing] disable = 1 fso-frameworkd-0.10.1/etc/crespo/000077500000000000000000000000001174525413000165675ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/crespo/frameworkd.conf000066400000000000000000000030211174525413000215730ustar00rootroot00000000000000# Do note that the order of plugin sections is important. Plugins that depend # on others need to come later, # eg. fsousage.dbus_service AFTER fsousage.lowlevel_kernel26 [frameworkd] # indicates this configuration version, do not change version = 1 # the default log_level, if not specified per subsystem or module # available log levels are: DEBUG, INFO, WARNING, ERROR, CRITICAL log_level = INFO # the global log_destination. Uncomment to enable # available destinations are: stderr, file, syslog log_to = file # if logging to a file, specify the destination log_destination = /var/log/frameworkd.log # persistence format, one of "pickle", "yaml" persist_format = pickle rootdir = ../etc/freesmartphone:/etc/freesmartphone:/usr/etc/freesmartphone # specify how subsystems scan for their plugins, # either "auto" (via filesystem scan) or "config" (via config section check) # the default is "auto" (slow). scantype = auto [odeviced] disable = 1 [ogsmd] disable = 1 [ogpsd] disable = 1 [opreferencesd] rootdir = ../etc/freesmartphone/opreferences:/etc/freesmartphone/opreferences:/usr/etc/freesmartphone/opreferences [opreferencesd.opreferenced] [oeventsd] rules_file = ../etc/freesmartphone/oevents/rules.yaml:/etc/freesmartphone/oevents/rules.yaml:/usr/etc/freesmartphone/oevents/rules.yaml [oeventsd.oeventsd] [opimd] rootdir = ../etc/freesmartphone/opim:/etc/freesmartphone/opim:/usr/etc/freesmartphone/opim [opimd.opimd] [ousaged] disable = 1 [ophoned] disable = 1 [otimed] disable = 1 [onetworkd] disable = 1 [testing] disable = 1 fso-frameworkd-0.10.1/etc/dbus-1/000077500000000000000000000000001174525413000163675ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/dbus-1/system.d/000077500000000000000000000000001174525413000201355ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/dbus-1/system.d/frameworkd.conf000066400000000000000000000063251174525413000231530ustar00rootroot00000000000000 fso-frameworkd-0.10.1/etc/eten-m800/000077500000000000000000000000001174525413000167115ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/eten-m800/frameworkd.conf000066400000000000000000000024211174525413000217200ustar00rootroot00000000000000[frameworkd] # indicates this configuration version, do not change version = 1 # the default log_level, if not specified per subsystem or module # available log levels are: DEBUG, INFO, WARNING, ERROR, CRITICAL log_level = INFO # the global log_destination. Uncomment to enable # available destinations are: stderr, file, syslog log_to = file # if logging to a file, specify the destination log_destination = /var/log/frameworkd.log # persistence format, one of "pickle", "yaml" persist_format = pickle rootdir = ../etc/freesmartphone:/etc/freesmartphone:/usr/etc/freesmartphone # specify how subsystems scan for their plugins, # either "auto" (via filesystem scan) or "config" (via config section check) # the default is "auto" (slow). scantype = auto [odeviced.kernel26] disable = 1 [ogsmd] disable = 1 # specify your modemtype here [ogpsd] device = EtenDevice channel = SerialChannel path = /dev/ttySAC2 [ogpsd.serialchannel] baudrate = 57600 [opreferencesd] rootdir = ../etc/freesmartphone/opreferences:/etc/freesmartphone/opreferences:/usr/etc/freesmartphone/opreferences [oeventsd] rules_file = ../etc/freesmartphone/oevents/rules.yaml:/etc/freesmartphone/oevents/rules.yaml:/usr/etc/freesmartphone/oevents/rules.yaml [opimd] disable = 1 [ousaged] disable = 1 [testing] disable = 1 fso-frameworkd-0.10.1/etc/frameworkd.conf000066400000000000000000000114221174525413000203040ustar00rootroot00000000000000[frameworkd] # indicates this configuration version, do not change version = 1 # the default log_level, if not specified per subsystem or module # available log levels are: DEBUG, INFO, WARNING, ERROR, CRITICAL log_level = INFO # the global log_destination. Uncomment to enable # available destinations are: stderr, file, syslog log_to = stderr # if logging to a file, specify the destination log_destination = /tmp/frameworkd.log # persistence format, one of "pickle", "yaml" persist_format = pickle rootdir = ../etc/freesmartphone:/etc/freesmartphone:/usr/etc/freesmartphone # specify how subsystems scan for their plugins, # either "auto" (via filesystem scan) or "config" (via config section check) # the default is "auto" (slow). scantype = auto # # Subsystem configuration for oeventsd # [odeviced] # set 1 to disable a subsystem or a module disable = 0 [odeviced.accelerometer] disable = 1 [odeviced.audio] # set directory where the alsa audio scenarios are stored scenario_dir = /usr/share/openmoko/scenarios # set default scenario loaded at startup default_scenario = stereoout [odeviced.idlenotifier] # add input nodes to ignore for idle activity ignoreinput = 2,3,4 # configure timeouts (in seconds) here. A value of 0 # means 'never fall into this state' (except programmatically) idle = 10 idle_dim = 20 idle_prelock = 12 lock = 2 suspend = 0 [odeviced.input] # format is ,,, report1 = AUX,key,169,1 report2 = POWER,key,116,1 report3 = CHARGER,key,356,0 report4 = HEADSET,switch,2,0 [odeviced.kernel26] disable = 0 # poll capacity once every 5 minutes # (usually, you do not have to change this) capacity_check_timeout = 300 # set 0 to disable FB_BLANK ioctl to blank framebuffer # (if you have problems on Openmoko GTA02) fb_blank = 1 [odeviced.powercontrol_ibm] disable = 1 [odeviced.powercontrol_neo] disable = 0 # # Subsystem configuration for oeventsd # [oeventsd] log_level = DEBUG disable = 0 [oeventsd.oevents] # # Subsystem configuration for ogspd # [ogpsd] # possible options are NMEADevice, UBXDevice, GTA02Device, EtenDevice device = GTA02Device # possible options are SerialChannel, GllinChannel, UDPChannel, FileChannel channel = SerialChannel # For UDPChannel the path defines the port to listen to path = /dev/ttySAC1 # Threshold of movement, this can reduce the noise when the GPS is stationary or moving slowly # Value is in cm/s (centimeter per second) static_threshold = 10 [ogpsd.factory] # # Subsystem configuration for ogsmd # [ogsmd] disable = 0 # choose your modem type, available types are: ti_calypso, freescale_neptune, singleline, muxed4line, option, ... modemtype = ti_calypso # if you have a ti_calypso, you can choose the deep sleep mode. Valid values are: never, adaptive (default), always ti_calypso_deep_sleep = adaptive # if you have a ti_calypso, you can choose the dsp mode for audio enhancement. Valid values are: # "short-aec": Short Echo Cancellation (max) # "long-aec": Long Echo Cancellation (max) # "long-aec:6db": Long Echo Cancellation (-6db) # "long-aec:12db": Long Echo Cancellation (-12db) # "long-aec:18db": Long Echo Cancellation (-18db) # "nr": Noise Reduction (max) # "nr:6db": Noise Reduction (-6db) # "nr:12db": Noise Reduction (-12db) # "nr:18db": Noise Reduction (-18db) # "aec+nr": Long Echo Cancellation (max) plus Noise Reduction (max) [default] # "none": No audio processing. ti_calypso_dsp_mode = aec+nr # choose your muxer, available types are: gsm0710muxd [default], fso-abyss ti_calypso_muxer = gsm0710muxd # # Subsystem configuration for onetworkd # [onetworkd] [onetworkd.network] # # Subsystem configuration for ophoned # [ophoned] [ophoned.ophoned] # # Subsystem configuration for opimd # [opimd] contacts_default_backend = CSV-Contacts messages_default_backend = SIM-Messages-FSO calls_default_backend = SQLite-Calls dates_default_backend = SQLite-Dates notes_default_backend = SQLite-Notes tasks_default_backend = SQLite-Tasks contacts_merging_enabled = 1 messages_default_folder = Unfiled messages_trash_folder = Trash sim_messages_default_folder = SMS rootdir = ../etc/freesmartphone/opim:/etc/freesmartphone/opim:/usr/etc/freesmartphone/opim [opimd.opimd] # # Subsystem configuration for opreferencesd # [opreferencesd] log_level = DEBUG disable = 0 [opreferencesd.opreferences] # # Subsystem configuration for otimed # [otimed] # a list of time/zone sources to use or NONE timesources = GPS,NTP zonesources = GSM # use an ip address here, otherwise DNS resolution will block ntpserver = 134.169.172.1 [otimed.otimed] # # Subsystem configuration for ousaged # [ousaged] # choose whether resources should be disabled at startup, at shutdown, always (default), or never. sync_resources_with_lifecycle = always [ousaged.generic] [testing] disable = 1 fso-frameworkd-0.10.1/etc/freesmartphone/000077500000000000000000000000001174525413000203165ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/freesmartphone/oevents/000077500000000000000000000000001174525413000220015ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/freesmartphone/oevents/a780/000077500000000000000000000000001174525413000224605ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/freesmartphone/oevents/a780/rules.yaml000066400000000000000000000040521174525413000244770ustar00rootroot00000000000000 # This file is in YAML format (http://www.yaml.org/) # We define a list of rules that will be automatically loaded # When we start the oevents module of the framework daemon # # The attributes of a rule are : # - trigger : trigger object # - filters : filters object or list of filters objects # - actions : action object or list of actions objects # # We define the following functions : # - CallStatus() : create a trigger object activated on a call status event # - PowerStatus() : create a trigger object activated on a power status event # - HasAttr(name, value) : create a filter that accept signal with a given attribute # - Not(filter) : create a neg filter # - PlaySound(file) : Action that starts to play an audio file # - StopSound(file) : Action that stop an audio file # - SetScenario(name) : Action that sets an audio scenario # - StartVibration # - StopVibration # - RingTone(cmd) : cmd can be 'start' or 'stop' # - Time(hour, min) : create a trigger activated at the given time # - Debug(msg) : Action that prints a debug message (only for debuging) - # # Call -> Audio Scenario Handling # trigger: IncomingMessage() actions: MessageTone(play) - while: CallListContains("incoming") filters: Not(CallListContains("active")) actions: - RingTone() - SetLed("a780__keypad", "blink") - OccupyResource(Display) #- # while: CallStatus() # filters: # - Or(Or(HasAttr(status, "outgoing"), HasAttr(status, "active")), And(HasAttr(status, "incoming"), CallListContains("active"))) # - Not(BTHeadsetIsConnected()) # actions: # - SetScenario(gsmhandset) - while: PowerStatus() filters: HasAttr(status, "charging") actions: SetLed("a780__aux", "blink") - while: PowerStatus() filters: HasAttr(status, "full") actions: SetLed("a780__aux", "light") - trigger: PowerStatus() filters: HasAttr(status, "empty") actions: Command('poweroff') fso-frameworkd-0.10.1/etc/freesmartphone/oevents/crespo/000077500000000000000000000000001174525413000232745ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/freesmartphone/oevents/crespo/rules.yaml000066400000000000000000000040241174525413000253120ustar00rootroot00000000000000 # This file is in YAML format (http://www.yaml.org/) # We define a list of rules that will be automatically loaded # When we start the oevents module of the framework daemon # # The attributes of a rule are : # - trigger : trigger object # - filters : filters object or list of filters objects # - actions : action object or list of actions objects # # We define the following functions : # - CallStatus() : create a trigger object activated on a call status event # - PowerStatus() : create a trigger object activated on a power status event # - HasAttr(name, value) : create a filter that accept signal with a given attribute # - Not(filter) : create a neg filter # - PlaySound(file) : Action that starts to play an audio file # - StopSound(file) : Action that stop an audio file # - SetScenario(name) : Action that sets an audio scenario # - StartVibration # - StopVibration # - RingTone(cmd) : cmd can be 'start' or 'stop' # - Time(hour, min) : create a trigger activated at the given time # - Debug(msg) : Action that prints a debug message (only for debuging) - # # Call -> Audio Scenario Handling # trigger: IncomingMessage() actions: MessageTone(play) - while: CallListContains("INCOMING") filters: Not(CallListContains("ACTIVE")) actions: - RingTone() - OccupyResource(Display) - while: CallStatus() filters: - Or(Or(HasAttr(status, "OUTGOING"), HasAttr(status, "ACTIVE")), And(HasAttr(status, "INCOMING"), CallListContains("ACTIVE"))) - BTHeadsetIsConnected() actions: - SetScenario(gsmbluetooth) - BTHeadsetPlaying() - trigger: PowerStatus() filters: HasAttr(status, "empty") actions: Command('poweroff') - trigger: InputEvent() filters: - HasAttr(switch, "POWER") - HasAttr(event, "released") - HasAttr(duration, 0) actions: Command("/usr/bin/phoneui-quick-settings") fso-frameworkd-0.10.1/etc/freesmartphone/oevents/htcdream/000077500000000000000000000000001174525413000235705ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/freesmartphone/oevents/htcdream/rules.yaml000066400000000000000000000103501174525413000256050ustar00rootroot00000000000000 # This file is in YAML format (http://www.yaml.org/) # We define a list of rules that will be automatically loaded # When we start the oevents module of the framework daemon # # The attributes of a rule are : # - trigger : trigger object # - filters : filters object or list of filters objects # - actions : action object or list of actions objects # # We define the following functions : # - CallStatus() : create a trigger object activated on a call status event # - PowerStatus() : create a trigger object activated on a power status event # - HasAttr(name, value) : create a filter that accept signal with a given attribute # - Not(filter) : create a neg filter # - PlaySound(file) : Action that starts to play an audio file # - StopSound(file) : Action that stop an audio file # - SetScenario(name) : Action that sets an audio scenario # - StartVibration # - StopVibration # - RingTone(cmd) : cmd can be 'start' or 'stop' # - Time(hour, min) : create a trigger activated at the given time # - Debug(msg) : Action that prints a debug message (only for debuging) - # # Call -> Audio Scenario Handling # trigger: IncomingMessage() actions: MessageTone(play) - while: CallListContains("INCOMING") filters: Not(CallListContains("ACTIVE")) actions: - RingTone() - Command('xset -display localhost:0 s reset') - SetLed("green", "blink") - OccupyResource(Display) - while: CallStatus() filters: Or(HasAttr(status, "OUTGOING"), HasAttr(status, "ACTIVE")) actions: - OccupyResource(CPU) - # while: CallStatus() # filters: # - Or(Or(HasAttr(status, "outgoing"), HasAttr(status, "active")), And(HasAttr(status, "incoming"), CallListContains("active"))) # - Not(BTHeadsetIsConnected()) # actions: # - SetScenario(gsmhandset) #- while: CallStatus() filters: - Or(Or(HasAttr(status, "OUTGOING"), HasAttr(status, "ACTIVE")), And(HasAttr(status, "INCOMING"), CallListContains("ACTIVE"))) - BTHeadsetIsConnected() actions: - SetScenario(gsmbluetooth) - BTHeadsetPlaying() - while: PowerStatus() filters: HasAttr(status, "charging") actions: SetLed("blue", "light") - while: PowerStatus() filters: Not(HasAttr(status, "discharging")) actions: OccupyResource(CPU) - while: PowerStatus() filters: HasAttr(status, "critical") actions: SetLed("red", "blink") - while: PowerStatus() filters: HasAttr(status, "full") actions: SetLed("green", "light") - trigger: PowerStatus() filters: HasAttr(status, "empty") actions: Command('poweroff') # # Headset Audio Scenario Support. There is still work to be done. # - trigger: InputEvent() filters: - HasAttr(switch, "HEADSET") - HasAttr(event, "pressed") actions: Command('amixer -d sset "Amp Spk" mute') - trigger: InputEvent() filters: - HasAttr(switch, "HEADSET") - HasAttr(event, "released") actions: Command('amixer -d sset "Amp Spk" unmute') # # Idleness Brightness Handling # (activating the user's screen saver from a central daemon is quite hacky # we really want some user's application to listen for the right signals) - while: IdleState() filters: HasAttr(status, "busy") # actions: SetLed("lcd_backlight", "light") actions: - SetLed("lcd_backlight", "light") - SetLed("button_backlight", "light") - SetLed("keyboard_backlight", "light") - trigger: IdleState() filters: HasAttr(status, "idle_dim") actions: - Command('xset -display localhost:0 s blank') - Command('xset -display localhost:0 s activate') - trigger: IdleState() filters: HasAttr(status, "suspend") actions: Suspend() - trigger: InputEvent() filters: - HasAttr(switch, "LID") - HasAttr(event, "pressed") actions: - SetLed("keyboard_backlight", "light") - SetLed("button_backlight", "light") - SetLed("lcd_backlight", "light") - Debug("Lid Opened") fso-frameworkd-0.10.1/etc/freesmartphone/oevents/htcleo/000077500000000000000000000000001174525413000232575ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/freesmartphone/oevents/htcleo/rules.yaml000066400000000000000000000103521174525413000252760ustar00rootroot00000000000000 # This file is in YAML format (http://www.yaml.org/) # We define a list of rules that will be automatically loaded # When we start the oevents module of the framework daemon # # The attributes of a rule are : # - trigger : trigger object # - filters : filters object or list of filters objects # - actions : action object or list of actions objects # # We define the following functions : # - CallStatus() : create a trigger object activated on a call status event # - PowerStatus() : create a trigger object activated on a power status event # - HasAttr(name, value) : create a filter that accept signal with a given attribute # - Not(filter) : create a neg filter # - PlaySound(file) : Action that starts to play an audio file # - StopSound(file) : Action that stop an audio file # - SetScenario(name) : Action that sets an audio scenario # - StartVibration # - StopVibration # - RingTone(cmd) : cmd can be 'start' or 'stop' # - Time(hour, min) : create a trigger activated at the given time # - Debug(msg) : Action that prints a debug message (only for debuging) - # # Call -> Audio Scenario Handling # trigger: IncomingMessage() actions: MessageTone(play) - while: CallListContains("INCOMING") filters: Not(CallListContains("ACTIVE")) actions: - RingTone() - Command('xset -display localhost:0 s reset') - SetLed("green", "blink") - OccupyResource(Display) - while: CallStatus() filters: Or(HasAttr(status, "OUTGOING"), HasAttr(status, "ACTIVE")) actions: - OccupyResource(CPU) - # while: CallStatus() # filters: # - Or(Or(HasAttr(status, "outgoing"), HasAttr(status, "active")), And(HasAttr(status, "incoming"), CallListContains("active"))) # - Not(BTHeadsetIsConnected()) # actions: # - SetScenario(gsmhandset) #- while: CallStatus() filters: - Or(Or(HasAttr(status, "OUTGOING"), HasAttr(status, "ACTIVE")), And(HasAttr(status, "INCOMING"), CallListContains("ACTIVE"))) - BTHeadsetIsConnected() actions: - SetScenario(gsmbluetooth) - BTHeadsetPlaying() - while: PowerStatus() filters: HasAttr(status, "charging") actions: SetLed("blue", "light") - while: PowerStatus() filters: Not(HasAttr(status, "discharging")) actions: OccupyResource(CPU) - while: PowerStatus() filters: HasAttr(status, "critical") actions: SetLed("amber", "blink") - while: PowerStatus() filters: HasAttr(status, "full") actions: SetLed("green", "light") - trigger: PowerStatus() filters: HasAttr(status, "empty") actions: Command('poweroff') # # Headset Audio Scenario Support. There is still work to be done. # - trigger: InputEvent() filters: - HasAttr(switch, "HEADSET") - HasAttr(event, "pressed") actions: Command('amixer -d sset "Amp Spk" mute') - trigger: InputEvent() filters: - HasAttr(switch, "HEADSET") - HasAttr(event, "released") actions: Command('amixer -d sset "Amp Spk" unmute') # # Idleness Brightness Handling # (activating the user's screen saver from a central daemon is quite hacky # we really want some user's application to listen for the right signals) - while: IdleState() filters: HasAttr(status, "busy") # actions: SetLed("lcd_backlight", "light") actions: - SetLed("lcd_backlight", "light") - SetLed("button_backlight", "light") - SetLed("keyboard_backlight", "light") - trigger: IdleState() filters: HasAttr(status, "idle_dim") actions: - Command('xset -display localhost:0 s blank') - Command('xset -display localhost:0 s activate') - trigger: IdleState() filters: HasAttr(status, "suspend") actions: Suspend() - trigger: InputEvent() filters: - HasAttr(switch, "LID") - HasAttr(event, "pressed") actions: - SetLed("keyboard_backlight", "light") - SetLed("button_backlight", "light") - SetLed("lcd_backlight", "light") - Debug("Lid Opened") fso-frameworkd-0.10.1/etc/freesmartphone/oevents/nexusone/000077500000000000000000000000001174525413000236455ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/freesmartphone/oevents/nexusone/rules.yaml000066400000000000000000000103521174525413000256640ustar00rootroot00000000000000 # This file is in YAML format (http://www.yaml.org/) # We define a list of rules that will be automatically loaded # When we start the oevents module of the framework daemon # # The attributes of a rule are : # - trigger : trigger object # - filters : filters object or list of filters objects # - actions : action object or list of actions objects # # We define the following functions : # - CallStatus() : create a trigger object activated on a call status event # - PowerStatus() : create a trigger object activated on a power status event # - HasAttr(name, value) : create a filter that accept signal with a given attribute # - Not(filter) : create a neg filter # - PlaySound(file) : Action that starts to play an audio file # - StopSound(file) : Action that stop an audio file # - SetScenario(name) : Action that sets an audio scenario # - StartVibration # - StopVibration # - RingTone(cmd) : cmd can be 'start' or 'stop' # - Time(hour, min) : create a trigger activated at the given time # - Debug(msg) : Action that prints a debug message (only for debuging) - # # Call -> Audio Scenario Handling # trigger: IncomingMessage() actions: MessageTone(play) - while: CallListContains("INCOMING") filters: Not(CallListContains("ACTIVE")) actions: - RingTone() - Command('xset -display localhost:0 s reset') - SetLed("green", "blink") - OccupyResource(Display) - while: CallStatus() filters: Or(HasAttr(status, "OUTGOING"), HasAttr(status, "ACTIVE")) actions: - OccupyResource(CPU) - # while: CallStatus() # filters: # - Or(Or(HasAttr(status, "outgoing"), HasAttr(status, "active")), And(HasAttr(status, "incoming"), CallListContains("active"))) # - Not(BTHeadsetIsConnected()) # actions: # - SetScenario(gsmhandset) #- while: CallStatus() filters: - Or(Or(HasAttr(status, "OUTGOING"), HasAttr(status, "ACTIVE")), And(HasAttr(status, "INCOMING"), CallListContains("ACTIVE"))) - BTHeadsetIsConnected() actions: - SetScenario(gsmbluetooth) - BTHeadsetPlaying() - while: PowerStatus() filters: HasAttr(status, "charging") actions: SetLed("blue", "light") - while: PowerStatus() filters: Not(HasAttr(status, "discharging")) actions: OccupyResource(CPU) - while: PowerStatus() filters: HasAttr(status, "critical") actions: SetLed("amber", "blink") - while: PowerStatus() filters: HasAttr(status, "full") actions: SetLed("green", "light") - trigger: PowerStatus() filters: HasAttr(status, "empty") actions: Command('poweroff') # # Headset Audio Scenario Support. There is still work to be done. # - trigger: InputEvent() filters: - HasAttr(switch, "HEADSET") - HasAttr(event, "pressed") actions: Command('amixer -d sset "Amp Spk" mute') - trigger: InputEvent() filters: - HasAttr(switch, "HEADSET") - HasAttr(event, "released") actions: Command('amixer -d sset "Amp Spk" unmute') # # Idleness Brightness Handling # (activating the user's screen saver from a central daemon is quite hacky # we really want some user's application to listen for the right signals) - while: IdleState() filters: HasAttr(status, "busy") # actions: SetLed("lcd_backlight", "light") actions: - SetLed("lcd_backlight", "light") - SetLed("button_backlight", "light") - SetLed("keyboard_backlight", "light") - trigger: IdleState() filters: HasAttr(status, "idle_dim") actions: - Command('xset -display localhost:0 s blank') - Command('xset -display localhost:0 s activate') - trigger: IdleState() filters: HasAttr(status, "suspend") actions: Suspend() - trigger: InputEvent() filters: - HasAttr(switch, "LID") - HasAttr(event, "pressed") actions: - SetLed("keyboard_backlight", "light") - SetLed("button_backlight", "light") - SetLed("lcd_backlight", "light") - Debug("Lid Opened") fso-frameworkd-0.10.1/etc/freesmartphone/oevents/nokia900/000077500000000000000000000000001174525413000233335ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/freesmartphone/oevents/nokia900/rules.yaml000066400000000000000000000061061174525413000253540ustar00rootroot00000000000000 # This file is in YAML format (http://www.yaml.org/) # We define a list of rules that will be automatically loaded # When we start the oevents module of the framework daemon # # The attributes of a rule are : # - trigger : trigger object # - filters : filters object or list of filters objects # - actions : action object or list of actions objects # # We define the following functions : # - CallStatus() : create a trigger object activated on a call status event # - PowerStatus() : create a trigger object activated on a power status event # - HasAttr(name, value) : create a filter that accept signal with a given attribute # - Not(filter) : create a neg filter # - PlaySound(file) : Action that starts to play an audio file # - StopSound(file) : Action that stop an audio file # - SetScenario(name) : Action that sets an audio scenario # - StartVibration # - StopVibration # - RingTone(cmd) : cmd can be 'start' or 'stop' # - Time(hour, min) : create a trigger activated at the given time # - Debug(msg) : Action that prints a debug message (only for debuging) - # # Call -> Audio Scenario Handling # trigger: IncomingMessage() actions: MessageTone(play) - while: CallListContains("INCOMING") filters: Not(CallListContains("ACTIVE")) actions: - RingTone() - OccupyResource(Display) #- # while: NewMissedCalls() # actions: SetLed("lp5523_channel4", "blink") # blue led # - while: UnreadMessages() actions: SetLed("lp5523_channel4", "blink") # blue led - # while: CallStatus() # filters: # - Or(Or(HasAttr(status, "OUTGOING"), HasAttr(status, "ACTIVE")), And(HasAttr(status, "INCOMING"), CallListContains("ACTIVE"))) # - Not(BTHeadsetIsConnected()) # actions: # - SetScenario(gsmhandset) #- while: CallStatus() filters: - Or(Or(HasAttr(status, "OUTGOING"), HasAttr(status, "ACTIVE")), And(HasAttr(status, "INCOMING"), CallListContains("ACTIVE"))) - BTHeadsetIsConnected() actions: - SetScenario(gsmbluetooth) - BTHeadsetPlaying() - while: PowerStatus() filters: HasAttr(status, "charging") actions: SetLed("lp5523_channel6", "light") # red led - while: PowerStatus() filters: HasAttr(status, "critical") actions: SetLed("lp5523_channel6", "blink") # red led - while: PowerStatus() filters: HasAttr(status, "full") actions: SetLed("lp5523_channel5", "light") # green led - trigger: PowerStatus() filters: HasAttr(status, "empty") actions: Command('poweroff') - while: InputEvent() filters: - HasAttr(switch, "SLIDER") - HasAttr(event, "released") actions: - SetLed("lp5523_channel0", "light") - SetLed("lp5523_channel1", "light") - SetLed("lp5523_channel2", "light") - SetLed("lp5523_channel3", "light") - SetLed("lp5523_channel7", "light") - SetLed("lp5523_channel8", "light") # keyboard leds fso-frameworkd-0.10.1/etc/freesmartphone/oevents/om-gta01/000077500000000000000000000000001174525413000233265ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/freesmartphone/oevents/om-gta01/rules.yaml000066400000000000000000000056161174525413000253540ustar00rootroot00000000000000 # This file is in YAML format (http://www.yaml.org/) # We define a list of rules that will be automatically loaded # When we start the oevents module of the framework daemon # # The attributes of a rule are : # - trigger : trigger object # - filters : filters object or list of filters objects # - actions : action object or list of actions objects # # We define the following functions : # - CallStatus() : create a trigger object activated on a call status event # - PowerStatus() : create a trigger object activated on a power status event # - HasAttr(name, value) : create a filter that accept signal with a given attribute # - Not(filter) : create a neg filter # - PlaySound(file) : Action that starts to play an audio file # - StopSound(file) : Action that stop an audio file # - SetScenario(name) : Action that sets an audio scenario # - StartVibration # - StopVibration # - RingTone(cmd) : cmd can be 'start' or 'stop' # - Time(hour, min) : create a trigger activated at the given time # - Debug(msg) : Action that prints a debug message (only for debuging) - # # Call -> Audio Scenario Handling # trigger: IncomingMessage() actions: MessageTone(play) - while: CallListContains("incoming") filters: Not(CallListContains("active")) actions: - RingTone() - OccupyResource(Display) - # while: CallStatus() # filters: # - Or(Or(HasAttr(status, "outgoing"), HasAttr(status, "active")), And(HasAttr(status, "incoming"), CallListContains("active"))) # - Not(BTHeadsetIsConnected()) # actions: # - SetScenario(gsmhandset) #- while: CallStatus() filters: - Or(Or(HasAttr(status, "outgoing"), HasAttr(status, "active")), And(HasAttr(status, "incoming"), CallListContains("active"))) - BTHeadsetIsConnected() actions: - SetScenario(gsmbluetooth) - BTHeadsetPlaying() - while: PowerStatus() filters: HasAttr(status, "charging") actions: SetLed("gta02_power_orange", "light") - while: PowerStatus() filters: HasAttr(status, "critical") actions: SetLed("gta02_power_orange", "blink") - while: PowerStatus() filters: HasAttr(status, "full") actions: SetLed("gta02_power_blue", "light") - trigger: PowerStatus() filters: HasAttr(status, "empty") actions: Command('poweroff') # # Headset Audio Scenario Support. There is still work to be done. # - trigger: InputEvent() filters: - HasAttr(switch, "HEADSET") - HasAttr(event, "pressed") actions: Command('amixer -d sset "Amp Mode" "Stereo Speakers + Headphones"') - trigger: InputEvent() filters: - HasAttr(switch, "HEADSET") - HasAttr(event, "released") actions: Command('amixer -d sset "Amp Mode" "Headphones"') fso-frameworkd-0.10.1/etc/freesmartphone/oevents/om-gta02/000077500000000000000000000000001174525413000233275ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/freesmartphone/oevents/om-gta02/rules.yaml000066400000000000000000000051211174525413000253440ustar00rootroot00000000000000 # This file is in YAML format (http://www.yaml.org/) # We define a list of rules that will be automatically loaded # When we start the oevents module of the framework daemon # # The attributes of a rule are : # - trigger : trigger object # - filters : filters object or list of filters objects # - actions : action object or list of actions objects # # We define the following functions : # - CallStatus() : create a trigger object activated on a call status event # - PowerStatus() : create a trigger object activated on a power status event # - HasAttr(name, value) : create a filter that accept signal with a given attribute # - Not(filter) : create a neg filter # - PlaySound(file) : Action that starts to play an audio file # - StopSound(file) : Action that stop an audio file # - SetScenario(name) : Action that sets an audio scenario # - StartVibration # - StopVibration # - RingTone(cmd) : cmd can be 'start' or 'stop' # - Time(hour, min) : create a trigger activated at the given time # - Debug(msg) : Action that prints a debug message (only for debuging) - # # Call -> Audio Scenario Handling # trigger: IncomingMessage() actions: MessageTone(play) - while: CallListContains("INCOMING") filters: Not(CallListContains("ACTIVE")) actions: - RingTone() - OccupyResource(Display) #- # while: NewMissedCalls() # actions: SetLed("gta02_red_aux", "blink") # - while: UnreadMessages() actions: SetLed("gta02_red_aux", "blink") - # while: CallStatus() # filters: # - Or(Or(HasAttr(status, "OUTGOING"), HasAttr(status, "ACTIVE")), And(HasAttr(status, "INCOMING"), CallListContains("ACTIVE"))) # - Not(BTHeadsetIsConnected()) # actions: # - SetScenario(gsmhandset) #- while: CallStatus() filters: - Or(Or(HasAttr(status, "OUTGOING"), HasAttr(status, "ACTIVE")), And(HasAttr(status, "INCOMING"), CallListContains("ACTIVE"))) - BTHeadsetIsConnected() actions: - SetScenario(gsmbluetooth) - BTHeadsetPlaying() - while: PowerStatus() filters: HasAttr(status, "charging") actions: SetLed("gta02_orange_power", "light") - while: PowerStatus() filters: HasAttr(status, "critical") actions: SetLed("gta02_orange_power", "blink") - while: PowerStatus() filters: HasAttr(status, "full") actions: SetLed("gta02_blue_power", "light") - trigger: PowerStatus() filters: HasAttr(status, "empty") actions: Command('poweroff') fso-frameworkd-0.10.1/etc/freesmartphone/oevents/om-gta04/000077500000000000000000000000001174525413000233315ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/freesmartphone/oevents/om-gta04/rules.yaml000066400000000000000000000056141174525413000253550ustar00rootroot00000000000000 # This file is in YAML format (http://www.yaml.org/) # We define a list of rules that will be automatically loaded # When we start the oevents module of the framework daemon # # The attributes of a rule are : # - trigger : trigger object # - filters : filters object or list of filters objects # - actions : action object or list of actions objects # # We define the following functions : # - CallStatus() : create a trigger object activated on a call status event # - PowerStatus() : create a trigger object activated on a power status event # - HasAttr(name, value) : create a filter that accept signal with a given attribute # - Not(filter) : create a neg filter # - PlaySound(file) : Action that starts to play an audio file # - StopSound(file) : Action that stop an audio file # - SetScenario(name) : Action that sets an audio scenario # - StartVibration # - StopVibration # - RingTone(cmd) : cmd can be 'start' or 'stop' # - Time(hour, min) : create a trigger activated at the given time # - Debug(msg) : Action that prints a debug message (only for debuging) - # # Call -> Audio Scenario Handling # trigger: IncomingMessage() actions: MessageTone(play) - while: CallListContains("INCOMING") filters: Not(CallListContains("ACTIVE")) actions: - RingTone() - OccupyResource(Display) #- # while: NewMissedCalls() # actions: SetLed("gta02_red_aux", "blink") # - while: UnreadMessages() actions: SetLed("gta04_red_aux", "blink") - # while: CallStatus() # filters: # - Or(Or(HasAttr(status, "OUTGOING"), HasAttr(status, "ACTIVE")), And(HasAttr(status, "INCOMING"), CallListContains("ACTIVE"))) # - Not(BTHeadsetIsConnected()) # actions: # - SetScenario(gsmhandset) #- while: CallStatus() filters: - Or(Or(HasAttr(status, "OUTGOING"), HasAttr(status, "ACTIVE")), And(HasAttr(status, "INCOMING"), CallListContains("ACTIVE"))) - BTHeadsetIsConnected() actions: - SetScenario(gsmbluetooth) - BTHeadsetPlaying() - while: PowerStatus() filters: HasAttr(status, "charging") actions: SetLed("gta04_red_power", "light") - while: PowerStatus() filters: HasAttr(status, "critical") actions: SetLed("gta04_red_power", "blink") - while: PowerStatus() filters: HasAttr(status, "full") actions: SetLed("gta04_green_power", "light") - trigger: PowerStatus() filters: HasAttr(status, "empty") actions: Command('poweroff') - trigger: ResourceState() filters: And(HasAttr(resource, "GPS"),HasAttr(power_state, "enabled")) actions: Command('/usr/bin/gps_handler.py start') - trigger: ResourceState() filters: And(HasAttr(resource, "GPS"),HasAttr(power_state, "disabled")) actions: Command('/usr/bin/gps_handler.py stop') fso-frameworkd-0.10.1/etc/freesmartphone/oevents/palmpre/000077500000000000000000000000001174525413000234415ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/freesmartphone/oevents/palmpre/rules.yaml000066400000000000000000000036151174525413000254640ustar00rootroot00000000000000# This file is in YAML format (http://www.yaml.org/) # We define a list of rules that will be automatically loaded # When we start the oevents module of the framework daemon # # The attributes of a rule are : # - trigger : trigger object # - filters : filters object or list of filters objects # - actions : action object or list of actions objects # # We define the following functions : # - CallStatus() : create a trigger object activated on a call status event # - PowerStatus() : create a trigger object activated on a power status event # - HasAttr(name, value) : create a filter that accept signal with a given attribute # - Not(filter) : create a neg filter # - PlaySound(file) : Action that starts to play an audio file # - StopSound(file) : Action that stop an audio file # - SetScenario(name) : Action that sets an audio scenario # - StartVibration # - StopVibration # - RingTone(cmd) : cmd can be 'start' or 'stop' # - Time(hour, min) : create a trigger activated at the given time # - Debug(msg) : Action that prints a debug message (only for debuging) # Handle changes to the backlight power (display turns on/off). Here we need to tell our # touchscreen management daemon to enable/disable touchscreen access as it will otherwise # read invalid values which lets the touchscreen to not work anymore until we restart it # completly. - trigger: BacklightPower() filters: - HasAttr(status, 0) actions: Command("/usr/bin/tsmd_control disable") - trigger: BacklightPower() filters: - HasAttr(status, 1) actions: Command("/usr/bin/tsmd_control enable") - trigger: InputEvent() filters: - HasAttr(switch, "POWER") - HasAttr(event, "released") - HasAttr(duration, 0) actions: Command("/usr/bin/phoneui-quick-settings") fso-frameworkd-0.10.1/etc/freesmartphone/oevents/rules.yaml000066400000000000000000000124261174525413000240240ustar00rootroot00000000000000 # This file is in YAML format (http://www.yaml.org/) # We define a list of rules that will be automatically loaded # When we start the oevents module of the framework daemon # # The attributes of a rule are : # - trigger : trigger object # - filters : filters object or list of filters objects # - actions : action object or list of actions objects # # We define the following functions : # - CallStatus() : create a trigger object activated on a call status event # - PowerStatus() : create a trigger object activated on a power status event # - HasAttr(name, value) : create a filter that accept signal with a given attribute # - Not(filter) : create a neg filter # - PlaySound(file) : Action that starts to play an audio file # - StopSound(file) : Action that stop an audio file # - SetScenario(name) : Action that sets an audio scenario # - StartVibration # - StopVibration # - RingTone(cmd) : cmd can be 'start' or 'stop' # - Time(hour, min) : create a trigger activated at the given time # - Debug(msg) : Action that prints a debug message (only for debuging) - trigger: Time(12,29) actions: Debug("A Test") - # # Suspend Handling # trigger: InputEvent() filters: - HasAttr(switch, "POWER") - HasAttr(event, "released") - HasAttr(duration, 0) actions: Suspend() - # # Call -> Audio Scenario Handling # trigger: IncomingMessage() actions: MessageTone(play) - while: CallListContains("incoming") filters: - Not(CallListContains("active")) - Not(BTHeadsetIsConnected()) actions: - RingTone() - SetDisplayBrightness("0", 90) - while: CallListContains("incoming") filters: - Not(CallListContains("active")) - BTHeadsetIsConnected() actions: - SetDisplayBrightness("0", 90) - # fix Bug #2305 # we must keep the scenario and COU as long as there is a call left, not when # any call indicates release # IMPORTANT # For this to work, the filter function in fso_triggers.py needs a patch, # because the evaluation is from outside to inside. # Note to the "vala reimplementors": # The filter parser should be in such a # way that the inner terms get called first, because the current implementation # slows down excecution by repeatedly calling already evaluated and even empty functions while: CallStatus() filters: - Or(CallListContains("outgoing"), CallListContains("active")) actions: - OccupyResource(CPU) - while: CallStatus() filters: - Or(CallListContains("outgoing"), CallListContains("active")) - Not(BTHeadsetIsConnected()) actions: - SetScenario(gsmhandset) - while: CallStatus() filters: - Or(CallListContains("outgoing"), CallListContains("active")) - BTHeadsetIsConnected() actions: - SetScenario(gsmbluetooth) - BTHeadsetPlaying() - while: PowerStatus() filters: HasAttr(status, "charging") actions: SetLed("gta02_power_orange", "light") #- # while: PowerStatus() # filters: Not(HasAttr(status, "discharging")) # actions: OccupyResource(Display) - while: PowerStatus() filters: HasAttr(status, "critical") actions: SetLed("gta02_power_orange", "blink") - while: PowerStatus() filters: HasAttr(status, "full") actions: SetLed("gta02_power_blue", "light") - trigger: PowerStatus() filters: HasAttr(status, "empty") actions: Command('poweroff') # # Headset Audio Scenario Support # - trigger: InputEvent() filters: - HasAttr(switch, "HEADSET") - HasAttr(event, "pressed") actions: Command('amixer -d sset "Amp Spk" mute') - trigger: InputEvent() filters: - HasAttr(switch, "HEADSET") - HasAttr(event, "released") actions: Command('amixer -d sset "Amp Spk" unmute') # # A few testing rules : # # This rule will only be enabled in silent mode # According to the preferences rules conf files. - name: 'test-rule' trigger: IdleState() filters: HasAttr(status, "busy") actions: Debug("Hello This is a test") - trigger: Test("test") actions: Debug("trigger test") - while: Test("test2") actions: RingTone() - trigger: DbusTrigger(system, 'org.freesmartphone.odeviced', 0, 'org.freesmartphone.Device.IdleNotifier', 'State') actions: Debug("dbus trigger test") - # # Idleness Brightness Handling # trigger: IdleState() filters: HasAttr(status, "busy") actions: SetDisplayBrightness("0", 90) - trigger: IdleState() filters: HasAttr(status, "idle_dim") actions: SetDisplayBrightness("0", 20) - trigger: IdleState() filters: HasAttr(status, "idle_prelock") actions: SetDisplayBrightness("0", 0) - # # AUX Handling # trigger: InputEvent() filters: - HasAttr(switch, "AUX") - HasAttr(event, "released") - HasAttr(duration, 0) actions: Command('/media/card/bin/hstoggle.sh') fso-frameworkd-0.10.1/etc/freesmartphone/ogsmd/000077500000000000000000000000001174525413000214275ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/freesmartphone/ogsmd/cell.db000066400000000000000000116062201174525413000226650ustar00rootroot00000000000000Hw€.D"=`Hm X@hAҋDsHm YOh׽VDHm ZHhADHm i Jh,AB@F Hm eh%A2f'F]Hm h$AREHmJ hGBFHm<~hAEHmPh?ìDcYHmIhABnHm+b khEB|F#NqHme mhfARoF Hme#nh:B@EHmf h4AREoHmf $zh9A[EHmi kh4 AmSFLgH y4\BҚCd!H xBFQ~H ]}CRdEioH }GCjE-H Q}3CF H }pCF ,0H|H}CR=FB Hy0BbFYHpBz(BإF}H`qz3BjBfHaUN{91BFHbqz$BMzAjHd{BQFGHV%{6B$F0@HGX}CSXE:HI)C}3_C`|EHP%S{ѿAEٮHP| ,CEDtHP{oC^EY$IHP8(|C8ujF?HP96|_WC)_EHP:j|qC4pEpHQRgz5„FZHQSpzC&CFHQUV}vCr[lFHQVg}FCvCE8ElHRmQxUBEzHRoxBE,HRx~`COEHR&~50CyFD26H PD€CYEhH w'€N2CDC$)H (€CD.H ,t€HD8CEH -`6€]AC˫^DH .d€YCH /~€KwC*EKH =K€mCEJCH >U€gCϫ@LpZH R:€D LFH T€D'S*E ݜH W-L€nCߐHDH XA7€CD>aH Y5C€uCD 5H \V€f]CύCjH ]U€g;CИA4lQH ^_€^ZCJbD$3*H _k2€X-CCmDKH o1b€D0jEq0HPfv€DCRHfg€D1hHhk_€]C33HhtM€kC[D Hhu8€y CDDaH  €D!?H  €D"'H  €D#D;&JH  ߨ€CF~H  ~cCLD4zH  'VCFF:DH kbXCF)DH '@C:RF[#H CirC aDH s€DH V€ߵD&CYH $€9D"C:GH Q=tkHCD1۳H RKCЇE9pH gKNC#FccH g8`mC$AH hmn€{D$DH hn€D#D4H ho€?D!ѽE"H a s:%AҫERH a rӠB!-H a rBvD H a trB&vE4H a rA vD*BH a rBYDH a irADGH a  r2AiDH a r-A?C_H a trAߏCH a ^sBAxH a RsGApEwgH a IrsA/H a rֿAPD\H a KrADCH a@0pBbDJH aC[KuoBsYFk@H aBnB_$Dr]H a1fsz-AEVH a2s}B"+dF +H ap.qrXBC2H a,updAxH aB8r`AxbEZH aoq LB CH a$pA ENH aPpB=E17H aSrKB yH aqreBUEuH aSrnBEH aqlB!RH a]?uBPDc9H a^T8uG`BXF+8H a_EvyBB$CH apdqB 7|D~H aqhr%B3F'H a;]m-F@RH amR?8FH amAE~H aCnd>A&FHH a=nAvEM$H apaAEs{H a*p"B DbH aSpB EgH ao2B MEH aoBH a{2p3A9E#H a8IpUBCH aUp2A`*E5H a17p^AaEKH amAE걦H a}mmB_DVWH arA nE ,-H ayr2iAϯELH aJqBB dC-H aJrDB+CFH aPrP B@m#H aQ >w BF]EH aQw}BE4H aQwiBԥE#H aQvBD7H aQvB6WECaH aQ vCEH aQ"gwKB EsH aQ#LxBFH aQ$ x'BH aQ6pAEXuH aQ`gq"IB>EhxH aSǪpB yEH aS%"rACԍBH ac'ty  E[H acpt/BJTEH afrARCѷH agnBACH ag!nAeD# H agLnBDCTH agp*n"AZEH ag‰sGWACH1 {iAAMJH1" zXiAһCH1" viAϰbC H1& }iv/A.QCzH1& }%inBeDdH2 jACq/H2 j*@(B̊H2 jGAsE|H2 qjSAEH2 jA44DYI-H2# qjA.9E iH2# jɟ@EH2# Lj@ąE3H2' jmA%\)H8 (1iA\D/H8 #iA-C;H8 %\i{A+H8 j$AC9H8L j_AC͢}H8 1Ui@ToBD$H8" *i>@/D/H8 j5A1iCH8 j@@Ddm=H8 jc6A33H8 $ejYA$H8 jA7%H8 jAjH8 j AH8 jAC4XH8 ,i@`BH8 -%iA.D4H8 +i@ fDH8 &i@qCXH8"U Jj[@oE_H8"W sjOl@AODiH8"X Wjn]AʠEflH8"Y jpA yC ~H8"s tj+#A$EH8"{ djAoH8"| ^jAH8" +WiAH8" #iش@H9 fiBl[CH9> ai*BZ@T8H9 tiAVCE>H9 siAvD<H9H O,i@=CKH9 XRitAH9 SiA`)CgH9 JiP?dD1ʚH9"3 giBCH9"< n;ieAH9'< eiAD_H9g" l@iAրB۩H9j cEi B4NH: jgAgD7H: gAbTEE#GH: gg=E H: xgEt H: g @$AEH: g!DH: gfG6EH: ̈gOVE;H: gnBE0CH: agdA@EyH: gѕA`6EDH: "gBH: gLE(&H: gUCIE}iH: GhCEH:! gVBIPDVH:" gkDEiH:# }qg?|(H:' fgKEH:( 4gBD H:) 9gʞA DH:* igBwDgH:+ gaYD7H:, gAzDH:0 vgBVFDꡅH:1 gb‘&1Ex[)H:2 g¦"E>2H:3 eh@BE# H:4 |hUO!E5"H:5 sh\B(%D)H:6 ?h@kC%mH:8 }h|AS4Ey8H:< ogBA^ErH:= YgA3Eb>H:> Mh}EFH:? ɺg@BBpDH:@ gCEFH:A gAE%H:E }hz@$B H:F h1?D-H:G qh%>VDH:| g"AA_9H:} 0g.AEE6H:~ h~¸E'H: hE&RH: h$TB3WEQBWH: g EH: g͆D'EH: CgZBO3wD]H: giB XBsH: ogAD{}H: &g^B7JDH: vgF@'BH: gB&^DH: gAЍDXH: qg%B~6DZH: |hA D H: h1:AlA@H: w|h.BD)H:" jg}B H:" gؒB$D䗱H:" g!Bk?qH:" gs~D;H:" Hg¥@DH:" gȮ@~ DH:" g ARC&H:" gAEpDr!H:" EgAOH:" hAwDcH:" gtAxoDH:" gA,E H:" |h EH:" ?hAf*DH:" gACyH:" EgmCEH:" lhEHEH:" c7h~ArE'H:" Mh~LE3H:$A h60A DXScH:$C hDA,?DH:O ]gJA@D >H:O g] 5CH:S gBrCkH:S KgACxH:g% gB BuH:g( qg1B C .H:h/ 1gA DH:h0 ig@DH:i Yg>AXH:i 4g@/H:i gBDH; ɨi@ϖDMH; Ei%@D^jD=HH; hҴA5DH; h@~H; ;h*AH; 4h;AU+ C|H; X0hADmrH; % h@. E&{ H; 'zhvA/oH; hk@DpDH;d i6@DqH;f iJCDH; Ҫi^@|CE3H; .id@矂C鎚H; hADH; i@2D|EH; iRA9DH; )ic@tEoH; Ah@ڗH; nhׁA mE {H; Th@ODdMH; %hrA2D9H; h@_7CɖH;! i/@@D*{H;!? ^hE<H;!A Lh AFgD^5H;" giSB3pDJ OH;" igE/ɯH;#> if@SH;$& &h>eEOH;$' ,hA8PkCH;$( qh@H"AH;$) 1h7@EM6oH;% BJh4A D|x$H;% 8h]A4ffH;& h$@VEZ Gi C̛ H>d Di*+ H>i >iV?^@3pH>& ' ?ivDKbH>) 7i@<9DXH>J Gi@Z+DH>L Ci9H>! <7iA~C&HDt~lY2ATD$HDy $k)AD eiHD{ kAF+{HD =k AuEHD XkU+A_ E*HD GkcAFd,FzHDVkPAD HDkAYFHDkeAAEHDl AFHDOkApF8I&HDBm AHD.nmInAHDkzB HDükqB ~Ad"HD;kADEw6hHD$l)A`E)HDl3QAo,E+HD]lLAJF&hHDkGlA#D*HDSlӠAOE@HDUSl A sF#HD!Dql|>AWEniHD!XkB }D"THD!Y]kAWEHD!ZpkNA Dy[_HD# JkAel1AR}E"Hu?ylg-AOIEYHuAKkAEHuCkyBRHuDΰkA1E3VHuEkxB@HuOʡkA/HuPk\AkESHuR kA$aoDLHuTkB9zEkdHuV N kcA'EtaHuW 7IkAE<Hu pk*AЋDEHu rk)bABDs9Hu Z(kLAW/EtpHv^qlACYH:m2H@D\BHñkǥAwBKHѽk]A뛦H5kAlH '2k@mHk ANE@xHP} CPFH^c}CP#EXH }NCtDGHqwwJB±DmH6}Co#IFeHe{6BBgF?.H~x!BvE7BjH~\CFasHzy BF~ H5~ 3CzR:F~H,|C6FHEwBևF9H8}%CYqEwnH| Co9FoH hz_F'?HOqz B?}+HQnxB]!F sHQ{DCqFH=y_=B8Eo]H&}DnCfH#}6C[D*ZPH Gy*BӡSEH H:y2BCODH Iy6B/ChH b^}CYH- Wq€epC5H- U€g6CAH- U€gAC1hH- w€#D[E}H- €D'S*E ݜH- Z€D F5H-"U€g>CkH-ZVK€f{CѩyH-\O€jPCҁ{DH-uWq€eoCcTH-|:€weC(DˆH-U€g=CжH-U€gH.GW€dCЊ=H.K\€`C/CH.u[€bCkDH6 bCQvF)MH6 "SCjEH6 AC FH6 \K-C_DH6 |͙CiEFH6 8qmCbH6 JCEH6 ~ĀCFH6 €CF 8H6 €1;FH6 CF:ȾH6*€D"#D-$H69~pClJH6aٽ€A`BH rBHY?s A{Heds[ASE$Hh rBk.EHjGrB EX(HarBhEI[HrACEnH rBD^Hr9AMHrB,D $XHtrB&vE4HrByEHuVr5AEJFHu`^wcBx DhcHp vC3XExHsv|BnHրqB :EMH>qFBEHrT,BҰEHq BEPH0rSoB`BHp.qpmB E3uHvr A%EUHpprgB 4EHr7BJEˊ HAwBҤF9H'P$prAش_E 9H'S pTBcTHNbmY/A-E7HNJn[AFxHNnB!!EHHu pAxCHu%+pl{AE(CHun;AHp B1/-FHs*q-B[F EH1m{A*EF;HxoyAӄcF-HpsE(fHpoMBHpBɺH|pBZHmsA F|H/pP?+K^F`H9?n{gAeF=KHQoB*F8zHO|quBȿmE5sHhsP\BC"_Hmtq>„4EHq[euBFuHvls BF H{XsAE HnDuiBqD6HxsiBE\H~JvyB!B+HsACHrtBh`C|1e5BAPBfuB A\iAfuB AZvB_`EFfvBAAwE{Sfv!BAb=A׋Dfv#BAB!CfvSBA9Ae F>cfvTBZAA'F1 fvgBAµAt"EǜYfvhBRA[AܷOE'fviBA(ÇѬEfvjB AzA:FD_fvmBvA?ABQfwBAFAmF>LfwB A>ABCfwBA[B_CcfyKB A?{A`BfyMB  A/AR~Dmf{BA{BpPfB 4A=BPE,fB A1MB+$fJBA%A =xv!BAB-C4xv"B[AnyBӐD xv#B“AnC aEhxv$BAgLB2NE?xv'B?A`BE+ӻxv)BAgLB2NE?xvgBբA`"BDTxwBAxBDx{B̿AsB!3E_xBA{BpPxB HA(AdD^rxMBİAdBiD_tB Av@և+B Av@և+BbAO`DSlCǟB3Ak2C[HǤB~Aa.D9UCǥBAp6CcLHrC-BAjD;J.B>ArED:SD$:/BAjD;J[BXAwD6ѸF)(əBPAbDBFAmDP}EB!AnDJ4REt4BLADEwEO0 iBPAbDBJAVD]|E89B "A`QD4ZFBJAQLDWXEfBKmATDD[*EbԩBKASDY!EVԪBAeD6vFܓԫBAjD9sFr BQAa5DW;DKBXCA[DU[E2@7PB6AAOKEbdPB:A AREPB= AQAAdZPB:aA Ac?EaR&BVA+@,0FwžR(BV?A\A>Et/R)B6A(AfNE`R.B("ADj@ffR/B)ABA4RBAB@VUBA4AHXB Z=FҲ.BHDAD9jF︲2BA:AYKE,7BA@x8BAACEʩBAzBqbEIʱBxALA7h B fAA?#OC @7)AAFM`B NA AFADH<R&BpAeXA8 JR(BtMAZrA_ CRB|xA@ʤDJ4RBA @MBEsyRBA6XRB{AB>q E#RBpAkAAD*RB9A8CNAF}RB(A{E9'DRBzAF@4D&RB}A=>1SICSB)AB!A C܃VBAt@XD_VBAt@XD_WBaA(B@RX{BAA;ãEl@X|BA&@+E?'X}BA/~XB[A'Ec$NǥBkAtGAx(BNA'Fye0.Bz5AG@fE2BA@%^D+%ʩBnjAlAGӊBpAeXA8 JB$AkD\JBdDAyeCZE BMAtCLkD|BfAxiCEBvCA}CGFUBM*Au*C21DB\AuCIEaBjAtCE12BN=AuqCfF35:B Av@և+BbAOnEўBdA1eE<>Bz*AÇE:n B Av@և+BArBpE:B6AUfBEA(B_AlB6x EߌBAYBwkWE'<BA;@QsBuAHABZtBA:`AG*BA?A>D<BNA #E9BtA#@EB SAA!YR&BPB"ABʳ J?B"ABʳ R B#}AD(1 eB"A/C:_} eB"A/C:_} hB"A/C:_} hB#ACsFd hB#vXAsmC B"ABʳ B"JA B B"ABʳ  B!AC^ 8sB!AC^ C!B!AC^ gB!A(GC.7 hB"A~Cd/ hB"֠ACQh:B"}AXCEbg;B#6A1C+F6>B# bAB{E?B#B#AfTCVJFlB#o$AfCuFGNB#o$AfCuFGNB#AwD;FHTB$bJAʇAǍPcB%bAБB{`ExdB$BACG EgB%bAБB{`ExhB$B$H$A AE.ZbU?B$[AC)Fu7UAB$KA[BFUGB$hA8C0E8UHB$HAHCaFUIB$zAB\/nEƢ.UKB$a AzC!|F oUMB$sAJCpEޫUB%3ABxoE`UB$KAAȴf5B#\QAd^C}iGB#AC:EcB%qRABB$vPA9B@уEUB$YzAA4B$PA[AJ'B$AWBE7(B$/)AsmBzFۣ)B$GAB B$|A BFDB%RABRDn[B%VA?BD6cB%{ABExdB%xABDyB#[nAb`Ci7EB#ABՁB#[nAb`Ci7EB#&ABD٘IB#(]A; CNLF8B#NjAZCyOEBB"bA{CrEB"bA{CrEhB$AChF"iB%oABfElB$AB=:jFymB$AB2~F?%B L AAˣFh B"[ ABB"0AVB EI1B"rACo5B"-LA[BuOFOB"rACoSB#ABՁB A.B1 7B =vA1RDp!G4FB OAB<ÖB qA=BQxB sAoA@~FVkB A%BHQF~B!A#BoqFB .wA}CGF3BA;@GB FA6SB,2-B -ADЀFB JARBUOF4 B =ABP[FJ B AD|F B JARBUOF4BwA S"FBAGBGDBAqChE%B AW@F\4BAiCE'OB!kAxBIEB!EA(BB)SEzB!IA|Bs}E4B!EA(BB)SEzB!AHBĀE;93 B!]ANBF̳(B!\A9C]j}F1-B 6A B36F.B AB B#{A:D B"[ ABB"AgAs B"AgAs B" AAE/@aB"rACoeB"rACo3B"1AA4B"1AA7B"1AA8B"SABE.BƀA`pCkF+jBàACAE@BуACk(!DDB! A*AiE6CB XA Dn[GN BB!ADA.F &B! A*AiE6CB hAAFrծB!AAŵF9JB!AC^B AWCФF!oB 4ACG0F-{B A1C FmB ACEG51B OAB<Ö2B ^AxDE3B AAӦJD C5B =aA%BjF+=B AOHCE,?BAACg@!A2AB AAkCEtBAYB,/]BĢA6C9E s4B!ABhs4B!KA2CE'4B" cAFB_Ep6B!7A A6B!3ZAvr!E98BACSGE@BA;@GJyB"[ ABJB"HA@B[8RJB"HA@B[8RJB"1AAJB"HA@B[8RKB AEAoDKB A lAEaKB A lAEaNB"HA@B[8RNB"HA@B[8RR'B"fAqB%E,S}B AOQBȄpESB AOQBȄpESB $AAA%SB AOQBȄpETB A2BXD^ƧT!B !uAۨB:T%B :AC!pF' T)B yAB%T+B 6A B36FTNB B`ACiFsZTOB qA=BQxTRBA%CUhF32TSB AxB>KhB!AC^hB!OA&C-@uEi\B!"A^nAEAGHi^B!0A(BFEiaB!+OAEPCcErigB ղAB#BEȷoikB A(A|E 0$o$BgAG.{Eo%BACFqVo'BmA9@+WFA B A@C̋sB QACpEB |AVAB!ABhsB!IA|Bs}E4eBnAVCeyXfB!!AA7gB!!AA7$BAC ]F_%B 6A B36FtB"rACooB A|A2bNqB AbAnqEyrB AA]E@wB!KA2CE'xB!0A>ACkEhyB!0A>ACkEhB!A=gB;dIB%ABZEyZfB%A?CBAA{Fu LB?AKCXv BA(IATD @BkAD jG? SB:A>ApHE4 TBA ď[`F XB9A۬/FÀ rBAnAZDv BA_@RCU B ABC  BA7AA BArAZFFbJ BAɟAOC7BAA)CzB:AACaًBA7lBAA7Cy!n!@BA@gE!DBArvA?D) !KBoA 7L!UBA_A4IC "B8ABE@z6WBڣAC 6kBAkZAH6lBAAE6oBALANXE6pB/AAkEdZB{AfAmZBtA A6E8ZBEA A_o[BQA9}Fv[BȶA,+F[r[.B&A%F[2BAC 3G 9[3B AA|ES\BWA/AE_rBA7?DtBAmAF) B|ADbF`o) BrZA2CG) B@A BF) BA=ODm ) BAU8RF|) BvAc_F߉D) BA7 Dd~F) BoAB،F!) B3AcWDҸ) BHAj3ABvFR@) BAd‰~3FD;) BA7C_KGY) B,AS8s9F݅}) BAB:GFz) BA)`AFl)BOGAI?A8C)BABFԥ)BAD0FB)BAO@FH)BzA]F)!?BAemA'F)!@B'A0ĥoFn )!ABJA@ A_3E%)!CBA@VDW)!DBA~AiyCa)!JBA]BE>pS)!KBABE?)!NBAAAnBB)!SBAuBE~)!UBAAzFC6)!WB@AJA6ߦEkJ%)!lBARBjFA)"B|Ax>CEc5)$BAizF)6WBAH?VD $)6XBXAcYӤCŒ )6lB$AB|)6pBAAE&)OMBAS ֶFnf)ONB,`AB!Fq)OOBeBA+AXE< )OWBԿA`pF)OXBA1p?ܜ*E3[)OYBA.<;mE+)OBLA1=?"C~Hd)ZBAA8Fd)ZBA A D)Z8BuA5@lGDTl)Z9BAӳS[C6)ZBASASF\y)ZBAAyvE;Z)ZB@AA()ZBVAZ]AFi)ZBͬAUF—)ZBA,4D2)[B0A.@CdLN)[BkAČ9{F\)[.BA%ZA)[2B AA0E)[3BA AMnE?)`B;AA8R%C>)`#BA܀BwD W)rBACA.F)rBAAI-(E!)rBAA&IE*1)rB AѠ@YSEv)rBQ9AG[?l)tB;AA8R%C>)tBAJATE^)tBA\A E$)tBAA qD)tB(A\@:E<)tBAAD)tBTALXD<})tBA {)tBHAC ư)tBAfD1W)tB)AmBF()tBARzB~,SFM-)u BAHE\sF*H)u BA)=R_EH@)u BA7@!D7P)hBA\DYUF)ؗBA̧Dh)BBA/’AF`)B Ah@\))BA3@)qB,A6D)sBaA]?IjFN)tBA*|ҍF-o)ڤB-A{AyDD;)ڥB1Aq@mBA{C-FC,>nBAêlE9a,>oBATCjFN\,?&BApxD'e,?gBoAD3^E,?hBVA|D!fE>E,C;B%ABLE ,CBUA|CEˁ,ZBMAD2wDm$;,Z B(TAy]D FX,Z!BAD.D3,Z"BAD4EY,Z=B.A_DyZFYq,Z>BA*Dj G ",Z?BOAWDW:FQ,ZfBACszE-,ZgB%A*B;eEK,ZpBFH,BA#p?'F`,B$A0~Fob,B~|A:ZB F,BADžAtE[.!vBAs?CU.!wBAMA\D9jn.>2BTA8B"Cڿ^.>7B!A%F]`CwA.?B A{D1>F$).?B__A A.?!Bv)A:/D_F3Щ.?"BAM"DPFvo.?#B[ A0"CjE|.?&BvcABD:>jFM<.?B(AF`D-D.?B2AOYD)/.?BܒAR`DkC.OOB=AavA5,E^.OXBA3@ ƨ.OBEA{Ct3E7.Z>BEA0D3.Fe.ZRB5!A2Cm*C8f.ZSBb AX3ÏOF .ZpB@9AξC7D].ZB]ApvDC'F.ZBpATDS,FZ.ZB[A9bDLF.ZBQAJD%4G!F.ZBDALD GZ2.ZBIAÓ+G.rBAjD'#EPߗ.rBAIDfFnX.rBFA DV(FrD.rBADIFPx.tB4pANCHF .uB/APCa+F.uB"AC Fh".uB3A:C1FCi.uBgA;CPF!ŷ.uBtAE'D,F@#.u"BlA>SDzF0.׫B@AFBF .دB0A-ACr^C.ذBlwA_IbSF}.رBreA4FY.+B2AOYD)/.,BAUgD.tBjA4@*@;C#.B/A1C\VE.B/AnD%E.BAxCFF.B/AtCҨEy.BAPD,$F%A.HBKACWD<.BmAHDD2B F~..BS!ACCiPF .B}AގCJF/BQA&_Ar/5B,ANֿXzD//6B-AO iEz/9B[A2AAFs/:B5zA>@u/IB*&AT>_w/KB.dALg6 E'(c/MB*,AT;;D!/OB-AO iEz/XBQA&_Ar/ EB%A]C/5NB2|ACz/aEB/AI/@Dn/a}B,AN|%/fcB,AN|%/fiBQA&_Ar/fyB*,AT;;D!/@B%A]C2BA׈A2CB! _A ;AE2B|AA6qDѱ2 BUA@5?2BA@fB2BA@LB32BA-@ff2BDAڟC?2BAIrFNAN2'BԑAhADٹ2/]B"AmAXB1 AȪ?GdhCњAXB/VAȪuSD pAeB-_AȨA׶yDAgB*AȦADewAkB-AȧA&ˌD6OB IA@B0E?<B!jAtB`)B!jAtB` (nBA BDvA (n'pB-ACC) (n(BBA!C?BB*| (n) (n.B>GACH% (nN:B.\A@t (nNXB,AtBEis (nPLBA6B& Eh (nQxB9A||C[R (nu^BABwE (nuBXAC@EU (nw\BKAB8E\Չ (nlBA3BؐE0B (nBQA^BZELo^ (n8BW$AC+iF<% (n@BA BD (nÈBoA+C6F$6= (næB:|AA (nBPAZCg4EP (nĂBUA B/Cj (n|BdAAC#F> (nBkA CF2&p (nHB4AOCi{E (nBlABـEuER (nXBWAC3Fs) OLxBk%A7@D7 v\B AhADc v\'BAȒAcGE v\'B̃AA4<,B v\(BA?!.F v\(ABgA،AZ v\(BA@0EY v\)BztAic@кFz v\)B]A;GAb v\*?BAAQFD v\*BkArAvEDQ v\+BA'pFϱ v\+uBkA]@+Fdp v\,BA @JtES v\-BAx4@9Cc7 v\/BAF Rv v\NBxAw?RUFc v\NBxA?@ހE v\NB AqAnDZ v\NBUAE@m=F., v\OQB-AA9C v\OBځA}a’CGx\ v\P!BOAYAU2F P v\QOBAwFFM v\QBlAzAnD$(S v\QBAA<#C v\RB~A̜A!FD v\RBACHF埄 v\RBBAH DDk v\SBA@ZEp v\SBAF24 v\SB(A+A^ v\SB:A@8Fj v\VBAAC[ v\XBA @E$ v\uBAl^4GF v\uB DAΩ@KC= v\vBA>„GZ< v\w B*A@E{ v\x_B#OAbmEG5 v\xBHA@}Ed v\y'B+mAAYC v\yBZA=!G>A v\z!BCAfBЯG v\MBA;@@FW v\3BA@EШ v\BeAĞAgC  v\BAAdZ v\CBA@^%F:O v\SBA AF~' vfB AY'okH{ vf'\BAYACEG?] vf'BA=Dp vf'B AxDF vf($BAݩD%iEt vf(AB{A¨Fx vf(VBgA$DNF" vf(B A^@Q vf)B%A`@` vf)BĆAD^Fvp vf*"BAF?D/D+ vf*|BAqGR vf*BAD}F= vf*B=A8=DR vf*B A1DM8EM^ vf+BmA4DV7D]g vf-BADF vf-BAxD8Eg vf-BjACcςF vf-BAD)FG9 vf-BߒA@vD DF vf-BAADD* vf.BAsND%+ vf0:BA4D=cF_n# vf1\BA D~F74 vfBBSAYCn|C vfNlBͷAŻG+8 vfNB-A#jC vfNBA@C-ET% vfO4BAVC6FH vfOQBAEC[Y{FX vfO[BAzDD(' vfO\BA`>DϦC vfOfBmAD*Go vfQ BUACpF vfQAE vfZ.B:A>DD^E3 vfiB6AH&D9E vfuBA~@Eu vfuB˼A?"D5H vfvDBAD*Da vfvlBA`>DϦC vfvvBAۑD4 F5 vfvB Af@ӵ C= vfxBHA *F vfxBArD5'hE vfyPB ADFw vfyBAeD7EyWk vfyBAZDZ vfz!BA+ABP vf{ByA#CǜF^] vf{BzAeDwDF;y vf| BA:vD TC vf}LBAD+*D+ vfTBьAhCF'l vfBA6DDa0CPe vfdB?ACCFK vf6BAF*D%y vfFBADy vfnBĐAGD_eENJ v'B5A`AdCY v'BAWQvC v(KBA@dZ v(B A'FL v(BqA>@7L v)BJA(DL&oG{L v*?BAA% v+aBAoAu7 v+uB`A@}C|L vNBA8ûGHVj vNBAg3A / vOQBA`@sVE# vOBAYAb vOB\ARA6fE= vRBk?A@gCnF vSBFA,AADC vw B~7A8@P*Fd vx_BA? h vz!BA;A C Sl vB;TACFW v'B@Ai'TFq v'BbA nF< v(5BAHӰE v(?B?Aɣ@]Bf v(]BGAuÊC3F泦 v(B=Až}Gg v(BDA@CF6{ v)B5A5~Cyo FZH v)9BaABWLFXԪ v)WBAGAGC v)BA@އC v)BAuA$ ?E&a v*B`Ar@uEÔ5 v*BAwtE v+BA#@C ,| v+uBܹA@ÞtC҇ v+B'$A}D?F# v,BWAAC;F,jA v-gB/PA%qD)SF vNBArA#̈EP: vNBC vvB"IA3&DHGF~ vx BAAbA?E:j vyB0AADJv vBc_AA8E*< vBWA#_F vBCrAÑ7G E vBJVA'NF v]BxAF| vBBjA/â{Fn v'BAJAYsC} vB.A.ĂEG&( vɱBBA3AF vmBnAIBQFY v7ByAUA~^DCZ vBcAAueDq xP(BAAAC?F3S x(B x*B>[AÁ8E(+ x,BbA Cv_B x-BۏARD x.B=DA?CG x/,B:A D!f x/B iA4F5 xNvB;A?pC5FJ xO BɎA3ěF$ xOfBgARIDC@' xOB8yACWF{ xPB2A8BFo xPLBM9AClEs xPB0-A@uC|G xQxBA1(ĀNF҅ xQBADCqFM xQBA`<ÇnF> xR"BL:AEAUw xRBA2D#9 xSBAAHDe. xv0BA [D6F(~p xw*BACF5o xwB0-A@uC|G xyBAOEDtFد xyBD4BQ2@DǴ4BQ'@IFl!#CLBQ'@#{@YBQ=c@[ @)JAAVsBQ>=@*c 4Bi[BQ&-@;R%DBQ&@/|dCP BQ<{@biCSˋBQ>@)A!ˌBQ:C@_D}ZBQ=@eBBQ=@h'7C>@ۏBQ&@/|dCPݖBQ>=@*c 4Bi[BQ:0@Op C BQ:@:y6DTBQ4@ Di'BQ&-@;jDYBQ<5@\DgBQ.Y@|EUBQ'@'-;4 B5BQ>=@*c 4Bi[BQ=@f3C[dBQ;@j8BM@AE;60NFBMn@*׽ONGBM@вoEdKy~BM@+B0yBM@$@BT98BMZ@&+EfJBND@@DOMBM@ֿANGBN@@VDSNBM4@Aa\E3fVxBMg@zABxBM@@ D.xBM@ErE%mxBM@4B&E")xBMC@Tg]ExBNQ@^[AECkyBM@@b#Dk:yBM{@z@AyBM@A*D_y'BM߼@{5@yUBMI@A*BFy}BM@A/Dky~BM@إvhDyBMn@%@ZDyBMr@AEYyBM@^.ZEyBM@A~DyBMN@bxEyBM@g$WEHyBMo@YAaEyBM@~DEȚyBM@AE.yBM@i@ LE'%yBM@vEgyBM@@3ޡDyBM@@EyBN3@U@:Eu|BMߟ@~A|peC|BML@A;C?P|BM@ƒ=D|BM@AbIlDY|BM@LD>|BM@A"C=|BM@LT~DdуBMY@ =ESO9BGf@=CkF;@:BGf@=CkF;@;BG@C[F ??CBF@xC;BFؔ@0C)E!BFP@CESBF@CF8BF@CPQF"nBF@>?CBG @ECٜDBG:@/Ch)FwBG4@2C6F(\ BF@>?CBFC@]CLE*BF@#C E(BFؔ@0C)E!BF@CU!BF@8 C'BFy@C0b(BF[@x Ca2EZK*BF@8 C-BF@CUBGw@CzF0NBGwI@CEBG.@kCNE\BGd@VCAC1ABGN@CF-OBG@yC%?^BG@ClBG4@2C6F(\BGw@CEڳBGd@VCAC1BGw@CzF0NBF@8 CBFE@ C)BF@BCkFBFء@0CE)BH@C;BFݚ@XC ExvBGa@[CNBƢGBI@eCG!BH@C{6EL bG9BH<@~CPG:BH<@~CPGXBHS@w.C ƨG\BHm@C?IE^?uGBH@C{6EL bGBHY@CdF"PGBIj @9vCGBIK@_C+E7xGBI[p@lCh?ENGBIL@ECD9GBI=b@5C,ERGBH@.COGBI@LC~LGBI@LC~LGBI@LC~LGBH@4C}GBI=b@5C,ERnBHfr@WGC7EAvnBHy@9oCnBHj@C6EnBHa'@DnC_nBH@C`ЅFFnBHO@aCEưloBH<@~CPeBH@.CO.BH@)CMVxFL|0BHS@w.C ƨ(BH<@~CP)BHG@CIEQkU )WBKXp@ALBG *.BJ@B1BMP */BJ@B1BMP */tBK,@UpBQB)` */uBK.@>aBD.O */BJ@B1BMP *1BK3~@Bʬ.C?= *1BK *@ BEIh *1BK4v@όBvE2_ *2SBK.@>aBD.O *X~BK1@B4Dڕ\ *XBK@NB' *XBK@NB' *XBK@NB' *XBK@NB' *XBJ@B1BMP *XBK@NB' *yGBJ@@%B *yWBJ@@%B *BK@NB' +BKV@=BQC+ +)BKD%@BD +)BKAE@\BjDQ +)BKEA@BD=O8 +0 BKG_@xB٧D +0BKG@BD]y +0BKG@B}D +0MBKe@B$HB' +0UBKeI@ߊB B  +0VBKc@٧B0D/ +0BK=@&BD8 +0BK<@HUBSu +0BK@@B.C& +0BK@@Ba8CM +0BK@@BU.C! +0BKB@@BDT +0BKD:@ByC +WWBKT@DB C5 +WXBKU@DnBC +WBKT$@I`B +WBKF@NBZDe +WBKB@@BDT +X&BKe*@ B XB+ +YuBKT@B +~>BKc@ߖB DЮ +~?BKc@oBWDYe +~@BKg8@BʹDW +PBK@0@ZBiPE +QBKB@KBE6P +FBKh@)B{CRL +˟BKe@B Ch| +ˠBKgD@uBުD] +ˡBKe@jB0C,P +̙BKI@sBC +BKA,@rBlDk +)BK<@B,;Di +*BK:@nB( ++BK@>@WBymC֌ +,BK;@4zB ~E +.BK@@uB7DY -)BJ@*B6 -.BJ@CeEXO7 -.BJ@*B6 -.~BJ@qBvk -.BJ@qBvk -/BJ@zCe` -/BJ@zCe` -/BJ@B1BMP -0BJ@@%B -0BJ@@%B -RWBJ@qBvk -XBJ@B1BMP -YiBJ@@%B -y(BJ@BL -y+BJ|@fC3EY -y,BJG@C; -y-BJ@0gCHEbz -y:BJ@qBvk -y@BJG@C; -yQBJE@&BEL -yRBJ@qC IEV^ -yWBJ@@%B -yYBJ@qBvk -y[BJ@qBvk -y\BJ@qBvk -y]BJ@BL -y^BJ@BL -UBJ@zCe` -_BJ@BL 0BKb@NB˯ 1BKe@߬BKCQ- 1BKg@BfYCP 1BKg@LBdC VBKho@zBKh0@BRzCz ~@BKe@۳BtjBl  FBKhb@BijCt ˟BKf@BwDd ˠBKg@BDT ˡBKgi@B&D[r BKVQ@F2BBC  0&BK[@AYB?D^ 0KBKc@7*B 0LBK]@KBKD: 0MBKb@QB0D 0VBK\$@ )BC8O 0BKa@BĖwC2* 0BK[W@ Bk 1BKZe@MB-D A  1BKUB@BD4 1BKU@ BIEC8 1BK-!@T)B 1$BK`E@3BD1 1BKJ@!0BGZEėA 1BKU@KBCo 2BKUc@CB9]C? 2BKX(@IB(D`Z 2BK]|@P;BD 2BK\@RBi+D8 2BK^@AB#Da VBKT@B VBKT@B VBKS@B`9D$ WXBKW@QB WBKb@PXB2]Cph WBKTZ@K%B]A# X&BKd@}BPb X'BKc@)KB5D\k X(BKc@ BǦD%n  XBK_@QBD+ XBK]|@P;BD XBKZ@SBD} YqBK_@B³`DH YrBKY@$BxD85 YsBK\$@ )BC8O YuBK\$@ )BC8O }BKc@"BwC.( }BKb@"BYD^h ~?BK[W@ Bk ˜BKVr@EB_DJ ˝BKVG@D7BC 18~?BK1@OBCF 18NBK1@OBCF 18?BK1@OBCF 18=BK1@OBCF 18=BK1@OBCF 18>BK1@OBCF YfBL@m]A% YeBL@m]A% YBL@m]A% Y BL@m]A% YtgBL@m]A% YugBL@m]A% gBI@`BC!$ gBI@`BC!$ gBI@B g RBI@`BC!$ gBI@B gBI@`BC!$ gBI@`BC!$ gBI@B3NC g BI@`BC!$ g=BI@?B gBBI@B m`BJ@C B m` BJ@SC?U m`!BJ@lC^Dyw m`%BJ@ptCaD m`BBJ@Cu m`BBJF@bBHF# m`BJw@aCmD m`BJ@kC' oTBJZ@wgC(gE oTBJ@CS-E6 oTBJ!@ 5ChS/E: oTBJ@N{CKDeD oTBJ@J-C3E oTBJ@qCEx oTBJ-@CIQE}n oTBJC@C oTBJ@CUC% oT BJ6@pCEv* oT BJ*@Ci_Ek oT BJ`@ߜCy5Em oTBJz@fB|F)9 oTBJ}@`C3ɋF oTBJц@C8E}4 oT!BJ@3C:Ci oT"#BJ@XCJfE oT"%BJ^@C-EB oT%BJ_@GCT.BP oT%BJ@r C`E1 oT&BJ@CEBC*\ oT&BJb@CfC} oT&BJ*@SSCcIE'R oT.BJZ@o:C^D5 oTBBJj@;yBQiE oTCBJ]@pC.EL oTCBJ@CeC oT`BJ-@CX9F oT`BJ(@JC?E oTuBJB@UBorF%{a oTuBJu@l2Bڻ;E oTuBJh@`CmJEZ oTBJg@z C*F% oTBJU@Cq oTBJԽ@KBUFe oTBJѨ@o5BF 1 oTBJ١@C!F oTBJJ@zEC0F6 oTBJ@C[JE oTBJ,@Cx@AEei oTBJ@bC|>EX oTBJм@tC.`E6 oTBJ@CN%E+ oTBJW@C5E&g oTBJ@$CItD oTBJ@#CD7# oTBJ@C#ET oTBJ@C7CCDZ oTBJ@CDs'BJ@BF_f'BJn@CC}pfE'T'BJm@;Ck&BBId'BJ@jC94Cʧeu1BK_@Y BevBK]@gBm1exBKb@&B]BveyBK]@BBAΊfvBK@uBbCfvBK]@qB!D8irfvBK@|B[՚DI{fvBK@{/BO/hD"efw BK(@mBL B]]fwBKz@gBmfzBK^@3Bzf!BK@6BvBD[yf"BK @B!Cf#BK9@}BEhD?f$BK@ BxDf%BKA@Bz'zDf&BK@pBؿB_fBKT@BI-J@,fBKy@jBmd|hv&BK]@B}ޡDhv.BKY@GBNC&hvhBK^@=B1@C2]5hvBK_@+AhwBK\@UBhwBK^@=BsC{hwBK^<@:BGs C)hwBK` @BBl|DWDhwBK^|@7}BeXChwBK_g@9B<ɼC<hwBK\M@8B'/CVhwBK]@'Bpj`DG3hwBK_@BsC]ohxBK^%@> B2+CEhxBK`6@+A;dhyBK]@]BSmDBhyBK]@cBcQC?KhyBK^Y@L*Bk}D0ALhyBK^@atB>dD*%hyBK]@doBgCҶhyBK^@Q-ByGCDRuhy2BK]W@lBiCUsXhyBK^d@hzIBKOZ@{HBo B_hzJBKOY@{B&BvhzPBKW@LB\hzRBK\j@0BŠCcdhzSBKY@GBNC&hzBK]E@cQB"hBK`B@D_B>whBKa&@2AG'BJ)@˜C1uBKg@ӂB01vSBKn@BrBDm1vBK@A`1vBK#@zBD>1w BK@kBeDn1w BK@ByC&1w BKo@ٰA-1w BK#@ºBDI561xBKD@ASCº1yBKn@B)C0a1y BKyI@SNB)Bl1yBK@ A-6D41yBK @Av1yBK@Ao,{Di1yBKV@rApDC1yBK@A\9DV1yBKP@AD}1zBK0@٫A'DP1z BK@B}D1z!BKc@ŎBGCɱ1z"BK@BGDx1BK*@PABCKt1BK@rAPDiw1#BKW@lB3Cn2>BKS@8Bn[C2?BKT@:Bz6F2@BKT.@Bg>Cn2mBKT0@Bj`C/2nBKT@/BPB؋7QBL2w@nx_AD5:7RBL0@nAV7SBL+@nAuDgY7öBL7 @nB>=q7ùBL+@nEA7BL6@nB3iy7BL7@n@n"A?D;7BL.@nZA CE7BL3@ny A؍P7BL2v@nA}C%!7BL26@nAj2BL7BL1@nrBhRD P7BL3N@nxA[JD q7BL3@nJKBKDd 7BL0@nwACq]7BL2@n}ACm7BL.@nd6AQDB>:7 BL6O@n` Aff7BL6O@n` Aff7BL4@nnNA7#BL/@neA%7&BL2@n~AYMCw70BL7@n]A#79BL7@nBHBuE7bBL,@ncA7gBL4d@nsABy,CSB_uBKa@Bb C_uBKa@Bb C_uBKa@Bb C_uBKa@Bb C_vkBKa@/B&eC]_vrBKc@,B4kwC@_vuBKd?@/BC h_vvBKc@-B6CJ_wBK`@/AaC\_wBK_@+A_wBK^@FyB;_xBKr@r4B:_x.BKa@Ba_xBKc@;NB b8C{_xBKc@AxB%4C7_xBKb@B9BD_xBKc@3+B TC/o%_xBKa@ESBLHdD*R_xBK`@CBrhLD: _xBKa@=TB15D +_yBK]J@eUBs3_yBK`@K BCK_yBKa7@=B=*C_y#BKa@BcC@_y$BK]b@?BiDiO/_y%BKa@Bb C_y1BK]6@KB~#_y~BKa@QBF@C_yBKc@DYBNC:_yBKc@IAwC_yBK`@EB(D>Y_yBKb@FBDU_z+BKa@Ba@>_BKco@CFB$gCJd_BKa@QtBCӔ_BKc@@BN~@C_BKd"@GD81`yBK@SA B ~`z+BKa@BaA.`zmBK@AA|D `zpBKp@CA D4`zqBK|@(AzDGY`{~BKqR@ Bt`{BKs@rtBP+D?]F`{BK~@ 9A`~BK~g@AD+:auBKY@BOBCZauBK[|@߆BCٲauBK[@eB\auBK[.@PB\BOauBKao@BboCVauBKZ@B\auBK[@"BPC|auBKl@BPjDb!auBKkK@KBMsDIgauBKj@_B Du avIBKU@B{BavJBKU@B BavLBKU@B BavMBKT@B@C~avNBKU@ B[BkuawZBKU@PBJB$.ax$BKl@BPjDb!ax.BKa_@yBaB_axBKU@4B!@vaxBKU@ìB BOkay%BKan@BaayABKk@B5ayCBKs@i2B6uCb|BKS@BB!ZCb|BKT@C=}Cb|BKT@jCдD>b|BKS@C>4D =b| BKTE@KC#)D*ebBK)@k>B b+BK@kB#TA0bBK@kBBbBK&@k+BWB~bBK$@kTBI>AbBK*@kBZAwxeBK'@kDBB8xhBK'@kaBAmBK2@kBSA"+BK)@jB B& BK%@k>RFx'SBJ@OCc3?Boy'TBJ@Cm'TBJ@cIC"j.FS_'TBJV@CE5FP'TBJ@C[D'TBJ)@C=)Fp'TBJ@CXF'TBJ@CAE'TBJǔ@FCFx&'TBJ@"CEF; 'TBJ@C=F24'TBJͱ@UCG$QF'TBJ@\C eFT'TBJN@\Cq7)F);'TBJR@]]C F 'TBJ@-ChFK^'TBJƓ@C+FI'TBJ@rCzEK'TBJH@CvED'TBJ@ C EĽ'TBJ@CTE+l''TBJ@lCSqE'TBJ@G~BeEF'TBJx@lC FEB'TBJؽ@8CcE'UBJ@4sCFd'UBJ2@-CcFP 'UBJ@C_oFE'UBJ@CqE䩓'UBJ@vCOF:z'UBJ@wClsDV'U@BJ@CD'UjBJo@6CRAf'VBJ^@CC='VBJ@@C^!'VBJ@uC=E8'VBJ@CzEU'VBJ*@̔CAE@'VBJ@YCڣE+ 'VBJ~@eCDF` 'VBJs@CkE'VBJ.@CE_'W BJn@eCyFB1'W BJ@C֣#FZ'W BJS@*CN'EU'WBJh@DCFB4'WBJ@CaFM8^'ZBJ@SCff'ZBJ@3C7B+F-'ZBJ @B,:EɊ'ZBJմ@F 'ZBJ|@ķC FW}'ZBJ?@C%Fj8'BJ@9CaF f'BJ@9qCn#E+"BL@:񛽗D% +#BL@:b`D1Z+%BLL@;|?LoCQi+&BL@:5-AD +(BL@;T"C+BL@;wB+BL@:}DZ+BL@:`?;d+BL@:C +BL@@:Ѕ@+BL@:A0+eBL@:C +gBLo@:?@gBb+jBL@:뿘D6 +:BL@@:Ѕ@,؀BL@NفA0pC6V,؁BL֐@NATDw! ,؈BL @NACC,؉BLk@Nȥ@C,؞BLؓ@NAUCw{,,؟BL׎@NCD@+,ؠBL֔@NAf#mDx#1,ؾBL @NACC,BL@N;AcC.϶,BL@NA*!Ci,PBL@NA:lCm%M,BL@NAZD7^,BLԑ@NG-C,BLԑ@NG-C,BLؚ@NA82`Cc,BLӬ@N7[AT,BL&@NAD5,BLԑ@NG-C,BLԉ@NF\CK,BLӬ@N7[AT-BLռ@NY\yd'BJ@CBJ@;dDjBJ$@Cl BJ$@CTBJ@FCQTBJ@FCQUBJ@FCQBC_C@B4BoEXB7^2@b C_b"BCW@nBDXu#BC@'bNBLCj0BBk@Y}CiDx0BBU@BC`@qBFqBCW@c^B t+C(+BCZz@lB-D6هBC^@VB+5DBCb@B^t.CXOBCZW@sBWBCb'@BL=CřBC]@|B;XDcBCYl@HgB(AD4BCT@ ΃C$(BCU@ 8C ^`BCa@3BEJCBC\@B EXBC\@B@ DXvBC_&@@B#-@@ BCP@eB.#CT@ BCO@OB(,-Dw@vBCC@BA L@BCO@BB1D@@BCN@B/BC@BCLj@-BbD@[-BCJ@@fBdDm @[.BCE@nBC@[/BCI @BE 6x@^BCK4@wyBfECi@^BCJ@oBs2D@bBCP6@vB,xVC?@cBCU@2B#@e#BCEB@iB5Dx'@fBCC{@BCud@gBCC@(B C@]BCE@BID@ BCOG@!B(wCK`O@BCH!@BhBU@BCQ@vB0ѺD)\@BCI@KB~VCZ@BCO@:B,·B@BCJ`@[BDȥ@dBCD@nB@%BCJ@lBxWD)r@BCU@2B#@yBCH@$BqC!@fBCK@uBBtdD@BCHF@BC*=@BCFr@ B5D@OBCV@>B D @BCP<@PB2DY@BCF`@wBxDɺ@wBCM@1BB3BHE@BCFU@BCV@iBCP@jB.C4@jBCP@B-@kBCO@OB(,-Dw@BCP+@tB.?}uBCd_@BnBC]@BmB" BC]z@BYCCPM BC]@Blc@<aBC]@BYCz,}BC_I@lB>BS/BC_C@B4BoEN\B?-@8CnE  8BC1@Cb10BCAkG,FiR0BCNAKCD0!BC+@B濧FH+Q0!BC @xBZ#Er0"BB@JBE0"BB@4BDX03BB@B|FM03BB@BbE05BB@CoYKEzj06BB@S;C1EF07BBh@^CN?wF407BB@CiAF]0=!BB@KCd~Cz0EBC=+@BE{0MuBCuA>XAΛE0NBCARC j@0_BB$@|BԎDMG0`BBk@BE!0iBBӃ@1C[3u0j)BCE@[B찂E(0sJBB@B޸F04BBS@B1Eƥ0BBj@C&E02BB@.[CE<0BB@nBDD0BBJ@%C`DU0BC<@|BEI08BB<@3B`E0:BB@UBEx 0BC1@DBF -0C D#0rBBu@@C hD0tBB^k@OB=0yBBr@B C 08BB@C)[DY=03BB@CDC0,BBWO@9PB~CB0BBy@QvCE0BBV@3BiC 0kBBW@4iB0SC!CL0eBB@dBY0BBk@c~CpDi0ӾBBv@@CmD0BBlx@VCmDOW0BB@1@CDyD0kBBr@B C 0BBf@C3u0mBB?@*C Ce08BBX@t#CE!ˆ0BB@=C)D,0BB@CCE0BB\C@r{BDG[0BBi@3C/CS0BBj-@CVEY0BBi@dC+#8Dn0BB]j@B oD'3ZBB#@˖C^E31BB@Cw3BBB@|CqmDJ23BBA@\C9oSD3UBB}@cC;#D3BB@{C]WfEFvD3BBY@ECJ VE^3%BBq@SCrEL3ЊBB@cCvFk3ӓBBM@CZEI3BB@섞C4{3oB?v@LC|E3B? @C F3B?C @ CDEK>3B?bm@CmF)3B?K@9C rEڅ3B?G@&CbDд3B?f(@[CãF$3B?Z@C }FW3?B?5@)C{MD 3^B? @C4F 3gB?dL@CE3gB?d@C&E}3gB?v@C|Eر3gB?3@ HC{3B?:p@CQES3wB?6@C3B?U@lC3jFa3B?>@)C3B?Cr@FCAEz3NB?5@@Ck,B@3xB?9H@xC E$!3{B?7o@2CD^3}B?C @8C@En;8BB9?CDCt`8BB?>CE{jC8_BBG??CvD8BB?CCc8BCf?SQC18BBm?,C]Eu8BC?C C8BC?0C$NEec8!BC?RC'JvE(^8!BCh?LSC&6D;8'dBC?'C/%EM8(BB`?C2F8(BB8UBB˨?C10 EL8]BB1?鍍C %Eն8]BB?C+8aBC@PFCLtE 8bBB>?>C#E38bBBX?GC"-E8dBB*?nC]5Da8dBB?8C*[C8dBBs?C8C8dBB?C8eNBC?C%D8@BC?jCC_E$8SBC{?o\C$:E?Q8IBC?pC%(Cx8ggBC-@C8BC& @qCE 8DBC._@PC D8}BC.@2CD[8BC.@CyC1K8BC.&@_C"Dj[8BCT@]B3C8BC/@hC E'8BC&t@C&J}B8eBC9@QCwD8BCT@ CyD8BCU@ C-fDF8BC.@:CDy8%BC1@ \BڭCBy80BCS@ C*'D-80BC.l@WCD84hBC3@ C5D\ 84tBC1k@ 9rB[DF85}BC1@ 5BݽD8@!BCCd@BԍDyi8@BC0>@ ڌBSD 8@BC/@|C]DB8B/BC-@C.D48H:BCZ@C&SDش8H>BC\?@LC)x'D\8HBCX@DCE&8HBCX@-C+DA8HBCXu@[C*bEKȩ8IBC3@ CJD!B8IBC3@ CJD!B8IBC\l@)%C*I@D/8JCBCZe@CDQ8JDBCIZ@B0D6+8LBC&@CC78NBC!9@C8PBC6@CQC,8PBC&@CC8.88PBCCd@BԍDyi8PBC/@ _ABJD*8PBC.@ B׸QD?8PBC8@9C%nDNw8QBC#@ C2Dwg8S\BC0@тCDY8WBCIz@|BE+SX8]zBCV6@C,Du98]|BCWp@a:C*Dy8_BC.;@ CeDz8gdBCSo@ T3C-qh8geBC.@ܘC [C|8ggBC.<@|CCus8lBC1 @ BqlDh8oBC*k@:C4MD,8rBCF5@)C2kEx 8rBC/@cC "C|8s BC&@.C"(oBZC8s BC @C CZ8vBC+@@C CT8wjBC+@x C +8BC0@ BD\T}8kBC/@PEC$C3:8BC&t@C&J}B8BC*@SOC&8BC1@=C}RC8BCE@8KBt%DG8BCF@L\BTD'8BC.@ BD+N)8BC-p@BD+8BC-L@ZB*DWc8BC$B@;CBCG@qCSEI8 BC$@XC"D78BC@l+C –E8+BC/,@ BJCy 8IBC.@ E:BKfBz8TBC 0@C!EA}8eBC.@CyC1K8,BCU@ C D o8BC&@C"A8*BC.@C"Dt8BC-4@)BD'~8rBCT@ CyD8BC#S@2CCd8BC(?@CbE{8BC0q@\yCD8BC0q@\yCD8DBCIz@|BE+SX8BCS@ rC,,+C]h8PBC'@CE7-8|BC.l@WCD9BBׁ@B7D9BB@^BvDc$9BBI@JBagm9BBo@@BsbDk9BB@BEy9BBu@B9D{9aBB0@ B!C9bBB@BBëD.9cBB@MBbQDä9BB@lB9BB@B@EF=9BB@tBE9+BB@B8|C`9+BB6@B`~D590BBW@B_DP90BBL@W,BC91BB@1BA}97BBJ@SBǬPE69>BB]@@QAdE,9BB@A9^D9BB0@ bBqCy9lBBv@B?B9BBK@JB_#@S#9BBܧ@UBDTR9BB@%BC]@BBQ@_BdEkE,@BB@BBE0P-@ BB^@B@ BB@+9B>e`@BB@DBeBi@"BB@sB7@BB@SBKB_Y@+BB7@BKE @bBB@ZBo%B@BB@5TA쁊ETV@BBm@BAHE͗@BB@EP@"BB@KBgD@#BB@hBDcf@BBc@S B5PDW @lBB6@oBOE9w@BB@9BY@BB~@sB 3dD@BB+@C{BcD@BB]@^;BkEL@BB@B TE_iE@BB@ BDm@NBB@QHB XUEC@OBBB@i B}3C@BB @LATD5V@BBw@>?BE+;@(BB@BkTB+@(BB@B~EHY@(BB@B^cDry@)BBz@ZBE)@)BBl@BEh,@)BBxd@Dwk@BBo@~B2E;?[@LBBb@JBLE}@BByB@B64EL5@BBg@%B[Ep@VBB~@_BxoE@BBu @nBDoq@BFD@țBB@lB6Dr`6@NBBD@B6?DL@ӨBB@6B;D@qBB @WBDE4@BB@R%BSD#=@BBz,@RB[@BB@VkA@D @BB@dwB4*Eqn@D ABC:Y@(K+BECA5BC !@'8Ḃ EB ABB@'KfBΗEEABB)@&BzEpeABB%@&BE0߽ABB@&ErBO%D 0ABBg@&B+E<ABB@&KBEtf4ABBs@&;|BEiABB@&B*EA\ABC,@'pBrABCU@'KjBČD}ABC/@'EB=,EZYA.BCFM@'tBίXC̈ABCG@'~BfD.ABCQ@''BmFDABC?.@'BpEBmABC,@'B2?DqAVBB]@&BES(ABBX@&8 BɪDߘABCL1@'kbB]D q&AƌBBX@'2BrE<ABC(@'BЬgDUQAvBB@&BݯErAkBC/3@'նBmDޓ6AѺBCO@'LYBD-ABB@'@NB3DJABCEt@'WBE8KAނBC9&@'q`BRQEmAߣBCT@&BABB@&VBc=D,\AsBCL@&B}EeA9BCB+@(BC0@Bd'BC@/BCmOdBC#@BuD}dBC,@B CodbBB@=BEDd~BC@ BD'IdBC/@ fBClD]ibdBC5f@B pDe8dBC3@ XB8DTndBC0@ :B؂D"0dBC7!@!B̃DWdBC@BCJeBCWh@ ]BhseBC`@HB'D;eBC`@OAB%gDze BC\@ BDeBC[@ BbC3e!BCT@-BBÕe4hBC4 @ 3C A'e=BCT@BC=e=BCT@BC=e=BCT@BC=eFBCS@BEJAZMeH[BC[$@ swB,C7eH\BCX@ [BĜeH`BC[@ zBÙC;IeHdBC[@ 2BxCeIBC3@ %B不B)YeKBCcF@VBdeKBCd @xB CeKBCd@ACl/zePBCYS@ BΆE ^eVBC`@HB'D;e[BCY@ BoCk"eWBC[@ BeBCT@BJC,-eHBCT@BKC<eBCY@ BoCk"ewBCS@fBU C:eBCV@ CDeBC3@ B4CH?eBC3@ B4CH?e]BCU@ C D oeGBCZ,@ U:BDbeһBC[@ yBCl8eՒBCa@xBD]KeDBCa@xBD]KeqBC[@ BeDBCT@BC=e}BCZ+@ oBDeBCU@ rCΗCb eBCW(@ BCW@$>jB]DU?BCX@#?BqC!+BCW@$_BD,BCWr@$gBD<BCX@# Ba9CPBCXh@#IB/D~BCX@#BC^"BCX@#B!Dg MBCk@B%aC 1!BCk@+BB 1@BCk@B CX DBCk@$BAB NuBCk@yB yCUg QBCk@Bk}C5 QBCk@AJBE kBCk@A&A^ ͣBCk@ sB.bC0 ͥBCk@BêCN ͹BCk@B(;C# BCk@BlrCrO BCk@AB:(Cncw BCk@ sB.bC0 "#BCS@Bn\B' $BCR@UB  I_BCRq@ (B8 K4BCR@ B&A: X=BCS:@BC$ XYBCS@BCC ^UBCS@ؑB\BV aBCT@+B5C{ b(BCT(@ڈB7Cr b*BCS@B'IC/ cBBCS5@;B5pDE ctBCS@۹A4CX fBCSt@BAC jBCQ@B7Bh qqBCIZ@\B\A9> \BCR@@BMUCʷP XBCO8@tBA0 1BCS@BfPOBwQ qBCSl@B[?CF ABCOU@BaEjI BCH@B* BCQ@$_B2D\/ QBCS @BKaC TBCK0@,zBr2DU^ TBCJ@BiD%E TBCJ@MBAf\Dyu T"#BCRf@BKdD ^ T$BCR@B `@B+B?1@C[{mC6BC>p@GB/BC>h@B-AW_~BC=@&BC vBC=@&BtC͋wGBC=@&BtC͋wHBC<@9.B;h@B-AW_x`@B+:B?@T5CZ:/zB?$@ CFK:/{B?@eCNF,;:1B?@TCQ:2MB?@XCNC@#:2NB?@hCOClh:B?5@-qCJ:E:B?ȥ@4CIEBB:B?@CJ3D:B?m@]CKB?@uzCP#EK/B?@NCN[C[/B?!6@c/CebVEG/B? @ CfE7Q/B?/@eC^D/B?n@iC_D;$/B?\@CHFEC/B?o@MC~eF>Y/B?~@֯CPF/B?Si@tCS[}B6/B?b5@iCSTE|/B?a@aCfH CM/B?@a(CfZC~{/B?k@f'CbC0q/B?h@bvCW}0HB?3k@mCTF30IB?6T@BC_Er0JB?67@HgC] E(G1B?&@aCf Du1)B?@TCSBAZq1+B?@T5CZ1cB?@nC`F^N1B?@C?mbFI1B?R@3CYE$1B?R0@*CYLEح1B?{@;Bl/F1B?Y@C|DF&j1B?@CI{#E1B?'Y@C`hD2B? @`C"EL2NB?@DCH_rBOx4 B?)9@cCh͑4B?@jCIMC4B?V@CQFP`MB?'@CE;:MB?L@#LCaEv3˖B?%X@nCd5Ci˥B?+v@hCd=B?$@4CfkCyqB?$@4CfkCyqB?!@ʢCEB+}^B?$@C`/_CNB?#m@CbHBB?'@육CDB?!=@졍C+E2CB?@{ClER0B?|@rCSB?@CSoB?@fCF4B#B?%@9Ca|CL9iB%:G@OwqxQ#2#B%A@qD;Q#Q B%@@^@`Q#VB%C+@USQ#dB%?@KW*RDQ#~B%A@qD;iP3B%A@qD;iPRB%<<@@֢Bi](B%<^@\A3Si^B%<<@@֢Bi^B%<@@ ?}idB%<@@ ?}ig B%>@#!EzigB%<@@ ?}iwB%>>@ uA_DpqB9@/hA4CA:BQ;B bNB!n D$DN=@B!mD%BWB!n;QD!3D B!mD&-MB56(xB!nVD @`B!nD#IB!n>D!B!5k[D$oB!n"D#|P!B!`mD$B2RAB!mD%BW}B!m D$A'}B!n"D#|}B!anD#nC霓B!^m)D&KCqN霕B!m,D#vB!霥B!mGD'!wB!kzD%B!&mD$=B!nDBD DvB!mnD%wB!`mD$B2B!kD%}B!m߯D&'B!mD$)BGB!mD%DC>3B!Vm:D$QD)cB!kmD$C9B!mlD% C<B!mD%DtB!^mD'D9B!ImoD%%CB!NmjD%D'*C<B!m-D+BVB!Im=JD%C+B!mCD(BDt%B!mHD$BRB!m;D(?!CB!%k`D$հDDKB!YmbDCjB!4mDBB!my8D$AgB! kDuYD;rB!kzD%B!kyD%?IB!nD$(9D|HJB!mD%D(KB!imLD%q}DU53B!mZD&)eB&JB!^m`D%qCDB!nDmD[B!lD$_B!>mF#D"?voB!nD#sC/dB!SlDbB?AB!~naDdP!B!m~D%CP%B!-m+D&NCP)B!m&D$a=BP1B!mMD'C>P5B!m1D(CffP]B!Xm:rD&MBPeB!mMD%~B}{PmB!jmD!D$_PuB!mHD#^C<PB!TmF7D"8A |QYB!mJD$kC:RAB!mD$9CoRB!myxD@BTB!9m4D)B;UB!DmaD%r=}B!nD#C;n}B!mD#D}B!nD"DJ}B!nD#uC@~B!gkD$?O꜓B!nmUD&Ӆ꜕B!mD${CS꜖B!Vm:D$QD)cꜘB!mD%jC-^ꜥB!myD'BꜷB!mqD%7ID^ꜻB!hkD$B!mZD DwB!kD"ꞴB!kOD%{DꞵB!mJAD%XD hꞶB!rmPZD(+bD6ꞽB!jmDD$-tCvOꞾB!mND$hB!slD CÿB!kyD'" B!mvLD@B!nJD B!n,D"N9DB!kD%DKBB!k;D$ACfB!kD$@wTB!kzD#jD]B!kmD D zB!kFD%cDPoB!kD&CkB!kD%B!kVD$B!kzD%=B!kx\D&)'PB!kD%C`8QQB!kvD$~B!k,D&WC4B~B!keD D~B!ikD%#B?뜛B!kOD%{D뜻B!kD&;%BsMB!tk|D"L7D[NB!kD$SHD?B!CmHD#"C^p)B!m D%QDD =B!mGFD$[RC_A>B!BmD#C.@B!mBD&7D AB!mpD%CRBB!lD!HD-CB!pmiD&QC}]DB!m D#CWrKB!.ll;D!$'B ]B!HlvD!,jD:^B!lSD%B0_B!lD)D4z\IB!lD#AнB!m(vD'uB!mg/DJCaB!mKD$eC1B!CmqxDyC`B!mvLD@)B!lD\C,B!l D#(cCFN}B! lD"|C`PB!0mKD'ECPB!mCjD'APB!emCD!CDPB!OmD%CUIPB!m D#PB!^mD@Bp/QYB! mPD&BZ#RB!OlדD!BB!l@D!&DB!VmD$D_bB!Ml%D잽B!mVDUC[B!5nD" D`B!D1E3B!u7D0mEtB!ulD1DB!w"D4$DXB"hDiRB/B"Li{TDOB"O|'0DUalDB"}pDbkF|6 B"X2| DY4D B"e$Do ?DB"tDiVB"drC,DvD3~B"[&}DVTlCWB"e,GDmALB"e+DmA ϸB"Fz|DJ DSB"K{RDPsE=]B"EFzDG7D:yB"L{oDOCB!wD5J}B"c}}"D\3B"^D{3AB"aDMFULB"p]~TDYFսB"hDiRB/B"_nDU DB"wmzD[$Ec( B"'DlQtE=lB" uDW]DȿxB"stDpFYB"Y|DYüEf+B",Dg+@EEB"eDlD4aB"p$De\E&SVB"DpEX B"YDf$ERB"%DqQDNB"PDq 8EWlB"ivDuE"># )B"hvDlTLDw ,B"abDtD -B"e+DrX_E3[\ .B"`|D}wDUW 0B"Dg,F/ 1B"e+DrX_E3[\ 5B"}v+Do:Eа 9B" {B"w~۲D]"Et |B"g}DXE}4 B"{*2D]IxE+v B"o~KYD]oEJ2 B"n3Dlq/E 1!hB"W|xDT"{B"j.}D$B!krD$߶B ?IB!lD"DnJB!ZkfDDOKB!lD!DCLB!CkD%D6]B!lTnDB}^B!*l D% %B!IkD3B7yB!k DDGoB!kzD%B!lDlBIB!,lMD!+A2IB!l=D CmB!el(D^HDOB![lD D B!UlYD ;BD5j,B!lD".$CCYB!kD%[B!)kDD\B!'k>D$Bn]B!kD[_CB\^B!*kDD _B!kD&D.:`B!_k%D?mB!4kD$C-B!m(vD'uB!1k|DyCaB!lD!-Cc=B!kD$j=B!kED%[GCdNiB!Bl#xD]cB0O]B!kDBpTmB!mkDD#B!6lD ؃DGlB!lD"XD7wB!Dk}D$B!kD%cTB!l,D:CB!l6DFC=2B!lD#]CB!IkDѷCB!kDDY>B!kD~5C,B!TkD`DBB!l!D%MB!5k[D$B!UkD%NsBګiB!ku3D$cDkB!kuD$BsB!1kD%C(B!kxND%Cne!B!k~zD$ICB!،pGD9B!>qRD!qB!n}UDHBÌB!xp DRNDVB!oKD|E%*B!SoDDPB!lp]sDWCmB!qD DB!MqQD!D(nB!܅s9D)AD<B!rD'ZDB!"ss D*ĥDeB!ޗskD,} D?B!8t.D.{DB!tD,qLDB!tD,B!pDKB!`p~DDIDd9B!qD DB"H9zDK- B!oD "B!CnDDCR|9B!qD#VDB:B!qD#;C|;B!ڷr$D#8DeG8AB!ÊnwDC}B!n BD#rB4B!ȍnD2E(B!~oODDuB!kso0D* DB!{u=BD2EbB!DpkD DB!nbD:*DB!n_D DB!Wo1@DB!TrnD%nIDJbB!ӫoD)B!nOD#ݰBB!ɈoDjE.B%F@ lBB%F@ lBB%F@ lBB%F@ lBҜKB%F@ lBҜLB%F@ lBҟB%F@ lBB%L@ 9B{AvB%@@ :tB[B| B%D@ 9BϸBXT-B%=@ 9lBBxt B%6@ :B=@BT  B%@@ :tB[B|  B%@@ :tB[B| ӟB%D@ 9B|BYRӟB%@@ :tB[B| ӡB%@@ :tB[B| qB@+zeD)q!B2@/V@rOBA<qyB2@-^C Ei7qB1-@/) @曐D ̝qB.|@. C cEbqB?@/qA^5?@/uA;AclqB?@/A2-qB;@/AwD3.qB1X@/I@яCcyqB9k@/5D?}D:OqB1@/@87C6[qB1*@/1A CqB5@.+CfEq B2@/@(q-B4u@/H;A5mq B8@/-i?Ⱥ?D?q jB8n@/4@>DꭃqB6@.՘C-oqB2@/@ABqB8w@/9zA DEqB5@.!TCoE9tB3O@/VA^2B_St jB3@/&@%uB@+zeD)'#B&EnBD @1:Bf\zAnhE:B i_BD\:!BeyVB@E:"Bey_BA5EG:)Bg|oATDf:*Bx+tSBFE&/:+Bgc|AuKD$:,Bhk}&AӢDo:.Bf|zAǞnCd:1BwsBE:5Bk}ABgS:6Bg|[;AEUTD4:7Bh}XA]D7:?Bh}'A@D,K:FBh}WUAQD: :GBk}A|A&:UBh.}AbDM#}:`ButYBqDԟ:jB~rLBD,:BowIBJ*E:Bdy2BEk:Bvtv@Bs=EϿ:BeBjTE%:Bc|dA9@BCC:BIqB= F#I}:BeBYE}:BY]TB :B]\BE*:B3kSB޿QB nBQ:Bf|BwsitBET:BԠ]B:B[RA9>D=:B%UBDW:B jT;;A3Ep:B޿QB nBQ:B#UsAiCD:BWczB D{:Bl\ B F,TFTaB6@.zFVd:B3kS@/@9C1C#B3@.BHE #B6,@/\rDXr,B4@.!%BŘE-B7@.E@n.B7@/=E/B0@-±BpE]AB2B@/9@D$XB4 @._?Da0YB3@/"A D1[B8E@.0SE8_B2R@._ԜB˝aB1@.vBsB2@.iC tB0@/A*uC0m}B0@/,]AA}B0@/(AZߢBϗxB:@/HDB3B@/XAJBxB2@.]/5CB1@/,?hbZDB0@/*A7UB{B;.@/%E][B0@/-cA#*B6}@/0@$ 0B6@/+o@eT+B0h*B87@/?xB@.+C.B9@/-?eDjDXB7@.Q? B YB4}@/Ih@B(sB2@/(%B%@ =C67%B%Y@ A/%B%@ 4rACwV%B%o@ )AMCU%'%B%@ B`gCGU%'CB%@ 2B6|C"%'YB%Y@ A/%'kB%U@ ǭBPCS%'lB%@ GBJC9%)B%@ C=h%)B%@ ըC9%%+$B%@ BDm%+%B%K@ B{_D%+&B%@ B7%+NB%3@ 4B?B %+QB%@ 2B6|C"%+VB%@ A(%+WB%@ #C_Di%+YB%@ J A$%+B%@@ MC@ BV%FB%Q@ Ap%P B% @ LCDx%TB%4@ 0B*B%XB%o@ )AMCU%cB%o@ )AMCU%uB%@ @AQ^C%v5B%3@ 4B?B %v6B%@ D/7%v7B%@ 4 CDF%vB%@ PC[Dc%vB%-@ LCgDuQ%vB%@ LC`DB:% B%@ J A$&;B%2@ 8NBTL&?B%@ 4B1*B%7@oȴ*'NB%w @IAPĜ*'OB%@PcAg33*( B%G@/AD*(B%H~@ANhEP*(B%/@1E7P*(B%9@ *+B%S@A$1*+B%P"@B@O\Dq*,B%S@A$1*,/B%YG@_AvĜ*,1B%E@a>2D26*- B%g*@zAEx*-B%a@j AD*-+B%l>@AsD6*-.B%l@A>D*-LB%?3B*-WB%h@{.A0~*1B%@AD)*4B%oz@q+AD*4B%m@b`ATD/*4B%L@?V*4B%H@ZE*4"B%L@?V*=B%j@TAKK*CB%b@TAaVD *CB%]@,A*CB%]@,A*DqB%7@@L(*F[B%G@/AD*F\B%G@/AD*F]B%?@? eE/*K B%l@A>D*OB%`@RA9xDU*OB%Vz@zAMBLD'*OB%7@oȴ*S%B%C@5#g*YB%YG@_AvĜ*]#B%@/AĜ*]B%@/AĜ*]B%|@ADwS*^B%h@rA^*^B%|@QADb*^B%|@ADwS*_B%u@2AzZDRo*_B%@7`AX|AEP*_B%f@AD7*`B%~@RAIDT/B&t9?OD(E </0 B&n]?w1DHOEc/0B&k?D ZEt/0B&e?ߙD/42B&ea?UD%?Db/JcB&jZ?rkD&OE/V/VjB&m4?@D#+E/xB&l??~D$E/yB&r?aHD'DW/B&l?VD#YE/B&i?D) Ed?/B&ky?+D%eE:/B&g?ꤸD \J/B&r?D1/B&l ?:D LE/B&r3? nD.slD.{?{B%~@ Aff? MB%@ A9?*B%y@ mBfC?*B%y@ BqBG[?*B%{@ XBIbC?+ZB%zo@ vB4!?,B%~@ B+FDuTs?,B%z1@ Bx TCTW?-B%y@ }Bc?-B%zW@ B@ BVICB%@@ MCM)&B%*@ f-BCZ M)AB%@ C;2DM)BB%@ C2CگwM*'B%@ CDBڟM*(B%@ BEB:6M*)B% @ |BjD.M-B%@ C>M-B%@ C>M.B%@ kBM3B%y@ BǮ?`^MIB%@ C>MSB%@ ՐC7YCBMWcB%i@ WCCMWdB%@ CDkUB%@ ,CF@TSU*B%@ ,cCFU,B%@ C,1hC#GU-B%@ ,cCFU1B%@ C>UA>B%@ +CGbUO/B%@ ,cCFUSIB%@ 'CBBCUUoB%@ C>]B%9@ ]:B%"?ͿEI]:B%H?nA,Dig]:B% ?qBIE]:B%$?A#Db]:B%!?fB7E r]:B%!(?ADԌF]@B%?AwD]AoB%$?AKD,]ApB%(!@HS@"EW]AqB%#?ދDEDT]DqB%3@"@ТD/V]DrB%2r@R?/]DsB%+-?AcnE]F[B%S@A$1]OB%"O?ATUD]OB%5@`w&DR]OB%8q@a 6;ED:O]OB%,? A?Ey]S!B%2@d?D©G]S"B%5r@c3D5]S#B%-@\@:E{-]S$B%*r?cp@/DyK)]S%B%6@C>T]S&B%,T?]@~Ek]S(B%#?"y@柾]YaB%#?{Aն)DsY]YbB%?ADJ]YcB%?@+]cB%(?DBg'5BΤL]cB%(?@xB:A]B%(?IBeBG]B%'?AsD]B%"?LTA°Dn ]B% ?BVE ]B%!#???E\#]B%"?A:D]B%?`MCp/a]B% u?qA;cE]B%(?EB]?A˴]B%|?8tB2_B&? C?F0_'B%г?B>E'k_'B%?OB2\E_'B&`U?vjD0~_'%B%?"B0DH_'&B%g?BEzy_''B% ?B(fVEEB_')B%?B)XD@__'*B%?PB'̮D_'/B%Z?B8D_'0B%? AD_'1B%!?B'E6:_'9B% ?6BNDv7_':B%@!ZB&1lD#_';B%5@~AwcDă_'B%@@B DY__'WB%@;A…Dq_'XB%u@2AzZDRo_'YB%u@2AzZDRo_'[B%y@AHE_'^B%l@AuD)_'aB%_? B~Da96_'B%?%BtD _'B%f?Bu _'B&u?CbF_(QB&f?_D!_(B&?CD(T_(B&?CC3DF_(B&"6?pRC(MEV_(B&Y ?2DyE_(B&E?cCÁEQ_(B&K`?$C߲uFcq_(B&%:?MC6mE3_(B&, ?UCU̧Df_(B&8?QBF%_(B&`(?kD/]Bm_(B&8?3dC̲MFr _(B&?8eBE( m_(B%?BE_(B&?C$E"|_(B& ?2BDMI_(B&3?$C $F\@!_(B&"6?pRC(MEV_(B&g?>D% Dk0_(B&dP?<D*DDm_(B&?{BRD_(B%?ZBMxEm_(B&j?0cBP_(B&$?qC3FnC_)B%V?j9B8D4_)B%a?oBUE _)B%?%BtD _.B%@uA_.B%?_BD#aCd B!kD$C)o B!0k.D""C B!k1D rC@ B!.kOD!>OC JB!kȬD 3D Bsl"BGc`Es/ BtuYA`E Bmz9lAAZE4 iBkCyQAwE. jBiyAD  Bgm|AbCC Bgp|H@b 9Bfd|A Cֶ =Bm{RTA(` Bf|@€OA Bu}ۚAZCʹ BpBBLE$EQ BpwYA[E BpkBZlC +Be|@kzDOl BBs@}_AuCҹ CBoy}^ADO` DBq~"A;(Dd BpBBLE$EQ B{s@BE; Eq BdqTB),D_ BԿWAF4 3Bȿf/]A Bc~AjM BrS? D B!'SAE B SãAE  BU?09Ea8 BBc]AvvCu, CB¿k~B>aH _BBW@3o bB oXV?U˻EځO uBQ{AC ?x BS8A[D/ BXQO@gAA B#U^3A9X BNQNi@A BdWAZ sBEQvOA1Z@ {BvT显nEB  |BR3A8CD)FOkB=AC9;FS]B=A^C9A%CCD6JFSgB>|AȞC:F'LFSkB>A6CFN7FoB>ACPDͯF%,B=MuA~BNF%,B=EAB;F%/\BBd~FbF%/gB?FeF%3B=!PA6BF"F%3B=CA}B[#F%6B=YAn*BF%6BGAB.F){B?0zABF){B?/~ABCF)}B?A$BJF)}B>AyB@EKF)}B>Ay?B F)}B>A-BYEI F)B>kA8BF*,:B=AHCDE$syF*OQB=ACZ6F*OxB>IACUF*OyB=A7CUDfcFF*OB=ACEDK,F*S]B=Ac/C'CF*SB>AKC!CF*SB=KAC,JEvF*SB> AC#SE߬F*SB=AuSC:F*SB>AVCJEk F*SB>AւBxEF*SB> )A=CD E(4F*mB> LABEIF*oB>AB[F4 /F-B8wAB٠{D )F'AB=AGC:)E2F'D9B=AArCk5F'EB=%AdB'F'IB=AzB)Ei@F'IB=|IA~BDF'B9mALBhsF' B;ABݙF'B:*ANB!iFmF'B;rA(BBF`F'B:FAPBنxD!vF'B:VA`BEF'B:3ABF'B:ŭAqB4FL F'B:A*B&EzPF'B;rA(BBF`F'B:rA Bf FI?F'B:AnB؇4DRF'B:kQAB.FcaF'B:A]HBGVFfF'B:oABøEN5F'B:ABʓuF'B:UWABŠ=F'B:cA(BFIF'B:A'B{FMF'B:O(AFBJDLOF'B9AeB4F'B9ABF/F'B9AKBpF6!F'+B9A B>gFXxF',B9[A_B\+DAF'.B9YArBOF5F'/B9ABʚEPuF'5B9AB5DeF'8B9AۂB EOhF'9B9ABFF'?B9FAIPB-{DЬ[F'@B9yABs!E_F'BB9SA0B(E?F'VB8ABF'B:?ABBp=FfF' B9ApB'EPݼF'0B8 ABDjF'1B8AB_dBhF'B;%A>B|jF'B:|A@BܰDF'B8ABF'B:1ABF2F'B:AGBBFOۀF'B:SA9BEO1F'B8A܌BF'>B9AACQF$F'?B8CA82BENF'@B8A BqEBtF'B;,*ABDF'B9ABBFF'B9JfAHBB=AQCWEˀF',HB=UA%CH:CebF',B=eAWCh\DF',B=!As4C DΞGF',B=IA@lClUCtU#F',B=ABXE0fgF',B=AhC0_EF',B=5AhCGEa?F',B=AkTCUET70F',B=xABHظEpF',B=KA3VCuF!F',B=vAB#DF',B=ACkiE%$F',B=AȕBBODF',B=ABT,E{qF',B=A]BDVF',B=PACOCp'VF',B=QAeBF'.KB={AC(fDlF'.LB=MAC-JEhF'.MB=#AC^FEF'.NB=ɈACBDF'.OB=A)ACDeDF'.PB=>AC>5EVF'1 B=AnC\C9F'2B=TAPBDٗF'2B=ABDB*F'2B=A7B*rEGF'2B=^AC0VE%8YF'4EB=6A9,CfsuF'4HB=6A9,CfsuF'4IB=6A9,CfsuF'7B=ABF'7B=A BDF'9B=CAbCeF'9 B=AeCbF'=B=AbC/U_EF'@B=A(BAF'D5B=kA:CiSD?F'D6B=dA?CjC.F'D7B=A'oC< EF'D8B=A2Cf.VF'D:B=PA.0C^DF'DVB=A kCZuC׮F'EB=AӿE(hF'EB=A1BEiXF'EB=TAPBDٗF'EB=AC^#E)YF'GB=ACPF'GB=AyC0DJY"F'I{B=AB}qF'IB=AuCU`F'OMB=AC 2TEF'ONB=A9BGFi[F'OPB>AOCh"EF'OQB=AH!FsF'ORB=A/CʵF޽F'OaB=AYPB"E8F'OeB>pAGC DF'OkB=AdA FTF'OlB>A|Cy^F<F'OnB>}AC-wEU1F'S]B>IA=7C&E0F'SaB>#A.C?F'SkB>A/C+{E>F'SuB> PAՙCMeBE2ĨF'SB>kA-EClDYiF'SB=AYC.{F'SB>A~C{r_FK 3F'SB=ABIEe9F'SB=cACYF'mB>qA|C%CF'B=׭AC"\E?F'B=ۭACC1EA CFFNORB>ARCS[FNOaB=aA~CFbFNObB> A#C|)7EFNOdB>AwC_EYFNOeB>A sCEFNOuB>,A~CdEmFNOxB> kA?DFԚ$FNOyB>Ao[C[)KE%jFNOzB> A@C]E\FNOB>-A*F02FNOB>:vAEԼFNOB>2PACT#9Eڏ FNOB>LAjCL'Eq'FNOB>NA`4CSsFNOB>TADCJD6 FNOB>NACAmC+F z!FNOB>AB*F FNOB>ACAbF/AFNOB>LAZLC]EĔFNOB>mACcF7FNOB>AC EPFNOB>pABE~LFNOB>AǴCUF"LFNOB>AC}ACVFNOB>aACn[EɿFNOB>ACZaE$TFNOB>AP_BFHFNOB>ACEFNOB>AеCX.FNOB>ACN#EN\FNOB=ACV; DOyFNOB>A/C]{E^FNOB>6)ANCbX8EĈFNOB>)A CYEرFNPeB>MyACG#DaFNPoB>N>ACIEI]IFNPrB>MA&C+EFNPtB>LACVYXFNPyB>O;AjCU[EhFNP{B>ZA7C2VDepkFNP|B>QAICTFNP~B>s7ACZDͪFNPB>TADCJD6 FNPB>QAICTFNPB>]DA0C+DFNPB>QAICTFNPB>hACQREyDFNPB>rABEFNPB>rACEMFNPB>A2CFE<FNPB>rACEMFNQB>QAICTFNQB>7AcCBageACiDFNQ1B>`A)CND4NFNQ:B>_A9 ClYFbVFNRIB>AwC$ېE1FNRB>vA,FSsDFNRB>UAhB(FI@FNRB>-AeBhE!FNRB>A-C,,EFNRB>}A"C#kFNRB>AVCĜFNRB>{5AC yEhFNRB>}A"C#kFNRB>A1gC0CE<FNRB>A'CT)EJFNRB>AdCCEFNSkB> AѤCL EEFNSuB>AC$xExoFNSB>pA^C&%EjFNSB> 'A_C*JFNSB=5ACXFNSB=ACK#FNSB> AC.1FNW+B>sACEFNmB> ACCFNmB>pACF`FNoB=A7CUDfcFFNpB>WYA>`C@mFNpB>{`AwaBF#nFNqXB>AjB!Ev9<FNqB>ADATCyF FNqB>WYA>`C@mFNqB>`A)CND4NFNqB>WYA>`C@mFNqB>c/A#CևFNqB>jA4CyEWFNzB>AGlB\bFFNzB>AjCKEEKFNzB>VAB]wEyFNzB>@ACCEKjFNzB>oAB#EQFN}B>DABP ELFN}B>ABsE<FN(B>GA0$BDe4FvOB>AICLD<9vFvOB>A}CFvOB>A#CFvRB>AC FvWB>HACFvqXB>!AHiBYFvsB>ZAECFfFvtB?A$BJFvtB>At9BEFvtB>gABw EM\FvtB? ABELFvtB>DA B0DdFvyB>XAD[B*lD}FvzB>uABsEFvzB>AUB$FvzB>ATBlEFvzB>ϡABCDEҒFvzB>A˧BwFFvz B?AB|6E'Fvz B>IAB~sECFvzB?+A+uBEFvzB>A\5BٺF~4FvzB?+3A'BxDkFvz B?'6ALBfwE`-FvzB> A_|EFvzB>ABFvzB?/~ABCFvzB>A@C%F~}FvzB>ACEdFvzB>A5C$XEFvzB>ATC!dFFvzB>A*C "EPFvzB>[A#SCڕEE FvzB>YA|EIFvzB>lACDaΑFvzB>Aa<9yEԪFvzB>AkBFvzB>hABE#FvzB>AxSBVFv{B> AsB0EJ.Fv{B>AW1^+EkFv{B>AN?C:^Fv{7B>Ay?B Fv{B?%AB:Fv{B>7AeB E&Fv{B>A C!KEHFv{B>'ABPDIFv{B>sABDDd3Fv{B> AsB0EJ.Fv{B>"A46CEHFv|B>HACFv|B>AC˕EaFv|B>AkC DNJFv| B>jA|@8EUFv| B>#ACjE*Fv|%B>"ABIF_Fv|&B?A;BFXFvB>A`IBFvB>AC rDFvB>sABDDd3Fv+B>$AB>ChIFv,B>ABDuFvbB?A BPFvB?ABEFvUB>܀ABEKFvB>ABF/YBE:DF/]B<#AʭB'F,W~F/^BB;ABHEIF@B;AwBEFAB;A?BEf#B3AMJ9C##B3iAN9pFor##B3(ANAyC#$B3iAN9pFor#$B3jAN7F#$B3IAO2A#$B3(ANAyC#B3(ANAyC##B3(ANAyC>B9 A"CH>B9 A"CH>B9 A"CH>B9 A"CH>B9 A"CH>B9 A"CH>B9 A"CHUB3IAO2AB)0AlCǰFB*AC)B*A#CFB+AOCѭC\B+ARC<,EѰKB).ACӗhFALB)i^ǍCk|EhMB)9ACχFgNB)[ACVFBfcB)'A,4C+dB)iA\CZEEeB)ACKB)iA\CZEEB)iA\CZEEB)iA\CZEEB){ACC`'E :AB+AC@EqB+A[C{E)BB*4+A~ CDB+ANC2D 3!B)ABCmF"B)ubACLED#B) AC]7B*AACfgB*4ALC~EB*&EAUCҶB*IAʮC\E,B)DACEB)/yAC͸E.1B*_AICbFYB)DAC4vE<B)ACXE /B*DAKCIbEy 1B)Af>CňF B)AC̑EE* B).AuC,FA B)ACXE B*hA*:ClhEiB*AED;FיB*AnNCFAjB+4kAGC9FYB+A~CMEɳB+=[ACREDFB+;ADtPF*B+wAgC,F oB+ACE5B*ACaDcB*QACE LB*AACfB*AwC7FB*IAoCDF%2B*IAoCDF%2B*^A#CFB*[ACFEB*7AcCFVSC'B2C'B2F7ACyEHC(4B2YACCE9CFB2AC+F,BCODB2UADF!JCUB2LA ]B)yCvTB2`@A$1C!&EFf*NB3,AXB1*O'B3,AXB1*v7B3,AXB1+B3:ArBBe~+'B35A{Bm+'B3,AXB1+'B3:ArBBe~+/B35A{Bm+:B35A{Bm+KB3,AXB1+NB3,AXB1+NB3:ArBBe~+PB3:AskBT+bB3:ArBBe~+kB35A{Bm+v B3:ArBBe~+B35A{Bm(@B4 A[\FF+B4FA/mB?FE+B4 AB'F6+B4$PAfBF6m~+B4uA1BP8E>I+B3AB]+B4uA1BP8E>I+B4"A|vBfBB3eABEBB3ՕABEJ˧BB4,A[ B F(OPB4ABѧuF!OQB4ABO\B4OAC>OB4AsBEHRB4,A[ B F(RB3AB ESB4ABSB4/Ag*B'JEJiB3AB]v`B4AB͒FyB3ABsF<yB3ABE8yB4ieALCqzB4ABzB4RAB3EB4!ABeFB4tAB4:F8` (AB4AsBEH (BB5A۰B.F\u (CB6GAlB2FI (FB6w/ABF (ZB5KAKB (\B5KAKB (fB56sABEƫ (gB5ABF,k (iB6AʣBڶE|( (pB6YAB5? (qB5ABx (B5ABx (B58AxaBe (B5ABx (B5#KA.B# 7B6EVyB/A=BhEKyB/7ACFpy"B0oA[BCF|ny'B/-A)BFy(B/eAtC޴EK&y+B0K@ABFF>)yB0PAyBKEQ}zB.AuB{]E~ˆB/A2-C>EVˆB0A(BܐDBڙˆB/sA|B B[ˡB.A“~Fߕ,B/ACACȊߕ,B/,cA}CpF*'B3PA)B'B3AGB*EG= ;B3PA)BuB3PA)BvmB4AqBRB8@wA'B֫3C2y0sB7ApB`FSG0tB7~AB%FD0yB8AA)1B'm0zB6AB#EJG0|B76AMB؉EG0~B7aABبs0B7wABӾw0B6ABۗEò0B8@wA'B֫3C2y0B7CABΧwF0B7CABԇFA@B8?A&.B/WB8A1B.E3 WB88]ABJWB7~fABiFL0WB7'-AB]FFxIWB8#?ABKEVFWB6A4CXFWB8?ABFwfWB7 ABVFGFWB6(A^BFmWB8@wA'B֫3C2yWB7 AB֜)WB8@wA'B֫3C2yWB7wAɘB8FFg'B8'>AB2-g-B8ABF~B7A1BөEŠ~B6ІABт~B7pABNF~B7ANBXoEH(~B8AYBFF~B7 A[Bz^~B7oNABE=|搉B6A/B!0B6AB౑EK,7B6OABu F);iB6A/B!b'B23A`tC+Eb'B2AtC2 EHb'B2AB 9EFwb(4B2HAC*=b+eB1ABb-B2LhA1C4Fvb.B2CA Ù|Eb.B1oABW%Ez]b.B2PAJMۡE3b.B1A2>BE“b.B1AwBE@b.B1mABSb.B0ABݡb:B3AO|Cf\)b:B2HA:C-nF Jb=UB28 AëLF"1@b=jB1`gABǘ~EJbNB2ǦACd_?FmAbNB2^A'BOE bNB3AO|Cf\)bNB2oAqjBbO B2bOB2߅AtBaEbO#B3AO|Cf\)bTB2DeAgfC 0!bTB1mABSbUB2LACEbUB1'AϼB[EKzbUB1ôA`9BiEbUB2UlABbUB2A NB%E bUB1ABYEbVB2NA#B͡EIObVB1D$A\BYEJbV B0A "BԨFp,baB2hA"CBE“b} B2NA3BFb} B2!AB~*EG"b} B1AwBE@b}B2JABևb}B2S AC.nE‹b}B1A>B EɆbB1mABSc-B0ABF c.B15kAÙBŶFc=UB1A=BsEB5cTB1``ABFcTB1ANoBkc{B2ABtc{B1ôA`9BiEc{B2UlABcB2A NB%E S+]B0 AWBՐiE qS+cB0ҏAA\DF$B,>A\DF$B- @A"DG'eFwG@B,trACTE)eB, ACXCR݈B,}Aܚ(_FFB,Ae!CE7B,AlAX;FRB,ACM F.B,wAC*Aי@ B6A\-Br7C{C B6A\BD3S B6 A\~B> սB6A\,Ba@ H HB6A\{BUR B6A\5B.Ǯ B6SA\C DP+ ?սB6/A\8CD ?վB6A\C`CW4 @_B8SASeBI7 BսB6A] QCD}DBd= սB6A\B0eDW` սB6A\BDu սB6A\:BYD3^սB6A\@B4MDC^վB6A\C`CW4`B89BASB|AaսB6A] QCD}DBd=dB89VAS{BCA~2սB6 A\BD9սB6A\hB?D?zB6A\!BlA%`B88ASػBeCfi` B6A\_BPdB89VAS{BCA~B6A\BC B6 A\BC $B6DA\BmA/,!_B8B1AS+B*~D63*սB6A\ BfA7Δ_B83ASB^E _ B8AStBJED_!B8:ASQC_El _ B7AY%l(XE_ B7AYB[GC_ B7HAXHÙoaE_9_B7zAX'B +D_9iB86ASBU&E6*&_9jB8AS`ÖTF_9kB84ASBѥE*_RyB61A\%C_S B8YARpE>_SB8AR-SBEno_SB8AR-SBEno_S'B8RASWB D_S*B8IAS^?E ._S+B81AScB_SMB8ATfBHDb_SNB7LATsBC rETa_SOB7ASBݲ_SZB8AQgBCݰ_SaB8!ZAT&B2EPr_SbB8" ATBxDA_ScB8+^ATcBY E`_SqB7ϪAX BF,_SsB8AUBF_SB7"APTBPMDA_SB85AT0BE p_SB89xASBf4Dh"_SB8:NASBCﳮ_SB8AQx'Bu/:D_SB7APkBCEy*_SB7AOmB"_SB7AOB2D$a_SB7 APBNff_SB7˔AT-2B!EU_SB7מAT HB@E_SB7AS3BbEZd_T B8SASeBAr_T B8OASUB_D{{_T B87AS;BE _TB8$ATBJEگ_TB8ASw }Fc_TEB8ARB7ME_TMB7FAOB6Dx_TNB7FAOB6Dx_TYB8AUBF_TZB7ZAUB}EEI_T[B7AV>B\}E _T}B895AS ?CEx_T~B8AS-FEQ _TB8AR$BIF7_TB8AUWFBnE_TB8AU|BEz_TB8ATӎB;ECg_TB8AQB=Eua_TB8AQBx E_TB8_AQ0BzOE_TB8HARkBEoB_TB7AT2B`B_TB7|ASBSEߺ_TB8 ASdB?EY_TB8ASBEVH_UhB7APBc>E&_UiB7AQBmE_UjB7uAPZBOVVE_X B7-AYB1D_XB7AYlBͲEK[_XB7AY]E_XB74AV:BbEr&_XB7AVB%Ez_XB7AW~lBXfE@We_XB7AYXn±[E\_XB7%AYBhD_XB7@AY `Bde?E3\_X-B7yAXMB2DaN_X/B7AVBWEV_X=B7 AXcBWyD_X?B7AXsXBpjDȃ_XFB7_AYnC4ME^g_XYB7AX A_XZB7|AWAYQ|C_XeB7AXBmF|^_XfB7rAW AE_XgB7AW B=`D%F_XiB7ޕAVVBC2K_XyB70AXLWBT2Ev9_XzB7AWRBoE_X}B7AXCКF _X~B7@AXBZwE'%_XB7AXdAzE_XB77AWB`D._YB8)~ATn`BDy(_gB7AYJB_6D[_gB7AYCEw_p2B7AYlC+E,_s%B8SASeB_;_s'B8RAS^4C6Cj_s)B82oASpBD_vB88AS4BkD3_vB8SASeB_v B8SASeBW _v B8>AS=B~Eh_v B83ATEjp_wvB8T5AS\vB>_wB87ATB8`D_wB8cASFhX_wB89@ASB8Aq_}PB7AY}BD5[_}QB7AYLB3ICC_}B8SASeB@U_ÃGB8OASD_B$D2_ÃHB8'ARToBYF?_ÃIB8JAS*B?}_íOB7sIAV@Q_íTB7AY[BE_íVB7AYhBtD_þ)B8AQBk_þ-B7APBs_þ/B7BE[u_B8PASKtBwD`B:DA2uO$P$?B:A2sOP B9A73 CZEt B: A7jDF?. ^B:%A7PD7F<$SB9RA4TCnD9B9JA5zC|EB0B9A5nYCՄC`0B9A5nYCՄC`3~B9IA6eCǮLB:A8DID*L1B:!nA7Df"EޢL5B;lA5DnEL6B;kA5D EL:B;^A55DuDKLEB:A2>CzDw_LFB:A2sOPLGB:A2|O3P4LOB9A5}"C;DY)LQB:A7(DCFrLRB9A5CoCvLYB:(A8'DUFsLZB:A7DpEL[B:A7LDQE;LeB;_A3D!FLfB;RmA4DD-LgB;QkA4*D@E7ULqB:JA7(D;DKcLrB9A4D [B/LsB:'A5D HF`LvB:6JA7LDK D0LwB:G A7,DÅLyB;DFըB: ?A4pCyT-E.qըB: A4DVCGըB9A4Dg ըB:HA7)DQCոB:1A9XD@B:A2sOPaB9NA6&C^bB9A5nYCՄC`cB: A4CsEc}B:A4CmB:A4!zCz>E'ڹB9A5SCC9ڻB9A5CqH@kB:BA8/D FPsB:%A8 D^F,ɘB:A2CE&B:A2|O1P6EB:A2CDFB:A2sOPGB:A2yOPMB9A5CCÿfB:{gA2CEgB:A25C mB:V:B'5AG^AxV>B'AGvAVB'EAGk5AGC+QVB'AG^ADx'VB'&AGnrAXCrVB'AGOBJCM$VB'AGvcABkVB'AGmA,?vC~VB'AGdizDG VB'AGc>1nDPVB'AGdBDHVCB'nAGR8B(wC%aVzB'AGpA/ CVxB'AGi̞?DVB'AG]^AܐD2VB'AGb\B bDqr!jսB6A\B=bսB6A\C`tE8HսB7CATsA5Dxս;B7AT~A)Eս[B6A\CYLDfCս B7AXSNBjDTս B7}ATA-E.ս B7%ս-B6A\:C{ս1B6A\vBiE "ս1B6A\wBDս54B6A\B٢DIս55B6A\mCETZս9\B7HAXBAԵEuս9_B7zAVؑ6p:F4:ս@B6A\CUhAսRB6A\BfEZսRB6}A\CDEսR B6A]uC3EFսR B6A] CaE<սR B6{A]VC!E&սRB6A\&CE սRB6A\=CPEBUսRB6FAZC{FսRB6A\CI!NCo4սRB6A\2CdE5սRB6A\CC%;bEO7mսRB6A] CLEȆ սRB6?A\CK ERսRB6A\BPD6սRB6-A\A EIսR*B6A\JCj{qDսR1B6[A\7CGPED(սR2B6A\ƒCPXEgսR5B6BA\BB)EseսR6B6A\B;)E սR7B6A\bCRwEXVvսRAB6A\RCDWսRBB6A\B@CEDսRCB6A\ȂC E*սREB6A\BY3E"fսRFB6 A\C l@EGGսRGB6A\jQBX1Da6սRIB6A\PC;3ETv(սRJB6A\C NE6L]սRKB6A\AEBJսRMB6A\BOEDսRNB6 A\bDdսROB6A\YA+DսRUB6A\VC\D:սRYB6oA\C/KDdսR]B6A\CUEuVսR^B6EA\Bt8E% սRiB6A\9B7C:սRmB6A]CpEսRnB6A]XMCtHE!սRuB6A\kA)?սRyB6AZBF#սRzB7XAY_B}M;EսRB7/\AYQaAEսRB70AY@EսSB7tmAW }EQսSB7 AUAD0սSAB7~AVAREսSBB7$AUpAURECpսSCB7AUhAEEսSmB7AU32@wD{+սSrB7|BAVPCA|սTB8 cAUHCBTCՙսTqB6"A\\CR?El"9սTuB7ATB =FWսTvB76AUN_A4cEWսTwB7CATsA5DxսTB7dATYOBxEr6սTB7ATuBXE 2սTB7AT0yBEwսXB7PYAX'0EqսXB7>AX@DpսXB7NAXF_@EgսXB7UAWCPFxսXB7zjAWAkEDսXB7AXzB1̭EV>սXB7جAW: BL?սXB7AV-BGDCBսX%B7f{AW EyTսX&B7#oAYBgFսX'B7hAXhe'DIսX_B7IAXTMEսXgB7ЗAWB,սXiB7AW.tBkE>kսXjB7~AWÈAKk_DսXqB7*AYzmAEսXrB7?*AXBBE_qսXsB7:AXcߙELսXuB7MAX/aQs6D<սXvB7; AXGBսXB7}AWH@X@ܫսXB7IAWBMxDսY5B7AVBEd(սY6B7}AWA#սY7B7AUAzսbB6JA\ȶBxA;սg B6A\w&B}@}սgB6A\qDu0սgB6PA\~BۜCtZսtB6A\B'ϮEtսtB6HA\XDTOսtB6mA\ CDսtB60A\'BscB[/սt5B6lA\C&iERսt6B6A\BE}սt7B6A\4BHսsB7ATǍBսB7wAZ !(E=Kս B7AZTBKDսJB7AU7AuEսKB7]AUQVDFL?վB6A\CFqCaվ55B6A\CDtվR B68A\!CCվRB6A\CZEվRB6RA[uCe F@JվRB6A\;C վRB6A\$BDվRB6A[)CeF2վRB7AZXBE/վR2B6A\wCE@>վR5B6A\oC^C)-վR7B6A\wBڶPD-վRIB6FA\ՅCTE4pվRJB6A\ۚC@,D|վRKB6A\CMDk8վRQB6A[LClEGվReB6A[BC1E վRfB6SA[lC%HE_վRyB6A[_CyZE>?վRB6A\PCqMD\fվRB6,A[CEվTB6A[sC!ENվt5B6A\C8D վt7B6A\CfCҒվtJB6A\BYDlGվtKB6A\"C+DPվ$B7AZC+RE}>վB6TA[CH%E ׿FoB6}A9?B}E&2׿FB6A9bB IlDA׿GB6}A9?B}E&2׿G B6 A9B.X׿G9B6}A9?B}E&2׿G:B63A8A׿G;B63A8A׿JB6}A9?B}E&2׿B63A8AnB'AGOcAZCۺnB'{AGtA*CnAB'AGN5B}sCHnzB'AGi74Dfn{B'^AGTA3=Dn|B'AGc7A+urDKnfB'AG_A kD+Nwn)B'AGUB5BjDn-SB'AGvtADPB7nB'KAGe^A}nB'AGNBCNDBWneB'8AG]AvyDhnfB',AG\{B!MDhWnB'.AGm(ACnAB'@AGP ADu.knBB'QAGN^B,D*nCB'AGJB+CnzB'AGbA#DM=n{B'LAGQAe#D a>B'AGvAB >B')AGvAAvu$B'=8AEARuLB'=8AEARuLB'=8AEARuLB'=8AEARuYB'=8AEARuB'=8AEAR N_B9A:+D\GE', N_B9A<D)F N_B9A;,DE N_RB9xA:?D{WF+ N_RB9MA:gDJEi N_RB9A:yDBC N_RB9UA:DEˁ N_RB9A:DE N_RB9&A;GDE N_RB9SA;D;C" N_RB9A;%GDE' N_RB9#A;D"Eu N_RB9!A:D ,E N_RB9ADJE% N_SB: A>DےEW& N_SB:"A>DngE;P N_SB:*A>D;Dpo N_SB9A<D3 N__B9A=DEnh N__B9A/DX]jB5AE@eD}X]jB5kADҠkDkX]jB5ŹAE4A BX]jB5ąAE,AKD~|X]jB5ƞAE6AyC~X]jB5AE3A\DX]jB5-AD9AHcdCaX]jB5AEH?pDkX]jB5AEAjgEX]jB5ADڎAV$X]jB5cADA/^B${X]k B5ƭAE7sA^CX]kB4wAHATDkX]k5B5ADA+{C&OX]kGB51AD@թX]kIB5AE7A ܳCg`X]kKB5AE04ACCGdX]kVB4CAHAGbDX]kXB4@AHABpDd)X]kYB4AHAC^X]knB5(AD6AAB?,X]ktB5ëAEA%CHtX]kB4AL*FѸX]kB4~FAJ/ADK|X]kB4AIAډDRX]l%B48ALcA?X]l(B4mAJ@=CBX]lB4AL-B4FBX]lB4|AJA X]lB5ǗAE64AzAjRX]lB5AE ?%EX]lB5ZAD'DSX]lB5ADݠ@BC<X]B4ALY[FX]rB5ĕAE%%YDqX]sB5çAE)!A~DDX]xB5:AEbAEҗX]yB5AE AEX]B5GAE@*AAX]B5AD%AGpBX] B46AKA:k1F9X]TB5A0o_E6bX]nkB5FA09Bi D$X]nlB5RA/B,AW0X]nsB5A0`>BjD!o=X]ntB5+A0llEPX]nuB5A0^}B,1D-X]nyB5A/BAZjD-RX]nzB5A0Bl C&GX]n|B5WA/B B+HX]nB59A0AjX]nB5A0BQCSX]nB5`A0BijZEzX]nB5A0`BuEYX]nB5QA0BUD(X]nB5A/B6DxX]nB5A/)6-E" X]nB5WA/fBBzX]nB5\A/BBBX]nB58A1aCgCL8X]nB5[A1:@@D%X]nB5A1EoA\U(E ;X]nB59A0AjX]nB5A1AECD@X]nB5A1^ADG%]X]nB5SA1YAE;NX]޺B5 A1 BGE2]xX]B5A0$E}X]B5A/%@ܡZD^GX]B5BA/BzX]B5WA/fBBzX]!B5TA/ByD} X]-B5A0)"Bu"X]ӅB5rA1RAezDvX]ӆB5|A1QAEE|KX]ӇB5KA1AD*X]B5A1u@CX]RB5A2tB*gEYX]SB5A1B= DǪX]TB5A1ٳB(DX]^B5uA0BZC!X]B5rA1RAezDvX]B3RAN}AaDX]B3hfAN=rAR`X]B5A0.Bn)CQX]B5;A1_BRF`X]B5A0ʬEX| X]SB5A4;yAǁDX]SB5A4AE8X]SB5 A44X@/0D6X]TB5A4pBCX]TB5jA4+ADt5X]TB5jA4+ADt5X]TB5A3B EQX]TB5mA3-B.EX]TB5NA21BVjEFpX]VB5$A6jBECJX]X2B5ݹA6eBG>D'X]X>B6A7HAbEnX]X?B6A7ߊADX]X@B6HA8 AD*X]m%B6)]A8cAtX]m'B6%^A8BAD^&X]mGB5A62\B,ӁEKX]mIB5A5RAElX]mJB54A6uBP@.EAAX]mKB5ϮA6SiB87EE,X]mLB5\A6 !B1E%HX]mMB5@A6BrCbAX]mNB5+A6ǚB EYX]mB6A8A?X]mB6SA7*?CDZX]mB5qA7+BEzX]mB5A60BBE;oX]n%B53A68B5pE4X]n'B5cA4ADX]n+B54A6 B<\ND;X]oB5/A3B3ttEvxlX]oB5TA2~B]E_X]oB5A5;7BEX]oB5:A4BDX]oB5A4qA EX]oB5#A4AչEX]oB5A5AvE>}X]oB5GA4B DCIX]oB5;A4ȿE5KX]oB5A2XB<ĄE@X]ߺB5A4AD$8X]ߺB5A5%KAD,X]߻B5A4;9°2EhX]߻B5A3nB(HEZRX]߻B5A3rBE^X]ѰB6)]A8cAtX]ѱB6&"A8JA耎Dd X]ѲB6"A80.A X]ѳB6&"A8JA耎Dd X]ѴB6&A8PnArhDbX]ѵB6&A8PnArhDbX]ѷB6 kA8*Aa%EMX]B5A5B ELX]B5A7@\BTSEWX]B5A6rBBJtE.HX]B5FA6iUBIE/X]B5A7 AKE!X]B5 A6B;X]ӌB5A2ABD33X]ӍB5A3BE0D.X]ӎB5A2KB$E X]ӓB5A4A^E*G:X]ӔB5A4|BKDlX]B5 A5:ANCwPX]-B5A2B@HE X]WB7TA1nC1۹CaX]$B6"A8.A6DX]%B6A8AAX]/B5A6B6ES>X]iB5A6VB9g>EX]kB5AsDEfKX]ZrB9A5PCiD'X]ZsB9@A4kCF.X]ZtB9A4Ck|E%X]ZB9ɩA5ACx; E#LX]ZB9A5QC:DKX]ZB9AA5}JCLDeX]ZB:j*A9DHxgF X]ZB:4A6oDK\F,X]ZB9GA4ЋC$F\[X]ZB9A5OrPsX]ZB9A5CyD˕X]ZB9 A5CX]\B7A1֡C4DX]]B9A6MOPX]a9B:A>7BOP"X]bvB9WA6,CޙE=tdX]bwB9A4C|NFo0X]bzB9CA4jCF0'X]b{B9FA4C{fX]b|B9-A4C9Fv=X]bB9A5CEX]bB9JA5C2JF\SX]bB9pA5ROPX]bB9EA5 C06E8X]bB9BA5U"Cy=E+X]bB9yA4C FݥX]bB9A5CTyEz4X]bB9FEA5hDV=FġqX]bB9qA4XCvFX]bB9A4iCwtF3X]bB:zA9'zDaF79X]bB:aA:HDZFiX]bB:ܾA:QOaPfX]bB:A:CObPeX]bB:܌A:CO?PDX]bB:HA:P+D+EX]bB;A7X]bB:2TA;yDF0X]bB:)A;DcF"2X]bB:A `D6DX]cB:OA>EOPX]cB:A>YDDX]c+B8FeA1CKdDDX]c,B8HA1CLz]CwsX]c-B8RA1#CDTD^X]c.B8EA1%CHD*X]c/B8RGA1CQ>"D"X]c0B8U|A1 CErDzX]c6B8@A1CI{DX]c7B8;A1CJ)oDX]c8B86A1CJ9CoX]cB8>A1GCJ=CMjX]c?B8SA1UCI&DOX]cEB8#zA2 CKD}X]cHB8.IA2CIC8X]cIB8%A2jCFEX]cJB8/A2CIÖX]c]B8AA1CQ{RDUUxX]c_B8\A1ΌCJ8X]c`B8;A2COF>'lX]caB8fA1 COE|TX]cB9(A8-DmVFAX]cB99VA9[=DyE2X]cB93A9VDuEBfCX]cB9qA:XlDfEX]cB9oA:N DNE ^X]cB:MAA5D@o8CVX]B9V,A4Co^FYLX]B9D0A4\DPBF'X]B96A4yDeQFmX]B9A4C oF&X]B9BA4fhDSB=FX] B9~A4 CFr@X]!B9 A4C)FX]#B:$A7CF/t7X]$B:kA7>CF!X]&B:MA8ZOPX]'B:(A89SD&4FSӓX](B:,HA7ZC"FwX])B:A97;C_FqBX]*B:MA8ZHOPX],B:LA:GOPX]-B:A:PO (P 2X]/B:ܿA:TO_PgX]0B:ܽA:SO`PgX]1B:A:KO )P 1X]2B;0A9f]DIsF3X]3B;SA8SDS.F[X]5B;\kA8HDEBFMX]6B;{A7+Db;F?(X]8B;A7DnF9zX]9B;{-A8ZDXhFX];B;A7iOPX]KDFX]>B;A7~DE:SX]?B<A86OPX]AB9A4mD!WNF`X]SB9[A5JDi,F!X]TB9UA4 7DV)FyX]VB:A5X]^B:L0A:kwDFGX]eB9?A5}D5S"G .X]fB9A4I(CdE~X]hB9|A4uC\FwX]iB9A5D6NG X]kB;,A<DBFQX]lB:A:|O P "X]qB;#A=MXD;xF#BX]rB;A=D_F X]uB;$A7OP%X]ǞB:IA>EOPX]ǟB:IA>=DE:X]ǤB:OA>EOPX]ǥB:jA>aD)F uX]ǦB:JA>CD E~X]ǨB:AA>7QOPX]ǩB:A>VDwX]ǭB:!A8Dz#F}XX]ǮB:LoA9P.DjTOF4X]ǶB8HA1CKCsX]ǷB8AA1'C?-DJQX]ǸB8HA1CKCX]ǹB8NiA1C^E"UX]B84qA1CJYX]B7TA1nC1۹CaX]B7A1QC8xE.X]B8fA1 COE|TX]B8QA1߿CODX]B8A2CRMFѼX]B8A2rhC5FCNX]B8A1nCJNX]B9'!A7ԔD5F95X]B9(A7=D~(E…X]B9'~A7SDkEsX]B9SA9D]}F=X]B9*A8'mDx!FAsdX]B9P`A9D:EUX]B9SA9D]}F=X]B9ZsA:pX]=B:A71D^EX]>B:0A7@DɈEX]BB:5RA=2DBC@2X]CB:@A>$[DEIX]DB:LA>EOPX]EB:A>pDE˓X]FB:A>m"D=F X]HB:A>7LOP X]IB:A>7GOP!X]JB;A=DAhEX]uB;fA7DjF+OX]҇B:A9rDDDZwgX]҈B:A9D EGX]ҊB:*A6DwF X]ҋB:)RA71DbSFiX]ҎB9A4D,oCfX]ҖB:A6 D,EgnX]ҚB9~A:3D;DX]қB9ЋA5DP(dF X]ҜB9A5NDMF#[X]ҝB9zA5;1D0lFyX]ҨB9A5DZWFE^X]үB9A4C٥FX]ұB9'A44BҹEUPLX]ҲB91A4ACt,FX]ҳB9A4C^F=X]ҽB;A: D:8RX]ҾB;2A8ٔD>SmFX]B9A5}C}oDX]B9A52CEzX]B9WA5MCcEQzxX]B;A7iOPX]B;A7iOPX]B:MA8ZNOPX]B:MA8ZOPX]B:A6}D E'fX]B:A6DDDqF*X]B: `A6D_CEͻX]B9A4C'PFat2X]B9PA4NC{F =X]B9nA4CFX]B9A6G C EwJX]B9A5OrPsX]B9~A5"OPX]B9{A5{CGDRX]QB8A1CZ$dF@eX]RB8RxA1}CTtE X]VB8DA1CI5#DvKX]WB7@A1CB^E!X]XB8A2 aCOX]ZB7A1CAZE"X][B7A1C;hEX]hB9'A6=D|DXΡX]kB9%A7lDE{X]lB96LA6{TD:EX]mB9;QA6dDvDDX]B8A2CNFtQCX]B8A1CSCy_X]B9' A7jD|@E X]B92hA8D\E X]B98A7DIKF%X]B8A1nCJNX]B9XA99D[E (Xu:B'\AG_3A D39Xu:!B'=AGB2DWXu:!B'AGBnCXu:!B'/AGn~BEHDMXu:!&B'AG]ABXu:!8B'AGOBCXu:!9B'AGBwfDXu:!:B'AGUB#D{2Xu:!;B'AGjBEuTXu:!B'zAG]Au7Xu:%?B'z\AGb;sioDXu:%@B'~AGqFAQB<Xu:%EB'AGhB \CwXu:%FB'AGeACXu:%kB'vAGpABRXu:%nB'RAG~A%Xu:%B'{AG*BԞCPrXu:%B'%AGB>C2Xu:%B'AGbA ,D;sXu:&B'AGpAXu:&B'AG}AC ħXu:@B'AGB"BKXu:@B'AG#D@_XuD!B'm#AGXA?!DRXuD!B'c,AGFA=jC.XuD!B'jgAGLAk/XuD!B'oZAH- Bc3BlXuD!B'U%AFACE)K5XuD!B'S AF@?E0֣XuD!B'PAF3A`E+-XuD!B'IAF1AV#ErXuD"B'oAH-^B:jtB9*XuD"B'wAHB&DXuD"B'qAH'aB:D'XuD"B'p:AH/B#IA&+XuD#B'XAG*?@ͦ4CvXuD#B'YAFEA:3E>לXuD#;B'o8AH,BhXuD#MB'vZAHAXuD#RB'IAFlE3XuD#ZB'GAEނAWEXuD#B'HAEH]GXuD#B'JAFv@DȱXuD$ B'oAH,B>XuDAB'MhAFLAEXuDAB'NAF>AHEXuDAB'NgAFk@qEXuDC3B'+AHAb@)XuDCPB'~:AGrB BӪXuDCRB'xAG`0??}XuDC\B'toAGh@LC cXuDC]B'iAGR @DyXuDC^B'o_AGY=A5qjCK~XuDC_B'mAGYA_EwXuDC`B'cHAGCA,FDXuDCaB'l+AGX A#gEWxXuDChB'AGyBXuDCjB'gAGLfA DXuDCqB'owAH,)B\ByXuDCB'XWAF? ̺EXuDCB'QAFA\EDXuDCB'QAF@L,EVXuDDB'u&AGg@CCDXuDDB'nAGYA>#D@_XuDDB'o|AH,BOBrkXuDE B'@AHACcqXuDE B'uxAHADmDXuDEB'G|AE=AE~XuDEB'JAF oD7dXuDEB'8MAE"qE\XuDHlB'AHA33XuDIB'IAF=@ʺaDQXuDIB'IAF A9DbXuDIB'IAF/)BVDÿXuDK*B'`AGAx@wRDXuDK+B'YtAG.@͟DXuDLB'EAEAv3+AvXuDYB'GAEB XuDu?B'NyAFUASE XuDuGB'M|AFbA%EGXuD͏B'o_AH,BeBXuD͐B'oAH-B:hC6XuDB'EAEy5?XuDB'^AG7AG}DҶXuDB'^AG:@hDXuO!)B'AGB'rjAH'A'bB!`XuO#?B'sAH$gB! DJXuO#B'vAHA:Bu9UXuO#B'x!AHB'E@:XuO#B'}cAGAJE:XuO$ B'prAH.B;g^CUlXuOBB'6AG1AkDHe}XuOC"B'>AG\7B-XuOC-B'AGrAyDѹXuOC3B'AG AۋDXuOCqB'oAH,IB_ @0XuOD}B'oAH+BY0A XuOD~B'dAG9A뼾DXuODB'o8AH,BhXuOE B'~nAHAғC~uXuOEMB'yAH3AʴDXuOEOB'wAH7B(3ERXuOEPB'VAGBxE&rXuOHlB'o}AH,BX/XuO͐B'oAH,NBVE@9XuOB'AG[B 0AJBPXuOB'AGARDCoXu_"B'wrAHB$ٰDwXu_"B'pDAH0B nB/Xu_#?B'p=AH/BXu_$ B'pAH.B-+rC'Xu_% B'rAH#B1OcDT_ Xu_CqB'oAH,hBW49Xu_DB'oAH-B9C^6Xu_͐B'o&AH,,BdlAL-Xub%B'GAEVXun$B'2AD@=C XunFJB'+AD~@"Xu!B'D=AEBwE .Xu#UB'FCAEݿ E!<Xu#yB'-AD@}DLXu$B'72AD^A0jEPK3Xu%AB'CAE,B-$E$ZXu%BB'<9AEf ;HEXuEB'EAE1A9_C32XuE9B'-AD@}DLXuLB'AAEsAybzEjXuLB'@IAEKA;.%EMXuLB'9-AD+Ak|5EjXuRB'HAECoEPLJXuYB'CAEiAEڑXuYB'HAEB 33XutB'2AD:AhoD&xXutB'.AD5@B'DXutB'/AD"@SDQXuuGB'EGAEA7Xu{B'2ADA@Xu'B'7AD@%EXu(B'2AD6@UEָXukB',AD@cDXuB'CAEAGE4iXuB'C8hE.jXLBAB֪UE*XLBA\C%:EXLB AtAF. _XLBAF)%^XLBҫAhADDe XLBA FCXLB AM!@FDXLBHAǍUE#XLBfAaCx FXLBڇA!c@[pEMVXLB~A9@0CrXLBA C"EjUXLBAnhB[F iXLB(AB 2TE0bXLBTA9CaFXLBAoB"E<XLBACXA DUXLB iA#6GECXLB +AB. E`WXLB A9!EXLBAb 0BE7HXLByA۠B;%EXLBABEXLBAE`XLBA^BfeEyXLBTA\mFXLBA&AXD!XLBAz.Cdw8EsXLBײA!kF2=XLBAr(XMBzA{aCphFVXMB A=B=KF3BXMBAl1CEEbXMBlA ~E8@XMBAOmEŲXMsB"AB+EשXMtBANCHʯFXMvBAi@B EXMwBA UEXMyBA< A[cEXMzBABrlEKXM|BAtE0XXMB;AcBE=~XMBA BsEFXMBA(BDfXMBHA?ICTMZXMB'A@LC3XMBAVAD*XNBQA#•&fELXNB4AkBE4i/XNBAc4EX‡ B+-AABCk:X‡ BBA=kE)X‡B$6AfdB6FE!X‡B ]AC SEV~X‡B}A(b>E/X‡BAP=BEX‡B AB!SD#X‡BIAHBom_E(X‡BACIF#%X‡BAv AH?$p~X‡BA 0B}E:]X‡BAB)Xˆ BA6A7IAkXˆB#AAxIXˆB%AQÂLjEӂXˆ BAcAMESDXˆ4BA.>AEcX±B ABCyX±B A@9\D]X±BlAB)LD|X±BABpEabX±B@AA_REe)X±BMA–bF-,X±BұA%!YEX±BAAREX±BACffFX±BACҜFA|X±BAxBbDDX²BAhCLEԢX²B]A *REv&X²BAELWX² BAB5 F"Y5X² BDAKzD X² BڝA?ndEDX²BACNF'X²B=A°DFX²BhAlA&7CEX²BAacEZX²BA w?Ej#LX²B0AJ)teF>lX²BAAC_9E\8X²BA"ECX²B lA69 E)X²/BA2AFdEX²0BAiB D:X²1BA%X²2BPA΀Ô3FpkX²3BAB7D)X²4BA)A5ιE'X²5BA}7BEX²6BvA4/µ7EkX²7B0AC L.EX²ABA,E^DX²CBAÓEX²DB!ATAX²MBAW=EX²NBvA]tQQEcX²OBggA CFm;X²PBAШAF @X²QBAHCAF>X²SBA.‹F]D&X²TBCACFX²VBkAh0CsTuEgX²WBAF'(tX²XBAMCF{X²YB1Ab~.2FPX²lBA BS=D6b X²mB(A2.ApD}X²B6A@A+GFiX²BOA@JFX²B&A_sBCFX²BBAS/FX²BA5B4EJX²BAGBFX²BA@CEtX²B9A?BU`EcSX²BEAwBEX²BZAArD}UX²BqA@E4X²BZA )B EDlX²BVAB^X³BA?Bu]DMX³BA#DX³1B'A@LC3X³2B+A?CX³3B'A@LC3X³4BA;~@b|CuX³5BA1>E>GX³=BA7E:!X³>BAA\E\X³?B A_1CElXBDAB#dEX4B3A@;EtX5B%AB#PD$8XlB*AARbNXB(A\A@ EXMB&AE6eXMB5wASaCSE4<XM B?AFDXM B;&A@&DXM BDZA B(E!>XMB"A ADԁXMB,AȩA^CEXMBrAslAcXMBAn#|AyAxXMB:A4AE|XM!B=A]@XM*BA B0F&XM,BAhAFXM-BAAKXM/BuA>A(XM5BA mCFF`E6kXMXBTA[A[?}XMsB!AAgDXM|BOA=B;CtXMBnSAܙAlF71*XMBcA mBoC3X~BbA33AEo*XÇ BA1?1'XÇBvAAEXXÇBCABE8ZXÇBXÉ:BlAADUXÉ;BTAJmA]*E{XÉBA{SA`BXBYA+#A?}XB5GAAy7XBq'AA$F1XBA{SA`BX BWA÷AX pBoAA{7X B'AЗArX B.AAZRFL)X BsAA%.FL[X BqAhoFX BGAZA? X BA̢ASGFLX iB AɊALOFF)X B)ASAB<AgCWA (?B<ACߺECU.7(@B<~AzCšBd(uB=AؠCoئF(vB=AqCQE%p$(xB=AD,(yB=A.CE&(zB=D/E B;@8WCPkDڒ B;@?bCD B;<@$Cз`D:O B;q@\CD2D7Lz B;G@?D L B;r@8D tpDUR CRR fB;>@CD_% {B;;@~OC&D[` |B;/@p-CCZ }B;@shC/DX ,B;@D B 0B;x@nC={Dn }B;ʨ@'-DC  B;@eD B;@<C^#DB;P@+CDQSB;@tCkD;B;͖@푅D DB;O@CRRB;@D D"fB;@Y`D 0DXB;O@B;0@RD DEB; @D(B;@]D~C "B;@5GD E0"B;i@PD 7E@DD,)B;͂@D D2y)B;i@nD DI*B;a@D DsU*B;z@DEN*B;@HD U*B; @DE %|*B;f@ED0DZ;*B;@(DDY,*B;B@DDN-*B;g@bCƞDO.*B;Y@3CŏDRa*B;؏@D5D*B;@?DD*B;c@ugD .D +aB;@ED8DP+iB;l@4D-B;(@/*CDPe1EB;N@NDviCg1FB;l@%D3tBFD2B;г@q+D "D k2B;j@]D u.Dm5{B;O@CD" 5}B;ͭ@UD 2]D5B;ʋ@CD79B;@RJD)7CB;ʁ@D B<B;ʽ@.C%xD:=>B;@TDDT:=?B;m@]D uDAT=@B;@4>D =CrC=B;@bmCjDY=B;l@h;Cݵ D d=B;z@0~CVlD\PQB;ʳ@*D ^jC^aQB;@*D bEܖQB;G@TD*DQB;u@R CDhmQB;c@D C{QB;W@DQB;͡@\D ]@D 5QB;ٯ@LD&D!QB;%@oD CQB;<@B=ACDP>B=Ay CٕD{PBqB=A4C=D>PBsB=@A;CBPC3B=AtCل@9PC6B=A CDPC7B=AyCD^PCB=AqCjAPDB=AjC DJHPEB=zA\TCP*CPE&B=ACDBܼPE(B=:ACCJN@ģPE)B=FAyCೖPE5B=\AVACwLPE9B=ZAACt@IPE?B=A=CڧBC:PEMB=:ACCJN@ģPENB==ACC1@> EPEPB=A2CA@PERB=PAnCC.WPEWB=w)AǺCST?O2Pc[B=_AaCPCP~B=UAQCeBYPB=UAQCeBY4B=gA2C|C34B=ACOD#ѓ4B=ACD 4B=AC>Cf 4B=AC>Cf 4+B=ACCu>?B=ACRCn>DB=AC">FB=~AlC6CBoB=ACCuBpB=AC@kD0SBB=lAC.DCB=AWCCLC!B=AWCCLCAB=A^CۻCB=AC\JCCB=A CACE9B=JAhCeAcYMJB=AC"MZB=ACCuMaB=AC"MrB=AC\JCMsB=1ANCMtB=3AQCC;MB=AWCCLVB=ACD@ }B=ACl,DR}B=DACCnWB=AvCB=A4D DXB=Ay%DDgYB=RAMD|aE]nB=A 0D`IEX"B=AD K E%3B=Ay%DDg>B=IADeEʲ>B=Ay%DDg>B>A yD EI>B>A CE0XQAB=PA*D_BCR"W[B=A[DEW^B=VA CEDWaB=VA CEDZB=AA Ch B;E@;DuFn B;#@dCEj B;@\DDֈ$ sB;]@8DD  uB;/@ jDKES vB;@Dx-D= yB:@D ]DC8 zB:0@KD !D5 B;@DD4 B;A@D#Dګ B;@f-Dp}ESLV B;N@D /FD B; @ԃD ȖE> B;O@D;EPc  lB;'@ZD:@  B: @xDDe  B:@BD@fE3  HB;&@]D}C룮 B;1@xD E B;*X@{DEvg B;@4DX B;@CEYw B;!@DCe B;h@CDEh B;N@mDE?N B;$@x|DE.+ zB;@D>D[ġ {B;@(^D D1[ |B;$@3D[0DÍm }B:@(DVhE_ B;@4D DF B; @RD?QDD B;@X(D kEL B;@BDD{ B;@ D Dsm B;@DiE@ B;&%@JD@r ]B;5@$CZED %(B;@*>D JDT| %)B;@'DDi %*B;@ZDE  %B;-!@D FEm &RB:6@D E~< &SB:@DGD- &TB:@DDVEA -'B:@DE\Cڅ -)B:=@ӘD .D݊ -,B:@DPD[ -B;&%@JD .B:T@܀DCPE .B:&@DCS 1B; B@DDE+q 1B; @WSDDe> 1B;$@CEt 5B;h@ D+2E8! 5B:v@iD DO 5B:H@DA :B;@D-EM :B;@D D! :B;@HDE39 :B; w@CE" :B:[@}D0hCAB ;B:@D2#Dum ;B;&@odD7DB ?bB:W@D pD4 ?cB:>@RDD KB;@C4Epn KB;@D΅EF KB;$@SDyE K`B;`@*D 4DV KaB;,@D !fE KbB; @wDE4 V,B;!@CEZOh V-B;*-@ C,Ef]Y V.B;%o@GD}E  `NB;@|DBEMF `OB:t@DHE/K `PB;@D D` aB;@(JDTDG eB;=@~D\ gB;@D $Ew xB;@D!Fk  xTB;@MDD xB;@ջDk x>B;w@D,F ^ xB;\@4EDE. xB;K@]%D VE  x PB<o@ﲚDĊDT x B;@ED8DP x B;@֮D&ENI x B;e}@D F2} x B;T@DF!En[ x B;@D1Eq{ x B<c@VED Dոu x B;@DnEW x B;A@tD jEV x E x{B;@`9D %EТ xB;5@R`D nwES! xB;@D sDPh qB=AID5C qB=vADwD́ qB=ADB qB=AoDN q6B=sADDz qB=A)DȢD ; qB=ADDS# qB=ADDS# qB=/AFD C( qB=թAhDD0\ qnB=AD5Cһ qB=SAD7wAh=d qB=ؕAsD hDD{ qB=A3DtD}b qB=A3DtD}b qB=1AJDj qB=ZA*D _D. qB=֓AC·EEJ q"B=7AWD E q()B=OAD q2B=(AGvDڠEcG} q2B='ADD8PE6 q3B=AD D q3]B=ADED.] q3^B=AD q3B=ADDS# q3B=vAD B q3B=تADD) q3B=#ADeE<\ q3B=ACDE= q3B=(AfeD0B=A X@C䈎DE& >B= A VC1 BB=A BCߙX CxB=@A o#C"DSE CB=A gDEn CB=?A CXE^;B CB=A $CޞMDA CB=A C^C CB=A <-CDJE@4 CB=A CD CB=ĄA C)6Dz DAB=yA C֪ DBB=A C^C MGB=A %C,D MHB=A 4CD MIB=A CDȇ MMB=A ZCEb MgB=A C|DC MiB=A Cٺ M}B=3A CۿC˂ M~B=?A !xCF MB=3A CۿC˂4B=ƂA =CD]4/B=A CmD40B=:A C:D-)4B=A CD\m4B>A [4CE4B>A (CD4B>A aC D= 4B>aA AC D4B>(A C1zE'4B>MA @*DoD?l4B>GA C`CV94B>CA CD"4B=A Cޖ DY4B=A CBȹ4B> A OC1B&4B>A C۪D4B>GA ŭC-D4B>/A CxDnV4B>A &CDt4B>A CUCǻ,4B>A ?C?C4B=A "CC4B=A rCXD4B=GA )Cۅ4B=A JCt.D4B=;A sChD4B=A bCqD4B=A l@CHJDz4B>A %Ch4B>#A xC) C&4/B=A CѭDe40B=A CC~41B=wA Cފ4D<4:B=(A CI4@B>A CES_4AB>A CݯD.4BB=gA }CjD]4xB= A {Cݲ6DQG4yB=A TC՚E:4*B=A CDݖ4,B>qA (CDL]4-B>A C+Dl4.B> A 4CE+z4 B=A yC0`E+4 B=A FCEz-4B=A CEDg4;B= A _EC4 A Cؐ*C@4&B>VA CᮽC~4(hB>A CXA(4-"B>A 4COEv4-$B>(A C1zE'4."B>VA CKCB4.$B>A rC$D4>B=nA (CͶE:4>B=CA CEjϷ4>B>A C:D$i4>B=A Z|CE4>B=A CECU4>B=A C^~Dk4>B=A )CEn~4>B=A C0E!4>B>PA tC燰D.4>B>A 9CE)4>B=RA CE 4>B=A C/rELv4>B=A KC\E4>B=ģA +gCr=EA]%4>B=A XGCDC4>B=ؿA KCEbLX4>B>A 2HC|EV4>B=A B>A C4RB=A @BǾBmSNhB;@Dq\B;@DD\B;h@D y\B;w@D CetB;w@D CpB;r@D %B=CA$ CŖB\%:B=KAmD%:B=iA"CaC18%` B=ACq%` B=A >CƈBV+%dB=ACq%v5B=kA"CCA%v7B=A >CƈBV+2JYB=iA"CaC182J]B=NA#CȓWB=wAJC Cɺ%B=A`DzRGlǿ2B=FAC<$GlJ8B=SADVE:eu0B=tACUEdyB=(A c/CIE}B%B=SAGCW%B=BAnCL%B=A0C b-B=A&CĘs $B>@=CmDs /B=@ ZC Do{ 0B=@=CIEA$ 1B=@SC;E c 9B=@XC$D5 :B>n@~CoER ;B=@yCYMDQ B>)m@RjC& B>&i@C~DV -B>@CTz -B>#$@CUg 4;B>$@1C|eEU 4&@vCr#D- 4=B>&@CgΛEu 4?B>%@CiDɰ 4@B>&A@uCnhDuMh 4AB>"@jCoER$ UB=@!CE  UB=#@jKD TFښ UB=a@CɚE XB>*;@ C ^B>@CF dB=@CdEa eB=@ )@8CC^] gB>#@ϲCkwE`7 gB>)@8CC^] qqB=@{C#EBP B>*,@CzC#$ B>!T@CA_D B>&<@EC~SD{i B>$$@HCGDy B>"@CrzWCa" B>!@Cra)D>! !B>@GCn=D "B>U@iCh:D7. #B>!@1Cw*EBI %B>"@Cq1Em &B>@sC@Ea 'B>@%C~CA: IB=@XC$D5 JB=-@C EX MB>U@CE  NB>@`CP OB> @Ct SB>@Cx2D^&  UB>)@CE&M WB>@HCJE  XB>@ CzE 3 YB>@ڄCzDl iB=@"C[Ch kB> m@C]D mB>@CwC ~EV B=@NC(Dq B=4@CIDv B=@C B=@z)D2E B=@CYiEq B=}@JTC_E+G B=@gCDEl B=@2C E* B=@=+CE>B;q@ DĪA&>B;q@ DĪA&>B;Dž@ ?D?}B;x@ DFAq6IN"B;q@ DĪA&N-B;*@IeDQN7B;p@ -DAlN?B;m@ DA NAB;k@ kDoNIB;8@fDNKB;8@fDNTB;׿@͘DgE%%NUB;@5DDWNiB;@ AQCDϏXZWB<>ACCŴD:XZ[B;-ACXZ\B;ACqXZ]B<&ACݧE(XZeB;|AcC@DX"XZB;zAVAD'X[B<"ACXCX[B<pA‘C+rDbX\B;MAACYCjX]BJEL1BEL6B;ޓA>C=L6B<1AC#DrL6B<ACE=[L6B<0ABCEL6B<'AĦCGOEe'L6B<ƽAGCΠ;DML6B8A9DNzFp%BB=YA*[D5 EE %CB>M(Ay~CHF%UB=tAD@F%VB=nAD0E圆%WB=vA'D2E %YB=xA'D>ŃE|%_B=xAD1+D%`B=AD EI%aB=AD3XD!%B= ApCȒ?Dgs%!B=fA:C3%5B=Ab?CiF!)%6B> AC1?E@%7B>CAvpCF{%tB=A6D!' Dkq2%uB=pAxD#S#Ehl%KB=VAD/Eln%LB=^AD+aEv%MB=AI.D-EEEP%!B>6?ACͬmEь,%"B>,AquBSF%#B>DAIÀFhL%+B>;AJHCcF/<%,B>;xAB F%EB=3A DO>E\T%B=\ADnFW}?% B=UA^DXF--% B=qAC[Dd%:B=_AniD"Ei%:B=ADE6%:B=A2D8=CET%:B=OAD)Fۚ%:B=A3AD%^A?D8-FÆ%:B>(A D F%:B=A$D@VEk%:B=A;D'7E9%:B=cAD%ERl%:B=AQ0D2E㌊%:B=A?D$Fj%:B=-AAC9AwDF@%:B>>+A"D&rFz%:B=A-UC6F %:B=oACgE2%:B=AJDqaFTux%:B>6AaDCFm%:B=ADDNF&%:B>?]A\CwyF%:B=AC'2F.%:B>AzD$sFW%:B=)AC/F7%:B=ACHTEY~%:B=A`CtE%;B=:AMC̅%;B=oAD:֝C %;B=udA>D/a~DG%;B=.AFUD;ZFT%;B=sAtDe6FM6%;B=aADHcEIe%;B=AzD+5)EG%;B={AsRD,7E?4%;B=AsD5bE>A%;B=AhD3D,%;B=xAD?%;B=jADGE%< B=oAD2Eؑ%< B=i;AD7En%< B=sAHD.O\D%<B=A:D.@C$%< B=lA1>DBEp>%>B=WAB=A)D^ՉE%>B=A]D("E(]%>B=AHDD%>B=AD"2E %>B=#A?D@E%>B=AYDBE@%>B=>A!D"&Eb%>B=ARD0VE!%>B>tA~CqE%>B>NAC(C#W%>B>åAC`F- <%A[B=oA+D.C9e%D]B=IA]HC^D~%GB==ADUF2%GB=?ADZDv&MAUD]F%J[B=%AECEY0%J\B>1A$D6(\F%J]B>9ACxF&%KB=AƤDE%W'B=IA1DP Cd%W(B= AD \%WEB>lAD\9FG%WFB=ACbFGs%XIB=ȀA5D@}C5%\1B=dAD-F%\2B=̻ADED=%\3B=#A`D%` B=AD=eKF2%` B=AD>WFDis%` B>?BAdC4F%dB>/dAID?bOFM%d B=A`DME*%d!B= A`Db7E%iGB={AHPAF0%iHB=KASDQQEj%iIB={AZCD3φC%k B=TAD]E%k B=AzD"DE0%k B=A*D#lfE^%qgB>QA(CEh%tAB>VACFy%tBB=ADF*û%tB=*A[&DD4{%tB=vAD@oD+%uB=AvDTFE~%uB=AA)D FB\%uB>V ACNEF$%vB=ZAcuDGE%vB>CAADgFI%v5B=ATA ǥF%v6B=ZA6DFY%v7B>7A@DƜFq!%:B=AD:%MB>^A.C8E%NB>AC$E)%OB>A,C^E%WB>$6AՊD:!G4p%XB>p!AC$F%YB>|AjCF$&%aB>A-CϟPF;t%bB>)ACEFJ%cB>ACЊFk%eB>ACFNI`%fB>SAgECHyF!)%gB>FAecCĵFO6%kB>3A B/F&_%lB>pA{CˬqFD%uB>ACӷEi%B>A;CFX%B>ACaACn'Fq %B=bAyDF%B>YAC4FkR%B>WA*COFu4%B=ADc;F{%B>pLAyCJLDS\%B=ADsFi%B=ADF+%B>0vA;tDF%GB=a\Ao%DWFfE^%HB=ZAnvDMէE4%IB=TBADTiF"u#%QB=UAAD: *E"7%RB=T[A"2DUgE`%[B=AD@Dd %\B=AiD4E%]B=A}"Di'E%eB=~ADMNE%fB=A[D>AE%oB=kADk$UE&B%pB=XA+-D]F %qB=oADXEF%B=AHD E%B=aAuD>aEL=%B=AYD):ED%B=mA DvBE&t%B=AXDP}F8%B=A7D+aF|%B=GAZVDChs%B=pADAϬE %B>ADC*E%B>ACE%B>$AC3E%B>UA0C'>F%7B> AC:E*B>2$@C*E7m*$B>7@ HCC5*2*B>1E@=C5E1*2-B>/@ʗCE z*2.B>/~@sCCM*4;B>.$@njCD*AeB>yACDP*UB>0@Cj D3.*[B=A}9C\E*^B>,@(C&C*`#B> AmCzWFA;S*`$B>A>CF *dB> ,AR@C5D6g*dB>0@/:CD4Ҫ*oB>'@#CDC*oB>@$C} E*%B>@rCC*&B>@]CT*+B>u@ʵCwE-*-B>}@C}~Db*/B>!]@TC:E}.*5B>&@~-C EY*6B>S@C\pEsk;*9B>&h@DCEBW*:B>!8@f3C{ ElL*EB>@C*B>{@dCpC*B>+@ZCPEM*B>.g@!CqE*B>.@CE2,*B>/s@CNE_IO*B>*U@$BCE)*B>.h@C E*B>(@>C9F| *B>2@Ck3E5\*B>/@CnEШ*B=AD^ClrE6*B=AD^ClrE6*B=pA1@CNE*B>ACE*B>@RCuFpT*B>A[CE0$*B>"@%CTE*B>@"CssF *B>, @.$C-E>*B>)@@ChCEJA2B<+A҂CFl2KB=KAI%C,2B=$AFCEp2B=AUuCNEa72B=9A^D.E`2;B=nAC>pFH2B=}A=CgE2/B2ȯB=8A[DYE182ȯB=9A^D.E`2ȯB=gAD(-9E w2ȯB=kADINB6B67fB<@uCO+F]S67zBBFPB=B@C#F&FPB=?n@*C֑DHFPB=?z@CEaFPB=>I@iCڕE-@FP SB=;@e CEQFP0AB=C@&rCF~FP23B=E,@LzC׀F_mFP24B=>&@E$C@MEyFP25B=>@۬Cr[EigFP8B=AZ@ZCGERFP8B=9@PCܞDFP8B=?@lCN|D57FP8B=@@sC]EFP8B=<@zCxE FP8B=?@^CZeQÉFP?B=<>@C#E"FP?B=#|@rC⥃F(@FP?B=6.@ECf7EFPIJB= H@C쀻DؑFPJB=@.C'FnFPJB@CeD-#FPB=@@C\kEIFPB==@Cƈ7DΪpJ8#B>qAa:CJ8 B=oA[DaJ8 B=\ADZSIF$vJ8 B=jsARDz_FJ8;B=yAR CJ8k B=AKODJD[uJ8pB=HA3 DW#;Dhu0B=]A^Cu0B=p ACڡhE/u0jB=}AqCBu0kB=AACoWDu0B=AC2?*u0B=lA\C#Du0B=hsACE# u0B=AC~Eu0B=~ADEu0B=FA#CzELu0B=AC,D&u0B=qA*CƣDTu0B=AC˄|E$fYu0B={A%CeE9u0B=ACODöu0B=ACD>ju0B=z?AYD U:Eju0B=RA@CQEJu0B=AP=C1 D qu0B=AWCϡDyu0B=AC׭E u03B=iANDK B4u04B=AqDAIChYu0=B=ACmCu0>B=AC&Dru0?B=AC7C{ju0AB=AC*D {u0UB=~AWC_Dn.cu0YB={3AhCqD 9u0_B=AiC)MCu0mB=AsCOCXu0sB=Aw3C2lE._u0tB=ACDǫu0uB=HAmCPEsu0wB=ApC8Du0xB=BAgC$Du0yB=Ai5CDCu0B=AC˸D$2u0B=VA7CEu0B=zAwFCWC4u0B=ykAC/C u0B=WACVnEu0B=ACrDDu0B= ACܔDu0B=wACVfu0B=$AZCpE\xu0B=zA2C E_u0B=A.FCDfu0B=;A*C0Clu0 B=+A CDiu0 B=aAdDEu0 B=NApCE6u0 B=_Af CIDĎu0 B=mA ,CDCfu0 B=xACaCu0 B=ADlDu0'B=ACPDbu0'B=~AC̏gDYtu0'B=A`C9DҺu0'B=AC/C`u0'B=AC D[bu0'B=A]CݾDpu0'B=AECVEu0'B=ACˀDu0'B=AƋC9Cl$u0' B=AńCںCUdu0'%B=AiCrEFu0'&B=kcASC.Edu0''B=yAC1Du0'/B=rAZCE u0'0B=p[ABCE* u0'1B=zuAWC|3Du0'9B=AOQCܝEu0':B=tA,CEqu0';B=zTA5C "Eu0'=B=AFxC$)D u0'>B=yA1$CE)tu0'?B=AEClD~u0'CB=AgC=WD#bu0'DB=AgCXDDu0'EB=qAuCցE/>u0'GB=AC Du0'HB=A C0Dou0'IB=AqC+E^u0'MB=ACD#|DڂHu0'NB=ADxE},u0'OB=ACsE>u0'RB={AECܔ\C;u0'SB=AC E@|u0'WB=A DDu0'XB=qaAQCExu0'YB=uAC E5u0'aB=8A C&F0u0'bB=mZA D2yE5 cu0'cB=AKCIE.u0'kB=bA5 Cנu0'lB=nADYE9ju0'mB=wJACޫu0'vB=naADXD`u0'wB=A%DvEl[u0'zB=A$C٘1u0'{B=A&D 5Et{|u0'B=ACݔu0'B=ACCsBHGu0'B=ADS#D!u0'B=AD.Vu0'B=AHu0(=B=_-ADCHE9@/u0(>B=iAwC2EI`u0(GB=WACȴ&Dzu0(IB=m3AQCޗE-u0(oB==A Du0(B='ACĒEku0(B=AFCGE2u0(B=@ApCDž+D#cu0(B=A2CDH u0(B=HAZCÆBWEu0(B=jATC D,u0(B=A&C`D?nu0(B=ACךE8 u0(B=AvCKETBZu0(B=A"CD3{u0)3B=Aa?CCsu0)B=JACŕXD`SHu0)B=A?C EB,u0)B=mpAC׉Cu0)B=ceA͕CEEn}u0)B=t@ACu0)B='AxTCqADu0)B=nAMCPDou0)B=pAZCx}EPu0)B=fAtCO/D'u0)B=tA?CVEXju0)B=AcC!Dݹu0*B=RAߞD |E9>u0+B=~ACȿA'u0+B= APC;Ccu0+B=AxC'DXu0,B=wACsCOu0.OB=AC׸C]u0.UB=W AhCkEQu0.VB=XAd CܞD҆u0.WB=]AUC6Dq^vu0._B=ALSCEzu0.`B=AC#hEFu0.cB= AuCD3U-u0.B=AjChEA7u0.B=,AChCju0.B=UA[CDj[u0.B=nArCԬEru0.B=AjChEA7u0.B=gA*CobED%u0.B=}ArCڏA u0.B=AekCvDu0.B=zJAuCӒQCwu0/B=zJAuCӒQCwu0/ B=LkA?CgD֊u0/ B=LkA?CgD֊u0/ B=XACbE7u0/B=AfC>C.u0/B=c!AD&SCӾE?u00B=UA&Cɢu02[B=AD#E+2u02\B=wASC-D_ u02]B=vAC͹Du02_B=~ A}CpDqu02`B=xYACl)D$&u02aB=vHACD;Lu02qB=c5AD$qu02B=ACӭu02B= A8Cu02B=b}AD4C6u02B=A+C C,u02B=n+ADgoE( u02B=\A = C#u03YB=A CCBu04B=AICOIDe{u04B=A;lCC+u04B=A1dCbCwu04B=spA-CD*¬u04B=uACΐAD/ߦu04B=wyAqCc;B'u04B=w.AICΏC_u04B=xQAC̓Cnu04B=xACWCٳu04B=uA C͘Di%u04B=}9AClD:u04B=)A(C̨Du08KB=tAICqCu08B=LA_CۊETyu08B=A^C欙Du08B={A4TD oDEtu08B=Ad CҬDChBu08B=lAq4CD^ u08B=AabC&D)bu0?B=ACwE u0>@B=A^CB:zu0>B=vAeDEɭu0>B=o ANCݼEgu0>B=xoAdDHE&u0>B=uASCD1u0>B=vhA4CۏhDYu0@oB=}ACC2bu0@qB=aAICƸDfu0@yB=A-CE4u0@zB=vACMEu0@{B=A(CɔCSu0AB=sgAIeD ŭE6u0AB=AC|E0u0AB=AzC_E9HCu0AB=:A#CŬu0AB=ACDTeu0AB=JAsC"E+ɻu0GB=|AAC{Eouu0GB=u9A`$CEeu0GB=A$C{qEqu0H!B=~A\CIEZc|u0H"B=n2A,DEKu0H#B=n_A ~DE..u0H+B=qAC&Du0H,B=hZAˁDAEh u0H-B=n,AC D~u0HB=YAbC^HDu0HB=YAcC:Du0KB=EAiCXFDQu0KB=HAFC^E-u0KB=AM#C-Dmu0KB=AICEO@u0KB=_A7eCvDAou0KB=gACţEI$ u0LYB=}EAC!E!au0LZB=|{AfCgDW%(u0L[B=WACMEu0SB=kA ClD=u0SB=kVACES~u0SB=wdAHCMEu0SB=pAXCӲE_u0SB=iA38D*E/u0SB=wA[CEDu0WSB=}$AbsC%DPu0WTB=x0ArCD^u0WUB=qA8D}E0u0W]B=ACp&Eu0W^B=2A}CɎDlu0W_B=AQ1C{Du0WgB=b[AD8.%AV0u0WmB=} A8vDEëu0WnB=MALCȷDQ u0WoB=A8lCDu0WB=GA~C֐D `Qu0WB={ACC-u0YMB=]AzCւDgu0YNB=\`ACE`u0YOB=_?A׼C`Eblu0[B=xA8CٙCٱu0[B=qA;CC u0[B=A.CۦD=u0[B=APCEu0[B=A]CDK"u0[B={AC}DcDu0[B=zTAC1D ʆu0[B={Al}CoDZu0[B={ACxCqu0[B=wACVfu0[B={=AC"CS{u0\B=A 4iD߮u0\B=FA ,@D,>CL8u0]B=x!AsCDIu0]B=lAVTCUD9u0]B={A~ C_B*vu0^ B=IA DD<u0^ B=yUACԼu0^ B=sA`DrEu0^B=pA:C7Dqu0`wB=vFAtCvu0`yB=a[AC̟6Cu0a B=;sAD!u0bB=VAdCEzu0bB=ACWE${u0bB=ACJEU`u0b%B=1AC E9u0b&B=sACPu0b'B={ACJCku0b+B=ZA ;^DDkAu0dB=AC!Du0dB=AqC"D/u0dB=AHC׈Em,u0fxB="A(Cju0fB=kAatCDu0fB=n Aa}CDYu0fB=hnA_CNDzҍu0fB=]ACAEAu0fB=A/CEVu0fB=AQCI=EFu0fB=d}AJCܹ2Ecu0fB=aANC ^u0fB=gAQCُE![Vu0iB=QAwCB|Vu0iB=|AUCҐEHu0pwB=A_CقE<9u0pxB=ACg+u0rB=bBAD79u0s-B=^5A 1CWD u0s.B=Q^A CKE`&u0s/B=QAڤC1AEu0sB=}ACNC pu0sB=czA-oCEOu0tB=jA7CFE`u0t7B=}ACu0tB=zJAuCӒQCwu0uB=~,A]C]*B"bywB=A(C8DyxB=ASC^D$IyyB=AC#DyB=yB=jAiCvD/ݝyB=yAWCy&B=EACA޽yB=AXDryB=~AgCC_yB=?ARCy7yB=MACڟAyB=:A5CՑBf5y'EB=A&CKBky'SB=AC y'lB=cADy'wB=AC)BȘVy'B=AYC!D(y'B=}AgD[E!y'B=A\C Dy'B=!ACMDQy'B=}AjDEy'B=A C쐎Da)y'B=A1DWAzQy'B=ACy/B=2A=CAD BF0y2sB=yA dCD4y2tB=A{C!*E Ty2uB=TA CD(y2yB=A Cؐy2zB=A Cؐy2{B=DA CDͯ/y2}B=A CH4DyW y2~B=A CH4DyW y2B= A |CVEy2B=BAClDey2B=A{C y2B=bJAD2xAy3B=A CDy3B=ACHVBy3B=SA !'CÝBy3B=ACdD`Iy8B=-ACݲy9ZB=3ACCbEy9[B=ACD~6y9^B=ACDvBy:uB=oACDy:{B=NA M.C1&B$Hy>'B=AΤC%Diy>(B=ACCIDly>)B=ASC}DDhy>+B=A6CuDy>,B=A CC:Ey>-B=AǟCD&y>B=OA CD=y>B=MA C-9DѮy>B=?A aCE "y>B=A Cʲ/E(|:y>B=[A bCuEy>B=A C0E y?B=ZA &C;yA3B=jACTyA4B=A CEyA5B=vA 'C"EyHB=sACfCyHB=A CyHB=A CyHB=A6CށwEyH"B=dfAfDtA.+yJB=AD3DʇyJB=A CyJB=ADdC&8yMgB=A kC'~CMyMiB=ZA &C;yWgB=bBAD79yWhB=ASClDS yX5B=A D)yXwB=A t5CǮy[B="A CQDy[B=A CH4DyW y[B=IA CժydB=A_CTyfB=AClDyfB=AC&wBxyrB=EABCyrB=b1AD5ڏysB=c%A*D%ʗA|}B=CA C\G}{B<ԛA+CFa}|BB=vCA^Cf4C 7}AGBB=zACp3D:eB=yAb CҠEOB=ȽAs.CBsEOB=AkCD=GPB={ACI D PQB=ACF%E B=wAezCfB=[ACzEQB=ACDF.B=A.CkEg .B=AACEKb.B=AIC!AEaul.B=hA{C|.B=MAkCfXExz.B=JA CͽE,.B=Ju@CNEBx06B=ACTE0TB=ПARCˣHE7s1B=ACJE1B=GAbCǒEV23B=F@;C2=B=JdACDڟ3#B=eACЯE;3$B=^AC0E3%B=lACέRE3sB=AC3tB=ƷACE2^3uB=AmCZDṃ3B= A!1CLD1}7B=AŴC"E )7B=΀A`CcD1CF:SB=ZArCC=B=vAQC2_D>B=ХAICʕCAB=^AC0EB_B=AuC\HB=ˋAiCƭ$DHB=ACE/HB=վADCHB=ϏA=qCgEiHB=KA CBE5>K=B=AECקE[B=ՅApCCM*[B=TA|C D[B=џAbGCE]B=AlC^/B=KUAC3`#B=A}9C\EdB=ARCdB=>ApC-E/dB=AECDMeB=ACEteB=ACK5E^eB=AiC~E+ vgB=NA C[C_jB=K_A"CPCB=ARC@DB=AhC/sE +B=AŴC"E )B=AC"E4щB=ƷACE2^"B=Aw_C6f+B=PA 9CE&,B=ACyEvq-B=OA?CHEm5B=A'iC _Ew6B=sAѡC,ED=!7B=AEJCϒE(9B=QAMC~EGA:B=AACpIE0;B=ACEF IB={AuC͏E:t&JB=|tACE#)/KB=MAC:EUTB=ACƓEqB=ՅApCCM*{B=\AC6.EC;|B=_AC;1EA}B=[7A^uCD)Eնf貭B=ZACwD貸B>UACB=AC6EB=AbCaD#0B>AiCqDT9B=_IACӬD6vB= A.CҖDtMB8+A C?y-B8+A C?y-B8A C:+Ah:-B8+A C?y-B8+A C?yVB8+A C?ynB8+A C?yB=ACvAYB=4AiC'+B=DAC}AɊB=A?CB !B=EACA B=EACA B=dzA OCiDf aB=s$A_3D5' BD$'B=hFAC A(*B,B=ACsB@B=PAC:qA@B=>AQC=@A5B=ȖA FCC @FB=!A RDKB< A WDMD9s\B=A 4iD߮p)B<A uCs3B<ɞA CC!Fq'B=AӦCAq1B=AC}A݄q9B>A5C͜q=sB=A֯D͉Aq>+B=AחCu0B=AwA:CDM/j-%B=ACƼ)M}B<-A C݁ %B>WAaC{r0B:@ӢCSD=Rr1B:@ӢCB r2 B:@ӢCX B}@Nr2B:-@ӣCH{ARr3B:4@ӢC̶A2"r5B9@D8sB:@SC=s/ B9 @1Ds/6B:@(C噣Bx2s/UB:@C C/s/WB: y@7CЅCs/fB:N@C捅D=s/gB:L@ԡC^D3tOs/hB:>@ԏ%CD+ s/B:f@ӥ-CB"s/B:@"CnDos/B:@B:R=@RD28Et/?B:N@ D/ Ept/OB:@]DE3S"t/PB:\@٤D(LE9t/QB: @8TD"D=-t/}B:,g@ړD. SEt/~B:J@D-LnDt/B:J@D-LnDt/B: @8TD"D=-t/B9@UDDt/B:KM@ڿD-GEVt/B: F@aD.t/B:<@ HD.-t0?B:<@ HD.-t0@B:<@ HD.-t3 B:x@D<t3sB:W@ڧD0K$D1gt3tB:T@ڲD-Ψt3uB:W@ڧD0K$D1gt3B: @,{D"t4 B:$@ژD.D0t4 B:$@ژD.D0t4 B:$@ژD.D0t4nB:c@ƿD2eESt4B:.p@ڵD.\EX8t4B:&@ڻD.K'D4t4B:Z@ڝlD2t4B:r@^D:DDqit5B9p@جDD5t5 B:{@1D? D5t5B:r@D<3t5B:`@ھ$D5Pt5B:x@D>Dt5B:#@ڨD-t5+B:~`@FDAFt5@B:Ϟ@{D3qECt6B:`@/SD7 FwGt6B:@,D9Dńt6B:x@D>Dt6B:@CD1[E !Zt6B:@mD4k!EDt6B:o@eD:D0e+t6B:@ۏD9Et6B:@TJD3 nEGt6B:@ܬD6XnEO9t6B:@ܐUD7vD!t6B:@pD2EB>t6B:m@3D2gE qt6B:@+D3NEm0t6B:@KD8iE$"t7(B:@>D:=mDmt92B:l@D8t9;B;@b[D5t9YB:@ vD2EH+t9ZB:@؉D2$E6&t9[B:$@D35EQt9\B:@>D3NE#zv7B;W@嬋DTxB9@[D DGix/B9-@؏cDx/B9@ADD&x/B9@ADD&x3B9@צSD T E .x3B9@DiEOx3B9@DcD>x3B9p@جDD5Z]B> EA 4vCwC'Z]B>A +CZ]B=oA C RETZ`B=eAܩD`Y@_8ZbB=A 1CtESbZbB=A FCԫkE-ZdB=A _C*ZdB=A ǍC-DwZdB= A KUC贡E3۲ZdB=A G{C4-EnZdB=܈A NC8EZdB=A )CEZdB=A uCE w0ZdB=A WCoD1ߛZdB=A \OCnDKZdB= A MiCڛ9C&eZdB=A lCoE@moZdB=BA bCEZdB> A 0kCVD7HZdB>zA CQEZdB>A ؈Cl-ENZdB>A C+DZdB=QA ykC;DZdB>cA bCCVD5ZdB> NA 1TCD)MZdB> A 1C>D@IZdB> A 1C>D@IZeB=*A #C?CZeB={A  C%D!ZeB= A ,_C$EuZeB=A $:C$DyeZeB>A #C Ze'B>TA CpE~Ze(B>@A 8C(DZe)B>A ICxDρZe-B>A FC#)E#Ze.B=A ~{C3E~"Ze4B=eAܙD`ZeVB>A CCZecB> A /CCZejB> A 1C>D@IZeB=;A vCډDZeB==A CܕDZeB>A ,CDZAZeB>A CD"\B=JAt9G\nB=6A5D$C\W]B=hAD/qEw]\W^B=AHDXND#Z\W`B=AHDXND#Z\ZdB=AQCEzr\\,B6A (CBi_dB>A +#CDBAi_eB>A CD3_e'B> 3A )mC/;`W^B=ӞA=D`U`hB=ArD46C`i8B=ӂA;%Da`i=B=ӀA:D`A}0`iCB=FA@D[*qCK`iB=AkD!ES 8`iB=AwXD6\E^`iB=ӌA;D`J`jEB=ӂA;%Da`jGB=TA8D`@1`kB=הAy4D}C`lB=A5CѷmbB=JA KCQbTB=lhADEbUB=A C8D4b]B=A  C٭aB9b]B=A Cb^`B=iADP4b^tB=eA߃DVb^B=tiAD)Sb^B=tAD%b_B=*AYC4E.b`6B=A5C1b`rB=nAD;Ccba B=A&C5Dw5baB=AYC DbaB=AzCߖfbaB=ACD%PbaB=ACYD7(ba!B=AC Dɵ4ba"B=,AfCE>ba#B=A}CaB;ba+B=AC,D<ba,B=AtCD,Oba-B=ACHDHba0B=AؐCSE<ba1B=ACbD3ba2B=A DFba:B=A {C]EN3ba;B=JACiE;\0ba/bfTB=AzCߖfbfB=A CDVbfB=A ~C jbfB=A CxEbfB=A%CBbfB=ACtCFbfB="A NC9DubfB=A apC=DQbfB=_A B"C[D0bgB=iA pCDHhB=AsCmD,hVcB=AZCD@hVdB=AsC6C#NhVeB=yA[CGD7-hWhB=AC#hWB=ACsDhWB=ACEDhWB=AȚC:xDp!hZ7B={ACδDj:hZ8B=xdAC_D&hZCphZAB=wACq4BhZBB=w~ACϨ*CchZGB=z;ACCγZhZUB=pAȢChAhZVB=AӁC֔WBFhZWB=ACADhh\B=AC~rDN\h\B=oACZCeh\B=ACrtD Yh]CB=UACaCh]DB=ACDB6h]uB=AC@h]B=ACݎh^B=-AP0C>Djh^B=AiCd DsI6h^B=AbCл|D2h^)B=AFCDh^SB= AfCYEжh^B=fA DUҰAZh^B=uATCĒ(Dӽh^B=s,AWCԖh^B=uA\CքDRh^B=x-AnCh^B=x-AnCh^B=sAT-Cε2Cjh^B=t4AB6CEC[-h^B=t7AJC2D&h^B=tA7gCڿ-D:h^B=wADzh_ B=sAKC'Lh_B=A=C}EOh_B=A[`C E0bh_ B=ACJEh_(B={AC#ADh_)B=z%A*CD'h_2B=~ACZDagh_4B=fADXsh_=B=|A~C٬D h_GB=x-AnCh_HB=v_AeCҵDth_KB=)A CACۑD%h_B=~AC~MDh_B=ACC%DYh_B=AC?Dmph_B=mAXCFD`h_B=AC.Bh_B=ACRCkYzh_B=eA\DY]@=Ph_B=bA_>CrEh_B=*ACUD8h_B=uACrASh_B=AKCh_B=NAC'Dh_B=xAD %h_B=AhD%Eh_B=5ACEA9h_B=ARCEdh_B=u(AA+D'|bEh_B=ALCDl h`B=~AWD0#ExE'pi]B= A CD\i]BEcKimBB=AD>\E؃imCB=AD;'E07imLB=BADE8fimMB=A{CԏEuimNB=ACҦFj[B4C>E!/jk(B<*A >4C>E!/B6B;@DB7aB;@6DzDVB7bB;*@KDroB9B;@DB:JB;@DB>B;ts@gDBJMB;@`DBLB<@C C>BBBB<62@2DC>B<@CDgC>B<@ChD (C>B<+@D !C?ZB@D !D>B;@@D{D>B;@D DN`D>B;@D DN`D>B;@ D D>B;)@DoUDbtD>B;'@D D>B;'@D D?,B;0@츶DD??B;J@@D"D=D?@B;@D0@DiD?AB;@DEyTD?BB;@pD.WD6D?CB;@BDELD?IB;@BDELD?JB;@9DAED?SB;Ǎ@'D\ED?TB;@DDtD?UB;o@ DDD?]B;@oDKD D?^B;@&D9'D?_B;Ō@bDDD?aB;@ D D?B;@vD XD¶D?B;@VD D?B;@#D DD?B;DZ@ 0DgD-RD?B;@D D1D?B;z@D CD?B;'@D D?B;+@D* D?B;)@DoUDbtD@1B;'@D D@}B;H@D dDSa D@B;@&D9'D@B;@;D ODRD@B;@ D DH{B;H@D dDSa DH|B;@`DDH}B;@D DN`DHB;@UD DHB;@UD DHB;@vD XD¶E5@B:(@ݒD06E6B;u@DE6B:@hD6kE@E6B:@(D2EcE6B;@%D/{E6B;@%D/{E6B;@|4D*ET{E6B;KP@G{D{E6B;=@ DD0E7 B;&@㒬D$EfE7B;[n@庸DEJ E7/B;UE@cDEQE70B;Eu@DE7EB;2@"DRCQE7MB;[n@庸DEJ E7NB;UE@cDEQE7OB;X2@D>EMp^E7iB;q@MD+7EiaE7yB;Q@zDDE7B; @D2ECBE7B;@D2ÅE7B;@D0~E74E7B;7@eDؽDƇ%E7B;*x@D'E7B;@T6D,!E&E8_B;@iD3LE$E8`B;@D@b=F E8dB; @ሗD5E8}B;( @nD.?EfE8~B;!8@ D3LExE8B;@D.E8B;4@D="DcE8B;0@DnE8B;'@MD'BF&'E8B;1@DDѻE8B;x@D28@F=E8B;=>@%DcDVE8B;=@ DD0E8B;7@eDؽDƇ%E8B;q@DDVg?E94B;*x@D'E9;B:o@ߢD4&bEmE9B;u@D#+F[B=lAM*C$)EE#+F\B=wM@XCQE{#+F]B=lAM*C$)EE#+F`B=ACCrE|c#+FgB=o@CK(E#+FiB=w@ZCǣCE#+FB=y@FC%E= #+FB=o@CK(E#+FB=k @C@BDCx#+GB=f@|C#+G)B=.A6CXEx#+G*B=,ACiD#+T B=AC-F#+T B=AnCÃGENP#+T B=~AGC`ER#+TB=jA1DH`#+TB=jA1DH`#+T B=AW C#+T!B=dACL#+TB=$A6mC8#+TB=$A6mC8#+TB=,ACiD#+TB=AUCMq#+TB=PACah#+TB=A©CDE#+TB=5AC}/#+TB=4ACH#+TB=A C@!E#+TB=AUCMq#+TB=yACZwF}W#+TB=BA CE#+TB=5AC}/#+TB=vAۿC&CE@x#+TB=ACEn+#+TB=ApCDE#+TB=$A6mC8#+TB=AC#+TB=AW C#+UMB=AC Dyb#+UjB=AaC@ Dl#+UlB=mA?CDr#+UB=$A6mC8#+UB=AaCoE#+UB=AC,D|#+UB=A/Cff#+UB=AW C#+UB=DA|CtEB2#+UB=A©CDE#+UB=APC#+UB=A/Cff#+UB=dACL#+UB=ACVD&@#+UB=AW C#+VB=AC#+VB=AeC Em#+VB=ACBD0{#+VB= AbCĜ#+VB=AZCaEk#+V_AF#+`B=ACKDp#+`B=%A0C#+`B=AC#+`B=mALC-$Dc9#+`B=%A0C#+`B=mALC-$Dc9#+`B=ACDn#+`B=ACjE6#+`B=3A:C[#+`B=AS"C:oEͥ#+`B=ACES$#+`B=AS"C:oEͥ#+`B=AS"C:oEͥ#+`B=AnqCDE#+g\B=AS"C:oEͥ#+g]B=3A:C[#,>B=L@ CE-#,?dB=h@CRo#,?eB=h@CRo#,FeB=i\@_C{Dޱ&#,FfB=i$@uC~D#,FgB=l,@CAEI\#,FhB=n@FCD6#,FiB=pE@>CZEd#,FjB=n@FCD6#,FsB=h@CԞDy#,F|B=kz@@$CDD#,F}B=g@;CEo##,F~B=g@;CEo##,FB=e`@ C"#,FB=M@7Cv"E#,FB=;@TC5#,GB=k@CҺ#,GB=kD@.C:DH>/#,GB=n@ CѼD1#,G6B=j@&CD<`#,G7B=j@&CD<`#,IeBB=;@TC5#,UHB=L@ CE-#-B;zAVD(#-XkB=gYAՉDTR#-XB=eADX#-Y$B=jA1DH`#-YVB=jA1DH`#-ZB;zAVD(#-ZB;zAW3D(B>#-[B;zAVD(#-[YB;zeAWD(#-]SB=eAQDX#-^B=gATDTD#.]*B=hAѸDS?B.#.^B=lhADEG),B=(AmYCT,B=(AmYCT-B=(AmYCT/B=(AmYCU/B=(AmYCU*B=(AmYCBGAʃCBKBF9AC"C?BGW>A|j-C#0HaBFOAhCȴtBFA#C<]DoE4BG}ACR=?Z tBFA^pCZ |BFA^CBGoA;CBGn}ACBC  0BFgAU4C 0BFBAWCьD 0BFoAT CDv_ 04BFAUCNCˆ 07BFrAWqC˥` 0QBFKAUCCH 0XBFAUCo 0YBFAUCGDB3 0ecBF4ASCVDBX` 0pBFAWrCӜj 0sBFAUυCq 0sBF֨AToCw :BFAVClEn| :BFAU5C}DAI@ :BFMAW8C:E' :BFAWC,E3X :BF֗AWn=C@EG> :BFʝAVvC : BFAV(C EUW :BFAWCɛEc} :BFaAWZC%F>  :7BFAXCҳ`EOM :QBFyAV4C+Ea :RBFAV.7CE :mBFTAV^C$EWF :NjBGAYmCȁ :NlBGAYC4D :NBG A` Cv*BCU :PBF{AU%C#Fu :PBFvAX5CEm :Q#BFAXDCE2# :QKBG;UA\eCSxD :QLBG3A[C)F; :QOBG86A[,Ck$E :QXBF)AW'~CEb :QYBFŒAVC5EoQ :QZBFcAW CE_\ :QBF AWRCtE"] :QBG{jA^fLC :QBF&AXC0-E i6 :QBGAY$C,ZF(m :QBFAXCF :bBGT~A]pCyFj  :bBGBA\J5CíRF :bBG?A\HDCҎF,0 :bBGuA^ C! :c(BG'AZzC+Fv :c)BG%A[+CF3} :c*BG0CVE+ :fbBGgsA]{C4yE+- :fBFAW CڢE :fBFAVCE :fBF±AVgCDEQ> :h BGAYCȐ :h BG>(A\$ C$Cl :iBFAXWCɕ@F_ :iBG3[A[ŢC\Eq :iBG)TA[zCiEd :kBG AZ"C8DR :kBGPAYHCE12 :lBGWAaCWC :mRBGuA^B1C$DUh :ovBGAZ Cͦ Ei :o|BGAYX+CpF< :pBFAXCYE) :pBFAWC>E=4 :pBFAWdCE :r_BGA_C7D{  :raBGu@A^=CNWDJ :rbBG6;A\CR  :rBFAWCS :rBF֎AW`CӂD<  :s/BG^A]H[CF2 :s0BGMA\c,C7E  :tBGiA]CElh :tBG_A]\eCD] :tBGQA]CF, :BGAYCEؼ :BFAVxCE :fBGLAZ4'CגnFi :gBFrAXCwUEν :BG5IA[ZC!ES\ NBFAK|Cu.Em NBF֦ATOC6C; NBFAUC Erin NBFAUCET NBFKASxgD-2F NBFAV\C|&Ew. NBF4AVMC{ N BF"AV*C*vD4} N,BF¿AU CE' N0BFATyC? N4BFAU SCc2D1 NQBFvAU/C NWBFAU2 CcE$W NXBFAU5CEL NYBFAU+C*E0 NQBFAIC6D7E NQBFAIC6D }Ecw NR^BFAK2wDE? NaBFAPYCDA NaBFALSD EFIN NaBFAKGD%hE Nb}BFoAOSDLE NbBF(AS=CtE2V NbBFCAQDB"IFZ NbBF֚AQDXF# NbBF0AMrDtF 1 NbBFAT[BF"u NbBFɀAToBnF NbBFAR+C,Fi NbBF׮ASfCLFvz NbBFeASICD: NcuBF#AKwD|Dl NcBFAUڧCFC," NcBFAI~DF$ʹ NdBF+AKD F9 NdBFAKCE NdBF4AKHDF:@ NdBFʙAJ5D]DǺ^ Nd BF͔ALD-F NdTBF1AMzsCE! NdUBFAL D:F;R NdWBFAID<"EJ NdXBFAHC+FD. NeBFAMJDmEÛ NeBFAOD7=F: NeBFsAMDGF1 NebBF݁AQDdEA NecBFARJDbcFj NedBFۀAQ=DDE NeeBFAQ~CtZ NegBFANQD2E( NeBFsAHD Y?F  NfBFلARi7CBEM NfBFASCqF6 NfBFثAQ!D=qF NgBF}AO NtBFoAKD QF NtBFYAJa@DyEvj  NtBFӪAKuD yEk NtBFۼAK]SC! NBFoAVC¥Ck NBFASOCE"E? NxBFAUC\VC墴 NBFALWDFEh NBFALDFa NBFAL7CCsE. XBFLAT9CCˢM XQYBFSAVC Xb|BFAU#C?6Ev= Xb}BFSAN C}D6 XeBFAOCԖD XoBFvAOC׉D} Xt%BFϋAOC Xt3BFAXqCGsC XtBFAK7DCU lfBFAVrCO\ ln}BFeAVC÷m lBFAXDCF1 lBFtATB F' lBFCANCD` lxBFAUυCq l{BFANC|D%NBFmArC*"CHe%NBFz+ArfC@VEg%NBFeAsC%NBFU{At&C)C%NBFArCo%NBF,AuGD EZ%NBF'AuD1D<~%NyBFcBAs&CI%N{BGYAmgCgDݕb%NBFoArqXCYCR#%NBFVxAt;C/ DÈH%NBFdAs@C E?u%NBFZlAsCoFRLQ%NBFqArZCOMD^d%NBFsWArAlCoDH[5 ONBH >A|]XC^5 OOBH A|BCdrD5 OXBHA|Cm,E#f5 OYBH HA|j%CgUE 5 OZBH HA|`NCc,DP5 O[BH 7A|m{C_CD %5 O\BH _A|GC`Dۤ5 O]BHA|!CoDu{5 O^BHA}>XCwfEP:5 OaBH oA{Com:EJz}5 ObBH A{CfLD*5 OcBHA{3xCb5 OeBH A{\Cl E5 OfBH A{ C,ͭER5 OiBH"sAzOeCcN5 OnBH kA|7 C_DCy+5 OoBH 2A|uC`C`5 OuBH EA{CkEFQ5 OwBH NA{mVCe/Dv5 O{BH A|CfD25 O}BH A| CfaC;5 OBH A|Cd;D 5 OBH lA|3NCc;QDd? 5 OBH A{ Cg|Dԇ?5 OBH OA|NC_$D*5 OBH A|9Cg5 OBH A|#CdQD5 OBH A{CmE=y5 OBH A|%Ci"D35 OBH A{4Cgٚ5 OBH At/CNBCWe5 Q BFAq$CǚE*ZN5 Q\BH A~g C~Q E5 QBH cA{rCb`5 QBHA}=CuE5 QBHYA}l=C}/4EN5 QBHJA}C~g5 fBFdAvD5 ksBHA|FCz 5 kuBHJA}C~g5 omBHA| CobC5 oBHAzCEEGP5 oBH #A|Ce_DV5 oBH A|Cm DA5 sBHA}:C|DT+5 tBH A|rChA=5 BFQAq;5CC 5 BFNAq#C΀54O^BHA}C|C 54OBHA~ +C}wDU#54QlBG@Aa]CdF1g54QmBGACyE54QBG AC54QBGACDa54QBGACD54QBGACoEM.54R_BH tA~}C)E54R`BG+AzCz46D54RBG A54b1BGA|C;pE54b2BGjA=ɷFZ54b3BGA B F-&54c|BHA2C|E}$54cBG;ACUB54cBGAPC OC;854cBGACֽFa54eiBG_ACᖔC_J54ekBGi ACC|t54eBGqACR54fBGACj$F&54iBG ACThDE54ksBH A~gvCxEf54ktBHGA}C}%ET54kuBHA}9CETN54kBHWA~CzLE554kBGAwC}^DE54kBH A~fCEk54lBGApCІE54lBGA)mC[Fj54lBGAdCZE@54lBGAC>w54lBGA,CpF)54lBGCAC(@54m6BG ACāDAs54nRBGAD7^F54nSBHALC^iFDR54nzBH 8A~_C{JtEJ54nBH A~PCtEV54oBGRANCy54pBGPA'CCE@54pBH A~gC|Ѫ54q!BG ACāDAs54q"BG;AmC'F54q#BG@A6CEhG54rnBGQABGA~^D եD15>NBFԉADF5>NBFA/CE -5>NBFAD;BFy5>QGBGvAcCb-@ 5>QiBFACEk5>QjBFΆA4DhxGE-5>QkBFA{CWDNQ5>QBGAACpDr5>QBGACoEM.5>QBGACVSBoi5>QBGACXAD5>QBGAZCE5>QBGsACE5>QBGA4CyEݝ5>QBGA.CF5>RBGA6CzD|5>RBGrA`=C(vE_5>RBGCAC\HE`5>RBGuAC5>RBGAgC 5>RBG?AC??[5>RBGdAۮC3sE5>b/BGDACV5>b5BFAAD:F0_z5>b6BFAwCFP5>b7BFAvCC`D]5>bqBGA-CU En215>brBG:ACɯEj 5>bsBGACgE,C5>buBGA6CE{G5>bBG*A7CޯECi5>bBGA{CRE|5>bBGACƙE?5>bBG$AD?F5>cBGj)ACBF5>cBG~AsLCC5>cBG}Ar/CeE>)5>cBFAbCzREV5>cBGAC| Eu5>cBGCACjERD'5>cBGPAC zA5>eBG[A=CDH5>eBGzAICEǴ5>eBGeiBG(ACE 5>ejBG.A[DF`~h5>ekBGdAeCEZ65>eBGnAiCkD(f5>eBG"ACF>5>eBG?JAqqC6E5>eBGA\C͎E55>fBGUAݙCE5>fBGKACC|V95>fBG=AqC&DE5>hBGACHD5>hBGuAČDFPl5>hBGAC'E\(15>iBGj)ACBF5>iBGyABCDE5>iBG|zAECE[5>iBGuATCOF6ձ5>iBGvAC!A=5>i7BGkACTEe5>iJBFACE!5>iBGrAv CE&5>iBGlATCDSh+5>iBGABC&KE1X5>iBGABC2E}j5>iBGAA CaJE*5>iBGACyD5>iBGBAC7Ea5>iBG.ACzE~5>iBGA>VCf.F zh5>iBGFAVC*uEW5>iBGǶAOCE5>j?BG7AC%ZE{5>kBGy~ArCG+5>lBGpA~CEf5>lBGyAj]CxUE5>lBGMA Cs(F_5>lBGAhCGF&5>lBGA C”95>lBGCACjERD'5>mBG AID&!D5>m4BGFACZGS5>mBF@A C@CE"k5>mBFA>%CC&5>nBGAp'C>DԷ5>nBG/ACGwDNI5>p BGn/AJ5C DS5>p BG}A!#Dh F/>5>p BGAD6?F^5>pBGAYD4E`5>pBGAs=D )E5}f5>pBG%ADFn65>pBHxA-D^="D-5>q#BGgACpb5>qBG=ACqF0:'5>qBGAsC,!F5>qBG9ZADÜF5>rBGBACٔFñ5>rBGXAL=C0E]L5>rBGACE5>rBGAC^EAsS5>rBG}@AxNC&,E)g5>sBG|-ADRR[FMr5>sBG}AƬDəF=j5>s BGdACcF_p5>saBGlACE5>t8BGVAK#Cb5>tUBGNACA25>tBG˚A8C|E^5>tBG^ACDE5>tBFA;CGD0S_5>tBFA{C^w5>BG AI?C(R5>BGABC5>BG3AWCC7>5>>BG A`ZCK'Cm5>;BF AM_CҏBt5>=BF^ACplE5>BGnAC)5>BGfA}CD2 5>BFѴA|CDClt5>BFAu CL5>BFAWC=P5> BF!ArCћD 5>BF0A]CD _5>YBGrgA?[Cm:E5>ZBGgAxoCĩD 5>BFAlD]5>mBFA{CbkD-5>oBFA CBD5pb5BF\ApDF95piJBFAC 5pmBGABHC75poBGAB2CvF5N0BH AzkCaC@5O>BH#Aw-BE}X5OTBHAzCd5OfBH!uAz/bCbn{D5OiBH xAzaCa5OBH$AyHC!E5OBH%Aw&6F!z5OBH!AwCC*5Q\BH A~fC/pDT5QxBH$AvC5E5lBH"AxgC3aECԕ5rBH AzqCj֨D5sBHAweCe`68Q\BH5A~9)C|(8HfFBHNAl+CR-8HrBHLAlCBN@BHiAd;uC'Cѯ@BHhAd}CL.E&E@oBHaAeC6D㭕@pBH`jAeGC|E r@qBHa_AeECEgW@BHkAdC&D@BHb[AdCD:@BHjAd@)CҀB4@BH_AeCD8@BH`Ae^CE.5@BH_@BHbAeCD1 @BHdAeCCAeCDve@BH_KAe0C%ND7'@oBHe AeޥC̀D~@BHmeAdoCDL@MBHk|Ad{CD@BHlAdRmCYDI@BH_AeC E1@BHb AdvCާDC@BHhAdCEH*@LBHnaAdnMCRDRiS@MBHL{Ad[DE@NBHjAd9CC=@BH_(Ae0ClBa@ IBHlMAdLCsD@ JBHlAd\@<GBHeAeChDȁ@<BBHeAeCrC@<CBHeAeCDu@<%BHedAeC DUţ@<BHdkAesC Av @<BHfAf&CqDt@< BHfAf6CiIC}@<!BHfzAf CsDH@<BHQAeCVCݭ@< BHfAf6Cmo@< BHf9AfCzC @<(BHfAfBCfBd@<2xBHfAfBCfBd@F IBHl+Ad;CNqBHTAez4CWaEdCN~BH#AfCFDCNBH!AfdClD"CNBHAh}wCCNBH?}AflCX0bAxeCNBH"AfCўC CNBH&AfC3QDCNBH$Ae8CD4qCNBHAdCJErCNRBH"AfgCeE6tCNmBH#AfC>+EYCNnBH"QAfSCwqE OCNoBH$AfC}E`CNBH$:AfC/˅CNBHTAe6CEe_CNBH!9Af+CDCNBH!AffCCސCNgBHAe,CNiDCN{BH AfCGOCCN|BH"LAf7CDy=CNBH6AekCB%EbZUCNBHyAep%C"^DcbCNBHAe CzE/CN+BH':AfpCVC֥CNdBH#AdCgEkX&CNBH"tAd,CsECNBH%TAdpC;EqCNBH-AdTCCCNBH'}AdCE;CNBHPAdCREsigCNBH%Ad^CAE.CNBHAeCfDfjCNoBH!Ad~CUCNpBH.AdRWC$D- hCNBH' AfyCD̘CI*CNBHOAe1C=9DqCNBHAe(CADj.CNBH+AexCXD CNBH%DAfC[jECNBH#AfMC@D.CNBH&-AfCyDġCNBH%EAf`CpC2CNBH%Ag,CYCNBH$XAfD#CDCNBH!~Af8CDCNIBH$AfX|CyCNBHAdFCDsCNBHAfCLCNBHAeeTCIEWCNBHAAd?CD`CNBH?}AflCX0bAxeCNBH?}AflCX0bAxeCN.BHAe̺CEBZCN/BHAeC̈ERCNEBH(AfxC9H2Cae)CNFBH AfCC,ufD#CNKBH!AfC4C݊\CN(BH?}AflCX0bAxeCNdBH.AdRWC$D- hCXBHiAd;zC5CZCXBHjAd?C~5A:CX KBHkFAdO^CBCXNuBHkAd@XCCXPBHjAd?C( B ͐CXP*BHjAd@'CgB.}CXRBHZAeCdCXd%BHjAd9C)ClCXd'BHlAdQCaDCXmBHkAd@ACnB&CbBG/AcHXC9&AwCb BHAe5CUrC[CbBHVAeEC/DCbBHRAeC'ADOCb3BH`Ad C\@Cb5BH`AdtCB CbBH.AdpCxCrCbBHQAe}CZCDCbBGCCbBHSYAeCRD?CbBHP3Af5F=2CbccBH Ac}DCP|E6VCbcBGA_1CBD7CbcBGA_'CDCbdBHAcDWCnFCbdBHAbCfbFLCbdBH 9ActwC>E!uCbeBGfA]CDCbf_BHAc^CyD1CbhBG%Aa/CkF CbhBGlA`C}dXF0CbhBGCA`CEECbhBGPA`Cj{ ECbh BG0A`KCrECbifBGAbYCCbiBH#IAc*C^ExCbjQBGFA_CqE1Cbj}BGA^{C CACbkBGAaf&CkECblBGAae!CjZCblBGΆA`YCYD ACblBG!AaYCiqF&CblBGAaY1CzyE'CblBGތAanCE1CbmRBGj.A]CrDq(CbmBGAbyC ECbnBG֞Aa7C`@Dn|Cbr]BGAaMCjF{Cbr^BGA^ C&DACbr_BGA_¢C}EkCbsBGAbs?CE{CbtBGAaCnECbBGApC"TFv G ABGUAmCVfG BBGAoECEwG BHQAuJCHCG BFěAqPCKFAG BFApzCIFKG BF FAufDFG RBGzAkCG SBG!AkCCE1G TBGAkChG BGAk7C5FG BGYAkCEƟG BGAlC FQG BGXAk7C`F G CBFAvZD E@G DBF2AvDFʼG EBF&AuD2F G BG$]AnCViF*G BGGDAmCEdG 6BG]Am ?CmD%3G BFb4AsQCEN2G NBFApCFnG OBFApPCYEG PBGZAoCVF+YG BF]AsCݫIE|G BFugArpDREvG iBF۬AqChElG BG#An2C/EG BF&IAuR D CIG BF&}AuD#E$G BF0AuD,JEG ~BGAo9CEWG BGEAn CԏE>G BGJAmC)E#@G BFdAq2CFF }5G BG{`AlhC,Ek[G BGnAlCEfG BGAlK\CME G BGAk`C-EL[G BGAkCEG BFApYAC"F$5G BG/vAn~C֕F,jG BF]As̗CKEz42G BFsAqqCFӑG BF߼ApC$+F4G +BFAq_qCȧG BG[AlC/E7G BFAusD EiޚG BFdAs/-CDWG BGAk׹CEmG BF)Aq֯CrEiPG BFLArYyCF8 G BG^Al}CU"E HG*BHAhZ\C$DG*BHAh\CJDxG*BHAhdC@EԘG*BH'Ai"C EAG* BHAi C D掓G*BGӺAjMCțE^G*"BGٟAj>CcFBG*BHAgCD}|G*BHAh}wCG* BH Ai bCCG* BHAi,ClDG* BHAhC4ELG*oBH%"Af+CG*BG+Ai4CEjG*BG+Ai4CEjG*BG;Ai_CEG*BHmAhpCۏD6<G*BHAeCZDoG*BG%Ai #CCEG*BHW,AiK Ct G*BGAiXC)EKG*BHAhCCxG* BH$AgtCZfDG*6BHUAiSCyG*ZBGAiCEG*[BGnAiaCzDMG*BGbAiVCG*BG?Ail4CqG*BHAgCvDG*BHAh'CG*aBGAiC_6ESG*cBGAiVC/D\G*BH AhC+FDhm]G*BH:Ah^C4 D@G*BG AiC+D73 G*[BGbAiVCG*]BH.Ah-CCG*mBHAh-CaDG*nBHAhhC*E@2G*oBHUAgKpAo F(G*BHPAioCEP&G*BGAhDCEweG*BH0AhCtE=G*BG؞Aj!uCT&EG*BGѣAj_CŏeEG*BGAjc\C6D;G*BH AhCE JG*BG_Aiq8CDrG*aBGAiiCG*kBGAiCEIG*|BH"AgيC/dD%.G*/BHAh'CG*/BH Ah'C3oE8RG*0mBH]AhYCeE G*FfBHAhCG*dBGwAjC8E/G*dBGNAjHCE~G*BGJAkKCC%E2G*BGӪAj+WC [ERWG*BGIAjrBHOeAlv3C9CrG>"BHJzAkfCG>NwBHqAtڢCG@E9DG>OO>BH&AwB"|uFG>OTBHAzrCh[G>OfBH(DAy6CaxDEG>OiBH%AywCm˻DǏG>OuBHAzC_EDG>OBHTApCK$G>OBHZoAn2{CYfEfRG>OBHQnAmCV>EG>OBHQdAm"*CzYXG>OBH+kAv1C\q|DrG>OBH%Ay`CZesEG>OBH$AyZCKEKG>OBH$Ax4C^ F1YG>OBH At'CXG>QBHAsiCXE]G>QBHAsCGWDwG>QBH$ArCB>DG>QBH4)Ar"CHrEG>R,BHY,AmbC')EG>R[BH\`AnAwCVEkXG>bBHBPAqCH^F?m\G>bBH7ArRCFi^ERG>bBH?AqעCDΨFG>cBHAsiaC@E/G>cBHAtCEuG>cBHAss#CED6E G>cBH*IAxChtDΟG>cBH*{AxC~LDʻG>cBH%AxtZCv)EG>cBH YAucBHAuCjnEaG>cBHAu!CI(E8G>dBHP AmCu DilG>dBHnAnmCUBD3G>dBHQAmTCqDG>eBH(hAwIFDOG>eBHAuCF4!G>eBHYEAp[CMxDG>f$BGAlCpfSFӖoG>g=BH=ArFCED,VG>i\BHrDAnϚCQACYG>iBHAsCLG>jBH!CAz:RCtxQDG>j BHAz8Ch;G>j!BH#Ay=CEG>kBH2ArUCGoEG>kBH@AsC~?E**G>kBH/+ArC@;DHG>k=BH9kArbCC>SD/G>kJBH zAtWCEE߉G>lBH(ArC?{E/G>lBH6AsNChWEiG>lBH As""C>rE*NG>lBHAsěCYEx8G>mWBHZAufcCOD"+G>nBH AsFCV9D;G>nBH &As%CNg C$G>nBH $AsC\RDG>nBH+Ar\CAG>nBHhAtCUiPEG>nBH AsC{AE~)G>nBH.\ArCCLF%G>oBHQ AmTC{'DG>oBH!AzE`C9DoG>qBHOAqCcCLEeG>qBHVApCLfEiEQG>q@BHI(AqYCIE"E(G>qBBH\AmB&EG>qCBHUAm%C EŪG>qDBHPbAmCa$EPbG>rBH AtuCJӯEahG>rBH LAtCI}IE2΄G>r BH At/C(EiG>rdBH$CAxTeC; DZG>rBHW>Am2jCnF5rG>rBHPAm#CkExG>rBHM?Al0C2ZE[YG>rBH AzC9%E`G>rBHAz!CfFC̷G>rBH AzoCJ0EzG G>rBH,AuCIfFG>rBHAu CECҥG>rBH qAtjCL0DG>rBHSAmC/E;G>sBH'Aw?BGzKFIӁG>sBH%AxEC@sBH3AuC*FƌGpBF^pA:mC\tBh5Gp} BF^A:CfGp}vBF\A1OCTDPBHViAj CZ E{PBHW>AiKCaEKPABHYJAi*CtsCo%DPBHYAiCr(CgPBHYAi+Co$D,PBHWeAiDCiJD NPBHZAh3Cm_E8PBHUAiCg[EIPBHV=AjCgE,hPcBHVAi3C}xLD @PdBHVAjCgQTEYP6BHT?AiCyCPJBHYSAjCPzD,pPKBHWHAj76C]i-EIPBHLAl1ClD?PBHLAkPCOДEBaPABHWrAj_C9EU4P[BHWAi$ChoEl`P\BHZ4Ah8Cj*C3P_BHYAiC]oEǙkPBHUUAiCiGlDͺPBHUAidCyaD HPBHVAkjCp&EPBHXAjإClȷCW"PBHXAjghCEʗDKP"BHM4AlC86D[~PBHV{AiCFE"2POBHM7Al`qCE4PR,BHP.Am"CeDцPbBHUAjCoEPbBHROAkZ2BkF ȉPmBHRGAmzCiCm*;PoBHoLAnCVC&3PoBHOAlC׃DPoBHQAmMCuC;PpBHOxAlC{E{CPpBHUAk^C]#Ejm}PrBHUAkCe%LE~(PrBHO_AluCD;PsBHN]AkjCEZ%PsBHMAkC^DbPsBHO*AktCqN E%zP(OBHS4Aq wCL+FޝP(OBHNAq\CIP(QBHeCP(qBH^Ap;CPGP(q@BHFAq CF%=EbP(rBHdAoCP!EjYU BHT~AfHCePC)U VBHTAfCe;CU WBHTAfCe;CU kBHTkAf3CduBCsU 3BHUAf5CddBu+U BHTAfCdBU ^BHTbAfCd@U |BHTAfoCf!BdU 1BHUAfCdB)"(U oBHUAAfB}U<?BH(WAgoCW.D]MU<[BHH Ah7CqaD(U<BH&:AgCC*uU<BHAAgSCm D`dU<BH?IAh*CwaDK'U< BH'Ag:C}DMpU< BH)rAgCkC.U<BHAAh&C`DU<BH>Ah.@Cz9C{)U<BHAAgC1E0')U<>BHC.Ah:&C*2DzdU<?BH@Ag܍CoD)U<@BH@AgCnUCyU<mBH&hAgdCmjU<{BHAeCD/-U<BH%AgKCo~E>yU<BHVAiPCtCZU<BHZAi CqvU<BHBAgCenBueU<BH%;AgEC\5E02U<BH$,AgCPE lU< BH) AgCeDU<BHCkAgCbdU<BHSrAhM$CmuD#U<BHFAh5Cx}C)ˠU<BHJ8AhCC5E[U<BH'@AgCE:=U<BH(AgC}U<BH#Ag>C DU<BH Ag"CODNU< BH)AgC`3sBQU<#BH@Ag\CayAB=U<=BH?Ag,Cv6DU<BH'AgC|E SU<BHQ:Ah;LCb+U<_BHXNAi&BdE|U<`BHWHAhRCi DitU<BH).AgCpCԸU<BH'AgC{DU<BH( AgCٝDQU<BHZAh.CDU<IBH$xAfGCU<BHZYAhdCnZU<BHOAhD:CZPEEU<BHUAhECfD1U<BH)AgCY]CvZU<?BH'AgtCp2oU< dBH(AgC`CSU<KBH$?Ag 7C`XE<U<LBH!AfCE&DtU<XBHAAgChE94U<YBHJ/AftCTqE}U<tBHWAhWChD@JU`( BDARC+mEwQ`( BDA6C D~`( BDACGL`(BDACC;7`(DBDA C`(PkBD,AlZCGDj`(PlBDIA%C;`(jBDA0C{`(lBDYACCDuC%`(nBD#NAuCI=`(nBD#NAuCI=`(qBDACzr-`(qBDA]CBCO~`(qBDAC`FBDAaC&A`F BDA~MCtDw `F BDuA C^D(,`F BDYAzCiE?&`F BDLA7C}tDƙ`F BDSACWE}`F BDACGL`F BDA@C[El`F BDAqCRlE-G`F BD`AB-E ܢ`F BDGACNrD{`F BDA CNvDލ`F 'BDyA;rCXE`F (BDA*C+!EmG`F )BDA*CVwEmI]`F *BDAC^Eh `F -BDACEy`F .BDAHCn1$E`F /BD}A;CûF9`F 2BDACg]E:`F FBD-AC'`F cBDACqs`F dBDAfCwQ E^E`F eBDA=C9Fo`F gBDACr%pD`F hBDAkCzIE?z`F BD>AtCPΘ`F BDGA!CM E`F BDpACKE`F BDAACM!E`F BDAJ)CQ.EL1`F BDACDpD`F BD8ACwDJ`F BDAs%Cr"E7`F BD AOC[Ef3`F BDAfCDE9`F BDARBE``F BDACi&DN`F 'BD AC D$Z`F (BDA8C1E}`F +BDALCrEU`F ,BD/Ak"CxlEF`F -BDBAR'Cf EY`F OBDACW]`F QBDfA8CSu`F cBDAj@Cf ]E{`F hBD#AgZCWm`F iBDA!C=kDH`F jBDAChu`F lBDA~CL^Ev/L`F wBDJAJC-E$T`F xBDAClDP`F yBDAvCEx`F BDAFJC]uPE(`F BDlANCGE/X`F BDAAEr.`F BDAC{D`F BDnA.C`F BDoAuCEi`F BDAC\;`F BD*AJCfE]`F BDACvID`F BDApC/E/]`F BD@A)CXODuƀ`F BDAgCx*gE(w`F BDACj]ED`F BDA.C`F BDnA7"C_Cz`FBDAC@+lEo`FBD9ACϚD`FBDAl)Ck҉E`FBDA25Cg%Cn`FBDTAqC`FBDKACcYEe`FBDA\Cc+DP`F%BDAgC+EEWa3`F:BDA@C[Ez`F&Cc#;F`ZkBDACEVE{*`Zl`BEkACvE`ZlaBE==AR~C.FJF`ZlgBEAHC\ `Zm BE54AHCvF`Zm BEACE``Zm BE&AC}|E`Zm BEcAn CFBu7`ZmBEo0ACEu`ZmBEjjACEɄ`ZmBDgA;CM5`ZmBDACNF`ZnBE)AdCE`ZnBD A>iCkFd}`ZoBD0AgCFEz1`Zp BDA4CEԵ`ZpBEAwCNEh`ZpBEcAmCBڠ@ `Zp#BEN|AeeCUE`Zp$BEACFvR`Zp%BE)A CFET`ZqBDA*CEܖ`Zq%BESA!ChC`ZqBEAYCsF`ZqBELAdCeaE`ZqBEs;AC*E-`ZqBE,A5KC@Ee`ZrBDA`CEm`ZrBD'A?Cm`Zs,BE7A=CEXĚ`Zs-BEoAPCEg`Zs.BE4A89Co`ZsBEACFk<`ZsBDA3CdFc`Zt BEIArCMkF05`Zt BEA*FCn?XD.`ZtABDjAצCF3`ZtBBDAC^*QE趒`ZtBE&AKC(߾`ZtBEdAnCEB`ZtBEwAMCE?`ZtBEA-CMG O`Z}BF AZCDLH`Z}rBFA.C`Z5BEfAlCDPb`ZBEAkCHo`Z!BFSAC`ZfBEnAٓCbC*`Z-BE+A!Ch[C``ZnBFzAC`ZBFzA\ChB{`ZBEIAVCNBLc8BFAvT_DQhc8BE*A} D pjEryc8BElAzdD+Gc8BFEAvJDuDKc8BEwA~CEc8BEsACc8BEy2ACHE3c8BEA}rDDbԫc8BF&nAuR D 9yc8vBFAv^D!E.c8BEfAz>pC^EhvVc8BEAyND 5F c8(BE)AxxDoC׺c8uBEAyD  Dc8BEAxmDc8EBF8]Au/Djc8|BEADKD7c8BEACCc8BE 7AkRC c8BEAztD%c8%BEAZC?EEc8BEtA}wDECc8BEA{)DF.Nc8BEˍAyCCc8BFtAv2DcB-BDA:`C]EVcBPDBE ACE8}cBb(BEA5CDK~TcBbBD AqCMDcBgBFcyA5CCEcBlBF\A1CD(ycBosBFUAW@CB%EXcBouBFjKAN;CZ#F)XcBqBD؎ASCcBtBEAC}CcBBE A,CE/cBBFA DcL_BE8ADWcLBE3A;CnEJcLBE5A. D?CYcLBEAyCcLBECayEɨcVBEĢAzCqE5cVBEAx|DtDX1cVDBEAy8CyaDAPcVGBEA|YDKDcVgBEA|%D E=]cVuBE|QACFcVvBE$A~CȂFBcVwBENA}dCF1x cV|BEUA\C'=EcVBFAwDEhcVBF Aw#KCCE9rcVBEA_ D9F ScVBEA|cVBDACUXEJ2cVBD3A^C+EC&cVwBE8JA DCcV9BE8A DC<cV=BEAnCE6icVqBEqAwҿDoEVcVsBEAwbDCʱcVBEA~(C>DgYcVBEHACDcVBEwA|DDcVIBEA{AD ;EcVJBErA{ĭDpF |cVKBE A{8ED"EAcVBFAv DcVBEA{}D XE cV:BE^"ALCLFOycV;BEIACGE@;cVAyDDEncVBE[A%CHEUBcVBEAzQDGE7cVBDUAXC_$E6c`BE8JA DCcj BDAcC'cj jBD>AkCiCcj lBDPAsC6E_*cj BD A`CjCGcjEBD.Ah]CGEV9cjPkBD7A_cCCԛCcjcZBDACLCώ`cjdyBD}yACTFcjdzBD;A]C<™Ecjd{BDFARCCEoUcjd|BC4AC@HjEcjlBDkA8CKhmDͤcjmBDkA9CM1cjnBDA|CJH%EVcjnBDAsCTH\DcjnBCAICL*EcjnBDA|UCLgE<cjoBD AoCEǜEcjpBDAC^CcjpBD2lAeCED!cjqBDVMAFCHdEcjr)BD4AbCBEcjr*BDoA3CHEScjrZBDAzCKEUdBF7tAPC4{p xBFAC1CpBFACXp2BFAC:Cip2vBFPAaC@p2bBFwA&C"Cqٝp2BFA Cz3E7p2[BFA#2CCp2uBFACW+s BFѐAC_FZLs QjBF(AyJCC%s b5BFAV8CpEs iJBF A}CiERs lBFRAoCBs lBFAϜC7F s rBF)ACEus sBF?AC\Fyls tBF֗ACFds tBFoAz5C[Els }BFtA(CCCWs }BFAACEw8s }BF?ACc\EFs }BFYAmCs }BFzzAC3s }BFWZACtTD7\ss }GBFoACDs }IBFQACfO?E@#s }JBFADUC1 s }RBF~ACdBDHs }SBFA/Cs }UBFiACyCQs }VBFJAFClf4C~'s }^BF~OAbCp EZs }_BFfA5CE s }`BF;AXLC|\_Es }lBF^NAiCtXCjns }oBFOAC"EGms }qBFiAyCs }rBFACz8fD9s nBFDzAC{ Cs BFȩA`kF#Vs BF$A0RFi "s BFaADK9AF5s BFA`C}&$FVus BFaAEtG#s BFABGAiCs BG@AWCFs BGAmC\F=`s BGAvPCysF-s BFAhC\Ds BFASCW:Es BFAFs BFAC&E3Ks BF,AXC Es BFs9AץD!F[s BFйA+zFo-s ;BFAX(B[E2s CXFD@slBFTACsmBF&ACD5[:smBFrANCfD(sosBFtA DYHspBFAHCbϲDsq%BE+A"dCjBDI?dssBFaAwCWENstBFACD6!s}BFdJACzDs}BFXACZzEzs}BFsAHĴ.F[s}BFcpACYkHEs}BFavACEjs}BFbLAC38E+s}BFXACErs} BF_A,CҦE3s} BFQ#A0C ˛Ew's} BFLA BEضs} BFYACE 4Is}BF[^AjCjeE's}BFzAC Fss}BFSSAC~Ens}BFA_CdX4F6c6s}BFoA6CIUF %>s}BFkA+C5WF&s}BFzJA&CѸmF5s}BFtiA;C%FzIs}BFh&ACEes}BFU2A^CEs}BFLA C{cE1H:s}BFFACcEs}BFEAbCzEs}BFXAZBd~F;s}BFXoACLE)Gs} BFVADCYcDhs}"BFdA4BkE/K4s}#BFVAHCE@ s}$BFSAC4Eo9s}%BFiA C9 E@s}&BFvA[XCFs}'BFbA+Cv!Ds})BFpGAT1CmEs}*BFcA*CVD/ s}+BFc)ANCYDwrs},BFrAdC0E7:s}-BFUACF)s}.BFY3ACeD s}/BFRFADCWkE9s}0BFHA[Cv4E`Vs}1BFHA,CnEs}3BF[A5 CiDs}4BFRAC=Eͻs}5BF_kA#CdDLs}6BFtAhCe E9s}7BFjA8RC@E"s}8BFggAC_Eٶs}9BFVACENMs}:BFPA[CE# s};BFOACc"4Es}=BFe)A+pBƖE6s}>BF[AgCN[C^ $s}?BF\3A5vs}[BFbAC\mD\s}vBFXA3CkhD|~s}wBFWA>^C[E02sBF.A3/EsBF0AdCFq6s BFyAC+DsBFaACfdCsBFKACThEjTsBFIcAiCEsBFKAD pFsBFACqsBFkACgDsBFADj sBFACUJDsBFzA4F sBFATCh^E(sBFvACJwsBFMAіC[Es2BGھAC$cE/~s2hBGwA,&CCus2pBEAC;s2qBErACmC-s2sBGfPArC)DK={s2}VBFv,AC}׍s2BH#wAtD&rFs2BF&AdCjD` s2BF:ACzs2BF۞A!Cs2BH-AD"gF9Ks2BGA9C1E 7s2BGרACCRtDs2BGA/BeEks2BGA CBGlA{oCbQEy's2QBHIACy8Ep+s2RBHAbCÝ[EEs2SBHEACF(Vs2TBHpaAVD>%ENs2UBH$A:5D&qF\s2BGUA3CE$js2BGAiCE#s2BG ACMD s2BGWAQCEs2BGqA=GCC^{5s2BFA{Cd/Dns24BG@A}C.E{фs25BGAzYC!Es26BGAICEhs2BH A)DPIFAs2BGL*AxCq#iFo's2BGADCwEfs2BGACqG ls2BGw A@CE֑s2BGA.CDFs2BFAظCB C"Kns2BH\AD;rFs2BHF6AD"FPGs2!BGb^AFcCCs2cBHDADgFL"s2dBHA:CFCs2BH1AID#Fgs2BGACF%ls22BGЕACOE4es23BGrAC Es24BGAIC mElVs2aBFA CzRE s2BGUAs2BG؂AשCrBTes2XBG>A} DUG&s2YBGwAȊC6FY9s2ZBGv(Ah¬YF7Dos2BF2AjCAs2LBG\ABERs(sqs<,BFAHCrE}sEj`tstE *sPBEԣACD.D';sPBF*A3CFsPBFAC6FVsPBEVACT9CasPBE֕AXBErsPBFTA,Cv-QF#sPBEACXN-DsPBEA0C][EIsPBEkAeSCADsPBEgAlhCC8DQrsPBE=AC(F#sP3BEA=C5~l(sP5BEesAmCCCXsPDBFI4ACXsPFBFVACED sPCBFAC5@_sPBEۡACL}E^msPBEϨA/CQ\Dm=sPBEA CÐF!4sPBEACHEO sPBF:AMCNFOsPQBF'wACFmsP_BEdAnCFBasPBF(XAeAFaosP BEACq0EV .sP!BFACDu sP^BFA+Cl:ETsP`BFAڄCgC@c1sPqBEύAPCRǖDsPBEA}CW ?D?gsPBEWA-CR-E6sPBFUSAC;sPBF0nAvCD#CsPBF9A-;CL)sPQBF.AGnCsPRBF=NACsPBE9A+CVPCsPBEօAIB,'E;(sPBEҊA$hCgrE-sPBEAvCE%"sPBFKAkCDJsPABF2AaCqsPUBFAZCsPdBEA{NCQ(EnsPeBENAC]EksPfBE.A,CQFƾsPBFCAD+F+>sPBF:ACiXFЕsPBEAټCH4aE)sP%BFALCF_sP&BEOA)C`E8PsP'BF>AZCȰEtsP.BFACxDysPBEcAmCGDBasPBFAC BpsZjBFA+ZC}E$sZyBF1AC;9E!sZzBF|?A7C9DccusZ~BF4AreCCe`sZBF8AuC}sZBFACEA8sZBF.AC pEs~sZBEGAT C `D$sZBFAkFsZBEaAAC1C)sZBF0iAQC0YDhAsZBEAC DsZBE'ACiEqsZBFxAYCEsZBF A-FsAsZBEAVCiGED;sZ#BFA CC'sZ*BFuOAMC.E>sZ,BF{A5{C:EisZBFACFk(sZBFSA)CU?E\DsZBFA'F32sZBF#AfCDgsZ?BFA CDԗ;sZABF"AC|DvsZPBFGA CC#sZQBFCeACDzsZBE ATC$CVmsZ=BF/AoCFhEJ)sZ>BF:AqCFF sZ?BF#ACE._sZBFNAD F OsZBFACEsZBF[AC7EgsZFBEA3CGDssZRBF)A* CsZBEMAJCpMD-sZBEA)CDCETQsZBEAZCE5EsZBEAk;A*FdsZBF!0ATSC!Em7sZ+BFWArCɥEĕsZRBFuA\CRiE٢sZTBFr2AR}CDAsZ{BFAT"CpDsZ|BF'A[ CVD " sZBBFAGϾF$sZCBFACekE\=sZEBF4AD"CoEx&sZBE"A C CsZBEA]C(XD{wsZBEAkCTD;sZBF@6ACtxE@sZBF?AC|DÓsZBF5Aw1C AcdYsZ=BEACD/qsZ>BEACDl sZ?BEAC\=E; &sZMBF6bAEB\*FrGsZBEA1yC6E"3sZBEhAA5 CEAsZ BEڡA'CnD)sZ BEڡA'CnD)MBHX'Ai=mCYDQ_NBHW9AilC]EkBH[AfQCH}DIpBH^AfrCA9rBH^Afs3CV/\BHaAfCDlmBHAf+CVOD+tBHeAffC?E'BH VAtbC;NBGyAylCqF,OBH A~gCVDC2PBHA aCyEŵBG8A'pFmBFAv͌}wH$BHiAfACBFACa BDoACvD(BEiAXCCKBDAkCkjAP BDAs CzBGA=ŰoHVCBF·A/CCBFDACpE4 x BD´AC[ BFA6CvkE.mBFA1C7)BB{ACcT BDɵAدCgy BDACP6\BGvACs'h BD­AC[d?"c]EBDdALCkBH^AfsCc C ]BFAՖCBDCAUPCcj=&BEAݒCCW BD͘A&CXBHXAf6CBFACs2C'z$EBDڔA_-CcD4BD2AaCtqJBFAsC_;d BDAC BDAC@BF71ACkd-BFdA8C,C6LFBFH A-nCCMBHtAf8LCYcMBH`AhgCPVM.BHfAhKCEz^M.BHfAh@ CEFC˂ MUBHeAhUCG0C{MUBHfAhJC5|DE`&MBHeAh?C@BD* NBHdAhCL+C̠&NBHaAhC2DǙfN7BHSAiCa`NBHWAjCc7D^sNBHY=AjCr)NBHVAjClD]NBHUPAi\C`DZNBHcAhqCLpDINBHULAij5C^ěDSNBH\&AiKCWNBHcAhCIF%NBH`7AhCSD{NBH_AhCTkDNNBH^UAhhCSNBH^jAhCTFWD NBHZAiNCW8D36NBHWAj]CyDO;NBHWAjyC} EWC5N*BHVAj6CiZXEN.|BHcAhCXdDGN.BHV(AiCcD`1kN.BHb{AhjCP"D@N.BHY=AjCr)N.BHVAk C`D|wN.BHW)AjlCE8N.BHVAk CbLN/ BHRAk?yC~ lF \Ny3BH^UAhhCSNy=BHSAiCa`N|BHdAhCL+C̠&N,BHSAiCa`]NBHTAiCd]k_BH]%AfCJD:0dk`BH\Af|-CAikaBHYAfX}CKDfkbBH\QAfzhCq1C%skiBH[#AfOCWHD|koBH[OAfu&CB~kBH^ AfdCiDzkBH^VAf"COD^kBH]AfCŠCskBH\Af~CCykBH\Af:CkeBHZAf\$C|k+BHYGAfWCxCZk+BHXAfYCsrC k+BHY6AfMCxZDI:k+BH[AfYC$DTk-ZBH]Afd#CfoD k-wBH]mAeCk-BH^AfenCvDR5k-BH]`AfOxC}(CT-kRoBHUAfhC{kRpBHWAfSCN7fDTkRqBH[Af^hCDkRrBH]cAfUCoA2VkR{BHZPAfMCDskR|BH]vAfT1Ck-CkRBHYXAfLCrCD87!kRBHZ9AfY_C8D19kRBHZAfYCdDkRBH[xAfu|CdB kRBH^AfqCjHBkRBH^Afe,Cu8DLkyBHXAf@BCkyBHZAf\ECybk{VBH[Af^Ck{vBH\MAf>C\CէkBH]VAfC0CkBH]WAfC8CY kBH[[AfkCGDikBH^AflCYkCi/kBH^ZAfCOkBHYLAf{@CC~DkBHYAfP3CzDkBH\AfiCSDkBHWgAfUCzDV)kBHXAfWCf}DkBHVOAf[C~#>D!)kBHU3Afe>CWC-kBH[Af2RCpSBH^AfsCjw@p_BH].Af#CKpaBHWAfNCDZ(pcBHYAf?CpBH^Afs5CrBpBH^AfsDClBĞpBHWAfRC|VDg'zpBHY_Af=vC5Bp+xBHY5Af[ CYD]p+BH^AfsCiKAp+BHXJAf`8C/Cp+BHUAfhCR`B?p+BH^AfrCr7B -Xp+BHU?Afh2C~lp+BHYAfAHC#p+BHZ*Af^Cc0!p+BH[AfYC HD pRoBHUAfhCxpRpBHYAf=*CpRqBH[AfZ"CMDkpRBHYAf;\C}pRBH^AfrCf pyBHVAfJCB,kpyBH]AfCMAɡpyBH^AfsiCvA.wpyBH\AfZtCyuDkEpBHXAfACpBHYAf?ICACpBH^AfCM$AZ1pBH]6AfCCypBH[Afg_Cy!DpBHYAfP^CuD*pBH[AAfg;CsTDOpBH^Afs C>AqiBHZAf_CkB qBHZDAfaCQ0bqBHAf7CoWAـ0qBHZ9AfauC[ݲq+xBHUAfhdCxPq+BHUAfh]Cn3@4Lq+BHYAf\/C\3q.9BHAf7C6AqRqBHYAfBCBiqyBHX5AfV'C\LDx5qyBHYAf:C!C*qyBHJ0AgqCHqzBHI(AgCz{AqzBH[Af^;ChqBHU!AfhCyRBn2qBHJAg4CBrBH^Afs4CjrBH^Afs#CmAyh\rBHYAf?|CrBHJTAgCn\CۭrBHJAgCRC̥orBHjAfZCxEDrBHKAf|CreBHAeCXE^8rmBHAeCm@ZrBHAf7C~ӶrkBHi;AfCrrBH^Afs6Csr6BH^Afs7Ce@Br9BH[AfYC|bDrBrRpBHYAf?C#@XrRqBHXAfHC|gCry}BHWAfECfB[ryBHY:AfO4Cj^RDryBHK!AgCCϾrzBHJAgC~C>[rz(BHL'Af]C^Bzrz)BHL'Af]C^BzrBHJ9AgCBCrBHJAg1CωB*vBHAezC>'EzvBHAeCv-BHAeCy@-BH#nAfMCBH#nAfMCBH#nAfMC{DBH#nAfMCvBH#nAfMCaBHZAf@CAB6BH^Af'C}DFBH\CQqBH|AeCBŽvBHAe CT{BHAe CTBHAf`CD-PBHAeCp8CU-{BHsAfoCy-BHsAfCA L-BHtAf+Cgr-BH)Af1C/-BHsAfCqB!.*BHrAfjCC1@.9BHzhAfC,Eq^/"BHvAeCEC/BHVAe@CTFD/BH;Aek$C<)0BBHDAejCNVRpBHWAfMnCmBeT`BHuCAfJxCFWCʾ{tBHsAf 9CBj|BHtAf)MCFDB|RBHAf4mCDE0|BHh Af+CzE+1BHvAf7CH>x:oBH~Af!CEAqBHAf-aC/BHAf6WCvAiBH[uAf2tCBH^AfsCr<BH^AfrCqB2odBHAf7aCw0beBHAe%C~EWXmBHAeCm@BHgAdC̋ABHi!AfC@BDBHnAf76CTDD0NEBHg%AfJCVFCFBHcAf-qC{D=+HBHpAeCE `KBHoAdxCDYLBHo^Ado/C CiMBHoGAdsCi!Di NBHAeCxTEXPBH`8Ae&!CZ6E- 4QBHAf7JC08@ UBHAeC-YEVBHAfCDWBHAe3C`cEA@YBHwAf7~C E dBH_+Ae4+CcCiBHrAfC~SDkBHgAfCIDXlBHhPAf TCD^@SmBHu{AeCEnBH_KAeC|_DuoBH_Ae&3Ce8DԿpBH_AeHCCD-sBHnOAe\CE?KVtBH`EAekCE-uBH`Ae4C[čE=vBHoAfOCODDwBHjAfQC^hDcxBHiAf&CDAgzBHi?Af$CAsb{BH]JAe"C-|BHnAfC~D)}BH.Af CJ}Dq~BHuAfCC8XD4/BHoAfOCODDBHhAf%CE%-BHmAe"Cy"2DN-BH~AfACE&-BH~Af?CCYSVCE-BH~TAf>LCZ )C̷-BH~Af8C]Du;-BHnCAd~CD\#-BHi?AfC-BHoAdZCC[-BHnAdOC?B-BH3Af2XCrCS-BHx'AfLHCG D~-BHAf"CE(N-BHxGAf?CEp-BHg$AfJwCX8Ba-BHk~Af%C'E'C-BHdpAdgCD$-BHh`AdCzEW.*BH{AeEC(pEWr.4BH^AfsgC_.9BHAf2C7cC1/BHAf!CQ@/BHAf7C/mBHo9AdOCCBH/BHAeCkD/BH$AeC۾DZ/BHAe-CC^/BH+AeCxE3/BHIAe}CD/BHYAezCCDlR708BHgDAdCÌCl0HBHAeɓC3C4KRrBH][AfS2CvtB@RBH^AfrCp\A&6#T`BHAf7CVdATeBHoAdwC ThBHnAe)CxD@TiBHqtAe|C\E-1TjBHlhAe1CxE#TkBHqAeCsdDNTBHfAfG CO?A4zBHrAfC=C2{tBHnAf>CfD2{vBHaCAf$.C4Eڥ{{BHkAd{CbDZ{|BHrAeC-E g{}BHjKAdC E'{BHq'Ae&xCE{BHsAf>NC1D{BHcWAf#CFE1 {BHaAAdCV(E;Vp{BHtAf,KC޸{BHiAf.GCr D{BHoAdkCMDh{BHoQAdX6CCA{BHo6AdOC'B{BHo9AdOCCBH{BHoyAdOCY7A{BH]GAe4C{BHcAf CD{BHt)Af#C|BHsDAf4CDe|RBH Af7wCp@|BHsAfC-^C,%}BHpAfCBHbAf:CteE |,BH_qAf|C CBHnAfACmSDHoBHzLAfCh+DsBHoAdYCC-ɝBH]AeSCCBFA6CE.ta BHAeC}$DGT 8BHAe\C2xB!-xBHAe@BHAeCv1BHAeC2EqBHAeo@C3CvBHAeCVvD{BHOAeC!{DݻBHAeC-QBHAf!_CKȴ-RBHyAeiLCٚ-BHXAeq+CC8v5-BHoAesCxB -BH>Ae CBkH/BH Af!nCPA</BH Af7C@ /"BHAeȗC:lE%0BBH AeiCZ3CB0CBHxAe CEv0HBHAeC/DΦ|RBH Af7zC{@ >BHj2AosCBnZD \BHnAo%CI bBHkWAo_CCCڛ hBHjAokNCBq'BHPuAmtCIDzBHSzAkHCBE gBHSRAkdEC[^>BHL)AkGCqE*?BHQcAkszCc8D)p@BHLAkCkDD BHSAn(CL BHZ Ao D9F?T BHTAlB;GFY BHRAmhzCf BHWsAnQnCJ D %BHGKAq۶C= /BHAAr-C>szE2\[ LBHWIApC,QE| MBHJYAqrC5F O NBH`wAp)C6)EH OBHg,AoiC<;E YBHSRAkdEC[^ ZBHmAo2CEEs [BHa8AnC/`Eʚ \BHpAo"CDD{ bBHoDAoCHwD؂ fBHcAoC5hC yBHS$Aq yC {Df5.BHRAnCOp/BHLsAl"CnAE>/BHIAk'CnL/BHMAkfCEYe/=BHOAmNCE8/>BHNAl|Cf^E{0BHAArC>E>+0BH9Ard!CBCfE0BHQAq(ZC1u=E0BHQAnbCOIfDCK0BH^AnICCXNE 0BHP2AmCDzGWBHb]Ao%C=-F-wWBH]iAo|D FA~WBHWAnCAEJWBH`FAn}CaE æ BH<5ArO=C?pbæ#BHPUAmCFE(æ$BHP$AmpCGE BHAunCJE~L BH4AudCGEdjG BHeAsp5C7CN BH"AskC3JD BH'ArjC?iE|@7 BHAuYCDEZ BH(AvOCEhM BH"fAv2$CE9I BHAuCHF BHeAsp5C7CN BH tAslCE1B BHAsCF6 BH0ArC=-E "BH AtC= #BH"AskC3JD $BH)Ar/C8E %BHBEAr$qCBD/ /BHWC' FBHAsj_C7CB GBH.AsxC8~D  HBHAsjEC8Bq MBH6ArsCBE=o SBHAuCCE0m TBH BAtkCAD UBH AtaC92E44BH%+AvCGwDLK5BH)VAvt~Ct)$ESBH"AuCAf0BH(ArьC/:0BH5ArfC6kE0BHAuOFCBEE;0BH/Ar]C2E -C0BHAuCEC˕0BH{At|C"LEı0BH }AsC3wDH0BH`AsC4D'1BH&SAvrCLE1BH Av"6CZAEז1BH5Ar{(CHCs1'BH6AtqC=:Eܤ1:BH0ArC@}E/+>BH(AxTCA%~BH>Ar>C<1BHAsIC32BHAsj!C3Bl3BH&ArC2.E])NBH'fArC/#E-PBH-`Ar6C2EDXSBHAsC1DߠTBHAs'jC9R^DsĦBHAsYUC4&YDĦ BH2ArC7Fa7ĦBH GAtrC;;|DUĦ(BHUAsuC3PDBFPoACBFOMACD BFUmABGC6BEA;CPBG?AC @MSTBDCAUPCcj=,&BDͷAOC(D;kBHWUAfMrCy*BDaWA>C9 DQBE AC8>BHiAfS!C\EKWBHdVAoڟC7LBG{A<C1GoLBHHAmoCuA[SLBHJA[ZCDI'L9BG$A$D*>{LjBGAC"LBGArCCܰLBG$AD @@0LBH~|ArFULBH~ACMEFLBH4A|C#EZLBHxA3D%dFYLBHu_A&nCFoLBH~AQCsC3LBHAA*C0RDfLBHA}CCLBHz'AnB1E.LBHSACEBH$8Ax(UCME%L>BH!LAz%CKEL?BH AzCXL?UBG$ADd9@l#L?WBG$AD@#LABHY ACRDrLABH[A}C*eCLABHAc½F̣LABHlAnC8CALABGA+ CʭLABHaAbC|DI<LABH|ACeaE[LABHwA҂CFpLABH/ASCEE LABH~DAC DvGLABHpAD _F0LABHyA0C8jE LABGAcCsF٨LABHf[ARDXF%LABH~WA"{CKEوLABHmAcCOaELABHhA*CsE|LABHiA=Ci;F7LABHo`AD`F?LABHDAlCwLABHGFAsCB|ELABGA]C LABHTAC^:F9NBG$ADANBGoAD>sFNBGZKArDJSFݰNBGAiDGFNuBG%.ADNBG,ABV}ܘF{>NBG_A CHF#kNBG|AWU3RENBFzAC;FoNBGNATT¾`FCNBH%AC6SD5uNBH#ADZ>`F\8eNBHA/CE-NBHQAC4E<_NBG ACFFNBGACENBG͈A`χFXtNBGO@AfD ֻFNBG?AD={ENBH_AeDCE"XNBH<=ADhEmFNBH.AD/^FjNBHUA-C,ENBH)XA4D^)2FgNBGAe#CENBGAĺD sFNBGAHCdEvNBGgACWDNBG AdDYF鬍NBGяA0CE~)NBGAϐCNBGpAΒCoJFXNBGMAC]DYyNBGzHADD-@FDNBGEAxC`ENBGY+AD~Fr`NBG.A]DhͬF,7NBGY;A`DϔF|NBG2AoD"F˃NBG; AD2RFA_NBG8A C8N BGXwAdDF"N!BGsA &C|FT}N"BG)ACACVN%BGyOA~C+DL*N&BGeMAoNDEڽ6N'BGeADcFN(BGAD1D ,EhʈN*BG7 ACEXN0BGACwBSjcN2BG~ACCN3BG$AC;EN9BG2AC7FN;BGA^CGtENBH=ACnhN>BH@AACsN?BG5ACsC`AN?KBG]AC(9BLN?UBGJ ADyJF=N?WBG$ADAZN?XBGlA2C\NABG ACNABG4ARmFq1NABHNeACOE3zNABGu=ALDu@ FR`NABGfaAYDIFlNABG>AftiFMgNABG3AJ BHF)NABGA/AAVqRE4NABGDA@C EINABGA C/ENABG?A)CвF 7NABG-AJCNFZ$NABH@AǔD jF%cDNABGTAG%CFNABGACL:F ~bNABGCAc:C FdNABH@AwD$`FLNABGIAMICs%E(NABGACRdFcNABGAZC$EQdNABHPA4CF O9BGA]CcG>O;BHIAgCDmJOOfBG$ADcAAOhBGRAC)hF'OiBG:TACE+ OjBGdA3D iF@OxBGEAa=C@F,6OyBG\LAC߯~BVOBG]A5C?RE"OBG\MACVB@ OBGA^#aFIGOBG@AqDwEOBG@ADtEOBGA߂D+Ge[OBG$A6D`!>ROBGAKCCTuE@yOBG:ACOBHwACRC1xOBHzAuCA@CLgOVBG&AuDC;1OuBG0AD "E&3OBGANCKDAHCONBG$AD @O>BG$AD?@{5O>BGsA5QCK>O?BH'Ay:\CamO?%BG@ADE:O?*BG$AD+>fGO?EBGA1Ct>O?KBGIAVDLFO?LBG@AyD@EO?NBH~A~CkC O?OBGHcADQEO?UBG\7A˓D FFd%O?VBGARr@"FkO?WBG8A;D MEUO?XBGA .‹G opOABGA6C!CYWsOABGAC(JBOABG]AʽDFGʑOBBG=AaC9DmOBHA~CA2,EOBG\GAC{tAPBGfA.5G#hP BH'AvzJC=C<PBHA{hdCJԾENPBHA}oCHDn9PBH&{AylCCE#PBH'&AyYCP Ed0[PBH(AwjCFtDPBHH.A{CfC fPBH"AyCTPBH7A(ٍE<PBH&A@FFf<PBHIACBEf}TPBGABAF&PBH1AFMClF(SPBH%ACC}F)MPBHJAaC)E_PBH A{ C\NF PBG AlCGPBH A{0CED,PBG$A DXP BHA{CYeENP BHVA{CFɕE PBHAzRCXHE߶PBH A|dCIcCk~PBH SA|#eCJMD1=JPBH IA|9 CK=D^PBH A|ksCIѪPBH A|sCIID<PBH VA|9 CQDMPBGAx¸G7PBHA}CB]E^P!BHA|;CJ—D1P"BHfA|CLהDꞁP#BHA|CNnD. P$BH vA| CG!CЬP%BH \A|4CM{DP&BHA{/Cb/0EP)BH A{hCWD*C'P+BH mA|CKD@3P,BHA{dCU_EP.BHA|CIiD'P/BH AA|UCJyP4BHAwSCG]DP6BHfAC3F+vP8BHF5A CP;BHTAWCGDoP=BHA~/*CIËDP@BG$ADU@[PABHOA~CIѪPMBGA0CGkhPeBG+A~Cq@PfBG$AD@EPyBG\IAC?qPBG$AD4U@PBG҄ArC^2C0UPBG:ACPBGǿA+CF'PBHJA}CtDБ6PBHS;AOC3PBH*AxC=\P>BH6A'B- F#P>BGfA5ŸGP>BH A{݄CBtDFP>BH >A{&CS EC;P>BH53A5C_PEcP>BHACEXF nWP>BH A~u>CPEEnP>BH3A*AӠFP>BGAm nFN$P>BHAM @woF$CwP>BH,A(CPyEP>BHA{|C]*EZ P>BH AzwC.EʬqP>BH"AAy C*XEFgP>BH"DAyC)FEP?BGAlpBDTG/P?BH%Ax 9CeFP?BH!Aw.C,VEoP?BH&Aw'C*F WAP?BHjA}1CH?D|P?BHA}bCAADP?BHA|~CQ9XP?BH.ABSC FK3P?BHA{]^CY)P?BH$uAz$CRa*FlP?BH"AxLCъF0cP?BH$qAy.CԚSFdP?BH(AwoED)P?WBG$AD>P?XBH-A}C8PABG%@ADJ5@rPABHLAMCO;D PABG$A DPABGgA5C(CPABGϸAKCfES,tPBBHADCyFBG Ag}Cb+EpoBjBGc.A.SCcjBrBG5A+WCWzDBsBG5{A"C_@D<B|BGg;A"_CTrCPB}BGfA#!CU|By6B~BGh5A"kCG'B3SBBG46A/C\+D}BBG1!AKCilD_BBG:ACf!D5BBGHzA&CSA}BBG7A"CsE@nBBG ACZE%!BBG(6AѴChD†BBGE3ACT&BBG9@ACdC_*BBG4A CgqhDfBBG>ACabEnBBG_A C}E5uBBGSApCE YBBGVtAݝCR6NDEcBBGXANCP.!DY BBGPAC#D<BBG/AلCEYBBG1XAȉCDp;BBG4AEC`DEBBG ACpٚDBG .A|vCm)`E1SDBFA OCjDBG AFCg9BFADC BηsEZBFAlCsf2CGEnBFAgCWXDEpBF[Aa`C|8CciBG>A@CEp;QiBG4AACj-iBG3AC1xEGiBGTAԋCRD͙]iBG4ACiBDaiBG2ACcQE[iBGVACRD iBG CDBKiBGDAHPCiBG@A?CiBGBAhCKsE giBGOAoC`lD[iBGAHAC]_E6iBGHYA_CxE-yliBGNACPD:AiBFرAC#%FG2iBG/.AC^CdiBGgA!CNiBGQlAh#C|iCg"iBG:A3CuCnǏiBG-SABC4EvΏiBGACJE\-jBG#AhCRY'EjBG5AC'QEjBG<^A5CBE R/BGPAjCo hD(BG5A}CyEBGQxAcCx^tB:?bBGh/A" CPxBGgA"@CEaB!-(BGMFAC E=)BG3qAC]Ed*BGLAmC]0KDBGgA!CE7A ޱBGfA"CNAdr0޽BG7AABEô޾BG18A'C EBGSAUߎbEG7BFAAVzG IvBG aAZCAqDRwBFAqCcDs{BFdA6C6EBFA0ChWDBFeACBFA`uECBFnAimCe6DV)BF2'A C!F8BF3AH,+F^:BFnA,D5AYGBFAucCvD9HBF:A=C{FedYBF~A{DTBFA.D DBBF AC}E֣BF 6AC jCrU BFAC2GEz2BE8A_C籪CblIBFA4A&CMDKBFA'CfCT>VBFz4ACYEBF^A4D![FBFeA*COF$BFXAC._EWBFqAAC-F_BF]uAѐCBFACO,FBFrMAD FDBFAGC F@BFRACrE*:CBF&AVHCmEA+BFAtCL7FLBFzARCC)M5BFoWA2C`F~9BFmA:CF3BFSAiC$FZ:BFACO'FBFoAxCt2MDSuBFAgTFU %BFAc0BrFF=BFHAkFOBFAByFt{BFlA3*CE9bcBF\*AzC EBFjAC D`BF9AhKC^F~]bBFQ.A9BDFCBFwuAOCO3E֢BGAm)CKEyBFאAAXF%BF^AgDDIFBF6ANC("ET BFޓA1óFBFLAfDB4SF BFMASDFzGBFAB¦0ECPBFyAeB^BEVBF ACloD;N`BFA`D(EnBFARCǍFJBFAT. FfmBFxA,D FBF]DAp2CXGBFACٷ8C1CBFA1CiF BFAEBF%A&D~9JFEĊBFA.DjFBF3AE8DGBF"A\CvF0"JBFALC^;E"BFA]CɥE#XBF-ACWFC_BFCAsCZFzBFBAuC2F1MD!BFacACCEH"BF@A\CEX#BF&AbCcF3$BFACE%BFAC+=E!=&&BFADCEZ?Z'BF^lA@C#Fb%(BFA!CˉEF<)BFAC7Eb=*BF AfCJE*J+BF`AfC|Eu~.,BFkA}BCE-BFAnCLE~6/BF8AC,Er6BF,AC7BF A-DhzF8BFACi:Es9BFpACTE.tu:BFA$C0Bڔ;BFApC2E >BFAy:CJME^q?BF ALjCDTKm@BF AKCTDvEBFA~CXTE9RBFAnCjCQSBFACEĺTBFAdCo)UBFAT-CgEzVBFAp&CpDDWBF1Aj7C| \EH2`BF A[SCE`DaBF:AC?DD cbBFACbDjhBFADCU[E)iBFACFjBFACE܅kBFAvCVE)9lBFA>C DBF^A'3D!ZBFA1ID9eDwBlBFРACFKRBBFAKcDb°@BBF^A0CjCCBFIAC)yDtBF~A0[FqDBFA%DܚBDBF9A'oD#B1GDBF0ALCAH6DBFAcCJLFDBF_A/D+FbDBFAǧB/FDBFzBAl^C*EMDBFRA.CxE?)DBFACFDBFWABCsFQDBFlA5D`нF@EDBFWAB%[FXeDBFACkFp~DBF-ACD+5FG%DBF"AD?F DBF?A{C1bF?>DBFAD"FpDBFSA:Cv.EDBFF4)DBF`A_oD fF[DBFARGCFMXDBFrA7ÑRFk}DBFlAEC!qF\'DBFjA{C(EDBFACQFEDBFAZ"CpUEDBFϘAhCiE7iDBF*ACDF4B DBFhA(CEjDBFAH-CKsF.]DBFoAjDF~DBFAzCcF DBFArCŮFDBF[AŸMFt[DBF/APCWE"DBFA CE `DBF;AuB"WF5ټEBF+A2eDE-EBF{A~D4F8eE1BFɠA;-CcF2DE2BF`AC?jF%E7BF A[SCE`DEBFAoC} EE@BF\A_DYFi`EBBFAC,EECBFfNA=C{FWREDBFAb„EEEBFACEOEFBFAHBOYF{EGBF3AyCEEKBFMOA˃CfEzELBFUA:CCEFZEMBFRACCyE91ENBFCA CKE,EOBFAfCq+E5EPBFACEDӔERBFqAKD2;vFW<EXBFA|CuEpYxEYBFAjgC2Eu EZBFAWCbIGE#(EfBF\ACfF1EkBFBAC)ETElBFAuDF%EnBFVAfCyDEoBF5A|ClEzEpBFAhC0?EiEqBFA.C_CEsBFVAcCyEBFAlC+D wEBFACUF,iBF!ACEF?iBF~Am)CB[LjBGACa#ND csjBFkACBF'AnCڅE Ol?BF8ACEȴl@BFfA(CFH6lDBF,AChD-qlFBFoAYC EKdlHBFcAyCE.k&lIBFA8Bv ECBFuAmCvkCBFAlqC}YCo;XBFbA(CċEhYBFAyCwEZBF@ACHnEv@޽BG A CU. BFA|C% BABFADIGdBF4ACuCutBGAzC~nCMLBF?AFCI@Ep$BE~AKC5)BF~$AD E_=-BEwAʠCaTC7K/BFAA CD2BEA3#DF/3BEAC]n4BFVABȉmE86BE{ACT5Eh#CBFAACtF`<DBFAh;9F\EBF/A^CQJE:KOBFKWAFC;E4VBFA5CMFS?lBEAYCH`rBEA42CLvBEAC]n{BFIAgC){D'BF_AxC EBF_A+CID&BEޥAICYFŷBF%A%CaFBF:ALhCmGE\BFtAxC~pBFACHbFlBFAmD4FX BFt.ACsCStBBFAm5C1v4EBFABF BFAGD]bG #BFiXAlCqEڧ5BFSAC%BFfMAC鈱EBFW7A2wCJBF[ACa_CxBFA7C~J_F|BF_ADOYF5BFYAI_FBFOA{R E'BEAkqCn?G ?BFȟA*AF#BFAٶCoFڱBE+AvC]*F69BFsWAzD#EBFACiDTBEڲAABIFBFARC+E7BF\ACjFmBElAǸCAFBF&FA0C1EBF"ACpAtBFWA.C C#BFG"ACF5BFɀAh;B)EhBFADC(3BFYANDnhDf~BG AdC3BG-A&mClrD؟ BG'AdzCFA BFAlCF/f8BFA7FBx BGA9 CUqEBG%VA2oCsF\BGACC FRHBFәAuD4FTBFA]BFOBGACEe.BG}A 8EBG!A0C9F#%BFAe D8OFWsBG1YA?C?CF+gBGAAIC%F$?wBGA$CcdD)BFAD F3YBFA"BuFOABG}OACtDZBBFACtF0>B BG AgD_DȏB BG AgD_DȏC6BFRAlC(GDCCBFSAD.C_CBFXAaCޚCCBFcA"RDMF%CBFP~ARF2CBFqA_CgF CBFv"ACCBFTyAl^ FUQCBF}AC33CBFxNAyCx@CBFxA CeODP՜CBFrA8DLaFBCBFAh-5F@CBFz ArCvcDWUCBFAqCPCCBF3;AbMCKGCBFDIARCQ:/CʤCBF< ACP;#CBFA FR;CBFґAID'TF:CBFADCo+CCBF:AtF CBFACoFoCBFCA CF:F)CBFAfC'^FgsACBFA}CزFT;9CBFo/A|CE>CBFiLAlC]ECBFnAC(iECBF^ACFCBF>AbCrF^CBFA8CFxEBGzABlCoTFG@EBG8%A?"CF4JEBG%AFCCʉEOEBGěAoCdEBGNApeFOEBGwJACDEBG@5AIYCEEEBF'ADnFBdEBGBFACgiC`jBF=AC?ЇjBFӥABC{Ee!jBFZACRF~jBFzACbCtjBF4AC9C&VjBFӝA{#C˺DpjBFlA>CECjBFNACeHnEEjBFZACe(EjBF{AdCfUC'4jBFtuArCo;$EjBFnACP`E'jBFh@A2Ds1F)jBFqAiCEjBF{uAC6DCjBFACCEc,jBFA:B=FFjBFXA-ÃF~jBF{AkCtCD#]jBF;ADdtE7Vo(BFvA7aDՁfBEtACCTvrE۲BFA"uDKGE&BGĬACBF([AEG7BEAC E  BF"A*CxF !BEAC‹B"BEA_ChREg#BEt{AB5Fb(BF"AA› GI)BFPdADgG(O+BE`ACQКD9,BEA=4C?Fr-BEWADoF$l.BF)`A"CF/BFAET(6BEAO T/E؏8BFbAmĵ%Ff9BF!ANCHEwj:BF.A DAG;%/;BF AefD6F)?>BE"AmCA{F7#9DBEA:CE1D*>GBFAD^ FHBF!A#,G{4IBF AԒÒ FOBFA'DQFUPBEuAC7FRBEbA+BEYYBF>hADFڐZBEA(EG[BF"&AbZsGbBFDmAGdCvdEocBF6A^CrF!dBFKA1B F&AfBE`ACB;F̿gBF]A CwFkBE(ACODHlBE3ABC*xEmBEgAeC6EUnBEԥAgCOFE pBF"+AmDUF=qBEbA C3FrBEACWZEh-sBF$=A7iCE/tBF1AAC"v`E[lvBFAEdDhFqNwBE׎ACE+&xBEҢA,Ce.sE' zBFApClF V{BFA1tD9fnF |BF9A `C9C#~}BE#A3CFsBFAkCDBBFJA;CMDCBFOArCJXE$BF3AfCqdBF"A&CvCBEAD #E<BFAD FܓBEAS#DEɁFKBEAiCE7BFAwDwF;BFCAC1.DmBECACԤYEy/BEA=D^FDBEALD9F$ BE7A˂CSJE0 BEKA9-D>vF+ZBECAFBEպAKD(F6ZBEDAD F(=BEA4 C^FV.BEAlDKFBEGARCFDBE}A2WC[F4BEA"CCE BE Amd,FR!BF"aAeFQ"BELA[CPF[#BEA C^EU-$BEϚAT5D'KFZ %BE AeCF[&BF AD3FT'BEA1C1RE (BEACCrE])BE2A@CvFH/BE&AC*E9&0BEAWC@EeE1BEAz%CEJ82BE;A*CʋF#3BEA(CȰE!4BEػAoCVEi7BF ACzID?BEAD[F N@BERA"D F. ABEADŤF+DBE1AcUDTEe EBEAD F.-IBEAE8F^JBEņAC~FE2KBF_A.#WGBFHAC BFIACC)BFHA'F;d|BFMAKD4v~E<BF?>AsC(+G$f/BFiADiIG3xBFAm~CUFBEAoCD\RBFABF.H.BFV/AEC#D aVHDBE}AC?F ,5HEBEWAWC&$EyHFBE.A?D %F4H^BE\AH9C|CC.H_BEAA`DuFAnBFOPAC/:DԳdBFA CHFleBE9A`CXF-v4fBEAj-*?"FBF@AdJ"ӁFi*BF4xAQCd#FpNBF?kAàVF]sBEBVACK_CYUBEA[CIDl{BF3AtCiGBFdA';CD=HBFGACbgBFA6DGD-"BFɝACQD׃BF3A[‚FBFAVCȕ\F BFoA^CE BFA\Bb*EWBFA$GD@iBFAmLCP1A` BGAR]CCFBFvAF'DdAUBFA{CpBEAjCHLCBFA%D,C >BFTA8iDI9D-BF2A4DAGDCCBFA~DFC0DtBF3AY CF鮒DuBFsACFօDBFA&iD CMDBF^GAVC8G DDBFACD`|DBFpA CeADgDBG}Ah CEDBFaAmB&F>DBFAsC9DBFA"ChCѼDBFūA6CbCDBG'ACb>C DBFNAkCDKBF^A8TC`&fA׈BF[A ;CP_&CBF[tA CJ7D)BF\VACPDmBF?AQvmFxBF2A)CF#6rBFnAvp^*SFBF9AC BF\A+|CUeB]BF[A"?CmDw?BF[A RC:ZeDkBFbA CZyE˚BFQcA CGE9# BFLALCgQDBFmAC@F BF]AD0oFfTBFg{A2D 2F2BFLAEB׼EBFRAC^vDBFe"AiC]TDBF[AC^ED}]BF[A]HCj^D²BFWAEBF\AICGUDBFiAC_XD!BFAzRC}q8F~BFoACvBF_A1CcqD<BFA"CsktDNxBFcRAC.1EmjBFIGAfCl;EBBF)AjCG"CͶ!BFlAi3CE >BFIAkC'F#BFR A(BEBFVAC^D؎*BFS9AsCWDFz BFNA/CjJGE.BFEAC:FmGEBFWA=C4aD/V!BFqA"D !BEsAgCPB! BEwAnCP#@CBFVYACE%.CBFMAJCVD1.CBFRA63C,DCBFTAsCEnCBFMACD8CBFPA"CCBFMGAmCm{DBCBFGXA#/C ECBFdFAuCNFCBFkADDFNKCBFMA6BF-(CBFnArCsbE4CBEARbF|8CBF8ACFB6CBF7A? EØCBF8A;CvEt[CBFE;A C;DxCBFL$A~CE}CBF<A3Cl+E=CBFRACMAS#CBFEACsWE8CBF`AC{*EPCBF1AgCpbF7EZCBFAChFD)SCBF[PACh[E)CBFvACB&#CBFi+AcP'F>CBFpAC"E+/H.BF3gAb%CKDWeHUBExAZUCCPjBF`ACe?DjBF\A(CS(BjBFRACMjBFoA"-{ZF/RjBF[cA%C\׶DjBF],ACnC]jBFACjBF[AXCcD}\QjBFY:A96CDZjBFZACCDɤjBFZADYCdz)DaxjBFZA7!ClDjBF`AC[\)jBF`8AݮCe D)jBFeA$CmKD9:jBFkAQ|Cg/Fy#jBFRACVvDjBFWACDͧjBFACojBF[AjC_DCjBFMAsCBM(EoZjBFMA$CñEAjBF@ACvzEV5jBFLA]CB}EC7jBFikAhCJFjBFM$A$ CGzEr,jBF]A+1CYnCjBFEGACj~EjBF>AcoCEh1jBFBA+C, Ed<jBFoACvDEojBFQhA¾D FjBFACojBF]A=CH)DjBF^A0C\nCjBFGA .CRӼDkjBFFAdCE{$jBF[ ACi!ErCjBF[ANTCWdC(jBFwACJDU/TjBFoA C{jBFXA C_D^kjBFn+ACEĨjBFEmACqTjBF[AvCWFD jBFjACFaEgjBFe"AƴCZENSkBFdA?CEPkBFzACb-kBF^}A8jCY45C+<kBFhnACcD/kBFhAC%EJo#BEACHBFYA2CBFW A C;E hBFWA C5ME"BF_xACGE'\_BGMADCUCWR`BGV_A=bCT:DgBG*AUVCwkBGLXANCTaqDr~BGeCAC\[BG^A3CUvDkrBG\A7C[qCBGaA/CQBGIAV-COBGf'A&CWC1BGZA9*CVkBG2 A[Ch'BG:A`Cd^CBjBGb A,CRDBnBGEA[rCTjCBBG@A`C`gC6BBGJAM#CN-BBGGASCRQDBBGNAECR}DBBGYA;zCT D BBGV)A@5CYHCBBGTAACM CwBBGZgA:1CdfLCێBBGXAjCUVC,BGYA<C]IDޭBGQACCVm6C|ްBG^!A5/CYRC IޱBGcA*CRCJDZ޳BG[A7CTMD޶BGYoA;CR D-BGJAM#CN-BGAA4 C~EH`BGAnCpBFAKHDbPAےBFACKD\BFACKEk%_/BFA,CDQ2 5BFhAC,DqL7BFAKUDbnw@ثABF}AbC2E#>BBFAnC^6ECBFAMCˆDLHBF(A;C[IDFPBFI^AUCZFUqQBFYrAlC7`FubIZBF`FACF;t[BFA<5‡eFXnBF:AC2EDpBFACDneqBFAC'E'rBFkAC0E3sBF@BAChFJtBFwA) Cp3EIuBF3ACdFlCBF4AA#C#DseETBFAI+DwE<EUBFUAMOC> ESEVBFoAYC EKdEWBFA&CXE^BFD8AC΄bEE_BF?AC;D)EgBFAakC.VE/ EhBFAIGC%BEiBFACĸE1EvBFI^AUCZFUqEwBFFAĒC{E_zzExBE~AOCڑEoEyBFA^CDvUE{BFAO C,E|BFACf'D#2EBFAbCC֧EBFAC=PjBFACA;al4BFAC2DEl5BFA2C}ZZE*ml6BFACDSQl7BF\ACk!FBtl8BF\ApC0&Eghl9BFAпCEk`lIBFA(sC]DBF^,ACOFTOBFqAQHC#[E<BFiAzCDBGA=eG@> BGADHtC[pBG AcD DELLBF€A/rC#xAaBFvA7aDՁBFWAP|CLEBF3ACwDgBF‚A/CS{A;` BFA?ChC.BFA$WD3BFnA,D5'yBGATCyEzBGvA/NC&nF|BGhACp/Eצ}BGA9C|E ~BGLACE7BGAC$tFhABH/VACミF-BH7ACwF< BHA[CÉF"rWBGAECENBH`ADDqE†BH3ADlFhBG~A-4 GWBGoyAdG5BG@.AC_F&BG%AD BHe?AxDEL_BHAoD!FBGACE5vBGAC{EJBGTA$NCtD\_BGe-AAC[#BGAt*CέE?ıBF^A?nC`wE%BFA$DsA,BH9AWCޒ;E3;BFAڎDi|F  BHd-AD D%٪BFNA CvhsCBF0ACpgEAEBF4A@CE3nEBGACCEEBG}AiCEBGPqAlC`F?TEBHSAʭC-E/EBFxAmC^XE EBG`A-CsE͖EBF%ADEEBHZdAoD9AEBG~A.CCEBH1AOCyDFEBGAAD8FEBF“A/{C)y@%ܥEBG4AuC]EEBGAVCdTFe;EBGA?6CFEBH^AcD/DRHBFA^ClpClBGAáCQD֋BGAC{FBG6A8CCBGACEBGA CUEBDADWF}>NBDAvC"NTBDTA?CR1C>_1UBDAC_EVBDAtC(Ev`BDA CB/DQaBDwAfC*eDӃnBD٭A`-C|E|oBDռALCD}BDAbC]tFCBD ACa !A@BDA;9C F ۃBDA)C-F)BDG}A]C;qFO BD}fA?aCg ER :BDA/CBD~ACE&BDAcCUZDWBDRAICPXBD`AC*B C{BD~$A5XC!EWWBDABCnE BDAbC$D^BD0A[SCD倂BDAgC̻BD Al5C'F3(/ XBD9VA]C=C YBDEAwC.C BDBAC5Fmx BDACE> BDEA Cd}E/v BDcA-,CJEI BDQAOCtENq BDFAOa>1F`W BDuAF”՘E BD#ABb}Fb9" BDA_ChWF< BDA)CIE6 BD%ATCfɰE}Y!BDA^C!BDA~6C6!BDGACKE!BDASCEģ"BDAECC$#+BDAvC|!#OBDACc>CsD#dBDnAs7C>C@#gBD3AmBR7E@#hBDA[+CIKD#iBDКArD*'F!9#jBDA)CGCűR#lBDA-CFDs#nBDA%CDEMZ9#pBE AAC>B/#~BDA :CwD+-#BDA CD#BDAVCr`E#BD֤AC EV#BDAUC#BDA CI#BDA%CFR#BDZAJCbp#BDACT?D#BDACi|)DCFrBDA@CQtFBDA9CE9OFBDACBHFBDACU#CSFBDpAACZFBD|A}C^hDFBDArCsDD FBDAy:Co;D3FBD"AhlCyNEFBDA|mCm D={FBDAlCcgERCFBDAպC}!FBDA@C_A3PFBD ACa !A@FBDAtC&B:mFBDA5CF^C}FBDlACG2DFBD؉Ad CDFBDtAC$FQopFBDAD (F@jxFBDAwD F!HFBDFA+CaBxGBDACfbGBD7ADCaC GBDAsCEmGBDACnFGBDA\C^EG,BDATCyC "@G.BDDA.D F#G/BD_AWBeEVG5BDACDG;BD,ACiE>VGBDADFF@GoBDA,D8&FIGpBDA,BKFGqBDĜACEF#CGuBDFANC:{DeGBDAZC=yEُBD{AۨEBDADCI;CВBD ACgBD,AAC hE' kBDAC\EzBDAK?C{GNCTBDsAWCg!BbBDACdĨE"BDAnHCEc,BD/ACEy4BDAAaC(C|BD;;AC>q~FIBDAKQCP`BDA CMCz 0BDnACG,Dv 8BD|AaCKFDy!BC%A\MC%$F;L!BBACyE1!BDA|-CMpE!BCjAC";D^!BCr9AC.E!BCAuCAYX!BCmAC6iiF'Q!BC8A0C@F'D!BCvAKC86F'K!BCDAC$E/[i!BC@AC #1D{!BCR AC#!BCr9AC.E!BC1A!CFQ6!BC0ACWCZe!BBACFa5h!BDLACG !BCAQC4.FSL!BBAC8EA!BB/AC-FKz]!BBACF!BCAe4C;WEN!BCTAC+Fx)O"BEuAC*,B?"BC3ACNlDsGBDzAi7CKȴGBBDlAwZCME4`GCBD)yAoCEGDBD}&AlCBYF\GBDzbADCWXD pGBDnA CSDUHBDoA#CFw E0HBC:YA-jC!SEHxHBCAaCNFeHBCACT_7EHBBAC*FHBCAACOUE&HBCA7CJ[E;HBC~ACI7F1CHBCAkmC-F"=HBCrACJ)E3HBCrACJ)E3HBD ACScCHBC-AkCFdCOJHBDARC:n.BD:ACm Dn0BDA\DP9Fn@BDAKCnFBDFANwC:BC'AFDCF]BBCA#C0F!nBC8xA0CFhBC+WAMC"mF /BCrAC6E]CBCAwC*[DHSBCAC"yBCAS CMkEBCA(CM+AEk4BCYALCHj:E/BERACEUDBDACvE rFBDACƇGBDACgEF/HBDƐA+CE ZIBDڍAtCY!Ee*-JBD]ACebD©KBD߆AjCn%ELBDACwDHeMBDCA1C] +D)sZNBDAA CRE ^OBDACC0GPBDACJfDQBDAʁC EG [BDaACbqDqY]BEAVCzD=cBDKArCoFCDtdBDACxoDAQkBDAKC{D7ulBD AC8D}[mBDA؊Ca.E rBD0AΡCE!EsBDAɯC_E:tBDAēCMsE DiyBDAz_Cr\ DKzBDތA{CJMC;>{BD ACWjD~BDNAaCaB,xBDACD}$BDA_CtEjBD3A$vC EnBDLAaC_4CTdBDA_Ce~C&BD5ACTz^BDAC~bE#LBD$AnCrwICdKBE eAfD"F BDAC{CBE A_C*CBEAe(&FR3BEABFT*BD@ACaXEBE/5AD9a1F֥BDmAC^D BDA̭CGDQ;BEAFCDEJ BDiA'CtB  BDA{C"+PFZ9 BDASkCDw BDA-CEQ BEACRF BDAnUD;F(F BDAC8F S !BDAaCLF% "BDA[C!wF ~ GBDGA#CFy SBDԤACґF2 VBEACCr WBEArhDF YBDzACF& fBEA/CF#Bٳ gBEAeDB% hBEAAcCQEl |BEADև BEmACq BEA\D"BEAC|`Ej"BE AqE`JF"BEAFA#E{Gw"BDAvC8AF>P2"BDAWCdEL"BDAC F)"BDӞAOD F8{#*BDňAColA C_^QB #aBDaACdBg#bBDAACsDΟ#cBDAC< Cuf#pBE AC A#qBDAaCEE C#sBDA8Cj_ET#tBDACgEI#uBDACdDв'#BD"ACoD9#BDAC_S E7a#BDMACI D #BDIA ChnEdFPBDAAC9CEFYBD߬ACs gDIFZBDnACP1[D̟F[BDACQyD"VFeBDAƧC@ WD+FfBD`ACi@Dm$FtBDAECXDFuBDACD{FvBDACDF~BDnATCoBFBDACv~D"FBDAs"BE\FBDAC!FqFBD)ACqE.FBDAC_EhqFBE CA[<¨7FGd$FBDAC^FBDAClCjFBDӺAOCz(E79FBDAʠCqzDFBDAC FBDACJCLFBDCAS7CqۂEdaFBDAxQCzhBn3FBDAǬCgDFBDbAClDFBDA*C|0FBDsAr@CsFBDGA[?CxDEAzFBD A CE FBDŲA̎CFBDA-C\]E ~FBDْA{C5tDFBDAC{GEFBEYAC3FόFBEACE;FBE,AZC<D<FBD:ANCQVC$xBFBD-AqCRCFBDAgClGDtFBDATC FBDHAVCbDLoFBDAFCu%zE1FBDA?CmREmFBDA;C&ECt;DrGBEACEV'GBE#6AuC,E)\xGBE ACE#:G,BEA oCEb@'|G-BDXAסC EG4BD ACMDկG5BDA 6CEe;G6BEnAmDPLEG7BDuAC7E>|G8BEAkD3'EHG;BDACȃC GGBDѼACžzD1GHBDĥAR-C~,EΉGIBDALUCEQGKBEfAy\D@مG_BEWAmCGBEMAwDAfHBD@ACD mrBDACD?msBDACfwD"]mtBDMApC1^Em~BE AЯCWEPmBDACi BEKmBEAwCEhmBD{A CIDmBD&ACjE%6|mBDA7CrE7mBD;ACn/ANmBDAC}|DmBD!AC4?DmBDջAoCT#mBE ACE6/mBDAxCN-mBD`ACWD!ҬmBDAnCnDy mBDAvPCTwCGmBDAKCV)mBDAC\~B3=PmBDAWC|UBD&mBDA&DHFmBDA(CcXEu7mBDAuuCf4ADtMmBDAqCslDiomBEnAClE3ܴmBDݫAѵCfDK7mBDDA#CY/CqmBDٞAԪC<#D÷mBDԒAԄCk˅mBEAC~ME ԾmBD}ACzFE9.mBDAC|KD<mBDaAC~D mBDqAWCvDsmBDAHCФmBDA(BF1E8SmBDACfDmBDACd7E!mBE fACE0CmBErA9CE`mBEfAdC)EgmBDACg@P mBDACkC.mBDLAŃCeCmBD%A:Ct:C@\mBDAɊCf\LDmBDBAdCnnBD?A$CdsE "nBDAC5E99n&BDݪACw49n'BDܮA2C(ADn(BEA Cn*BDA`_CF|n5BEfoAhCzn6BDACEFn7BDACk\F#l:n;BD*ARWC|1n=BD AQCĜE\n>BDSAABDA`CKlDm #?BDACUD1 #@BDLA>CHsD #OBDFACbAE #[BDˀACkC #\BD{AԹCLӸDN- #]BDA`C^ #hBDKAfC  #pBE:ACCo_CQ FUBDmA޸CW,Dw FVBDPA B\E, FcBDA0CUzD FhBDA C{jCW! FiBDŽA(4CQC.; FjBDA CHDEk FnBDWACR:C`N FpBDALCrݡD#< FwBDA7CD FyBDàACq[Bx  FzBDƄA$Cc=C-5 F{BDASCH F}BDACDa F~BDABU}E FBDFABkDy FBDAACg+ FBDӾACRD/t FBD@ARCfpDsT FBDҝAlCbLDC FBDkAԣCnD FBDȈAٹCa:Ce FBDLA~CY%[D FBDA4C_AzD2xr FBDACBE FBD=AC wDN.e FBDA+tCVV FBDAmCJCJ FBDACL FBD} FBDACxCD FBDA۾CHc?DC FBD4ApCGDn FBDAC6gD FBDKA׺Ci FBDlAmCaD , FBD^AC}LC FBDجAChC FBDA^C<;D( FBD%ACPD% FBDACk#kB" FBDBAzCs@D GBDdAC C($ GBD>AeCTLD)'R GBD A CsHD GBDĎACoD  GBDAmCAH" miBDVAPCw D  mjBDАA!CYE mkBDЮA#SC1W;Dr` mtBDGACh+C muBD%AhCiAeB mxBDAClC myBDA CY!1DM mzBDŔACqC m{BD ACwDX m|BDACwCy m}BDACjPaAL mBDzA;CiDu3 mBDdAkCiBBjk mBDA4Ca+C!\ mBDgA9CnBf mBDґAtCk:D mBD`A׻Cj\D mBD\AׄCD]< mBDSA&Cd~DbR mBDqA]CG̐D  mBDNCƯw nBD ACsCmd nBDACXZ nBDA.C[ nBDA8|CYDV nBDuA,CY8C nBDnACD nBDAzC:bCf n BDACFf n BDAlCjDDG n BDjAǓCEr n BDBA,$CXD% nBDA'CRD` nBDA'OC2EH/F nBDAAC0D nBD|AOCtCb nBDAҖC'RDi nBD9AJCxB\D & nBDiA_CwPB&KR nBDACkDG n BDAAC B@ n"BDA#5CVv:Ej> n#BD{ACHsD BDVAݖCLDc/ BDtA׈C}&tDrJ BDLA+CK1Di+a BDGA?CqNB~:a BDAfCD8 BDĹAYC2El BD۝A CRE` BD7ARCXMPD BDA5Cg@D0 BD AVCXD#\ BDAC|Dt BD /BDUACF%D2r 0BDYAC>MD 4BDίA1\C,s!E%# 5BDA, CdcDw :BDA>C0E{ ;BDAC7VDPj %G8BEA`iDPC %GJBE}AUCCo"%GKBEJA:CcEZsu%GLBEACLBI%G`BEFA@CBh4%GBE|AC5DG$%GBE9AC,E%GBEABiF4$4%GBEATGDuB%GBExAtCKE~%GBEATD-%nBE'A|CEY%nBEsAx0C1E&BEtA&C2G&#V&$BEhMAJuCNYoE&%BE^AQC!lF ~&&BEiCA\FCE&4BFSAC/ EW&OBFZA.C(D+1&VBEtA[COZD&WBEACL F Ο&rBE6ACoC:C3sM&BE}A.LCID&BFXAC&BFSAC=D2&BEAɡC|CD&BEAְC[?>CP&BFMAC@F 5&BFSsA(ACE& BEgALDhG7}& BE;APC*GFʯ& BE(ACl,!E& BD ACp%B#& BDpj&! BEåACqo@DC&!BFACF""&!BEĸAkCEfF76&!BEA>C`Ep&!BF*ACV>Dx&! BEIAbFBH F1&!!BE©A!DE:&!"BF AiCaFU&!*BE*AmC<ӷE,&!+BE AbCfESz&!,BEAtCd'D&!4BEA$C&!LBEA^F_&!MBEhAFA2Fj&!NBEABDSIGm_&!^BEdAuB9D&!bBEACɺ&!eBESdA8;C}FQ&CBF_ACr`CL&CBFBA& Ce(Dv&CBF>AdC&D?K&CBFWGAN C<&GBDA+CwDB &GBEFlArCfF&GBE*cAJXChWFJ o&GBEA:AKCEE)&GBE"A#9CE[&GBEACwEfjv&GBEA,CVDr&GBE AC*sE{c&GBE zAC~wF<&GBD8A(kCIFd&GBE]ABFgFAPHCHF#P&H?BEޓA.C2Eٸ&H@BEAC FFds&HABEACE*&HBBEAC<8EL&HKBEAC'WBE!AC!F05'lBEAWCHz$Dg' BEW ACE$' BEaIACY9'!4BEopACF)C'!5BEZBEDDA/CFDGQ'!@BEEAaCB_2E'!BBEj9Aw`nFz'!CBEE:A9CUEMj'!DBEfAeDF'!HBE;{AkC#hE;'!IBE8Am C%{Eq'!JBE`AMF;F'!LBEAxCEESM'!MBEbACBʥC'!OBEEA/CS#'!\BEPA_CM{\Eٴ'!]BE>cA>CHGE'!^BEddAqDCDh'!aBE{A C'C:BE5_A_qC8D5'HBEjACmQbEx@'H(BE[RA9CBNZDE'HEBEASNCѩBP'HJBE54A[C;o'HPBEjA1‰Fڶ 'HSBE>AtCC3yE\'HTBE7[AFdC~ES'HUBEVA˔CA{F%='H[BEc$A{CIC'HdBECACNC`i'HfBEOA{CnBE^'HgBEJAe\C_ɸE='HhBEGA)BE'HiBEkAC*Fa7'HjBEIDA/CREEՈ'HkBEdA\CΌFD'I9BE\ACE>'IbBELAwC`MDXY'eBEA9=CEL?Ch'rBEC}AC(Eoh'sBEBAICE͖'tBEEA)C-jhE['xBE>AfBaEP'zBEZ ACDv~FP%(BE>AėGX(LBF^A0CQBlf(&BE_QA_C@(:BFAAD^bB/(BF4ADDdCA(BFACiDT(BFAC%Dc5B6(BE3AOCC(BEACl( BEDAt[C+hF( BE'dAB|FTV( BEAC3rF|( BE:A,CE?( BEdAwCaE=4( BEh$Av3CfEwe( BEZACE( #BEC6A[CJEMG ( )BE[AEzCaEF( +BERACIE X( 3BE` AC8EHV=( 4BEVsAmCC5S( LBE|A>Cu_F( NBEiAPC( OBE_A5C EF( SBDwA,CHE6B( VBEWAҿC܎|Fy( WBEABAF+( [BEXAНDsSD/;( \BEZAC/E1( ]BGIEA@ CSC~@( fBEyAD&Fď( gBESAβDFΛ( hBEA BFo`+( {BEAZC( |BE}ACDfK/( BE;AjDB( BEA9C F'( BEVA\DBG( BE*ApC˲)Dh>( BEVA-C_E!( BEXADeDuپ( BFb}AhCEV(! BE̶AKC_T{(!BFXA@CCp(!"BF2A0CʞC(!3BFpAC¡FW(!aBF@ACD̔(!bBE+A$C("BEAnD B+("BEA9C>&E^("BDA?C*Bz("BEDmAN]DȬF(BBF(G BDiACw (G BDA CpB(GBEAC(GBE1-AbCD (G*BDAC6D[(G+BDACCH(G,BETAwCƼF 39(G6BE'A6CEC(G8BEAkD(G>BDA{CȫB(GFBDtA*CBi(GJBEaAxCҺF;o(GKBEACF\g(GLBEy\A+CF(GPBEOAC*E1 (GQBEzBA:CCFA(GRBER1ACMZ~E(GSBEYA8C[F(q(GTBE\A+CME-Z(GUBE[AI=CEd(GVBEbA0LCD?5(G\BE[AsDaDh(G]BEUE(GBEATD C)(GBDAC.C.(GBEV$A֣CAeu(HBFJA~EexG(HBF,ACخDE(HBFBACoF\1(HBFjAeDD#F(HBF:|ACکE)5(H BFYAD/DBGYZ(H!BEJAC$G<(H"BFiABrCE(H(BFSAE3D(%GR(H)BFHAiCݪPCJ(H>BFH~D)GBDūAqCgB)muBDAѦCDZn)mwBD+AgCfPDIx)mBD:ACL)mBD AcC)mBDUACK{D,)mBDnAC@Dw)nBDACf8B1)"BD3A#CHB4)\BDTA^CL0A)]BDACW/D)^BDAC_De)yBDqA;C0D3;)BDAZCgB; )1BDACIDl])3BDAdCX2lD~)ˡBDrA C )BDƖAʋC^C)BDAlCLTJC&&)BDAC")BDA:CZnFD miBHWAfHC{ DmBHY Af?CADmBHAfCDmCBFSAxCRBۑmCBFSACLGCimFYBDA*C|0mRqBHY Af?zC/myBHVAfICmkBHYmAfBCD m{BHAfCdmBGA&3CmBGvACnFA[imBGA?CSmBGAqCFmFBDACBs:mQBFSFACY2Bm^BDACsDCymBFfPAC[mBEiACD(qmBGWA_CL{mBDAC{D9mBHLcACC_m BFA}QCwBHWUAfMrCy9 BFlwAǥCBDAgCu!D#XBBAC 0 BD­AC[d?"c] %BDAl1CkC؝ kBH^AfsCcC > BFA]IC\ '&BEOAcC ;BH]PAe6C? 0BD´AC[  BHM!Aqu|C( 9kBH^AfsCcC > 9pBH^AfspCbaBX* 9rBH^tAfsVC7A 9BH^AfrC_  BD­AC[d?"c] hBD´AC[ RBH VAtbC;h :BFAA0C/*B?RXl :BFATA3Cm BDA0CdޮCr{h8BGtAC 8BB"8BGtAC 8BB"@OBEAzCW3uDOBEAzCW3u#BFEsAjCrRBDA#YCP?BFBACf%A9#5BFACt#5BFEAlCC'xBFɒA2 C@OyBH Af7C BDA_"Cu6C{BH Af7CBF=ACE BGY!A;6CV1&BE IAثCܒEĈ[BDۃA͋Cx,zC[ BDۥAͥCgB*\BDA CdNBDAAACXCC"BGY!A;6CV#yBHqAeCtD(BGfA&CVvC')BDhACD*BDAAACXCC-6BDhACD/NBHTAiCd]/BFQA C 1dNBHTAiCd]1BBAC4tE!2BFCAC3 BF>AjCvD@8BBfAߦCLDD$59 BFk9ACyDH9 BFkAFC:BH]PAe6C?; BERACXC)7<BGfA&CVvC'<BGfA$^CY BABDA_"Cu6C{CdBDArCEBFCACFBFCACF BDACI BDArCKBEA Cl*DLBGY!A;6CVapPBH(ACe#4DXaeBDAƶCeBDzACB |.&BEA@C^&BE AՉCEV &BEOAcC(BEN_ACD(BELZA}CIC:f(BEhAvCIXBISsAtRCaV HBHAo CACqBJAwBCrnA%oBGAZCDBFDxACHBG^A-CCF ;BF2?An8CF/ BHAo CACqBFKA_PCEBFJBArCD<BHAoWBC><BH[AfC^:C:BHYAfCzCQ4BH[GAfC?DC('CBHeAgCOBZg$'DBHWAg CVwC-'MBHWdAfCfcPD.ʌ'NBHU)Af|CU'C (QBH` Af2C_EC~Q({BH`?AfC`C@(BHVPAfCYABF(BHU)Af|CU'C (BHUAfCUpC(BHXAfkCpD'(BHWAfUCf=D5^(BHYAfCzCQ4)LBHXAgMCT/B )BH^kAf|CKC)BH^AfCY+BHUAfCUpC,BH_SAf1CX4DC@-3BHbAf1C7Dۨ-4BHbAf9C:R--5BHaAftC9Dz2y-GBHewAgCP @O-HBHVAgxCTJDn-IBHXAgMCT/B -yBH[1AfC?֗CDh|-zBHXAfCyB:-{BH[1AfC?֗CDh|-BHeAg CO'+-BH]{AfCC;_By".BH` Af$CXCO3BHadAf2C7 B4BHZAfCvT4BHYAfCxDC/14BHaAfњC9_CD 7oBHZAf_CAA7BH`AfC8,'C{7BHa>AfNjC8Dc87BHa Af?C8yD֯89BH``Af-C7֏C`8CBH^ AffC;_}CF8BH^AfѬCDsgq8BH[-Af&CAB9BH_#AfC8D9BH^AfHC5/D9BH_AfCI|Cׇ9'BHehAf"CS@C?M9(BHXAgMCT/B 9)BHdAgCBHTAfCROB<9BH[Af7C@sB9BH_AfzC71Cf/9BHbAfC;TC79BHcYAfRCE1y BEU'Ab CڋD >BE_AbdCgrE< ABE8A^7CzEp% A BE pA^sCD k AoBEEAbCD@< ApBE2@AbB)C(D* AqBEK,AbbC ArBEIAbC0 D BABEA^ĦCz BCBEA^CyDvf BLBE A^f%C9 BBEBAbCCDߨ CBE>"AboCaD CBECAbC˼8D5 CBE2AbtCCdD) CBE/Abi(CDsy CBE3:Abk?CDC/ D+BEJA^CME> D-BEkA^CD) nHBEFNAbCāD[- xBE/Ab*CC xBE/AbqmCnDH }oBEf&AbC͉X }pBEA_+CczCp ք BEA^CcD  ք BE A^eC{AK ֆBE6Ab[CqD=_ ֆBE0SAbqC}D ֆBE3AbNCD`2 H#gBH|gAjC= H+BH;AeCU HBBHAjCs7 HBBH zAeiC1 HP?BH+Au=CKHs HPBHtAmNC< HQWBIMAs_CU  HRBH@Ap?CDEgq HSIBHXApwC1Dj HSJBH}AqڠCC%F(x HU3BHAqdiC: HUQBHAsڄD5-FkS HUBHAp:CH% H[BH1AuC#IE H\BHAnqCBEXAbPCj nHBEXAbPCj BH_AdܶCC4)BHbAeCC)BHc$AeCD~)BH^Ae CfD$+JBHm AhCc,/BH^MAdCR-Dp/BH_AdCxD3/BH__AdyCu4D0,BH]&AdQCDs05BH]6AeCD~07BH\AdCD*0KBHaAe^C!D2J0UBHcMAeRC9C#01XBHbAeCC1YBHb AeCL1aBHarAe{CD721bBHarAe{C#DFa1cBH`AeD4C(EaB1kBHaAe{C\E1lBH`AeeC,D˟1mBH_jAe7CVEKo1uBH^AefC,D1vBH]AeZC$gE1wBH^_Ad)CE1BH\AdC*Aͩ1BH^LAeC qE.p1BH]Ad0C̋D1BH^HAdCJD?23BH`AeOCDx24BH^Ae'2CDGP25BH]Ae%CkEO|2BH]lAdֆC BC2BH]AdٗCC2BH^AdևCkCrr4OBH^'AdC.sDQ5BH]Ae%CL2E`5BH\lAdCC\:,BH^wAdsCE<:-BH_AdC#eCѫ{;'BH`SAdCA;XBH_jAe%QCDhNBI-Au|:C{NBIpAvCE>NBIAvstC4F|NBIAvUCD+5NBI.AutCbEQNBIAvCӋE9{O%BJ!Ay$DlO&BJ,AycCw%EY O0BJ8AyMCdCO1BJ2~AyN|CEIOBIAvC=;DOBIjAxeCEL;}OBIfAxXXC ES?OBIAv]CxEYOBIsAx#CD7OBIsAxC.Eg9OBIMAxfCE?BPBIдAxsC EPBI AxgCcLEfPBIAxC2NFPBIAx\CD=EeRPBIAyCۻE.IP3BI ;AwCeqEP4BHAwC}F2|P5BI%AwCZF#P6BHAw-C]ESP7BH AwCTF NP8BI Aw`CbF4PGBIIAxCFPHBIBArDnXGl_PIBIyAw C EPBH AqC-mFL/PBJ AxкCIDNPBIAxCE_QBI?AuVCQ#BIj_Ax)CFȅQ$BI?AxC5CwQ7BI\AtxCnnEFQ8BIMAtaC@{FQ9BI*AsoCX"FAQABI;mAx,"CxJQGlQBBI@At{dCbFhD}QCBIyAtZC2F%fQUBHAw0CHalDpˮQVBHAvo1CpJEQWBIAuD7FQtBI+AwYCl WFZQuBInYAxrjCZVD[QBJ?Ay>>CE!EtQBJ8AAyECn CPQBIAwCjdD?QBIAvTCDcQBIAw|C|EǩQBIԞAwCoE7D QBIAvC+7FQ5oQBIIAtYFCIEیRBIŪAy*CoDgRBI]AuCE(RBI]AuCE(RBI8AuC{ DبRBIAuCEs,RBJAxIC{EARBIAAx ~D LE+dRBIHAx1C^ERBJAx9xCERBIcAwDD'F *RBIAx C EUSIBI4AxfCE-SJBIk^AxC*FjSKBI|AxgCIE3S^BJ7>AyC3BenSBIAAv|C9JFYSBI`AtCF*x SBI9AuwCFCuSBJ9'AyiC~/DܻSBJGAxnDHDASBJAAyEC5EqSBJ,Ay'D! pEzSBJ@Ay?HC|E|SBIAvUC1EbSBIAwBC$TBJ6AyCBBBTDBJ5PAy'DQEaTFBJ@Ay>C}E}}TOBJ8AyCV_IBI9AtjCXD6bBJI{AxC*CFBINeAx`CuFPBHAw7CbOEz3BIKeAxC^EckBI0AwCjDǕBIAAxClnEGABJ('AxC\&F,rBJAxD}EE3BJ:AxCwF'qBHACdP:C;BIAvAC-BJAwƼC?A/BJ@AyDhCEozBJKhAwCrBJ>SAy4nDAEBJAw5CTBJCAyD+CwEvoBJ)AxDLn^FfBJAx~GCyDgSBGA'CrOMBGmACμCTA%ONBGACF!OBH}AAvCE)OBH|A=CIE1gP]BHxA:CxEgPyBHn+ACTEYjPzBH[A C{CPBH;A}EÇF3PBHYA6C.IE!PBH]+A"CE@SPBHAqCYF QBHM|A5'CFC1QBH7AF=QBH\=AfC}{CN JRBHycA bCaDWBHs4ACJC|BHgKACZQBF:lBH A$CCziFBHMNAp/C/E1ecBGlAfC̩D{FBGAoC BG>AHCܞDBGMA-Cu]BGߜAC DBH70A FsBH[A>C|%CyBH\AChBG…ACC=:OMBGRAYCwPBHdA^C{BcBGA+TC~PC.BHcAC{BBBHEMABCy;3BGdA_CXDX0cBGA @F=SZeBG[AWFD P FrFBGA;CF ZBG֬Ak&CEBHkUAӞCC05BGAMCoD~7BG"AA0F-<BGҎAmCA%&Fu{BFpAhC}E#}qu|BFjACf$BVu}BFfgA3CCEpɣuBElACDh4suBF?tAC_DouBF>/AC](CuBF;DA3.Ba!qEKuBFI:ACpDc7uBFAJgCqGE`uBFJAdCvF uBFFdA{D:kaFWuBFGqAUCbFFyuBFJAUCAF,uBFMeA CE uBFtACkvErQuBFG`A=CpvDVuBF@Az Cd'EKuBF=NAo7C_(GE_uBFDsA]CzgEݥ.uBFA~ACaEiuBF=A]CeۼF~vBFgfACEvBF[;AC8EEvBFQAKCvQEPvBFACP"DhvBF5^A"qCjFdvBFFJARqC@OE0wBF^A'C#%E,;yBF3ACQ8EXyBFOUAjCVD`WyBFBA.QCNFlzBFBACyDd{BFMACzw{BEA BCo7LEɬ3| BF)A_OCu{*E_i|BE`ACqҺE|:}BEAbACt'F*DҐbBEщACZCҞ@BFFFAYfCvF[ҞABFUA0C4E/EҥBF0ACzҥBF5?Ak3C=E#vҧcBEyACkQ E8ҧeBEACYDDҧsBEi_A)C=DҧtBEiACjDR`ҧBE՚ACRvD6HҧBEfACYqCҧBE@AnC E 'ҧBEA"CfҧBEA 0C|9DJSBFJAC EWq,BF6A"Cm@EMq-BFAC_REqfs=BFACE#eseBFAwCR,HEsfBFkA%Co7nEsgBFACfKFa@u}BFxQAyCp\v BFAPC[EvBF^AC\oF vBFACtWFCuvBFAVTCeϠE2߳vBFAC]SF GvBFA'C_qF<vBF%AyC]RElGvBFA\CE $vBFxAhCDGv"BFAVC;E;kvBFv4ACwMBF;AwfCDgx BF!ACPEx BFWA1Cq:x+BFLACEskx[BFAfCC+x]BFAz"C?D-xBF#AvCDQ*y7BFHASC+LDByBFtAYGCa4FzBFAZC`C{BFACs0F&vo܀BFAmD}E܀BFAC*-E܀BFEADEE܀!BFA~C[$E:܀"BFfA8DPFf܀#BF A~CyE;r܇BFԥACCC܈tBFA%C9y܈BFA%CiCEj ܈BFAoC_aF.T܏"BFnACi7 ܏#BF%ACCV^D$oܐBFԏA&CrjEpܡBFACcC0NBHAw)CED P4BH8AwQ!CZNVQBHG'A(C4EQBH3wAsBwV-EUQBHFWACDEQBH>A"CE|QBH/YA*0CHF=QBH=ACC°D i#BHLACq'@3BG_AbC(gFdFBHDAv CfNBHAw C^pENBH0AwC\ E!NBHAw`BĻ"E)NBHAwCeENBHAw~C=D,ONBGԡA6C!D6P4BHAw9CcZD%P7BHAwOCmczBrQUBHAvCRQBH>lA+C^1EQBH/ ACcKB:WQBHYA`[CkEaQBH+FACfE TBHAv~CUwE-JTBHpAudCQdPF:[BHDAv Cf^lBHAtCF2D[BHKAC\1EtBH/AClE0'BHACD`3BHA{CEwBHFLA_COK]EBH FBJ7Ax#DU*FI7BJB?AxD9߉F1(BJ$AwvRC~LEGBJ&Aw&C@FSBJ%AxoC0F$BJAwsCDB[BJpAwCsEBJ_AxxD E4zqIBFACEb/zrBFA CDztBFǕA$CtdzBFwAzCFB zBF#A,C1EdzVBFAC'+z6BFA.C$DYzUBFA?CPCnzBFeAC-zBFybAbCzGBFv8AJCC2zBF[AC܋zJBFzACDczBF<AޕCy CMVzBFFAoCAECz(BFuACmBFA@CVBFnAC'uEocBFEAvDCOE^eBFA.CC#\gBFoA}CbEdhkBFRAZCUE6BFAC˹D^IBF#AC)1CSBFA:C|D"cBFAy^C]DBFqAqC>E}BF'ACbNE5kBFACE>\BFAzC EqBF)AzCEq߂BFAt~CCDdۜBFALCE~BFA*C? E^O"BFA`C?ώ'BFA[Cޔ@ sBF@JA,'C8RsBFgA[C\BFAC0!BF\ AҘCK>FAqIBF6AmCfF qJBFAWC{8EQMqKBFA'KClEvnqgBF.9AJLB;~FBFJA${FɁeBFrANCELfBFjgAC3DgBF` A~ChEjBFooABWC,EkBF]sACE_kBFACCBFAa@CX&FoQPBEA'CBEAvC8~D0 kBEAC{EX'BEAChFE ^+BFfA~C%cE,BFp%ACJFi-BFRAxCLE™?BFKArC eF, @BF7A-QFfUABFNNAߗF~IBFArM FzJBFASCbF ~KBFA%CFSLBFiATFO9MBFYA0CɱENBFfACF%SBF=ACEȾTBFAC EUBF>A[Cd^D- ]BFYA?CrB^BF#AB&E*`BF6AgC.DaBFmA-CKE}3bBFgATCgBFrAR C{EpBFNAC5DuBFWACqE(BF+CACSDBFLAC9EBF/A|SCH E\BF/yA[CDZBF-AC7E5BF(?AMCVEMBETAI=C_DE#BE0A1CREX %BErAHCEuBEAECDBEA(`CE;lVBErALCwE}BEߑAWCMEBEލAsVCp,E'BEA C EsBF5A4D%eyFABF\qA=CF%|vBFACF(tBF}A^C;F<OBF*vAC E6VuBFGAfC2Fn vBFAkC`wBFAmC4/EBEKAqC"2Ez%BEݴA%CdEBEACE+GBFrAlCeEF*HBFnyACCPIBF~vA/F%fJBFpAZB׷F wLBF}A9rB?|E`fBEACDgBFAi)CxFhBEAhCNE0ՆiBEACEAYjBFA;xCitD&jBF=AݐCABvBEACqoC*#BEACuEBFA C(QDRBFEAC*DjBFAuC|D{ BF_ACF1BF1A|HCEnwFBFACXEBFABLEBEA%C~E BFuAǘDF1BFBHAr)CJ#DoZFBHAmqC}P>BHAtC#dFs P?BHAsD2FokPBI2 As5BGV|PBH3Al0CQNJE0PBIArCFPBHAqCAmEdXPBHŏAp2CC-DRPBHApCmh=DQ9BI &AsCM_E3QCBINAtE7C`DQWBIAsyC 'FEQ_BHAmCabEGQ`BHAm CCLE)S]BHArC?FS^BHeAqjCE6IS_BHAq|C̣E-TvBHAnuF.nTwBHAmZCRHDTBHAlB7CGۄErTBH5AlJ>C*BF#TBHAkCK[E۲UBHAnoC?E?U BHoAn{)EjU!BHAmA hF"2\UQBHAuCSV_BHXAk"C^}VtBH[AkJCV3EH0ZBHAmC?DZBHAmCFLFZBHÖAmfC(JEU[~BHAp#wCiEJ\BH/Ao۬C?E:y\BH>AnE\BHAoC=D_]BH_ArYCAF]7]BHFAq{C< E"]BHAp4BFK|^BHttAsQrCCM^BH'Am0uCQE%,T^kBHArrE^lBHAsB#FMp^mBHyAr@C hE^BHAnCN=^BHDApC= F_oBHArPCE8F`_pBHAr\DِFb$_qBHApEQDFw+E`BHmAqC9=Ea`BH.Ap\CAuD`BHxAoC>yAEzaeBHAo=CњEj7BHAkCuq{BHArCHF# q|BHArCDsq}BHCAqԿC>?EbqBH4AmC8 E_[LqBH'AmUC7EcCqBHxAlCF.qBHtAqC=F- qBHIAn`nCJEQ1MqBHWAmCNFBqBH#AoCΐEx~qBHAo_CiEIպrBHAsOPCG?ErBHAsVD F>˒r BHnArC1?E\rBHApnC?V3CۻrBHAl1C@ErBHAl uC[oE4rBHAlpBrmEwnr/BHƵApaCCDKr1BHApB?C@4E1O>rBHAlC聽FgrBH~Am_CFbrBHDAlCOEsGBHobAs*C8zEtRsHBHkAsFVC#E\rsQBH7AsfChẼsRBHAw CcsSBHAsCYs[BHAoC^ Eّs\BH#AoCΐEx~s]BHAoWC eCEc֤seBHAr_C?E'sfBHAttC4F}sgBHArl Es{BHAm9CA4D85RsBHȋAs`C=HYDTtBHAqSCOojDtBHAq/C=c=D%tBHAqC;DJtBHApCDeDtBHOAq+C<`Dl\tBHApߒC7zEtBH!AkCXE$2tBH/AstC?ZmBHAtCIhs BHe"AfC5 (oBHf#AfT~CwID (pBHaAf^Cs Dq  (qBHbAfQC#D5; (yBHfmAf| CLމC (zBHcAfvC\D]ـ ({BHb4Af}tCdC (BHgAfRCeD^q (BHfAf[CsDtvp (BHg/Af2CEƇEm (BHgAfC[-D1H (BHg\AfCxD\#* (BHhAfC7 "D{ )uBHo%Ag%wC/SD;> )BHdfAeςC$D: )BHdAe,C3 )BHmAg7#C>>;DK )BHkAgJCDy )BHb{Ae`C0(Eb )BH`Aeb C]E )BHcAeWCDS +BHg6Af^nC\GC* +BHhKAf"CtD +BHeAfFCbD  +BHeAf.CC JDp ,BHcAf,Cc3Dٱ ,BHeAfC7EI -4BHdAgC9DT -GBHeAg CO'+ -BHerAgCO0b -BHe Ag7CCiC -BHiOAffCO̿D/A -BHeAfC^{Bh -BHfnAfChDG .BHeAfCQB7 /BHg9AgfCMC/ 0BH`AfAC B 0BH`AfCkC@a> 0KBHdAeCԩB 0SBHdAeyCRDr 0UBHbAeC 1BHgcAfJCo D+ 1BHgLAfUCkfC۵ 1BHcAenCuOFn" 1BHkAfC+!D@ 1BHd Ag-1 1vBH]AdCkC 1BHkAfAC` 1BHjgAfiUCDė 1BHkAfHgChC8 23BH`AejCq 25BH[AdC@ 2=BHoQAgCND@ 2?BHfFAfCTxD($ 2eBHfAfCTzICe 2fBHfAfTCV>D! c 2BHcAfCbED` 6BHdAgCUC~G 6BHfAfCLPC; 6BHfAfGCMlC] 6%BHhAfRC]V 6&BHgIAfClCn 6'BHdAfCD] 6/BHkAfBC[YC 60BHg"AfBCpD8 61BHfAf*CcfDh[ 6BHfKAfCOQhD!s> 6BHeAf?C9aD2 6BHeAfC8Cʡ 9BHbAfCjOCy] 9BHaAfnCYD[ċ 9'BHeAfiCQ Bװ :,BH_#AdCsb :]BHh[Af|CUUDQ :^BHgqAfHCkLD{ :hBHjAfEC[QD ٹ :iBHlAf?CTAy# :{BHl>AgJCpDī :|BHfDAgN~CZZE( :}BHkAgLmCBC< :BHd-AeYCP~D; :BHeAeC_D :BHeAeRChC< ;BHeAfC^{Bh ;BHfAf CeCC8 <BH[AdACL zC^ Bރ JCBHkAfCD5 JDBHfrD KgBHo(Af(C5B-} KBHiAfCBDm LBHa!AfQ`Cx DӘ LBH`AfICvVdC#  L-BHgUAfChВDhj L.BHeAf1C-E/T LBH\AdC@ LBHhAfFCd}D.UBH]AhFCw_rEtμBHxYAjwCWE/*BHjAhnChD%@,:BHlAhC_SD_J,BHn AhCD,BHgAhC^Dj-oBHpAi&$CWME;u-pBHqyAi2CDwsQ-qBHq Ai96CTE/BHqAhCjϞ/BHmAhACEv/BHj6AhCm]E/BHmcAhsC;E9̦0BHqNAi^xCDQ1BHdAh~ Ce1BHdAh~ Ce1BHcAh}>ChC5v1BHpGAhBCihEC/1BHn}AhCDۺ1BHmAhCDc1BHpAi$CE7a1BHpwAi CaD1BHqAiVCaE 1BHsAi_FCWbEu1BHqNAi^xCDQ1BHr3AiuC2BHvpAi=CEd2!BHrAi^1C E2)BH{\Ai;CE32*BHw4AiCPpDDd2+BHu5AiC0D<\2QBHjAh C`DE02RBHjQAhNCRTKE 2SBHnAh_C;mE H2BHtSAiCC+yD,93BHmAhC;4E3BHnAhCZnD4AiξCͽD$OXBHAkCYFPBHEAk7C_F$PBHBAk4Ca;E-PBHAkPB\ES5BHAjC~ES7BH{VAjZCk۹E#`TBHêAlGC?%`V_BHAj@C/E1ʸV`BHVAjC1EIVaBH~tAjU4C2EQ[BHsvAiC%C\BH~AjMCE]sBHrAjzC^BHAAnHC5taBHJAjzC+j7BHEAlCRF*j9BHAk1iC8E.rBHAk;ChDQs!BHsAiyC^)sBHfAg(CD)tBHfaAgCyE )uBHhAg%BѥXD?)BHs`Ah CCVkD,)BHqAgmCSCC,9BHoMAgC(E ,:BHhAh2C[yD 1H,;BHnfAgCHyD{n,BHf;AgZCD3u,BHhAg|]CAAU-)BHrAhgHCqC-*BHsAh+xC`Ѫ/BH`AgCXo/YBHmAhdCu./xBHi;Ag|CG=/yBHiAg|CF7B0]/BHjAgCU8ExQ/BHgAg#C}D/BHiqAgCZDW/BHs-Ah$rC|D1q/BHs]AhCWDp$/BHrZAgC[D j/BHrbAhACmEP/BHsJAhCXJDT/BHpAgpCWNDƦ/BHhAghCXD\/BHlAgCJLD~/BHqAhjC"E =/BHrAh[Cp^5/BHjCQSCKBHt%AhL&Cg9XKBHiAgdzC0D_hKBHiAgtC<CWLBHiAghCVDfl|LBHgtAgZC\{DӴLBHq%AhgC[4AfC_J=L3UBH(AfpCN)L4oBH kAe?CBL9BHmGAh#CS3L:@BH^oAfCS9,BIL>BH nAe/CLBBH AeZCkBkLOBHAeCLUBH sAeCaCHMBHIAfpCRVAMMBHWAfCwXcC< gMBHIAfpCPMBHIAfpCPAN6M'DBHXAfSCD.XM'EBHYAfC`CM'MBHXAfwCyM'BH:AfC}IyM'BH:8AfC}CtyM'BH:AfCtFB1SM'BH:9Af8C}CgM'BH:7AfC}B0M([BH^jAfBmUDM(BHWAg_C}`C!M(BHWAg^C}@CM(BHWAgCD3?BM(BHZAf CxqB M*oBHZAfC]CMMM+BHWAfCu+CM+BHWAg$CvD[M+BHX AfC}9CkM+BHWAg C~͑M,BH:0AfCVBWM,BH:8AfC}CtyM,BHIqAfsCT4C+ZM-HBHWAfC|B9.M-yBHZAf CA!M-{BHZAfߪC<M0BHBAfC`CM0BHDAfC`M0BH?GAfC`M1BH_AfaC_t9M2yBHIAfthC[_CalM2zBHFAf} C` CM2{BHIAftCUnCpM2|BHHAfwC_fBjDM4BHHAfwC_fBjDM4BHHAfwC_fBjDM6BHH)AfwCUCM6BHA!AfCOsDӚM6bBHADAfC`M6cBHFFAf~1C`NM8BH[AfkCAsM9'BHdAg kC9ϺC1M9)BHcpAfC5M:@BH:IAfCr/pBMKBH^AfCQKMKBH^AfCTP!MKBH:AfC}IyN(5BHV*Ag4CN(BHVAgCCm+N(BHVhAg/C@CB,N(BHVAgC ;D'IN)KBHdAgC6s3N*BHVAg!CCHN+BHVAgC`N+BHVAgCիB0N+BHVAg*1CN,%BHVhAg/C@CB,N,'BHVeAg1fCCHN,BHgAgz C?C}N-BHPAg8CAN4(BHVeAg1fCCHN4)BHV*Ag4CN5 BHWAgC]BN5BHVAgVCRC N91BHVAg,CB=qN93BHW2Ag&CO(BH?AfjC_CO(BH;%AfCBDKO(BH;VAfCTODV51O,BHIAfpCUO0BH?AfCR UC.O0BH>.AfXCNeCuO2BHADAfC`O2BH?,AfXCNxC烟'BH4AfCQ -DS|'BH(AfpCN)'BH%AfAC{PFD֘+ BH-AedCCr+BH:AeDZCϦwE>h+gBH#nAfCFCZo+BH!Ae !C}E?+BH&Ae +C D_y+BH'AeC^D^.BH+AfyCN.BH+AfyCN1mBH]AdCRA 2BH^'AdPCW@P 3BH#Af CC%о3BHAeZZC)E[@4 BHQAeC4OBH_Ad$C4oBH AeLCJ1C 6BH8AfCT7BH+AfyCN7BH(AfO;C E /7BH4AfCLsu7BH#oAfCAAEt8BH<AfrC@DY8BH6AfCGC*8BH35Af CV oEt:ABH8JAfCEnDH:IBH%nAf0kC:BH#Af CB%:BHAeC/:BH#7AfCb;BH$MAf2&C+DV;BH$vAfC(D2<3BH.)Af;COm<4BH.)Af;COm<5BH+AfyCNBHAe9CtE.>BH(AeyCm BJH>BH .AeCXC E5y>BH)AeC2;Cx>BH@AeAC֚EL>BHAe]CsE>BHAdC9DWC>BH#zAfCHPCr>BH#nAfCFCZo>BH#zAfCHPCr>BHAeCwD>V>BHAeC "Dg3>BH2AeCBH%kAe C.Dr>BH*>Ae C= Bf}?BHdAe8C„E> ?5BH$OAf2:C}tDU?6BH$MAf_C6DB5tBBH AeCB|UBHAeLC2 tBH AeC5D ٌT'BE6lA:CT'BE!A%CEQ[T(BDAARCoEϸT(BDAcCDE:T(BEbACaD"T(BESAlCE}.qT)#BEACi1ET)$BEAŶCMET*eBEA+CzŢT*BDAUCEDT*BD|AGC{ͭE51T*BDoAgCz9UF T*BDҤA6CET*BDATxC ?FNT,9BE=yABZC`T,;BDA0CRE(T-BDAXCF T-BDƒA CSC-;T-BDA OCClT-BE15A4CDT-BEAICUE%lT-BE ACEtrT.BEVA C=BE#YT.BE]AXC>"E4T.BEDAVC̖EhDT1NBDFAkC@DT1BE-A0C DT1BE"A*CqDCT4BEMAcCE|GT4BE6AGzCtZE|T4BE+AoC@FTrT5BE'A/C\WE#T5BE bAׯCFT5BE>AXCyTF\T~BE:ALCrEkT~BE#xA,CE\TBF@AACz\CToBE=ABfC#DN~TMBDzA}UCF1sxTPBD`ACE!TQBDϔA=DC5?T)BE AC^E5~TBDׂARCzYD6܇r*BD˼A+C~Vr1NBDVA xCCpr2BDƔAC}Dڨr2BDA@CqE{BF2ANC#5B@A1C#_Ba8B@AC0s=B@{ABY?B@{ABYGBBZACB\BBV,ACļ3 B@AECOCM3 B@ACaD3 B@RA?CJDHc3B@AC#|DH33B@\A?CD$)63B@dAڝC3DK3!B@RAVC6`~CA33B@AcC(?nC.34B@AC#BCts35B@AC!Dw3=B@8A:C{3>B@ACA|DX3?B@AC0D#3GB@|ACC>3HB@ABDֶ3IB@IAkCc1CΨ3QB@A֘C D{3RB@cAC D)3SB@>AC&BB3\B@HA`CA1 3B@AC ^pDj3B@\AoC zDW3B@ACCw3B@dACŲC^3B@uAiC*B3B@ACu3B@AC0MD3B@A AC'D+3B@BA C D(3B@A8CV3B@AtC-DRB3-B@A>Ct7C$Q3 WB@QACCJK3 XB@AC=CDF3 YB@ AfC DDi,3 bB@AHC D).3 cB@A C7C7x3 B@AC@E,3 B@'A _C CX3 B@CAgC+BD3 B@AACy3 ~B@AC9C<~3 !B@ACDr3 B@MAC+C3B@AC@DPS3B@AVC7XD[3B@AՇC$uB+3LB@$AC=/3uB@^ACF%3vB@AēBEw=3wB@EA+C'-Dڨ 3B@AC;\A3B@zAC C '3B@*ACD3XB@ASBDj3bB@qAC"@D3lB@zAC 3vB@XA CB3B@aA߆CCO!3B@A2C}C3B@ALCD}=3B@ABESׄ3B@ACD3%B@AC z^3'B@A=C(Bo3'bB@sACCz#3'B@A MCD63(B@A C53+B@~A TCDH 3+B@fA%BD3+B@ A C'oD3,B@AC^3/B@AACy30B@DACJEDB30B@A =C4_B@AQC -P4EB@pATBE@:r4BA&A ;BEd4B@A#!C&Fan4BAAkbÃgF'4"B@ٱARB>w4B@ADT\/F3 4B@YACEMFH4B@A.%F<>4B@rA8 BN41B@BA1u>E42B@A3pBu4iB@JABDy:A4B@AABw`DV!4 9B@]AEC&ZE?4 :B@.ASCxE04 ;B@AEBgD4 BA5AA)CE,4 B@vACuF 4 B@hAC4 B@JA~C sFy4 B@ȇAPCFE0]4 B@ۼAn|C4 B@A~%CE9{4 B@ۃACQ.Eѓ4 B@Am7C4 B@ϧAwB D4 yB@2ABGEMv4 BA#A߫C@Ej$4 B@7A=C F_04 B@ϭA_BAҭ4 B@YAz ¹҉F:.4 B@9A>C:yET4 qB@JAŵBh EO4 rB@A^F 4 sB@YADBsEzx4 BAA`BF Df4 B@ϠAnBF4B@lAaB4B@ACXfF)4(B@AOCE4(B@ALIC.Eф4(B@AKEvEĔ{4(B@AD:F4( B@AvComE^ip4(BA|AҔ>F14(BAA5B@AC B45B@A/pBU5B@A/pBU5B@BAFC15B@IAG/CDڡ5B@Aw`B5LB@IAG/CDڡ5_B@׮ACPE'W5`B@ACEc 5EB@ZA~CXD5FB@NAtCD 5GB@AyCH8D- 5B@A8C)BʿI5B@AC*CNMH5B@AC5YD"5B@fAuC5D-E!5B@2AAC*5B@cAdC05 B@AC%ZDRE5 B@A3C?Di5 B@uAgCu9DC5 B@AB~Eb5 B@AC B5 B@A&CGC'5 B@ACD;435 B@AC4pB5 B@A%E[5 B@ãA^C !H5 B@AC"B95 LB@fA=C+5 rB@͈ACYDD5 B@ABE5 B@A/pBU5B@AC B45B@A/pBU5B@ACfB5B@AwC ^CC95B@-A|C35$xB@AC#S5$yB@AmC #5'B@ãA^C !H5'B@-A|C35(B@;ACA4Dc,u5(B@1AC!VSDڅ5)B@ǑACV5*B@A #CSC .h5*B@+A%C9D5*B@aA#,CD5-B@A*C3D15-B@AgC@Ds֑5-B@AC D5.B@ ACyE~5.B@aA.CUm E@&5.B@AB!D:Gl5MABC X6'XB@8A.Cu6'YB@A+C6'B@A`8CD^Dv6'B@AAC E 6'B@A1C!tC,6'B@A-vC \6(RB@AuCD6(SB@`AB76++B@|AbC E6+,B@ArC6+B@{AClC6,B@qIAKC kBEA6-B@bA=C6-B@A1SCDCO6-B@A-CcC26-B@J7B@IACC7B@T APC %7B@XA7C9`@7B@fACF77B@A6nC>fEq7B@AaICR^D7B@ƎAYCUiDÉd7AB@mAkCP`E!7BB@4AlCLoD'7CB@6A^CSD>87}B@A9C0rqE7BA'AC)8E&7B@}A"C'jtE7BA'BA C!E;7BAFA*C3ZE**7BA;fA#C12E7BABA(!C6VE]7B@TAKCC HYC 7"BA/AC!EG7 CB@=AP5C<!D7 NB@bAC8A7 B@AknCRAxCHDƷ7 B@ AubCH_#Do7 B@A2C4ygE|7 BAA'C(ۋF 7 BA?$A%C5ngEf7 }B@ A CSEUZ7 ~B@$ACow,ECs7 B@SAPC ZD> f7 "B@AxCKE'7 B@UA2C7 B@AYCPDi7 B@$Ah]COC7 B@AYCW7BARA1#C37iB@A6C3UE7jB@tAJ9CAE/7BA iA~C(/E5t7ZB@AcC1DDЁ7$xB@ACDB؝7'NB@mAB@gACD_.8RB@HA5C!8[B@xAC#ԿE "8\B@x6A CNEg68B@ABE.Y8B@jA?C XE8B@ACZŋE8B@AvCE_T8B@zAB:EW3y8B@A}CPER8B@A(C ƭE8B@ACDd8B@|ACO;DҘ8B@AFC.)Dkh8B@AC-6EA8B@{A C'8B@xhACE8B@eAAݏE$8B@uA C*ҫD08B@Z7A֌BIE8B@^A0CAES8B@f~AǣB~E87B@XCACPhEJ88B@/AËFb89B@_AC*OEu\8iB@A$ABqXF8jB@ A,vBV("Fk#8B@oAC&ϘEZ_8B@i"ABVE28B@zAАBDo8B@qxABGE^8B@wAUBE!/8+B@dABE'8,B@| AB4dD8-B@iAC:Db@8 B@AaA5`CE(_%8 B?kAe C1E 8 B@#_Aß/FBH8 MB@QABcEr8 NB@ACYE]8 B@dACEl=}8 B@ACnEs8 B@ACvE8 B@gAyB Eo8 B@qAC 5>D8 B@ACZtEs8 B@}ACJ6D(8 B@~AXC7 DG98 B@]ACKDA8 UB@yAB¸D操8 VB@v"ACD?8 iB@vvA8CeE' 8 jB@khABE$08 sB@gAC5@=DzH8 tB@IABE58 }B@FA(C/=8 ~B@AC#EU8 B@3AC8D T8 B@uAC8LDj8 B@RA0ZE48 B@&APϑF8 !B@]A=CHRSECأ8 "B@ A8BWEWB 8 B@xACsiE8 B@tACGt D8 B@|AfCDς8B@:AHBx)DnP8UB@jAFEe8VB@eA1EW;8WB@lqA6AyE:8vB@AւC C8B@uABDx8B@r9AvC C8B@lAXBُ.C8B@QAC,D }w8B@A%C58B@ACi D8B@|ACCDj8B@,AC 1~E:R8B@A8CnDJ8%B@#AбB͎D8%B@lABuQCm8%"B@ABD8%#B@AC'EN8'MB@wVABEZh8'NB@jAjB(EN8'OB@rAA/xE28'B@AdCl8'B@AͱCE (l8'B@{AͳC LD!uA8'B@ABD8(QB@%bA0,'4F[Y8(RB@A X~MF8(SB@SACx&E38(B@ABNSE9o8(B@8AaC4'DwɊ8*mB@AC);D*8*xB@AC]Cy8+!B@AJBszlE-28+"B@qAC$ E<8+#B@A5C,E`8+B@qvAOCK|D5E8+B@A CCڋ*8+B@ACCGy8,B@yA YCE_Ts8,B@h+AA`4Ep8,B@|VACH zEt/8-B@XACDD8-B@ABzNEK'w8-B@/AC6K<EB@VA}hB"N<BA2A)CE#+x<,BAJDAXC1ET<-BA9AeC`<B@nAABCD<%BAhA,B<&BAA#2C'F <'B@|AQi7-LFY%</BA.AKC=pEH=v<0BA#ACWEY<;B@[A$C^<BAAC WE<<[B@AnC>ͪFyl<\B@A_~@E7<]BA$ANC!I7<gB@ABRDb<lB@ABEF<AB@AHiFR1<BB@"AUCIEk<CB@AqÀI]E#<1B@ͿAMB㐐C< B@.A \BЃFeu< B@ABl< wBAQAC/-EV< xBA@A,BD< yBAEG<,B@ A1C Fz<,BA=AUCE<-BA8AWC E*!<.`BAI3ARC?AÌ+FC= B@A)dCƞCU= B@oA@GB]F#;= B@kAB@Ev= B@AC*EF=B@+ABE=#f=B@-AA-EF='CB@AGd MF59='DB@A9?OE='EB@AË F='B@yhABw='B@SAhAE-3=(B@ACE=(B@ATBDF=(B@AB7=)AB@kAC~5=*B@AA%:CE =* B@AB_ED#=*YB@ACyEV=*ZB@{AdD66ER=+B@ABsE;=+B@Aڴ,pBE=+B@8A?HEb=+B@YA\BD=.sB@AA EB=.tB@ArsCUFa9>(B@åAIBu?xB@xAcDFF?gB@AB=E4?qB@AۚB +F,?9B@fA$Bᄤ@*?vB@RApCbE?B@JHA)C FE?>B@cAF. @,BAPAKC$pE@-BAA†+F@5BAeAD,]F+]@6BAaAgC@lFa@7BA`4AVDC>E1@@BAlA8+C\ZF :"@ABAr7AvÅF$@YBA AhC&jaE1s@kBAgAC@lBAACB@mBAAA~C>oE@eB@ZAqC.C @gB@AC-:CFB@BA]|AtHCLSF@BAIAC5@BA;AC[F,I@-B@ARC(F@BA^ AZCWDn@ /BACzAC(@Dж@ 0BA5hAC CX@ 1BAC%?E@BA0-ACV DR@BAAYECO@ BAAC{Dn@mBAlA C@=NER@nBAaVAAAE)Q@oBAa AGBjE+@BANACuEyz@BAQAeCUE@BATARCWDj@BAkA&CxF3'@+BAqAAC{5E @,BAp5Af±?F<8@-BAxsAnCU@BAm#ACKSbE3@@BAvQAיCEn@BArGACi5El@BAACF48@BAOAXCE@BA6AA0F$+@BA~AxzC^ߵB_@:B@PA#CE9'@'/BAAAC&Eԕ@)BAl"ACv^5@)iBA AB͇]FN@)jBAAtU6E-@*BAEtAƉCZsD@*BA?jAOC5iYE)@*BAJACt=E@*BA;ACC4@*BA6AC,D[@,MBAhAU5Cq,bEI@,NBA`/AmC0D=@,OBAmmA-B'EI@-oBA9^A^C 'BN@-pBA:ACDW5@-qBAFACDS@-yBAA~CK@Djg@-zBAA~CN{D $u@-{BAA{CU)C'@-BA{A.CHZDOK-@-BAnAAmCE@@-BArA C;+Dɵ,@-BAaAC &D7T@-BAA|*CI^DJ*@-BA8ACE@..BAqAC<.DEh@.`BA_YAC(QF>@/2BAA"C2DD @0BA6ACAm@MwBAYA5CΏ~E @MxBAQAaC;Et%@MyBAPwAQCREu@RmBAIAVC5BB@пA]CHEBCBAAKC)E9BDB@AAC"CBEB@AMDF^BOB@AC$D=t/BB@AjCaE"BB@AC DBB@AC/D%BBAfcA`ÿFvBB@EAXD[ FJkBB@`AVDE?FhBB@OA^C)?FBsB@tAkC"OE{pBB@BAC*ZBQBA}^A/NC@!BB@APfCC)F>KBB@HAC"E,MB BAnAC/9B BA_AjC-CqB B@A7C!;EB B@ACqFDeB B@A CMFveBBAA}/C$F)BBABAsC²FhBBA5OAXCGEFBFB@hAwCbF#BGBAA|C2}EBB@AQC`jF8aBB@A`:À7EBB@A&CE%B?B@A~'C7EҹbB@B@3ASCQJEBAB@AO>CErDΰB)B@ABhE?B)B@A B|EB)B@ACQFB)B@AC&jEB)BA;AQC!DҳB+?BAFA[C3&EB+@BAWAw^C0B+B@AC E?B+B@TACE#B+B@qATC$hFB+B@.Ap'C'FEjB+B@AC(F%\B,BAGAa/DFG<B,B@SAk4E(B,B@ACFF/B-BA AC#1B-B@ACFB/;B@A$C4p(EuUB/xB@A4C%7DO@B/B@AnWC޶Dƿ,B/B@A[qCoAB/B@AC1BMB@CAMC;dBOBB CACi E (BOBAF>BRnBAWXAMjC_sFASBRB@ A@C<" BU{B@AoC~E4BVBAްAXCVE,BVBB-APCBZBA>A3_CI;EB\BAAnC? FlB\BAaA]4CNOhFjyB\BAVA#CPwwD uB\BB%1A\C1hC"dB^9BAaACYW`EDB^:BAmA:BYF>kB^;BAvAB@IFMB_IBAACMD{wB_QBAAIC[BF BvBA ACTE[ԱFNBB$A\CaEcFNBBAC<'E8FNBBA#[C8~D@FNBBWAC"D8FOBB/rAlCj:FPBBAIC߄zEsyFRBBACo?Et<F`MBB'A=CfuD֪@F`_BBۑA aCE MF``BBAC@E8kZF`BB A(C}`A٦iF`BBAs_CuEFaBBAC|ETzFaBBAKCElFaBB^AD@ FkJFvhBB\A!LCXGBB&ACܢ)GNBBRA>C@E܈GNBB{ACkF!GNBBf9GVBBAC$FDIGVBB.ATC_hEGWwBBA+}CG2EGWxBBA UCFGWBB>A5CE%$GX]BBZA CNEGX^BBQAsCjE}:GYBBUdAbCw5.E7GYBBDA#CF GYBBA"DVGZBBCACrFGZ BBCFxG^BBjAC{1!EzG^BBACF(6G^BBhlA CFEIG^BBnA#CEծG_)BBGA&CE.G_*BBEA 4C˘EWYG_+BBEACEvVG_GBB6VAvCfEG_HBB6`AjCMFG_IBB!yAMeC8F<.G`BBY5ACg&DxG`MBB!VAC_JE9G`aBBA>CDڋDG`uBBYAC E:G`BBS A.Cw-PG`BBBA CrEחnG`BB4'ACoCt|jGvBBiABe_F7dNGvBB>QA[CGx4BBLA^7C̑EGx5BBE)AoC ,E:GBBJWAMSCnqE9GBBJA*;CĜE.hG#BBXACDDWJB@AHCKD.xJNBB`ACD(0JNBBf}ApCJVBBfATCOJ`uBBdgACD|A B@AUCJB B@AC B4GBBZACeAp 55B@A1C#_Ba BB@kABCx1GBBZACpCGBBZACp73B@AhC"$BXʃB@cAg|rFN0UB@OAC)fD0y%B@AC${D y/B@A/kCDyMB@?ArC.e"Ai0yUB@ACRE/yiB@uAC=VDyjB@AZCi\E ykB@&AC,RCnyuB@AC BEHyB@ACyB@CAcCDbyB@NA C1TyB@AC8ȽDyB@A)C13CWyB@AԯCnVyB@кAC~E;yB@AAEc!yB@ABBER]2yB@ACEyB@AC=D yB@AvCn]D¦yB@AoyCTLFE)|5yB@ABCh<yB@ADCVRDf\z B@ABEczB@ACEilz'B@|ARCDXz(B@}BA!BfDz)B@aACF(Ez;B@A/C&zzB@hAGCECd|zB@ACPE}xzB@ACDpzB@ApC WE_!zB@ABEJzB@AiB!E|zB@AywCiDqzBASAWCBzB@A!B/E.wzB@YAEC[^zB@MACfEq{B@rAf@^E~{B@AcChDk{B@ASIC,Dę{!B@APC ~CGi{?B@EAd2BE4V{]B@AC CDpW{^B@ZA!C={_B@AC 4BD{gB@AB7ُEq{hB@zA"CID-fC{iB@EA•CE {{B@xAC4D\U{|B@aAC6uD{B@ACcDw{B@0AA&ENC{B@6AbC#DS{B@A'CAE^{B@A~CEL{B@A#C Dri{B@AB0EWu{B@AB ET&|CB@kACwEA |DB@ȉAfC)Eb.|EB@AyCD|MB@AeC8C>|NB@Ab(CG*D,F|OB@AZCQE7|B@AMC}!)CJ|B@0A"C 9D`\/|B@AxC!NC>|B@nACpAƧ[|B@5ACafE|B@XAAOE|B@A2BCL|B@jALB3E,Ƭ|B@eAcݐEԁ|B@`A,C@sE`SG3B@ACD4B@tAٵC.NC5B@AC;jCG>B@.ABmB@>A%CE"DOyB@A'C B@1AԚC$B@A C $C<)B@xADCe=CcB@A.BDZB@QA$C goD6GB@NA YCDB@AlC*ChB@DAuC 8DxUB@AȲBFNnD͵B@{AFC*DSB@zA^CγB2B@|AC ADgUB@AoCJD5vVB@ACQNNEZ=kB@͈A BDDB@?ACtD'(B@A CETKB@ ACpMDB@$AC=E)|B@AUC#TD4B@A`C"HD|B@pA׏C^DB@ABC DS1B@9A}C+ 1DB2B@gA CE#W3B@AuC"{E'OB@íA/BYB@ AhgCwDBZB@Ah1CUXlDVeB@{AC9C B@ABKDB@A8EsB@tA9`C CE0B@AC#^wB@=ABtDFB@CACE(B@$AW$CMB@A/CZ̧D8cB@SACfDADB@AHBWEԊ+B@7AC2EzB@HA$CZE)Y+B@϶A*CCe@DiM`,B@ГADC 5DǦ5B@AwCyi7?B@AC %Cp ?@B@ACEAB@A CETKgB@ A٠CjDRhB@ ACDiB@AЭBJDvn{B@A/ZC E}{|B@ ACݻEB@ªANC ~B@A=CbB@A=CbB@ACXMB|5B@,ACEBIcB@AByNJB@A^nCD!ǝB@ACnC%DZB@aAC-.TCdDzB@>AC5SDvǻB@ AۏC1BTHǽB@hA׌C"=D2lB@A[CJD'PB@}AZ^CXWD1B@AXCT&}DB@|sAC%DRB@~AC B@ACsDB@A)C$DtB@AKCb5DuB@AECY8D#ɑB@EA)CC4ɒB@UAC)`DuSeɥB@|AhC:>EA$XC;E`IB@AJJBdD 8B@ABu0DWB@AqBTF[BB@TA4CCF9CB@ABO\*EXwKB@ACZF/LB@lAxC4F@IMB@nAkB麲E6'UBA\AXSC^|_B@AnC#+0E(`B@ACEE#aB@AKC^EB@faArBB@b>ABEB@d8ABCv BAAUDzEdFuB@*AG C.Dj 7BAEASC?PD8BAXAreC23EBB,A3D7DFzB@,AD5F%BAAvC"E B@GA CDyFn B@ACcEm| B@hAdCqqFB@AFA?F"py'BAsACE[)BAnAB=Fl1B@ACFW2B@AC(FfH3BAAmiFnEBAuACFDFOBAsAsÿuFQ/PBAhAC<E5HQBAiAWC'%FfoB@AC)'C)ywBANAC5JD.BAdA|-FǿTBAaAgC?0F$BA%AIC2^B@`(AlC„FaB@]AVB]CB@AiA%EsB@^NAvBDpBB@AnCEBAtA)CNfE[(BAyAC@QADH BA{A7C[tDBAraAtÆ F>B@gfAuBG!EB@dA2 CB@PEAjAF%fkB@ϫAnZA$EB@AC'hFJ 5B@AGD1Fa*B@foABPB@ASBMDxB@AB ETB@A}UBE-BA4A“C&FBAAMvC^DOFB@hAC!xE]dB@lAC(jDNB@IACHޖEHBA@AM1C8Ej-BA7AC‘+Fd B@>A!C CVBAHA^(C;tFE(ZBAXA80C84FdB@AC)sBAqAyC)JD(ڪBBy AKC[#2BBA)ChME>]2BBAaCj9E92BB|'ALCd{RE3BB'ACAC6FCbBA}AJCE!B@ABffCBBACEWDBBxA)C#}E}}EBB~AC:E62BB|AiC^}TEkiBBqAAB7FBBBw ApCx)E>BBDA,A9D E16BBxACl>EڮBB:A"C DiBBA6Cf0EBB}LA7dCEvBB\A@[C:EJ1BBuSACoܭExmBBo&AC`REBBj/Av~C8EOPBAH+AC@^5!BB8AyC0 .E"BAA(BF?BAFACKODI#9IBA?AC:EJBA,`ACdAFPBB mApC`F?eBBA/ClF!BAzA@BFCBBA*LC+FBAABeF*BAA@C)fFLBB 5AbC =EnBAAg]A hFBA+A]B]EͨBAyA0C,D -zBAzAD6C$BA2AhBBeAjC>rgEt{BBjAwC6E|BBgAn CAE}BBRA5xC9 EDCBBVA!C'QEcDBBMATCՠE3~EBBjAaCqE'kBBKAC3 E4$+lBB1AC0EouBBwAwC[g?DYvBBA{CVDJBBACE.E~BBAD;BAH+AC@^5CG?CvVBB0AC+e/FNBBIA@xB>NE MBA$AoDC/NBAJQA*C}EOBAApC D|bBBAw;C2߾lBA%AC(?EEBA6yAICTbNBAA|ACץEυBAH1AtCABAC{ACt)EJBBjAC̉Ci.BB.PA,D#Dz1BA%AChF6Jz2BAAPeCQD z3BAAPeCQD zB@AʾCޏDzBAxALCX?5Dv?zBAAKCX;DenzBAA\C2 C!*{BASAWCB{BAAMCGFj{BAAIC8C~|BA AIC`F% c|BA`AC_F'~BAtA6OCVCBAACa!`F$BASAWCBB@&ACU0CGBAACfHF63IBAAP'CR2DBASAWCB TBE2A8D3E) ;BEA5ZD%YE+ DpjF3 GBEAD E. IBEA DEz BEFA=mD8E^ BE%AD4E) SBE ADsEu ^BEADQEOk BE+}A?DE, BEADDq8E?r BE,zADF6 GBDAC޾V ;kBE%AMCȭ*D ;mBEA>DHF[ BEAJC%F$T BE ADZEo BEA/BF IBE$A#CE + JBEAxCXE KBEAjCvVFk _BEGA-C4mD, BE`:A=CFe[ BEb9A8CFn BEA\CRE] BE|AcCsLEO, BEA\D`!EH< BEAiCžRE:n? BE ACmE$ BEŧA@C IBEqA|C{ [BEAuCAGEWu \BETA?C E ]BE«A2CyDè BEA_CXF BEɞAn:CEm BE1AB{F mBEA "CҘE0 nBE AHDvE oBEA·DNFM BEACF) BEAu C۬[E BE[AoFu BEWCA\C)EkqBAAgCZ B@EA^C"[COMBBA`iCG?,BAqACE/^BBhACBļ/_B@ACy^DBCABDoE BC`AƠDs"EjCaBCADrFbBC%AD{EվBBlA C1DxBBI[ARC3mEBBMA CX.DB8BBawAfC@2Ee}BBAC hDBBA{CCF BB?-AtCکDQ*NBBWjA9CEtBBBA&D 3BBKA5BF BBMTAC1FBAAUCn5BBlADXE"BB9ACssF1BAACoF;BAqACE/BAAC-FEBB9AqD 1BBADCmBBAI>CJnBBIAC.E$BAAkCbsF~BAA,C>Fl{ZBBOA 0C^D,BBFTA|CiEBBm~ACD4|B@A%OCC}*BAHA C'Dj}=B@AWB4iFA}>B@ABࡸEL;}?B@A,מF "}[BAdAd<:F\}\B@?AěDFF5}]BA'A{BIeEɍ'}BABA66sFe}BAuAD$ɇF}BAGE۱BA@ABBEFaBA07ACEC2vB@A HB)qC B@Ah2FB@ABݦEoB@AAGB&EFBA,ACHJCp?BA$5A7C%LAB@HA;TCIBA22A5C]gxDNCcJBAA{B;F C=B@AXC:F;>B@ӐACgmFj?B@ARYŽ&F)QBA nA ßfFU<RBA4AUBFd7SBAA@IF0t7BA=AqCJCEl?:8BA8YAKC,FD09BA4A?:CGiB@pAAIF jB@۝A)BqFkB@AH&E謶BA@A$C`!Eug BA9[AECxDKB@ڥA%RC^DE1B@ACp09B@A$1B_zEINBAOACE.B@!ABۺTEoB@PA B*FrBAABF*XBATACdEfBA+AB]E:q_B@jARBF;BA6AK{C aD B@AwC'\BANJB@.A7C +EJYoBA2AC!pBAABF.LBAAuBDB|?ɹB@?ABFGTɺB@ֵAPCk|F9B@A!CEtcB@uA>BZD֧-B@AC6 E+B@VA{B%CcBAMAޭCDdBALAC1DBAA%C6FLBADA C\s3D3BA5ACC>CI=B@?ACD)9B@A@BDuaBA=A*CJ-E`( BA;A6C( E&t!BA=A#C ^E)B@AIlE*B@AUB@D_+B@*A:yB+DB@bACw-EORakBAAAwcYtFMlBA%BB&AxCcE-BAAiÓWF.BB AC~UE7BBZFA#qCuD?8BBAA4D/Fj9BBJA$CFBB:A׶CNFcBA4A~SCM FbsdBA+AVCNyFxsHwBBH8A|CsExBBCSATCxÁEgyBBQACҷE{BBAA CXs2FaBBYADCkBBA3AC.EF|BIBAnABsF!ۣBA2APCITEBAAC7BF*BA=AZCNjUE:{BB2AxC[uF_|BBAPC|xF*}BBACFlEBBT8AmCvBAACNSEEBBP+AC:W=F$IBBW:AkCvEKBBVAuCE#ۓBBJAAڧCyfE۔BB6uACz2E[ەBB&AZCaJF)BD#A>D"BD#A>D"BDA7 D#E?-BD#A>D"BCAD.SD —BBkA CCBBe8A÷CD,byKB@AIC3FCbyLB@AC/Dh$byMB@ACNlDbyiB@OACdCbyB@WAkA3A%EG%~byB@jABeDbyB@`ABEAbyB@AbC1tDïbyB@$ACk$DY6byB@A CeD{LJbyB@AlBD$.byB@|AUCz+$EbyB@\A>lC2bz;B@A;C <=CxbzBxCb{B@&AYSCd2Db{B@SA@ICtEb|B@}ACaWD b|B@YAyCiqCVfb|B@A&C,D}7b|B@HA2CmlB)Fb|B@!AMC$ACb4B@A˶Cjh C7'7bGB@aACD6bHB@A#C=lC2b B@:A0eCٙD~b B@B@ArCD#b?B@`A^C3EVb\B@LAs\CbgB@B@AvC PDGe?B@bAtCZ^egB@9A(Ct_C^eB@A.CeʳB@AC BDeʵB@AgC'jDeʾB@AC7e"B@ACaeqB@{ABKDrerB@yA`C[EjeB@tATCuD"^eB@mA]C1&DeB@AC.D$CeB@jAۑC De%B@hA~C >#Dte&B@AB#Dse'B@$AC,XoD;EeOB@rANC1CmteaB@AC!eBD誅ebB@{AFCPDecB@~AhCEekB@A'CwEelB@{AC8DE emB@ABDyeB@ACўEvzeB@}.ACDe3B@AsCe5B@A_C9e=B@qqAC"Ce>B@s;ACnC#1efB@eAxBWDgRBA=oA>C1COxgBAs]A4BbEL.gBAK>AZB[E}gBAMjA:CF2yg{BAC^A^C%JE6g|BA;ACvD*g}BAB{A+WC3~4DAgBA{AWCƽE0>gBATA*C^.E㪅gBA>FAǬCtDvgBA;0ACCEgBABXAqC#FE9яgBA;ACogDNn,gBA\A;CZkE7gBAD_AC"yDr.gBALACEF+gBAMAnC;ʡEgBAGAC-CYg$BAFJAC%g%BARAkC4Eg-BA? A5C#D@g/BAJlAeiC qEg;BAE9A-C8E1Eg!AVC3RDКgBAEA C&KEWgBACVA$C#DgBAQRAnC~eEwgBA@ACWzD4gBAO^AL@CίE{g{BAAKC5/DR<gBAhA3C;DkgBABFA+jC4gBAAAQC 8^E|PgBA}AICRgMBAhAaJC5 uF mgNBAIA3~CA ErwgOBAjAe8CnF&-guBAQALC gwBANAHC͡EgBA;LAB DgBAAA\C8E=lo;BDAٛC\$Cr[oGBDACaHoBE#AwD ?eLtBB_AۗDd(D&taBCAɟDNDtBBAggCuE_tBBGA5*CEAtBB;A?CDtBBA#aCmC{tBBA(qCHsAa#tBBAC|EtBBTAC FDFtBB_AC0jE t}BB\AjCͿAC6EatBBMA%{C$EΐtۀBBfA(7CYEAtۋBBzA CrDY,tۧBBAC_EtۨBBA&CEkt۩BB̳ACK:Edp2t۱BBA_8CEbht۲BBA:5CDDQt۳BBA?CvDtۻBBrA(CC D&tۼBBA$Cx9D4.tBB^AICFܳtBBACSE_tBBACJ=EF\tBBACpE>MtBBA%\C|E㉩tBBACpER`Dt BBYACE.Et!BBACC$D!t3BBALCCft4BBA&D wEt=BBA0CmD@t>BB AuCDiڀtGBBA C DJzzBA6AACz}*BAEjA@AcCt;D7z~~BAIRAXBDL6z~BAP]AeC:E):z)BAOLAiCDZzZBA@A+QBzBA:AlyCAE`o`zBA@A}!C1F00zBAQACBET5:zBAJAAkEeZ B@A%CD"B@f)Ag5B% EoBBaA@^NH<BAIAC(oEc&BBHACIDPnBB_CAC|B7BBRAC~5BBBKA'CCjB@ڰAPC !H BA:RARCasxBAJAC7 #BAAC_wpE'BA(ACF\DlxBBCAAlCp׍ELۓBBVAkCSBD]EAD 1'BD]A(D =Dgw tBA|AmCHD] tBAAhiC5qA*geBBiGACAUBiBBiKAC-'ABAAJCQ# BANAPCACE%$BAACHDBA+AQCR<B@A+C9B@A+Co _BAACJ&Aܪ, BBnADC= B  BAACJ&Aܪ,nBAAǺCO yB@A C#CYYHBAACBA$ HBAAǺCO yB@AwC#C9B@oAxCz^E˅B@gACG5FK˅B@aAZ CV+˅B@ACC$E"F˅B@A1hCp5D˅B@DA5C'E;=˅B@VA'C ˅B@AC fDϭ˅B@AC4 D}˅B@uAeCEoDՇ%˅B@UA2BEk^˅B@ AI C&$Dj˅B@JA#CEw ˅B@iA+C8YE~˅B@AnC4cD=r˅B@7AXxCУD{˅?B@iAcCE/˅@B@AQC\ D"n˅CB@AC7Ao-˅DB@ACD]˅EB@A CE"E˅FB@A CU?˅GB@ABeD˅HB@@AC)D˅IB@_AtE6/s˅MB@A&CbAA/C\Fv˅B@A_B%4E{z˅B@hAHBE?˅B@'C/˅ B@;A]CFjJD4r˅`B@gAC >6Cc<˅aB@AB$GE˅bB@A]AMVE˅cB@AzC ăDa˅eB@A+QEI˅fB@#AluAE1b˅kB@A C ˅mB@A*CCx˅nB@ACC!N˅pB@dAC4BH:˅tB@OArC=EdO˅uB@A%CIEv˅vB@*A"C dC$˅wB@AC߄D˅yB@ACXyD˅zB@^A_C/TDY˅B@fACTB˅B@A;CRBDw˅B@Ay9C;EC˅B@A~eCw,E˅B@+ACr$Dұ˅B@AC E˅B@A C7Eu˅B@AB8FQJ˅B@bAj CE 3˅B@%A_QBENDս˅B@PAJ2CGuD㡃˅B@ÄA]CCȕ˅B@sAaCD[˅B@AwBD˅B@$A^CD˅B@tAC& DQ˅B@AAwBD˅B@ARCE(n˅B@A:BBE%˅B@AAC=C,˅B@]ACGuB˅B@PAC-A3W˅B@'A;CMfDE6˅B@A5HC4B˅B@AB >Dc˅B@A[CwE .˅B@-ApCD+ ˅B@3AQCC3˅B@GACB|˅B@NACC?B# ˅B@AC$D8]˅B@ABE%˅B@ABE˅B@A6C4DFw˅B@AC D/˅B@A%@E˅B@ACEH˅B@ACGE,˅B@AnC˅B@ҢA:C AD"˅B@ȫA+C *lD@˅B@hA8CYD2˅B@A- C7DS˅B@"AI%CDJ`6˅B@A(C\D* ˅B@ A[C{Ej˅B@A,CY$D׉?˅B@AXCieD˅B@AyC EO˅B@A|AE{˅B@!AS2AEX!˅B@A1C/5ZC'S˅B@A߳C*C˅B@AۂC+D˅B@ALC'uD\8˅B@AEHC hDX˅B@ALC'Dm9h˅B@AȘCcD"z˅B@AC*|DL˅B@APC D˅B@A|CBPO˅B@AbCxB˅BAB AvCJE3˅BA9Ai CTD˅*B@nA%B ˅ABAcA\*B UFL˅LBA?ACuAFC2˅MBA0Aà4F 0˅NBA A9@FV˅OBB$DA`Ca˅PBAWrAD4F˅QBAuAoCEױ5˅eBADA:C,˅oB@AC˅qB@ACt"˅BAJAC ˅B@!AB˅B@ATC{OE˅B@AGC R˅B@|A B˅B@A4Cx5BaP˅BA3QA8CE˅BAAC gF9PS˅BAsAgCcDi˅BA7eARC Fq˅#0B@کAOC peD˅#1B@vAފC]F '[˅#2B@lAB(jF:˅#=B@ؕABF90˅#>B@ԳA0CF85k˅#?B@mAjB,iFWn˅#@B@AD3FX˅#AB@ԏACFUS#˅#BB@A"BCOFS{˅#CBARSAs=C+qxE;jU˅#DBAKAeCAE˅#EBATlAx2CEE˅#IBA?.A]C"˅#JB@A6#CqcE1i˅#KBAzNAHļ\FC˅#NBAHA;C]ET˅#OBAAvCgEr˅#PBA3A/CuE˅#SB@AB]FrpG˅#TB@A.Bq7E.lN˅#UB@ACi}Fs˅#VBAACE˅#WB@Ata+Ex?˅#XB@}AC?F1lL˅#ZB@AC?F'}˅#\BA%ACSEK˅#]B@)AsC E˅#^B@ACEMC˅#eB@ A tEjt˅#fB@AcC~E˅#B@OA4rnF[˅#B@HACOPF˅#BA?AjDuFr˅oBA3XA%Cq˅|B@ABmD'˅LBAK$Ah?CbNˆB@RA~DG7"ˆB@AaRBDˆB@AdCo@EˆB@MAZCQ[rEEˆB@AsfCqDxˆB@AjCdE(ˆB@AbfCME sˆB@AC DˆB@AC.ˆB@AC xC`ˆB@AB҃D;zˆB@pABEM1ˆ B@RACiFY߈ˆ B@AhCPlF"ˆ B@ACDL~E\-ˆ B@{]AיBՍEIˆ B@|"A3°E2ˆB@{AC/)ElˆB@xA(BkE=TˆB@w.AC^NE#WˆB@ABZ E՛$ˆB@FACEˆB@[AICCEIˆB@ACmE`uˆB@{iAC(lECˆB@ACE@XˆB@ApCl6FˆB@A'CݵEˆB@8ABwC,rE5MˆB@AC,E'XˆB@)ACEˆB@|A˜lEzˆB@uTADVbF/ˆ!B@GA kChˆ"B@ACD ˆ&B@a'A1A0FF ˆ'B@9zA!lCbHD*ˆ(B@UA+Dj'F0ˆ)B@poAcCZ rFYOˆ+B@!AC7\DUWˆC*E5ˆNB@JACˆgB@fA^CˆhB@AoC2VDmˆmB@AGOCoDD~ˆnB@UAfHC@yE3ˆoB@AKCnGDu5ˆpB@LAJ~Ce=D\ˆqB@pAC*EYˆrB@ACEJˆsB@ACEmˆtB@ AvCucD.&ˆuB@AK3F?ˆvB@AoBߞE(TˆwB@tA>CxEyˆxB@SA C aE `ˆyB@ˆB@[AcC9E.v}ˆB@dAbUCSKE'iˆB@JAS!Cg7DF-ˆB@iALCeDˆB@A+BdE_ˆB@֦A+CPCˆB@AB4EPˆB@tANC^CEDˆB@AVBܐEW:ˆB@UACL,,EyHˆB@:ACPD=`ˆB@AoCzYE!ˆB@}AmCDDˆB@AhCQ5?D7ˆB@AdC orDˆB@AbCuDsGˆB@A~CEˆB@]A~BAEwˆ B@AN5¸Fˆ B@A{B­Eˆ B@AvLBEVˆ B@AC1D`eˆB@AYC )CˆB@A"C ,zCTˆB@%AcC$aC+ˆB@AC ˆB@AC8-DˆB@A+ CAJˆ B@A+mCcE/ˆ!B@AC(Oˆ"B@AMChA.ˆ#B@1ACIB Eˆ$B@_A-QCyLDˆ%B@ACF Ezˆ&B@A&CeE2ˆ5B@AzCC ,CὛˆ6B@AbCJEYwˆ7B@ArCRġDjˆ9B@GA'C$\jˆ:B@A/C Cˆ>B@GAAmxEdˆ@B@AyC)D$6ˆAB@AB-DV6ˆBB@A9ڴF.ˆCB@USABE ˆDB@JAD:ZFpYˆEB@ACE+uˆFB@DA^CeݕEIˆGB@$AB)DˆHB@ACE,ˆIB@}A .BʼD\ˆJB@uVAB4*EdiˆMB@LAbCcE~ˆPB@TA.=CAEiyˆQB@LA UEKˆTB@]$AͥBΕEGfˆUB@NmA -C EˆVB@GA\yC.LF|ˆXB@ AEC!DˆYB@A.(C-YE&6ˆZB@AG2CVC.ˆ\B@/ACE*pˆ]B@ A(C]Dqˆ^B@GACHEˆbB@A%C^~E@ˆcB@AjFHˆdB@UAkF_ˆkB@JAEtuˆlB@ACEdQˆmB@MAB{E#2ˆnB@ACD_ˆoB@AiB]E"5ˆpB@ACLD%ˆqB@oAC-#IE?ˆrB@\3ApK\iE6ˆsB@pACq(ED ˆyB@@AZCˆ{B@!AC ׉C*\ˆ|B@A% C$E"6ˆ}B@ށArB}_E.ˆB@_A C./ CNˆB@A~ACSCbސˆB@bAsBEˆB@ACiE% ˆB@A)C# 2CR:ˆB@A0CQEEEˆB@ACFC:BˆB@fAXC+E:ˆB@^@AC=F.XBˆB@?D(ˆB@A!C7D;ˆB@MATËF%Y@ˆB@A 'EˆB@tA;C 7ˆB@y7A6CeE6IˆB@rABD#ˆB@{_AxAE2ˆB@EA#C#.E"{ˆB@|FA@WOhE'&tˆB@bAC((E ˆB@A$Ch1ˆB@,AC0+4DˆB@_AB"IEbˆB@ABbDIˆB@aAKBD߬ˆB@c@AC걎F!jˆB@C*=MBBhIACMBBhIACMBBNACRF@|MBAI ACzEMBAAChsMBB ?A:BF-pMBAACaD$gMBAAC,EWP|MBAACaD$gMBAYA9CEMBAMA$CMC"MBAGAaCPDM"BAA B?M/BAzAߔC]D5M0BAb:A=C XElM2BA^AH6ExM3BAACv3DGM5BB$AKCY?C`{M6BBlAC2EAM;BAyACnMABAQdACWSE!3MBBAfA B #FMCBBAC7Eg;MIBA{ACo9E4 MJBA A NSFMLBATAXCDnMMBAAtACpMNBA8Ac;ClD PMOBAkAnC2-MPBA9AhCMRBAYA9CEMTBAAB>D̜MzBB*A|pCDҭE-M{BB+A{C`DBpM|BB*\ACkqDMBA[AbC[EbtMBAAlC8FMBBARC+?MBAMAC[^ElMBBqAC|+E MBB$DA`CaMBAyACnMBA'AC =MBADACMBAA_C DF7MBAEA& thE)M BAIApC_-D>+M "BABWA+CDCM B@aACM BAAC SPAM BABAKCYCj0MBCACJMDx"MBCASnCDiDNRMBCcA+C@\jMBCA$CM6EsMBCJAJwCBQE *5MBCACHEkMBCAC2ME!qMBCAIC=EG#_MBCVAC3CB"DؾMBChAC=xFCg>MBC$A CN-MBCA\CE,MBCkACFEC4MBCACYEJMBCAIC=EG#_MBCAC2ME!qMBCAsCF.DA4MBCAtCADgMBCACV+DMBCACoD^MBCAoCbiE$MBCŚAtCgEItMBCATChrMBC+A7CNCEKMBClACG_FMBCAhCJ.MBCusANCF=^MBCxACKTE{sMBCjACHEzMBCxACHEMBCACPE MBD>A\ CqF!MBCAreCoD~DMBDtA;CllE#+MBCAreCoD~DMBCAreCoD~DM BCAYChE?-M BCٔABCaĝDMBCAg7CmMBC+ABJCh6E0MBCLAFCnEuMBD5AaCHF MMBD&AQgCqYEFMBDABCm&EZ0MBDABCm&EZ0MvBD`AnCoE QMBD}ACE8MBD\AWCwEKMBD\AWCwEKMBDACGDƂMBDjA^C)MEMBDAC[iDJjMBD\AWCwEKMBD9ALCrEd MBDPA/C#EhMBDQACuE{MBD-A^CoE8MBDAGCk;DkMBDzAC,E?mMBD:AC-}E0MBDAlCE=M\BCA{C2 rEW,M_BC)YAC>yFM`BBABS ESMaBC)AC8D~`MbBCA{C2 rEW,McBBAC2EFMdBBA?C4DMkBCCA*QC9|CE\MlBCHA/C?gE.)MmBBAC2EFMnBBACiiFOMoBB6AC=F HMpBB3ACuBEMrBBωAC)F%MsBB=AFC%E}wMtBBAHCFoMuBCGA@C6,;FRE*MvBBAC K EòMwBC13AC>V&FoZM#/BABAC5NEM#BAxAC D\MoBA4jA/lB&CqߗBD-AD&DBAAҤCd]qTB@AmgBC9 BAACFE +BAFEAnICC @BAA CDdK ABAAC5WF BBAAʝB1F  iBAAtB[EӶ jBAAHB 8zF|}  kBAʠAC-&Fq BA[ACQC4 BABwApCS BAA}C,QFXBDBAFCU!E%BD=AZC7F:BDHAjD!Ex1BDRAQC5FBDLA;DQCoBDYqAD*DɷBD[AD _D,fBD0)A{DF:?+BD6)ADFBDN+ACX-EBDLA4D;EW,BD>sAXC?F$.BDJ3AcxB#KFBD=AD hFmTBD9A_C]FBD?A CFFp@BDACgE}pBD"XAsCْEKBD!A bCESBD3AD`:F7BDRA.DRE<*BDAOClE/BD9vAGCŻ^F#BDC]ABFcgBD]kAC!BD]kAC!BD^AfD8VEā:BDHAqFBD^AD!LF& BD]kAC!BD__A*D'FzGBD^hAlD B:yBAwAGuy7BAAOC pbyBA TAjCRG yBAMbARCwyBA9APC ELyBA%A&CrEK3>yBAHpA9CsDoI yBAGA(R?E_ydB@ACKFiJ yeB@AD3Fh4ylB@ABJ=yoB@lATCkFN!yqB@_ACPZFl#yBAIACRE+TJyB@SA-DeFyB@A|DJ[FJ5@yB@\ABCE2yBAE\ABC`.yB@A=BFCyB@ALC zyB@A°RFFyB@A,tfFea+yBA AC ?F.yB@AB9F_yB@AXlE߅yB@&A_BMEqyB@6A;CvC\EyB@ATh-FyB@ ABDyB@A >FvyBA6CAFvCIDyB@A:iFyBAOAĈClF nyB@#A0D!F"yBAACkF cy BAKYAUzÄFy BA6ASC"WF'%y BA]AC Fey B@AA By BAM AaC&'Edy BA:2AFCES&y BAUARCFry BALnAB3E.-y BAC#AUC wE9y BA>AƒC@ Dy BAPANBE.y BA=SAȿBECy BA=dAͭBrExy BA.AsC+9Ery BA>yA3EXy BANA[CAA ERy B@kA7+$ZFky B@ AۢFy B@A@Fzpy BA-AVCuFt<y B@'AC1krE8wy B@ AC΋Ey B@jAcB :Ey B@ ACH!D_y B@)AqBlENy BA ASDU[Fy B@-ADCĞFxy B@ABf2E6wy BA A1+DHFCy B@A\C6_E0y B@)ACEd#y B@Aq?BXEџy B@'A;:Eiy BAmACtGF y BAfADFJ@y BAk,AC锕Fy BA%A]CSPDAQ3y BA)A]CTDBy B@:A;BC qy B@SAtCF"y B@DAA)Ey BA^3AmD(CF y BAr3AC 8E½=y B@`AhB8E(y B@+A<1D(,=FCy BAYNA%CM5E\wy BA=gAfBUIF)iy BAXHADcSF(y BAg.AC8TE7y BAGA[BE|y BANABZF>y !BA2ABEEcFy "BAPAgCETy #BAE^A zEk$y $BA2ACESy %BAT%A7C0Eny &B@HApB)E5y +BA>A\YCM;dy .BAApCb#C'y 1B@A[DkFoy 4BAmACDEцy 5BAW!AFy 6BAtA]ÙeFYoy 7BADdAVF,y 8BAAl5D+Fy @BAAkC3y BBAACD϶y HBA<=ADBD&y JBAmA^C7rFny KBApAx,IF=dy LB@_AcCFHBy MB@;AAyAF+dy PB@SALCX@Fy QBAbCA~B)7E;y RB@A_B\FB6,y SB@AF#Ky TB@(ABFV]y UB@AX?Fzy ]BAtVA8CUE^y aBAjA(CF:dy bB@JAC>Egy eB@; ABPDy fB@I7ACEy iBA)A CDZy jBAwAcCD-(y kBAPAOCD]y qB@RAy B@AC lF(+y B@NA7CEi.y BAGAlCZLy B@ACKFޢoy B@ACJ\Fv_y BAsFA9@aFy BAUAēFyEy BAjAAD+Fy BA6AGCS bCuuy BA5As:CXRF$y B@A7ĄGHey BAr A3CB6E y BAIqAFCFD@y BA06Am}ȄF y B@A®FޯLy B@A^9zFm'y B@3A3†HcFy B@R%AC]xF9Ry B@A?-Fby B@CpABB\C9 y BAABFwy BAdA~AFQy BAFrAC]OEy BAACܖFzhy BAA{qC79FuFypBBkACmE۽yqBBAxD i'EyrBBAUCMD$.PywBB*ANCCyxBB!AD =DyyBBlAD;8E<yzBB A{CK F#y{BBA>C,E~"y|BB_A/,CFAy}BB4AC,E!y~BBAJCODkyBBADCsUFcyBBfAƵCyDyBB߹A"C5DnyBB4AC,E!yBBACEuyBBA DCID&yBBAmCOmE8yBBACCyBBAZCkE7KyBByADBcMEЏyBBAD0EFyBBA^CrEW?yBBA>C DSyBBA_CCUpNyBBAqeC;yBBA!GCuEEoyBBA$CùCyBB͵AwCEmAyBBAMDlyBBQAJDxE]yBBQnABOuFUyBBNAxïF&yBBXAm^C'E]tyBBPAFC*EyBBGAD zEyBBOA@D΄E;yBBF*ABAwCKFMyABB=AC~FgyBBBA.C\^Fm}yCBA!AqCQFyDBBAOCu'FxIyEBBJA&9CEyFBBBA*C.F@e%yGBBB_AeCE yHBB)AL'CiEϼyIBBMAYCUFTyJBBAAICmcF{yKBBN AC mF@yLBBPACʷFyMBBVHACx0Ei9yjBAACCXtFxykBAACD,F~ ylBA{AnC#FyymBAIAFC{F ynBA.AeCm|FyoBAACCzFEypBAACt8FMyBAPAmC$ExnyBAPAmC$ExnyBAPAmC$ExnyBAuACl:DZyBAwA4SF?yBA,aA0(vF|y BAZAEFU3y#=B@:A9BogDyy#?B@eAC׍y#B@ZA+B \CW;y#B@AlBBCyBBCACyCBB^GABCOy'BAmACHy1B@ACBDδyPBAmnAcC3E ywBBPAC~9XyxBB3ACqDyyBBNACr)9DQ8y]BBG9AmC}E5=yBBZMA'C^B@ACQB@AgCwA;B=qATDMNYB=A}gCDYFfB=A}gCDYFuB=A}gCDYm|B=]AC^YmB=AqCYCYB=nA'CvWD:YB=A}gCDMB@LACUC¹[M-B@JACfAgM7B@AChBHM)B@/A NCK˵CW[MMB@AVC6D?MMB@A 'CW8D;MMB@ ACgB'nMS?B@ACkD MSIB@xA CqD)uMSSB@-ACg_mB/MuB@A_CvNCCPB@ACB\D+PB@@A CNC_(PB@sACGDQ*PB@ACH DkPB@FA CCD zPB@A8C^mC~1PB@>A Cc4D0XuPSIB@xAUCMC PPUB@A&C%CE'PUB@PACUGD]P]fB@fAC?CpCjP]B@AmCO^^D?VPb{B@qA CaDfPbB@A CtDB#PcBB@kACa8D,PcLB@A9CiOfDPcVB@ACDm=DrPeB@ACJޤDPeB@CH Dč PeB@ACE*aDhPv{B@A)CUWPw9B@ ACACDPB@ACYP&B@ACBhDP0B@ACEDkwP:B@ACO(DMPB@AC%EP B@ACB@ֳA]CQ;dxB@0A vC#B@XA?CCO`C}-;gB@AC.xEC ;iB@ЉAC1{C0;kmB@&A $C"mD>;lB@CA CnZD;mB@sA%CZICg;mB@AaC e;qB@eA ;y7B@ޅA C%5BM;yB@լA COC;yB@ACJHD"Һ;yB@ACADYr0;zB@ֳA]CQ;z'B@ӼACCBz;|WB@AC;|B@ACHHC|;|B@\ApC8_C;} B@%A&CMDeq;}B@AC28 D`F;B@݆A CC;B@ڂAC^FDi;B@ݩAOCuID;nB@&AC9x1At;nB@AC{'@Dr;xB@AC\ #D;B@׿ACH"DW";B@FACQaCk%;B@ACL0C&;B@IACm9;zB@"AC4u\Da;MB@0AeC0gD';ZB@1A/C9B ;dB@1AC9&C ;nB@AC8 bD-;B@-AC8C;C8C ;B@#ACcEpCI;B@gA5CQwLD[ ;B@+A-C{AR+C B>{AR+C B@^A/CR/ B@|+ACP Ei B@iACCD` B@mACK%D U B@EAC7~fE B@d0ACME#K B@EACBBSE{ B@]AMC?C B@UAC@Y 5B@JAC7xC鼶 BuB@!AC<! ^B@UArC?ZD ^B@_A2C:HEgP^ ^B@XACH1DY `B@dAC@9 vB@YACGzE#] vB@ugAC?&Eb vB@vAtC6End vB@BA|CJ wB@r1ACHD wB@ACQ) B@'A~CyZ_E  B@:nAC2|FE?m &B@A COsE 'd B@:AC6)E8Q ;B@ACHM[D y EB@ACJ~ E B@GAC8H$CM B@t&ACG; ĔB@ACS E iB@HqAC7E  sB@EArC3o\D1? B@[AaCB/E B@a-ACKNEOn B@]AMC?C %B@kAC3vE /B@b.ACF{E\G 9B@iACB|,DuÅ WB@oACGDn aB@uACC]E! |B@?AC3JxD. !B@ΣAC7 !B@ΣAC7 !V,B@ΣAC7 !V6B@ΣAC7 !^B@ҴACE^ !kB@APCEB\j !B@ΣAC7 "VsB@AC8A6B@A4CEq7B@LA4~C.C,9] _B@A1_C2 ]B 9h iB@ʻA1PC*h B@ACMbNB@HA8^CA;/B@A3 CPND B@A Cc[CqG#B@A CfD4-B@A MCw@D'7B@A Cw!D/ !?B@CA_C$-D1@)!IB@QAVC2%D}Y!!SB@C(JDB@A1_C2 ]B 9hB@XA;C[>DdB@KA^rCDD:RB@*AXC*CsB@AVC#)7@9sB@A/CuADh}B@A"C^DB@A"C^DB@ШA-C"fCxUB@ɅA,C3B@=ARCS|C=9͆B@AM0CJCUK͚B@A1_C2 ]B 9hwB@AAKC /EKρB@AO|C4rEdϋB@FA4?C/2dC8eB@:AB@y"AB@!D HB@xAqBD< RB@x4AqCQ/$DD zB@xeAC0uD2 B@yfAC3`C8o B@xeAC0uD2 B@{AxC(AG B@kA C]7UES B@s|ACCD EB@uAC7C fB@zADC6Z B@yAC39Cҫ B@\-A C1D B@_ALWCCD #B@zAC,#C $B@v_AsBE $B@yAB+D $9B@xAC"D qB@uABDP %B@u]ArC4L B@jADhrEї B@VAD!F ! B@hRACf{EV B@AA6DIkCi3 %B@wARCLzDTb &B@nA3C@jD B@QA'D$E @B@AgCwA; @B@ĺAgC.2A?DAdA>D=.B>|A?DѢrCvr.B>`A?DќC'0B>[A>Dؿ0B>A>D30B>tA>DkCύ.0!B> A>BDBHJ0B>pA>DD(BA0B>$A>wDvCuT!BX:B<>Av#ChDUbvBAw>CeBBCDhB(?#BBWAoC1FYp(?-BB[ACҕYF%t=(?BBTACsT(DBB8A\Cs)A (DBBvAC4,Ff (D/BBAeCySF(D/BBcA2:CE(D/BB|AHCos$*ADME.}IB>A CD .}SB>%ACʽD.}qB>xACxEC .}B>DA1CߘIFf.}B=AF]C.}B=A CXC.}BB>LWACCHDt.}B>8ACi1EW.}B>ACQE.}cB=\ACʾVEx.}KB>BACEDEp7.}B>AZC2@|.}B>12AkCD-H.}'B>=AVCEPX.}"B=A:CȜnDڠ.}"B=nA"CDC.}"#B=A,=CD\.}= B=ACXĘ.}MB.}PB+`AC2.D.}[qB>FA5CqiC-.}\CB^A,JD .]FIT4.}`B=[ACAPFM.}aWB=NA pC)E .}aaB=AڷCݽE?H.}bB=ACӉ&E.}bB>>AED6fF5;.}bB=4A C՘E{h.}bB> AѫCY2F .}dB=ACsENT.}dB=;AC|Ev.}e?B=6AWCE=E.}eB=3A&)CKDh,.}fkB>AC DF6.}hB=5AkC̉.}jB=1AC76Eo.}jB=A=C+E .}oB>iA CdC .}p9B=ACDbS.}rB>A`C;.}sB>ACAZt.}sB=AdMCʑDh.}sB=gAbC D~.}uCB=A}VCnEd.}uB=A+C oA9CЮEK.}B=AC{D .}]B=ܼACE5.}7B=zAȜCMnD.}AB=ݱAC͇SE}.}B=ACD*.}B>AOCF.}?B=lA'CE.}IB=AECBeDu.}B=ިACorE/(.}B=ܑA7CE(.}B=ިA\CaE,4.}B>I/ADeDTy.~_B=]A7CyE6.~iB=YACD.~sB=Y[ACԵbE.~'B= A [CX.~;B=|A5CQE.~oB={lACi7.~yB=f)A1LC.~B=6A|C.~B=CACޔDRk.~N=B=wARC>D.~NGB=AC.~_cB=upA{CUDH.~_mB=\ACUEU.~_B=`A7CF.~_B=hDAWC2EO4.~`!B=P@A C׉`EK&.~dwB=ACɽFD\ .~dB=mAC/D$.~iB=AoCA2CޙE5.B= ACE  .B=ACLD.BA+C᭹C?.BACbEp .ģB=<A0CEE.B=zAkC.IB>A%C˓.B>A CC EB.B=A~CEo.B=(AaC-KD/^.B=A~CTE.B=WAC7$E .B>.tAC.B>6oAC^.eB>-nAC'."B=AKClDdd.`B>ACaE .`B>aACʶC.jB=AzCqC .rB=oAC /Dmg.rB>BAнC-.sB>\AqC$E".sB=ACLjDv.sB=ACLjDv.B=ACDjV.B=AaCeD.B>kACoE.B==A}CA)EvU.B=A qCcEj1.B=qAC?F.B=AZCMq.;B=AC6Dh. B= ACl0DЯ.=B=AeCDs.= B=YAC E:.N3B=IAbC&EEsS.N=B=ACκxE .NGB=3A2C6E5._B=6AKCӼE":.aaB=bA|C}/.dwB=A#CϔD.dB=ACgiE.e?B>mAGCCfF?J;.eB=AC#E7M.eB=AC̕E.hB=A>C.lGB=^ACԂ.lB=AC]DHQ.lB=A+C̪^.mB=+A~CәC.v B=ApC"/D].vB=*A9|CD]L.zWB=ACۧEa.zaB=AnC&ES.B=%ACP.B=!ACF.]B=cA3-CC.B=3ACӮw.IB=AC{.B/B9B<AD͕EuC.>CBEV.\CBUCl.`B=A>=Cћ#.bB=˔ACχZDj.dB= ACDT.hB=ASzCШD.hB=ûACңEG.i1B=~-AUC{ DҌw.iB=DA&CDp.m#B=.AUC5CEc.msB=KAC.v B=×AsCӮw.vB=)A]CE&!.B>EYACN.B=HACD.B=9ARC̈́ CI.AB=˟ACkD+0BuB@"eAC<70BB@A'CCE){0TmB?A+CCi0XB?oAWCDԉ0B?AAICFj0 B?oACE ~0B?)ACEI0#2B>DA?Cٰ\EE0#AIC^ET0$B>AACEw0$B>AxC0{F 0$B>؜ACIE80$B>ANCE0%B>A CîgE r0%)B?kA{ClE$|0+B?JgAPC0RB?>@AC 6EP0RB?=ACgC0RB?rA|CAEb]0SKB>AFC0SUB>A1CC=0SB>A<2Cw/F]i0TmB?&ACF't0TwB>@AGC^ZE0TB>ARgCF0TB?BAC\E'*0TB>2AGC5E0UB?`AC{^WEw0UB?:AtC#F N0UB?N]ACAvMC,QD0UB?HACEaN0UB@CAA}Cx%G 0VB?`HACXEl0VB?_ZACVW,ES0V/B?ACUD&S0V9B?p4AKCE-0rB?A8Cy0B?vwAC Em01B?ACo0}B?nA{C8D0B? AC"!E l0B?(AC Dm0B>ACCD0 B?$%ACcE0B>AWC6F iv0WB?IA&CV0"B>A?CKEՇ0B?Q8Ay6C0XB?dACC40B?A:CF0B?ZAtCFC=0B?L A_CS]E0B?kA}nCEO0B?,(ACƒD0B?CLAC׍0>B@uAC510 B@tA/C6*+A20(B@OAD)E_Zn0B@\ATKCIqDR"L0]B@_ALWCCD0gB@_ALWCCD0qB@kA-C'Eߘ0B@tANC7A0#B@[AWCMC,0B?ACg,F0 B?JA 9Ch Dt0 B?>AOC]7E0 B@#A7C^E0B?:A9C 0B?8A~CE0B?2GAC C=^0B?ރACvEl0B?WACwL1E0B??AkC|E0 B?A2C.D 0B?~A.CD5Kv0}B?yATCPED0B?A(CWEC0B?vA7C\F *0B@AlCLLfE}0B?ACbET}>0OB?BAC Eu0B?PACE20 B?AhACp5D>0MB?ɶACgCń0ΓB?ACE,;0кB@$0AC6d*C0sB@AC>D[[0EB? A eCWYE`0!B@ ACNKDm05B?FAahCOEY}0XB?AC@B0B>eA\D F 0B>A(*CD?0B>զA CD4d08B=՚A,\D"C;f0 B>A 7C֢ F50 B>GADEkg0 B>A CCDb0$B>VA GC0$B>}A )CٻE0%B>A0C8D02nB>R|ARDzD0>B=ױAD?%q0BB>0A{DA&E0GB>RACD50L9B=bADE-0LCB=|ADB5E0MB>AaDGF0p%0M B>lADZES40NB>AD+CE:.0NB>AbDE0PB>dAD̻ER+0SUB>:A %CsD0SB>5A ClF0[B="A%ED X0eNB=LA8C`AS0B>XA(CIoE0B>(AHD=EG0YB=A-DyE@0cB=A+DkE0B=AC#>F )0B>ACE0B>.A8PD 9Eh0B>A *CteF2>0B=oA0)D=TDT0B>JAC*E0B?WA9Co0GB?JACaEP70VB?8A%CuJ0E0VB?ALC+FE)0VB?SAC\0ZSB?ACkXDN0rB?AC`0B?0AmC E/$0B?A?CE`T0'B?WA9Co01B?ACQEA0WB?A;CM#0B?/AC+Dzv0B?hAPC_L0OB?AbC7Eb0MB?AC{cENs0WB?HACnEƂ0aB?A4CEf0B?ACÁE0ΉB??A@CZ0ΓB?ACV0NB?A3CE0XB?A=C9sEP5\B==AAvDVFF5\B=gA-SD>\zDI5\yB=f A C5\vB=jACk$EeK5\B=sACp5\B=ACRD@cBiB@AC9 DVlBi B@ӸACMlBiB@eADZCuCuBiB@AKCCCC5BiB@ AC} DBiuB@AC|1hBiÿB@A#CqD;[ BiB@ACirDcBiB@QA͸C_-C[BiB@:ACGB8}BiB@gACGBi"B@0A(CQ_ABi,B@5A(CR AdBi6B@6A3CR:AzBiҡB@CACJD_BiB@ACIBBiB@AC^^D BiiB@BACEDDuBisB@%ACRFBkB@|ACD;CBk+B@AC>BMBk5B@ACC4DV[BkB@AChDBkB@AwCiDS3Bk'B@ACDoBkB@yAC3BeBkB@,AC6DʊBkB@)ACRQD*Bk B@AYC~pDFBkuB@AC1$Dh=BkB@IA޻CO`BkOB@A@CvD3&BkYB@cAC>D9BkcB@ACkU]DBkB@_ACRB#BkB@oAkCBk$B@AИCXkDBk%B@iAFCABk%B@AZCMzCBk&B@~A CD+CZdBkV_B@AC90C9!BkViB@+AC@*iD+kBkVsB@A)C{AR+CHGB?MAM qDoLB<'AvC)0EL B<#7AvCaDGPL*B<&SAvEC&CmqL>B<"dAv\C\CLBLBB<*AvCDL\B4LBJAx֙CE_XALB;4A{vCLLB;[Az!^C EKLB;PAyoiC@EALB;OAy`CuEvLVB;RAyfCLB;A{{&CvME LB;9$AxC[@ULBB;aAzdeCE_܆LB;?A{6FC\Fo~LB;2_AzَD#1F|LB;`AzwC E;LČB;AyQD1> FLĖB;AzCpCZLĠB;AyCC qLľB; AyٵCL%E~LB;fAz=CA)E7cLȐB;+AxC[E;LȚB;Ax CA>DO$B>pA?DnIC- O$&B?NIAMD+CO$D^B?NALUD'E]O$OB?OALD eDiO$PB?OALED&ELUO$RB?PPALDDDO$_fB?OALD">EdTjO$_B?NAL_7D, O$`~B?NOALD#EqtO$zFB>AF{D:GO$B?NALK@D/DO$B?NhALeD&bO$B>A>DD{O%B?BXAP\`C D|O%B?D3APS0CPDՉVO%B?BAPUC+D>O%B?@AAP\C[{C1O%U4B?9APnCC{O O%X^B?3APCZO%XhB?EAPLCIwBc7O%B?>APlC`bO%6B?FAPICtCwKO%@B?LAP3CLO%B??.AP_CRDO%B?;APmCԜB O%B?DAPSCԈ{D*oO%XB?>]APkCBO%bB?=APlCsCumCO%lB?>APhCCC)O%B?=wAPlJCYCO% B?:APpCTDDO%BB?I:AP(APcPC,D=O%B?BmAPTCɾCO%&B??APhCC*&O%B?EAPKLCDO&~B?AOuCؐhEO&B?AO/6C޲ EAO&B?AN_C&EO&B?#QANTD=FO&6B?AO.CC20O&JB?nANb D EO&B?ANΧC~E798O&NB?) AM8DEO&,0B?7 AMjD V!D9tO&,:B?.~AMaD +F|O&0B?0APCpbO&>(B?-AO;CӟADVO&>2B?+ AOCI)Da2O&>ZB?+6AOC-DO&>dB?*SAOXCkCO&>B?=AOzCE O&>B?CAP|CFpCC(O&>B?@AOC2DQmO&@B?AOCO&W(B?1JAP-C'C!O&YB?AO{C؝:E O&YB?-AOJCՉDO&YB?AOCٹ;EM8O&Z B?AOICf%O&Z*B?yAOZ=C٠DnO&B?DAP vC{D O&B?EAP CsDEO&B?>?AOuC˂ O&B?8GAOZCbDO&B?AVZD]1EO'8B=AVXDVEO'BB=pAVW|D\#Eq O'B=.ATIDu{FlĮO'B<:`AY@DCDO'B<`uA>DxBO)B>AQC5E[O)B>AQ1CEO)B>͗AQCEVWO)B>AQC&E9_WO)B>mAQCTcDO)xB= AS'DOF=pO)B>&AStD`FO) B>iBARrYCbFv0O) B>ARBC7EzO) B>YAR|-CD;O) B>^8ARqC&O)0B>AQChE =O):B>AQ,CE'cO)DB>AQ9CYC>O)\B>eAQӚCLDkO)pB>AQCaEiO)B?AQ!VCҐO)B>wAR+0C.D`O)B? AQ(VCӝdDP6O) B>vAQCg=EAqO)+B>AQyCzAEnwO)+B>ARCgEkMO)+B>OAQaC;E6h4O)-B=vASyiD! (EO)4dB>7AQCgD6O)4nB>AQlC/O)4B?>AQ-C D3O)4B>XAQ(CXE\uO)4B>qAQCDO)5B=ڭASYD BE, O)?TB>APXCܧCO)?^B>AQ)=C}EPO)GB>AR+CEO)GB>{gARA(C#En`O)IB=AS:D\D7O)KfB=AUDNC$O)YB?AQ$OC?rD]O)bB>jAQwCD@O)bB>AQKC[FzcO)bB>AQC2DO)c:B>1ARDFgO)cB>AR C*cEqO)cB>nAR CߒlE0O)dB>LeARC>EjO)n4B>wAQXC.E-O)tB=_MAU5DhMF.O)yB>AQ 5CyD$O)~B>}ARHC6EpdO)~B>?AR8&C娰EɽO)B>APXCÈDBO)B?AQ2 CO)B?EAQ&CٓDO)B>nAQ:!CEVðO)B>AQp(CsDnO)B>AQAfCFEO)B>AQ<3CB$O)B>AQV CbNO)B>#ARҿDFyO)B>!^ARDMF+5O)B? AQ&.CC^&O)B=OAS~CD#\CUO*B?.JAP1C͵ D/O*B?/APCЅDaO*B?+APC\C8NO*4B?3APCxC"O*B?0AP8CޥC@ۓO*0B?AQ2 CO*0B?2APCУBHO*0B?2APC:QCO*1B?3APCDWCO*1B?3nAP\C>C:O*1B?4APCЭO*2 B?3APCC>O*:B?4APRCĻB/8O*:B?4APC_+C1{^O*;bB?3APCVCf.O*;B?3APCڽD ~O*<*B?3APəCDeSO*AB?4APCCLO*U4B?4APCED O*U>B?1#APRC֌zC_ŦO*UfB?0APCAYO*UpB?1KAP(C^B}O*W(B?8APrC̘CO iO*WB?:APmCү|B5;O*YB?0XAPCNDO*Y&B?0APFCJD5:O*Y0B?1uAP1CԐCɧ?O*YbB?2+APC"DtO*YB? AQCEBpO*YB?AQ!CիEO*YB?AQCmD"sO*YB?AQ|CVSD3[O*YB?PAQ#`CӉ{AR+C~B>{AR+C!B=qATDMN$B=qATDMNDB<AD EB<AD FB<AD 9 B<AD :B<AD P %B>A>DwP >B>A>DwP@0 B>A>DwP`B>A>vD٬)AP`)B>cA?DʕqP`;B? JAEڹD9@W<P``B>A>DwP`8B>A>cDقP@^B= YA6i$D0B=BA6UrD D'[B= A6kDEIB= A5D_DMB=A5.D6D<1B=A6D(B=A7GDKuB<3A6FFD50D}=6B<$A6@CD&`EUa!UB=%A3GDq}qB=A8 DBDDڽiB="A8XUD}E>\jB=A8 D uB= A4CIEA B= A58DD B=A4C߼EZB=^A7DzDBM/B=A6VD;DkM0B=A6D(N B=kA6t Dz PB="A8{D9DPB=A8 DPB=-A4CEPB=A3D%=E oPB=A4'D$F=PB=A4!CEy PB=A4_DK"E:GQ B=A6njDADR̟Q B=eA6n3DWDa)QB=A6nDDRjQB=A6D`D^QB=zA56D ?DQB= A5,DDUIQB=A6%(DCgYQB= A5kDDvQB=A5gDE[[QB= A5TDrQBB=;A6[eDQCB=pA6 DCSbB= A6eiD`5D;TbB=WA6]cD [D>u3B= `A6j D8Ccίu4B=dA5HDfB=LA6DkD8lB=9A5DCM4B=FACuEB>)}A CٕCS]B>WAWCkB=PAuQCDFB=FANCDVCB>1ASCE$kBB=ACGD85CB=|ACELDB=AACOpE,WMB=_AFCC~B=A6_CEpE"~B=ACpLE[`B=A$CGE{B=ACdE B=pAZCA5YB>UACC̱&B=aACʫD s)B=AC̼E(o)B=A1C@D;)B=AC˒?Df7B=A9C9B=AAC˖9B=A?ICBB@B=dAZ|CBBB>*MACMDݜBDB>=ACGEyLBB>0^ACˁDfpKB=tAZC@GAlMKB>$AC璿F,/KB=A{CE;b^B= AͿC=^B=ACDj^B=]A]CʑFCl^B=߮ACWEa^B=ACuD\^B=jAoCxE*)_{B=jAZCE_B=AkCD-2_B=A8CMB7_B=UA{C]EO_B=A{;C8D.-_B=*AC3CҜDF_B>8>ACۃ;Ef_B=tAZC@GAlM_B=AC2[D_B=A C2'Ds4p`B>)ACRAI`B>6ACщTD`CB=8AwCeChv`B=(AU=CеDI`B=eAeCLEh`B=uA2CqEJE'`B=ApCVEP`B=ACA>atB>)ACRAIaB=AnCC}bB=Ag?CDnbGB>??A߯CiE{bB=ACnCbB=AڢCЃ"E" BME{g09BEF^0^B=ACE*0^B={{AZCˀoD‰0^B=qAECʂC0^B=ACGEGM0^B=y~ACHE4}0_BDEVE @BJVE!~@2{B@:DB=A.2XD+uwE @:EBEKG@GZB=A.iD$ D8@GB&AKD#F(0q9B=lALgD;DlCB=AMmD,6EDB=AMrD7CDEB=AMWXD5sgEUjB=KcAL D2F B>LAK_DE5qB>DALBD.EB>jAKT^DIE?>+B=%EAMD7/Es7B=,&AMD/wFSB>drAKx{CFB=ثAMD0F B>"AKٮD)EB=jALD=NDB=AMK@D2~DFVB=ALD,EcX;B>R AKCuF58#B=ETALD9'Di&B=ҫAM40DC:RF 0}B=.AN(D4*FM*/2B=[]ALlD>*Ee 2B=EvALD;XE2B=[_ALbD=Eo3hB=AMHD0 ':B> ALYD%FEi:B>ALaID'E*T:B=#AMqDmAKD ]D%]JB=fAM D2cFWJB=c[ALD<6EMOB=jALD@D xOB=GALݍD:60E]OB=LALD?,EPOB=aALѣD;8El=^_B=AM7D56EyH^`B=/AM_DEnEr_B=ҞAM40DAFO_B=AM=DhFbB=#AM@D>B- B>AQ`ICE  B>AQ[OCԙDg] B?AQSC;EgHB?@APfC8AB?AQ?TCյD)%B?HmAPCbCe %B?EAQCvC[%B?IAPC-q%B?EAPC=CD*%B?>AQCؘ%CS%B?8AQ.CD0%B?>AQCC)%B>AQ[OCԙDg])4B>AQcC DDZh3CB?AQHCAQhC{UB?HxAP0C]D4 ]zB?EZAP*CDB{zB?;%AQEC"D[@B?*AQ7CԚB?DAPC JD=B?@APgC@AyAB?FAQ,C?D,B??AQnCԥ B>qAR*CK B>rAQ]C/AE B>AQCDC LB>a`ARCE- MB>a`ARCE- NB>qXARv`Cw=Eih OB>֡AQvC5D=^^ PB>AQCdE QB>AQ5CN8C RB>AQCۀDC`_ SB>AQCDs TB>AQ|C޿B  UB>=AQLCREI  VB>EAQ?CgE$nU XB>РAQy2CڶEJ ZB>zAQ&CD䰗 [B>yAQ$C4nDƆ B>ARCD{ $bB>PAQqCL>D )4B>eAQneCԳ5DgJ .,B>ͺAQ|CݐmE5 /B>[ARCtE1t /B>UARC9D  1B>~AR[&C7E 1B>nNARC鹥D4n 2DB> ARTCE ;)B>dAQ~C޻D. ;*B>ӴAQvCD ;+B>ZAQq-CֶE z ;,B>=AQCasDo ;-B>AQ*C)D ;.B> AQ.CsE6 / ;2B>AR/C&Ed ;3B> AQ.CsE6 / ;4B>AQwCgND ;6B>"AR,{CE JB>AR$cCEL, OB>(AQiCրD67 ]B>]A?D7A/ aB>AQlrCE aB>eAQneCԳ5DgJ aB>AQ`Cَ3EH: b B> AQCpE X 5B>PAR,CE/ 6B>tARuC F z0B>qA=DF/0?B=A@C4E00@B>aAA6CGE`0B=wA=D..DJ0B>A>DIC/0B>ABΤCH0GB>ABCiEm0B>zAAlCɒE0B>{AA PCE,0B> AAKCE0B=!A>„C|gD#0+B=IADEk'f0 B=JA>C "Ei0 B= A?C3SE ڿ0B>aABCF90B>ABCEH04B>?AB$CE07B>[@ABC妖DՒ0NB=kA=QD20"*B>J'AB_C9ERwABaC_Dw0#^B>PABPC07D'T0&B=zA>EC6E_0(B>AA &CE0(B=A@"C'm0)B>]A?D7A/00B=4AkD E)02TB=A>ۯC04B>A>8DxR0:IB=A@CpEg0:JB=A@CEKs0:MB=A@CpEg0:NB=A@C>ER0:OB=lA?CCQD0:B>A>DƮC<0:B>QA>DߒCf0:B>A>D0AB>lABaCA\FU0AB>ABLC3F0B/B>%AACE50B1B> +AAC̜DҒ0B2B=A=D(0B3B=A=jD$E" 0B9B=A<6DyE0B:B=-A< DmD̪i0CB>AB6DCEv0CB>9AC!C(EE4C0CB>oABCiE!%0CB>fABCDq\0CB>i`AB\CG*E# 0CB>[/ABC*xD0CB>R]ABCD˺G0CB>^ABCoDt#0CB>VABC70CB>_ABCoEK0FB>tA?Dj8C0FB>0A?`DBj0FB>rA?DåCX0GB>TABeCS&C0HgB=oA?0'CE110HhB=ΥA>ՋCmE0HiB=A@CF+Fp0IB>fABѕC炏0KqB>LABCcaE00KsB>AAxCDE`sP0KtB>oDACCzDTq0KuB=/A>1CTE0Mi`AB\CG*E# 0]B> A?DC;90^B=sA@QC`Df0_B=A=jD$E" 0bB>0ABfCE~,0b B>)AACE0bB>7AAt,C0!0bB>~AAC%E00yB>A>}DثA07B>7@AACBE$0B>A>8DxR0 B=A>z6DCEF0 B=A>..DJ0B=&A?_CځEw0B=WA?7C(E0B=AGARDE$@B>$XARC?D#@$B=oAS\D%E,@.B=9APSD-4E @/B==|AQBCXJF.mz@0B=HAR~ARCX7AR*CRhE@:B=KARyDmEy)@:B=<3AQDUEE @AB=SAS_UD&E{@AB=LASD56E]x@AB=AS*DEM@EB=ASDVE@EB=0ASjD EF@EB=AS}yDE@EGB=|ASU"D LE@EHB=dtASKDkE@EIB=^AARD6Ę@FmB=OASD h%C@JB= ARVCܲEVB@AC,E4q;g4 BA(iA^Cg4BA0AC,>C)Ig4BA+$AC+D3Lg4"BA0AC.eAUg4BBA(A C">C5g5 BA'AC(&CכgKRBA#_AŒC*]Cg[BA%A@C CTg[BA0AC.eAUg[BA0AC/NAg[.BA,A=C)]Dg[2BA.AC,D"g[RBA#_AŒC*]Cg[BA(AsC)Pg\0BA$A2BEFkgr`BA$yA C!!grBA*ANC-$FC6gBA'2ALC-OE~g>BA,A=C)]Dg@BA0AZC29gJBAAC(gpBA0mA7C1NnBhKpB@ANCmD2ywi2B@AC1DN"i30B@A(BEIE}i3DB@|5Ac+C<i3B@\ANCNDHiKB@,AEC:SC iKB@ACBiMB@vACcEq,iYB@~sAzC0iZ@B@{"ASC6;DziZhB@~oA8>CF2DfUiqB@CA1C7.irB@qA C1itB@ A(CyE;[.iB@|WAc C9g}Dڴi}B@A CFbiTB@vAPC9MiB@AaC4NViB@AC5EwiB@ACl2B@|A{C3(sl3B@|}A{C7Xl3B@IAC8Fl3>B@HA9bBK*E^[l3B@}AԌC)EIl3B@|A҅BxEB.l3B@iqAGş FOl3B@ARACFhl3B@AWCADW:l4&B@6ACEKl4'B@>jA*Co+E>el4(B@L[AQ ÐFCl4)B@AACF:l4FB@7SA9CaFl4GB@I'AiFl4VB@tQAF Zl4[B@oA\C tF4l5B@vA!Cy~F*l5B@NAJCF l[8B@(FAãFEAl[9B@AC\Fwl[VB@nACASDUl[WB@-AKBFfl[fB@JA[ C}agEk3l[hB@tA4CF_dl[jB@bA7BEl[B@qAC8El[B@~ABÐFPl\B@ATB~Fkl\B@AB: Fl\3B@ATC:XEFl\4B@pAXC:3l\B@A=?MgF3l\B@~ABњFkl\B@r~AiB:FFdCl\B@FAFC7FMl\B@/A9BF~l\B@AC_Fxl\B@5A$Bo^Esl\B@]A~CgBFl\B@zTAFC5 EERl\B@lAVQCE0el\B@AC8El\B@ AMBE/l]B@ACg5CRlB@*A{CI}Fz4lB@WAC7E~1dlB@ACE<7l%B@A6qCvТE lB@yADC85F $lGB@AAC=8E lIB@E*A&FhIlvB@Ae/BhErlxB@iAQAWEѺlB@pAC@D``l-B@oAC8tEmlB@{TAC&DV>MlB@paA#C1FlB@u,ADCأFlB@h%A{}DԨFoplB@$A7CE{АlVB@,(A.4C#ElYB@G[AQCFD1lB@}AHBi>E>lyB@)B@/ACDpC愩qVB@ʟACDϔDz}qWB@4ACROKCq_B@oA@C@vACcqbB@A CXBoqfB@ A߃C9C@qlB@)A dC[BqpB@ʒAC99DJrB@5AC,C YrB@aAׂCZ"sUB@A۳C?C/t$B@A CDaCF<t2B@&A2C8tNB@AACACٜfB@˻A1CFgC7mB@AC8vnB@AC.CJ`}oB@AڱBɥDDpB@CAڣCCy\BGqB@aAׂCZ"rB@"AnC@D~D 'vB@ACDk2CLB@bA 1CY6B@)A dC[BB@A C]CB@aAׂCZ"B@KAtB1Ei$'}B@ACWAA 'B@ACj0D(B@خAC`3uJB@AaCqD%cJB@0AXCvmA*AKB@pAPCDN8KB@HA CbsDKB@ۺAC\EKB@hAC^KC&KB@ACpcCL B@AC3DdL B@QA%C}D L)B@KAlCD/dM1B@ACZ9CgIqB@EACqDfqB@8ANC_mD+h/qB@Ab)CzRB(rB@AN=C+D,rB@#ACsDbrB@ ACo߹DrB@ACBDϋSsB@AC`DsB@ACDcsB@%Ay0C2xCc!s8B@ACws9B@AC_B@Aw C`qB]B@>ACcTBB@EAChIC[.B@AUCBB@ACSpB@ACHD7)B@AuCvD*B@A7COD'8B@*ACFDpgJ_B@"AzC5@C}2J`B@JACAE~DWoJ{B@ACED$cKB@ɳAC"D4qVB@.AZC=gCqpB@CACal2D HrB@ȱAC> B7rB@A,C,|tB@ʘACHoCrB@ɇA=C*C+-B@ArCCB@ARC>KC 'xB@ AC/& D'B@AC]ثD@JpB@VACHIJqB@kA1CDTJB@AC&[D"JB@A8C;DJB@AQClCZfJB@KAChr&DqK!B@+AOC& EK"B@ ACFm`Dm{KXB@AC)CJKB@*AWC6M2B@ATCBM7B@AԏC:!C7MB@AC$ D/MB@$AC&DqB@A"CFZDzqB@AٶC2@DEŎqB@ACJqB@AACwLB)M qB@NA6CY`DjTJqB@AC,qrB@AtC]Dpr1B@A^C r2B@ACCJ\Ce86rhB@AC,qsB@ACL|!D s B@ACD[D9sB@ACJsB@A\C}8CnsB@ACn[CsB@AhC;DfAs8B@1ASC DC#s9B@AOC\;A@psHB@(AڎCDǽft2B@AեCIOD%t6B@AՀC=DfOt7B@,A+C;UC4ft9B@AOC=BBػtBB@ATCBtFB@AӕC; tGB@AӕC; B@A_C\)B@kA1CDTB@ACF'D5 B@^ACKbDPB@AC:CPB@ACJB@ACF.DB@A]!CBB@ACawDB@AőC)!DrPB@6ACpB;)B@ACk"CÑ/B@AC!CXB@AքC:DC=B@LA{CB<2B@ACzȅE2B@AxBEM2B@AXCGEo3>B@NACeEN?3CB@,Aj}C[E3DB@ACsPE{t3B@~AC,R5,B@AB~EV64B@JAC5E]K]B@:AC2/FD7YB@KACQEJHqB@A:Ci'E6qB@?AaCErB@GA)CDD<r B@ACEir)B@AC]JE7rlB@rACdD[prB@NAC=kfD'YrB@ANC()ENrB@jA4CxE&rB@A CErB@mACFrB@AC=Q:D}rB@ACJxrB@hACVE-rB@A CEUsOB@ACRDmsyB@EAPCJ>5sB@`A C P'DsB@-AC&7E5tB@MA3[C:D9ÁcB@A C2CqÁ}B@AC7lD=ÄSB@AUCIhYD^>ØB@ACQlDѐØB@A3C:DDØB@sACBD0TAÙB@AC'9EfÙB@DACIÙB@A%DCoE ÙB@ACÿB@rAޅCHYD#'8B@ȳA*C:8J_B@ACHqCtKB@nACQL>B@ȸACQAwCA t>BBBBBBBBBBBC7De >BB<@Aw0)CtDy >B<=Aw*CWD]>B<<Aw!CD>BFAw'CKDBCDSpB<;Aw'C CZkB<=Aw5cCD>BBBBBBBBC#fBACDYFiB=A`CˊqDYFjB=AI^CL|C:!YFlB=A{C{YF{B=OA\C?DB{dYFB=9ACTE>VYFB=`AD)BFRYFB> A C@$D4 CYFB=OA\C?DB{dYFB>AC(DKYFB=AC͍TCMיYFB=HA0CDDYFB> ACsDjTYFB>ACğoAYFB=6AXC2DYG'B=AcCɉ@YIpB>A3CDYIB=ACCM͝YNB=ACbCM:YmqB=HPACܨ3E!YmsB=KACDYmzB=iABDOFcYmB=OA\C?DB{dYmB=HAGChE_ YmB=AjIFzYmB>A Cp!YmB=.ACǠHBYmB>vYn*B>ACjYn-B==ACðB`Yn7B=A{C{YpB=ACbCM:Y|B=ANXDRAMYB=DAuC[YB=RA )CCYb.YB=SA *C;#YB=xA^CI:D-YB=ADZFFcEYB=9AODQ?BYB=څATCiB.U]YB=]A]CD:YB=IAC܎7EYB=ALC_YB> AIC Dh)YB=(ANDQ7YB=6AXC2DYB=7A?D [E3YB> ACk@JYB==ACðB`YB=AD FkYGB=qACDSYB=A{C{YB=mA'C^AYB=xACWDSBZBB<|A&{D{#EO~ZBB< A&ƓDZBBEYܖZBBZFB<2A!DE4ZFBACɬCUZIBZiB<A%DE^ZiB<*A(D]4FZmhBO~ABC=^BnB=+A<>DEC^BqB=A@CD\w^BsB=~A;DpqE*^BuB=[ A:֐D PE7^BvB=A=DPpE6V^BwB=A?4CE^^BxB>6AACEx^BB>?AB ;C+E->(^BB> JAA]@CDE+У^BB=A<\DE^BB>)AA/C%E^BB=A>$CF4'^CGB=͐A>]CCt^C[B=xA;%DWbEУ^C`B=A<|D2F;F^CeB=A@@)CEt^CfB=|A;DEx~^CgB=A@16CEO^ChB=ֆA?CE ^DB=A@CE^D-B=M]A:YDEp^DfB=AAAmCxF7^EB=eAA6CR^i~B=A;DEEO^iB=A<zDHEu^iB=R;A:"D gE^iB=A?mC0EwQ^iB>AAyCsE̢^iB>GABNC!uE# ^iB>A@CEދ'^iB=9A;AACA6EdT^jWB=JA=DnEl-^jkB=e>A;6D3^jpB=mA;ODuF#^juB=A@Q;C^E4^jvB=hA;CGDDӑH^jwB=A?tCYD#^jxB=bA>k8DEI^jyB>O~ABC=^kB>AAxCLEP^k&B=A@CmDC^k=B=AA:D tD8^kMB=>A@jC8R^k\B=A=aDEA^lB=܅A?TvCEݚ^lB>O~ABC=^lB=A>ծCbEϣN^lB=A@2C^B=/A<(+DdEH^B=A@C3E ^B=\RA:+DE^B=OA=gDE^B=ɬA>CE[^B>(AAuC~EgN^B>%AAC,F^B=3AA AAQCE#^B=A>0D2^gB=A>\DE4+^{B=XA:DThE%A^B=A?C"CO ^B=ZA;DEiB^B=A>CjEv;^B=A=AD Ei^6B=eA@CEtE%^]B=AAgCQUDGABNC!uE# ^B=ҊA>#CE0^B=xA@C{D-^B=XVA;3DD!E^ B=A>D!MDq^^ߩB>O~ABC=fB=A5DD'E+fBjBBD"gD[G:fizB<GA7tDYExfi{BA4SD!qEG?gjMB= A4D ^D gjTB=AA7pD gjUB=hA7D lEdgjVB=OA-[D-pF gjXB=A.'D"CgjZB=hA7D lEdgjcB=A5Y DRDtxgjiB=6A1ZD(E{gjjB=>A4 DE~=gjpB=H~A:DTE gjzBD D_gB=A8!dD ?D֫gB=%A89D 4EBgB= A6lEWgB=A6pD!CB{ BTbEF(Bu ח~BTfH~rBʆSC%X xNJBTHIC yyBTF/C ܆BTDeyB1uECo ܋LBTĦCSCB' ܜ2BTK~8Cu5 BTvF#tB F- BTIC1E$1 'BTHdBߦD[U ,GBT0BKBȴ /BTF=C$E 04BTsHA?NC 3tBTsHA?NC 5!BTHBeE" 7BTF'A{F w 8tBTBA EH9; :uBTER BdEL ;BTsHABRC <&BTxC@ BCҖ <'BTD BksE <:BTGB{ED 2 <;BTH=hB0D& MBTgCoTC5D NBT`HuBEUMx VBTI B"N WBTrHڄA0»C* XBTBBFEhEe ZBTUK^CFE \1BTcHCBEn{ ]BT0GB8EDH _BTBIAEz aBTBGGuBpE{ b)BTsHA?NC c7BTD1BE fBToH+BaDP^ gBTCMzBE3 tBT|D BdE/ u"BTmI C*ME xgBTaFBȯF yyBTHtBġD zgBTCBAED ~BTJ+CD~FY< BTF=B8R BTC.|B$E`{> ABTHNBd$Eէ BTApACu BTCBE3{f 9BTsHA?NC FBTCBjB1SD[ GBTDgB> E1 ZBTHCU]E\ [BTVHG:B_CU5 BTĦCSCB' BT EMB Clb 2BT#IhCE=R ԴBTцBBD5 RBTaIߥC*ECe| BTĦCSCB' BTCAݷ:D nBTK.CE}E_ lP|BTKYCV lBTĦCSCB'BT$MC`q_C!BT$MC`q_C9BTNC|AZ9|hBTNC|AZ9BTNC|AZBTNCX%BMB|GBJ^ЍBMBWB!B_nKqBD p!B_KB1TE*!B_KBu ErD!B_HL_BOEw_!B_UBU!B_ءRbTBID\!B_TB6,E!B_6WwBHDn,!B_ T B/C !$B_SêBED!'B_QBV՛E!Y!(B_V? NEVq!)B_,R4ALF!*B_ÍKv:BCP!@B_ؘMB?*E<*!GB_O4yBcaES2! B_޹RBlCf !zB_̸LsBZDgJ!{B_M*B^8Dݼ!B_RwB=\D<!B_R mBp!!"B_dL1BHE!#B_uK%BB'DM}!$B_NB1Eʝ!B_ތNqB>JD!B_N:~BMEB!B_֌PRBElV!B_NpBE!B_̹MwB|]E!B__LBDѣ!]B_%S[Bi_E !^B_ OBEP!_B_QRۍB<*Fp!B_ԟKB`.*Dw!B_KiADb!B_K]ANCm!B_K'AC!B_KBD-!B_Q̪B(QDpm!B_JPB"E*\!B_nP*BBBߟ!B_|L;B B\L!B_iLBB>!B_K:BQDMWL!FB_ĐN#Bc[C!B_ޚT!B_܀T~BEH§!FB_͙MBBÚCQ!^B_8MAB;D!_B_̮MIB%Ch(x!gB_MUHB{Cf!hB_\LЗBBPEM_!B_$LYGB*CXw4!eB_أNBEov!B_ַOEBE:rM!B_ OBVE#!jB_XB~+A!jkPB_,RBy!jB_dVBGWjEg<!jB_Uh>B,EҬ!jB_݄U%BnEE!jB_UB`ApBD@i ?B`bAgBgE"B`רAB~E7>Ba3-AnB-aE^BaA:ByFכB`3A2E{UB`AcdAUEe3B`A|oBF=EBR5B`ACDF#BaApANE1 B`cABuDޫ!B`AgB\uEo[>B`AUB7D֯?B`A.B,}rENr@B`AYADDy(B`9ALBM3Dc(B`FAKB XD[(B`?A٫BD)BaA4BB2 ENz*'B`qA=TB@DLv*(B`A BEE.[+B`ABreE|+B`ABQ+B`AyB$D_2TB`AFADX }4Ba" AgBaWEm?s4B`ATA~D^$4B`aABDܞ4B`ASA"4B`A"B9D%B]LA'A#DB]HA'BE*B\oA)B^FPB]ITA' @HEBB\A(B)FfCB\~A'BFi@GB\LnA)AB DdB\A*A)B~E$5B]?YA'ZBFcLB]A'R{BtͲC~B]>A&BMB\A(ZBWcFE B\? A)"D!B\>A)%+0Da{B]DA'BDB\݂A( KB_B\A'YB EY`B] A'BqF B\=A)1 CeMB]<A'B}<Du6B]$4A'/B^B\*A'8B/ E3B]IA'hAbbcE- B] A'vCBvD=B B\*A(SHB2E³ B\A(^BOFJ[ B\uA(OBE B\SA)Q]BVE B]"A'dW)E * wB]= A')B`E xB]/,A'B;Ds0 yB]AA'EB]4Ex߀ B\A(B/E" B])A'|BG{E  1B\>A)8kD 2B\?A) .D/ 3B\?oA)@:D_ B\dA(B%EB\ A(BF#B\A(޴BFW!B\UA)9B2;[E"B\BA)\ADm#B\\A)$B/EպB\>{A)Ѐ?WLD3B\=A)waAtB]hA'mBXB\FA(+}BEFB]NA'BEB\?A)A!DZ{B\J'A)IB}pDO=B\D~A)f3B;5=DeB\A'ہB8EB\S4A)8`BJcDnB\KcA)KtB4E9B\bA)!BJS.EfCB\~A(BlDν2B\?A)/ GkDr(B]LdA'.AEB]KpA'YA7E#B\A(BEB\vA).BkEȘ]B\oA)@-*D/uB\?vA)h@QDTk2B\?tA)@SÉC3B]SA'cB8XDH_4B\AA)@]O5B\?A) KDEB]\A'xY#EB]VA'7A$%DBmbB]LA'`[ADjC$~B]dA&ۭA\AF B]A&^BIDB]5A&UA3HSDgZB]IA&^AwDJB]9A&BE~-B]cA'N’(E8B]zA'@EG%'B]zA&#A^§DdUKB]\A&5@B]A&EAOB9\B]fA&]@[rDjB]A&A HB]cWA'*bE"B]_OA&ŖpEB]A&NL@ʓBTB]vA'QA'[@B}B]A&EAD-B]#A&nUAPEfB]EA&g@K3D;EB]A&AE'B]wA'PA aEYQ;B]]A'%qEtB]A&{E0'$B] A&TGB EB]FA&rB:E^B]}A'y &gDŚB]aA'4dToEY8zB]8A'B~B_;-{B]BA']B#@E[`B]cA&:At3DJ%B]A&AD#L&B]A&oAO{Dd͒'B]A&kA5RDy.8B][A&Pm"E9B]uA&jA`H:B]vfA& ..DFAB]wxA'-B&EҝBB]yA&AECB]@A&ݒDFB]~0A'@'WB]A&@уDn'YB]A&"@D 1a'dB]zA'qE 'eB]m4A'=OEk'fB]zA&A ES'B]A&RAD`'B]#A&Je?7D'B]A&X@ Bۅ+B]h1A&2D+B]]A&²A:D ,DB]{vA&KB3eDls,EB]A&g@w.TB]\A&5@.B]A&(A}zD=`?/B]YpA&B@?D@I/ B]lA'HIE`/!B]PrA&BVE[0[B]A&cQA*1;B]{>A&&@VD1>B]A&<9ApCMq1?B]uA&BA#*D61XB]{ A&W@74Df1B]jA'\A6ZBO1B]|*A&jAX2B]:A'B'WE'2B]A&@D32B]A'@ LD%a32B]A&;@C:2B]A&JL@$9DZ2B]D BabA )B_E"}BaaAm`AFZBaUAAXjDzBaP2A;A2:E0Ba{A6—FGBaD ~BaTA }Bj1EmBaA AfBaA BTq^DjBa5AB.$C"Ba=AqA@Ba{eA{QAfF BaA XBrLC8vBaAB F5ABaCAB !D2BaSAAE<Ba?ATB)D4Ba#A4BqC$BaA =BC5B]jA'YAĜ8~BB[:A2AB!8~CB[:A2AB!8B[:A2AB!8B[:A2AB!8cB[:A2AB!RBayA ELRz;BaOA G CCW7TRz/BC= v B`_A"WHBD vҗB`~A!pBfDDҔ. vpB`A"BAAǒP vB`qA! B|(aDC 2Ba$ASQBG: 2RBayA&BZ 2WEB`A!B 2WGB`nA!7B-V 2X_B`+A AZD78 2]5B`A hBC 2]gB`:A AYD~ 2]hB`+A AZD78 2eB`A .BE)z 2oB`A!B 2BaABTRAF r 2ǻBaA4BA2MC'+E6BotA(@)FN7BoA0/C6qEڊ}ABo]A'BXAS3.BpyA2C#UD[X 3.Bph1A2HC!m19>B})A'CܻE+`i19>B}GA'y'BBSEeRN19>B}A'oBkD19>B}A-9BD19>B}A*0BDs19>B}A(OC\NE019>B}eA-4–2UF 19>B}wA#"BFj\C19>B}A"WC+19>B}~A"B)19>B}A'CwEE19>B}A'BB}A'(-Bブ19>B}hCA$C߾19>B}A*0BDs19?B} A#gƠ%Fk19?B}A'C D19?B}7A(6FCDEC19?B}A'բCwE\:19?B}QA'B]EF<19?B}OA(?3BE19?B}"A&WB5rFj19?@B}_A'BD19?AB}8A'CpEW"19?BB}A"B.BnF`119?CB}A+1BE=19?KB}A',BEL^19?QB}A"BZE19?]B}A'BQEs19?^B}A'vB$(E %19?`B}kA'BVD2%19?eB}A#B]F'F19?gB}^A#śEh19?hB}{A&LXB bE x19?iB}A"oBHE"L19?mB}A'=BsCwp[19?nB}A(A&Dl19?pB}A%BF`{19?uB}\A'ʉBDEi19?vB}A'yBqE 3I19?wB}A';ByDd-19?xB}ZA"kBy19?yB}!A'IBa+E Y19?|B}A(3xC"EH19?~B}A'@|B&J19?B}XA'9B-3GD\19?B}A&A 19?B}A'HC D{/=19?B}kA)CsJF 19?B}A"Z@}YE[19?B}A%AZ19?B}6A'rCJ%Db19?B}pA#sCP7E19?B}yA-AnE*19?B}}A.“9Fő19?B}a!A#?%^Cv&19?B}A) B%F 19?B}A,}>BBE4 19?B}/A(eCE19?B}A'gB:E19?B}OA(JB$UC19?B}A'!C!VDm-19?B}A"<>Z{/F 19@B}A'yUC |XEL=19@B}A'AB|HEP19@B}A#BgFv019@B}̷A#c@ǂmE219@$B}zA#cBXEq19@,B}`A#BK:OF{19@.B}A+GBpE19@BB}2A) CB_E"19@IB}A(PC;C\Ej=19@JB}A'mB] E"119@MB}qAA#sXBs= E|19@NB}A'J$B4D*19@QB}A)GBZ19@^B}A"V-HBN19@dB}^A'njB؁E=619@fB}-A"`A;E'19@gB}A"DBOCZ19@zB}A':B4 D~A19@{B}A'A]Ex19@B}SA$ҍB Cw19@B}A+BE P19@B}A'BDз@19@B}}A"BREM19@B}'A-+Bt19@B}A, BDwl19@B}A"CSE[Y19@B}JA'C%CK19@B}A"$C4zE]19@B}A(`C]7_E D19@B}A+77B519@B}A'eBpA&$19@B}A'FBoCD)N19@B}lA"ZCiEgF19BiB}A+ XBxF19BjB}ނA.ݜ@LC6{19BoB}qA,2B0F>l19BpB}A6HNBA19BqB}A'5C6IF19BB}A*B19BB}A'2BΒD0y19BB}=A'׭BxiD$19BB}A3\AУ19BB}HA'B@ 19BB}A.C]!K19BB}|A/?j19BB}۶A/c\E19BB}_A/@AD19BB}zA.D v19CB}A1{`UhF0A19CB}.A/cC[3E:19CCB}A.[ȿ@D19CEB}.A.m@HE:n19CFB}A.[ȿ@D19CGB}8A.ND19CaB}A-.cBm]F9 19CbB}׫A2!+>M#F19CvB}ށA/GX@DX19CwB} A/ij!E=19CxB}A-lAqF19CB}A/M0TEm19B}A$A%332e>B}l6A$\C-pDC2e>B}|A%B}\A$AgE"12e>B}irA%BEI2e>B}iA"}BCiGu2e>B}ldA%BEp2e>B}A"jB 2e>B}{A&BB=2e>B}tA%9?BPEx2e?B}`YA%{9BE,2e?B}gA#QC2e?B}iA$]BE׽2e?B}vtA%OBWE$ .2e?7B}WA%CtDϰ2e?>B}pA%wC bD2e??B}u9A%C"D72e?QB}A"1B0Es2e?XB}hA$CDr2e?ZB}tA%;CDk2e?\B}oA%v@ChDƘ2e?aB}mA%XCC EM2e?cB}}A& B\2e?fB}kA$C+2Be2e?iB}kA#5BeFSE2e?sB}gA%+CoDh2e?tB}eA%BME2e?}B}yA%RBAE-2e?B} A&BWC\2e?B}k~A#VC 6ME0@2e?B}rbA#BǥE42e?B}^A$:PFL.2e?B}k A%ZC VDA2e?B}eA%CT{2e@ B}hA%CyD-@2e@B}tA%BCECLI2e@#B}YA$ԘC ,E?2e@$B}^A$ 3@+Ft2e@%B}lA$[yC+5IC*J2e@,B}XfA$UAKD)2e@1B}iA%r~CHCW2e@2B}g|A$CckfE?2e@5B}aEJ3. LBqA3C8Fs3. MBpA2[COHcEL\3. BpEA1CW3.BrA3C}E 3.Bq^A3\B;D3.Br0wA4>9BFO3.BrA3C3.BrFA4+BC~w3.+Br|A4C D 3.BrLA4HC'3.VBpA1CAEFL3.VBp-A1CBA E&3.V BpyA2״CMwE3.VBpA1C<REy3.V=Bp=A2`CDZq3.V?BpA2~C+sE(3.V@BptA2fB-EB3.VABpA1BC;E(߰3.V[BpbgA2rpC(F m3.VdBpA2~C4YBs>A4CI-P4YBrA4 C]F4YBrP.A4fgC"Ew*@4YBrPA4DC6F-4YBsbA5C~C4YBsvA5CL F4YBqQA3ͧC"VD484YBrA52C7FQ4YBsVA5CslEVs4YBsA51CJEdF{o4YBsWA5CpDx4Y+BsCA5CS D4Y,Bs A4lBjF"4Y5Bs 2A4C'DB4YLBsA6jC_ .F`4Y]BsA4?CFc?|4Y^BsWA5ChE4YaBrA4CZFH4Y|BrA4{A4CI-P4YBsp A6gC4YBsnA6TCEY64YBs%A5CCF4YBrRrA4xCYDF]?4YBrXiA49C.ODQ4Y+Br"A4CFKW4Y.Bs\A5Cy{Dyt4Y]BslA6@}CE.m35bBo.oA'*UBCΎ5 BoNA' C5BnlA&hBE05Bo wA&NC"K4Eh5Bn{A&BJ=5Bn:A&B~5BoA&BE\5kBnA&4CD5lBo[A&C#`E5tBo 2A'*Bx E@5yBo~A&-BbE)5BnA&3CsD~5BnLA&B5A''BFc6BouA'4A$\DT7w6)BoA'|AjDA'{hE6cBoPOA'H'Ef6kBocA)@C6nBo\A'QB"DPJ6qBoA(lA L!E6{BoA)R#AZA6BouA'4A$\DT7w6Boe+A'B^Di6BoCA'B!D 6BoUA';B׍6BoUA';B׍6BoUA';B׍6BoA)A^56Bo~aA( AELC6BoZA'8B%6 .Bo5A(d@n/DH86 /BoA(lA L!E6 BoXA*?@dD$Y6 aBowA* 7L6 iBoAgA'Aj@FEq6 8BoA*beD я6 Bo+ A'#kC 4E6BoSA)AFE6Bo9A*wa@#6Bo9A*wa@#6Bo9A*wa@#6BoPA'cBE6BnA&!B16HBoJA'AgVD_6VBoA)A^56VBowA* 7L6V#BocA)@C6V$BoA(@V6V+BoA(@V6V,BoPA(@DL6V-BoA(O@v6V.Bo5A(d@n/DH86V/BorA'BEZ6V0BoyA'A ?}6V2BoA(3@EC6VEBobXA'vzE846VHBoUA';B׍6VIBoUA';B׍6VQBoIA'YB~Ehr6VRBoMA'-p'E6ZNBo9A*wa@#7BoA0UC,n9E7Bp!A1dB 73Bo;A0uC <)7BoyA/CEW]7BoA/ C /E!7BoWA0 B텨D%l7Bo;A0uC <)7BpA1CrEE7BpA1<}C JDU]7Bo;A0uC <)7 BoA0;2B7 BoA/!B眬7 0Bo A/>C*D17 SBpA1CrEE7 BpQA1C'lJ7VBoyA/CEW]7VBo;A0uC <)7VBoA/oCE&|7VPBp#A1YB4E,17VXBpQA1C'lJ7VbBp#A1YB4E,17yBu A4CqDO7yBs`A6B0CG]__7yBsA7kC+FG7yBsl$A6CeԒF7yBv?A/cCFɂ\7yBv~{A.CzfGݸ7yBwA-PECF`/7yBuOvA3CkFi7yBw_$A.CFdi7yBuXA1Co~Fl-n7yBsA86C66Fcq7yBwdA.:C1F d*7yBvF;A/jCF7yBsA8p]C,7y BsXA84C@ȁE507y BtTA6nCARFQs7yBw:A-4CVF(7y2BtA5sCsJE7y;BwuA.8mCE97y=Bt@A5CfBE9[7yABxA- :CA*DG(7yLBsA7kC+FG7yYBuA4CkF 7yZBuA2TCbbF7yBs`A82lC F7yBtA5Cb*Ew7yBttA5CcF7yBt?A5CTIFBzA CChEwte9m>B{A C~FjE9m>B{ XACF@o9m>B{=ACB|CArC4FFZ19m>B|)ACBD9m>B|-A aBF=59m>B}]A8COF59m>B|A$&ZF9m>B{LACSF[C9m>B||AH#ByF#9m>B{ANCp:EG69m>B{ qA CF^ 9m? B} LABQw9m?QB}QAJd?Xb9m?BzNA nC-DnT9m?B}lA"+C3P!9m@B{jAC[Fp9m@B}/A"U\ཅA 9m@;B|A?1BE-9m@B}A'F ByiE4:>B}A'EBqE:>B}A&t@['Di:>B}lA&IFAvCF:>B}A&BhDs:>B{A<C{yE3I:>B|rAù F3:>B|F5AABD::>B|.,A,C&:>B{tA^C8F:>B}A%"UC:>B}A&͡BSiy:>B}B}A&=Bޕ EC:>B}IA'\5B:>B}A&&BE:>B}A&AHDݨ:>B}!A&MADY:>B}A&#Bmx6E9&:>B{LAC9EGF^:>B}~A%(B!D#A:>B}}A& BhD4:?B}zA&>C:?B}A'B[/AF:?B}A'IBӗ:? B}A%\BjE,:? B}*A&XBARD:?B|ABLVPEVn:?B}A'7dB`96E@:?B}`A&GaAʬ:?B}.A&hWD:?B}EA%BNEm:?#B|;8A-$HF":?:B}A'BD\VE:??B}{A%VB`:?EB}8A&EAC;:?FB}QA%nBEQg:?IB}A&XBztEX:?_B}.A%\AYhE:?`B}A''BJE=~<:?cB}ZA&B>E?G6:?dB}{A%"B EQ:?jB}FA&DA B*Q:?mB}A'_B٫C:?oB}A&BLwE39=:?qB}A&Al7ODS:?vB}*A'BrD5:?}B}A%BN@C:?~B}A'B>Db:?B}A&BDמ:?B}rA&FA5D`:?B}A'HBCTKE;:?B}'A'jBדC:?B}sA&VAmSDzM:?B}A&E1?D1:?B}oA&dIBuD:?B}A&XA~E׌:?B}A%A ZE3:?B}A%B=XDE/:?B}A&"-:?B}A%d@S:?B}A&B UD:?B}BA&AB?kD8:?B|A B F;b:?B}A&EAsB:?B}A&cAD:?B}A&P@:?B}$A&YmAsCf:?B}DA%AmCY::?B|&ACϞF:?B}A&fAՋD:?B}EA&#mqD>:?B}&A&Bc'DGj:?B}A&`B2vE:?B}wA%C,vB:?B}nA&M@9C?:@B}A&~BPEJ:@B}A%AwD:@B} A'BA0%:@*B}A&4:ApE :@5B}A&%nADY?c:@FB}}A&hBݙD:@NB}A']Bf:@UB}{QA&wBCɥr:@_B}A&BEe:@dB}A'B D/:@qB}A&jDp:@zB}A'A@":@{B}A'aBD:@|B}ĔA%BOpEU:@B|iA@B%6F:@B}bA'C.:@B}A&BhDs:@B}A%D:@B}BA'j$BuE::@B}SA&sBDρ:@B}A&AADd?:@B}{&A&"BBD:BiB}A'B}Av_:B}}A&FA%(BB:B}%A&FA^D:B}:A%AeEZܚ:B}A&Awt$D:B}7A%kB~E[:B}eA&B{E=:B}OA&}B[EY:B}A&jByEtFL:B}A&EA™C--:B}_A&eKC@!C<:F9B}r-A%>C7L?B}r-A%>C7L?IBoA&&C;EI?}B}r-A%>C7L@B}r-A%>C7L@uB}A&(Ba}D v|@uBysA(#D3WFC?@uByPA*D F[@uBxA*CZFbF@uByŞA&ΎD,j FT,@uBz=mA$D"6F@uByA(kDnG"@uByA+NCE@uBx\A,C}G?f@uBzA#9DFZ'S@u'BxA*C݈yF|@u;Bx7;A+yC9F[@uAByYA(D hG;@ukBy+A+C\@uBxeA+CqaF@uBzKfA$D/1F]@uBxA*̟D{Fǹ@uByA(PDDG[jFv@uByڄA&CGD)fFw@uBy[A*sQC F/X@uBxA*C1\F)%@uBxA*jCyF@u>B}A'WBD a@u>B}A'C>?@u>B}9>A$@HD\{@u>B}HA%HB?2FL@u>B}#iA$qtF9}@u>B}k6A%CE*EW@u>B}A(CCYEd@u>B{AA F8C(EG@u>B|iA$9AJE@u>B|uA$vAVxzDw*@u>B|AqCsF@u>B}QA"gÈFsm@u>B|5AAE:@u>B}mA%CmE<\@u>B}A$lODɲt@u>B}A"[}BʁD @u>B} A"R4F5@u>B|AJCB@u>B}A"]BBԧD.@u>B{OA $C)@@u>B|A$ӸAwb6C}@u>B}KA'C#"D0q@u>B}GA'\5B{@Wt@u>B}hA'; B%D,8@u>B}A B(@E#@u>B|4A$dA>:Cnϕ@u>B}oA%yCB}ubA&CtDK`@u?B}cRA%C@u?B}J A AT@u?B}1A'CDU6@u?B}A'\C2.?@u?B}A'BD@u?B}uA&CiE^@u?B}.A@$_F>@u? B},ABzF ډ@u?5B}jA%CP$D%@u?7B}ODA%9aBFg@u?AB}A'C%%D*@u?BB}LA!BfoF [@u?QB}X@AHB;Fo@u?XB}A$NP̺FCo@u?ZB}jA%CQ$D@u?]B}A'BE rEW@u?cB}zA&>C@u?eB}nA"aMB 2D!4@u?fB}^A$@I?X_F^e@u?gB}?A$C zFB@u?iB}IVA$G>BF @u?pB}A&`D*RF@u?sB}hA%C!D?`@u?tB}cPA%_BEU@u?uB}A'eWBCI@u?vB}A']BD05_@u?xB}JhA!AߪD=@u?yB}GA'\;B@,@u?}B}zA&CY DE"@u?B}A'B$`C8@u?B}A'NB]@u?B}MA#EE@u?B} A$ds@ɴE@u?B|BNB@u?B}A'C 5DSj@u?B}GA%{Bt@u?B}A',C8@@u@ B}jA%zC@u@B}A'_+B#,D&/@u@B}OA$AdCwu@u@B}fA#'AȰ!@u@#B}MA%4By6E0@u@$B}MA$i@EQ@u@%B} TA$DFY@u@,B}NA#BC9F>@u@2B}L;A# @ӫD@u@5B}A&B6D9@u@;B|uAB_3KF7Ų@u@EϕI@u@B}7A"UBA@u@By"A&CD, El@u@BzA#DoDa@u@B}A&BЬ@u@B}TA%C n@u@B}aA%YCeVDc@u@B}yA&0BD@u@B}TA(BzVD@u@B}A ̳A~F'@u@B}a1A%CE‡@u@B}\#A%̹CzD@u@B}UA$2BER@u@B}dgA%CD۸|@u@B}] A%ӗCi,DQ@u@B}_A%C"@uBpB}A27·-FO@uBB}A'ZB%D]@uBB}DA%FFs=@uBB}ѴA0C3Fx@uBB}A&rB9F&@uBB}DA"S Kl@uBB}yA.\\?^5@uCB}[A%&A@uCB}8A2R' Fx@uC B}.A2.A(CWo@uC B}A'+C}P?e@uCNB}ݓA. @uCUB}A%s³FFr#@uCB}A$AդGCdF@uCB}A%A2D @uCB}{A#\DAqrEH@uB}A''BDA=B{A nA{|A=:B{A 0?DUA=:B|YAeCFl[A=:B|(4AB <]GA=:B|KAÓCSkFhgA=:B{A h>&E@>A=;B{A '?BWA=;B|:A{BFF9-A=;B{eA EAA=F@A=?#B|XAUCwD:mABo]A',BviyA BoA+UCdD4՞A BoA*ΤrA BoA,5\)A Bo9A+}DbA BoA+A (Bo*A,(pA .BoA+m bNA `BoA*ΤrA iBo]A'MBi1@A BoA,BaA BoUA+C A BoA,*AAA BoA*WAD>}A BoA*ΤrA BoA,*AAA Bo>A+AE^A BoA,%A 8BoLA*/A B}A&&B E$1>B}:A&A?iaD%1>B}A&A:r5C1>B}A&?YCK1>B}A&NUDjt@1>B}A&*GDߐ1>B}kA&`BJE_1>B}vA&AE11>B}A&&C4E1>B}YA&@BB}A&fBEś1>B})A&BżE-1>B}A&A*$FsB1>B}wA%B$D1>B}xA&A{E1>B}hA&鰼鹖EΤ1>B}oA& C z$EN 1>B}A&B7D1>B}A&BBDE1>B}A&!B &E 1>B}dA&;@B>1>B}A&[B)& Cŗ 1>B}7A&B]D 1?B}A&yc@.CZ1?B}A&@D1?B}A(@DEc1?B}\A'B#*D51? B}A'ސB%CG1? B}&A*m^C >F1?B}-A)B>E1?B}A( Fʉ1?+B} A%3sC&gDZlH1?,B}1A&&¬.Eٚ1?-B}A%)aC+bDM1?TB}jA&BkI7?f1?ZB}A&x@MXCH1?[B}A&iA1?B}bA&tBݎZD'1?B}FA'BZET,1?B}FJA'BoE1?B}I@A'AZEC1@B}A(G SF1@B}7A']BDF1@B}A&-HEE[1@B}EA'nBD{1@B}A)lB 1@B}A&BBeDxJ1@B}~A&"Bh6E[1@B}A)AE1@B}A'ސB%CG1@B}zA&`2B[E1@B}dA&;@B>1AyB}A( B0sBz1AB}A&A_lWD1AB}A&t*@PB&1AB}A&z@C*O1AB}kA& @ڌDnOT1AB}A& BCD .1AB},A&ߵB_)Esy1AB}A&BQDR1B~B}kA-BtCU1BB}A+@K1EDB}KA,;AvD1B}A)EBE>1B}vA(E)BQEe1B}A)BjE}1B}mA&PE1B}oZA&"BE621B}EA(B=[#1B}A*'BD-1B}(A&0@3CD1B}A&AD71B},A&m@3Dm^1B}VA&A3D]1B}A&AD.*1#B}?A+9BEr1$B}A+ ChؒE.E1%B}7A+>?BNCEz1_B}A(yEd1`B}VA([B+D 1aB}A(5B] E7h1B}JA'esBbEM11B}/A*CoE, 1B}*A&<[A;AH1B}UA&}@1B}A&HB̐D΢1B}A&GBcD1B}A&@D11B}QA*_A12B}9A( F13B}A&AB 1;B}A&ØBD11B}dA&;@B>1B}A(H#.EE1qB}A&BfD1rB}A&BTDK1{B}A&BE+1B}A&ĆB*D]]1B}A%KpCC5v1B}A&D1#B}vA&pBELp1/B}A&Bf![DX17B}A&t*@PB&1B}A(N1EؖB1B}A'lB34D3k1B}A&BeD1B}gA&;@)B3d1B}A&BMD'[1[B}VA&BwD1cB}bA&BAYDC3m1oB}A'<=B LD'+1pB}SA'BI6F1B}dA&;@B>1B}dA&;@B>1GB}{A%B[DRtb1B}]=A&8 E)1B}uA&_A E%1B}A&NB3E2 ]1B}A+&B0Et1B}rA&A¦E1B}A(EB^EPZ1B}A' B@LEX1-B}XA&&B[D۹1AB}A&̯AoDp1BB}A&B$Do1HB}LA&B.C1IB}A&B ADN3? B}A*,Bl3?B}A hCچ3?B}gA,B3cEv:3?B}A,pkA*E43B~B}MA+q@չ!F -3BB}A,iA-3BB}dA,LD޾3BB}wA,(='\Dڛ3CB}HA+ĺF"73EDB}A,B &Ek?3$B}A*WB'C3%B}A*ɵAZDn3?B}HA'C u3EB}A*BC@3HB}FA*B;E3\B}A,BNEb3]B}A+ũEJ3eB}A+њA6VEUd4$B}A)Ay4EB}A*iB,kCK?B}A(+ B^CK?5B}@A*5B9D8K$B}A)EB(ZCYIK5B}\A*#7ASCy<k1B}{A%OCC[h?B}%A&@l1B}}A&\B5h?1B}bA&@~h@1B}A&ͱA<*ANle1B}?A'xB`B*1B}qA&rALB1B}{zA%B;BC}#(BpAt?=4D1#(BpAdž2AC#(BpADžzACrh#(BpAgf@8?l#(BpADžAMB#( Bp Akſ)IDTT#(BpAǁ=Kl?C+#(#(BpADž$AiD0#(GBpnAǁfA|9X#(Bp1AǁADӱz#(BplANJi?CY#(BpAlj&AtCn#(Bp AljqaJB6*#(!BpADžAkzCph#(BpAoACy#(BpA|WvmD45#(?BpAǁ?7UCk#(SBpAǃABF#(UBpuAdžOAfCx#(BpAv`@HHC>#(BptAjv[D1{#(BpAǃYAqDVv8#(Bp+AǃAMBBGl#(#Bp Am@4D4#(BpADž^AE&C#(BpADŽAH0B8d#(BpsA^iABC#(BpAqAoX#(BpANJ)C):q#-BpѲAeA D:P#-BppAxRAh#-Bp7AoAWDY-#-5BpBpA)An #-?Bp߽A$4A`B#-BpAmtAj^C)#- BppAxRAh#-BpݫA1A:Cp#-wBpAxACa#-%Bp3AWAD]8#-;Bp.AAM!`D%#-;BpAxACa#-BpYA{A v D #-BpAQAD#-BpA6ARDP#-BpA@D#-BpAAfC#-Bp̹AzA-#-(Bp?AFiA,D#-Bp̢AuATAC #-|BpArACjQ#-OBpRAnADMH#-QBpAoAeDV#-[BpLAuAB9 #-BpAUAρD;i#.BpEA[AC)#.BpAj[AdC1#.BpA_B D#.BpA_ A˴!DDd#.BpAdBD#M#.BpAdA2DG#.]BpAsA_Dj6#.BpA]tA@V#.BpAiAwG|BEq#.BpA}LADƆF#.BpA]jA&}C#.6BpJAǀA5SDG#.Bp AdBkqD$#.BpgAjATxB*#.wBpAu^B{EDɌ#. BpA`AħBv#.:BpAxKA5C39#.;BpA`Ai7C2 #.;XBpAbAG@#.<*BpkAlAa/>#.>fBpXA\EACJ<#.?BpAǃAvA@?K#.BBpA]\AبZCI_#.BpkA{AYDe#.Bp{A\ApBp#.GBpKAs Bm.DҚ#.BpAgASC7#.BpAbAUA7#.BpAfAaUCǔ#.ί4Dj#/ BpAi@sD}'#/Bp+Ad@Ë#/1BpA_KAMC4#/#BpiAko|CY#/BpAb@U7C#/^BpuA^A#5Bp1A |ACu#BpRAAfA{x#BpcAQA/#BpA>A!DC9OW#/BpAgAAK#0BpAOv#Bp`A`AĦC#:BpAABd#;:BpAAAdB# By?AĈ AҼj?SBvRAIJ@mE?B{?AľBATEva1?BsA⧾~?BwiAĿUmE@t`?BAħB8+^DS?BDAĦAlr?B AĥUA8f3Bd?BuAī&EEx?Bw'AuKC?B|AĘB#B&B-VBZWA{B,EEBZAʄBǜC)BZAʂABBBZ}AQ1BJqA jBZ}A?C"%EF KBZPA CODp rBZ9AdzKCE @ QBZACE. RBZ}AȉwB 7E SBZzAȲUCSD[B; ZBZAC_EVO [BZ~AzBE \BZ|wACxEF  BZ A-CSEyx BZ#AvC +BZAɫC7XCT@ BZAyCxD jBZACD~ kBZûAEBŢ  BZAPBE׮  BZAM"C&E#Ն  BZjE^ BZqAC/\Eb BZA5CEE\ BZĄACID2: BZAʪ(CeD׭,KBZ}AQ1BJqA 4BZ3A CwCuDBZ}AQ1BJqABoAl8BXF_Boe=ABO9A B^6BUC1{:B^BUBEb ;B^o B +E$=B^nBC k>EG>B^BvCVBnwAhB2FFBj'BFCBjMBFoC<5FBjuvBFwB F W{Boe=ABO9A B^6BUC1{:B^ABBBcBZ"Dq%;B^_|BvC )ECl%=B^xnBB3?5;B^xBB5=B^pBBD-H;B^6BmC.8Bo':ASB ElBoeHAA7CBo[A@EBodAABo]jACE BobA5;DP BoZA!TJDM Bo[ABu1EU Bo>A)AᄶDT! Bo1>AAD BoCԖ^BoAAD\Z^HBoABaR^VBoATBV^2Boe8A}ABo^3Bo0AB4^3BoAxA^3BoAB"UDDD^3BoAuB[@A^6BoA$B"E0t^6Bo0AAD^6BoArBX2Eq^6Bo(AB4pEXPv=B^XlB/BBpAB+BpDACvD n,BpnABAΥ-BpEABTB.BpYAC{Cd/BpsAB.C[G0BpcA"Bu0Cq BpABMC@ BpACC| HBpA,BEC  KBpABKBS& BpbA(CC Bp^ACC3_BpKABC3aBpLA?BծC}3bBp]ACyC3cBp!AZC6DL3dBpACB:;;B^wBPC'̤E,;=B^x,B0BΚE'V;B^UhBCeD&3_BpAxB B^6BtC-B^6BjC/~`@kB^6BC-,AcB^6BuC*aAxB^6BhC/Z^`B^6BrC,FAjB^6BPC1!.B^6BoC+#,B^6xBC0D1B^BEC uBOd1UB^٤BOC1XB^gBJkC D9{1YB^gBJkC D9{1ZB^BECBN1B^BEC ޘ@71B^BEC uBOd1B^ BEC:B^6BdC1c:B^B/BD^:sB^wBBΫE:uB^BZBC:vB^BBDt:wB^*BB:D:xB^ABBtGD q:B^6BtC-:'B^B`Bj:B^6BxC-t:B^6BnC/k@S:B^B9B^Dt:B^BB9:B^BB9:B^B/BD^:B^6BjC*:*B^6BdC-A?]:-B^6BiC,,A :ٗB^BB:٘B^:BBߋEa4:ٙB^BVBE`g:ٚB^BNBYBd:KB^BNBYBd:VB^ABBtGD q:&B^BB;B^rjBBgiE;B^nB\E;B^\By:EIU;B^d&BpBD.;B^k0BBE};B^[B{)CE.<;B^pB3-Eǎ;B^6BC-Aޓ;cB^6BsC0T@3;B^B%C>w;B^6BjC*;B^6BeC.}A4)Z;B^6BdC.ATD;B^6BcC-n A:-;B^e}B\BCep;B^6BeC.A3;&B^dB[jB;)B^f+B^GBխ;NB^BCNAD@ ;`B^6BiC*7A7}@;B^6BhC/Z^;B^6BgC/;B^6BlC0@bTL; B^rIB5C&@Eg;B^`SBC,BD";B^cB}CzD=;B^p2BgC!7;E;B^ZBC'E`2;B^_9B BzE(~;B^xBCCA^ ;hB^iqBo.B Cd6;jB^iBqBFDpLj;kB^fBh!CHDEY;lB^iBqBD*;B^6BeC1i7;.B^6BC+A+O;sB^rDBCE|;tB^a}B~B!E*;vB^pB:c(E;wB^bB~B,ER;xB^uHB[B _!EO;)B^6BgC-R;*B^6BC/ޤA>;,B^6BxC.A0;-B^6BC.3As;IB^nBBCE;B^T By@C{;jB^ BB4Cɴ;B^``BjC J;B^dlBZByA>;B^bBbC rlD;B^bBbCnD;B^bBbC"DK=B^iBC\Ew=B^B|Bٚ=B^pPBBME=B^WkBC'=B^BBIDj=B^hBXCDA=B^BpCͦDs=B^BlB2Ae=B^[BC*==B^e BBD'=cB^WBBHCְ=dB^XBC gC=fB^XBCC =B^WBBHCְ=B^oBCE7G=B^WB1B#=B^^BCEE=B^lBLBEƗ=sB^BB =vB^BBB?+=IB^kAB`C EF=LB^sFB_CP=E"=B^Y9BC܂D=gB^BC<=jB^BC<>B^ǯB|`CC>YB^ԼB^WB_s>-B^BtC >Du\>.B^BBiC8>/B^BwC0Dѕ>0B^BBڄD7>1B^ByB>D >2B^ęB0C Dh>'B^ͦBpCV>'B^EBekB>'B^ͦBpCV>'B^@Bs{CsCZn>' B^1BgB=D )>B^EBekB>B^ӁBaBsCB>B^ͦBpCV>hB^/BB >jB^ęB0C Dh>pB^ͦBpCV>B^EBekB>B^ӁBaBsCB>փB^٤BOC>B^٤BOC>B^&ByC>B^EBekB>GB^BBX>HB^BB}q>JB^VBB?D>KB^]BBDC;B^gBb BD}B^6BxC-tDB^6BdC1cNB^6BfC/yNB^6BhC-_}NփB^BEC uBOdNB^6BC. ADdBjZ3BF]C9tBo*ABE^Yt^Bo /AB^!gCT;yBo AB]TBBnNA:Bj@CBnNA:Bj@CRBnAрBCHayBnAڭBBnAB BnPABuBnN A BT{"WBnCArB!F-"XBn-A|BF3"YBnAuBkDf;Bn~mAwGBRF)/; BnZ A(B'&E=I ; Bns5A_NB҃Fh; BnsA_BE>; BnP&ABLqCa=Bn}AtB^QF,ĝ=Bn{AVBIE]=BnsA^B>F7?BnNA=BC?BnPAB?BnO-AB6C?BnOfAB;C0?5BnABeE,=d?qBnABF?rBnA (BCE?sBnA-:B YD:Bo-AB]Bc,^BoAB_BjBFսB)yx Bo4AA=qx BnSAB4Cax BnABE탲x BnAzBwxVBnbAdBrx; Bnr|A^xBDU<x=Bn`]A8B&fx?5BnAڭBBo%oBjBFC1EJpBjFBFC" D\{ Bj(BFC6D' GBjD#BFC!̨Bj\ABFB̨MBj/BFC ؙC[̨TBj+BFCE5̨^BjBFC'iC̨Bj"?BFC6YEϟĮBj[GBFCB$̨BjBFBT̨BjBFSC5uEqųBjBFʿC#pHElo0̨Bj}BF|Ch̩;Bj*iBFC6Cnͨ`BjpBF~ C`eBjorBF~C D]3fBj^BFBkEefgBjm1BFB EwhBjp]BFXBDlUiBjhcBFmC/EjBjk-BFGBc*E#boBjKBFjBVbE=>pBj3BBFC#[EHqBjQiBFC D PrBjRBFC\DsBj@BFAŒEytBjQ0BF}CBDyBjiBF}CnDK'zBjf`BFC9mD,B{Bjj\BFC!2Dd`|Bjh"BFC#oIDZ}Bjf@BFNC#D~BjkBFC3D=BjTHBFC nDsABjP3BFC>DEXBjZ BFwC$E8BjUXBFC1DXBjPBF"C,DBjXEBFsC LCdOBjKTBFA5EI<BjMBF+CD$BjPBF CBDKBjKBFHCcD}:BjLQBF3C?DjBjOBFC,E[BjBFPC9Bjq3BFCCinfWBjdBFBFC 8IDbBjWBFCDBBjVgBFC=Dh%Bj|BFC'C>Bjv7BFsC$ByRBjyBFC)YBTPBjwBFC'|C\BjiBFC&CWBjiBF4C5~BC?BjW+BF BÙE\BjRmBFCDoBjTpBFCPD?BjU/BFGC;sDhBjQYBF#CwE BjTBFC EBj=:BFC DBjSTBFplCCtBjBj\BFB0LDpz?BjdBFDCm>D@Bjc BFNChMDxKABj\LBFB#bD9BBjbBFC3D=QBjV|BFB ֨Bj^cBF~C$ܮDXm:֨Bj[BFC!7D~֨Bj_BFFC"8D(֨!BjGfBFC CEƦ֨"BjEWBFHBwE֨#Bj^BF?C7JD/֨$Bj\BFIC*N EG֨%BjZrBFC (cD֨&Bj^BFC%D޺֨+BjdBFCAxD"o֨,BjbBFSC;;Co֨-BjhYBF BʷDָ֨.BjfDBFCBD&#֨/BjclBFrC5D~M֨0BjgTBFC3D֨5Bj_BFKCS[D֨6BjZBFBߠD֨7Bjc BF B<&E!0֨8BjaBFuCCwD֨9Bj]BF;SE-6֨:BjaBF5Cm"E$k֨IBjP&BFCHE?t֨JBj>)BFBE֨KBjT`BFCqES֨LBjQBFC:DL֨MBj/rBFCD0J֨NBjQBFC UD8N֨SBjHEBFdC DaE֨TBj1BFCZE֨UBjCBFCD>M֨VBjIBFJC!nDZ$֨WBj17BF-Ck5E#w֨XBjGBF}C VE w֨^BjoBFC&D/֨_Bju=BF9C֨aBjmBFC?Dձ<֨bBjrBFC&yA֨kBjaBFsC=-֨vBjiBFrC-uC'%֨Bj[6BFCj֫BBjhBFo}C'Aץ֫iBjkBF=C!fDC֫jBjjBFfBD֫kBjmgBFCgҖD֫lBjkBFC=YDx ֫mBjkBFC)D]#֫nBjlBFCmDB֫sBj\{BFCDP֫tBjZBFC3D|֫uBjZBFACbDHk֫vBj]BFC)D u֫wBjZ1BF}CD0֫xBj]XBF-CCcv ֫BjXBF|qCE,Q֫BjOBFpuC,֫BjOlBFqC V֫BjPBFC5 Dv֫BjPBFz@C6zD{^֫BjRBFwCO/D${֫BjP!BFPC}D7֫BjOBF{C!Dg֫BjRyBFx9CD"֫Bjr%BFC%C֫BjtBFCrD֫Bjq*BF\CNDۆ֫Bj~BFC,B֫BjBFC%c֫BjwbBFC(֫BjYBFb}CUD BjBFs#C "C.gBjrBF,CyBjkBFC,BzBjnzBFC/B2BjOBF{CCXBjZBFtaC$BjOpBFxCCBjBFyZCQ5Eg&BjrBFvC Ex4Bj#BFqC>+EBjrCBF(C=qdBjaBFvCFTDzeBj_PBFuBeEfBj^BFk~CDD2Bj{BFC D.:BjzBFC4D)BjBFuC\EFiBj|BFC-gDBjzBFfC-DPBj{BFCt1DxBjoBFC߯EBFC+JCBjz}BFC&'BBjxiBF?BtDbDBjBF~COE"BjzBF|BENBjBFx}CE-BjTBFC}HE$ÈBjuGBFC _C{BjBFnBv}Ee{BjmHBFB]E m7BjdBFCR?EBjhBFC?E cBjnBFCRyE2pBjd)BFBDmBji2BF C.E "BjvBF}CBjBF{C'ED"BjxBF CEuBjBF{CiDuBjBFC E1[Bjx/BFoCZEW\BjeBF_BD]BjhABF^[C ZDr^Bj}BFkCE_BjgaBFbBD'`BjxBFr AGEhuBjoBFmC6BjtBF0C ]_DHBjcBFaSC-7DBjaBF`BiE1BjbBF_FCND^BjkBFUC0l`BjgBFC/^BjYBFatC WD0ٕBj_BFtC"kBjyGBFBƵSD"lBjqNBFbC DD"mBjtBF8CR.2Dn"nBjwZBFCOD"oBjqBF?C;υDG"pBjuBFC" DO"Bj]bBFlCKE3Ӡ"BjTBFlC-,E["Bj]BF_"C;D@#"BjZCר]BjuBFC"C12ר^BjTBFA: F&msר_BjfBFC$EXR רaBju BFB"4E*uרbBjz9BFC?΄DרgBjBFC4gDʶEרhBjBFC E\רiBjbBF{Cq!E*PרjBjBFC0DKרkBj{BFC2EE#רlBjBF~ CDרqBjhdBFzCEޫרrBjbBFt?BsSEרsBjh^BFsC ~D֜רtBjiYBFRC$p8DרuBj]BFvBOEJpרvBjhBFsBSIELר{Bjx|BFCOxEר|BjoBFfjCIDAר}BjBFmrCRgE>,ר~Bj|BBFr,CAEjרBjoBFfC{%eD/רBj)BFn)CbEרBj"BFC#VרBj7$BFCD8רBj8BF}+C()D-,רBjBF{ CDӤרBjBFzCD 5רBjBFsC"vD3׫ABjhBFn[B~ D׫BBjfBFgBhD7׫CBjkC dD[q׫BjuOBFC9E"I+׫BjtBFtC\ D׫Bjr,BFC D׫Bj~FBF!CC D0h׫BjvBFCt7oE,*׫BjTBFB#E!׫Bj~RBFB=D8 ׫Bjw3BFzBeD{)׫Bj~BFBpEQz׫BjcBFiCvGDǮq׫Bj]BFfCME׫BjcsBFeBLHDu׫BjcBFibC mD׫Bj`BFaC E.׫BjcUBFd>BÇfDNBjBFUC"BjBFsC0FBjBFxC/lAB-BjSBFCzCLȺBj5BFCh'E9յFBo1A_A~ &BMBwCF7ABMBC4@B]BnsAWBUDxcBnFA!FfBnfhAG3hFBiBBByBoƭA@8XCSzBoA"BuF7BnACFUjRBoŢAACR aBo_AZ;FoBoSAXIKEeBoAQ&A-wEpfeiBoAAABo%A:GH=cBnS ABzBo^:A A!8\AeBo AĺCBoRAmA DϝBo]ACqe'DJBo?"A@E8:Bo>AزLa$nBoegABAwAƏ(nBoeAA@4A)nBoeeAOo@b0nBnA6BB Cn@BnABDv@BoƩAAC=DDBoɕA@"Cr DBoȆAAB"HDBoɗA@O(B!L &BM8HBCD(`BoĤA@;VAdBoğA@}B_imBozAA7lmBozAA7pnBoeAA'tnBoeA\A#Bp &BM'BgCBkCB"BIE/*BnABMP &BM6BqxCEYBnAB_D9BoAiBdC+Bk@5B*$BfDbBm8AB[DtC#Bo4A!hs BoAu@!B3$BoƮAF@˜Db((Bo8A{A;hdAҪLBBoƢA?A@KJBo-AA+ &BMB{C8jE)vBoaAB3BncA_A!BoN'AAp}D9)BoGAV@D BoAnAEfGBoBA4@Cc Bi;BUBJRBoKA!@ CVBoMA@DCU\BoA G1`BoOA@wdBoMA@&qBoBA4@Cct BMB,UCE%x BMB,UCE% BoaAMBtB'|BoAB]/ Bo`A1AsmcBn®AuBBnRAuBByBoȹAS@ߕ Bo=A@hsrBiYBEBCBo)A+%Al&BB{Bn^bAB,B  Bn^iAB7H BoŤA!1AA0Q &BM:BC!`BoaAMBtB'h?Bl{oAw+BIC "p?BlxABprBnAjAMC;B|Bo€AB{CiBo)xAA;dBiBB{ (Boe=A0A;dQzBotA v\BnB9A 5BA5 v\BnCfABC v\BnDAB.DL (KBiBATPDq0| ,KBioBID 4zBnVmA$B[DZ 8zBnS\A9BwL \Bo)xAA;d xBoeA AB" BoWA@.CxJ BokAARu Bo AADC5 bBnAB>Cl BoeYAA oBnA2 B oBnYA=Bѩ^Dqw oBnAA9BяCY WBn]ABH1 BoewA6B" (Bo2A]BnEC4  tBiBcB!CY tBiBB+ v\BnCAhBpKC:[Y zBnU|A"+B8D BoewA6B" v\BnCABC7 &BMB~C'Fq &BM7BokC*E &BM"B̐CTF8 &BM(BACjKEn & BM(GBCB1DC &BM1BiCFE] &BM)BM8C`C& &BM*4BI)ChE &BM,6BPBE'D &BM&DBHCCx) &BM)BFTC 4D} &BM0BqCtE &BM*,BFCE &BM+BLC-EI*- &+BMVB}C{DV &,BMCBzOD5E  &-BM[BvCb_A &/BMLB-CLEs. &0BMCBqCDEW &5BM@BC3DIo &6BM:BCj0E p% &7BMDRBC`+EUP &8BM@BC5(DT.S &9BM4qBtJCE2 &:BM;XBwC1E׉ &?BM1BlXCSgE" &@BM""BC#[F &ABM02Bg$C&EE &BBM/B|C*3E &CBM/&BngCEݎ &IBLBile;Fme &JBLOB[1íF1; &KBMBC_2FX &ՙBM8BvC[E &՚BM;BBDc% &՛BM;BCD DiR &՜BM=.BC+ڦE%YV &՝BM6BtCbcE &՞BM= &׎BMGRBCFDHL &׏BMSWBCE 3 &אBMOBC ĥEL^# &בBMEBQC9EN &גBMPBC E8P &׫BMB$CBh &BMDBC Cz &BMEBCz D  &BM-BQC  &BMB!CIDD &BM-BQC  &}BMB.C%E?f &؀BM BCK Ds< &؛BM,gBtCj_E  &BM%cB9CĎ &BM$B5B(D5 &BM$B5BڏD"* &BM)BCC E- &BM%"B8LCDw &BM%QB=C MDڶ &BLBu=C&E &BLBCPYEJG ,qBo59AMA] 8qBoBA@Q x &BoA,AMAX +BLʃB5C CU +BLB9C[# BoA=ÃCAW BoA@A&%B# BnFABXiC BnEMABC4 BpA}ABB'» ABo59AMA] uBog@AAeBf BoaAB3 @ ]BoAMAWC DkBo_)AAGo D ]Bo ACAE HkBo`DAAsl PkBodAAMo \qBoMA A dXA'C  BoADBnHArBȞ!B@XBnQAB"D\BnABD_zl1Bo4AEBojAB]FBoABZB'BBoABZB'B Bo4ABoelAAA?^U@ߢBoepAFA ^AX*9BoepAFA ^AX*9Bo]A*A^QD@(Bn}A&BcYC(BnABԥ`(BnAB\CD +BoetAKB?@yBoexAaB >A(BojAB]F,Bn;AUBEpD1Bo7~AA BLBo\?ALA GPBo] A2@C-iTBoSA@XBoeABB @|BonAxQ|qBo=A@hs|(BnoABC(BnA$B_C>(BnRAϜBCA *(BnAB2vCBoRAA/ BoAYA5?Boe^AAk AiwBoebA`1>ABoe^AAk AiwBoebA`1>ABodAAcBz(BnAnB&C],Bo`AA3DqVBnAB:DnBo_AA/D|0+BoeWA4AA`Boe:AAs?+BnABBnA=BĈhDCs Bo!AA̜?D Bo59AMA] <BnJAB XBnQAB)Cb$XBnSA`Bq'P ;BoaAMBtB'`BoAA ;Bo`A1Asm^BoexAnBAT@JBnAƥBEDJBnAÃB D1׶JBnCAȭB>VBoAA9BJ5C P BoAkBQ^Boe^AAk AiwiBnmAAC2UABo AGB#BoemA2A;;d@v{BoaAMBtB' BoeAADȴ^BodA^AM'B ^BocAA$IBB+H{Bo`hAAslL{Bo`A?AX{Bo`hAAxE cB+cBn)Aq.B{CŵcBnAxBbc5BnAC7Fcc6BnVABF(Үc7BnAC1B*Ec"RBnABMEјrc"SBn AF,Bc"TBnlAD-JF9c"UBnzlABbgEcwc$Bn+A#Buc%Bn#AYBD]_Rc%Bm@ABMCӭc&BnKArB]ƖE*c&Bn>QAwwE>c&BnDA!BnD=~c&BnFeARBD6c&BnCAbBC(c&BnD A~B2Dc'BnAo=')F/c'BnABVEzWc'BnnASBlEtYc'BnA7BEb2c'BnAMBD^c(BnA`K%,F]c(BnlAB6Fc(BnA^B,|Dc,BnAcBEc,BnqA:PBD7.c,BnAWBK%Dpc,Bn:AA]B%c-Bn-%A1B\fE&c-BnGA BkE$c-Bn)2A+BxEc-BnAtA\BCGvc. BnAB,C4Hc. BnABB 6c.XBnaA;BWE c.YBnAw*NF5c.ZBnSAIyBWrE9$c.[BnA8BB&Mc.\BnMAB?Ec.]BnA3BLEHec21BnAABk!D]c22BnD"AދBM+DewEc2]BnnAB@B]E?c2^Bn4A B'Ec2_BnAqB0Dxc2BnAiB ZDSc2BnAUBEM0c2BnA[Aۤ2Dc2Bn6A9B8EM!c2BnASMB*E'c>BndA\BDDIecPBnABC݈#cPBnԕABD&9dcVBnAdBc]Bn5Am(E c]BnArC눬Epc]BnAwm5Fmc]BnڎApyCnFDc]BnAs;BIBߊqc]BnKcA=BpEUc]BnBABeD/c]Bn?0AB0TDFhc]Bn)AA,PB2EFc]Bn>A@Epc]BnSAC*?EE!c]BnЎABkC]c]BnLABD9֝c^Bn5Aq: AFDc^BnNAC=EzpDc^Bn^VA:B=Ec^BnYAEaTc^ BnI@ABE:c^!BnAB(LFc^"BnUA8IFi c^#Bn_ABB+c^@BnwAByc^JBnXA1BFEc^KBn2A4,Fc^LBnA-BFDԀc^XBnANBk۞F%c^YBn-qA7Bn |F c^ZBnRmA~E c^[BntAZBiFcc^\Bn$A=BWE Ac^^BmABODQ8c^_Bn`AQBJsDBc^fBnZA-hEljc^Bn GAVB1Cnc^Bn YAXQBևc^BnA\ #uF[,c^BnMlAqC(MELc^BntAD B[Fsd)BoA@rABfBnpAx0B^Bۊ\f;BmDA_AuFꨶfBn?eABEof?Bn7;ABJbDjf2BoAADfBlFAB4F$f}BmAVB{Ff~BmQAzB6F:fBmA{BX'EfBm^A#Bef BoTA+BHf/BnhAB$4E f1Bn/ASB5lnFh f2BnAwB!vEf4BnqA4A"D fcBnlABkw'Df BmA5B4SxFDVbf BmA%BF27f Bn~A-BRDfBnA#A}AFKfBņAoARD@fMBnKABiZUEfNBnSA:A,EZfwBmABTWESfBmA}AF :fBmA}AF :fBmFA~AIFfBmAB.fBn&A)B[KD fBn*ABFƉfBn'A#B;ŘEfBn5AjA2EYfBn(A!dBg52DfBnIABV/F;rfBn+A'BnFZlfBnEA^BnEJfBnd5AqAF 6fBm6AIB.ŎFVvfBmAAʠBGPFpfBmA=FB\$oF)ygfBmAB2<5F#xPfBmˀA'B2 FfBlA^B)FDWDfBlAB FfBlAVA[F SfBlzAqAqE[xfBm5AAtFyfBmY A8XA7MF1fKBnABoF-gfBo xABNvEXfBoAAQf BlAAD$f BmOaA]B7'Ef BmAkAX͕Eff BnlAY1B#~EBf BnABT)ER3f BnABDDf!@BlAHAvOFpf)bBlASAEf*,BnuA)A`jDv0,f*ABn,AB$FFYw@f*BBn`mABsFE`f*CBnABdEAf*EBnqALB9iD9f*FBnABLEf,#BmAaB=SEF>-f.BmPABPkE0@f.BmAܐB*E&f.BnKABKDGJ+f3lBo=AvAN^DFܪBiBJwCEBjTBiNByYG  Bk AlAF5A7BjKBž0$GBlPArB2EcBiB:yEb BiBByC'RE!#UBiB jĖE=#VBiB.RwEI#WBiBC'F-#YBiQB:eB2EW#ZBiBCkEn9)`BlAqB6tE۬7)aBlAB%oEz)bBlʦAOB1LF *{BjBBF3*|BjpBwBWFk;*}BjB8CF*~BjïBPFC\eFN%BjBC UF,N(Bjn%BBB-FPN8BiBlBE7DN:BjnBBEN@Ble#AȏAFSNABlhAJA0HFYp NDBlA /BtENEBljVABEFHhNBk BAGDTNBkfB~BM&FZNBkWB!BpD5NBi|B3C+ADžNBkA/AF NBiBdC#@F|?NBi[B[CAsF:NBjT~B[2>G'3BiRBuCB D4BiBBj7DL5BiB4Ev ~BiBBfD8 BiNBVB%8CK/  BiBBD BiBBCp BiB7LBiBB_D%4BiTB=BD\BiBBD BirBB_rD<!BiJBBC~F<BiDB97B#_EݚBiHBPBEBiB_ƖEfBiBfB&E\BiHBCR"Fi{8~BiBBAx BiB9gF BiBӽCF6 BiBBC>Ct| BiBiBAS BiBC)pDҕj BiUBB5E  BiBBl%DV BiBaB D'A? BiBBwDm BiNBVB%8CK/ $BiBBDY$BiBBڰ!$BiBTA{E$BiBADN8BieBC;_FsqN9BiRBBCWE{wN[BiZBTB'DkkNbBiB] E>NfBixB{XE7%NgBiBDWÏnF6aNhBiBB_CWNBi.B#BGDNBiB7LNBikBsSDY~cBnA޷BMDBBnBAԜBy? WyBo?Af@R/B y*Bo6A3AۃBߓy.BoһABffyBo]AA{GB(=yBoKA77AAyBoDAB OBIyBoA+BLm=FHy !BoAЮBU8C)5yBoAJ@%C\yBoIAAy;BoAЮBU8C)5y>BoAЮBU8C)5yPBoNAA0CKySBo'AhAξC+@yBoA4BCA;yBoAWAPD0yBoA A AyBoA?A`C#yBoA֤B뮭yBoAծ?KAU yBo̢APAC:GyBoϠAQAaBeKyBocAQAC4y BoAQAKy*BoˇA,AVBYp+y,Bo˸AFA3&aASy-BoGA5A$y]BoIA7yACuyBoSAzAt0C{)yBoAȩAC# y BoTA@my9BoAATZCQy:BoZAUA,NBЉyABoɋAA"3CyBBoAB07yOBoAQ aC\ yPBoŞAiOD yQBoyA~XCy BoASAD*_7y(xBoAPAzy(yBo}AQQA#y(}BoϠAQAaBeKy-DBokAA:]D!y-FBoA>ADwoy-HBoAZBCy-IBoABYKAy2{BoJA?Ay3BoˍA@@Xy3uBoAfA.B`y3vBo`ADA B1y3wBoAAzDqy3zBoAnB/(Cy4BomAA]D#y4BoBABNCpy6BoA*B#dDAy6FBoAэB8Cy6BoA+QC1y6BoAB_y6BoAxn:Dy6BoA <7C`y6Bo[A|ICJ6y6Bo3AA Cgy6Bo/ACy7Bo A]AA]Cky7BoA:Cgy7Bo*A?DVy7BolA BXCBy7 BoA@DPy7 BoAAdFCi_y7Bo;AAFCy78BoANBy7GBoABYDx{Zy7HBovAAЗC"y7QBoAAɍCGvy7XBonA4AD%y7YBoAACzy7gBo1A[BCLCDXy7hBoA,ApCqy7iBoAB;CYpy7jBoAB [C)y7{BohAAǷD^~y7|Bo,A;A"D)uy7}BoAB SD)Zy7~BoACA&/BBy7BoAAOCy7Bo%AAC͍\y7BoAy7BoAA7Aey;BoAPAD(<yJB DyJUBo AB>B8yJVBoA"B1B yJWBoAB&9CRGyJ]BoA1@ƎDa'yJgBoA.B 2DyJhBoA߮A BC3yJiBoAAϪDyJjBoVAD(BT yJkBoʂA3AIxC~yJmBoABIPD wyJnBo[A B?΀DYyJvBotAAGQBsyJBoYAAxB3yJBovAAeB|МyJBojAx yJBoFA*B2_CyJBoCAB9DA TyJBo,AjBDKR*yJBo.AB˯vDyJBoAABUyJBoLA]AFAcyJBoLA]AFAcyJBoABvC.€yJBoAVAKCǘyJBoAB+yKBoAA&!CyKBoAACyK!BoAABCyK"Bo AA]B#yK-Bo˽A@?/A yUBoAAC]wyUBoAl@BYyUBoAhC<yBoAB.BCB@yBoA(AJ;d@UyBoA AGyBoA֢BþyBoAXyBoA~BkENyBoAՎIzBoAqA2F.<zBoŌA:BjAzBopAzAFKBZIz'BoHACAsAB7z}BoşA:}BBMzVBop A/A(OC{9zBoSAdA4CQz BoP?AAkTDX=zBo?A^?BoUTAA LVC07zVBoUTAA LVC07zVBoYJAACAlzVBopAACICzVBopAACICzVBop A޺AfC ga|BoAI?A tC|BoȂAL@C#|%BoȱATA=;^A|'BoAJ@ΫC|(BoȆAM3@Z#B|BoEAAMCב|BoA݇A"CЬ|BoºAb@mD;A|BoBAAC|BoA׵UCJ| BoUAB^Ce |BoAB<:^|"Bo ABpC˂U|BoAR`ANCAE|&Bo-ABY2C|'BoŲA:B|*Bo+A@|,BoAS@߰B_V|,BoȱATA=;^A|4BoUAB^Ce |J@BofA@Гu|JNBoAnEC|JvBokAq@*A_|JBodA"@^|JBoAAB=oBf>|q]BoȱATA=;^A|q`BoŃANAP|qhBooAsACKW|qjBoABBp?|qlBo˘A@K|qyBopAs@1A/|qBoAQt@By|qBoȮAN@!{C|qBoAR@8C G|qBoOADAxZAl|qBopAs@1A/|qBoA@|'Cxe|qBoƠAAVDj|qBoOA@լ|BǸ|qBoAR=?шA|qBoȽAVbBC W|qBopAs@1A/|BodA+AًD C|BoAU+GCE|BoA'A|B|BoA;Bfx|BosA2B?4Cg|BoAdB8BXh%|BoxA"ABٜ|BoAֽ}OC <|BoAՌ rB~|BoARBY=A|BoAABBxC| Bo A9B(YB`|!BoMA؂LBͨI|#Bo6A׍B9\Ac{|&BoABD|'BoABD }BpgAA,BA} Bp_AA)C9}ZBpvA}\D)}=Bo'A|B6C} BoJA@};BpAAscC#8}AжSCg}/+BpAAՃ}/SBpAKA}JBoAQAK}K*BoVAtBS}K-Bo,AEBoABE>BoABWEhBo$AW7APJEBo8~AAmXDBo/A7A1EABo*gA\B:qVF/8*Bo?pA lAE>KBoAAAE7+BnAAD*BnFAADיCBnkAAZD-BoAA#P?Cl!2BndAAcDBo2A/ADBoAfA$DBo,_AAA>E+M/BnțAB 71BnAASC\2Bn0AxAD$ Bn4A<#B3DF 8BnAB@cEd lBnA^VB6)EۖHBnAAAgE?Bn׶ANAE`BoBoA@BE|qBnEAiKBTEZBnAAnHDj0BoAB0N-D<5BnAYABBES6BnAC B+7BnGA\BUED!BonABUE!BoA~?}fD+,!BoyAwCFc 0!Bo AB Da !Bo#AYA(&D.%&=BoABksD2[&?BoABfC4&BoH/AAɣ&BoAB}'BnAw{BF s(Bn3A?B Co@(BnA4BϪ)Bo"AE>*/BnA$A{*0BnA@D}],BnrACJE!,BnASBEբ,BnAC'F+,BoSAyiBuoE3,BnABD4 ,Bn3ACFXEF.[BnARBD/=.BnABYI.BoA%BaD v.BoA_xC%F1KBo&jAMBEX42\BnAbBLB\2]BnA_BjDSG2BnΪA[ @`ʥDW ]3lBo A ALE]3oBn\AAWCmϭ3BoKAAn]ESI<3BoABdYD3Bo ABB*@E+3Bo1AA4Bo AAff4BoeA~@D4[BoM#A(ApEUR6BoAADy6 Bo 3ABn-D'FC>BnAzBU|E!?]BoABvEPBnABDPR ]Bn[AjIBE(]Bn$A\BE=3]BnA8Bm>YE]Bn9ABqF+q]Bo5ABFD]Bo[ATB`DQJ]Bn[Ar=A ]BnYAh1BUE~]BnActB2WE ]Bo"A`AUEw]Bn5AYikE/Z=]BnA@qEe&]BnIAX˜%E"\]BnA1B/D]BnABR^D~1]BnABaKD.]BnǭABEcP^BnϥAB`vDЈ^BnТA7BRDp^!BnbAB^/BoAjBHVE ^0Bn ABZ)E04^1BnACz*E,^2Bo A,B BmD<^3BnAy'BE@O^4BnAC3Eyu^ApAC\r gBo{AY3Av CTlBoĐA NAfD6 *mBoĐA NAfD6 *pBoAA5CBoA A4CQ Bo{ABC.RBoVA.AD&k^!BoMA4yC~QOBoDA"AqwDPBoAA/QBo,A(zBo$APADQuV(|BoTAOACC`z(}BoAPAJ2D |P6BoWAVOD6BoA@mh7BoBpEAAzDG)BpAABD);BpAAeD@dE4SBojAA|BoSAA|GDg7BoQAlA\oDaBoXA[B)E)X5BokAA DBoOAqAqE?Bob7AsADf7MBo\AAݒBBoc>AAymDfBobAANBD_yBod AEDJBobAbBD}CD  Boa`AAB|!BodlA_fD&ZSgBo0A-BoT8A@RCi,BoehA^AqAiBoAiB9BoAB *C>BoAGBE\BolAaAD]BocAA(E_!BonRAAD}Bon AAUDvBol8AAwVD~("BoEAACn*"Bo2AADS#LBoeAA# C8##MBoeA{AUB$BosAsAP%KBoeAuB/A=&=BoArB&?BoAB)'>Bo[A9AD=s'?BnA{BE'ABoPAA{vC;|)BoLpAAV5?Cö)Bo:BA 1*IBoo.AAMKDM*JBokqAAD|,sBoIAAiE&E8,BoMAnBPb,BoTAA(D,BoAuB 2GBoqCAzA2]BnaAkBq'3BoOAA.EҢ3BoO=AA~LE3BoKRA@E3BoHoAAM:Epz3BoNDAA^+$E%6Bo$AB E=6 BoAWBkkDV6>Bo`AA6?Bo`AAdZ>BoRYAA]4EU/f>Bo7 AATEg>Bo\'AA:3Dz>Bo]*ARE >BoZ~AtAaRD\>BoUNAoAqE`>Bo\ALADW>Bo_APA:GnDz>BoeNAAtC2>Bo]A>Dx[>BoenABUCa>BoWA@rvDd>BoXAYޠGEE%>Bo\gAASCG>BoPoAA>EB>BoVAsAwD[P>BoF.ARAN%DC>BoeAAִOBi? BoK AAqD}??BoEAAD9|{?Boc?AAA?Bo]AABDT?BoaAiBmDi_?Bo`AAdZ?BoaGA=BPhD^@?%Boa2AKVOD 1?&Boe>A6ACai?(BoeAALC?2Boe;AsACv\?3Bo_qAuє/D?4Bo^AADI?6Bo]}AA-\D??Bo^AAHeD?GBoCpAAZC]?]BoAtBEL^1BoABGFsCM^4BoA-BtBoaAAQyED2BoZA A{E:]Bob}AA{+DBofALAm7Bo\@AAD^BohA-AiA#DPdBoXAABo%AA^ĜBoX{AkAFDBBoA.ByCfBorAx:ABoABR=EoBofAtAEtjBo0A;A9DkBo97AkAD'Bo^AC1D]?Bo^DABwgD^Bo[AaB DDBo_2AAjC|Bo^ZA B~ASIBo^ATBDIo<BoQA)Aɮ@c~BoQAe@YBBoqABFEpBoA)C'9EXBn~A|BFE*7Bo96A#? KDԅBoAܭBE`BnAB(EBoAիBCEwÓBoAJB1BBnQAB*DNBoARBPA5BnAt)B9Df7BnAT)BC% Bo'YA{B;E"Bo:A^AwfE+"BoBowABE%X&?Bo AAɭE&Bo&AgBE'BnmA BY'Bo?A^?AZ}@RD'Bo9AP@D:F'Bo7iAJAGhCh 'Bo8BALA2C99(2BoRAB~E(4Bo#AAE_(5BoyAB sEeA)Bo1A=AD$})Bo5(AE@HD)Bo6/AH%A'IDV])Bo5AAE~C)Bo'YABRjER6)Bo2A(A,ELL)Bo)A.?E=i,sBoFZAA%ENd,vBo5AB E4,BnA?=BFF:,Bn AZkBWE),BoABDp,BnAwB-BoV@AA$=(D HF-BoUAA(f7D -BoV@AA$=(D HF-BoVUAAY8D.BnaAB@EG.BnA[BEw. Bn+A֚BQJE!. BnAEB2C/Bo5AFAb"C/Bo8ANA#cE~/Bo6AIp@!Dɘ/BoDAi@uD1KBo3 A5Dp2\BnA`BD(2]BnޜAuCϭE2^BnACzBܵEG2_BnFA5BEA3BoB-AA[aD3Bo83AAEDE3BoHA@E"3BoHuAA{D< 6Bo3AAE^6 Bo#A{B +>BoABE>BoDAeAyLE%/T>Bo AAZDw>Bo<0A A1IE~D3>BoTjAAbN>Bo,AAyEr>Bo66Ao5?>BoHAADd>BoRA,AH>Bo4CA;@M{D'? Bo9AGA],EkS?Bo=AAEh?GBoCfA AdD5?]Bo~AB'@E#PBnA=BÆE5P Bn֠ABƽDIbP Bn[ABD칬P BnABrXE4PBnܼA`CFBoPBnݻABΏExPBnABӺD(KPBn8ABܒDPuPBnKA}BC>PBnABC PBnAS}B~>EzEPBo2ABA3 BCPBnABFB}UBoONAAk/D'HUBoM^AA]BoUA??F 2C/VRBo>A\AwDVZBo+SA.@AKDWVrBo1A=A|VBoAB uEvVBoXANADVBoWAAl(4De_VBoY@A%A1CVBoZcAAȴVBoXAQAXDHEVBo]#A7AAM]BnݯA>>BzEf]BnAPBE*]Bn5ACBP^BnAbBy?k?^1BnAyB[DYo)^4Bn=AviBbE;fBo{AY3Av CThBo{AY3Av CTiBow6AgADMjBoAKAekD3kBokAM@Az2lD6BoQAAkC4V BoD=AAC\- Bo{AY3Av CT!Bo/A9@{ &BoPAA D|(BoZA AC!;BoɝAOAXBoLA\A?}BoA3AA!8E@ BoHA A2D\uBo7A" AEBBoW!AAz C-dBolHAA~ JB|Bo%A;AaCA;(BM9BCvyF(BM>BC6E( BM:_BAC%DG6-(BM?UBC(BM@ABC HDM(BMBCCFBC+(BM6gBC4DS(BLBmF (BLB]ÓF)(BM BBZFQ!(BLfBx,CAE (BM9B~dC'ήDa(BM:hBRC)-D5(BM5BrCP~>EO+(BMrB֩CD`(BM+BC-f%(BLB^dÕMF*(BLBCf8E/(BLBHCcHE6(BMB-C>{EX(BMB*CnDPocV6BjDBFvD+tF cV6Bj>BF"8C,yEcV6Bj?BF$ZCOLF6cV6BjcBFm{BǔiD$cV6Bj_BFf8BтDcV6Bjg"BFeC+iDcV6Bj`BFn'C]E?cV6BjVBFICNELcV7Bj`GBFsC-EcV7BjPkBFLCs`E‡cV7BjiBFUBKEeDocV7BjI[BFrDhFocV7BjKBFK~C"tE#cV7Bj]BFDCKECcV7 BjzBF}rC+EcV7 Bjs BFiC\E$cV7Bjw*BFC[E{\cV7BjrBFiBo:EHcV7BjtTBF_oC(+EKhcV7BjBFr5C+CrcV7BjBFt*C CcV7BjjBFreC(uA9cV7QBj{BFC D[cV7RBjxBFCDVcV7SBjwBFzBE7cV7\BjvBFJBnYE0jhcV7]Bj{gBFSC D%cV7_BjyBFC4DcV7`BjtBFCEB]cV7aBj}BFB]&Ef-cV7pBjBFiCz8Ev\WcV7sBjVBF{CECM4cV7tBj|BFk5CE>cV7uBjkBFiEC#`jEmcV7yBjk BFn6C PD&#cV7zBjhBFrAa?E/^cV7{BjmXBF] CH4%E ucV7}BjocBF*C EicV7BjoBFaAsdEQ4cV7BjiBFCNǩE ӹcV7Bj`BFsC*D۸*cV7BjfBFpcV7BjkBF:CIoDހWcV7BjhBFCA&6EcV7Bj]BF-BEe0cV7BjiBFCaETcV7Bj[HBF;BD1cV7Bj\BFBDcV7Bj_BFC\D cV7BjZ#BF"AtEcV7BjoBFC85DV>cV7Bji BFwC(ME LcV7BjhBF:C#kDcV8BjqBFC36D5cV8Bjd BFC{ME?cV8BjqBFC&LE vtcV8Bj\BFvCҎvFcV8BjtBFC?,E*|cV8BjfxBFqCEcV8BjbsBFC.DƫcV8BjkBFCI)E)cV8BjjIBFC$E#MTcV8BjdhBFCE5cV8BjiBFCnEztcV8#Bj[MBF6CsDqcV8$BjSBFC}DcV8%BjS/BFB;-DcV8-BjWmBFC!2DCCcV8.BjTCBF}B|D[cV8/BjWBFjfC2EkcV87BjP4BFCcZDqcV88BjOBFsCD9XcV89BjRJBFioC{}E=XcV8;BjNcV8Bj@BFC1`DDxcV8BjBFC1CDBXcV8BjBFd4ChkEXScV8BjBFn[BnEd_pcV8Bj|BFeCˊEcV8BjU"BFCC,DXcV8BjQBFBD[cV8BjRBFCDԩcV8BjXnBFBvECcV8BjR"BFC"WEXcV8BjBBFMCYE.cV9 BjBF{tC+CicV9 BjBF|CD5D&cV9 BjBFuGBv}Dgc+cV9BjkBFcCOD8cV9BjjBFCYJDscV9BjnBFXC^G;D+cV9'Bjt|BFmC-BγcV9(BjqBF_CoD8cV9)BjuQBF$C'DD?icV9,BjqBFBn@D 9cV92BjO8BFCD'ɒcV93BjPBFCYEDH1cVSBj;ZBFxC$%ChcV^Bj@PBFCcV^BjiBFf2C*DocV^Bjl\BFVC'+cV^BjFBF6WC cV^Bj>oBF$C$'cWBiXBE+9B|OEf@cW6Bj?gBF¬C CBXcW6BjCcW^{BiBECE cW^Bj(RBEC!ErKcW^BjBEBzFcW^Bj@BFCuB#cW^BiBECѤBecW^Bi!BE/BsFcW^BihBENEcW^BiֳBE=“F3cW^BiBE3p$FDcW^BiSBE&LCF8cW^BjrBFpËyEOcW^Bj\BFN$CTEcW^BiсBEB3C4NF$2ucW^Bj7FBFCE cW^Bj-~BECFؼcW^BjBECF>cW_BiBEC @bb+cW`Bj2BEeC TFcW`BjBE(C*E㓩cW`Bj BECFic BL BtCy*E宰c BLBC(XE-c BLBrAFc BLBqtC<qD^c BLMB`CREc BLBl$FZec 'BLB]î0F,cc )BLB[C>NeE%}c BLBiBEc BMZB=gC^C?c BM*BIC_{cBM;BC#'RA4cBM1BC(DcBMBClE$fcBMC;BCB2cBM=BC?DcBM=BA˟D'cBM>B?CcBMBC1gEc,BM=#BCDgcqBM5zBC#L|DPcBMC4B6CUBt~cBM;BhC&wB cBMBB-CUC1zcBM9#BQC 9DRcBM:cBMC0AcBMCBRC& cBM>BC+qC_FScBM@!BmCJ>lDP! cBMBBC!CcBM?BC%VcBM?BCdVD%cBM@B%CD3cBMBrCzEcBMBTDCOEbcBM@BTD/7FGcBM/TBC DB'cBM XBCFcBM4BC*шDc6BM:BoC ;D_c:BM6}BkCcBMBD3E FG9cBM7BD^FN cBM"B8,Ec4BM"BţBŝFmc5BM>BCPDmc8BM:BCE8F:c9BM=BCP/SD(QcVBjdbBF^;C"7CWeaBWTA8\CUcrBWDAIeCdAFArBW/AI}CgrBWAC`DSBrBWA CoAI)Cd d@:r BWHAI\Celr zBWAxCL+r }BWACKGC[ BWkAR)Cb~ABWAqCF^wBWAGCeC}o)BWAܧC`Y+BWBAܒCRF-BWAܗ|CV4CI|BWAܒCXDVluBW-A܏CJlDbۧBWA}CKBBWA܃CL|IAiBWAy=CI=D1BWAaCZ3BWAkCB DBWAqCLC$DZBWA3:CDfVBWAAwCϔDuBWA=zCȊD_;BWA4{CDDJBWKAܟCpBWAܕCTBWAܧC^@iBWA܂CXABW%AܔCU'D8 BW,A܄gCN(BɓBW[AܴCQ]BWA<CIBWAbYCU"BSEBWĻAܞC,oDFBWAܘwC=,DznGBW(AܒCMC&HBW AܝC85DIBW#AܘCW]D<JBWPAܑCES|BqBWA܊Cv^D ,BWpAqCKCBWyAfTCUD(BWAlCSHBW.A܄VCMzB?BW:A: CmhD\@BWA1ChB'D38BBWA?C['CFCBWhA2ClDOrBWAܙCZ=qBW6AܜCsBWA܏CR$iCEBWAܬUCACVBW|AܣCYmD=BW6AܧCjDIBW4AܧCAD<`BW5AܥYCo5D$BWAܹ)CRBWAܹCODSBWAܱJCXXDLBWAܹLCv DBBWAܬCm4D[BWƪAܮ^C]DHBWAAܪCeYDY&BWŻAܶCUBWAܬCjD1FBWAܫC_cD=BWAܛCdEBWAܧC_VHBWȶAܧCaVSBWAlCCcC]OBWAكCMQD@aBWArCMBBW\ACEsDBWyACQC6"BWAܧC_fA,BWAܥCZiVDy8BW,Aܜ.CdȎC2BW\AܯCUC8@BWUAܣYCrCUBW?AܟChCBW5AܪC[ CBWAܫ CZbBWaAܛvCZB %BWAܨ$CfD$ԝBWAܚ%CMɺBWAܬTCf^SD9BW8Aܮ9CiDnBWAOCGHiB3 BWȺAܧC` BWA6CMa BWACD BWMAܾCA D BWAܽCr1DW BWAܼ0C0DHXU BWAܼCO=Dm+ BWACJ$OC BW$A)CoBWAܫPC[B3%$BWwAܛC_C$8$BWAܝCgIC.eBWA$eCVh$BݘhBWA"C_aD+BWA'C\5^D 1sBWzACKbBWVAcCL DhBWACGBWRA#?CQqBWA yC޽DrBWACGvD[[sBWEAMCAFnD'tBWA CN5CuBWAUCD}D<BWA'aC[vDC~BWA)C]D BWkA"C^^D=BWA'UC[{oDBBWA(+CZD&^BWA$C[VADzBWA?CJCWBWA+CH[$C_OBWeA OBDPBWA C4UDvQBWAvCq`D#6RBWACQ~ DSBWA C:ŦD8TBWA?CXID1TBWA'lCVpCDUBWA&aCYQCBWAkCHBIj CBWAC[^CT FBWJACYD A BWACIWB BW*A#mCS>5 BWAA]AC BWA DCVoBWAC]DJBYAAg=C?E>BPxAEBhEhBTPABDG/hBP<ABBRAJ}BGQBP.A{BPG1lBPzBAKC yBP.8AS)C2C,BUHA@;CBCe#BY_ATCJ'hEu;FBV@A9B^GQ}JBX}FAҢCBgFKBX!>AyCf#D*lMBWA DCVoNBXkAC%CBRtA2CB BPAC XEM˅FBVϫAC[A MBWgALwCCDs NBWAKCgDJ BWJAICn2Cv BWA=@C^٫DΟ BWA:Cp[Dq BWpA"CQ D7 BWA&C]C? BWA^ClDt) BWAChD 7 BW[AVC`pC BWANC_ BWaALOC`/lBW BWAQ\CQ7 BWABCkDg; BWoA60D BBW8AܬCQPbB  JBWxAܳVCTCD[ KBW\Aܴ!CYtB UBWAܾCkaBf BWAܾCdj^@  BWACrPDK BWAܷCTvcC BWA˗C{ C% BWAiCQE6 BWAܿlCeDf +BWAܯCHA -BWKAܜ}COvB% .BWAܵCRzD /BWAܷ1CY?W aBW~ACUCQ BWUA$CSzDt BWACvHD BW5ACv D/r BWwAܿ?C_ Dئ BWAeCkIDM  UBWACkECX CBWpACVo EBW~ACUCQ 'BWMACSa 'nBWAܳ0CSoB7 'BW[AܱCQC>G REBWPAܿ CaC RGBWAܺdCLB~/ RHBWAܻMChD!&< RIBW!AܸCojD RJBWAܸ:C]DgW RwBW AܳCTFD K RxBWAܶxCYVBW RzBWMAܬoCNC4& VBWAͅCrbaBQ  VBW?A*CŃDBWZACqN BWAMCkBC[VBWFAHCoB&"jBWRAICiu@ZABWgAICiBObBWAMCXDIBWA)C^CG%BWMAIUC]AD^&BWOAIKC^)A{*BWA۱CzC%-BWA"CDb.BWAhC[FC6BWAmCC7BW`A۽Cx"DZ8BWA@C6&De9BWAМC[YD[:BWAۺCD|U-ABWAACLB^CBWA]C6hD5rBWYAICjs8AF\+NBWA CDEpPBWHA+eCG^Az#QBW AC-vDBRBWAhCBvBWACj!wBWACnٔB xBWAuC BcW6yBWA zCDvzBWACzDe.BW0ACshD.FBWACTD.BWAkCe'D;BWACr-DTxBWAmC_eDBWACb|ByqBWiAHCjlMBBWBA;[CX:DBWAmCe;BWACeBBWA CºCЬBWAACqgBWAcCkj=BW AhCZMC BWoAHCp3BsABWSA+CGBWxAMTC[nC'lBWApCZDxjBWdAHCrBWACvhChJBWdA۾CG BW}ACZCBWRAcC9DdBWAٗCuzB7 BWACS>~D BWrA~CNYCY BWAHCu$DT 9BWACTD HU :BW|A@CRA#D ;BWACJDa BWAC>E&vh BWA*CTCy BW"ACOEdL BWA C[ ~Dɻ BWFACjl?E% BWA!CQ BWACkqjCR\BW[AVC`pCVBWaAHCg)BzV BWWAC+C.V BWAlCvrC7W=BWACPsC]*W>BWAC_SD*W?BWAC`W@BWACA%DrWnBWACaTCWpBWACsVDOxWqBWA$C3tDWrBWA}CR'D]HWBWyAI>CtSBZBWAPC]&C<BWACT%%D^BWACJDAYaBWhAxC*SD0hbBWiACUDs'BWACH}BWABCUxC#BWA >CrkD iBWACKC  jBWA CLC ABWACH,A) BBWACLwC CBWALCW~sC)x DBWnAxC.D1+] EBWQAYC)&D, FBWACXn`DP<W BW.ACV,JXBWABCOC8EBY-AdCO7C3|NBYXBY:A֏zC`FYBYAeCKEU4BY, AnC"BY~AC8E"kBY*AwC#GDj$"lBY)A}rCFzER"mBYQADF "nBY9A֬CFғC:"vBY7AֽhCK-"wBYaiAշUCFX"yBY8AֵCI{DL"zBY;A֘C9/D5"BY)AׄgC9OWE\"BY*XAR/C3eEI\"BYc{AՖ7C"BY2|AsC89CFn"BY)!AlC?{Ey"BY4ACF3D\"BY46ABC=HD׎"BY.ZA:C)E<"BY4EAާCFDe"BY"A׼^CEi76BYXlAչPDLDLApBY8C=DϷ2BY8zAַCFҟD;>BY(/AqCAEXCBY/AC7CBY%jAז:C,pCJk@BYbAPC֍DQBY^AC=E2ρBY?3AcC#D#BYPHAMD0FKBY=3AC.tD.BYOZADC5EPTBY,A3C-bBY:A֤'CCZBY(AiOCBuEǡBY/7A2qC(_E^BYTeAC.F~ BY/A-C$kEqcBY0AA4C7GD BY1AOCfBUFALC5EIBUvAC7BU:fAC?LZVBVA٤CHJBPrAB%ĎDLBPvAǘC0E#MBPr&ABYhE<NBPsEAC2EOBPA$B;DaTAEC,kGEg^BTv2A6C E5%BTfwA+C,ޅE&BTPACE'BT\$ACQ@Eˌ(BTiA$%C&xEl)BTOAC>\EJ*BTe%A /C/+Ep'WBTACOF*|XBTޘA7C$"eFXlYBTA$C8XEGaBT9AbC9huEMdBT@AДqC5;F5iBU1DAҩC%FrBUAQ0C84)FoBUAC/9E&7BU%AҫsC@dD?\JBUeAӢC4:CN}3kcBTYAX;C1JEJ?kdBTsA3BE2kfBTAϒC,FkgBT|qA>VC-&E_koBTSA{C-EYkrBTcAϢC6D QkBTA?C>ekBTAύC.xEh5kBTAC3ȹE$-kBTʧAдC?w9D+UkBTAϯC6} EX4kBT4AWC1 kBT]OAC3QDg}kBUAA9C;HE.3kBUKAOC2(EkBUBUAֳC(AD^BTAHCJEEBUA֊CN_EhHBTAC< FhKBU/AC(cBU7GA*C?VE!ABTAЈC:E5BBBT1AϩC5DE}CBTA϶C1ETDBTxA=C5YEƆEBThAAcC6=BTBTXAώcC>1BT Af\C?sBTAϧC,BUtAfC/]!F!BTAсoC&E"BTAѠC/E0feBT@A_iC;hBT׉AvCUÅFCjBTwAC3oE7WBUAxIC3XDEOXBU+ALC4QF_lWYBUA9C9WE9ZBU01AC>CE<[BU A҃QC-fF//&\BUAbC7QXEҖ]BQ]rAgDCF;BQ!XAB{C X FBQhAŊC mF($dBPLAHC+fBP[AB=qgBPOACN9D-wBPaACoDzBPdmABD!ģ{BPc*AC|?DPs7|BPeVA7CCBP7ABE wBP?ABD`(qBP[xABDlsBPZABCtBP[ABD-uBP[ABD4vBPZ6AB=Cs.BBPMhAC C(BP`A8CD'BP]ACUD$BBPMA-C YD7BBPMHAC-CBBPLmABDQiBP]AB;iBPZA,CDrxiBP\(ACc7C{ZiBPeAjCC=!iBPdAC!HBx4#iBPAA RB8C{\iBP?]ABCjMBP_AC BxjNBPbAC_C-jPBP`FACDnjjBPTA\CACWHjkBPWBAC oC6jlBP[AC iCg8jmBPR0ACwDkjnBPWAC TDcjtBPLsAB}CˬojwBPLbAByD"jxBP=tAyC&BP+cA;BBP]AgBBP\ABwD6BP[yAC߇BPeOACCy߈BPbrAB]Dj߉BPeApB=#BáߊBPeQAgCGCoߏBPZfAC &B\ߑBPZA^C1dDP;JߒBPZACDMkߓBPVACD)vߔBP]AC=D"ɍBP(oA4C)BR-hAx$C rCUBQzA:CF BQ;AɨC3E!=BQIACL2FBQ޴A_LC$FL0BQA CSEBQĤACaEʤ BQAWNC/F3\BQiAƶC@F,;BQQAwCDFfABQAƣCsHEe`BRjJAF:C F9BRCAȿrCrEaBRQACNE9BRA63CG:FBRAu'C |YEBROAߠCBRޑATCF0BR(AʵC E@WBRAWC EFsBS6^A̗9CθF\~TBS&A CbFZ BS'A]CF_BSiAGC(O\F3BSJAKFC*CF/%BSMA̢)CFBTcAsC5F3BSAC3[FGBSAͳCHF%BT>AC7{CGBRݫAGCp}E?BRMAfC eE!BQmAřCDkBQ Ar_C?DBR`A^C gF)}~BRA̞C˅l]BR%Al?CDn!Ll`BRALCkDܓnlBSAvMC7DtlBS{AEC%5F|[lBTJAxC3F{glBSAӝC>WDEm:BQE*ACPXEB>BQIACCBQPA/C(BQACLC\GIBQAǁHCbEγBS1Ä CE`BSAXC aBSACtF5 BRAˆCBFȢ"BR"AC IC6$BRAAC ET*BRKAmC/E9OBTHA ;C5+BQ+FAĘCQCdBQ7#Aļ Co+BQ4AĵC xBR_AuLClE;BRwALC ,F -BS>jA̶,CxF)VBS;,A̘C[5C]_BS"jAkC EbBS^)A*C/CBRACBR.ACDSevBQ]ADCNBQy^AżCFP3BQAǘ`C kEa:BRAW2C H7F=BRbAʯC MCBR=Aȝ"CfEuDBR- AwCfEwEBR)kAj3CUEvFBRCAȿrCrEaGBR%A_CEBOBQAQC FQBQA{ZC SFGRBQDAǾXC]FIWBRayA/CCaBRcA.C F:cBRvAxNCEBR-AȄC MF#ߠBQJA|C#BRyA=C hEBQA;C FGBRAGC;DBTCAC5E%{3BR.A;C_E&'BRA;C4E5BPAClFZ4D6BPfAB=E`7BPbACtE|K?BP{AZC`F.!@BPASC1kFSABPAA}C>EEmBBPvA{dCFYZCBPA,CsEzmDBPAaCJFeJBPAG(C EKBP*A.UCFHLBPAHCK@|E tMBPA…C#5EqNBPhACF$WBPqAiTC9wEwXBP^AShC.EBQ* ApCFR'FBPAlC MDDTBQ%A/COE3dBQA5C]DRBPANBDBQcAW>CfDBQAʭCCDCOBPA DCEgCPBPACBECYBPA,CEjCZBPSAxC LcEP8C[BP/Am>C>EujBPuA™rC+jBPANBDkBPAܔBFkBP~AACDMkBPqA CQF"kBPqA5BD/kBP{AjBBTElBPAŒC rElBP4A¤C DZCԿlBPAC lBPHA´B>*E4lBPAlC DumlhBPlA¨C llBP%AC3umNBPA}C1'BPfAðBFBQ=A,C EXBPA6pCEE<=BQA>CFBPeAÎ0CEWBPACGE7cBP(A#CF)BPAÛC-F:BP%A4CrEF BPuACcE sBP A+CjFtBPTAC>\EXBQ=AC ^wdBQ1AīC>DnIǁBPʦA.CؓǂBPXAcCNDŽBPUA(C}dE )BQ9AļvC FHI*BQOA-Cc+BQAB!FtOBQACw BPZAÁ C JBPAdC D臛1BPAkCEoAR2BPAB F&ݦBPJAyBơE[PݧBPAGCvFDbݨBP.A`C JyEaݩBPA;B7E IݪBPAUCE5aBQAʭCCDBPA×C9EBPuAC R!Dq BQRA-Co/DBPAC zBP%A®IBgD3BPACE+fNBP)ABEG ~BPA}C1'BP`OAB;ABPAMBևbBPbAC/BBPSACkBPL0AOB,BPPA/C xmD9BP9ABDvE%BPAHBDBP{AWCf9BPLAHC+jBPA CbߑBPSACkqBPAAQB=mDțBP1DABFBBP1AUCWFe%BP,AKCBP>AwBiBP@A}B!DiBP&ACDiBP@`AB%DuiBP=ABYtDBP1A@BFgBP&ACFDZBP>AJB5CWRBP8ABfDr:BP'NACYLE etBP>8AfBiC۷BP5'As]BE`9kBTASC2-l.BT\ACBVABڷBۋ<BV AC(sBV:ACpE BVAߣB^D+BVAZBD?BVAOCE4EhV)BVAؓBE `BVJAB$D>BVAuC4D PBVABE[ QBV$A|C=Dj RBVABh3EN SBVARSAFiG TBVABm$E7 ^BVAYBnC*Y! |BVACC, BVVABj BVWA0CIy"BVA&B)Cc"BVAɀB9Do/"BV0ACD"ZBV8A̻CDN7"[BVjAXC:DѨ"\BVuA>C LHBVf AB)C{~moBVAKCݔD1mpmpBVA.CpmBVACdmBV]ABΖCmBVACDHmBV4ABOCamBV_AC DGmBVABDFLwmBV_AC dDۇJmBVAB{DbmBV:A BSDmBV"A'CVEmBVjABDTmBVAB5D\@mBVARC 9mBVYAB6mDtDpmBVpAC6DkHn BVAC wD˺nBVAC*BnBVAűCD''nBVAC "C[ϟnBVACnBVAB?CnJBVwA4BABI{nUBVAMCB n[BVA83CytEW4n_BVvAbB3Cn`BVAE]C wdDDntBVkjAdCnBVA\C\D nBV|AC"ҷF9nBVA6!CnBV{AJBLFi)oBVyACX;oBVVACnqBVAEB'qBVAC BVABBV|ABvDU=BVA1C1lD5BVACBVUACD>~BVAQCV9BHBVA*BFUDj@BVdACxAVGBVBAiC0Dq_BV&ABBBV/AtCiE&yBVA~Cx!EBVA_C`EOBVAZCȣE'BVACA8BVAYBJBVACKNBVĝACC/BVA)CR%BVAӝCC&BVABCv+BVACKD .BV>AݍBkkDG/BVA+BW`DlBV ABBVDBVA2CiBVA`C5|E&BVXApBqBVAHfC#F! BVKA CFa BVA5C6FD BVKAv]CF BW AȀC@FF BVACFBy BVAǔC"+xFx !BWF9AYCFGI !BW3AI}CVFD!BW6AީC F8SH! BWl0A̲C+3F/!BWX{A)vF1jG!BW^aA =BzF'*!BWAiCZF{!BWtAuCFZ&p!BWllAJC.YF0!!BW:A57F!"BWFA3C$pFF!#BWAdC޷yF!+BWA"C Fj!,BWAB{F7 !-BW)AAC&IF%!|BWӹA|B~ZDc!BW\AigC7!BW^rADB!BWVAʯCFM!BWYABvD!BWSABfE !BWIALCF,L!BW]Aɷ}C9,E!BVVPAÓCd!BVA/C2E1H!BVzAŀC";F R!BVAC+E!BVxAJCF9\F"BYFAӪCCDC׼ "DBYAӹTC'CE"BW΍AQ@BUCY;"BWRAъ1BxdD"BYAӠ CH'BWA#CFfʐH1BW}AаB[vF5 \BWeAѲ|B"UDlkBULANC?kBU0PAC+3"D'SSkBUPAXC-nDC9nzBWA0CӞ"Fun{BWABDj[nBWAжGB\OFm3oBWmA̾BFȆwoBWV=+BVj6AĥC+D,y,BVdAfRC x9E-BVjAġCD@BVAC2P[Ef?IBVAǣDCF<JBVA8CF>6KBVۑAuJC3!F%WLBVAǧ|CE[NBV8A:C9hF:d]BWiAZBF_BWWAʣtC Fr`BWfQABݮWFuaBWQ'A%C'DNAbBWV~AʶC UFyqBWAx|BF[֬rBWCA5fFsBWABV%Eϲ>BWA(aB\FGBWo?ABpFyBVilAĴ=C%E0%BVdA~C!,E۱XBU+tAC?DZBUXAvC,E[BU*AC@?C wBWU:AɢC;F1zBW^kA=CojE`uBYAωC C' 4BVAſC+Ff5BVrAC@ FN7BVybA_5CF$ e8BVfAĶC NLF/@ BVPA~B%DJ SBVwCAC:E_ {BVpACF>Q |BV$A9qCC3 BVQAFBwCF,] BVmA CFOr BVSmA3B~E/ BVUAPBݱFD  BVoAC'@Fz BVbAadC+FY` BV_WA,CFF  BV[AÇ|A2Fe !BVVAæuCy HBVh8A&BE,z HBVrAC E n[BVxaAtC4Es n]BVAmCLEps n^BVwACs F  n_BVl0AB{SEI n`BVx>ACwE'c noBVNAcIC5ynES npBVPAzB%D nqBVjA!]CvrFV_ nrBVR$AQB F0p nsBVZAB*KFG} ntBVjAMvBB3!XBVAC C+!BVA BtD/qO!BVTAzB~D'z|!BVAC2!BVBA{Ci?DZH!BVA)CrD蜍!BVKAB! BVbACD'=!"\BVA C !mxBV5AC '!mBVAIB!mBVA B}zCY!mBVABڷBۋ<!mBVAB!!mBV9ARCC;!mBVA?C2E)WC!mBVACPD*9!mBVACs!n"BVAC!nJBVA#ClD(a!qBVqAC{CK}!qBVAB3C)!qBVQAfCD6!qBVHAB}DPu!BVACC!BVA;CBG!8BVA CCd!9BVABزD%!WBVFAC jCN!aBVACI"bBViA0C CX"iBVA:CzD Y"jBVnA)CC}"lBVAHC XD!"mBV:ACCD&t"pBVAC'C\A"BVAC1'"BVACC"BVAfC C"vA"BV}ACDvD"BVAlC/ As"BVAC"BV[A CDY"BV"ACC "BVeA_C!;YC~"BVACD"BVכAzC 4BBV"BV|A\C"BVAC 4B䲠"JBVüACCYp"KBVüACCYp"LBVACtCF"MBVAHC8^D"NBV¹AuCsD <"BVAC0E#"BYA"C#E$|#6BYA#CqnEH3#pcBYA7C1E#pdBY AyCMEn#peBZAgC6wD#pfBYyA"yD܉E#pgBYLAYC\Eo\#phBZACSD#Fy#pBYNAԔCC#WBYAmB(HFt#XBYiAbfC BA#YBY9A5B/3FLI#BYJAӪ#CCrX@#BYA C@Eȓ#BYAOC@&Fzx#BYɲAHCŰF#BYLA?CvRDk #BYQA C- E #BYA=gC%DGE#BY0A#C>EL#BY_AC}EUZ#BY|ARC#BYСAпCnE #BYߒA~CXEGhu#BYAӌCaEʱ#BYA$RCpwE#BYA8CEfx#BYAZC.EK#BYKA9C0hE(b$BRAp"CdC 2$BVhAĄC;#$%BVlQAC#C%FBVAfCO%XBVACDE%YBVjAbCt%BVADBME!%BVAXC BB%BVAC Di%BVAC57GEA%BVuA2CɼDJ%BVAC>ND*%BV~ACC?go% BV'A C % TBVA_C" % BVAB{D%moBVAC%mvBVAXC BB%mxBVACa%mzBVAC kQCϗ%mBVAC YBT%mBVRAC0C%mBVAC D(g%mBVA{C RC]%mBVdACD?,%mBVVADCD$%mBVAMCEh%mBVA_C{D%mBV*AFBnBb%mBVA C #%mBV!AC eC~B%nBVA)CR%oBV&ACIC%oBVAA$CElCU%BVAC 5Bb,%BVA C #%BVA1CD %BVsA=C Cw%BVAICHC%BVAC3%BVAC`%]BVrAJBڮCS%`BVAC5aE l%aBV9AkC C%%bBVzAC}Cz %BVA?C C%+BVmACCd&%BVAXC BB%BVAC E@%BVFA)C%BVOAVZCnE8 %BVA.CsE$FBV' A3,C;XyE@FBWX A۲Cl,F BWL.Aۍ$CtF 1BWIAۃ_CvsCF BW*AJ`C{'DGwF BVA}CY;ETF BV!AڅuC[)E<F BWAںCaDUF BVAl1CN׍F BVA\C@4Er̆F BW&AڟCXFEF%F BVAl1CN׍F BVA CHsF&F BVA=CUuEF BW/AڢCYdwEA|F BVJAgFBUAKC.QFTjFBUAԜCF$F&FBU5A;IC-FAFBUǟAC>E+)FBU AC:lFFBUA`C2'F FBVAnC+:`FeFBVAְ^C6MwDɃFBVAoCFGFBV# A֔C,FXaCFBUA[NCUFCFBVA_CFQgFBVAiBbFHFBVEA׻CGHFIFBVv AgIC*|D4FBVA؎C(FFBVWA#CXE֌FBVxkAlC+EEbFBVbACBF2$FBVANC:FBVAٮRCHE̾ FBVɖAٱCIcEFBVAɆCI*"EsFBVMA]oC;_D*FBVA1nC;wEbRFBV AIC:ܓEwFBV AJC: C$F BV+A3PBsFtF BV&A qE$F!,BWA]C F!BBWeACCCrF!IBW~Aٛ)C9RF}[F!KBWֺAئ.CKSF )F!LBWAڦB\F).F!MBWLAdCEF!NBW(AB3FgF!SBX A֡CNFvF!TBXAֻ6C!F}8F!UBX%AD }FQL~F!VBX$AL$C&F.3F!WBWSA~Cr$F:F!qBW&A؁f9FFF!rBWAc(|Fl^F!sBX AsC]}F<F!uBWΆABF?F!vBWAhƒyFFk2'F!{BX6SA BFF!|BXADYFFmF!}BXAUACIDF!BX+CA%DF!`F"BX-|AԱQC;iE<F"BXAD[vFlF"BWAXB'F,F"BW#AҐBLFu:F4BWA UChJF4BWACC9E鎟F4BWA}7CXElFDBVAACHmFDBVG{ACGF8"FR'BWLA!CXEǹFR*BWAٽCE'FR+BWAʲC_>(CFRBV5AhC]ELFRBW XAگC\ EFYhFRBVAڒ:CZ1DFRBV8AFCYEFYBV[AFC9^FYBV:AؐCH D$FZGBY,AiC6FZHBX.AըICDlBFFZIBX*A&;B\F FZ_BWEAtCSFZBW uAڷC^E$FZBVNACM}F[ BVAلC=?CF[BX/9AfvB FDF[BX-AgCXF#<0F[BX0kAԺDFlF\JBUAC)FwF\OBV3AׁC>9UE?CF\QBV#A֚0C-F=F\mBVABCICC_Fk[BUAC6?F_>Fk\BVAC%FFk^BUAC*zFrFkBU=AC;E{FkBU/A)C+F,FkBUGBWuACBODGBWmACwD#"GBWlACUDVGBWmACZAmdC޴DG BWRA۠CpDF1G BWeA C_D OG BWVAۮClG0DG gBW A^CxTDgG jBWOAZCiVE G kBWwAC6IR{BWnAܸCl/rD% iIRBWA[CT`%CBIRBW*AYCS7)J=BX9AإMCF,RJ>BXAءCQ+Ef3JCBY8A,yBܰaEQJDBYA'CCZD0JEBY%+A׮ CcFxJBXACB5NCJBXbA7C{;E 8JBX9A CnVCDJBX3Aڣ}Cx#F78JBXlA`CHEBJBXjACp3JBWEAۋCS3JBW Aڥ|CtJBX޺ACQMEǧJ BXZAWCKDZJ*BXAl|C+oFB`J4BY)A|=CJyBXSaABFgJ"BYEA>z>tFJ5BWA٩C5D8JIBXkA-CC1JTDBX;A٥ACWkFJTEBXYApCF EoJYBWAdwC BPJ\BX1AiTCwdJ]BX,ApzC}xD&UJ]BX&CAuCr8JqBXAقCIF JqBXAaCgmEJqBXHAٽC]1F\!JqBXAC=bBE8JqBXAJCC0L BWACCwC! *L BWAە@CVDL BWzA;CvVL BWAECDL BW$AۖCV~\DNBXj^A@C&sDI7VNBXkAټC'|hCNBXkgAC,D ,NBXiAC!DNbBXkAټC(aDCoNbBXkAC(k|CNbBXkqA6C+C-YNbBXkACCNbBXjzAҿC."DSA_NbBXjzAҿC."DSA_NBXk-AָC#BeNBXiA.C%SD܃NBXk&AC @A9UNBXkAٸC%C*NBXjuAұC*DQNBXiAC'QDNBXk-AֳC"8A(NBXiAϋC&zDHbN BXkAC'ANBXkAC%aCyZBWA~CN:PBxZBWA|CNZCZyBWwA܍qCMCZ|BWwA܍qCMCZ}BWA܆CJ3D`Z'oBWAܛCM=D;zZRwBWFAܰCXscC4ZRzBWAܫ7CQAM ZR{BWiAܳCaTCZS0BWhAܑRCL6CS"BWCA9Cjθ@8 FBVAՁzC1CwBP@AByXwBP?AmB%FBVAC(EE<"FBUA9C!9E"$RFBV ACFXR1FBULAC(FF$1FBVAՍC6iy1FBVAՍC6iy2hFBUcAӞC2?E[2yBWACO\3BWuAދCQ3FBVuA}MC/3FBVAՁzC1C4FBUaAC,4BY;)A֞dCCCx5BWCA9Cjθ@8 5BY;$A֟CBR5BP<AB5BP<$A|BC 6BP;ABeBWuAދCQjBWACO\yBY,AiCJyBQDA `BE[+d}BLnuA>cBdmEJ/WBOAC#4B+BKAsC"0!yABKAsC"0!BOA C/uBQ^ A|MBZ*BPA;B5* BPfAYB*!BP AKByDG*[BPANB B*\BPAdxBZF6*KQBNqAAAqB+9YBPsACSu+9ZBOmA/BNF+9[BOABf+9dBOA\B[+9nBPACEEF[+>BQ@hAtmB!FL+>BQAK3CtF]H +>BQ@A!C qF!1)+?qBPAC E{+?BQbA(CFo[+?BQ OA 0|F+?BQTDAeAC &pF6+ABQ5]APGC+ABPAVCF+ABQAjt5Fh+CBPɘAC'F+JBPAxB5E%M+MYBPΧALC.E6+M[BPANC>F"&+N{BP AQCFb+N|BP ACCC+N}BPYA C*XEb+yiBPyAC2%F}+yjBPCAH,F@W+ykBQ*AC F{+z;BQA"B椽E+z+9BPADJCE!+BP,AiC9D<^+BP,AC4DoFC+BP)ACZDq+XBP܀AϹB+YBP3AKBͦf+BPfAWB校D'+$BP3AKBͦf+';BP3AKBͦf+'BPAqBa+(pBPAqBa+*BP܀AϹB+4BPABԥDӛ?+4BP3AKBͦf+5BPAqBa+5BPAqBa+:iBPYAڳB+=BPZAϊB|+BKACGE|'+BK:AC${D + %BJqACCH@,+/+,BPfA?Bi@Ϲ +/BPAKBSE"g+5BPA{B"C+6BP"ACMBɜ F2+88BPeAقrO\FA+89BPrA7B1E+9BPAVBaH+9BPHAJBpE f+9BP݋A/C;F.+9BPA.CFS+9BPAB;+@BPMA.ZBC-+ABPAtBaE+BBPABCR+LBPHABDg&+MBPCA4BFEu+M BP٨ABj+MBPVAsBB+BPAB~F1$+BP,Ar BbEj+!BP-ABVFA+"BPABgF+#BP̄A]BTD+BPޥAoBUMEd"+BPfAB|GL+*=BP/A+C`C➢+*EBPADFC+*FBPA]C+*GBP8Ar*9EGa+*PBPy,AyC jFjo+*QBPA`¨E+*YBPSA'CPuF+*ZBPxAt*F\+*cBP#(A}vu&F+*dBPQAC'Fm+*mBPA6C Fb+*nBPA0CtEM+*oBPAܳC)F_b3+*xBPVABANFO+1BQAE B+1BP1A$F@+1BQ.ABC+1BPWACwFB+1BQAEC*<5F$[+1BQAB CCdE}%+1BQA\C~F++6BQ\AԒBEx+6BQA'=CVGFD+6BQACF(j+>BQ|AZCWB+>BQBAg\CEBFm+?BQXAZCAB+@BPGA^B[F&G+@BPYAC`FR+ABP02AjqCC?+ABPw`ARC6}Cs+ABP4A%A%fF0+ABPHSA$C#E@[+ABPL.ACE+ABQ A?B4(B9+N|BPҲAxCTEp+NBQ^ABBW?>P+xQBPy ABвFP+xRBPeAcHC@FOd+xSBPzA*@Fh+xfBPAQW0Ff+yBQxAC2F/+y`BQAJC.E+yaBQACF-Eʚ+yiBPAC ]D@+yjBPOA&qE+ykBQAoC$_|A+y~BQApC_DFe+yBQdzA(CSFy#[+yBQAkBF5:;+yBPAdVBcFno+yBQ[A BF6+yBQAB/C/DHH+yBQEA@BƍE)+yBQAC7E+yBPAVWøFQ+yBP AXCE+yBP(AF +yBQAnUC@E},+yBQfACF5k@+yBQACZEʳa+yBPmA+CE1+yBP6A@C+yBPA/D F3+yBP0A,0D+F+yBPA,F'+zBP}Al/@F><+zBPACj)Ffσ+zBQaAC(+zBPAA$F^յ+zBQ'AQYjLF]F +z'BQA25DBP&A C +>BP$ARC./FFW+ABPEABOKACWF&\+F?BOAtC$+MPBOlA!BkF E+MBO¡ArBo+{5BP7SA-C++BOxAB+>BQL AJC BD++ BOڙAd#±Fq+ BOACFS\+ BOAQ@ʂ)F)+ LBOAPBmF*++ ,BOAB\cEq+ ,BOABFW+ ,BOAB\D|+ MBOAB\D|+!FBPABC+!1BPA,BCą+!wBP1A(B D1+!'kBPA^B y<*+!4BP+AzB.+!8sBPAjBojD;V+!8tBPABD·+!8uBPAB[XD++!8}BP1AKB}q+!8~BP+AzB.+!:?BPABQCrW+!:@BPABD·+!GABPA#BΕE)+!GBBPABS.D&+!GCBPA'Q+"6BPAJCD?+"6BPAdB|E:#+"6BPPAB՘Ao}]+"7BP4AnRB+"8}BP_AaC*4E z+"8~BPAY)B E.+"8BP5APBD+"9BPABaCy+":@BP`A(ByDE+";BPWABAeH+"UACCDF#+$BJoA4C@+$)BJAS\mF}+$1BIAyCFC+$2BIACe}F^>+$3BJ+A CVF+$S?BIuA_Co_+$T/BJ>ADCo1h+$T0BJ(~ACnFY,+$T1BJpGA~CEEH+$TDBIPAC}9+$TMBJXA Cs+$VBJACpEƍ+$VBJ&ACFc+$VBJAChA+$VBJ&AC2GEw4+$YBK`EACo+$YBK37AMCp}+$ZBIk.ACuF[_&+$ZBIaACVF98s+$ZBI-AC1ކE+$^YBJ>ADCo1h+$_BITAMC<E+$aBIXA(^C>FNg+$aBIJA3CC`Dq|+$aBIYA C^ATE|+$bBI;6AC>b+$b+BJAMB2E+$cBJ ACZ\E't+$cBJ A%CszF8_ +$cBJASCG+$lBIfA>CrN+((yBPӽA6Bٚ+(5BPABm+(5BPMA)jB׭PE+(88BP AB4B>w+(LBPvAWB-/B+(M BP{AxB؏qE+(MBP؉ABCUi+(MBPABEjRBI*AlCB4{RUBGA}C?)E?_RUBH A,C bFB RUBG9AB>4FRUBG AC^?}RUBH ACBtExX8RVBGANCpE{+RVBGAӖCkERVBGAC:ERVBGACE-RVBGaAޢCmsuRVBG[-AWAЛ/ERVBGhOA?hE4RVBG|A#C^ƝFLʘRVBGmAGC;RW BGGArCb BR`BHiA"C0VR`BHACFD,RcBGA7CQE]RrBGjACxAЅRBHcQAC8Ft=-RBHACEVVFRBHTACQFxRBG߾ACURBGoACYDRBGAC>wEiRBG'AOCT F^tzRBGACr_4F%RBGmACOEaf5RBGAC%ERBHPAC>lE RBH=hA lC_WFNRBHAC9pF:}RS?BIEA!CY}ERUBHyAC/)R`BHAR"\BLXAC/SuR"oBLUoAνC^D`R"}oBL`ABC!R"BLWA#~ER"BLZAQBEGR"#BLYAvCzEqQR"BLXAC/SuR#VBGQ~AjSCVER#WBG&BA;C0FR#WBG&BA;C0FR#W BFA!CR#W BG-A(CzR#WBFA=CFشR#WBFlAhCxFR#WBFxAC~FooR#WBFAcC8FQ$R#W(BFCnA@C'R#W1BFvAC$D R#W2BF\AjiC,E;R#W3BFpAXC1)FL8}R#ZGBEA~DE#R#ZHBEADFcR#ZIBEQAADR#s>BEeAC(EUR#s?BErA_C>Ep{2R#OBF}A*CE"=R#PBFvAC}DR#QBF}AVCǎE)7R#3BFhA}CUyBLEA(B.y/BLRAGBS|cExy0BL6ABU%E',y1BLHAiC!Dy9BLA ByBLACuAޢuE9yBL͂A=C!nBpyBLvAvCIE0*yBLANB1F2ayBLAYBDyBLAC{yBLQAC8)*Dy#BL6AGChC _DyBLVAC"LyBLTAC&DXy}fBLACy}qBLAPC&CyBLAC!C~?yBL AfCEI]yBL:ACzD yxBLdA BRFeyBL-AoC D{yBLXAޝC"wy"4BPzABwCy-y"_BPABܲEsy"`sBPABĜy"`tBPkÂB.Dy*,BQfABy*BQgA~BEy*BQgABDhy*BQiXAAB/E|y*BQhA`BD\#y*NBQb4ABEYy*NBQABr-y*OBQAB6qCAy*O0BQ=AgBfy*OlBQ`ABEy*QtBQeAB DEy*VBQ^A3BREKy*VBQABNVy*VBQfABE~Çy*VBQi+AzB{EXy*VBQ`A BgE<y*VBQdAմBhy*VBQf8AJBDvy*uBQ]tA%sBbEWy*uBQa,ABD۲y*uBQcA٠BE#Cy*uBQGABCCy*uBQfAB"Dry*uBQyA,C5>E-3y*uBQhAވBnD[y*yBQgAvBEy*yBQsAXAzOB EJy0:BQfAwjB;D{y0:BQ6.AzBfEܟy0wBQhA?eBb#D 8y0wBQdA|nB#Fy0wBQ3MAzM@F2y0wBQ2AzCF6y0wBQ IAxAQF$y0wBQ7AwB^F6Jy0yjBQwAyBEcy0GBQ"Ay_^Fxy0HBQAxA#Efy0IBQAwC0]FCFy0BQ8AzKB nFMy0iBQAx΢BSuy0?BQAwBSDayABKtAC~aF[ZyABKAC .Fj\yABK@AGCBF,3yAGBKACI2CBryABKGA3QC%E yAvBK ACFyAvBKNACD>yAvBKdACyyA}BKALCCCuyA~BKAB^B0FG!yA~BKSAMCFw^yA~BK:AoCFV>UyA~BKIACEGTNyA~BKAvC FC,gyA~BKcAJTCFyABKACERyABKNAC UVFXyAsBKA@D4F1MyABKHA,C[FEayABKAC$aDU yABLB.C-ZyGQ_BQAW}ByGQBQjA BjDU5\yGQBQkAVBX|F yGRBQiA tByGRBQ} A! AțF.7yGQBQefArBODyGBQ|6A@'F"}yGBQACnF_yGBQABf^F4jyGBQABC`'yGBQfWAfB< E#DyGBQkrA] BVCFyGBQwAC7ՅF=7ayGBQdA|oByGBQA4BCEMyGBQA|B1DyGBQsA9B&/EgyI_BPAB_FQyI_BQ ACZZFG`2yI`BPA68BkDByI`BBPA BsTFEyI`CBQ A,0BCƳyIuBPtA>BUFeX4yIuBQ8AByIuBQ UAABDyIqBPABF*J:yIrBQ6A"nCF5GtyIBPAtbB6CyI/BP#A&B+D?yK!}BOcAKClDwyK&BOACF($yK&BOnA׷CEByK*BOA0CEWiyKBOA`.CFQyKBOARC E%MyKBORA{ BEyKBOAC2TFQyKNBOA0C@ByKBOACŹDoyK BOAMC<[DyLvBK:AFC*yL}GBK\fAmlC)yL}IBK,mAgCyLBK*%ACDyLOBK(6A'bC1yLBK/A/C%JEX|yL BKAAL{C/_EPyLBKAC-QyLIBK[(AqCyPmBKqArC#B=}yQBKLA`C+yUvgBLAC3yUvBLTAkCyUvBL AKC/yUwYBL A/ C n EhIyUBLA3‰WF*yUBLABB\yUOBLHtAB鉺yUQBL0AA{CqEyUcBLACY\DyUeBLA.ByUBLAClyUBLABCvyUBL)+A@B!CAhyUBL(A]CCTyUyVuZBPASB'yVumBPoAwBOF}yVunBPAeBFL)yVuoBPAxB/EyVuxBPA-B$_D;yVuBP϶A5#BV)fF(yV BPAZGBDyVBPpAMBF>lyVBPA BE${yVBPѠA'FBFW*yVBPNABF yVBPӲA]ByVBPʨAbBfF0yV'BQAvB髬EyVBPAxCPFb yVBPLAsBpFyVaBPy\wBQ1At4BFy\wBQ4As:[C2F g&y\wBQ=ArB$uF5+^y\wBQ=AqCMWF&y\wBQAtAq,%C E{y\wBQAAqC E.@y\yjBQ*Ay Fcy\BQ56As}~BIFj(`y\IBQYAvrBCs_y\lBQ:VArBC~y\?BQAw BydSBQAABNE9_ydTBQ?AB@F(DydUBQ@AxBPF?&KydNBQ=AUBydPeBQ1oABEmydPfBQ A BˆEI]ydPgBQ*A:BWEydQBQ?ABvhDgydQBQDhAMBkDrydTBQ=AZB>Eyd_BQ(AB_E`yd_BQ90A@BMCIyd`BBP'A2+BiCWyduBQAUCleFyduBQ?A}BF#ydqBQiAUB#AA\ F hysBQG2A@B4lF.ysBQU1ARBOE7ys%BQJrAB+uFDbys'BQ^A4BBDIysBQ;uA)BytO BQZA^B\yTBQ=AcBE'yNBQE9AtBEZoyNBQBAHBFGiyNBQ=TA?B"E:%yPfBQ#"AwBȋyEyQBQBAWeBbE;yQBQB AB FKyQBQB+AB҆FLyQBQBqA/BBEyQBQBAܠC{F\yT BQ@A8BPF yT BQ5A|B^]EdyT BQBQ|A!BDI-|PBQ,AAeB+|PBQxAlBeCX1|Q_BQABnE|ZBQ{A B|wBQAaVBadEx|7BQAcBD}|BQABȰE|BQAMC0F!|BQA B*E3J|BQ]A{B%iC>|KBQqsABD|6BQYA{B D~|-BQABɺ|:BQJA{\B?CT>|:BQ^A|\B@|OBQ{AQ/^F64|P>BQAdvBs3|PBQPAB Bu8E|PBQuNA/ B/Ei|PBQ}AYvB*1E|Q`BQAGBKFx|QiBQ}OAB"Eθ:|QjBQ}3ABF=|QkBQ|A~F0|ZBQyA%+C Fd|ZBQAnBDU|ZBQ@A BF+rZ|wBQ=A|BzDi|wBQ} AF8"|wBQ~EAvZF|wBQYAB-|E=4|wBQA6A*D7|wBQ4NAz)B}7Cx|wBQZA{ BSD| BQtAB(E|BQ3A~BAC#|BQABKE,\^|7BQXAC"Fg|8BQABۦ|9BQ|AyB`Em|BQ{NABIJE2~|`BBPiABDs|huBQ4sABCp |rBQ )AIFBd?ER|)BP Af%BsFC |+BQAQBĜ{C|`BPA|BFOA|hBPABB\|uBPAB4DK |BP6ABsC&nk|BQABcCk}4vBL;A5B$ND}4vBL4A~BzD@}4wYBLAF*CE`}4~BKrACJC}4~BK>A3CC:j}4OBLCA 7CkD}4PBL?FABV}4QBL@FA BC /}4;BL\ADCB =}4BL-ABܾ}?BLA#BHDru}?vIBLpABݺ^}?v^BLAEBCC}?vBLA(B}?vBLaA rBoD.}?{BK>AC `C&z}?|BLABd Em}?[BLcABC|}?EBLAXC-EX}?FBLAB>E7EZ}?GBLsA845E D}?BL}ABvCTK}?BLABÙ}?WBLyWAB֫C!}?_BLxACDW^}?`BLxACDW^}?aBLACwEaP}?BL ACB}?BL,ABDX}?BLrAxB9DQ}?BLABEbU}?BL8A|BL}? BL2AJB3C e}?OBLxjA_B1hD}?QBLv1ABߠB}?_BLmA<B.}?BLRALBq}?BL~ATBq^E}?\BLACǀD/2_}?BL7ACCDj}?BLA5BDւ}?BL=AB(}?JBLUABy}?BLAJB׸D`=}?BLAtBd}?0BLA=BE.I}?BL|AzBȴ}?BLABaE&}?BLAHBX}?BLAY}BhE}?BLAaB BEIu}?BLABB\}?BL%AHB}?GBLARBE9}?BLA?B}Gz(BL"A}v^BLz;AiDB!El}v_BL4A[BhDC}viBLnYAGB9By}vqBLnPAB:CUPG}v{BLbA4AC?}vBL6AE*BL[E"}vBLnA)B=EO}vBL{A&SBENf}|CBLqA=BD}|BLABE}|BLqA]BDTU}|BLzdAC E@}|BLADA{E<`y}|BL|A*CE *}|BL7AC8E1q9}[BL~NA~pCOE4`}\BLtAX[BE1Df}]BL}AGC,E?f}BLqWAjCR5E}BLdAKCxDH}BLi\AI BD}BLpXAEBєDMB}BLiA@BFYD}BLpA&B'fD_}BLAC3}FBLAp_B}q}wBLztAB㽵D%}xBLtAB{D̶}yBL|eAmBDB}BLkxA7C Dn?}BLhYA2C 9DFA}BLoAJBxD@`}BLABE9}BLrAiC,D4}BLwaAj]CD4}BLkdAR^BD}BLkANCfDM?}BLsAI5CJ6Cn}BLgkA^gC oEE؄}BL`3AJ/BշD>}BL[&A#CC:#GBOCACIJFh7:#HBOAu B.tF,:#IBP(AxBTFF1:&BOkAkC%V:&BOA\;C "DW:*BO\AvC y:*BOACCy=:5BOA4^BE$!:BPABHDG:BPABЇ0Ds:BO$A=CHQF:BO AC D\n:BO̎AKBDL:BOyARCC:BOA{C /@F*g: BO֖A6iCFV:!BOA"Cj>F@T:BOEACqE8:BOATCmFE:BO,ABXD:BOAB zF#:BOզAB` DCBOeAC_C,C BOABEC BOA-C={E=C BOAC$EiC!!BO$A2C>C;1C!{BOAoChF:C!}BO,ABFC#BOCAC%E^C#BOVABD_BPAqBÿEL_BPADB,DZ`_BPAdCWF?``BP2AqCFZFQ1`aBP$AeB[E$`jBP{AhG¼ Fn$`kBPAv:Bn E3`}BPgAdBDbM`BPXA5YB,CRa'BP|A+ BDE[`a)BP:Ai%BFH(uYBP|A#B\)uZBPAB͗GE(ucBPUAwBEudBPAC]EHueBPA?>B5EwʙBPABjʙBPA=BjʛBPvA)B_E[XʛBPFAB EBʛBPA\ChF2ʛ BPm1AHkFV ʛ BPABLFWBPpAaNBvFBPA0B;BPQAjBEBPkA!BgD#IBPzAtWB\%D/*BP MAIBx_BPFA/BFf_BPzXAB[OD&_BPABD`WBP A`?BcEEIf`}BPoA?>F@8`~BPM>AkA+F`BPpAI`FWtuPBPA`BuZBPRAKBˁBOABPgFk_ˁBP,vABFJOːBPABQCa$˙BP4A4B˚kBP]3A:2BzEY2˚lBP<(ABoGFRЋ˚mBPk:A'kF)p˛BP?AB4BOA'BFD2BPA'B׍_BP(ABQCp``BP AvBt9́BPC)mD0̙BPH!ABmFkR.̙BPA$uBCeW̚BPAٽBC}BKACGcEt}BKfA.C}GBKPAvC)rF31}HBKjACOE}IBKiTA CatEK}BKyAoB< F}BKl]ANCZDBZ}BKdzA Cp0OEAeCŇEmߨrBKHABK\ACp7lC%?BKKA C:6FC}@BKAXC> EjABKtAfFwHBKfACȗED]IBKbADfEǰuwBKJAICC)DJ xBIpACׂD HBI]AQC{aHlBIOAtCCpBIQAvC.C/FBQAB1TD*I/KBQGA6BzBB>:/TBQA-BWOD3=/VBQAݧBxCC v/BPNA BhC81BPAA BB8C 1BPA BC2BQAݧB_)CNBQA)BOB<NBQAYBD,?NBQA;B D NBQABDBOBQA@B޻B-OBQABhCOBPABDo OBQABzwBROBQAܓBC\DvOBQ~AB$ZPQBPAB#PSBQA_BZAPXBQA_BZAQBPAiBRDYQBPA CB)D"QQBPtA{BݰB}6RBP&A=BRBP.AB1SB`RBPABtC<[SBPApB]D:hSBPRABDSBPABJRCSBPAB{UBQABKBͨUBP_ABCwW1BQABDBW3BQ#AB?UD Y>BQAGBCYE&CPBI0ACDD:PBI+AsC`BvPBI+ACcSBHXAoCĜSƮBHAMC%CBHA8CDkSHBHAC*EMSfBHA8CD |VØBHACD&VBHA)CLXEb,VBHA& C}VpBHcACrEoFVƮBHACRCDRV1BHA!CB?K~VFBHA&CVMBH6AC.(CV`VQBHDA CCVRBHYA#CVYBHACLAVXBHGAC /VʍBHAC&CA<VBHWAC( BPԠABD#( /BPBA!PBUA( 0 BPA(B+( 08BPՓA3B֮B3( 0FBP]A"B D5+( 0GBP[A32C1XD~f( 0HBPrA(yBDO( 0MBPlA+?B֮UD( 1h( NTBPAxBD( OWBP!A, B DG=( OXBPA%BϓE-F( OYBP|A$BzDx( OBPWABٞ@X( OBPACBmBӼ3( P=BPA-BMD6$( P>BP?A]BsDe?( PBPAxB3wB( QBPRABɂC( QBP*ABD8( Q BPABDi8( QBPAUB8rBb( QBPA#B&DQ( QBP~ABDJL( Q BPAgBV>BUG( Q(BPA"B+D( Q)BPA"B/D X( QBPA B@( QBPATB_]D( RBP"AwB\DL ( RBPA)B#A 3( RBPOAB( RBP9A(BpDC3( RBPLA*B( RBPA B;( RBPABB2w( RBPpA(B7Cjn( RBPA":B#;B4( SfBP^A ^BSC*5W( SjBPۮA B߄D( SkBPߓA B/C( SmBPߙA BcSCb( SBPA(B C@( SBPذA)&Bۗ ( SBP[A }Bí_C)l( SBP)A!MB%C+l( SBP|AGBI7( SBPA*BB( SBPbAmB+( T7BPA YBE?o( TuBP'ACADȲ( TvBPmABvD( TxBPܘAB_DP( T{BP]ABD8N( TBPZA"8B( TBP׌A(BCcV( TBP=A=BzC&( TBPABڥB9e( TBPHA;B+D{]( U{BPjA,B"Cr( WBPEA#CDA( WBPԍAB,D( WBPA5B%Cm( WBPBAB"Bq( WBPaACHD/( XBPA%CD( XBPA!BB2( YMBPA-CBu C( YNBPA/CBv]B ( YOBPԩA#B,C( YWBPABqD( YXBPծABi&(D ( YYBP݀AB:DS;( YcBPABZ( YBP*ABLCx( YBPAtBQC( YBP'ApB֨C( YBPAB+( Z&BPVABq'( Z'BPVABq'( ZBP$ABD)( cBP~A -BChz.( cBP=ARBٳ( cBPtA%B٩CO]( dHBPݙABwbDla( fBPABZC>}( fBPA B`DFZ( gBPuA-BͦCOZ( gBPWAVB:B( ipBPA"BvCdl( iqBP(ABjC,i ( oBPA#(B]/( oBPA"B/D X( oBP:A%B( oBPABCID҅$(<BP AB?EE(<.BPƄA C E(BP#AB9A&.(BBo(A(VB[CF(5(A!BtBF_C7BWRAC VEdRC@BW2ACmCrCBBWApCĪEC`BWeACD~CCBWIA,CӼF8eC}BW4ABDDSP_BPAAwChFEcSR4BPAxBEL24SSGBPAHBB!5EDSSLBPABBD}STBPcA9hCU]D'STXBPAKCYE2SaBPA3BCaFSb]BPPA8UBRFkRSb^BPlAWƸBHAC_EQhWƹBI(ACY'E)WBHACQaExW BIA#C[_EWǣBIAACFFWǫBI!ACp>DbWBHA`CD4W1BHAChEIdWABHMAqCWRBHAC9BWSBHA$CD.AWWBHA)CGSB7WYBHACC%W`BIAԛCvHC cwWȓBI oA!Cc\jWȦBHEACVEFWȮBIAPCDsWXBHAwCE W\BI A"CFEWʋBHA^C}EWWʌBHA9Cd)Ecd[WʍBIA~CvFWʼBH$ArCi6 EWBHڲAoC}ElWBHAClEMWcBIACDSWdBIAC^^Ee”WeBIACDW@BIAАCnWνBHAC^1EdzWξBIAC'CFWBIARCD`W%BHACW;BH*ACC5WBIAkCeEWBI@A/CbEXrc5BI,ACrBUk1BQAfB:^k2;BP+A2OBWKA?мk2BPҞA2qB]kO`BPEsD6#ss gDsӵD;sssysDb´sBMA/CCs/BMFCAfCbqCC?ms2BO['ABFjs2BOABF<2"sOBOAUCEsOBNWA^?CFPsOBOACEO|sQBOtABCsR1BO9AH B~FåsR2BOuALB8FsR3BOeABaqFp:sR4BOYAV7B܇F,sRUBNA!Bҗ'F[sRVBNoA;B$ZsRWBN=0AIB FV3sSBMAC40FK<sSBMzA_C!._FOsTBM=ACSD\sTBMACExmsTBMkA nCDtsTBNvA]yBˋFNlsUBNA:C F2F@ȫsVBNWABv[F%sVBN[A"%B~dDsVBOQ ACDyF<sVBOA>BE!sVBOmABZsVBOABsb5BNzAUC F>sb6BO 8A1,C Ffwsb7BOAdA CF=?sb^BOOALB/E sbBN|AC!D9NAsbBNACLF=sbBNA^C EscBO֊ADCwE.scBNNAKCBQ A3BqBLqAuBmsE{qBL[MA+BDibBLjAB+DshBLADD;WG^BL/yAmDG=SBJADˆBKKA} MAG BJKAD4ƕFAWBJԭA$C7E<BKAYCmCBJADy'dkBPXAaB E`dlBP?AicBDMgdmBPAwWBoBXdBPA\B!HdBPAqB\)dBPϻAB|dxBPABG+d?BPABl}D9xd@BPχA5ƒ2E dABPABͺD[dUBPABRDdiBP8AB)dwBPA/BԈDd}BPABB d #BP;Ai{B:Cfd ,BPFABE%Td /BPrAEC5CEsdBPDAz,B;Dqd\BP߹ABH1dBP=ABlDTdBP8AB8E-<dBP+ABiEYCDdBP A͉Bd BPACADd"%BPAmB-DWd"&BPAm5BҼD-NfeUBP A͉BehBPAs lES'neiBPABɐDk/erBPAlqB ḎesBPqA)Cc{/E؟euBPAADvevBP?AA/tE @ ewBPyA[YB8oE>J!e}BPA.B'e~BPjAB.D%eBP]A|BYDpAeBP?AA/tE @ eBPAB›E#d^eBPGAqB40D9]eBPAшB$ZeBPA0BC֖eBPGAqB40D9]eBPAE eBPA5B1eBPAB JDŬyeBP!ABzE[e nBPA B5EeBPA0BC֖eBPGAqB40D9]eBPAЀBeCQ(e4BP&ABE Be?BPA?BeBPAA CEA(eBPA.B]DeBPǘABJE6eBPA^zBDlNeBPAF)BQD+fBP2A\Br;f wBP,AzBE Gf xBPA]B(svvBPhA^bBDuvBP.ABSCsvBPA[BC3(vBPLA0BvBPtAB Dv BPLA0Bv BPLA0BvBPAZBCv5BP5ABvBPABv `BP^ABDH25v wBPA`6Bv BPA]B.>EWv BPAVMBDv BP ATBˌDّvrBP^ABDH25vsBPA *BZvBPALB0!vBPABB vBPABvWBPA(B߿Dv$}BPACwD:v$~BPAxB)v$BPA [BODav/BPALB0!v/BPA\B!HvDBPA[BnCvDBPAR`BDvDBPYA\"B/&En- BP5ABBPAZB BPOABC^BPA#BƦDiBPAvB_ !BP2AxB]/ "BPXA[B #BPAlBDx BPAqsBL@A; BēB<qsBL~A=B C%qsBL~A=B C%qsBL~A=B C%qsBLAeyB޸qsBLAJBlCqs3BLApB"DVOoqs4BLz\AUDBE;Daqs5BLnA^BNqs6BLApB"DVOoqs8BLBAaBC%qsBLABZqsBLqWAghCZ&D%qsBLo'A_CDqsBLnAZ|BsDqsBLnAhC+rD6qsBLmAZSBsCHqsBLo6A\BD qsBLqA"bBqDqsBLpAVBvDqsBLr;ABLDͷqsBLo>A#sBBoZqsBLp8A"B%qsBLqAuBDqsBLpDA;BDhdqsBLrWA BCuqsBLrQABីqsBLoYAB3eC)oqtBLwA1SB qtBLbA9BUD;IqtABLnAuB+3C-qtBBLmAyBG+qtZBLe ATB>wqtiBLtA0BJ DqtjBLqA/9BD)OqtkBLqA#B)CqtlBLrA/@B\DxqtmBLqA3BCSjqtnBLrA'BцCpqtsBL}9A]BD.cqttBLwA_BD`qtuBLyAYC ܘDqtvBL~bA[lBsvC]9qtwBLz-AZ;BmCqtxBL|VAXBۜqt}BLu AXB DPqt~BLrAZBv?DLqtBLu\AI7B:^qtBLv AO1B-"DqtBLtALB'CqtBLtAJQB1@CqtBLlALBđDqtBLmAHBNVqtBLnAH(B}+CRoqtBLkAECbD1ܺqtBLhAC9B( DAqtBLkEABC_DqtBLhA@CDoqtBLq&ArC]DqtBLoA{Bɂ0D8%qtBLnAlTCȴDqtBLo AyNBCqtBLmAzBvB? qtBLoYAu&BCDqBLAҁC;dqBLq AiC%]D|EqBLwAXB8CXTqBLxAYTBȓC85qHBLoABgmqIBLA{BuqBL~bA[lBsvC]9q=BLdlABBƨqBL{A=B/DqqBL@2ABSDkqqBL>A>CE+yqqBLF"AC-oE;qqBL>cA7CE5NqqBL?AvBCqqBL[NABؓqrBL_=A6B_DdqrBLYA1B RDh+qr BL]A1B%D> qr BL_A6C D`qr BL[A:QBÂB5qr BL]A1 C#D> qr%BLKA B.BOGqr&BL*!A_Bnqr'BLH^ACDmqr/BLS[ABlEqr0BLKAC)W4E>jqr1BLTA B(Eqr2BLUSAB_DÀqr3BL@cAB5qr4BLWA`BD/qr9BLPyAC3ECJqr:BLKAC$D`qr;BLOZA2BCCqrBLMhABqrCBL[8ABZDqrDBLOABkDXqrEBL]ACD qrFBL[8ABZDqrGBLPABE8EͽqrHBL\ABwD_PqrMBLdATC AEqrNBL]ARC EOwqrOBLj2AiBhD"qrPBLb/AՕBGDpfqrQBL^AݫBCUqrRBLr~AB C0|?qrWBLg1AC}EUqrbBL6KAlBqrBLuAVBVqrBLo-AB'DfqrBLpA|BRCaqs=BL^AD`B?Dxqs?BL\3A3BC߁qsHBLnADC/qsyBLHABN8D9qszBLN AgB?^,qs|BLHABN8D9qs}BLN AgB?^,qsBLA.BDqsBL6KAlBqsBL%ACRnEcqsBLB-A>C!XDJqsBL0ABO(DqsBL?cABD@qtBLFXACIDqtBLGjABhD%qtBLJiACʺDgqt BLHAbBPvB9qtUBLoA0BÖqt_BL0WABP{D1;qt`BL/ AxB+sDAqtaBL,AB?D]qtbBL/ AxB+sDAqtcBL A~FCEd$qtdBL(A+C(qtBLU1A1B˳qtBLMA C_7D=qtBLU1A1B˳qtBLL2AC D~qu'BLFAGCqDqu(BLAABDqu)BLBTA BDqu*BLEAdC[D/qu+BLBAB:Dqu,BLBlA oC!eDOq%BL8AnhCq BL@AC Dq BL8ABEPqBL>A&Bw D(qBLA\"C qBL\ A#.C^DqBLSAOBE`qBL[AwC_DJҩq BL]3A!CB q BLXAC Dq BLZhAC >Cq5BLcAQBCCUq7BL]A.zCFDGq;BLcAQBCCUq_BLsNA-BDQcrBLxABC?m*rBLoAMB(EϦrBLvAgBrBLpABDrBLxABC?m*rBLxABC?m*rBLxABC?m*rBLwAƭB9DrBLxABC?m*rBLu1AjB!C"sBLzAB{dsBLyABECsBLr!A#B޴C-sBLwAB~DL"sBLqZAةC AD6sBLtA[B,DvsBLsAyBk[D0sBLuA B8DbtBLxABaC-tBLwABѯBLwAHC#bDBLwAHCuDBLxA8B D$bBLwABѯBLvA BZCBLyABECbqBLnADC/bqBLjA=B)D'UbqBLlA߾B(|ClbsBLABE%bsBLAJBlCbsBLCACDK;bsBL,ABhsbsBLA+CDܖbs BLCACDK;bs!BLCACDK;bs#BLCACDK;bsGBLABoD]bsHBLnA5BS|C,bsIBLAeyB޸bsJBL2AC bsPBLk`ABbs\BLjgAB`Dԥ>bs]BLjAB[DoEbs_BLl/ACݹDbs`BLkA BD9bseBLmAtBtAD^ObsfBLj:ABӊEbsgBLlAB1D&bsoBLjABjDaObspBLjAB(DbsBLAMB;EbsBLApBbsBLCABEڅbsBLAQC'bsBL,ABhsbsBL|ACBEbsBLABZbsBL2AC bsBLABZbsBLA BD}bsBLAҁC;dbsBL-AC REbsBLoA0BE6bt7BL,ABhsbt8BLk?ABo%,Dbt;BLgjAB C}btBLjAkBeC7btBLi@ACkD btBLjAB\DabuBLABZbBKVABWh`BL8AnhChszBL6ABq'hsBL(A+C(hsBKcA BTht`BL(A+C(htBLAC %E΁phtBLA,C@=Eh%BLAaBC *EZh&BKApCDgh(BL AC^D]h)BL%C>UF)h*BL2A*C#F.QhBKjAukC'ExahBKAiCFڦhBKAnCF%hBKA+CAjB+̷BL AC7E̷BLAҁC;d̷BLArCD]C̸%BLxACzOF@̸&BKэA#CDD̸'BL%AjC E̸(BLxACf̸)BLxACf̸BL8dAC2Ej̸BLUAeC&FY̸BLyAnC" EϔQ̸BLDAC4EF~̸BL%AC%Fw̸BL]A:BkFRQ̸BL tAeB:}E*l̸BLoA0BE6̸BL<ABEa*̸BL{AvBE̸BL"bABOOEp̸BLֹA&C'z̸BLA'CC'̸BL AC̸BL]AmC<`Eь̸BLAiCwE}̸BL/A۶C En̸BLȆA9C t4El̸BLVACu̸BLA+CEQC̹BL~AC喘F̹BKABE̹BKABE̹)BLAWCE ̹*BLXAbCEOҼ̹+BLXA+COEBr̹3BLDAA*|C7 ˆBKA{/CڢEU0^ˆBKAKOCޡE\QˆBKGAzsC0D&3ˆBKA^C䘻EˆBK=A"wCDˆBKACEIˆBKJA\CDkˆBK8ACהETˆBKDA&CEBˆBKM/A\CDˆBKAA$CEV MˆBKIAˆBK4AC fDaˆBK0A'CDJˆBK0A'C5D=ˆBK6AoC-gE7ˆBK0A,C͡DˆBK-AeC8DU1ˆBK4ACsBˆBK/A ?C׾jDˆBK2A7C$EBˆBK*4A(C Cb,ˆ)BKoAmC_hˆ*BKbACE0ˆ,BKc|A\Ck?EDˆ-BK_YAD F"ˆ.BKe+ACdEbˆ3BKy:AwCNLE,>ˆ4BKkA;CWˆ5BKiA |C`MEGυˆ6BKeAC?F.ˆHBJ"A *C9D7ˆIBK-ADDPˆSBKACˆHBKgACUEp3DˆBKoAmC_hˆMBK AWD9@"F+ߧˆNBKA7CFCtˆOBKpACRd7F ˆPBK ACEZmˆ©BKACCDˆBKPLA>KB:F.Z~ˆBKVA%.ÿ*FQFˆBKb,AC#E!ˆBKseATCCQWEˆBKfAC "Dn;ˆBKiAC_DˆBKgA+C͆DˆBKhACZzE ˆBKe{ACdDl7ˆBK\A2C%FE&AˆBK\$ACr+DOJˆBK`lACnDgXˆBKTAC`cE2@ ˆBKRAR|EhˆBKagAqsBEVˆBKFyANCE'ˆBKNAYeCFˆBKxAXCOEˆBKaACdxxC߸ˆBKv.AN@CLEˆBK\A Crˆ)BKgA1Cb Eˆ+BK^5AdCdD6cˆ.BKYARC~/Dˆ4BKEACE)ˆ5BKciACpERˆ9BKe9ABE-ˆ:BKHACυEĝzˆ;BKXAC)EFˆ=BK^vA]CD{ˆBKA^CEp:NˆBK AoC,EVMˆBK&ACH{F JBKACCERNBKACbE@TBKՎA CQDWBK AxfCE%BKA rCFU&bBKA mCUEI&fBKACpCuFnD3R'qBK6AQ.CeC)BKAcC8Fw,uBK~*A&C.FAe.BK+A JC5Dax.BK]jA8 CKE>4PBKēAC7BKAnC$D:BKAC_En;SBKMACE7=BJA~CeD>BJAГC?EW_>BJACEVCBKsA`9CYC6DxBKh AYC+EgDyBKhAuClE1G%BKAcC8FwG'BKAID1ҘFKBJA CETaBKA 6GC@D[TBK,AqMCEWBKsAuHC4vD<XOBK.ACoeBKACxOBKYAC@EoTBK3A! C)XDBL A#[Cj7BK0A=;FBKA̺C!ELBJUA1CNEBK AUCIEBKyACj,F\BKdAC:FNSBKc^AC'EBKE)BKA^C EU4BJ$AdCR BKAhF"BKTAD$Fv$BK.A;C$F iBJ;ACE$߾xBKA C\Fg5BK A"C.ODmMBKXACEtyBKAlCBK9ACnEGBK^%ACOF)BJDAC9~BJACBKrARCScEBKgAFC[E`BKdAlCSEQ~BKY-ACFBKlAtCF:BJACӭEМBJwAVC"E:[BK}ACE]VBKfA3C;F <BKsAXCMMF BK{*A1BCmHF nBJACBJ=ACEQ/BIACgE T!BJFmAZCCBIA|C@E1CBJ3AC(D/&fBK"ApCO+DaK&BIA;CӟFf'BJWAC{C?F2c'BJ?A9CdE3z.BJeA$C$6Eӛ.BJWA>CD/\BI^ApC?F#̈/]BJ4A@CF/mBJACDED7UBJAC7zBIHAC.D-KBJAACDE>QBJ~jA\ACT3BJACWBJ ACWEBWBI[AoJCyE ̜WBJAȔC CWBIrAynCcDXWBIUAC[EXWBJ7 ACǿVF3IhXoBJAqCEwIBJnAC BJ>5ACX^EKdBJACEGBJKA?C,ERBJACF"TBJi3AUnCF*|VBJACoF+}dBJ4ACFgBIԵAC$F[jBJKANCF mBJXZA4CDqBJhyAC¦>E$BJGACkEBJX_A5C!D4oBJAC%B]) BJ`AC9CiBJTGAF/CMEjBJwA4oCF:BIhAܮCRNBJ)ACD2BJAeC±F_BIAmCPF27~BJ|ACE&vBJ!AC EBIϔACUFuBIмAIC?~F BI8AC/F#BIAC3FL jBJACɤEkBJ@ACFJ|=BJ%YACOEiBIAbCشBJ9JAC9EBIAC!F"BJBAGC DSBK*A*CBJ$FAlC7F:]BIAFCEA ^BI,ACcFW0%$cBBAʗD*щxcBS՜A 7*Az8BKA0uCBo qBL}@CwC tBLJ@5C+@& NdBL@`CBS UBL@|2CKqCǼ BL@vC BN-@BETr  BNݬ@BCF BNy9@B߾ BNW@,gB=E BN@њBG1Er  $BNv@FBRE BN,@EB)E3޺ !BN@B'ND47 )BN@3BGEu 0BN_@BE ABN @UXBrEF! GBN~@gB6A +BN@5BMC BNP@BkE} #`BN@B~ #aBN9@KBEg #bBN0@ CK E@, #cBN@B>4ER~7 %BN@B E &BNC@4eBF (GBN@BFGE7G (]BN@B^kF % 8@BNx@B?} 8BN)@BE0 9NBN{b@BE< =BNړ@BE CiBN@5-ByEK JKBN~@,BC"S JbBN@B7F } NBN@0B E& [kBN@GB}ؤEP:z [lBNړ@BE _BN0@ B|dEl _BN@Bp2EP _BN@4BcE{ _!BN@AB _"BN@}BqC _*BN@BEk) _+BN.@MBE7 _,BN@:B+&D . _0BN8@fBEp _1BN@BbE[C ghBNx@'B mBN@[BCE  mBNV@B0Dm IBNv@BzDq ]BN@BcE $BN@BC %BN۾@PB-nD BN@GB $D BNũ@fBEY BNi@VBp),E2~ DBN$@aB{D|> BN|@B{;D x xBN@%BEFD fBN@ԒB~eAv BN{@Bf BN@ BmD{0 BN@AB&DZ BN@WBgE+ BN@ؚBh;(D BN@BxA,H BN@BC3_ BN@BhE BN-@jB,E} ʬBN@BE ʯBN @!BE BN%@ABSC BN|@vBDD / BN@FB1 tBN1@>BqE[ BN{@UC>3E  BN{@rEB5F*3 LBN@%B E̩o BNٛ@B/ C kBN?@ BxD BNz@aB -E BNY@WBMqD; BN@ҶB]DH BN@tB fD4 BNY@:BQ0E)t BNߌ@WQB8C#; =BP.ABV <BN֓A*D pBGA5EYCݥyB# tBGHA5\Cۻ h BSAsAb BC A_C<2dBFo^A1CiBD2dBF`TA1CdC`MH2dBF[A1_CDihB>AuCd9>BALA/:C3yBRAP0A`lBCdA C SA7D8=B@kA9xDSKB@kA9xDSZBALA/:C3 ՃBJ@oC9Ctb &xBJ@ C BJc@CCa BJ@bC\ BK@*BX B#w uBK@(B]lHDn!xBM@xBH!BM@p%B D!.BM@EbB2-!> BM(@VBWD}!> BMX@;BE<0P!cBM@ށBʸD!cBMR@ s#Es!7BMy@dB;E3a!BM@*B̫E*!BM@ABҔ6Dս!BM@C`B]7DWl!BM@SBt!PBMD@CB۾!gBM@?BBRD!HBM@2BDæ ![nBM%@5mBEL!iWBMo@@BjD84!icBM#@rB+D4!ieBM|@&BًC!ijBM@zABD忿!ipBM?@BDn!iqBMm@#BT!iuBM1@<(BӣE1 ;!ixBM1@<(BӣE1 ;!i~BM;@lBC!pFBM@QBDH?o!pkBMh@f^BƓ Cr(!7BM@hLB(D!:BM(@BDg!/BM@IBDE6O!@BM@BбAﮑ!bBM@TB-!cBM=@蹑BlD!BM@EBۦ!BM@XBgDr!BM@oBL)Dc!BM@B[E;!BMP@;B'tDe w!BM^@yB"C(!BMl@zBJE 7!BM@,BfE!BM@%Bx,BE8c!BM-@BaD`[!BMc@PfBC!BM}@B!BM@*B+!wBM@Bc%D>g!BMz@\B«E g!BM@1BXDE!BM@.BE!BM@BD!!BMS@_B0!`BMu@ZBCDg!BMR@xBFߢE96!BMX@"BMDb!VBM4@岵BLn>CO<!WBMc@oB?4Co.!BM@]Bg!*BM@BʃC짽!3BM)@BoPCXb!BMt@xBv]EF!^BM@:BAA"@BKQ@.B]GALu"BKw@0VBG*AtW"BK¶@TfB'OD"BKO@nBDNr"fBK˰@BlCxN"BKه@\>AzA`"BK@yBGD\"BKͥ@᭾BEX"BK@fB@BD"BKU@mBDC"*BK@B#B/D<+z",BK@⳷Bk7D/"9BK@7B9P";BKh@AĭAE@\"_BKՀ@1*B333"vBKՃ@0B>eA)ݱ"$BK @CB:6D9c"$BK@@8B+VDJҖ"$BK @CBHD<^"*BK@B1E!"/BKB@uB"EY"/BKR@{B(TDxa"/BK@B*Ds"5BK@rBNE1"5BK@mAdE"5BK@ߟBzD"5BK3@B"עCL"5BK@,7BRnC )"5BK@"B_QC"ABK0@4BV_Df"BBK»@ADY"B~BK!@Bw$C^"JNBK@ߑBhDp"JBKڙ@[B(mA)"VBKc@XwB3"dBK@$B "eBK̭@rBtTC~:"eBK8@AB(Do3"k%BK@)BV$C]"k)BK@bBg}D/ۘ"wsBK@ߜBNDb"wuBK)@߽B]`D^Q"xHBK@OjB`LC D{"xKBK@=BW'C.{"{(BK@.wB_D"{GBKO@̲BBdD̡"{BK@BID6м"{BK̗@xBCU"BK@qB6DX"jBK0@qBWET"BK@ߘBBo;E^"BK)@B3F./"BK@BH5D"BKʙ@XB#"BKw@BJD "BK@BMQD"BKF@߯AGE"BKe@ʬB@1D^"BK @ϽB"B"BK@B')KC;f"BK/@mB*=6D,1"BK@VB8;DK"BK@dB+pD"$BKѤ@H*BF-nD"%BK@arBjD*)"&BK@?}B$D?"BKu@B5nDH"0BK@NBj\" BK@BuDyZ"BK@3 B8"VBK @>KBEC˲"eBK@2KB?F"BKU@(BH"!BKg@FB5FC;e"BK@BEb&N"BL{@OCkDՒ"BL@,CJB"BKx@BŸR"5BK{@B8tEtH"5BK@B̸DrI"AIzE1"BKϪ@C KD־"BK@3CUDD"SBKk@ᦂBI"5BKЕ@7EBr/E*r"6BK@2B3UEwk"BK@OB=9D}"BKI@%BE"BK@pBŠCW"4B$DF"_BK@.B,ͦC5 "vBK@,!B%C`"BK@j^B.4!C"#BK@B[pDm_"$BKֶ@7jB CP,"$BK@B C"LBK@ UBo D"LBKQ@SBD"LBKh@GBjDX "LBKb@ݱ`Bw?EW"]BK@B )B"]BK@gBI"cWBKc@-B"cXBK@B"cYBK@BCB"cBK@֩Bj"cBK6@ݔXBEYBK"BK9@ifBkB"mBKv@޾AZC"uBK@BIE"BK5@"AZD "BK@P$BC=E!W"BKg@AC"BK@BGαDg"BK1@aBIDߙ",BKj@ᾌBkD"9BK@:BN0!"$BK@wB4EkD"*BK@DBER"-BK@B,E7"GBKZ@:B#Z@+"BK@*B\~vAl;" BK@IBCbC"$WBK@*B[AmL^"%%BK@i5BEY"%BK@-A"("'BKH@aBhB׶~"*BK0@ B-bE#t"-BK'@B^qCwO"/BK@ŶBe!D"5BKȑ@BwL"5BKJ@ନB:iE t"5BKW@B-E]<";BK@B{ B$";vBK|@ݓBhzBf9"ABK@[BCӕDa]"BBK@ϼB\`C("DBKA@nBYBoZ"E:BKγ@IJB$^Dم"KBK@ݛBSdC"LBK@݄vB\D0u"LBKn@FZB;q;D"LBKK@/BCD"LBK@TBkE"LBK @ BY*EK֗"VBKX@|p)D"VBKk@NB"bC4"]BK@݅BBSB"cSBK%@ݻB5Dv:V"cUBK@zBA‘Du?G"d$BK@ۡBZDg"d%BK@@.BPhD6QL"d'BK@ݸB]DfIv"gBK@$B\Cّ."gBK@BBQXD'E"gBK@ۡBZDg"k#BK@BS;E"k%BKh@xGB:fE3m"k)BK@:B.D7"wuBK@B"D.Dy"|BK@*BL1'"|BKN@;C"FBKH@_BelF$d"HBKW@MBŦE5"KBKq@B!DH`"uBK@2BON0C?"wBK"@BZ.oD"BK3@BFCX"BK@wB3")BK}@FBQCo2"SBK @DBw@D"BK@BIE"BK@*B[AmL^"^BK@ݸB]DfIv"~BK@ݬB=4C,"BK@ݖNBRVC9 }"BKR@C%z3Cc#BNf@ BD6#BNh2@͜C vE,`#'BMY@C PEټ#*BM@"CE #BNO@BlDkD#%BNe@BD# BN@BD#BNM{@|BD#BND@oZB0D #BNS@B"cE$#BNcf@+BgDh+# BBNm@BbD#!BM@D~C/Ec#"BNV@A}|E##%BMw@C'Eҹ##aBNK@BE#%tBN@BdDL#%BNt@2CgEѳ#%BN(@0C2E#%BM@C#E^Ϊ#&BM@qC FE0BG#(mBNt@B*uEdS#(nBNk^@BdBYDv#(oBNk@#BФER0#0BN[@BIO#0BNc&@BorEf#6+BN>@?BE/#6,BN_9@BwET#9NBNy@Y?B'|CW#:BN9@EBfEH#:BN'j@.BD2#:BN",@%)BHD #:BN,@sBFT~{#;,BNM$@EBhcC/#@BN@BDÚt#BBM@FC .9Ee#CBM@CjEv,#D5BM@C,DL#D8BMI@%C[D #EBNa:@,B#E~p#F2BNj`@)BـxE}#JKBNy@F=B)! E#JOBMa@C.dD'0#PvBN'@BXWEi7#PyBN@ ChF9#RBN\{@K=BI>DO~#R BNZ@7BEeDr#RBNb@pB#SBN[@BJDG#U@BN@>BD%#VmBN@BL{E'#VBNn@CEۃ#XBM|@HCiEN#XBM@6?Bv#Cg!#YzBM@CC \#ZBN^@HBJ5C㱥#\BNT@ BEy=#^BM^@oC DfD[#`YBN8#@bBE\!!#`^BN3d@;BEX%#`BN7@]BxExK#ghBN@|BmE#gBNb/@NEB#gBN;@DBDd#mJBM@`CEo<#rBM@D~C/Ec#sBNo>@BB@EE/#{pBN;@RSBE#}BNq@?Bf EJ$#}BNe@sB~iE`Z #}BNfP@BrmzD#}BN^\@^BE0#}BNL@Bq7qDdh#}BNL@lGBE,#HBNk@GBj#IBNf@BzE#JBNn@BDw#KBNm@eCYE#MBNt@/BH"Ei#NBNs@MBBدD#BN @lBJOE+I#BM4@\C2D#7BN@BD#8BN@lC*E#9BM=@~^C F#"BNY@PB9+DO#BNy@B٤E#qBN86@[BBF j# BNF@BWBei#BNfO@ BtfDp#BN@?XRFn#BN0@8BǍ0E}#?BNLf@6BXE4U#CBNU3@ʿΆE[#EBN]@9"BWE#{BN@PBiEN# BNQ@BED!#/BNf@B-E#3BNPT@ EPI#9BNJ{@'B.E,%#CBNj5@*B!Ej##GBMO@e*C5gCuG#HBNw@&WBdEQ#pBN@BȊDҌ#BM@CC \#/BNӁ@,B\EWE#1BNcj@KBvC#6BN@BDڹ#8BN@FB1#sBN@ BݳOD'v#ǤBM@gC;EeI#MBM@.ZCMDPc5#BN[@BJDG#BNf@-BD9_#BNz@WBYEM+#BNAz@k!BCE#BNS:@1BwEʺ#tBND@B\EF7#uBNA@BvF C#vBNf@BiÖ#BN[@B[$EMt#̧BMM@C)D>#BM׷@\5C D/#PBN<>@GBE& #BN/'@8BOD #BNw{@ҸBz-D荕#BNW@ZBlEͽ#,BNT@e8@H&Ea#-BNLz@:&BnE5#WBM@CC \#GBMg@C)1E*2#HBM#@bC<-#,BNN@SBu;Et%#-BNV@BwEW#*BNX@#B>EO#JBNH@HBDt#BNJ=@KA~EYu#BNP.@uBo;Df]#BNI@sgB_E#BNK@zWA E%#0BN\S@KbBN#0BNa_@,BG#5BNI@!BOdDȯ#5BNI@C AEV#6,BN] @{BG#:BNB@)BOBy#B{BNT@`/B; EI#RBN[M@B>21C _#R BNVo@B\DH!#RBNM@BjC#SBNY@@UBKRD#SBNZ@BU߉E^2o#SBNYO@"BdDz#hBNP|@B4E R#lBNDZ@@gBCD5#BNYj@wzBI+Exw#BNR@#BoDo#BNQ@B# BNU@AEL#ةBNM0@ZBuC #BN@Bۦ#BO@r@WB2ZD#eBN @BW=D#fBN V@aBuD87#!BN5@XByE/!#!BN3@*$BMD+##BN@B؆#%BM@BC=#&BN@B®DX%#-BN$@\Bj#/[BNh@?BD#/BN @9BhD} #0BN/@dBEB #1OBN1@,BxDо#:KBN'@#B*E#:BN0@LB}RE4#=BN 5@BC#BrBN9@BRyD #D5BM@)BCnP#GBN@NB #HBN@BuD.#JJBN+@BSE0#KBN0"@BNEN:#K{BNPk@BXCx#MrBNDF@%BjE5#RBN @XBʰD[N#RBN @BE(#VBN@B{D:M#XBN9@BHDv[#X`BN6r@BD f#XBN1 @BunD#[BN @׷BD)#[BN @B7DG2E#[BN @bBߞD%]#\BN@NBD{#]BN<@BgE2#`BN @?B6wD#hBNT-@XB} 7#lBN 4@8B##lBNA`@BCb#lBN;@^BERx#vBN @LB.D#vBN @4B2!DX;#vBN @ BDHI#xBN@BUBG#xBNB@T{BMDB#xBN-0@hB#xBN @|BiBu#xBN M@%B C#xBNR@bB%Dw#xBN@yB,1Dߗ#xBN @&BfCGf#xBNj@B D#xBN@B]XD"#}BN<@Bڠ#BN!@B;Ey/#BN?@BaED-6#nBN@BT#BM@vCE#NBN@BODՈ#ةBNQ@JBEa#حBNU_@B~DrL#DBNNu@뭴B3#`BMV@;BDL#hBNY@MC FEܻw#BN{@ B(5Ev#BM*@}BZDˍ#BN @؈B=E{#BM@ABߨD?#BM@t^BӶvD#"BN |@޷BDͺ#" BN@xB˚D#"BMN@aIBwEZ#"BM@BjE~#%BM@^B~Dˌ#%BMw@]B uDܧ#'BM @ƨBmD;#'BM@BnD#.BM@Bц%#.BMe@ߒBӟ#.BM@BSE #.BM@zABC+#0BM;@BZ#6;BMw@B_C+0#:BM@B?C#> BM@2B=#A!BNy@rQB?1DC#ABM*@1OB̟4D2#CBM5@XB2DQ#aBMy@}C oD^ >#a BM}f@ZC9EY#BMj@ZCIa&E0w#BMp8@-CYw#BMs@\C7MDo# BMiE@5COD# BMbt@CB"DD;s# BMdO@>UCIb EAn=#FBNS@SBR C}#BNԷ@"BȨCl$BMf8@tB)y$BM@WBWfCs$!(BMU@BYDٮ$!BM@ރ!C}Ev#[$"BMx@+ByhD$&BMo @B2XE$/BM@=BD$GBM@ctC]E=6d$UBM@Bx$mBM@C'${BM@D C $BMy@޲C>5$BM@Bx$BM@BJ$BM@XBEo4$DBM{@n-B=C5g$EBMs@ B E,T$ܮBMQ@{BBDʎ$#BM@ܤ&B$&BMq@+B%`$BM@CC$BMl@mBƪD$ BM@EB%D>$ BM@dCE$$BM@⩲BE5,&$BM@BLrE?1$@B෎E A$(TBM@ջBǂ Cȣ/$(UBMJ@*vB6Bt$(~BM@~eC&?EhK$+pBM:@BN$+BM@ C&2E|$/XBM@~QC+@}Ei$/^BM@FB4E)$/`BM@6BZHE$/dBMM@CIE*$/BML@iCYCEq5$2 BMB@ KBE$4BMQ@▕C1$Ee$4lBM_@cBE$>BM@DB!$@BMM@cBEv$@BMo@&B E:]$B BM@6BΠDFv$CBMU@BoDlݴ$CBMk@qBDŐ"$DhBMG@/+C oA$DBMl@߲C+D$DBMy@BtEO$DBMw@ߒoC XE c$DBM@B>BټD#u$DBMʙ@B? DPm$EBM@߳B \D͇$FBM@BE$FBM@BٴEEy1$F(BM@B㊼DR$GBM`@C;EIx$SBM@ΑBD5$SBMʋ@6iBηEIݺ$SBM!@+B*D9$SBMʋ@`B>D;$SBMʉ@LB悂A($ZBM@TCEU$ZBM@ C0E`F$ZBM@ग़B߫Eg$[ BM2@BxEg{$[BM@\B(E.*$[BM@nByEpQ$[2BM:@FCD3C.Y$[6BM@*CE$[EBM@+B2D,$$[fBM@'CbEqs$[gBMC@:C(,EO$[hBM@QC5E2 1$[nBM~@ÓB%C$aBM@NbBlCŽ$bdBM @[~BuDbE$oBMz@44CBM$vZBMЧ@3Bڠ$yBM-@J+CjC ${BM6@vBVEl${BM@y5CDQ$9BM@BC=$;BM@ZC)AX$BMh@Be?EJ$BMz@sBHEP|$BMG@⳧BnDDt$NBM@nBɴC8`$PBMj@ BȣZD,$QBMF@YB80DEF$BM@cBEQ$BML@-tCpB$BM@5{B&(CnmZ$BMj@fBCE]+ $BM@t BE$$BM@㑐B$BM@6oBٲaE,x$qBM@.Bɺ$BMj@inBE$BM@ BeE29$fBMH@IC D#$BM@4BϏD`:$BMI@B D$BM@ߤ]B[D$BM,@-BSE"$4$BME@tBҬE$BM@BrE$BM}@޴ C2En$BM@>CD!$@BM,@ⲺBREp$ABM/@ℂC*l+EA$BBM@BDS$mBMt@yBYWE#Á$qBM@EB4D?$yBMP@UBL EO$@BM<@{BˡDK$ABM@@5C 9E %$SBM@~B3 E$SBMȠ@hB5D˞$ZBMq@TBQD$ZBMm@BЋfCnZ$ZBM@BDH$ZBM@B4&VD$ZBMή@ $BM@BEQO$BM@BE#$BMܫ@LBEEӕ$BM0@ZBb$BMl@eE&%BLp@يBDfC%BLE@ٟBpDX%BL@ِ?B]DP%BL@ٌBD[D@!% BMS@0Bg2qE1W% *BM&@ٻ5B% BM:@B&*{Dt% !BM[7@!eBtD % &BMZ@'BTLE b% &BMU@Bl/jE% &BMO_@ڼBgXE% *BMh@۬BEi % YBMj?@۝{Bw(D% xBM&@پ%B DR% sBMC@XB)lE-.% SBMX@B[dE % \BMD@J2BYCQ% 2BM?@&B9qQE "% 4BM7@B8EO% 7BM;O@"ZB&%Ec6c% SBM8=@B.DZ% ܮBMC@U:B'5cE:% ܰBMD@_B-^D+% #BMou@{BErn% &BMj@۶BE%B9% UBMY-@Ba4EuI% CBMk@]B E8p%BM@ٗ4BDS%BL@٪B^D"%]BMR@ٖ6BaxjDZ%_BM@لBŔD@5%fBM@ي3AE0%iBM@s#BDтg%BL@٬BewD8%BL@٫&BvDh%BM@ٓBUDf%BM k@كBADz% BM/;@AB V% BM|@٤ B1D u]%8BM@لB;'D%dBM.@B_DA%dBM)@ټBG DS%dBM+@=BDiA%xBM@وAʇE]š%-BLq@٨B[ BM%}BL@٫BnD>%BL@١B]uD\%BL@٦B[MCB%BMT@ٝBgD J%BLM@٭8BS7%BLp@٠DBdD7%BM@ِB(iD%7BMC?@ABSM%SBM%@ٽBDt*%WBM2@َB EM %oBMA@BA\)%sBMu@ٔBYXD&BM3@竻C9QE(&BM=@CjwD&BM0@ZCcɰDg&KBM-@SFCTIC&&OBM@pCc.D^&BM@CQ2E&QBM @CdoCR ^&&6BL@RKCsCH&3oBM3G@aCoD̢&3pBM0@X1CiE nA&7kBL@{CCD&9BLf@|"CcD&HBM@urCP0D#}&M?BL@>`CFD@E>&NdBL@*C+oD&NeBLK@zCyC &V BM%@JCOD&V!BM@:CeD@&BLL@'[CkEz&BL@&C{OC8]&BL@@-CpD&BL@sCR3 E%&BMQ@X C{E U&BM@OC'E*x&&BM @@e$ClDĈ&BL@cCyrE5ْ&BL^@kCDof&BL@f%CpEL&BL6@}CID|&!BM#G@FCND)&"BM @8CkxE&$BM(7@FCKXDE<&BM@CE&BM&@9CEj&BM-@Q.CVT{&BL@潺CD>&RBLZ@bCfHDby&#NBL@ϬCC:&%BL@CC&+BL>@&CD8&9BL@C" D\D&BL@P0CmD&BL@FC'DtZ& BL@4iCD{ &BL @CqD~&8BL@CC&BM@9(CA{&BM@{WC,D D&$BM@ [C@c&#:D&#QBM9@!CpHDH-&#BM@BбAﮑ&*BM<=@{C~1E&*BM&@9CEj&2,BMK#@C|EL&3cBMI@m\C}HTEP&4jBMK@CNPE<.&E@BMI@lLC^ED&KBMQ@!CxcCg_&KBMN@+CZC{&LBM]@4CUEG&LBMP@C`AaE?&[6BM @]@B܌ B&oBM@M!BΟD mN&yBM@U CB&{BM@QBAi&VBM+@/:B}@h7&BMQ@C`E&BML@颽CyE b&BMG@ C}Da&$BM9@!CpHDH-&BM-@SFCTIC&&^BM@Q'nBOCA)BD='nBO3A+Bs4EQ,'nBO/@B't{BOUAQBEC'vBO0A~B观E$'vBO%3A4C\E:C2'vBO%ABÐE*'vBOAGWByE ''vBOACo&E#'vBO3oA TB?E'vBOQA?B,F'vBOKnABzE/'vBOMA@FGR'vBONApB0E8'vBORAkB}DL'vBNn@BnD'vBN|A B)|EX'zBOTAۂB&E`<'KBOUuABEv'BORABEq'BOeABF'BO_(AmaBD]' BOWACIB)'BOBd\D+'hBO@0B{UCy' eBOt@BDD$j'QBO@fBXEPDa'BP @B:XEK\'BP@}iBO_Ew'BO@B8D 'BO@Bz[Dة'1BO¹@hBZ'Dp'8BPO@B+0Dw'D ' BO@BtBe'#BO}@XBIDl.3'#BO_@fBtUB''BO*@CBYРDUR,'(+BOa@BhjE?BR'(,BO@+BF D'(BO@BICE:u'8sBO@amB\Db'8{BO]@tBoEk5'8BOׅ@B3D'8BO̦@mBTF'8BO̟@=BFD'E5BO@VQB?D'HBO@2BqRE!*'HBO@fBPDғ'HBO@5B{2E@'HBP 5@B;Ch;'I BOw@:9B^D'IBP@B?WEt 'IBP @BVJE#>'IBP@B/K.CjӲ'IBP.@'ByzQE_'IBP@B)E+ 'IBP2@BZb&E'OBO@o&BTD,'OBO@NAuE$'`BO@jBB)Dɠ'`BOó@%_BE52O'`BO@B!E'aBO?@mBXrD'cLBO@_BT;C5'iBO@bBOEJ|%'BP &@B1E2'oBO@BQEc'BO@EBS%En'+BOx@EBnPF&"$'-BO*@EaA.ME?w'aBO@B]=DU'5BO@BdD'EBOH@B]2-EX'MBO@|{B`EB'BO@>FB^{Ḛ@'BO@B[^Eb'BO@BvE$Y'BO@qBh9D'BOr@BkDUA'BO@Buw@E')BO@gBW3E2:'.BO¸@wBTvDS'BOѭ@qBDu<'*BO@BM*DV'.BO@B'3BO@iBME8'6BO_@@ BCZDj'mBOP@.BJ}D('BOޮ@ޗBeODi'BOƁ@yrBLFD|'BP@BgLXEt@'BP @B/D'BO@VBbE'BO@vB:D$')BO@BmD4a'BQ-5@dAV{D'BP@SCFLD&'dBQV@ B|qEX'eBPݫ@CBD'fBQ @{:BEL'gBQ0S@aBG6D嶇'BPގ@C 'BQ@HBE@')BQ @BMDu7j'"BP@B>zE '!LBQ-5@dAV{D''vBP@BpEr'0lBQ@?BDYG'3$BP@,@BEhT'9 BQ'@ BE'@BQ.@kBE>'@BQ@9BE.-'BBQ h@+@ؗ"E %'B BQ@dBsEUc'B#BQ@BE'B$BQ @BpEՆ'D,BQ!@BDq'D-BP@\BNF%'DBPэ@3BnMD-'EZBP@0B4D['EBP@IB'HBQ$@SBXE'IpBQ @B,E;j'KBQ @CBFay1'KBQ%@CA:F-}'QBPK@B>ǛE 'qbBPӨ@nBDJ4'qdBPË@B;E'qhBP„@yB*(E$'qoBP2@eMBEd'qpBP9@؃BBPEĤq'quBP@5BA'|2BQ1'@B}E'|6BQ1@6B+E8x'BPr@QBn'%BQ @BEVk'(BQ.@KBn'?BQ1@DPBdEpE'BQ0@9BdDBe'BQ(!@cBj|E'BQ0D@$A:{EZS'BQ0@B2E/'BQ1Q@}B92E)X'BQ@ BWD'BQ@ѣBE!$,'BQ/@B(Dt'BQ@bBb;DL'BQ@BmE'BQ7@ ,BM'BQ&b@ "B E'BQ/@)BKbD^'$BP@CBPEk'ҋBQ-5@dAV{D'ҕBQ)4@)BZCEa%!'BPt@{BE3'BPDŽ@BE8K'BQ#@BZDCv'BQ!n@BjD'BQ'@!BEH' BN@hBBC5'BN@~B[E5'BN@?B.E'BO@C3B E'BO@SxBrEB'$`BO @K&›cFQ!'$aBO y@BaB3'$bBO]@BE<'BBOL=A B̯C-'CBO2A BƚES'CBO@A`BCo'JbBN@B7 D͊f'[lBN@7BkEb'_BN@NtB1wEbl'lYBO i@B/fE'mNBOc@ gBM~D'mOBO i@BE'mQBO @BD''mBN@BtE'mBO@pBFDǰw'mBOO@.(BE'mBO@BhD'mBO@MNBT'mBO@B E^'mBO@aA$Fn'mBO@SBuE*O'mBOG@EsB$D'mBO@BmE͢'mBN@BEx4'mBN@UBDӷH'mBN@hBciDk'mBN@B'%E='nBO*c@Bú FFrm'nBO2%AFA DFk'nBO!@W\BE,'vBO ADBVD`'vBOA!'BԘ/EĶg'vBO"F@DBń'vBNa@2B圬'vBN@#BtDZE'BO\AA[BrC'(BQD@XAGB'BP@ѩB' BQ@\AE' nBQ!@퉴A8|E' sBQ@NBz>ET' uBQ(2@-A`' 3BQ6 @sB dE' 3BQ@XAmZEȼ' 3BQ@!B0lDW' PBQ74@\B#D' XBQ@DKBouE(' wBQ@BE;' wBQ@BVDYol' wBQ(2@-A`' wBQP@B ;D' wBQ#@{AODV' wBQ @0AٛD.' wBQ@?B ' wBQW@A%ET' wBQw@QB`Ds' wBQ9@X};cE' wBQ@ԺA8E' wBQ@6ADJڢ' x@BQP@ B$iD3' zBQ%Z@gAD2' |,BQ>\@ƛB 3EKT' |.BQK@DE5' BQ?@B zEa' #BQ+@Bzr' -BQGg@E' /BQK@jvB E' 3BQ.@_BcE' 9BQF@%AEHL' =BQ@AIE' &BQ@;By' BQ?@ ACd' BQ@BCH' BQ/@BtB'%' BQ@OA6Ee9'`PBNA)D'\(b[BI@&(B{D8j(xBI@DBDpz(YBJ@Cy(\BJ@Cq"^Db/(HBJ@B(BJ%@ B;Dp((BJ@2BB(-BK@B(.BJ@eB+EA(0BJK@ Bp[$D I(BJ@BE 2(BJY@EB)Dr(!BJ@,B("bBJw@ lBC>(' BJا@:IBYUDQ(56BJ@1B"C+G(5BJW@BtD(5BJw@EBDl;>(5BJ@>kBdz.Dr(7*BJ#@(BL(TBJa@BOED(YBJڎ@BBg(YBJث@ lB^C(BJ@B DjT(BJ@BB(BJ@8B D.(BJ@@1cBtDt( BJ@%BC39(pBJ@ƧC(dBK@B(gBJY@&B?(jBK@B(lBJV@ QBYD (mBJ@]Bm`CѮ(ՃBJ@ȰB:(tBJo@EB(BJ2@B|(|BJ֏@B(BJ^@, C"/EQ(BJ@禝C.D &( BJl@Bf8Cb( BJ@䟼BnPC(BJH@BUu(BJx@5BXDEc(BJH@BUu(BJ@.BTVBLz(BJ0@:B(D(BJ@B{D(7BJ@BD~:(ABJ@FB:jD(VBJ@,BźEք(!vBJڈ@bBMD}(!BJ@GBCq("BJ0@BwAY-3("aBJՊ@$BD+("bBJ@'Bw~D ("cBJ+@%BT(&OBJ@C"bN(4BJ@"OBdDx(56BJU@ AE A(5BJ3@BYvYD(5BJ@BJH@0C-5CE c.(HBJ@/BVsC5u(BJ@VBV(Dn(BJJ@BD'k(BJ@SBLD(BJ@vBPDrj(uBJ @TBh C(nBJe@BlDt(oBJ@Be]DƯ(pBJ@^B[\DwK(rBJ@^~BzC.ñ(sBJ@UGCtDS(tBJ@BDj(ՃBJJ@SB⬯EWͫ(OBJ@BsxD(QBJ3@BYvYD(BJ@xC - Db:(BJ@0BW D#](BBJؚ@qBDBeO(BJh@CB(BJ@WZCuD(cBJ@Bd*Dir( BJ@Bp0!(BJ@ȜB Q1D{(BK@BvqA_(DBJ@PC9(HBJ@BzB?9( BJ@=BtuCɯ`( |BJ@GBV|Dx( BJH@QB%^C$(  BJџ@ItB(  BJ@8tB"( BJ@C!C( BJt@a CF ( BJ@CJFK( VBJ8@EBD~r( BJ8@NBTC( BJ@޿C&V1D{^_( BJo@g8CN?D( &OBJ@2OC dD}<( 3BJ@NBzDB( 5BJџ@ItB( 5BJi@4-BRJB( 5BJٟ@xTBe7L( 6BJ@=B}EEc( 6BJ@qB8)D,( 6 BJg@yC&6FCaD( 6 BJ@~~C!( 6BJ@YBCV( 6BJ@[~BDK( 6xBJ3@;B ( 8BJ@ CzD[( ?BJԛ@Bae.Eb( ?BJ͐@Be=D( EBJ~@<B@( MBJ@NBmXD)( MBJm@$B{]DD]( NBJ!@ UBl&DU( WBJx@2B^]B( YBJK@C9!?D o( \BJĞ@蜭CeE?( _BJ@N C yFO( qBJ@BGEL>( qBJ@VBCn( qBJ@C DcB( qBJ@C?( uiBJ"@BeɩD\( )BJx@5BXDEc( BJv@B}phEV( BJ@Bw0Ena( HBJ@sBOD>t( BJ@ByDD&( BJ@sB̗E.( BJ@dBy'hCo( 1BJ@BnܢD( 2BJ@BwwE q( 3BJ6@掖BLjE( BJυ@彬BU AAb( BJњ@E`C=uQE( BJҽ@BCFt@Də( BJJ@_IC!wC#^( BJ@jBDo( BJ@ByDD&( BJM@%BDT( BJ{@\B4C)( BJ@PC ѴCJl( BJ@C?( BJ@#BdsD( BJ@=BtuCɯ`( BJ@ B_qD ( uBJt@vBlr( ~BJM@&Be(E/}( BJ@RB^/DW( tBJ@(C!S( pBJӱ@,Ck~D>( qBJ@CD( ղBJ@CRsHD4 ( OBJ;@BhCL/( ۮBJ@1C} E?( ۯBJ'@UfC:0EIV,( BBJ@yBf^B5( BJ @MCEU( BJ'@@CEӝ( BJ~@;BA( BJ@Bt!EG( BJ3@BzEY( BJ@"SBMCF( BJF@;BE~( BJ@9C.eE( BJ@CME#( BJ@Bj( DBJ@&BoEd( BK@cBڠ( BJj@*B EY( uBJܶ@C=c( BK@wBC1>( &MBJ'@UfC:0EIV,( &OBJ@BeEAD( DBK@pdBsCVw5( _BJ@sC:WE{( kBKk@mB"N( BJ:@#B`( pBJt@C=CCq( qBJ0@濄CE.( ղBJב@C/Z( BJx@vmC.C( DBJ=@ C@!CD( BK@B̸DrI( 9BK@BD:( BK@#B$Cm<( BK@X@)E( BK@iBf?D[( -BK@uBXBŝ( GBK@B d>Dч( IBK}@( ܛBK@BEn( sBK@uB"E%( BK@vB]IEW( BK@;BˇEh( BI ACC_I))BL}@yC2E)BLk@+C>E)BK@CEԎE')BL@C\D8Y)BL@BC,DF& V)BKo@C El3U)BK@yxCBuEߕ)BK@CݻE3Y)BL@*C|m)BL@C{cDX )$BL^@ӦC6sFdp)5BKY@vBD )DU[0kBQ AV9rBe>D1c0>BQAV?B*beD"0$BRuAVlBybN08BQAUҥC_10;BQMAUWCEcu0QBQdAVECB)DJ0RtBQAVD1c0BQAU[C_?0BQJAUԣBD0BQAUÔBarC00BBR:AVS_BiD ~0BRAVq\B]0;BRzAV`BD0?BRAV_BDf0ABRAVR#BD>0FBQAV8,BIxCӨ0BQAV69BnB%0cBQAUۡC lDey>0eBQAUCBC瑘0uBR)AWMAQBY0gBR$AW'IBD?0;BR(AVrB'DLM0DBRAV-BC0DBRBAV3B5DH0DBRAVjBCH0QBRDAW#B_SC0QBR GAW'AD0QBR AWB9u&CxNv0QBR AWB/CQ0QBR lAWB<0bBR AWAB4C%yJ0bBRAWiB-Cަ-0eBR AWB*Bw*0fBR)FAWAKjB0gBRLAWB51CO0mGBRAV{B>4C8P0mIBR_AVBwB5ϭ0sBRAWFB+AW6A 7D60QBR AWBD>0bBR AWB8B0eBR AW;B"DO0eBR 'AW,BDRL0h BQAWSAZDi0m?BR{AVrB.lB:0m@BR JAVBiifDx0mCBR AVBaDsp0yBRAVB D0BR WAVZnD50BR AV;A0BRVAWAiE 50NBRZAW5AӰE@&0OBQ8AWXBBD0rBQAWLAlD*0BQAV:B$Q0BQ AWwA-0BR AVB`cDlm0BR NAVBDc0BRAW3?BDc>0BRAW5AD0BR 1AVcAD0BQAV?B(pDd0;BR AWB4.D85+0=BR sAWBD0>BRXAWADDw0BR AW:B6@L0BRAWB, 0tBRAVwtBcDUv0 BR AVBCB0 BQdAVDBB 0|BRAVf9BaD30BR>AV^RB(DA0&BRpAV\Bs0BRAVAVh,BDU0RBRAVBgD0RXBR:AVB EDc0RlBR2AVj`d*D0RBR AVBAeqC0SBR rAVQBX0SBR AVA0BRAV{)Bu\0BROAVA[D90BRtAVxA/D00BRpAVdAt>DZ0BRGAVKB7@B?!0BR#AVlRA(D0 BR6AVx2AVDS<0BRAVHzB#:C<0~BRAVBʜDk0ςBR oAVBD120ύBRAVz[?D{0 BR(AV]~AFCǏ%0BR AVB^A340BRAVKBCX0BRAVF=B2C%s0BRAV=B#iCz-0BQAUnBtZ0KBQҡAUIBQVA&0BQҘAUiBYI0iBQ{AUMB( @Aʄ0eBQҀAUBFhBBx07BQlAU!BD/B'09BQkAU8BD1B~{0MBQӻAUBIC1BPn)AKdBXE 1BPTAIBݶEi1BQgxAIB1%BQ#AE~ƒFP1BP#AIyB+Eʅ1BP_AKBcE1BPAEAJfB(F5f1 BQ]AJTzB!E1 aBQ2AFrAF1+BPAKȹBQNEй1BPACjCwE1!BQAMf"y?}1!4BQAO AFE@1#BPAL*B/-EᲿ1*|BPAL=B:^1*BPALBLF#j1*BPALBPD1*BPO*AJB9FFg1*BQbAH{Be&E1*BQAAGڷB nF7˘1*BQ1AGOAS^1/SBQ0ZAO(TBz`B1B F"1IBQh,ALMPGEQ1IBQAOtA܁%E1IBQZAK@A"zE1KBSPRAL.A˾DrQ1L{BOXAH)C:1UBP AD±BV+D^p1XBPAN}9BV1Y+BQVAHuAE1Y.BQ:]AGAF71Y:BQDHAGӲAE1ZhBPAIB'E"1czBQ/1AGh@AhEP1c{BQACB'1dBPAAB1fkBQgAIAcD11fBQg8AI{Bgm1gOBS\ALA&DvU1jBQMAMATE1jBQAL+BFp1l1BQkAN;@WF&1oUBQ_-AJu>F/1oVBQ^AJ߁A F=1oYBQgALV9&:E1oZBQZAKBEJC1o[BQ[AJoBE}61o\BQ\AJA9@EZ1oBQ\ALG?11piBQ ANA漓Es1pjBQAN6$AHF<1qBQANBF 71qBQ{AM۠ET1qBSn ALQB?1qBPAN}9BV1sPBQyAM+qEF1sQBQAM@:F0P1sBQANB(Dmm1tBQ\AKAn EOJ1xLBQ^AJAtF! 1xMBQ`AJOMF1BE &1{BQADB,NE1BS_AJB,E=1BS`ALA 1DBTAGB>Cuz1BPAITBE1BPYALBGeFGZ1BQH AGUAw%EwL1BQQANX@FEWT1BQ9AN?ALP Dk1BStAKB%vD.*1BQAMQF1BPAMBF]1ȔBQ`AI[A?}1ȡBQgVAI8B@}E T1ʌBQAAN]AFE+J1BQMAM֬A19BQADBYF@ 1WBQAOA1̲BOAHJBE!Y1̳BOAHB>ET1̽BPiANYB1̾BPAMAB EP1̿BPALBY FV<1BPAMIA\)1BT AIYaB3{F11BQvALǃQE12BQiAKQw)Fy13BQdJALmE'1BP^"AKBҐFz61BPK]AJBF#.1ԔBPALB$$Ev1՚BS+AKCB(5ZD1BSAL A`B1cBPEADqCBz^1%BQHEAHVBĦF:w1&BQ>AH&C%F1BQdAIAD1BPЄAM\BbF11! BQ@ANAEӛ1!*BQ2APB?DPs1!MBQ3AOzBV4E1i1*|BPAL:BOPEXAa1/SBQ4tAP4AhEZ10HBQ4]AQ;BEj1A\BD^E1!BQA\Bȴ1=BQ׃A\osBvQE1=BQ8A\>BgE&1=~BRH@AZiBWE1@BR0~AZBmEjEi@a1@BR9AAZBb.EL1DBRiAYpB{1GBR9AZBmCb1JBRjA[IB;~cCE1JBQ^A\ BKCA1JBQJA[ mA^1MBQA\ B ȴ1MaBQ7A[B?;E,1VBQIA\8B33EƗN1V'BQA\EBNcDh1V(BQqA\([B<E1eFBRA[BCEc^[1edBRA[B5E1hhBRA[gB/E1hlBR"'A[:0BLqE}1hBQ;A\B`D!T1hBRA[B;P1hBQAZjB1xuBR#RA[4BOE_1BQA\`BC*E8f1BRA[ؚB65EA+1BRA[^B1`B1BRJA[B;s1aBR&A[.B_1cBR*A[ZB*]D1BR_A[cBK+:E W1BR.AZB\(DP1ɎBQA\B[E`01ɏBQA\xBD1BQpA\BUDfh1BR7AZBSEV1BR>[AZBH1BQA\BF* I1BQ&A\lBE;1BQA\%B|D[1;gBRtAX{B1dBRmAYfzBAlE2P71BRkfAYB1BRv:AXBDڍ1 `BN,AYBF4OQ1 BO#1AYZB֌J1 _BN.AYBEԹ1 BNAY[BhF1 OBOAYȳBaDw1 fBN AYBk1 rBNAY?BE1 rBN|AY B8E1 rBN|AYBl=D>1 BOAYBE1 BNAY=ByE 1 "BNzAY B̢:Dd1 BNAYW$BD)1 :BNyoAY!B]/1 ɄBNtqAYOB{Dm1 ɉBNyAYTB˽D 1 BNQAYKBCRF:dN1 GBQW,A\+BE@'1 BQIAfAFG 1 \BQHNAfAܞE1 jBQIAf AEΒ1 ~BQJA_>F&,1 BQJA_B"lF21 @BQzA\w^B#Ft1 BQXAcXgC܃E1 BQPA`dBED1 }BQH AgPBl0E1 !BQA\BQF%S1 #BQ?A^yCtE1 #BQHiA]:sBŋF*/1 $BQTAa'BgEp1 ,BQB8A_H@]EB1 -BQHAf+BsE91 4yBQIHAhAV EpC~1 4BQKAf§Fi8F1 6kBQWAaADτ<1 =BQ@A^BbE 1 @GBQA^B5DUV 1 @BQA_B)V1 PsBQA\mBo6D1 YKBQASA_|BAEpU1 eBQ#A\]BAE?T1 eBQ}A\[@BB{6EQM1 fBQ^A\dBE01 fBQGA]CPF'1 fBQOA\)B4Dv1 kQBQ[sAbC TEkWN1 mBQItA`0/B{Ev1 mBQIA`4B6`DB1 t?BQ\Ai*iBJ6Ed1 tABQWAi#CcF'E1 tDBQ^Ah6B E(x1 tNBQ[FAi`Ek1 tOBQ[/AiFAlTEo>1 -BQ}vA\KlAl1 XBQCA] ALEӷ1 BQZuAbC'Ej1 BQQtA]*CF(uY1 BQWAA\(BwEBR1 BQIAe BK0Ed1 BQONA`7B[E;&1 BQGA_chF<1 BQA_B)V1 BQA_B)V1 ;BQRAcC-Fiz1 =BQYfAb7CG#F1 BQcA\eBD%3C&_1 BQjA\'XAMEd1 }BQ[.AiF)@Ư Eb1 BQ_AhB{{EG1 BQO-AhAUB|BF.1 BQ[,AhB__DWK1 BQJIAhb B]D=1 BQVAiC6>rEZ1 BQbcAhB2rD 1 BQb7AhBFCm1 BQA\wJBSD1 zBQEA^BrFDU1 YBQQA`B%D#1 BQTAa7BE1 BQWUAaA;C~U1 BQ3A_NB&E_1 ?BQGAgݻBE@M\1 @BQH5AgqTBnD81 ABQGAgBAE/1 BQP2A` B=C`1 BQA_PB:^@w2s1 BQIAeBe1 kBQV_Ai"CVyE1 ɜBQNAh/AEq1 ɟBQXbAiC+lE1 _BQ[pAbmBvE1 `BQV%AaRBPqEX1 aBQVTAakLBItE;1 ͊BQ_AhBMD1 ͑BQHAgB1 ͦBQ_(AhA_D1 ͩBQ\Ah-AA0DD1 ͫBQ]%Ah)B$D1 BQVAa'B^1D]1 ۣBQA_B)V1 IBQanAiE x1 BQahAhB8Ey1 BQ`Ai|EB1 2BQWA^BeE1 BQTAhBOE1 BQBA_BETC|1 BQNNAej5BÿFBAOB`H1BT`AN tBTF1?BSAIBLEU|1 oBTKAN+BWF9&1BSANbBf lF1BR AV+5}EEK1!BTAOY$BY 712BT2AMF\j13SBSAI(BXDC19BT6ANz$BcEF&51?BBSAKpKlkF{1?RBSAMj=F;1FBTOAN\zÁFVՊ1F BT^0AMCO F5O1FBSAL(B<9=C71KBSk)ALdA}EA 1RBRAV{BKEFv1T_BT+AHإB<1U)BRGAVNB\MCC1U*BRAVBaERD1gOBS7ALڂA Fs1jBRAUvBM1qBSALC-F'1qBSALTBk\E1qBSDAK֯È&F1qBRAUvBM1sBRAWi BdTE1sBRAVzBCu Ed1sBRAVBbܑEMp1xBSiRALcWB 1{BR{AVBwB:oF_Z1-BTAIOBWfFP !1ʰBSAMxÉWF1ʱBSʫAMLBE$'1ʲBS`AL+C]F't1BT4AHB8UC81՚BSAKeՀF1՛BS?AMA!EzY1՜BSAKgíFq1BS?:AMWA]tFS1BT {AN2fC?;F1BSAL7+¹vE 2 `BKA]B E^2 BLA\BiDˡT2 BLA\BCu2 BLA\w6BDKd2 BL A\@BDKf2 BL A\9BͧCڋ2 BK֑A^eBѾw2  BKA]b&B;d2 ,BLA\ B^D2 LBLA\cB2 BKmA^CEOL2 BKA^ B|D#2 BK]A^Be-E2 vBKmA^B GD:2 xBKA^BEn2 BL)A[CBj2 VBKA]LBA'E42 BL)$A[cBtCM%2 "BLA]8Bѡ(Dt_2 /1BKA_B7Cٌ2 /2BKA^`C/cE2 /3BKߺA^@B =2 EBL)\A[CCY2 EBL(A[QB D52 EBL)$A[cBtCM%2 EBLcA\GrBEG'2 EBKA]a6BmDis2 EBKA]B P3[BUFA1}Be_>D3BUA2?hBQQ:E3%BU A3SBQDExR"3BU3A4BD `3BUA1BnObD3BVA/}C"!OFy 3BUrA5>B P3BUPA2Bg4E. 3.BVA0&?@ٮaDlBZ3HBT[AMuCFP 3FBTr"ANLB D_M3BTfANBBN 73BTCAH{B^13BTaAN@B>T3BV91A)AO@EUMx3BU]A2naBs(D y3BUA4nNF,Ӳ3?BT:AHBXF<*3 BUA9ԵFc3 BUxA;NvBoEI^3;BUA9GCD&KFhO3=BUA:KC_FL3EBUA:-C3KIF3BV#1A+8B,C-3bBUiA<;xFtc3JBTyADBaD3&BV?A/A Eƽ3*BV \A.QAÙF3-BV3A-BF;3 BUHLA>|CFk,31_BUABI~BXF33SBTUAH-BuFB"38+BU}A8`H#Eœ3:BUA9vW}kF 3;WBU6A8bB6E c3;BU.sA@O0B>_EF;3E|3SBU6A8B7\EH3SBUA6XC'bF53SBUA5yB F 3SBUfA4ܔB5LFg3TBV'A-ICkFR3T BVA-sOBÎE3TBUA8B@dC#3TBUHA7B- =3TBUA9B.F'3T_BT5AHRBa3EV3UmBTAEB kCPf3b73z)BU~xA:B4Ey3zBT%AE}BaDvr(3BV#A+A+Es3QBV A.FF:w3YBUIA4#IF:3[BUA2] F"3vBU'A@5BpaFb3zBU`JA=BS+E3BU.A?Bn3E3BUAA r"HEFt!>3BUA8SBB-3BUA4 kcF7p|3BV1A+A3BT)ACB=E,3DBT(cAH`BHrDl(3BU>A2rBY %ExBa3BUA1rIB E3%BVA0B>"E3vBT&AF(BbN3yBTAF(BC3ɕBTADtBC"3ɣBTfADBͪE3ɩBT1ADoBF43BVA0ekB1vF3BV A.rB J3BVA/BDF3&BUpA< BkEz3(BUwA;Bs:D_3BUA3|mA!E+}3BT_fAGBnF3BTq>AGBeF3BT{GAGU|BGFb3BTd AG#B2Fd3BTrAF]B1kFǴ3BTAFa&BAE3BTAEBQFNN3BTUAEVBʪD3BTADLB6#E3BT.ACSBF+h3BUAA8C~6FqK3BUS1A=BF=3BV fA.A F 3BU{A:tBiC3BUA9B12D3BUZ0A=fUFb$3BU:A5B1'3BUA3:3BI>w3BU]A2naBs(D y3BV UA.ԪB~N Evu3BVHA,CF?3BUA7^kB9X3sBUA7JeFqN3{BUĆA60CmF!3|BUA66C0FV:3BTj AGBHF_03BTjAF}BlcF%3.BUA1sB1FW/3/BV'A0BbF/3BT*AEuBF) 4BOA4òCӏD4BO/A+dCK`Ck+4:BOA/)CJD64=BO\/A.CE4BO2A/C?F+4 BO>4)BO_A+]CLg3D41WBOIA.|CLCDH43BOA7Bq49BOCA0χCV4>BONVA.NCwE[k4?hBOMA3nBFx4?iBOeBA3CnE-4@)BO{KA4+CpCL}4ABOA4dCE04ABO8A2B\4FBO>KA1-CE4GBO4A.*CD~6Fko4GBOr A,CSUF4K3BOA4BDa4S&BO|A,G/Ce 4UBOA4єC B4UBO`A-IKCPV9F4UBOX}A3|CvF&4\ BObA4C!DPE0p4\ BO=A2BODbt4\BO8A2B\4_BO!A0CF>4_BOdA-Q^CMLFz 4_BOA+{{CvnF 4eBOA4C%Er4eBOKA3]C"F%U4gBṆA0@D]DY4hsBNA1C;4hBO{A,Cc4BOA5eBզE4BOukA,CoWFKO4BOhA4FXC c.D{42BOIA6SB-D3)4LBOA5BcFc43BOA5BnF4ԖBNA01CC4BPWA,1uBD54BO|%A,fpCgD4BBOA4fCF=D{u4iBOA7B5FN[4BNA1C;4 BOA.iDY/FL4 BO A)C&tF^F4 :BOBA1Fz4 =BO;yA/5CֶFM R4 _BN4A.WzC).E 44 BN(A/jFDvF:4 BOyOA,ZCGCFK4  BOQA3SUCFO4 BOA29kBnE f4 BOxSA,eCX?C뮛4 BPA(sBE\4 ]BO A1C0AF$Nj4 sBO-A2CEV4 BOdsA-8COhCs4 BNA0HC^4 )BNƹA.evC2F7x4 BO"A+ECuNE4 BOOA+C|g!F4 )BOA+GvCT<EX4 /cBO3=A0wCFq4 /fBO+A/JC_DA4 0BOA1BSQF4 0BO#rA1ķCF &y4 1WBOA0HDMFm4 3BO'A1ՓFi4 9BNA/C5F?T4 =BP1A( BV4 >BO`A-;C@PF m_4 >BOnA,C^*Eb4 ?NBNA.e~CDI64 ?OBN9A,[CI5Ft4 ?hBO9A3XB`DsR4 ?BNA1C|E14 ?BO A1&F~m4 ?BNʃA+ C4 ?BNA0' CSE4 @(BOC7A2I@3EВ[4 @)BOyA45C4 ABO>A2BqCҦ4 FBOA/u CfE:4 FBN@A+MRC6E4 FBO:A0JCz^4 FBNA/ CF Vl4 FBN A/Cׯ;4 GaBO-A)C)]FKm4 GBO< A/kCXC4 GBOgwA-CCcF=4 LUBOXA("Bh2DeZ4 QbBO A2 BOSF7)g4 S%BNcA0#CGWF 4 S&BO}A,OC[tFP 4 UBNĭA/3)C&F78h4 UBO8A/Co9Fc4 UBOA+CF4 UBOeA-(CLDa4 UBO7A2BER4 UBO A*BFL}4 YBNIA.RCtE4 \BOSA1HC7F[V{4 ]BOA2aApFYH|4 ]BO8{A1uBv/E;4 _BO7A/ZBw\E4 _BOA,)+CN7F$I4 _BO!A*(CZPD~4 fBOcA-W-CVIE24 hBOrA,CX$E;D4 hBOxA,CT_}4 jpBOA/c|DT&gG*Ui4 veBNA0@5CվEH4 vfBNA/1CێDtԩ4 }0BO9A3 "B'Ep4 BOkVA-[CxE>4 BNA/CE|}4 YBO A1;05E!4 ZBO3A1QC NCq4 [BOA0CSBE֞?4 vBO=/Bm4BPbA8~B C!\4BOA:oBeEN4BP'0A:'B=D7)4BPA9QĮF4 'BP7A9xAB:JF/+41BPMA9)BgD"41BP=A9aB+AA=;41BP'.A:'BcBL43BPeA8}BCq43BP/qA: Be"D*|44BPkA93BOǮ4?BPA7B^D4CBPMA43'BP;A;zsB6;oEo432BPA8!4BE֓433BPA7fB,zEڋ43BP>A9MBAp43BPA:BB$F 43BP@A9$&CFs44BPhdA9-X)֠Eģ49 BP;A;zsB6;oEo4:SBPvnA8zqBzE3Sb4:BPgA8{BьD4?BPEA8C[KF/4?BP+A85(B(SFCy4?BPA9lE,4?BPA: \B3b4?BP,A9aBL$D4@BPA8,BԧE4B)BPA:J{B4iEH14DBP!gA:8BD24DBPkA93BOǮ4K+BPA6}BBD4K5BPA7pC1'4UBP.dA:B`ijC4eBP A:QUE9m4eBP§A:BB.D4e$BP§A:BB.D4BPA9B;7Dۺ4oBPmA;BjHJEMz_4BPs#A9BErD)L4BPaA9B_Ed4BPs#A9BErD)L4BPA;FBCZEq24BPZA9nB4BPn7A8ҡBYE<4BP*A8BlE4BPA:BE.JBRtF)4BPAABF3 4BP.A>zBzE4BPA@F4"BP[AAAa=Fu4^BPA@)NFBb4*OBPPA?IB:1Ag43BP?A=5=|FMc43BPA7AHF'>4CBP:A<B4CBPA>nBt}EQd4SBPDA?|2BEC4[BPXA>QBD4[BPA@$BoE4\BPA?(Bc[HD('4\BPA?AB0F4czBQtAEyPT7FW=&4c{BP,ABOC*Fmã4mA$ND46BRbAYBlCҴ6BRdAViBX6!BRsAXB9DE65BRzAX/BElK65BRAWBj5_E66;gBRgGAYrBQEl6;hBRTQAZBfE>6;BRSAZB2Eo-6DBRmAYfzBAlE2P76DBRrAXA^EQ6GFBRAW%BZD.Q6HBRXAY-BcEG^6U*BR@AV6Bcy6["BR"AYtB3V6dBR$AYA5Dh6dBRkAYJx/OEl6fBRAWʠBG6E6fBRaAW0B3aE@{6fBRAWy0Bb)LE6hBRRYAZ(Bv4YD~6hBR`RAY&BE*eo6hBRtAXB`9E7w6hBRvAXxBD-6hBRvAXB/tDg6sBRkAYBߛD56sBRxAX1B~$D6sBRAW&B\%CZ6BR|AXB"El6BR(AYXnA6BR%zAYsBDhD6BR"AYgB 6TBR#6AYB,q6}BR6AWwBW5/EF6~BR~AWsBv6^BRAWkqBwSD`v869BR({AYUlAQB6BRRAZBrQEvx#6BR?AZ5B`E6BRRAZBEfy6BR%AY]A<D>6BR(iAYc?B 6BROAZ1BjvE6BRmAVPBF6BRsAX&EF6BQpAX%A\)68BQݚAXshACdB6BQAX{E6BQAXGAr6BQ4AX$}E6BQAXA)Dr6BQԲAYlA6 BRAYBMÖ6BQmA[DOAAD6%!BQƑA[|/Ab6%"BQA\S7BGODU6(BQ6AWtA$6(BQAXA ADy'6)BQAXCRk\EJ6*BQ0AX\8ADd 61BQiAXAx!EXP629BQAZB,J=>c062:BQAZB,J=>c06;VBQAXADK6;XBQAXAD6JBQƑA[|/Ab6P2BRAXB BEu6PBQAX$Eؒ6PBQA[B 6QBQAYKA]D46QBQAZ-B ܹEzi6QBQAYKA]D46QBQAXA336QBQAXbA9Cy6SBQAXw@E6WqBQAZB ff6WsBQJA[ mA^6WBQA\Bȴ6`BQAZB,J=>c06eBQϳAWiA'Dzт6eBQbAW!A^6{BQAXB!E6{BQ{AX}B&DUs6-BQlA\,/BQEJ6BQA\BXgD+96BQkA\TBEC2i6BQpA\hAz6^BQAZjB6BQA[BPD 6BQA[ AE26BQA['BSD26&BQAXKA5E5!6BQAX8 AC#6BQQAXSAD^6BQAXLBrOE9t.6ɎBQA\BAEY_6ЧBQ AWrA2E~6BQAAXAG6BQ_AX=1Au6BQAXAAD0[6ۃBQAWAV6BQA\Bȴ6BQA\AC@=x6BQA\Bh/6BQܵA\BlBQ)AUB-B[.6BQAU)BaPDo f6BQ4AR]ӯrEz6BQ:3ATιBjE6BQүAWyAԑ D(6BQSAW-B(}6BQ AU𡿒ZE 6BQAVB{D 3t6BQXAV!{BL6BQAVLByE56~BQ7zAT0B"LmD1d6BQ7ATBB 3E ^6BQ4ASA*ELW6[BQVAUbBoD`6eBQ܇AUsCE[6`BQ5 AR%9ADj6bBQ5AQؖAeD!67BQ݄AUCEr69BQ҄AUjBl%A6BQ9AU Bh6MBQҁAUIBPlD*>6BQ7ATB9E׍6BQAVBWbCM69BQ4IASp BEH6:BQ4ARKAe6E(6\BQAUBqDH56ЧBQ҂AWA6BQΧAV*B RD\q6BQAVB|'D8n6hBQ:-ATP BQ6iBQ4ASfA6/BQνAV)B3C)^6BQ=AVXB-D6BQAVBiD6cBQAUC356BQؿAWIA`B63BQuAWp@`D{66BRnAY1Egr67BRAXZAnEeZ$68BQAXLAȴ6BQAXAE(6BQKAXtE=V 6BQAWGAgC6BQAX8IEI6BR!AY=BOEv6BR AY Y@&p5E@I6BR TAY\BC#D6BR AWAcB5цC6 BRAYBL6uBR(AWAkD6{BR AWB};C.6BQAWwA@^Y6BQAWyAK6BQbAW!A^6#BQAWYAZAf6'BR#aAW,A)rDy6'BRAWnB{OE'F6(BQnAX;1BD6(BQAXB^E>6(BQAWِAE6)BQAXB+~E6*BQAX^B/D[oA6*BQ*AXAhD61BQAXMAD>M6;VBQ>AXA"$C/6;mBRvAYY>'EnK6;nBRAYGB}REd6;pBRtAXAWDv6;zBQSAW-B(}6GBR8AZBm6HBRAY?CE26P2BR\AYAAEv6PBQ0AX6A C֔"6QBR QAWGeB3FA6QBRAY1B+WD6QBR6AY A EN6QBQXAYAǃE96QBQAXR<E6S#BQXAX[ B1As6SBQAXAUDw6["BRYAYx2BnEA6`BQաAWiAeD[6bBR HAW,B%Dn6c BQQAX[B2-B&6dPBR!mAW=B7 5E#_6dSBR^AWJBM>+EN 6eBQաAWiAeD[6eBQbAW!A^6eBQAWb A۷D76h BQAWlA"6lBQAXA)sCK6lBQ*AWˢB^DC6lBRQAWtB(3DV6lBQݥAWBME9l6mBR AWBDCK6mBRAWEBD6mBQAW'AEd:6mBQ3AWB E#P6wBQpAWڟAEh6{*BR(AWgA|D0V6{;BROAXADӈc6{BQAXA1EP_6{BQAXqA ``E{6BR(AWAҰCͼ62BR$AW'ADn6BRAWVoB1D\6BRCAWBD܇6^BRAWnBDwZ6BQաAWiAeD[6BRIAW A D ~6BQAWBEE6BRAY;#BhE6BRWAYdBE186BR!AYB*Ö6TBR="AY{BEK6`BR.AZA[E6aBR AY~A1,E&/[6NBQAW^A/6OBQ.AWpAC6BR AY!B&]QEA6BR AY^B̳E$X6&BQAX4SE~6BRPAWjBB6BR)AWVBBC\6BQAXrB"EK6BQ AX8B;E? o6BQAXABmسEl^6BQ:AWAX6 BQAXFE6 BQAWCE6 BRAWBnyPDD 6 BRAWB D©6 BRAWBۤDWe6BRpAWdA|xBKx6BQAW[BE,Y69BRAYaCLEu6:BRAYA@D"6E|6BQ'AXoE 6BR"AYwBD/6BRAY8AkEV6BRAYn*A0E:6BQAXwEVA6BQ[AXe!B[)UE~6BQAX`cAQDb;6BQKAX8Z2+|E'26BQAXO!Aw\DxY6BQAX*jB]E]v6BQcAX`PCk Du6BQxAXZB1iy6BRAWJBDl6ۃBQAWqA7E$.6ۄBQ AWAAC86ێBQҌAWy@궫D̈6BR AW,BAŢ6[BR lAY"AC'6BQ`AXA|#EW6BQlAXFnA D 68BRAYBBeE%6;BRAY7Bx"Dy|6vF6 BR!AOiAkE6 lBR4AO@zoPE =6 oBRAN Aa;FM6 BSFALAD$6 BR AO2 AAKFf6 BQwAO;1BE6 BQAO AFE@6 BQAOAUB.JE06 BREhAOAF(E6 BR6AO~A\EW6 BRH8AOMAg*5E<6 $BQANWA6 VBQ=AODBpE6 XBQAO5LAE(6 BRcAO@RE6 ΔBRANKAFf{6 ΕBSAM=\F;S6 2BR$AO|:F( f6 BSnAM'ADm6 ٻBR-AOv@Emf6 ټBRAOj{A\6 ٽBR%AOnIBE|>=BLH`A.C%@ BVBA!B1kE)#@ BVBA!8A D@ BVAA!tA^0D@ BV@A!x,AGE@ BV>A!SAD>@BV>A!EQ?ҦD0@BVD !@?BV+A ߶Ah$C]a@ʊBVA!Q A~D5@ʕBV=lA AL!D6Z@%BV='A! mAl@ABV=A A7MD98@GBV=A! ADDQS@BV@A!~A+@BV=A!&dAyCA@BV@A!q`@@BV>uA!2ØD@BV=A >Ap8(D@>BVDB@h\BV,_A h?uC79@sSBV4A ! @ĦCi@֏BV5%A s!@B@BV4A @ @q@wBV,A o~@JDx@BV.LA f?D(@ BV/A `?DE@ BV#A a,BUE;1@ BV1A Mxc+D{~@BV5oA CA/lDj @ BVKA VE @QBV& A nXADi@BV5BA 4@DMYm@BV56A +@fDh@2%BV7A ӿ@2BBV-A o)DR@2dBV3A [jDU @4BVA NL$@6BVA M)Dɥ@6BVA [eDE@C;BV3A B}@DR @CBV4A &8@ C @C]BV$A aWA#CD@C^BV A N@[E!@CeBVA @ A.BEg@CkBV A "B<DO@CmBVA 2\ [SD@aBV5A =?K\D@aZBV5A ),B@hVBV5hA V/qDz@hWBV5DA \)8IDM@hXBV5+A DAQ!CA@IBV5A ePPD@JBV58A ^@D?@KBV5EA L _D@@BV 1A WBBE7@BUA G{EW+@BVA iND:D̙@BVA O 5ExlQ@SBV6A UAA RBWAοeE_sA RBWA@D%E&A RBWA2?DqeA RBW\AL@M̥EA RBW!A@UdEnCA RBWAf0E8zRA \bBWXA=SES-A `OBWA@W)E7^A `PBWsA%mE5A `QBW&Au@KuFxA jBW|AI%DA oRBWPA>`ECDA ocBWpHANFBA |BWWiE'A ~BWAeD(XA UBWÒAFAE!IA VBWAA1OE A BWA;/EÔA BWkA5@׏F1A BWvA?4EA QBWA=dAnO{F(gA RBW!A;@2?ELz>A BWZUAʞ1E]kA BWcAuEA IBWOAl`EV?A BWA¾D A (BWA.yEFIA !BW}dA@iȟF UByBV7A)ƁAXBIBV>A! GDyB BVBA"M_AAB!HBVAA#vœ8DF:B*BVAA&ABF B+BVB`A$IBB-EB.SBV:A#=FABDS΋BC1BV@AA$_B)Er0BC2BVA"oBbj'E2BBV?A"8B EFBBVBrA")AkE)BBV@YA"neA%EBBVCiA!ZB3=+E ABBVAA"QAIEr~B(BVFA(BI)CBBV?A$`B:eDktGBBV?A"xA^yDB,BV?A%lAϠCfC2BW$A @CBVJA  ElHCBVRA Kad)EŀCBVE(A 1E5CBVhA h>iOEjC,BV}tA @E)C,BV:A '˜F@C.BUFA BfvE3kC2BV A E,C:BV}lA ^`BNFnC;=BVMA @1F#_CF|BVcA HqEo!CFBVUA GypCMBUA @YE:0CMBUGA tECMBUSA CO:BUA k}ɁFBtCiBVA o<$ECiBVpA s L?EcLCmWBUfA a"3D7C|BVA B<FkC4BUA VAAFCJBU[A i]ED:1CYBVA (JuBEJ0 CZBV A O D6SCpBVA HD,CBU(A pDCBUA E CBUA HE!nCBUfA E"CBU{A h.Fk4CBU94A h>DZCJBVOA ӾLYiF5*mC BVA @^E=,C BWrA MCmwCBWwA /Ϳ炄ECBW`A cBAEvC,BV>A 9XC;=BVA ?*DCCuCaBV̿A ?:E+CaBVA T`F&CaBVA V'EpCkBW{BA s=y#CvBWxA ) DzCvBWmA ZR@E&+CvBWxA *DޓcCzBW'A 4Q@٪LEMCzBWA AhFPC{-BW1A S E%+C|BWCA dKOE.NC|BVA M@&E&\C~BWqA A 9}E2.jEXBTsA iB`ENE tBTqA ,[DaE uBTuA |jZElEBTA Y,dDdEBTA 'IqYEeEDBT6BA SA$DsEEBT,"A nZDE̼E`BTWA ?+DChEBTiA b¯-lËEBTTABEOKEBTVArB Eq!^EBTrA LDܡ8EBTOtAMh)EEBTuA NbAacEEBTsA RDE!BTYABEE"-BT6A ȓ GE4mKE"xBTA =oDEE"{BTA VE%BTu/A aB }EE+BTGA @k5DaE,.BTWDA ߇?pkPCE,/BTUA AC|<E,BT,A D:(E/&BTiA AE5E/,BTYA ɕCKE3BTWdAq9 E4-BTA m]hsE4BTkA =,E+AE9OBTX A 5?̪@D|E9SBTgA fErdE9^BT{VA oŴEE9_BTA &ɔ"D5E9BTA ,DKE>BT[pA אXDCE@.BTSA AtD$E@2BTTA @>:D_E@BTq8A F=qEE(BTv`A jxDEFBTVA c@kCD2EIBTSAʂBoXDEbEMBT|A XlEeEMBTmA AkFE#SuEMBTA 1E[dEO,BTTA @CEO-BTPA w?DEEW^BTpA ¨hE-EY&BTA 2DED{EbBTTA A mEh1BTBlA kLKlCZEh7BTDA tRICEEi/BT@A dϿY#Ei4BT@fA ZD/EmBTUA ?5 CGEBTS,A @1ECBTuA ]EKBTQXAePDEEBTdnA.vw=DEBTrA NC JE'EBTY%A EyEBTaA !DEBTVA U@D0bE'BTU&A LA>C+O EBT9A D{}KEBTPA @&DѹEBTISA 1DuEBTCA ]MeHE;yE%BT:A LEJ=E'BT8A ~u2KDsE(BT7'A DzEBBT/A RQEBT_AJE?ugEBTWAu^X4DvEBTZeA\}OxD}YEBTPA@6+EBT\AUMBEWEbBTsA *±FEINEBTl1A ĜBEuEBT\dA WAD EBTfA P/D4;E[BTQA oD׎E{BT[pA אXDCEBBTgA)GDECBT`DA:-.DEBTFA g$E!dEBTJ~A vc2EbEBT@$A L=4<D+E/BTL`A >|(Dӛ-E7BTBjA UyD{EBTA f]ChEBTuA B70E8QEBTnA ߩ =QD?EBTkcA D2$EtBTA x?DynEBTrA BvE!EBTPA AlEEBT?A =yEɝBTV;A !ASEɥBTOA :@ C9EBTAA Z;kHDEMBTa,A>>_DځENBTXcAwQE kEPBT\A`KEJEBTjAXFN?E 9BTbAA@CD&EDBT6QA  EEBT/A RQEBTBTA Ҽ@J;DVEBBT WAu@&WC1EBBSAdA-tEFBTA&5@dؠFgEFBT3A #A*FKEFBT3A"JFEMBT#AKϿEEMBTBADa=DXEMBTMA@EMBT)AL3E;EMBTjA]r@%!C\EMBTAI|@9}C:YENBT4A Aw:DEN BThA «[EdENBTPAŬ2 D\ENBTN0A!)< E AENBT-AENOBT)AE ENPBT,ALA;EENTBSA 8AzyEX'BTN4AG?3E#EX(BT1BAaAFEX)BT?AjEgEaBT*AvEEaBTVAX?}E1REaBT*UA^B ËEjBT,:A?=EEjBT" A@JEk]EjBT&A$D!EmoBT8AAZE\sVEmpBT74A:XE(EmqBT5A@CE}BTLA.Q-EhEKBTCUAHEeE(BT9A 4'C7EBTPA@i*REyEBTPAtDAEBTQ=A PD*ЯEBTPA@09EBTA H4A -ACEBT$|ANWEEBTA@DU:EBT*Aڕ{EEjBSBA“heF}E BTAAě@SE=BT)[A @ 6CEDBT A #@۪DE"BT6A(DDB!EiBT.A0BZ2DsNErBT6,A'CpEBT4ApE E|EJEՊBTA@sDE՞BT3ADQE E՟BT5ABEEBT A 4AɨDEBSA BqFNEBT.A EEBTA ;AN{EBTDAla~DXEBT:AlA FAEBTG7AA={E1WfE BSAdBnFfAE+BT A BE}0E+BSA b!AkF E+BSA 1AxF E+BSA 5AFE+BS­Au AoFIE+BSQAARdEE+BSɾAArCEE+BSA ǶA4E6EBSAiB FE&jE>BSRA A@EE>BS4A AEMBSA AF#EMBSA ǶA4EMBTA >A'$CEMBSA fAjDEN BTDA 9@9XENnBSPAB`FA{ENoBSAA$MExxEFBSmTA}BNgmEBSA tAPEuEBSA @PD`^EBSA_AOJD *EBSYAũAD EDBSACÁ EVqEFBSAYA٦EEGBSAsAFE^BSMAJ8AFj[ EsBSADfE0BSRA|*AtE'EBSAR$AsFۺEBSAAF<EBSȴAvA7PD{pEBT11C~EFEBT^1AEmrEBT_A85A!VF*ͨEBTAt.D8EBTۚAFAfFq/E$BTBAyD?xFE+BUA\A F#E+BTA6A EE+BTܯA;AAb4EpE+BTdA@YFDE+BTfA7b@EcE,BT;ABNQEE,BU(A8ABDċE3BTAl:FJED0BTLAF@ĘSEnEFBTbdACEEFBTsCA:MAVEFBTT`A@ǻE EFBTAE@TCEFBT[GA˿wCVBEN!BTsAA2EEXBTMAAaD1-EX%BT A`@-F+EX&BTvWA;?,nEEX'BT_cAY@C}EiBT\A@؁C`WEiBT%A0BS KExtEmBTAiAv}FzŽEsBTAYBh EPEsBT,AAE EBTAA95iEjEBT>A&AIE]yEBTPA\AEڗEoBT2A1A}EEOBTABB -CEBU+AAB[EBTXAjJ;|3F|BE\BT/AA:FE]BTA)A;EsEoBTmAUiA*\FYEqBUASA]-F@LEBTAnc@FFAEE=BTwrA7 EE>BTZAb7KEYBU4AfA$FعE]BTZAT[A/pEEжBT`uA|@ E{E BTAM*Ae4EoE$BTAˤA!pEgE3BTJA8¹IBFzDE=BTAADEXE@BTg3AA( FE!EABTqA1A1uD@[EBTQAW3=^E EBTAA-!F[9'EBTAdxA*CQzE]BTmzA@EN,E^BTT`A@ǻE E`BTA?? D+(EuBTXAA@EIEBU AA^]C2hEBTUAvB3Ff<EBSAeP—YF EBBSErA?sFcE 9BSAdB*F#E ;BSoEEBS.AzېF E!BSzASLFdE*BS{3AIBE4?BTA@9XE5BSiACF,#E9MBSAd3AFfPEABSvABk?}E.fEBBSRACVF%xmEBBSA6AF EBBSAS+A=yE$ EFBSAA5?EMBSA@(KD^yEOBSAFMF)U#EOBS|AA-Dc=EX(BTPA5@B#D<EiBTAh1BpDEmYBSAr@jEBS#AAEBSRANB5wDEgBSARB F.:EhBSArlEfEiBSA*.bEEBSArAYbE3Eh E> EOBSlAƏAxEmEPBSAS@F JyEBRhA [BenWC6EUBQ*AAFF EbBQAAVEjREBR(AqB4DTE3 BQA F rE3 BRAAp(FGaE3 BRAsFb_EWBRUAWA%8D,uErBQ3A{?AkBX ErBQ A{AYCh,EBR OAym@EF7ZEBRA A 0+EENiBTA A`UEyGEOBTA KABjDϴ|ER^BTVA IAE.tEpnBTaA AaE EpBUA ;?2EErBTA TDSErBT'A )A{EIBUNsA )FF$EJBU^A 1@hCFWEKBU;A [aF@mEQBTSA )AVVEERBTA @PEyESBTٳA @FhE\BTZA < @adEEBTA _AnE8EBT>A mA EEBTA YA9hEBTA g"fEpĉEBTDA yDdVEBTA EEtBT?A l'E LEJBUA #wEBUDA E1BUKRA zEEBUA >FZEBTA AUUDR.E BTENTBSA 0A\rE(EBT!-A f—hEqE$BT!A ׸k^DEBT0-A HB7EJKEBT.aA 1DݛEBTA O/@yEaBT7A *旍E=BT(A '4EEԟBSA rA`E~CEBTA a@ͲDFkBV@A(}A稙E֚F }BV>{A&kB#CZ)F BVAA%LAEsHF BVE5A$>B9DFBUA A6E FBUA ^K@+FBV A FfBVF!HBV;A#yA&~B!LFD$BV?A&A#2BEPX~FIBV;A)h,BDïFIBV<A)AB`DGPFL#BVBA$^BED0F\BVAA%pdB6EiF\BVAtA%BEF\BVBA%-A#~vE6F]BV'>A+B-yCXFaBV>yA&pZB8")EݙFjBVBsA$AGB5DFjBV< A#B5pD$FjBVDA$rBCFlBVF A(3BOkD?OF{BV=A&nB,FBVBA$J+B5C`SFBVA"A& ?5aEAFBVAPA%B>2FFBV0dA+/^ACC)FqBV}DSFBV A F C8KFBVA X,"LDFΰBUA d*A"AF,BV=A%B D,FBV@7A'AeEhFBUolAmBE~FBUkvA&BEFBU%A;B)EE@pFBUJAB~lDkFBU1A+BGՋF, FBU$ A*B2EFFBUABF/#FBTAABjE.F BU~AZAEJtF BUHAB| F-~FBTݏADAnEKF!UBUF_A&BFu[F+BU(AA0F .F+BU9AIAK-?E9%F+BUg~ABF0F+BUN@AaB .GFr)F,BU! A(?è^hFwsF,BU,A*^ANE:F-EBU|YABNIEF-IBUJAB,E˶F-[BU\nA BBFF4pBU}gABDESyF4qBU,Am BO,E"F4rBU=8ABxyDF4BU 8ALOBEF4BUFBBU>AJBEQFBU-A9AaEF@=BU@ASA ~CufFO+BUA CF\BUA qZF}BU9A IEFBU}A 5 .3EQFBUAC/JEaF8BUCA 6ǭE]JFiBUA E @D!͖FjBUA bKDQFBUAmBEg\CuFLBUbAA<9E\FBUA (~FE!!FBUvAKDFBUiA  E]6FBUA 8YD[FBU AFBU0A dZ EFBUA >KCEєFBUA 'MwEF(BU1A |h|EcF+BBUA IE F+CBUKACDF,*BUAA r9tGE{cF,+BU(AE@F,,BU'A AE#FMDF؁BUA uwCzCGFغBUA a[?NCD}FBUgA l@ZE/FBU3A $,FE(FBUAB6FFBUޑAGC FBUA xDuE"&FLBUAA?EKF ,sBVA-4BE`&F IBVsE?NF (BVFA(6BN9XF BV1A+AF BV=A&B?G@BY5pA GNDSGjBYA P?+GBY/A _$EsG^BY5dA ^G2BY3NA D""tGCBY"&A nb}C\GBY2A au.FpH6GPBY:zA .C3vF_G[BX`AB@ZD-/G"~BXAJF<G"BXALG5?G*BXJAZ@`]EAqG+BXA"-wDiG3BXAZoEo*G3 BX|Az@ADJG3%BXA3F >G3*BXuAˆEG}FuGLBXMA]AH(fF52GTUBYGA HzF)OGX8BXAkA+8E(G\SBX1[A]AY0XExG\WBXNEAv@RG\bBW)AοnD޳G]BXAoxMF DG]BX2ACrFGaBY4bA `kFCGkBXA*_D GkBXAJ͝kEnGkBXAR-EGkBXA@@_'E+kGlBXkAm:3F&*GlBX$Af@wEgGlBX AU@ꠙEGlBXCASSyF3ץGm5BY<A iBQ$EYGm9BY9BA BEGo?BXLAA[DtGoKBWAnA)EMGoMBXAbA'"E8Gz~BX'AAWC2tG{4BY A sؿ=qG|WBXAB QaFniG|ZBXgABzF&=GBY1GA bG,BXqtAyJB%GoF-ӦG-BXTA@EFasG.BXaAAEלGBXgA ‰uFb|GBY7A %/+EcGBY?oA %# -$E-^GBX A?FAT GBWeA@ zGBX$Af@wEgGYBX.AA=qGBXABSFMGBXAmF1GBXA/E HGTBXAXdA'^D@G(BXt&AA֝EdG2BY99A DdCGBXAb F_GWBXAB#;+DuGsBYA )D#=GvBYA ؂‘FO\GwBY zA 1Fe(GABXAUBx?GBBXA AXF8GCBXlABUBFDG݈BXA%EG7BX7AB EzG9BXhAA>.FG8BXwBAA^F(=G^BXAYAriEGzBX~A`'OCFG{BXAhESiG!UBUzAMTBR E|GWBUUbA\BFGWBU7AAkFz{GXSBUfAB? IFGxQBUyAD?B:OEiOGRBUTAA|GBV#A&A5D*#G?BWQ|A>|EG@BW7A~@ dEGwBWJAH@;D,zG BVAnK@ Ch%G ~BW_A?EݩG"BV"Ad@\)G*BW"A9>(E vG*BWFAJ@HoDG+BVA&A =G+BVEA"AnDuG+BVQA.?A ՠEG.BVA,@E٨G.BVA@nE>*G/BBVAY@EoZG/JBV̙AjyEC|G18BVAAgEG!G@BVA@XD G@BVuA@ECH+GPBVӛA2@EEGPBVuA @/EGfBVxA@#pEVCGfBVόA]vhvEYGfBV)AAEQGfBVA$NAcD+GfBVAAlGjBWAYDOGozBVA@MXE+Go{BV>Ah@HhDGxBUzA*&B)DRG|DBU{AB C`{G~BWtA"E$iGBVlAXA GE/MGBU{*ApSBKE 4GBV[A?AE+GBW AS@*D wGrBVA >AGuBVvAAm~EhVGvBVZA\AbID[G~BW'A@X EcGBVA,@E٨GoBW }ATX@aCZG}BVApAwC}>iGBW4A=݁ EGBWAB?EwdGBVAAu&D[GBW8A @EGBW A@ ESoGBW:>A?EGBVAA)D%GBVAфAMEGBWA*?yEϖGBVA{[@ELGBVAeP@ұEv#G؅BVA&A =GBVAUA~D %GBV%AAXNE_G=BLH`A.C%GBLH`A.C%H^>BALA/:C3PBQjA!dB 0EMPVBQAAEPBQABmC UwPBQmYAPXB6AbzPBQlABZDPBQGAR B_^P-BQAKBQEJPBQACA EZnQPBQA$AE8PBQ|A8aB,$D"XPBQA]6FPMBQxA9B^DUPNBQzA.BDPRBQZoAzACfP_BQhHAsBPBQP *BQA,A.EgZHP BQA2`B)LD\1PSBR;pAB6"EmPgBREAZB8PBQAB]lDePPBQA1BE`DPBQKAi*E5iPNBQvA3B9LEa<PlBQ{A|A2EO2NPBQi AcAjoBPBQkUAEAnPBQ?A-BJ.E @2P BQABN9D{IPBQzABSDP#BQzAJv@i9En;PBQA—BDWPBQKAB_GPBQsAFB5DPBQ+AB9EXDZPBQ{ABXPBQBA Be E/5PBQMA" C~EB4PBQl$A"v?C aEPgBQnA!)@BiD9P~BQzA BYF$5PBQAABD{C)PBQd`AfBDVE /PBQ|A,`CQEGPBQAsBT EzPBQqArCU%lEgPBQASA+CFEmPBQ'AfBRPBQAܚBXQC,PBQDA?BpPBQ A9BlȻDtPBQAzYBLE[2PBQ|A.B$IFDXPBQ]AZBB'E՟PBQAB1E`-PBQAWBJE7'PBQ}ABREPBQAG#B|.E'PBQyA ∐E;PBQArBOElPBRA?BEgFPBQACEF YPBQAB7PuBQhAB¤FSP~BQpA"`BR5PBQDA7BP BQA=MBE P"BQk4AB^uDf5P#;BQ~A UBhE[P#@BQ AߐBX]D!(P#ABQ~ABk+D%P$BQA0EP$BQAB)>FP%BQZ A,A"P%,BQoA@B[C>pP%4BQbAL5BD"P'BBQ6AB8E P'^BQA*BHAEP(BQAAVDP(BQALxXEn~,P+zBQ|LA~BCkLCP+BQ~AB[P[BQzAB CrP[BQABnDuKP[BQABoںDB2~P[BQA+BmD.;P[BQ~A(BO#1CpP[BQ{OA BZDoP[BQ~AAe+fEP[BQABEP[BQ"AOB1DRP[BQAtBD!P[BQuABH:DqP[BQtA BDVP[BQuABtD#P[BQhpAuB?YElOP[BQAIB#6D3&P[BQ{A*BWE'P[BQ-AXBFCP\BQAB`0DG^P\BQߏAB]cE!}P\BQAglB)E<P\BQABE~EVEP\BQzABQCP\BQA%h0E8P\BQ$AB)C7 P\BQAYBEP\BQkMATB.pCP]*BQxA!#B5.CgSP]KBQAZC0EwPcBQGAIB5DqPeBQABEPeBQA:BFD(PeBQ~xA@)DOPeBQ AuMBLDQPeBQA~BFUDPeBQ3ABX,9DPfBQwAAҺ ETYPjZBQACSEtPj[BQABC)PjBQAB3DPpBQABDCtPpBQXAB3bDABQnAܻBBC\xPtFBQoA'BKPD8Pv4BPRA!#xCBPwBQmJA?Bp`EPxBQ ArBahC:PxBQOfAZBEPxBQAoBA1EPxBRAB5`EPzBQQAaB2E(P{~BQpOA"CݏE8P{BQaBA#xCVFP{BQqA!C?FGBP~8BQBABPBQGAw+BDP BQ[A0E8PBQ1A'SBpA PBQMANfCME&PABQAB(`dDaVPiBQ{A0B DsJPBQt6A!Br,C~PBQAӃB*DAAPBQABg] DGrPBQAăBPeDPBQA/BAD|iPBQ{=A@CFTn PBQ|LA~BCkLCPEBQvKAXB]ExPUBQ]A:B(E7PBQEAB E@PBQA\BMNELPBR7^AB: PPBR#.A6B3EPBQABxE3rPBQ!ABM}DSPBQ!ABM}DSPBQAOB bDIPBQAGBREOn,PBQA#i'EPBQA}B]PBQpABfwPBQ1SA*ByPBQ{gA ~CGhF~PBQ_BAMCbF-jPBQAkcdEdPBQA@B(`PBQHA߯B3C>PBQAOBTD&PBQAeBEK۠PBQdARBa'PBQsABENPBQ|A߫"BEgPBQJAB픁ET&P BQA7BԎEq8P!BQA6BEZ8PBQgAB_DjmPBQAFByD,EPBQ%AlBE"PBRBAB3ElQPBR[&A"B0@CD' PBQ;Ah[BwEIR;P BQA@En7PBQ\A#OBbPGBQcABQ_ABd%PDBQAzYBLE[2PEBQTwAyA()EPFBQAB=FbF6LPBQ;AB1D7PBQAUBV!EXPBQ]AB7EɘP¡BQAPB*jDPKBQ04ABDzC5<PRBQ}A7BzD@PBR*AB1E`PBQA"BQDPBQcAҘA`BPBQhHAsBPҐBQrAʣC;NEiWEPҒBQwA@BE5*PҔBQrASBVeDQAPBQATB2CT!PBQ~AIBAYDPՀBQh`ABanEQP[BQw'A;GBNPBQA4B+lE)IPBQAC@EoPBQAB1uEPBQvA! AC BEYDoPBQ"A pBGEznJPBQ=Aa.C SE PBQr@AOBcE<PBBQvAB>EG5PٍBQ"AxB?D/ PBQA B2PE7-PBQABCC[-PBQfAKBBPBQ!AA'DO<PBQbNABE^5PBQGAR B_^PBQnA^BCCPBQBAEKB: DһPBQAaIE^BPBQA4B5eDիPWBQTAnB9]EPYBQ+AB9EXDZPjBQ{AB}' D{'PlBQ{7AB\HPBQAPB:DzDԌPBQABB#EvP0BQA@C.FH2PmBQYA8CFGPnBQ|A 3CFrP=BQHA~BmYGE|5PEBQ(AŠ\F>.-PBQ@A*BH-C'+P2BQ;ZABDP4BQmAJB{D0PBQ=A[BFEܢPBQkAuBrcDIPBBQPABAwuDPFBQYAsBq\P!BQ|A9B=7LPMBQABB=nD"POBQA ADPXBQAiAE8 4PqBQABlDM!PuBQABqDNPxBQ ABljD,PzBQwABWDP{BQA_BHpCXtPBR]A%&B&/E8PPBR"AB4FQB#)Dk"PSBR<A0B5BETJP`BR*AB2EɍPgBRPBQAdVBEGPBQ{]AYBFbr PBRA3AEߛEPBQYABEXP9BQKA7B F RP BQ'AB?P%7BRA@иF'P*BQ}A CV\hFObuP*BQWAQB2OEP3 BQAAE79P3 BR AB%P50BRڏAB EDdPABRPAR{B.EPABRyAюBeF-PMnBQfAPB"kODsPVBR>AHC_E=PVBRUAr@ˍEzPWBRYA³Az6EBPWFBQ_A]B➚EÕPW{BRwP!BR=AB9lD2*PBRVA{A`'-E cPBRxsAA7HFE|PBQ9A5AQE dPBQYA sA"FPBQKAfB3\CEPBQABEPBQAp.Bi^EؖPBQ]A>B*ERPBR=AAZED0PBRֳAͷA{EFyPBRA,OB C ePBQA${ZE&QPBRqAXB@E2[PBRA,B!DjPBR6AR0AFM=PBQKA-'AuVFwPiBRpABV"EZ+PjBR AB7E}7PlBRA.A3E, >PFBQA3BDE(PqBQfABC{Er qP¡BQ+AjB-EK9%PBQjABFoEwdPQBRAˡ2EQYPBREAcAdE[]P}BR AAF_POBRARAlvvEuYPBRWAARBBvPBQvtA, BR1F'PBQuA WBS@0PBBQAaA EPOBRaP BQ'A%DBrE P %BQTA($C${EP @BQiA'(B#E?P 0BP׉A**BŁP BQeA#BSnE˰/P BQ rA&plé2F_GP (BP؃A'݌C@xE8"P )BPA'VB7E5,P BQA(vB _DP BQA(B>EP BQA(`B)E!P 5BQ0A$ƠBfE TP BP A CP hBQ[JA#6BVFP ~BQcwA$7C`PF9P BQD`A%F'„rFjvP BQ6A#:9EVP BPA'B0XD 1P uBPwA(B0(EiٜP BQ[A%v4Bf]{D)P BP:A'iF P #BQAA'CEP # BQ&A'B BHFǻP # BQSxA&>F$19P $BQA% A-EƩP &%BQdZA#IaBaEnP (BQ4A!BPP )BPكA'BP )BQA(afB8Ee<P )BPA(fLQETP ) BP^A(BLE!P )BPA(-2CEZvP )BP^A(E$BJEgP )BP!A' E2{P )BQ8A'VBDF6OP )BQA(1v/EJtP )BQBA' C^EP )BQA(ZBDP )BQA(eOCXDP *BQwA%7BEcP *BQ8A# $BE>P /BQ]A$C3EF?P 0BQdnA#QBrFAP 8BQqA&6C1}EoP 8BPqA&uACF/P 9BPA($B7D2P ;BQ(&A(B&>E$?P =7BQ A(BfEAWP @BQBA$X trF#1P @BQKhA# B>E-P A BQ4A%A0FP JBPA'ABP JBPA'5B8DP JBP˺A'!ExP K BPA*NBE.P K BPݛA(3B=EU4P KBQA'BEEP KBPA(tBDSP KBQ zA'KE~P LBQUA%,BۻCAP LBP~A(}~BܢELP LFBPA'gWB DP LTBQ:GA'B8EP LYBQQ~A&ZFP L[BPѴA*BP MIBPA'F$]P O3BQZA(:eBΐEdP O5BQA(qBӳDP O7BPA*B<(C %P O9BQ A'wEQP TBQFA'MB^P TBQP;A&guFUP UBQDA"mCNFdP UBQ A&4BEEZrP VBQ7YA!bzBP VBQMA#5C E[P VBQSvA"ACvF#5P Z|BQA(=BDuP Z~BQ9A(~eB;D?P ZBQA(OBD 8P ZBQ A(xnBDmKP ZBQA(BsE6P ZBQ(A(K|B7E$vP ZBQUA&BOTEYwP ZBQXA&"EBw EձP ZBQX~A&0BqaEoP ZBQRA&l‰wuF.gP ZBQXA%|Cv'EAiP ZBQAA( KB`E)@ZP ZBQLA'.B7E`TP \BP2A&B,FP ]*BQ:A ߑBZP ]aBQ7A#H³$KFsP aBQ9lA(,BEP aBQA(JMB{DP aBQ'A(BXzE0[P cBQDA ^BP iBPA)AFvP j BQ=A'hB/EP jBQ/A("@`EtaP jBQ0xA(B E'P jBQB&A(KNBnbP j%BQ;A(-Bu?P nBQKA'ЅC2EɚP nBP A&ȡAE P oBP A(yBD EWP oBPA'BmEMP qBQDFA"CWEP zBQA(SB@EP {~BQiA#BDN#:P {BQ]A$0CxFtP {BQiDA"pBoED*P {BPA C &E9P {BQ#A%B}%EuP {BQY?A$]B#HE`]P {BQNA$£>EP {BQCGA$e F6P {BQC4A$n‹RBE BP BP|A"!CGF㆘P BQZA#ޑC#Ev,P BQ3A#Ԫ2MF$P BQ: A#YBڻ:DOP BQM A#CEᴔP BQ@9A#CBE1ZP BQIgA#l7B2DRP BQ=IA'lBDEQP BQrA"9`Br.P BQbA"8U-4F<_P BQOA$ bFnP BQA%3BCF.P `BQ2A( B|E7P BPA(SWzE{P BQA'BfȘE`P BPaA(B@F}P BPGA&1WHFNP 3BQDA'BEP BPA(oB0EGP BP:A(VAeE%P BQcA#RB~EEP BQGA(B7D P BPNA(=A4UEpP BQlA'B<P 6BPdA+|B5?P XBQPA&{7FVQP KBQA% B{{&F1P BQeEA"#BEŚP BQa#A#FC[[FG P BQQA"(CE7P 2BQ:A'~B*E?P 3BQ!A( WC"?SE@P 4BQ!A'BE*QP GBQ#uA(BzAEP HBQA'uBPA(OB)EUP DBPA(V`BE=P EBPڿA( wCFEP FBPA'{Bx`EP BPA(%?dEP ЌBQ7A!sRBE)6P ЍBQ;A!C EnP BQBQ3A$[BEWE+P BQA(;B¢EDvbP BQA(whB^uD P BQA(&BdD((P BQ "A(j,BPC~P BQ A(QP@CEP BQ A(4B,$DZP BPA(wBEPBP/A, &C EEJP }BPA+aCD;PJBPA+BSF%PLIBPA,GCKE\PL[BPlA+"C 7EA/B'IF)PQ *BSr/AB,DKQ +BSVAzBC,EF1MQ NBSZoApB9["CQ O BSfAyB6@F%Q OBSqA/BqhEQ W9BS'A%AF(y;Q qBRэA@E"ZQ BSNABJE߅Q BS A|B"Fx!Q BSWA@BB.cFE;Q BSA B-F"+Q _BSA8!B%%FQ BSjABC E{Q BRA)A-B%jQ `BRATA.FeQ BRAA\Dt@Q _BS1pA3AoEQ `BSNZAfBN-,EQ BRA`Ah7D|RBPA !BhERBPɲA PB~E"R BPĹA OUBiHEaRBPA BэEgnRBPA =BB \D09 RBPA UBaXD6RBPA L@B9EDXRBPĤA ?!BoEBR(BPA fB~CZEqzR(BPɊA LBBtDr],R)_BPˠA tBJEpR)cBPA FBU,UE&CYR*BPɆA [BEwR*BPρA BDܵR,BP0A WBB}REBPA /B,CNmRILBPPAxB =RIaBPVAyB'AcujROBPŀA IBk("E*SR`)BPόA oB5E=RqBPA kTBz RyBPXA jBQARyBPA nBuDAR{BPΈA )BR~5BPΈA )BRBPA BERBPzA )BEHy3RBPA A܆ElRBPA ):|wFMRBPA (>BE KRBP΂A BЇEfRBPA &BcxDt3RBPГA ,B1D$~RBPA @BDyRBPYA 5!B^5RUBPAځB ELRBPƫA OBfDTRBPϽA BhFn}xRBPA xBlCK_RXBPYA B!E_R\BPA B嘷E+VRBPA @BA_EhRBP#A"|C cPER RBO/AC#SDORBO ABCR BP*dAnB: E*R =BPlA)BKDR ?BOA BCR LBPTAQB-C0R BP%fA=BιCTeR BPA}BE@-R BP)[APBER \BPjAY+C+RSBOAmB1D7RBP%AեBPKC6RBPAB,BWRBP,PA LBE1IRBP0A nB)ERBOAB#9E_iARBO"A&BnRhBO٧A pUC'E,RjBOA?C ;F,lRtBP A JB߼E[fR BOACEȻR!BP&AһB}qR%BPAB8Dc.R%BP5A.BuDR%BPAB)DMKR%BP.AvBDR%BPACNDɿR%BPSAqC DR%BO^A C~E4R%BP+DABWE%RR%BPBArB E"R%BO6AIBްEYR&|BP(AAtELR'BP AKCB DlRBP'A C6ERBP)AEBLD,RBPvA ʹC \CcBRBP"AHEB?DѕDRBPA8CBE ͗RBOA `C0)EݰRBOiA+BDIRTBPAB֝ERUBP[AyBYDxRVBPA3:C80E=ĩRqBP)A B=E4^RrBP$A7CeEXcRsBP'RBOA2BQ]D0RBP+>A B@RBOܩA !C5NRBOAB(D*^RBOӨA!B.EbsRBO"AeBLE RBOA>CT{rFYe<RBOABkCkR BOiA#CBi\D0ReBOfA9B]hD;%RjBOAB$:E'RjBOAB wEκRtVBO|1ABFԴRt{BOeUABiEWRt|BOfA BTEN RvBOfAAB3R~OBOZAABoEqR\BOurA@B}DftRiBOsA BDVRBOAz[BfEWXRBOA[BD`ER]BOABE; R^BO3AfBME^qRaBOAE_C8E" RbBOABRdBO~AfB-EfReBOAB_E%RfBO5AXB E,RBOAՃBEkRBOAB EBWERBOA2B-pERBOdA\BwCӕRBO0AFB.EBߕRBOtAuC2ERBOk>A45BRF'E*RBOq+A9EZaRBOhA&BEڭ\RBO]A.C.%EPRBO_R^BOA B5CYRmBOA BpC5RBObA ȇBs DGRBOA Bm@Q*R FBPx8A 6B=D#R LBP]A 0B5R VBPuA rBcDRBP]A 9vBNxEwRBPD2A BϷuDR%BPvA |Bw`DޕYR*BPQA BÜLDREBP`A _C2CEgREBPcA BMEHRI?BPDA XBDRRNBPA sBERNBPA 9BEd3ROBPA C(2;E,1ROBP'A \BjEL@yRTBPY{A BċDR]BPyA BCORlSBPlA sCE}RlUBPcA 2CtFIRluBPoA BV$EfRlxBPA -C;kRlyBP A BfE-‡RlBPm7A B*E RlBPUrA BRpBP'A 8BF E R=BPA @C E>ROBPA 0,C EARQBPsA =C FRBPm A xC"D-RrBP-AnB@RBPTA BWCȠRBP`A MBfERBPA *BRBPtA CfEDRBPRwA &B߿DmR̺BPKZA FBpC2R̻BPRA )BLDRBPuA B7D/R BP`A MFB*E bR:BPfA BR;BPuA BDR?BPcA aPR GBOAfBt E R BOA,ByC"R BO܎ABۣCSR BO`ATBO]ElR BOAPBCR BO{DA:~BDR )]BOc(ABEs]R *BOABOD R vBOO>AT6B='D (HR zBOOAdZBëR \BO|vABFgEroR iBOxA.BnCR dBOjABȢNR BP*A,BFDmzR JBOadABD8o`R sBP'2A CLR TBOzAuBE3%R bBO[ABR BOƤABnEAmR ɡBP,ABہDLR BOplAwBiDRBOA aBAEbRBOA BdC(RwBOA BҹE%RBOA Bܸ0DRBOA kBD6 TSJBBNnA#[C2DSsBNnA#[C2DSBOE}SrBMRAIC^BxF}SwBNAC(:ESyBMsA6C-E:S}BM}ACuF>SBMUArVCVESBM@AECBF SBNA7VC)ESBN1XA1B*ESBNAQC dESpBNSAICSS!2BNAC DkS%BNyAC'>FS%BNO>ACY`CS%BN"ACP7D4ǪS)BNA4 Be=Ey5S*BN AC fE>S*BNA&C5RE!S2UBN;AA\C3D[S4BNaAkCF qUS5^BNZA[CCFS6 BMAp[CE _%S8QBOtA LCA*&F8S9TBO>A!iC'EHSA C+ESBNAC4-EŋSBNaAkCF qUSBMMAC5EYSBMACC EL'SBNA RC+4D~(SBM6A"CFeSBNQAC OFZiSBNpA C&TSBNAC*h{E{ISBM͆AC+DxOwSBM:6AC^aE咡SBMA7SC~D;SBL:A(Cr-S BMWAfCC,;S BMAz9C8h!E=MS%BM7AC#ӶS%BM AlC"}qS'lBM1AC0.S+BMACrFMS9BM.A׵C.j7E\LS<BMAZC$_IDd$SG9BMACSLBM=AC`E}SLBM SA\C]%EzM:S^BMT@ARCMD(SdBMxQA"&CdSfBM(AXC?C7ShBM&A7C$E8SiCBLA C`REcSrBMAiC=Dc'SyBL+AC0\DmbSoBMAdoC ɘCĠSBMA~C&ErSBMwA)C-nERSBM5|A C"DiSeBLNA*CdSBMAxCD@SBM=ACXEXSBM*AܭC/DySBMzABAC/}S BMHA#CGS BMBAC(C̮SBLA7CSBMGA%&CVjSEFVSBMIdA0CRvEvnSBM@\AC%uDSBMG,A#CFEN{SBM$6AsC+E Z S!BMAG-C{PS#BM ADC=yS&BMQcAA_C{DS+BM AHCQ;ESBMQAFCˬDNSBMPA>Cpo*E1SBMDoS9BMqA'FCAF>S,BLACQSBLAzC"EKSBLc5AgC)wFTx8SBKA)'CLFxSBLA5CENSBMfA+C.fFESBMACVF:SBLuACQDSBLA0C7N8FųSBLqAC9DzBSJBK6AQ.CeCSBLWAACcSBLXAChAcS BMjeAƭC3E/+LS #BMACOD ͠S!BMo9AECoE.uS%BMACE^S%BLCACx[#S&BMACg%EG>S'qBKAJCFS'uBL;AChkC S+BL6AFCTS,BLACE\S1IBL~~ACsS6 BMDACF1RS9DBL9AC^F~S:BK8ACYEύS;SBKNAZCuE&US<BL$ACVBSCFUPBK#A9cC6Up BK{A7A,CDUpBKA6{C\E UBKkA52CtU@BK}A7C}U#BKA9BC^a~EUBK}A9)CNEcUBLFA?EwC=UBK A:;C"pE<UBK~A9CCE9\UBK}A9C,'UzBKA;T:eF?U/U{BKVA9CExU|BKuA;CqLUBK]A5ӾCXC<UԫBKA7CDZUsBK~A:KC\EBU BKA=)CUBK~A:hC(FqUBKRA9C EUBKA9]C,ED[UBKpA5CUBKA7#CRENUBK A6CWUvBLbA,(àFUBL[A,pmFMU#BLuA*CI.cCU3BLA,k bEءU=BLA*C/DG@aUTBL A*C)DUdFBLzA+0C_ΨEJ1UzBLbyA-GCm'C9/UuBLSA-RCF3CUBLA-UCGUC\UBKjA9{C~ G=SU4BKA6LC[FPUKBKA4{CQE~UOBKA3aCIU#BKɡA4ojC DrU.'BK˸A3~CpEg7U.(BKǺA4\CNF 1xU.,BKCA5 vC]EškU.BKEA4CQE7U3BK\A6CRdF fU48BLA>CժD8U4BK A4CD ,JU9BK:A4ΕCU:BKñA5KoCU> BLA>CaB{nUABL"1A>XCTKE߾UCfBK#A6DCDUFVBKJA4ZCϦEӌUHBKɍA4(ClEUL BKdA4cC|E^UOEBLA>C D|#UTBK&A3`CԢElUUBKA48CDUX@BK5A2f*BFQ@U^BKA3CUeBKA3RCEAOUnnBK5A68C{EwPUnoBKgA7 CyEƉUp BKA6\C EbnUpBKA6C|fErUBK A5rC wEk~U@BKA7%C;eEUBBK/A7CQEU;BK>A4|CD^UBKĒA5*CEsUBKġA5_CfEn`UBKA6=gCzD0UBKA6|CB"U BKA0CTEU BKcA0C#OEDrrU BK;A0CHEU BK4A0'C|E8U BK̟A1CEU %BKΘA1Cw?E 5U :BL8A/C=AFU BKA0CJ9DvU BK7A0CT]DU BKA/!C;D/D3U BKwA0V CH߄EZU (BKA0MCNE _U BKA/CړFU BKHA0C yU (BK{A/Z5C9F~U 5BKA2,qC&Eu(U BKA0yCSjD9U BfBLA1TzCHEhrU FBKA08@CODRU FBLA/?CP7@IU FBKA0 C[T|D9U FBKA0>CNEE|U GBLJOA.7C%xEPyU L BKA2DwFGU LBKA-#CU LBKܹA2VyD*FdBU LBLA/MCG$@QU TBK3A1%CEoE*U TBKA0,CG CCU TBLA1UCDFDU U TBLA0_CPΜE&U UBKA.C8EU UBK[A- B-EsU UBL A.vC;F8~U X@BKA1C!F$DU cBKAA0C5U eBKA0CP3DOxU eBKsA/vԑFU eBKA2{ CE 9U jBKKA1C FO{.U jBKA0RCBIDsIU jBKA0|8CNDʌJU nZBLA1U\C$6EX8U naBKA1RBE_ES#U nbBL"KA0 2CK4F9aU |gBKA. CEkU ~BKA1|B!|F(s_U ~BKA1CyEPU BK&A/CC WU GBKeA.CDU NBKoA.cC@ENU BKA/8CG6DuU BK|A0ClF6jVU yBKA-#CU BKA/CƑF<U BKfA04CENU BKA-`CU ?BKfA0'CCU jBKA2 CηElU kBKA1B޶FU BK'A1HPCTEU BKtA1>C9;=E: jU BKA1JC EWU jBKA0o{CeDU BLA-CTABU ;BKA0wCH4~DU QBKA0HAC6tDrU SBKGA0kC@*BU "BK*/A>RCQCC$U BKmA1g%CiD`U BLoA0C=KDU BL_:A->C[C5U uBK5A0CFU BKUA1CF=F U BKXA1c Cp{U BK|A0-CK~BiUU BKA1CWU BKFA0CCEU #BLA0C6_Et]U ,BLUA1eC5>E!VSU BK>A1CKcE :U BL]A-8C`(D&U BKwA1C3EU BKA/lCŎFU BKA0|CKOE^U BKA0HSC1WD~U BKXA0(CzyE,sU BKA2 CښERU BKA2xTDFTfU BKA0CjKDU BL9A1C6EU BK A0oCBC|tU BKA0~CTlD|U BKA0COUD]nU LBKeA/D(0BFU ^BKbA/,CocF>UU aBKA/gCȖJE@KU gBK%A0*CPKD/U jBKA0%CRD]BU lBKA0C? 'Et8U BLO6A.[ÇFG U %BLrA/C?*DU BL}9A-C;F"U :BLA-zNCFmDU BK`A0:C<3AU &BLA/-EKU BKdA0Cm2-U -BLA/bCFEU -BLA0BCFDU -BL+A/ C^FbýU -BL A1C9(?U 3BLeA-CFU BL]A.eBS$ F)U ABKA0C8j1F hU ABKA0:CIE5MU ABL[A/TC9zEwU BfBKA1>Cr+DcU FBKA0%ClCE?SU FBKA/~C8RHEU FBKhA0CE@.U GBL/A/BLsA-oCCRD٢UGBL^A.SCFUGBLBWA.@BE۵xULBKA,|CF7ULBKA+C(#-FtULBKA+#C E-ULBL7@A.C zD!JULBLA-uC6GDUTBL{A*C!"CF0CUV,BK}A*CwE7UVBKA*3CEsUVBK1A*pCrEcaUVBLA+ClE`HUVBKA(CE5UeBKpA/2CiF..UlMBKA,|CF7UBKA)=CsFLUuBL{1A-CnF70UyBKA,~CPUaBLA+CNB4UhBL|A*0CSAE+UjBL{A*C]Ea1UBK_A,C"UBKA+AuC4\E{UBLtA-R=CuEaUBLODA-8C>D UnBL?A-DLF,ZUsBL^WA,C=FY UBL{A-wX6FUBKA)/Ců}EyUBKA( CiEaUxBLA!CqŨE,UyBKAVCEbUBK|A%sCve?E"QUBKaA%8C#TUBKA&OCE@UBKA$GCE8UBL NA#sC;#U!BL IA#QCjUBLA-mCX}UlBK;A%CJEU{UpBKA$C-D͈UBLA!Cs_gEmUBKA!=Cs?hDmtUBKrA%ԶC]ODfUBKA lCEBU6/BKAC@eE0WUBKA+;[CfDz`8UIBJA,wCT+FO U BJhA+ D DUBJhA+ D DUBIA,BCݍADUBJ'A,CFTWU'BJjA*՞DѫEÁU'BJA,DCLU.BJgA*ըDEU0{BIA-CCf%U1BIHA-eCuEWU1BIA,$CbE)U=BJۧA,DDUBXBIA.CCECUG)BJuA*CEUG+BJgA*ըDEUG2BJu6A*zC$DULpBIAA. C-ULsBIA-C5E"ѕUSBJZA* D}DUSBJ]A*NCGJDUXBJ\>A*DDE̴UdTBJOA+D/CiUdWBJA+ D(C2UUfBJY4A+DD%UfBJ_A*xJC"DUfBJ)(A+C.pCʘUfBJA*DASE:OUfBJZ#A*ODDUm+BJnmA*`ECCnUyoBIA,CEgHUyqBJ#A+CTE!Uz&BJ(A,9CbEUz'BJA,C"YEUz(BJ(A, C{sF LUBJWA+(DBUBJR+A+fD aDp#UBJ1A+kCdhFUBJ1A,D`FFGUBJ A,qC|?F9hUZBJoA*h%CXD(UBJ0IA+ߊCF$UfBJA+D=E*UgBJ|A+ D DChJUhBJA+DEJUU{BJA+/D.%U~BJA+D DUBJXA+D ADhoUBJiA*CEiUBJA,\sCFLzU6BJA/CF CvUBJڼA.DUBJA,D@EUUBJ:A,DZMD[UBJA+*D#C>]UBJA+DCC{_UWBJ`A*^$CDyUBJA/!C- EUXBJA/*YC!F,4(UBK A/ثC絊FkU'BJޚA- DU-qBJ^A/щCݾEU.BJA.Co]FRU.BJ.A,D #C U00BJ A.CF[ֈUA[BKXA1TCVUBBJA-mD EͬUBBJYA-QDGE@UBBJ:A-ٖD-ZEUBBJVA.&DeICyUBBJ`A.CTDBUcBK+ A0CsE2UcBK7A/CrEkUzBJnA/nCWtF bFUzBJ&A/DC+D]NUzBJA.mCE6ΑUhBJ:A,DZMD[UYBJA.CdEٸUZBJބA.LDVEU6BJA/jC㿘E:UaBKA/WCPU;BIpA.HCF<UBIz=A.C E6UBIߥA-iCɽEo:U+5BI&A.@CDP4U0{BIA-ϮCE:(U;rBIA-C0EUBXBIʖA-C8ElLULpBI2A-[C6EULsBIA.`CEUBHRA0C*UB>3AjCV BQ3@B;FCV gBRACALF}V BPABȼDu8V BR3AB~F3V BPcABf,E7V BQA%BXFŌV BRrA AEcV zBR^A`Be^EUV BQ@"CrBF ^V BQdAvB~EV aBQABBH1@V BQ`B8zV (BQ5@BV )BQG@vB5E0V xBQ0A/~BIDV zBQ@B!gE~V ~BQ@h.9zEmV !BQ{AKB\}HE0V BQ+>ACu.EV BQ A+@ ([EkV BQ1AC\lEz\V BQ5AhBczHE V !BRxPA7ADl kV 'vBP @&BڋD,V (uBQbA+sB=F{BV )eBQg@XBxEsV 2BRN=AA܋F<V 4BQ5AhBczHE V 8ZBPՓArBEV BBQ@B}veEV B$BQQ@ƜBArV EBP)@EBDLV EBRABCm\V HBQ A%5B1V HBQ+AVBOEK|YV HBQABCDV HBQTAMBQB.V HBQ1ACQZEtNV HBQ!kAB>EJ <V HBQ:A`C"}D:V HBQAVBGߤDV HBQTAMBQB.V HBQ0rACKeErCV HBQ9AWiBDgV HBQIABԉD0V HBQ5ABDIV HBQA+BDߑcV HBQ$IAB3V HBQ7GA}E1V HBQ?A^B)^E} V HBQZA!B9)Dq[V HBR3A2A`EV HBR ASABk{EV ]RBQUAVB]CELV ]YBPpABYkFyZV ]ZBQ>ABk{EV ]bBQDAuBN)E/V ]{BQA!BӿD]ɛV ]}BQA5BDVV ]BQAfwBD&VV ]BP*APB_9DIV ]BPAhBRCEV ]BP\ABEFUV e}BQ=@vviF>V f5BRPAAiKETrV fBR \Ad0BhE,V iBQbA=A9XV pgBRAE+AEV pxBQ@sCwEV pyBQ@ BwE|V tBPݏAGB(bEYV zBRXAmAFDQV zBR AB#EV CBQKA%Bm-FB8zV BQz@EB*E V BQ@LBE`V BQf@ZsB^^E@V BQA1BeF (V BQ!AYB.kD1V BQA B*EwJV &BQmA2B[lDe1V BQrhABתWCUV BQ!AYB.kD1V BQA@BA HV BQABDdV iBRAĂAV ΐBQALA>FTƆV ΩBQTAmlBVEfV ьBQsAMBإV эBQ\A{l;ET`V ׉BQAB[CBV GBRODAB&E`jkV HBRqAc]EV BPAYB1`DFKV BPٯA1KBjV BRODAB'7 E`aV dBQA/*BC2?V BQQAqHÎEc,V BRz-ABCAV BQFH@ZBV7/DzV 1BQb@]C(F5V BQ,@A1EV 7BQ{A@'FFhV xcBQ>Y@SBdDV BQR@A0 ECV BQ@d@{ F$ 2V MBQ@s E$V BQD@B=HE(zEV BQM0@Bn[FEV BQIO@EABGEֿ]V BQLj@AȪ#E\PV BQ@ AKwFVBBQgA^VBQ\CAj.FfþV BPA cB2[CV"BQIAB"EV&BPAwEP'VmBPpA -BMEpVmBPҁA B_5CPVn4BPA mB_yVpBPA SIP}FqVqBPA kBvBTdVqBQQABmVqBQ7HAKBVqBQa?AٰCߜFVqBPiApCKEVrBPiAGC,lsE~VrBPABF#VrBPA BzMD.TVrBQAJAEf0VrBQApB$FVrBQOA1B5EHVyBPZA BHE~VyBP̂A ,B`hE} VyBPLAOC%=DXVzBPfA ]B;FV?BQAU0B#HEʥVABQ6A_BXE-VBBQ_=ABC&EVCBQSABlID6RVEBQJAdBm.DVFBQ7QABE(TLVOBPA cLCٷE!(V3BQA7B!abD#V`BPA VBz|EVaBP0A7B=F NVbBPA 5CFSFVBPA DF VBPA BEVBPҝA B[2DVBPQAC/V"BPA%B8@EVBQXAB Cf}VBPA mB]ffVBPA BMr@D`VBPA CDVBPuA C+#DVBPԺA U@W7E(HVBPA ݢBRtEq]VBPA yBUDVBPA 0C6DVBQ%A9"BEQVZBPߗAC8E VBP A C5}C'V1BP A .CWWE6VBPSAXC2wE/VBPޞA%CXF 2VBPѻA fpBl5F*VBP˛A tB=kEvViBQjABnEUVBQ(AiBEVBQAEڥVBQSABlID6RV%BPA pCQF VNBPAvF&VBRIABVBPڮA:C cFPұV-BPA zC3DކX BQbDAŋBQ DQX lBQKA`A| DbX uBQI%ABoE.7X BQKARBTyDtX BQfAAD}X BQdA<B EqX BQiQAXB5A E TX BQ=;A@}E'X (BQ=mAؘBQDZX BQ,kAK4BTX BQ8AB{X BQcAtA{DϰX eBQJ^AkB=x|E_X fBQAnAB|X mBQ5+AdCH ERX yBQ2AWB DhahX BQAYA!BnD;0X BQFA\B5?D-X BQApAcBhE!^sX BQ8xAeBpipDȑX [BQ5ABx:Eh0X BQ]AAKE-zX BQENAf1B-DsX iBQ.RA MeEX jBQ5fAdC5?ETX "BQdwAJqB6N DDX "BQ_AQmBq\'EX "BQ]AB EXX %BQOABE]X %BQEAzBQE>/X %BQB\AR8E|X %,BQkAsB?EeX %-BQiAVBEpX %/BQdA7BrDyX %4BQ]AgB EBX )BQJABnCX )BQL4A}4BaCsX *{BQ>ABL)X -BQjoAO.B?@ERSX 4LBQghAÄ́^EEX 4MBQP4A,B 6EX 4NBQNiAUvaF@X 5QBQj&A1BMrEs֍X 5TBQh#AB D~@X 9BQh9A CTEX 9BQf/AB3E:X 9BQcABQbAEX BBQgA&4AzD SX EBQcABwLX ҐBQp"AFBCsX ҒBQiA,B0DnX ٌBQBAB\YDa1X BQ_Axw`EEX BQ]PArBD޼X BQ]KABEDX BQ:AB|ERX 4BQlAB`ŢX BQ6A Bl'X BQeABrD|X BQTALBtCX BQYA`nC/#Eu XBPAjB],EGX *BPAJ=BIX ABP&ABtDr1X BPADB3X BQ2A$BUX OBPAJ=BIX BO0A"vCLEXBPA~B9DhXBPAȻB8RXtBOA"{9C>C1XBPA y]CPF%XBPAǽBWDsXBPA"C"zD=XBPz}A!VPBfqEîXBP8A BtEjX BPZ A"XoFCoXuBQAA B:JD\X >BPMA"CdEX$EBP`A"E?ERNX$GBPTA"LF X'BP݀A!@5BEFX(BQXA!tCaFCKX)BPaA"AByxDr&X6BP-A" 0C8"FȞX9BPA fCF!LdX9BPA~B9DhXB BQCpA B!CП3XBBPABjXFBP0A$ CE>XIBPAbBNVXKBPPA pC=EغXL-BPgA'@B`BXLEBOwA"XC*XVBQ7,A!kBVIEa9oXVBPʑAoB Fh XVBPA [BDkzXVBPA ECEsXVBQ6A!B]EbslXX|BP=A"C#F X\BPkA$!(B|Eq>X]aBQV1A"fBMVX^"BPz(A!}tCE- X^|BPABOEVzX^~BPՅABEi\Xo_BPc\A""q F)+XpBPA$C`C >E|XrBPYA"sBYEՅXrBPRGA"BxkESXv4BPA C3EXyBPl+A!{B,[C%X{BPA B>E0XkBPvAJBSE XyBPA B͔nFWEXBP5ABÆEJ?|XBPABcFXBPAcB:DXBPAB=sF oXBP.ABc/XkBPA kKBUFHX?BPA x]CO*E[XBPy0A#nBwF)QXBPA#BE8XHBPzA#/BCXՀBQReAB Fa(XՁBPA#BKEҪ XBPjqA"%B.CsXBPNA#}CEX}BPs3A!BE XBPl7A"k/F\RXBP#A"&hÝ&FLXBPLABn|CT X)yBPvAqB&EQfXg+BP8AlBDLXyBPABEGbXNBPA#BDNoXBNʀA+PBPA"C\C XFX(BP A(UBEmpX(BO'A".zCl7FΗX(BO,A"&C/>F83~X)BOA*B`lEÝX)BNثA*D@X+RBPA(?BF X5#BOA*zpC7$X8IBO^A!+CN'E{X8QBOh|A!ICEE_X=BOA)DSCw(FX?BNʓA*CD&XEBOAA)CƴE"8XEBOA*8BH!EfXGaBP>A)ÙaGXHfBOA")C$F*XJBP7CA(BI7XJBOA)B60FfXKBOiA)Cj3EUlXLFi4>XYBO.A)-]CFpX^*BOJA)B@C OFȶX^0BP +A(eC)F-!X^SBORXr>BOʼA)]WC.E3XrBP?A"VC(NF/XrBPA"QC%]EAXyBPjZA'C^X0BObA"#C#@FXLBOA" C(F*XBODA!=C,*EAmXlBOA">C0'EXmBOPA"= C2ErmXBPnvA"kB͓X7BOA*C68XEx XBOA)MzCDuE$XBOA)S`C$X BOsA)B2CXBOvA)éC% FNXBP iA(#CFRęX@BOA"CAMBFfjX6BN˖A,CD0i}F1XBO#A)C'F6t9XBPeA"KC"HE=%XBPl+A!{B,[C%XBPfA!aCC{|nXBQ;A{B D>6XKBQ"A!BGwXRBQ%A0CE.X BQ+AKB}yDEX BQ5AWCoEeLX BQDRA6BxC\XBQ&ArBE8XeBQDA[C NEC,XfBQ6AP C(EXgBQXA +?b F:X|BQC#AC;EXBQdAB+E)u'XBQIA4CEXBQ;CA9BhEoXBQE+XWFBQ{ABqX\TBQDAh|BnDFX]*BQ^A J@EF)gX]BQ8A)BuXcBQA A 'q2E7XcBQDQA~C'EiXdBQ@WA BEQXg+BP AC/XpBQ_ABjXpBQAB(ɺXqBQuA)BuE(XrIBQ4AuBrE|XtBQ1A7C %E+XtRBQ7ABآEiXxBQAfC F@ѵXxBQgGAЦB.E2X~8BQF,A\C .1F9XBQemABdEgXBQ/ABdDXBBQlAdB?}XBQ!KAcCHF8HXBQPAMC%Fj3X"BQAEC d"FiXHBQOABFds/XBQ6JABnEoxX[BQ2A$BUXBQAaA XBQfAjB^CDnXKBQ6ApBqEXBQJALAB~XNBQA0"B?DX>BQBEA`BC_XBQw}AO6F+FXBPA&!BjCnXBPtA'`B|F#.X BPA({RÎ4FX BPFA(CxD@X BPuA&ZûNFm2X BPuA%B(gD X0BPϏA*B?E XBPA&"BtRCHXBP`A(FB@D>XBPA'C+FܽXBPpA'DBFiXBP/A&BEX%BPA%ñBEX%BP)A%GBEX%BPCA%6BE1X)BPiA'*CĽBP4A&QB~C5XFBPA%JBE}XFBPIA$QA@EXFBPA$CޜEXGaBPHA(C_D/XJBP]eA'CEXJBPA) }C{nF9XJBPA'Cm8F8XJBP{A){CF;XJBPA'jC#FsXLBPXA&CF>XLBPA(EhBCvXL-BPA(YBE,%XLXLGBPA&rByEZ@XLUBP"A(DB$QCΝXL[BPA+`B+D_tXL]BP4A+OhC U?XL`BPA)|qBFb XMIBPKA'$\SF1>XYBP1A( B@" X[ BPA(BEX\BPxA&@BEX\BPGA%BŮ#CX\BPA%BF#XaBP|A'ʯC`[DXcBPA+;C DoXoBPA(EhBCvXpBPlA#rCEXyBPAaXBPA%BޝE}XBPA%`BBOA(BX6BPȿA+ CCX*{BQF>AB~XLBQSABp@VqX[[BQVABXg$BQQAyBD!Q2Xi'BQ[AZB׺DX֞BQN AB BXgBQD}AB{rXBQBA Beo+DV<XBQ?\ABN5A#BcT`BGi@AB?E|a`.xBGfA!B/E8d`.yBGAUBME`.}BG AB4E`/BG7 ABqE`/BG- AB'EU`0DBFpAFB{`A2BG^ABrE<`A3BGjAbBӪEm`ABGJ:AoBOE)=`^BG0PArB2D7`gBG A.BcE4`g#BGASBCDv`BG-AB}D`>BG^AlBdE`?BGLABtcDc`PBGA%BDܐ+`QBGLAlB)D~`RBG ABD!`SBFA%BD2|`TBFAtBTEF`UBFABD`BFA̓B0E`BFABBD`tBFoA SC8qDo{c`ηBGOqAB`ER`ιBGL`A#B\4E6`λBGVA^BEEe!`ۈBGyA-BBD/` BFA *CCa` KBFՃA OC1Dv` _tBF÷A %?CtCݨ` BFcA *UCC&N` BF%A \C=\` BFFA >#C#C@` +BF÷A %?CtCݨ` BF÷A %?CtCݨaBHrAܚBGE3aBHA!BȂDOaBH5AUCD53aBH!A xZB31D1aBHA BB#ayBHA]BAE3aBHOA7BLDyTaBHA.vBəDiXaBHPAB8DޮaBHAB% EZ;a'BHA BΝE89a0BH`A BW*E:(qa0BHOA ByGcEAa@BHA vBEoĬaEBH=ARBȶD:aEBHAB^_DaEBHACBp"CsgaZ#BHTAhBxa^rBH#ABLDH/agBHAB3C$am2BHA8BTEayBHABC:az BHDA1B%B_aBHjA_B&E&RaBHwA]pAxE+FaBHASC$EUaBHlAD"BHDa(BHAB9=EJa*BHAC(Da+BHACEd;a,BHA,Cq`EXa-BHABA[B EL\aBHAkDB DzaBHABgDaBHAB9aBHAB3CVa~BHAL+EXa BHxAC3Ea'BHgA w BDM.a)BHA BB#aBH!A ͠B6EazBHAJsBCCa{BHABBYDؖa|BHoA BAE9naBHA OB(EBaBHA x,BDlaBHwA B(BaBHbAs8B_YCibBIDA CX=b BI1zA (CN5E0*abBI[_A CnD^%b BIEA 5C[bDTb"BIVA CiDb"BIRA .CYDwXb+dBIA E!bQ1BIA FC EbQ2BIA C#E}&bRBITA CD6qbRBIPA=CIfEk%bSBIA C!E;bSBI%A C9bZBIV-A$CD«bZBITACnDǑubwBI7A حCcobBI=tA OCc}qbBIA FC",!Db4BIvA 1CUԔEHb5BI5A CH Efcb:BIN[A CxEkbBI8A "aC&mbBIA SC4VF_bBI{A 4C`lRE>FbBINFA CSmbΣBIPACFEvEbΤBIFZAm-C\EqbΥBI@ACբE&bBI>A CHEh1bBIN^A MCw{EkbBIA 8CXvE5b BIA $C(uDeb#BHA LB DdbsBHA BbsBI A CE]*b8BHA BbBIA B3bBHA LB DdbBIA oC%EDæbBI)A.CkDbgBHACFnqbBIOACxD~bBIA]C+D18baBIACrEbIVBI*ACEQbXBIPNAnCqCbY%BHACFp;b`HBIAxXC;UEbkBHEACCbvBIAsCTb BIACPDJ½bBI AXC^NCB bBHʝAdC6DLIbBI ACZ=bBHA`CN8FhbBI$A%CnVF~bBIK*AC̖bBIOACxD~bFBHјAْC b jBIӊA ZC,?Eb [b BIϵA dCqEvb +dBIA 4VC E"Pb 3BJ*A uC\ub ;BIA @C8>EGb ;BIIACzEb ;BIGAwCԋACb ;BI;0AzCމEb CBIA 1C)pZD`$b DBIA CFE۽b QBJrA bCb QYBJ&A efC\D~b XeBIA tC2+EYۻb rBIA XC"D4b {8BIxA 5C-;b 1BIXA ,C)E=b BJ0A {~Cb=E1b BJwA ?CS7Eb BIA )CcKE#b pBIA 4VC E"Pb tBI?A WC$Eumb BJYA QCpFEpb CBJvA 3CNBEؐb FBJ A CU`_Eb BIA C5Bb BIA 99C4Db ;BIA SC3DESb oBIA NC,D׭b BIA C=E[vb BIA .C)E Ib BI?bA#Cb 8BI8jA%C /E9b 8BI@#AC D b QYBJ*A uC\ub Y(BIEYAWC! CYb Y,BI5AfyCNE^b dBI3@A>DCED b IBJAR}CpEb gBJ-Ad!C:aE b KBJpACo#Eb 4>BKnABC F\b 4UBJxAFCHFLb 4BJJA OCugFZb 4BJA$CF%b 8xBJSA )`CotFԅb 9dBJԔAFClnPEb >BJi A \C(\Ezb QBJtA C=b QYBJ9HA C[DDqb UBJA(CEb XeBJ A CBNDb XkBJNAWCE%b \:BJAHC0Bb hBJأAYC}b rBJ"A U#C\bb rBI8A m;C9'jE~5b }xBJ|AhCE]/b BJXA 5CvEb BJAnC#Firb BJAzCFb BJ]ACo^EYb ~BJuAmCE7Ǖb BJrACFb BJsCA DCaEEb ϣBIA ѮCI[#b BJ@AHCBJAC*FN7jb ?BJvALCdEoFcBEZAxC"FVc 5BEABϓEac BEyABEc BEfhAHB9O@EYc !BEANBEcqBERA-BcBEIA/BXcBEABDcBEAB.D{L@cBEA%BhDSc/BEAnB-C!c7BEA^R^Ec=2BEVA B׺D&GcBBEABAcBBE AIB6@cBBEABAcD@BESA,B̸RcDABEYPAfBHcJBE^ABu7EFcKBEAEBC9cMBEABE cMBExA4YEvcP>BE|AbBQhc\BEAvBByCtI c\8BELA[B&D c\>BEAUB蠮B(c\BEh}ABDzic]BEAkBCJcaBEKA̖BcaBE A/FǨcaBEAUB蠮B(caBEAB*CCZcaBEABcaBE1A8m*EctBE>ABEEcuBEA)B\cBEAQB9ccBEA-BCBEi1chBEA0YCmEciBE.ABEHcuBEAPBD} cyBEATBE|'cBEAϼB1EcBEeABvF%cBEAkBCJcBEAB*CCZc?BEABoDcBEAQB9c0BEATB.DÛrcBEA!C3DcWBEiASBSD9c]BE3Ad&Bc_BEAF*BUDBE<cBEAuJB5VDR,cBEhAzBB7D&gmcBEtAlB(kD+cBEABsD+cBEA6BƪEMcqBExVABaUDr$HcrBEi:AUwEcABEcAYB2E/=cBE[ApBDzecBEdABjDRcBEAABD)cBE-APBEUcqBEYAsBcҝBEAB;D{]cҞBEABoDcҟBEArB{cDWcBEATB.DÛrcBEABsD+cBEAB#GC^cփBEA}BBD- c։BEAvBByCtI c֊BERA BшD[:c֟BEASBDcBEAkBCJcBEYAsBcjBEAѼBVDcEBEABVDpZ[cOBEA BDH>cPBEABcBEA`pBFEΐcBE!A%BD)c 5BEA7B.Dqc BEAC.c BEUAΟBcBEmAB2E'GcBEwAB EAcBEA5E+c/BEvAFCEVc0BEpABDZc1BE(AzC7Eytc=rBEFA BE*cEXBEH ABkcLBE+AB{EcT7BEuA+0BtEWcT9BEfAYBcaABEAB.pDtcaCBEAC D caBEACK?cb8BEAkBE?ctBEYzA{BctBEABEݨ7ctBEAB/E`"ctBEuAB=D/+ccBEA#ʷF5KcgBEAC, EciBEAOC>E;cNBEAH$VEtdcBEAC0u7BcQBEmABq`DVc֟BEABCQE5c֠BEAqB'DQg&c֡BEvABBEEcBEjAB\ctBCA eClDzcBEǥACFË"cBEA_BDecBEABcBEACBƊDc/BExAJBXC/c12BE6AWBCc1BEAB{c1BEA BoE=cMBEAaBcVBEA_BDecaBEAN%BD|caBEBAGB֯DWcaBE-AJ*B8CLctBEuAIBzctBEAp5BàvEcBEUAC<+chBEABDcNBE'AB{cBEtA^BD5ծcBEACK?cQBEABgE>c%BEtA^BD5ծcѽBEAnB-C!cqBE3Ad&Bc֟BEABCcjBEAMBc3BEAqC&YBΤc BEAyCDDvc BEAIC=E&r0c BETAx C{iE0c$BEFAzCg!E>{c(BEAfC:UDPcaBEACK?cb BEAC"BkDcb BEACOYB[jctBEAw}CuCE,AcBEAC9Dnc%BE!AC3Dc&BEAHC CYrc)BEA&C./DcBF1AC+]cԲBF1rAVC7cBEAuC1߾c BFACCC EmcBEtA^BD5ծc BF1!AC5c BF1A2C2FWCʧPc-*BF1AC/^{D LcBF1BAC.BTXcBF1VAC4D/cBF1rAVC7cBF1AC4tD(jYcBF1VAC4>D/&cԮBF1VA:C&TC=8cԲBF0AQC,CfcBF1A2C2FWCʧPc 1-BEAzCu'Agc aBEAC3CcBEA{Cv9c2BF;A:BLE/3c BE\AeB~D5c BEAYBjDZcBF tAw;BDX4yc6BF&pAB5c8 BFN%A^BcBBEAB(cBBEA;;BE,i!cGBF$DA BD=cHBF+ABюEocHBF.A0BBEDycIBFA^cBD6cIBFA9BcKBEAOB Db;hcYaBEsAl#BD@!cZBEsAl#BD@!caBFA`B]KDۇcaBFfAUJB0C caBEAN%BD|cBF ABB{ClcBFPA'BńDUcBFA9BcBF8AUBBE cBF:A/BLDԇcBEANBEc^BFTARBXE iTcaBFA,BYD֐/cBFAtBT0BG.@ 3C:D^a0dEBG.@ 3C:D^a0d`2BG@C;*D%HdBG@BC2DdBG@C;*D%HdBG@CC*dBG@CC*eWBHgA xB]aD[eBHcA cBB 5eBH`A BŐDreBHcA BơHe(BHlA BBěACU e)BHm.A B*C2Ie&BHd*A mBȑC~ie&BHcA rBC"Ye&BHc!A qBCCVe+ABHdJA ,BLD(fe+GBHdA B +CeEBHlA B|CeU[BHiYA BDPeU^BHlA UBD eU`BHc6A qVBVeUbBHijA yBODMXerBHdEA lBCdesBHdA nZB D u5esBHc!A qBCCVeu5BHcA FBчnDevBHbA CCeBHcA oBGBjreBHe8A BTsB@eBHj7A BWDX@eBHhA KBDYOeBHg!A ldBWC1eBHm4A B)C$eBHe^A gBdB-eBHdJA ,BLD(feBH`A mBepBHb[A ?B/DeqBHd\A BJ=C]xeBH`A BŐDreBH_A B>DӎeBHcA BBCheBHlvA B?VCeBHaA BWD0 eBHa8A BDTe:BHlA BC-MeBHl9A BΦC#teBHcA p7B˜Ate BHlA BƀC6Ze BHwA B@:!ge $BHA yB.UD5 eB@kA9xDSgBH@gEBrCgBgEugBF;AWB0EegBFyAABPE*gBFp/ABDeshBDMA{C%RhBDgAEC?aEZ(hBD ACZPEh 8BDAiCkwEyh BDASC-TEh BDSAC$EshBDAoCAEhBDAC7vvEϳhBDACHFDh1BDϲA@CZUFNAh1lBDAC.h1yBDACzoEh1zBD8A=[CbثEh1{BDAl1C*ZE]h2BDAșC;mGDh2BDuAgCcިEh2BDOAC5E.h<+BDACƌzFMhFBDAooChIBD2A$C@aF`hTrBDA-VC'C,E<ɒhTBDzAC'|EZ\hTBDNAO#C--EQxhUrBDA[C5E6hUuBD@Aa)C({E=hVBDAۖC@ffh^EihBD'A՝CsEg!hBDϜA]eCbffh3BDA)mC:E>0hhBDQACcDwh%BDrAH(C5!Eh)BDAC7wEshPBDִAB.EJhSBD{A CNڦFohZBD_ApBF#BjhxBDARC+EqhȈBDA.C|E+ahȊBDACpEz+hȷBDACc(DehɻBDۋACy>DshBDȐACZhBD]AC5hBDתA(C?Ech{BDbAUC|>aFhBDAC29D3hBDUAC.Eh BDA CjEkh BDAHCD_E[h BDyALCe)h BDmAbC>E8h BDsAމCfhBDvArC4Eh:BDuA}CyEUhBDA&CEVhBDA @CǛAEh!BD AL~CҳE)h->BEA$ CLE./h1lBD\ACd1G.ih1{BDAC+gDh1|BDAC-aHh1}BDA~CxRh7NBDMA&CRhC\BDAԞC{UF+hIRBDZAfCWF.`hIBD`ACQ hPBDAXpCD=IEhTrBDA C&E|hTBDAFC(ۿEhUrBDAIChV~BDAGCE8EhVBDASCE]h`BD؁A CM1FkharBDoHAChbBDA&CgD%hrCBEA$C}ht[BDAAR+C\EY ht\BDvAC0QF"hteBExA#CǧF\zhtgBENAA#UCt7CH/hu/BDAhCVEQhu0BDNACkxEy hu1BDASC9E<hvBDÈA)CaCD]hx BDA C=٩Ehx(BDAUC(F-h{BEA t CBRh{RBD A40CK5hBDA8C"EhBDaA=CFhgBDA C>DhBDֈA̚BE͚QhBDA2C3'EhBDpAC`hBDMA2%CnET{h +BEA OC3zqE۳h 2BDxA GC6Fm h 2BDGACF7r+h 2BDA xC5IF,h QBDAt C-E"N:h QBDA C=PEf<h ^BSEh"BEAvB=qh0BEL)ACWE;F#h2>BEAAB[‚Eu&h2BE ARBD#f~h2BEZABzh<*BE.A] B㉲Dh=2BEoA'5BE/h=OBErACBhdnBEcjAbBEhdtBEPABDhdvBEheACzzE˞\htBE7AsB؎D+htBEPANtCWNF1htBE;qAD4BOE)rhtBEiABHhtBEkABfh^BEyA{BLEh_BEABmEPhgBEABDY<hhBEmABpChBEABhNBEAFB hBE-ApB.E5hBE'ACBיE#ph֟BE#A+BFh֠BEA)Eh֡BEGA BpE^hBEd AJiBE9~hBEaA|BڅhBEeABDh BE؉A>B~hBBE3AB-D5hBEկABxD(hBEKAbBND hBEAzBzhBE{A;B.h7BEaAfB{huBEUA hCHhuBEAB{h BEWA/Cm"E0h BEWA.CEHh tBE@A @BEhqBE[Agf@^EAhBEMAD/BEEPhBE2A OCAoEҩh%BE7A XBICh%BE)A Bh&BE*4A B̊0E*.h&&BE>A +gBySE1Bh06BEBE|uASC{F&hP?BEX[Abw))Ec%hP@BEZ9A$JF"hT7BEYPAfBHhXBEA vC2 hXBE,A BO\h[BEMA B`IEwvh\ BE?yA 8B#E\h\BEK>A PBlCh\BEKA B'BzEuhEBEF^Ap(B+'EyJhBES:AA@ EL?EhBEPAIEgghBEXA7CyEhΒBE;A [$BйDvhBEY5AtBƜDLVhbBEKA BRBihBE=A R*B&EehBEGA )9B EgړhBEGA "B6Dh BEA PC=3h1BEA VC@fCjhqBEA FB}E hBEA CQAEBhBE#xA 8CF+h%BEA kMB;h+BE A {C1h.BEA X)C;C/h.BE(A ClEh0 BDtA oBоh06BE1A ,IChMEEh2BEA N4C5 pEdhJBE6A CAGE|y?hXBEA CpGEU hXBEA +CeEthXBEA B#hXBEA KCg`D;h_2BEA -wC E&hdnBEA NCBqhrBD>A B̜)hrBEA "C  hu`BERA C&YE=huBEA |CK$vEJh{BE&A t9C7)-DhBEA nJCEE^h(BEA QChjETh9BEA ljCFgWh:BEA C@cE0hBDAbCNuh@BEA OBٳE!'RhBEA 7C@BH1A=XCDiUjBHACErikBH ACS[BOitBHBAhCdEW-itBHAaRCyECivBHqACqEivBHA'CD],iBHJA"|CνDiBHALClEMiBHWAC, F;;"iBHA CE[iBHtAC&xE>iBHnA qCiBHoAC bBiΥBI*A^CDV#)iVBHAC DiBI_A!CiBHA_CZDiFBHAssCIE;i.BGACpBDA CJEFbpBDAC%p}BCACeEOj&pABCAuC")F4vp(BEA!\C͑gF0p- BDƛA=CFp-3BDAzTCp-@BDA"iC=p7NBDxA/C,pIRBCACtT9pPBDڮAC3DxpTZBC#ACn~wpY|BCA|CNEp[BCALCDYEPPp[3BCmA"CF7D=paBDAhCىE.@pc6BCпA]CGRpcyBC!9AmCޞB{ipqPBDACEPpqQBD!AqCVE9ptBCcACOnpuBCcACOnpuhBDAC9D^pvBDACׯDK/pvBC͘A@$CZEOpvBDժA8Cp~kBD̥A"rCp~wBC&AkUC CXp~BCAC^T{p[BD'A"=CpgBDFA8Cgp~BC A_C$C.0pBCAUCgEpBDACтpBDFA7C-1FۄpBDA$CYEGpBDA C`LFCpBDA CDvpBD )ACDDEXpBD $AlCfpBD#ACF9܋pBDUAC;EpBDA&CF(lpZBDpAdCpȑBCArHCDDpBDBDA!D!FdpBD-{A#CνF.pBDAq CpBDA"jC[Ep(BDA!D-MF[p-3BDšA!D)QF>p-@BDƵA!}wCSE5p0BCA#Dt@p0BDŐA!ID F`FEp7NBDgACݨpFBDA#CpPBDACI4F/\pRYBD]A#^CgEfpRZBD?A$)&C3Es~pR[BDyA#^|C8FpTBD'AWCZFF5pXBDqA#CE{xpaBD]ACvFXTpaBDAQDuF*mpc=BDA CFQnpc?BDA 3"CEpi BDUA C:CEy pqQBDA eCFApteBE=A#OeC:=puBDEA#D)E-pvoBD"A#rDE 3pvwBD$A# CѽFp[BDA"C|FiSp\BD$A# C9FA*p]BDoA#DFdp^BD!A#A#C2E pOBDA#D,cFi7pSBDCA#g6F|pmBDA#AD4·FIJpnBDQ]A#uCFQrpoBDbA#|C8F VpBDA!%C#F%pBDA!{CF!qpɳBDA`C?F tpɴBDAWC/F0pBDA CFB$p"BDACEE8pJBD?A#(C̪E(pPBDAC D*p|BC0kAVVCHE4p}&BCKQA(CxC!}p},BCLCA0,C6C%p}TBC6A "C_E |p}UBC2A=yC Egp}VBC5A!pC fEp}ZBCTA:CEdxp}[BCAVCȉEkp}\BCAkCE7p~BC>AC D*pBCQ AENCEpBC;kACD5F,pBC*ABC uF ӜpIBCH9A(CxE pɒBCA4CE;KEpYBCK[A!#CvD2RpZBCI A;C Dip[BCK>A'CxDo:pfBC)ACQE`pgBC+A BJE phBC"uACѲHEDpjBC#AC͌ E`pkBC4~A+hCָEpӢBCAh?CTEBpӣBC%aA,C%F;pBC!wACECpBCMA)C攊B/pBClACpBCA6C¤bEpJBCWA2CO}pBDAC pBCA:CpBCAC&9 E pBCtACF)EpBC[ARJC|FlOpBCA©C)Dp BCRAZtCmDp QBCAKWCDp VBC^A9BE玔p BCACmEܹpBCA8eCGEpBCAgC0E+p$}BC_A^CjDpABC:A'CoDQpTBCA5gCM6-EUpTZBCACVaE p[BC1A*CI2pbBCAxC}J=pbBC)XACCPpbBCACA!HpbBCACMDpbBCA2CD0pc6BCAڇC["]EOpvBCAdC{pvBCApCDpwBC|'A@CFpwBC-AfCEe@yp}?BCACOCTDpp~wBC(ACpBDA+CwpBCAyC~>OEA!pBCA6tCb pBCAKC`pBC/AiCmgE5EpBCAKC`pBCAUC"DpBC/AiCmgE5EpBCAtC}EApBCAGC}EA<pBDAC pBCAdC{pBCxQAC(Dep\BCA".C3De9p]BC\A&Cp;BCWA4C؏BԕZpwp)ZBCLA N:CףEp5BCx~A!RCLEnpLBC{A!YC Eh+pPBCJ6AsCjFop_KBC_[A CհE [p_NBC\A CEhp_BCgA! CE=up_BCKA LCsE}prNBC]A CE4pxBCHACF pxBCHACEpx3BCIOACO E<pxEBCJKAUC˅D*rpBCJAJfCZDzFpBCHAGC_EpΈBC~#A!_\C:ESpBC,A!CȠDc|pBCmA!%CJD_MlpBCpA"CzF 2pBCw}A!UCREnp*BCA!g"CڰEppBCbA!!C!CWpBCwA!o{C oDpBBCHA ;CvEGdp BCJRA CDgY]p^BCeA ׌CFpfBCWpA @4CFMpBCyA!8CCӆpBCA!WpGBCA vCCip`fBCRA CCk fp`oBCA CE<%p`qBCA +~C7Epc3BCAYCCwpo BCEA 7CDv$p+BCA eCV9D{p,BC$A lCtD1p1BC_A /CXE Tp?BCA CDdp,BCA KC=E,p4BCA "C8Ep5BCA piCGD\p6BCA PICE7pLBCA PICE7pPBCA 2&CEhp4BCA C@A%prBCA {C p|BCA CA^=pBCFA CpBCAY=C!Cp BD A#$zD+D+p BBMAD Ep BD ~A#!:CjDp /BA*?A!`DͱC{ap BBcAmD"gE^p BBvAQ,Dhp GBBAD&D{p BB A#PD,EЯp ABBA0B&Gip BB75AD6p BCA# D@p BC9A#D-EPdp wBBAԧD&49p 0RBBOA-C<p 0BCA#CE/p EbBC@A"DF\6p IBBACcsE.p MvBBAԧD&49p R[BDA#"CEGp iBBAC-E"p kBBAÄ0Fp tBC4A#CDp tBCA"DWF9%p tBB AEDLE\p tBBAp%D(BCE #p uBBApCZp uBB AvDFEpp uBBr8A2Dp uBCA#SD(FUKp uBCA"CⵔF %p uBCA#*CFiop uBCA#C>(F3p uBBAD{E-p voBCsA# D!BFep vvBCA"DF2p vwBCA"D F8Yp |BBACIDY¼p |BBAChnDPtp |BB|ACfp 1BBRA#mDg p >BC;A#GDFgp \BCyA#D܎B{p ]BDV:A#PDp BCA# C#cE@p BC5A#C(D.p BC݁A# CE;p IBBkA,DoE.xp PBCoWA#.fC-Fi0p BBOAFCوC"Pp BB ABB{AkCrpD(p BC֏A#FCEp BCGAwCmDp BC|A CEp BCxA FCCp BCdA *{CEp BCx,A NCLEp $nBCHAGCcE>Pp $BC;A.C IDlHp $BCFAzC%Dp $BC^A DCAE-p %BC[A gCFp 9BCZA Cp QHBCPA CME/p `rBC[A fECCEp cBCEACqDWJp w*BCoA ~mC7F=p yBCNGACûERp |BC2A!C&Cmp },BCMAEC0DL_p }TBCCAC*=p +BCpeA yC{ZFBp BCNA CB6D6>p BCGA~CNVp BCaA C5WE6Np 5BC{A Cg?EEp 6BCbPA Cp rBCoA mCF3 p tBCWwA CEǧp uBCbA :CnEZp 2BCMNA$C躝Ep 5BCOUACDWp hBC3ACC΍p pBCKA9C.p yBCIMA_ CBCàp zBCGArwC Dڋp ӣBCGA~CNVp ՑBCfA C\EJp ՓBCO AClp BCAHANCUEp BCCAvCE/p BCSA ŽChE{p BC4:ACGEp BClACp BCbACp >BCPACzD36p ?BCQ/ACD[p #eBCDAVC D܂p #fBCCMACGFE&p $}BCsArCp 1UBCBACMp A BC3=AVCz6E2p C%BCJAYC8)Dn1p IiBCAC C|p R*BCPAu C>DWp TBCJACDxp TBCFADCGbD`xp V_BCJ1AlCp ^BCPAC,Bp ^&BCMACBp _ BCSACvDp bBC@A_CID9p bBCB&ACǮp bBC_7A3C,p cBCWACYbDĔp cBCWACYbDĔp cBCUACy Dp cBCRA\C E0p hBCHAdCNKDYp x*BCRAA2C ;EGp x+BCNKACp }?BCfACE}p }@BCR}A^CZEhup ~wBC):ACp BC)AX3CDQlp BCdAxCE p BCaACUEA;vp BCSWA@vC)D*p BCPAChJDډp BCcA}MCXRp wBCKAC_xDp BCBACEE`p BC@TA}C5XE.jp BCcgACp BChA$CM'E;Dp IBCACZE !p xBCFIA>C:BD#p ~BC:vACEp BC7A6CDۈFp sBDAC p xBCACp ϱBCXNAC\EQsp ϲBCPAiCs?*Ap ϴBCV AqYCBp BC&A8Cp QBCAAGCCp {BCDA,+Cgu[E@ݒp |BCDAEtCEap }BC=A 6CDp BCPAyCP@p 'BCPA~CtDNp BC1AVpCdmPp BD܋A' CE1@p BD[ACFjPp BDPA|CxoE{p BDACjeDE|sp BDYADN+F%p BDAZCf p !BD؟ANCNE2p /BDhAƦCrE]Ep 7NBDA5}CFp PBDA CIEىp `BDA2C}E$p `BD`ARC EbHp `BDMA*xDFNlp `BD֪A`C^Fp `BDAkCFp bBDACF9p bBDAR#Co}E*p uhBDACWEp umBD\AChEp vBDARCVDp vBDA C\F1ɗp vBD7ACuvFSp gBDՂA,CbE}p BDACκqEp 4BD&ACnT9p BDACsaHp &BDՙA2CƝErp *BDA*C,FGp BD A zC:p BDAC!Hp ZBDAcCFa;tp mBDHA)C[bEp BD4A`CzדDGBp #BDPASCgUDLAp ɻBD{AC?TG8zrp |BD]ACsfp BDAWCc(YE p "BD#ACb@E` p :BDAi"ChE2ZCp ]BDӜACFb p BDʂA,Cp BDA}Ct4EՏp BDȐACZp BDAC@pE\p ABD+A1C=p 1{BD:AC5p PBD\`AC#E yp PBD5LAHEC"lEp V~BDjAtC{Elp amBDRsACD+p anBDWACp aoBD1ADC|p aqBD,A:CIE|p arBDKAC0&EDp iKBDjAUCfp u/BDA/CLp x(BDRsACD+p BDWACp BD4.AHCxp BDAC^p BDȵAX=G lp BD5LAHEC"lEp BDA CEecTp =BDWAeCbEN>p @BDCAeCEp BDAPC2ЭDY]p BDNAzpCPFp ٬BDsAACH[E[bp ٭BD^ACExQp |BDXJAC:E R p }BDcACpzBBIsADGgGE=pBB|ACfpuBB`AD*F >p BBAND";p BBAxCpCewp BBϒAO9CzDKp 'BBAS=CeDdp `BBA`CnD#bpBC-AoC5EblpABBA-CCxqpBB*pA`D=NCpBBACZpBB9AnDEpBBB65AwDKEpBB$AYD F|pwBB6A~uD8'p!BCATCsEp"BBA( D:p( BCACE1p(BBADCDϛIp(BC2AC hDp-BBAvCCCp.BBAA:DPEp0BBDAq4CDucp0RBBAFFCyCT p5BB! A߭D@aEup7BBA?0CEMp8BBJXAfD:}fFDxp:wBB2wADD)Ejap=BBf}A: D%߫F\8~p=BBAD9ME+zp>~BB|ACfpBJBBQA?C_ Cx9pFBB)A'CD@pOLBB]A(D4$F(pOBC-AC JpVBBjACsD)pZBBATCABE57pZBBAcC&F"JpaWBC+ACpMEWpbBBA(CoEO2XpbBBAC2Bpc%BB-ACuphBB.A,CCCpi2BBArAChBDspm)BB°ASDBF`pm*BBACԞE}prBBA5CDMpw{Ep!BBTA0DSFTp"BBVIARD8 EZ'pBBݤA*CCF#tupBBˊAC"CpBBͤA!CwDʓpXBB)AcgD>pYBBH8ADEpBB@A%C DRpBB0A٥C'FWpBB|AzCZ\E_p BBҠA&nDL]6Fyp7BBA1DEF<p8BBA5CYDNpBBQA?C_ Cx9pBB A>CDtpȭBB,Ak~D?WDXpϓBB7RAD"E҄p9BBACM4Fޞp;BB9$AUD.EGpHBBAqCDRpIBBqAoC.Dq!pZBBAD>ƚE@+p|BC-=A8C3D3pѐBB4>AD:E^bpѝBB0A^GDD3ppѭBByAqD=pѮBBNA"fCLp`BB7qAD;%Cp5BBA:CD p6BBA6`CEgep7BBA1eCαElp>BBJAC#DLp?BBAC|EvpABB AD8EޥpBBBAD EpCBBACEE^pDBBAiCApEBBACIDY¼pzBC)AFCVp|BC) AWZCE|Cp~BC)qABC[EӨ#pBC..AgCE99pBC+AzC|E_pBBדAC%E/pBBKA7CDpץBCAC~FpצBB,AV]CE-pPBB^AzC:E6paBBvADJEbpBBhAo9C҃FP p BBkAC/F4pfBB[Ap D8EpBB{AQvC֫RE1ipBBeAwC}p-BB|AlbCIbC4p5BBTA2DQF pvBB%ACGSEpBCNA%DGA?p qBBA#pD p8BBtA#6jD cEPpBBjvA"DDF12pBBA#pnDwF1>p"BBA#D\p/BBt`A#HDE:pEbBC(A#`NDDpEBBA#zDzp_BBA#ChDsp_BB}lA#KDPFǬp`BBrA#O DE?.;p`BB2A#qDIEpuBB9KA" Dx;EVpvBB_=A"D&HF<pv6BB A!0DFQS-p1BBA#SD3z{F|[ap>BCA#TDF&pBBe7A#~DEWpsBBA#BBRA"oDWbD7ypbBBWA#|DE©pBBOA#vD EUpBBAD5pڟBB7A!'DpBBA#xDEupBC)A#_DEhpBBA#DEL+pBB"A#glDYMEpBB A#DEepBBAD5paBBMA"xDyE-7p PBCRAdcCpBC6AC)|E^JpBC2A/CˉYEpBC>ACEpBC/AC Ep(BCACCǸp-BC>A~CQ?E]sp0RBC%Af[CCVC1dp6dBC;A IC}džD pEBC>aAeCΪEpEBC@AxC; OEtpcBCBACpcDGp}EBC1AuD0 AgCEm*p~vBC&8AƐCɿpBCZACZDMpBC7ACoD4qpaBC2ABCw1EiڌprBC.AACpDpvBC. AQC{E.(pBC8ACpyCpBC6ATCM8Cp BC/A2C5EpBC8AXCzDϯpBC.ACךEpBCEAPC_hD*DpBC;ACDpBC9XAECB6EK&pBC6}Ac.C3E%^pNBC$,AC>K]EtpBC5A'8CyEΖpBC/&A"C"DpBC@A~¢4E'pBCFAKECnΌCp~BC,=AC/pBC*AC}QDЉpBC=A:CEXpɇBC1AdzCX:/Cy)2pɈBC%2ACgpBC0}AgCvDdpBC'tAC_u~D 9pѦBC:A{ChGE۱9pѧBC6MACERtgpѨBC0AC.EMpѩBC;gACMFCpxpѿBC2nAPC6E3pBC2A]C}xDpBC0ALCw[D*pBC+A CgCw/JpBC,ACwFC)]pBC)2AC(D;pBC.AFCpBC1AZC}CǞpOBC:,AuCkDpPBC5A3Ca2C$pQBC7AǤC{)DYpӂBCZAhC^BpӃBC(A|C~E>QpӄBC$>AyCđEIpӇBC3!AHWCkD\KapӊBC5:A.CC8pՙBC'ACS@{Ep?BC%A.CBΤDp@BC%jA7C(1D cpGBC15AqCfDpHBC, AC4XE,)p]BC.AoCX7eDϺpgBC-A!CGyDGphBC1AfC]DpBC*AC70EpBC)qACtp!BC&A:Ci pBC.AxCS(yDpBC*A|Cw(DEpBC'7A{C1EapgBC-_AxCezEIpkBC)ACyD[)pfBCBaACUDOpBC*bAOCGE3@p3BC)pA/C,EJypGBC;AYCQDTpQBC.-Aa5C|E*ljpUBC"A C[SDpVBC#vACQЪC!JpZBC7ACDpkBC0AFCZuDplBC-AFCq En~pmBC96AQC\DT!poBC7AjCj DEfp BC7 APC_ADep BC&A̬CbnD}EpFBC.AWCiDpKBC+?ACۮC@pMBC,+ACD& p8BC,JACDCɆp BBCD&ACCDYp MBC=ACAHysp rBC+ACp tBC,#ACoCqDp xBC0AC?DLcp BC;;A=Cap&Cp BC9A;C27DTp BC:A1!Ck=C]zp ?BCIACpBGpI8BC+A*CҗCĥpM BC<{AC\8@GpYBC.YAC{D!pYBC-*AC+DȬpYBC(QACChOpcBC6ACvDpcBCHCAC3E ipcBC;A^CDpcBC?1AC/Df)pcBCH%ACTDepcBCEACUDЇpcBCCAC̷DÏdplBC/AKCyisDkplBC+A5CD"HpwoBC>ACDp|BC>AxCCKp|BC>4AiCvC!<p|BC>AC63Byp|BCKA'C D&p|BCCACG|Dp|BCFAC)D,p|BCH=A-C:Ekp|BC:A"C8:Ep|BC9ARCIE[*p|BC8A CtD+p|BC:A C^VDZp|BC:IACHDp}BC7ACD:p}BC10AgCrE (p}BC0AFD|TE3ppBC=AC:kBpBC1ARCcPEypaBC<|ACQ'pIBC:ACv[ECmpBCKAbCE^ pBC6APCOiBpBC4!AC0DpBC0AChaDpBC/AdC4DcpBC7jA=$CLDp=BC;0ACCD pJBC1vA$C^5p PBC):ACpBCD9AECg=DDA0<pBCE Af.CZC誇p"BC1AE>C?p-BC\A9CiEYGp-BCBACp6dBCDA:Ce?6DrpC%BCOA`CDqpEBCDAC`JBKpEBCGA2CkD<pbBC6A5C`%aB"pbBC6A5C`%aB"pbBCEACjDuepcBCH_A6+C:E~pcBC?A&GCkEu~pc BC2ACFåpcBCYA`CD%@pcBCWWARCZVDpcBCLAC{pcBCtA?CspcyBC"AsCzC=p}IBC:A5GCgd(CO_pBCTAUCD:pBCB%AfCEpBC8 A CuQEp BCYAC_EDpBCAA4CiF N_pBC=A-6CE^pBCBC?ApC[ELp?BCsA^CCpBC)AC'Dۂp!BC*ACCxp)ZBCIA&CRD۟JpNBC?AC0EWpOBC1AJCXDDJpOBC,YA-CEE5pOBC-A4ZCsfCE)pPBCANCFp_BCHA CC6DIpaRBC,ACOE 4paSBC+eA#CVEt'paWBC)yACe E-,pa[BC1ACjE)kpa`BC.eACs-EvpaeBC/ AGC E? pi&BCVAXCEpi6BC,A|C E&pi7BC;A_C,F~GpiBC7QA,CFe>piBCAeCpxBCJAG2C4D?pxBC9ATC;cD6pxBC#AaCa5CpxEBCItA\C E{pxFBCIA%CȶgDڬp~lBCDA]CѪ.C]YpBCIAKCfEpBCIACE_pBCDA\CmEpBC,AoC`MEpBC(A/CE2C pBC?A CՠEjpNBC!A؇CF3BpPBC9ACUE p BC8qA"WCF=qBCAӸCzFU!FqBCA CYC&q WBCAlCbidCq BCyA5CjVDGq PBBFACFU*qBBACo7E6Nq%BCA C_EUDq&BC ACdE*oqC%qlBB"AC:q?BBqAJC}CNqBB AwC+E# gqBC!Ao(Ca1AQAyq`BBA$CnFEjI{q|BBߛAC7EgqBBAhC~E:q7BByAxWDqBBAFCpADqoBC A6jCdDfqpBB A@CcDqqBB@AeChEh$qBC ACe#DlCqɀBCA)Cj\E ]qɁBCA+Cf}DFqBCATC^qBBACXDqBBASCxDqBBA#ChE\qӕBBACr Ej}qӖBBAClqE~fqӗBBACjNGEEnq^BBAqC8qBBNA"fCLqBBACr[qBBACqtDqBC!An?C5DqBBA!]CɃ}Fqc*BBbA6CD. qrBBױA_C&:EiqrBBԊAtuCwEqrBBՆAUCyDqx5BBAͭC\FE`!qx6BBACCeaqx7BB1A&C>E{qx>BBAyCEqBBܬA[ CzqBBRA"CP{DYqvBBA|CE$@kqBBRAC܋qBC AGCbD'Uq BC ALCE3 eq tBCACC<|qBC*AH^C)MDqBCPKAd?CiE:q"BC1A2Cq$BBuA$CǮqV_BCGAP!Ci$E9q^BCKACWE?q^&BCJBA&CDqcBCA)C^@qwBCAأCq|jBC LAb CEq}@BCKAC {D1qBC AF,C_ ES8qBCA9CE(qBCHAC"D +qoBC 4ACEAjqBC LAb CEqEBCAFCCgKqBCA0CDTqBBAC@qqYBCJ1AlCq[BCJATPC{quBCKADCa}DkqvBCNKACqwBCKAC5`qؐBCALCثCskq9BCKVAhCGBtq9BC AvCDqIBCA9,CDA:qKBC CACBحqWBCJAfC\CwMq(BB 6A CKEqBC ACd7EhqFBC-AC4_D,lGqKBC+tACC-qMBC,CA aCCnKqBC VACDqBCAPCE,Fq8BC'AFCD|Oq BCARCB>q BBACZq rBC+AC"E1Hq tBC 'ACWEJq xBC2AiCEE=q BC,AtCLq BCAC}Dqq BCAwCE>qBBAljCD`qBBjAClC@ۯq+~BBEA`C DYq/BCA$C=E>Yq1KBB!ACIBlxq< BCACjC/{q< BC!AIC{Vq?SBBACAqFBBAC0DqGBC ACG!EDgqGBC?AUC\D\qGBCkAC1D qGBCA6CJDqGBCAűC5;D{qIiBC AOCeEqIkBCtAC`DsqYBC(5AC=TDqYBC&A CwDqYBC'=ACD_q^BCAC+C:q^BCAC+C:qcBCAECME.DqcBCACz/$DǒtqcBCACDqcBC%yAC #D5qcBC!ANCzqDx4qcBCAWCzMDqxBCZACwvDϭfq|GBCACuD]>q|HBCACwDoq}BC1A ~Cq}BC,A@CDq}BC&$A$CCAWqHBBACD qBCA"Cx0DqBCCVEqJBBACLqEqKBBA)CEV,qBB~AnCFqؒBBԜAC Cm7Hq8BCAuCfGCfxq.BC2ACȨqIBC%AӳC/DTKqJBCuACDqXBCAYCzd:Ds5q BC.A<#CahqBCAւCq UBBADAC Pq &mBBhACFC\Wq J?BBuAw~Cq QqBBAJC܁ D_q QBB7ACӝq RBB7ACӝq RBBARC׊]D[q bBBAJC܁ D_q }BBAD:E]<q }BBڊACD#Dq ~qBBAJC܁ D_q ~rBBAJC܁ D_q ~tBCQA CFfq BB7ACӝq BB7ACӝq BBoA8 CƗEKq BB$A0KCq BBƮACVoDqcq BBNAEfCFq BBOA D-Pq CBB$A0KCq BBÂAPCXq BBACԸDZq BBmAqdCGCpQ:q BBAJC܁ D_qBCADCVqBBadAqC1F+qBBAWmCE%EZqBBACDq(BBHA9zClWD]q(BBAD FNyyq(BBATC7Exq(BC AըCfq.BBA{C`E< q5BBA[CJT'F \Tq6BB|AC0|E'q7BBA`CEq>|BC pACy~CWqBJBB1A;CםCd8q`BBACqa$BB&AC#EXmqa&BBArCPEqa:BBArC[EnqbBBAZCkqm*BBmA!Cqw;BBAlC EjqwCuDqABBAC:DMqBBB)A5 C.Ai(qCBBbAU2CqPBBAC´ELq^BBeA+CpEkqaBBAZC4EjmqbBBACE`qBBAU-CzCF=q BB>AC.E qBBA)CGEqqBBA$CWEޠqBBAIC}E-qBBA`vCyqBBDAC#@E#qBBA:CExqBBC(ACECNq1BB{ACQE2q2BBnACuE})qBBiADLEq$BB4AECCk;q(BBLA*CE6Kq( BBAQC?DGqq( BBAkC PDMq(BBA$DrG8q(BBvA:CtD{E+q.BB'AC7Dwq/BB|BC&AmDD;qJ.BBnAjCТ|EUFqV3BB+ACEEPHqV4BBIqAVCTEq[TBBAC֎q^bBBByACEq`BBACC^;DqaBBAkC PDMqaBBAQC?DGqqaBB}3AC:E*qa$BByA,C$qa&BBAYCHqbBBRAoOC؏BԢqjqBBZAǒC2mEɝqqHBBWAA C Eqx"BBACqyBB+A>zC:hC璺q}BB+A>zC:hC璺q~BBACmF{6qBBw)A3CqtBB A CvD/qBAA 'C뙩F=qBANA WCERqBAϒA NCHEqBBaAMC' q?BBAWCcE0(q@BBAWCcE0(qBBgA!OCEQ*qBBs A,CEqBBrA\C/aE@qwBBKA>OCyq|BBA'CqzBBAaA]CžDvKqBC A C+qBBA C:E}!q?BB 6A CKEq&BBAGCEYq'BBALDC4"D9q)BBAxAC q*BB|AP'CzDDSq+BB|ANC~KDpqfBB0AaCUBNq;BBA7CCg5qdBBAC F`qeBB_ACUE)qzBBaAjCE;<q{BBvAuDÎFq|BBvZAfkCPE=qZBB5uA:-CqBBGA$C)qOBB1A6JCi)E۞q1XBBAC\ErLq5`BB*A66CQEqV4BBZACD$q}BBb(AcvCSyEMq}BB84A(4CF'Dq4BB(ACDsKqCBBjACaE6q/BB}AC E>[q6BBmApC챃E#'qAMzC>VElqBBLACT{qBAACEWqBBuACAGqBBVA4CF'q(BB2AǷCPDMEq*vBAԔA-CFLq0RBB A5aCq8'BAAAD8]Fq:zBB)AOC'Dq:BAAɲCؒwEqFBBAnCtqQBB-$A CǂE"qf8BA ACqgBB$zA?OPqgBB+AC]D1:pqtBB:8ACwgE5qwBB4!A[\C!E$qx!BBeANCq$D \{qxBBPAMCE)*qxBAA7CEcqxBB AECˤ1@qxBB)nAHMCfqxBBAGECʖD*qxBB AD#C*B4q~aBAACIDq~jBBACDlqBBLA"BB@ADJCz}ENq+]BBKA0.DEq+aBB#AD$xEmq+~BB7ACݮfBq0 BB5A+CE:Iq1JBBA-C>Dޔq1KBB^ACCyE%q1PBBAĤCqDfq;BB2AD ťE3q<BBACdž&D0+q<BB֍AլC/ Dlq?SBBpAGC$Eq?TBB ACM|E.qFRBCmA~CиMDLqKkBBSACXDYqKlBB4AӥCE<qKmBBAC<'E[yqMBBնACCއD*q\lBC*ACDwq\BCA٥C(D^Cq]~BBPA?DE.q]BBALCE| aq^BBuAC]DԾLq^BB܇AVCˉ$E٬q^BB A$Cy*CNjqc"BB{A YCݜEKGqc%BBFAvRDGqc(BB;AC1-Dqc*BBATCFqwBBAcCEk\qwBBAD[E1mqwBBAշC$CY q}[BBAChE5q}BBA/CbEq~.BBA7tC@E<qBBAӷCE qBCALCثCskqBBAθCD{qGBBޙACٍADZqWBBAAC|E8:qlBBDACqDqBBA0qC~4E"j*q BBA:C E )qBBZACDqBB؛AaCF)E8$qBBA1CϮAFqBB%ACEqBBA^ CdDڈqBBխAd Cщ*D:qBBۓAD EqBB A`QDQEqϵBBACXD94q϶BB߇AC֐EPqϹBBAClD̊qѹBBA4CdFD\qѼBBACyEXqBBAn{CנE0XqBBAC2!E#Hq!BCA‰CBNqJBBAC1qKBBCAZCMQE9RqhBB#A[C3ExqӢBBA[D$EqӣBCACqӴBBACXDqBBADEoQgqBBlAVCVEqؒBBПAtCE&CqBBzA4>CеEyqBB|ASCE{qKBCA$C(EDqPBBAC?EYqqWBBCA)CBdC_HqXBBbAqVCE_EEAqBBADPD!qBBGA~C8DqBBRAmCA-CqBBPACiDbqBBǼA`"C[D`qBBhACL-FIqBBAC\E&qBBACEtq$oBBΏAACĜF}q/BBƂAcC_BUqVBBAYC~Nq`BBA_bCwDqx%BB_A\CELcq}rBBgACWEq}BBmAC$EIq}BBAsC[F}qBBACqEBBgAxCEՀqFBBGACE.qBB4A0CT{qBBACVC2rFqBBA;CDOCŰqBBSA}CcE'q(BB2AʋCіD`0qBBA3C|FkqBBEACNqؒBB֧AC KDqۑBBȍAhC]DqەBBAACDHqۙBBɡA>CEqۼBBA:C33qBBA=CD/LqBBAPDDFǍqBBɬAC֖~Eyq}BBACE4qBBҪAC%CIgqBB˜AǕCxDlqBBATEC±9DcqBB AGC+EPqBBcA0CFE:VqBBAĈEq/SrRBAb @C,Er-mBA`@LCO\r8)@YC/ND|ar0XB@M@&C^FBr4B@@gC2VELr4 B@;@ B_F&G r4.B@F @:C(cTr?B?(@!CX-rHB@@5Cs8EjrNB@l @N C9E" rYrB@@3C EhrYsB@X@^C6ȣE-rZ^B@@C94[EZ1r^B@m@+C1νE'.r^B@H@CEEE-wrttB@ 5@MACWE7rv"B@0@CX-r3B@\@xC9rB@F @:C(cTrB@e@rC? ExrB@N@)CHLErB@@ C.=qrB@d@C8ՄAhr B@@/C5\rB@8@5C.K$D'rB@>*@C.DhrB@@@C/DrB@D@Cz8FirB@>@wCIQC{rB@*c@C^V#Emr[B@8@5C.K$D'r\B@@C,ErۆB@i@ևC#E*~rB@@7C;E(r4B@8@5C.K$D'r`B@@C,D-rB?`A1"DEL8r rOB?@?#CDE>rB@KE@8CKrB?XA@DPC&zrB?ADPrB?ƸA/CE{*rB?AXCpE3rB?A~D>WElrB?A"D6 E\rB?؏@G1ACXD rB@@CWaE jrB@{@6C[D)EzrB@ )@*CZUEFJr+mB?[@:!CTgEr+nB?ۼ@CQ2F r5cB?ʋ@CM@EKr5dB?@CJEW&r>B@&;@CXD%r>B?@wCKFE r?B@G@CV C,<r?B?@C]G1ACXD rTB?2@0CHE2rgB?f@=CPRDorsB?ġ@|SCHE@(rv"B?@gCP@FJr7B@@BCbprSB?@CTlnDZL^rB?@wCHlF+!rB?@ CME`FrB?@mCLSDgÑr$B@0@CUCա=rB@@-CVWC,rB@-@s CYBEhrB?@$CUݲFȑB>aA@C@`CEʆ,r &nB?I@CWE(r 4B?y@YRCHE/ r ?B?Xo@WCQDXTr TB?[@hCE Er YiB>@WKCw!ES Jr YmB?G@EyCiRDag r YnB?/@*B@Eӥr YuB?.b@!XCI*E2r ^B?/@B{Elr ^B?@C%ESYr B>@Cu r B?|@@b8C>:EHr rB?$@Cd^Eb?Or SB?K@xCKeEMr B?@CDur B?]@ CKgFׇr B?X@C_E r B>I@9CgE Tr B>@?CE#r B?@CC9Er B?@5+C>#EBgr NB?_@CS8EyZr -B>@CshiD Gr gB?X@C_E r B?Dr@CWSEѢr B?2@sB۷(E?>r PB?H@ĈCREyr B>@>Cr T?B>@C;{E%r B>3@KCDnr B>d@0CDr UB>w@CDsaBDgA +B֒Ds RBDvAA B8RsBD,A BET2LsBD}A4BEV9sBDlA7BlBs2BDA$B6E|_s2BDhA+B̊*D}]s2BDA BTEs2BDMAtBĸ4DEs2BDgAB֊E_s2BDlAPBF8s2BDCA (B"E h8sPBDA %B3ODEmsQkBDz&ABB,EsMBD`AnB<sBDA BFsBDhA+B̊*D}]sBD ABQhsBDA BE0HsBDunARB4s_BD>A&BDحsjBDABC7sOBDpASBaEisBDA B"D#osBDAlBF@DLnsdBDdžA mBE3sfBDA QB'E4BsǯBDA ,BDA BEuDs ,?BCAC%5EC48s -BC[An~B{s 2BCACPEOs 2BCKAoBDVs 2BCABs CBC?ACCIEs CBD wAiB؜DTs DBD*A?B,E$s DBDAtB;DE%s EBCA8B&Ds MBCAQKC*s \BCAC6s _BD zA^CCms _BD $A^Cls _BCiAQC)"C s _BD CArCПDOs bBCACNE,os bBCxA|.CߺE7{s bBC=AJoCk,Es eBCA eMC`GEð{s eBC@AoC~F"s gPBC+A OCBCJs gQBCACjD%s kYBCԼA5~B߇7Cs BD IAz,Es,>BCABws-BCpA|C`F's-BC٭AcCEvs-BCoAkBJaEs2BDA?BVs2BC'AvBs2BCAGC|-EXsCBCnA B~wsEBCQA\B5E)sMlBC̴AaqCP'xFd^sMBCA}CT(Es\BCA CWC8s_?BCRA0CB5E3os_ABCyAGCN/Ds_MBCՋADCVyyEt٧s_OBCΖAGCEbs_RBC>AC9ZD s_TBCA*CvEzseBCfA]CEOc7skYBCABQDs~|BC.A`B sBCA BoEsBC0ABpE2sBC٪ABٳDIs*BCA@C1F xsBC}AvB߯E'sNBCA)tBEz]sBCABDsBCA>BEZsBCA9BsBC٪ABٳDIsOBCA2CEEeVsQBCABQDsBCACCZE =sBC@ACvEsBCAC/"E&hsBCAC+EWsBCdACn\E[sBCABEhks%BCdABa@D}5<sBDABܦE>_sBD/dA.2BٙBMs BDטAB8Rs RBDXAjBJ~Es BDVAmBFsBD;ABǮsgBC AB/F<5s4BDAB˶E s# BCAQKC*s#}BD AfC$ HEs$8BD*A%B Fs$BD0:ABܳTE qs23BD[PABzE"Ys24BDKAuBѭD{ms25BDL:AkEBEs2>BDAUBɠDs2BD8A oMB7s<'BDAGB>E=Vs<(BD2A^BD#s<*BDANCK{FsLBDIA$tCtEdsLBCASB[s]BDTAR1BSF8s]BD3A:KBEJs]BD,AC*Es`xBDAږB-!E s`yBDuAyB\E es`zBDABFsaBDABڕ}DscBD$A'BUcEwsrBDAABFYsrBDeAE;Bһ EsrBDAA [\B/EZ1stBD1"A7BsDĤstBD-YA|C kE|stBDA`BȹEMstBDTABF osBDAB3E;sBD AߗB E)osBD2AB=qsBD /AY*BLsMBDNACB/E wsBDlAuBkDAsBDXA}BC-ECdsBDXA}BC-ECdsOBDՅAz:BEMuQsPBDYAAFlF.s>BDSAVBgEZgs?BDFiABsBDAzBڳ3sBD}AcBs_BD"-AC E=#sBDPAHB \CЉ]sBD6IABDysǯBDA .IBeC*sȢBD-(AuBsȧBD,SAPBϥ*DXsȻBD6{A73BzsBE.A] B㉲DsBDAsFBHDXsBDABBlEsBD+Ao%BD%tsBDE]^tbBBPA~CGCGtpBBFAhCaCYBtBAY'AD+ڣF>tBA+AvD-=HF<tBA4ADApD56lD6UtկBAA bDqt B@^PA`DDt :B@yA/D/t G}B@3AD F bt `7B@TA DERt `@B@U AfD YEt `AB@VAޗD]E1t B@UAxDǥDlt VB@Y AxD@ t XB@YADMCt B@TADp'EVt B@VUAQDEpEt 1BAD6FɊt ?BAaA CSiEșt EpBA_rA D6FK̃t EtBA)YA 5=D EFt EB@UA )D4CTt EB@.A OD+E6VKt ^)B@%A r7D$8E36t `BApAV_DJt cB@&A D*bEQt B@:A D7$Dut B@A 'D!MExt B@A kD#aEt 6BAHA _DEƉ"t 7BA-A 7D,Et *BAoA D 5FJ[t B@d A wD,̥E9t B@p+A TD%xDt B@BA ĎD2;E2t B@=A |D4fE9%t B@\1A D+EW?t B@A 7QD/6EUt B@yA UD-Eewt QBAgA ZD Fe t {B@MA 1LD/lE_wt B@:A D7$Dut BAbA MC~C/t ȕB@A ,@D!lEͼt ȖB@6A $D&F zt B@ A f}D'_E+t 6B@$A D:Fkt ̤B@A D&`F%t B@A :D-Jot B@A ,D2xDQ]t B@$A ND<2.D2t B?}A 6DEx<t xB?A D4 vD{t B?A D'Dm?>t bB>r;AC}AyCGCګt B?|zA qDt B?A CD*HFIt "B@8A D;)Dt ?^B?A6 D]]Et IB>rACElt QcB>rXACEqt YB?*A uD,&Et ~B@1A D;ңEphpt ~B?A D4F>bt B?A {D5?Eęt vB?oA DHF# t wB?A WD5Fct FB>|AC|t bB>ftALCCt oB?;A D Et pB?QA !/DaE+t B?TA D.X]Et >B?A -D=CZt 'B?A WD5Fct -B>rACI2E?t B?ADOt B?A :DT[Ft B?AmDYfE(ut B?$A D3Cft ̐B>rACElt B?A a7DJaE#t B?qAdD[(E\-t pB?AtHDY ?E-t qB?dADOC:mt rB?KADXKEt uB?xA D3t EB?A QD2Et B>}AyCGCګt B@GA wD0F<t B?FA D#pF4lt B@*|A ܺDCB?A DAot B>mAD %Dt`+B>AD CPSt`,B>AD^tQBAA ICE½tSBAA `CjE4t!)BAA D*=t(BAA `CjE4t1BAA qDt7BAlDA DL1F thA~SDT9Fu_B>Av-DEӵuB? A6D 5E uB>Az{DnDu oB>ȊA߉DAGCF`u JB>A:LDdFAu KB>ACF`Mdu LB?IA BChuB?'APCQEuhB>AC\E>ueB>cAYlDP dF gunB?4PA%$CDuB>ADPA uB?$ACnD [^uB>2ACquB>A9D0FS uB>ANCܳD0uB>uA CTE`uB?,ACEuB?$AC EouB> A߶DKQF~'uB?8ACyEyuB>A]hC!FMuB@0A"CD hF5u!B?GXAMDC٠)E )u!B?AAD>4Equ!B?EcA,CJE{u"'B?BAD.*E&tu(B>A$DD-fFI u(B>Aj(D)F1u(B>.AՊDC9-u1B>A>D\FHu1B> ArDEMu6`B?AZD:E[1u>B@ZA"*DP>DCu?DB>ACOCuD B?BAZCPEHuG{B>pAND+D=uG|B>AD ۨEYuLB?FAguCE)u[B@BZA"9@D !]DcN{u_B?FA@CE( u_B?DXAC6*Eu_B?<A}DEu`B?(A0CrE&u`B? ACEC)u` B?3AߤD#E.u`FB>AD'0E1u`GB>A+D,sD˽ub{B>A{D!Ful-B?uAoDFzuu*B>AED cDU"uu.B>AؘDnE`uYB>˽ADEuZB>hADE{uB>/A']D* FBuB>A?D D]uB?D\AMCѐETuB?8mAKiC9*E;uB?AD2KFu B>ABDuB?AEwD:uB>ACDFYCuB? A"KDHE0\uB?AdKDduB>AD"nFCuB?AeTDuB>xAzDD@F3uB>AAlD'FDuBB>ArCnEZ]uǿB>AnD*F^NuB>bAD*EayuB>\ACuB>yA1C6uYB?ACl|E84uZB? A/C|oEDYyu\B?ArCEueB>A{BDD^ujB? AC&EA~ukB?ACйC,duB>ݹALD$-uB>|A&D5=CJuB>۱AYDGWFPuB?ATCEMuB>ArC]SEGuB>kACEWuB>#APC;E uB>ACLDquB>aAPCو@EguB?A`EDE1iuNB>̚ADB9MFu7B?AEwD:uB?.uAC9ENsuB?"8AC9E2uB?5AC5BExluB?<ATC9BDuB?5A-C^ D\EuB?EA3CûEuB?@'AGDEuB?CA2DEnuB@=AKCtEVu'B?A]8DsdEk"u(B@cAiDFu1B@ACD @EJ7uYB@VA"D E hu >B@A!DuGE u DB@A"D G$E Kcu FB@kA" D0DQu G}B@ AAD .u `9BA' Au)D:u `B@{A!CEO^u ,BAWAҡDfuYB@'wADEu#B?AeD EubB@AD(Fu"B@AZdD ou6]B@ATDaFB3utB@>kAxD FEnjutB@AADhEJutB@1ARD{bEuB@TAېDCu B?A@D CCSutBAVADuB@FAD=EeH~uB@TAېDCuBA'ADN5FjuyBA'AD.VuBAA#xCᢏDuBAA"%CEu,BA+Ab}D*(FuBAA0D AE,uBAHADEuBAAhDGu BA ADECEu BA:A '+D lFeuBBA5DCuBB$'AD ?DuBB:ARDN-EVmyuBA2A D C9pu >BAA0 CDR$Ou BAA@FC_Du %BAA C1u ZBAJA%CEu `BA[/ADEu eBAAAcBbZF u /BAnA!QDFDŽu BAdA (C_FBu BBAD !+Eu BB0ADKBFuBAYAC8F'5u;BAUA DiF%uAD DduBAFA!tDDnpu5BAAcDT{uaBBAcCjuBBB(ASDBfDuB@A!DP8Eh6uIBA>A!ACRF4ӸuBAAtD,jIDuBAiA 8rC^uwBBAԧD&49u IBAAD5&%u"BA!A!sgCDzF=ru"BBA|,CiF4%u(BAA!|CND<u)`BAAheD9F+u/BBuqA#YFCDu4BAy A9DDuBAACoKFD6u>BAnA!hzCEu>BAA!L{D-}Fu@BA(Ab]D-Cu@BA/A"U CCKu@BAA!D:?FbOuDB@'A"FD EzuDB@`A" DXuFB@ܛA!D!_DuFB@A!rD_4Ex5uU+BAA!C@EluUnBAAD%$FuVCBAA!BC~EuZ?BA2A'= CuZLBAA!sCEebuZBA=A!fDEu_BAA!CVCku_BBMKA"DDxu`9BA'AD.Vu`BB@A0DIXuaBA%A DiEUubeBAA!6CEEbudjBAA!bRCBupBAA!ID+Dus BAA8DF7usBA%A!DD0utQBBA"CFTNuuBAkA"D2mu BAA!95D7Fuu BAnrA!<1D5FV.uLBA}DA +vD k?FouIBBFA'D1EAuLBBAdDUF]uOBB ABCٟF.uQBAAC VFmuRBAADB5F6u^BB&A#4C.C;uaBAA! DF=fueBAA CHF<ufBAA [OÞFQuBBA7D2uBBACFOZu BB7AD)#u!BB!0AD@7uXBBAI)DFuYBB%{AD4EbuBAA!DlDdԌuB@PA!pD8tF 'CuiBAmA"B$D*E=ukBAA!CsE#6u^BAA!#D/FueBA{A {[DZxIFMfuSBBgA!~CF8uBAoA DhDuZuBA#A(vDDu BAA DEDuRBAA3XDEuZBAA\DWDXuhBAA7D9E#u@BBI*A"weD"ER`urBBA!'D|FV(uYBAADD|buIB@WA!D!EnuABB62A!D',E0?uYBB!uA!kD5D'uuȭBB"1AhDSXEZ$u#BAA"D,Eu$BAA B D F#u&BAA!BBA!ClFHu?BAA" D SF)OYu;BAkADDuBAA'D!٬Fmh uB@A!.D @fF ouڞBAA!CVF +uڟBAA"?BDly@FEuBBpeA#?rD=uBAҤA# PCDFpuBAZAD$qEF"Xu2BAfA)D EZu4BAADsF(uBA%A!'DaEDuBAADNFnuBAA!bfC@ u BAXA>DE+uBAGA#:,CEtluBAA> DDFu BAA`DuzBAAgD fREZ8ubBAAD)ufBApAnDuiBAA!D BusBAqA`DuBAn8A!OCBD/buBAA D Eu,BAA8D2CqF6uBAXAɰDD uBAADC<uBAADLju BAѺA 6DGEMDuBA;A 5C Eu >BAAmCEMu QBAA)DE8`u BAAD)E`eyu~jFYuBA3A C*EuBAf"A UCQEu -BAADE}u /BAAD@DKu"#BAqAWC2E)2u"BAmA5!DE u)`BAApDlu>BA;AhbCiEz'u>BAA6C:D)u>BA}AwDDTu>BAA7C3F'*u>BAuA `CQ%Fu>BAviA HC?EauDBA[A(DЃuUnBAdzATDEKHuZLBAA .D FUXu_BAAD=u_BAA!smCE@?u_BAA&VD-$FauaBAÐA lD$ŔE*?udABAA!q}CE%udjBAA!l*ClEdudBAAcDT{us BAADBr{F2muvzBAVADHuvBAAH}D]3DuxYBAgAsDCAux^BAA$D,EsuBAQA!=D{uBA~AWDbEruBA)ARDAF uBAADfCxu BA` A!^\C/DhuLBAA~DcuuNBAAcDT{uQBAGA+DBkluRBAA%D\FuaBAA!C!ueBAA!]dCE~ufBATA!^CuBAsvACrZFuBA,AtD.Bu^BANA!%ChCuBAA:C+E?uBAAWD#.Eu BAABD.hE'uRBAA"D,EmuZBAA8TC4Eű0uhBAADGEurBA,A !DaHuYBAhADhtDGHu:BAгA LD Dhu(BAPAD5C9 uBANAOD |EpCuψBAADE[EuЀBAi]A CFELuӳBAA֟DzCQuӶBAAڄDZtDuBAGANCCEuBAAVC QE$VuBAȕAM@D_FVu֤BAAMCDu֧BA0ACݎD܅uBAAxD[TWFQu2BA#ADFEǭu4BAiA6DYNFHuBAAAPDՅEעuBAACD{uBA7AJ(CiE2uBAAuC+ENuBAAC&EOK*uBA'AҷC;DWuBAA!a9C@Dp!uBAtiArnD0FueBA0ASeD|EeuBAACDDuBAA$PD cJEuzBA1A 8 CFu|BAAuCDE[suUBAAD $E[CuWBAACDXuXBAA$NC͸E@ueBAA$CȶF9ufBAA"DF5uiBAQA[`D"7FuqBA"A%Cfu/BAAkDDO/usBAADDuBAA CWF=tuBAA"CCuBAYA%D&F}uB>iA VC,Fu_B>AGD,VF|Uu ,B=AC´Bu IB>0ADN%A!#u B>ACxBDu B>ADWD+'muhB>AC'musB>A D=pEuB>BARCEuB>A:^CݶEG5uB>A^ C扺@ muB>AVtCOF>u'B=/A@CE_uB> AYCNWFuB>AD >D'uB>ٌA6C*FFwu(B>A+D3u(B>AC;F0uG{B>ADcE<uG|B>AgCF{du`'B>/ArcCEtJqu`+B>/AzCFEd*u`,B>A^aCEub{B>VAD:uB>8AoDeuB>ACsD"u2B>A9C;Fgxu3B>pAGCQQCP>uB>AD&JuB>AC CAuBB>kAlDEuCB>ADAAuǿB?A8DdJuB>ѽAVDBEluB>wA4C-RE;uB>oAG%D^EmuB>AyD DueB>ALDUE=7uB>߲ATCe:EzuB>A.hCHyEZuB>AQCȮlEuB>ŦADF uNB>A EDNuUB=A~CjumB>zA%C̒DTAvWB>A$ND4xBC9AXBTDH!xUBCA40B2E()xgBCABcTx5BCxA dBl E4ďxMBChAuBxEwxBC(A BoF_yx$TBCf#ABDUsx%gBC(AJBpE\Ix-BCƕAB*D@x2xBCABFex2BCAGBEx2BCABE x?BCgA+B`E5x?BC^A{BELx?BCLDATyCkxE{BCAB|D!xHBC\@AoC5xTBEϭxnBCgAɏBuD(xBCSACx5BCfA\BE2xeBCEKA$B=x/BCXAKBDD4x0BCwA5BEx;BC:ABƶ:DCxAB=ExbBCJA,BԷCBxBC6AuBGEX@xBC A'CE ;xBC0CABzlE.xBCAEaBDx'BBKACxBCdABVoExBBARCHlEɅxBC5AɞBE xBC_A<,BE0x)BBA4CD~Vx`BC6ABP>EYxeBC$&AdBgEx/BCGA6VBDx0BC4ABGErxԣBC7APBExBCSAB쓓DQxBCN@AaBA@D7TxBCL:ASBxBC:AuBfExBCFA-DB)x BBމAvCHE-DxBBҭAXCEmxBB=A C Dx4CؕEdx,BBAxWSBCA kCٟEU\xWUBCA ^qCEox_>BCAƋChx`fBCA 1YCтCx`oBCA C[6{F"x`pBCA вC0Eox`qBCHA CnEhYxeBCA (CE7qxeBCA ECxgPBC#A pC{1yECxgQBC A C'hD}xoBCA {CE(xo BCEA JC,Fx,BCA >CF(8nx1BCA 8CyD1x?BCSA WiCGFZx@BCA 'CD+7xBCOA `SCREVx(BCZA =CchF,x,BCA xCEeJxCBC{A pnCExDBCVA TCER)xEBCdA C!:FRx5BCA CKx6BCA ECvE xzBCZA 6CDA xPBCyA ƙCSEx4BCA _CyxBCA ڙCDxBC#A SCEhxuBCoA tC߲Cjx5BC\A OaCoEktx7BC6A CEGxӒBCA =CEKTxӓBCA C\Fx|BCA CAxBCA *CFxBC*Af$Ci/D3xBCõAJCA\x BCA%Ci E?x BCA5ClExBCACzbDXWxBCA CXRx%gBC>A&kB%`x2BCAtCr/E>Q[xMlBCA A ~,Cx{xgPBC}A hCesExgQBC7A n)C`E$x~)BCoAC}qx~+BCeAC]wD|xqBCA C/FxBCNACRxBCA U-CW?DPxEBCLA #)CEDxӒBCeA MCV-D%xӓBCACE nxBC\A(Cj\xBC\AC%D xBC7A n)C`E$xBCA u"Cl-D^5x BCA XCL"%D-[xBApAV_DJx >BB @M^}F@xBBA=D²XFwxBB@QnC9cDxBBAߝCEExBBh[@C *DOxBBk@C YJD 8xBB-AC2Ex"FBBA-9BczEx"JBBACHEEx"MBBAhC xoDxABB@ADtBzE7sxLBBA}2CR-xOKBBt!A CvENtx^BBn@C OF04x^BB| A3C3D4xsBBOY@ C \DxxBB @L!CZF 1xBAoA;DIqxBBAqC)x BB~@C%\F`x BB\@wC EAxBBwAnC EȐx0BBK@ CEAx4BB@oB:+E{x'BBRAmC zDFNxBB>@:BCx,BBAC 9Ecx~BBAYu@WExʃBB@vC@F|xEBBO@QC 1DxDBBYAC EܷxbBB@OdBIgE9xBBy@CcEkdx+BBOF@C E+bxVBBZ@`C DixBA"@D+"dExBB@ƧC1HEZxeBAa@0CPapDxBAǣ@>DEE*qxBA@$CUDYxBA[@CDyxEBB$E@mC{ Ei<xBAk@DreEx+BB,@9C{EU xZBB7@CE[xBB @4BCx BAACDVx BAю@CTѭE5x BA@D 1F9Xx BB 3@%SC@D*x BA@4C8Rx BA9@2C!E x +lBA@SC}EUx -mBA^@[CDF px -{BAz@FCE x 5iBAAIEDLDYx ABA@CŸEwx GBAǿ@8C[&E{x G`BA@rCtEx GBA/@&C;Ex LBAͰ@C"^D>x b?BA@CVDsJx bBBĀ@CEsx bMBA@C"Egx bNBA@zC=ʻE)x bVBAP@KCDx bWBA@5CZE7x bZBA@C\rFk+x b_BA@[2CbDix baBA@pC(+Ex bbBA@]4CvEnx bnBA"@D+"dEx bqBA"@D+"dEx byBA@GCkDXx b}BA @dD$JEw7x bBA:@ݖD-IEF+x bBAA@dCDwx bBA@CEJ@x bBAc@SC'Elx cIBA@(Cux cJBA@YCDМx sBA@CvsDx sBA@MCjEx ~BAK@TC ^Ex BAB@#Cx BA@uC,Dcx $BA6@I;C{F1Xx %BA @fC}E}Yx "BA@CvEBx BA@IDfx eBA@C)jDx lBAͩ@aC]CئDx BA@lCxb2E qx BAB@#Cx BA@NCx BA?@C6LRD]x yBA@dCEB7x BA@C{XF0Tx BA@CEyx BA@yC|>E]xSnBC[A7CtD >BBZA73C@E#>BCA7wC#E#@BC A7WCEKABC SA7YiCCGAuBBmA81C/=EHTBBA7n CEGVxBBA80CD4XBBsA7=CEڒXBBA8/8CȪQEVXBB/A7xCE\eXBBwA7C4DOUXBCXBCLA7rCBHEXBCwA7CݛFXBC{A7d*CءbFbgBC.A7k+CEuxPgBCL#A7aCE(#kgBC qA7VVC_>C߬gBC=(A7uCFuagBBA7CVD_gBBA7CCX>hBCA7PChxsBBaA8*CɷTEGxtBBA7BC#EBBA7CxEB=*BBA7C Ci,BC %A7qJCIE.BBA7CiE/BBA7uC>Eb6BC?A7w+CEABCFA7|uC&EoBBC3$A7p:CPE{DBC+A7kTCF~EBBA7C>EfFBBA7C&Ej5uBCLA7mCpF5@mvBC:A77CF BCyA7wCD/BC^A7~CVE5BC9A7s%CCE+~7BCl'A7C3|E8 BBA8BUCʴE BB)BAA0̸Ck'Fq.>*BAFA/dC$]E, ?XBAdA2?ZC:FZ`EBA8A3D"F2/EBA}A1D4D0EBA:A3}qDFDdH^BAGA/WJC킵EDeUoBA"6A48:C}E r.VBA#A/CFS>V%BAA1 D1EXBAEMA31DE[BAA0`CEw_8BAA+`C@C!dBA$DA4DE/*kBAF\A3yCAjDYGxBAIBA2DxE=!BAA+DD8BAWA2CmoE|HBAzA1vCҹE<BA4A3D#EBAA+BAA.3CQEBA6A/CE$zABAA.YwCvF` [BA@&A338C{BAZcA2PDF>BAA,CF]?BAA,e]DF,)BAqA+!DLEV$ ١BArA/oCDI(BAyA1CFJBAA/[#CFLBAA.$C.RE^BAA1W]CE3BAmA)C=Ck>BAA%CF#BAhA+ADF2oBAQA-@C`E6 BAA,rDF6hGBA/A#D6EBA4A#ZD LE7E BAA% C EI  BAxA.L-CE_l BA:A-ECz[DT BAA-LCfEd$ 6BAA(^D,D*RBA1A*C)FfBAOA/(yC Ev>VBAA/VC$BB`A#D qGBAjA"$C3ZBA]A0PCyZFC/oBAA,DE yBAA&7CD <!EF)BAA0^C_FT*>*BAJA/tCfEk?XBALA2DŁE;?BA&A$KCΗEϔ@}BA}A$D F {@BAA#DKGFVq@BAA"@`D ]F:BsBAA%FC3E DBA,A(iCwUF0o EBA2cA3lDErEBBA#pDhCtEBA1A38CdDtH^BAsA.{C6F]zTBAA$CwEUqBA/A3)C7EVBA A.hC`De$XBA]A2iCoD}YBAA$iC-LE{YBAA$G4DfL F<Z=BA2A'= CZ>BAA)CוFnoZ?BAA(uBFZHBAA$YDq FUwjZLBAA!D/2F쉤ZPBAD."E2MfBAA#ICمE0BAA(C&dFDBAlA"CLFBABAA/^CBAA,(D F6c?BAA,PݡA?JD]CB?IA= ~DcC7Fh>B?TA;aD$'F]k=+B?[A= DD"B?uA< D'rfA|l B?A;C4F \WB>AA?4D{B?uABA?DsDD89>GB>5A4DȡEB7?B?5A; Da0G;sL?B>۷A?fDjB9DB>)A5D;qUFiXB?zzAA;>D?XB?iA;wGD,EWn[4B?6A:4D[FK8]B?EA=aD;FGUhB?@6A76D LhB?A6ZD!PF" hB?A7qC$F hB?Y`A7DEhB?xIA7hjDCEsh B? A;D!mEh!B? A; D&oA;ՓD4hCh2B>A;sD7<)heB?&DACFhkB?#AA>P2DUFtjB?_A5wD(jB>&A>-DNCb0oqB?fA7,Dc| B?EA6ICMF| B?"A7DzLF"}B>A>DEIF *B?=lA=DcUF/GB? A9ODF9B?,A8D8mXE} B>oA?oDB6B>ˏA=EDz?FuB?QWA>D1B>:A?DB?bA9 D[7F<B?A< _D GQ%B?A8TD-FEB=B?FA9DFB?4A7DָB>A?õDǬB>A>D+F&(&SB?@A:,;DFx fB?A9a:D6fCljB?A9DV F$EB?A8PD/Ei{B>"A> D*F[`F!B>A=Dd'F-B?"A>nDaGF:7qB? B?6AED=FG`7B?vFAEXD`[EWoB?AEAtD>EGaB?kAMgDFI} B?AEDr@B?$AE@UDۏEB?dAEKFD; E;B?9lAJKDF CXrB?"AKAFPDy+FN7XB?4AD|D)VG5iXB?FAGZQD\XB?7ADD ۥEX%XB?\MAN(C܊YB?{ADDCFh]jB?L"AGD>Eڙ]lB?PAG?CqEcb1B?P'AIDbB?7ABkDG mftAB{Dp ,F^-hB?k~AM,CEtkB?;AHBFpskB?mAMCɄ_FtB>ALpYDE˝B?K6AF D >EY)B?~%AEDEvB?lAGDܾF'B?vAEDC?B?OAILA?3D.B?HsAF]B?WAOvCtJE©lB?UAOHC6EsB?YANCE-B?JAP_C9pEH6B?WgAOCK%D#B?X8AO-CLEdB?IAGD=Ff~@¯B?8AED7FRjȠB?AEDqDr+IB? ,AEDDc?7B?sAJ>D5-E-Q9B?AJD>E0iB>*ALDEX8'B?GAEDؤ B?6DAE:CRcFRcB?aAEQD?+KF DkB?#AECD*TF",>B?>,AH=D"FkMB?'ADCEB?IAG$DaFkTxB?6AEsDB?MAOC5EuB?AEDdC'B?jA@7DD6GxgDB?P*ABSCcFB?bA?uDMF B?wA@7CJEǓB?aLA=9D'^E*'B?5*A<Dg{G UEB?'A9!DFE3B? AEDQ*D2xoB?0AE~DsDo<D=E~B?aA=-D8$FD  B?2ZABsD&CF”"B?IA?}D$E%B>qA?FDPC5X"B?lA:D5CEq.B?3xABCځ4D_61cB>ǻA>RD`D}Y1eB?rA=5DˌE!:B?A>DDFw:B?97ABEC>Ee:B?@ADDaFxl?B>چA?mD& C^I@TB?:AAmC'@]B?kA@iDFY@_B?=AAC燹EsDB>ߟA?iDmC DB?9AAY ChC DB?96ABvC"FPDB?_A@3C<)DB?v@A@C$EJB?#ACD2n`FUB?<\AA Cߢ$F+aUB?fA@QCqFV B?AADCm D!eXB?_A<#DBF9XB?A@6CQE|XFB?+\ABpD\!GXGB?/ACX?CDɕXIB?DcA@C覊EzN)XJB?uGA=D&.E+XB?4ACCѨpFXaXB?1 ACDEtXB?6(AB>C+F( XB?ACCEcXB?A;ZD 1E\$XB?JCA=rcDZ:FR]XB?A;LD!E|Y B?IA?C(DYB?JA?EDE,YB?|A@ ECD[B?pA=D%~D-h$B?AABDDTSFgh{D1 DhkB?1)A=hD/C_1hoB?lA>ND)C hqB?[A?D,[EhrB?pA?rCDgNhwB?V%A?D5F$#hyB?KA?WD F)}hhB?9DAB>XC&EBhB?5ABCJjB>A>DaF'zkB?uuA>jCF?3wZB>VA?D(*B?DA=DgFB? >AEڀD ?bB?IA?cD E}B?A?Do/FN>B?EA@CEo+B?A;nDrF!p B?AA=tDN]aF B?lA:fD/AAyCB $NIB?HABܫDH'Fx%B?"A>DtF2&B?J A=D/EΘ'B?A@E&;G0{'(B?n9A]A?"DB}B?&ABEDGB>A> DF` B>3A0,DQJF lB=A2DPpEU1 qB>ZA+rDGYD] mB>(A/DC8E^  tB?!dA5D F=& B?sA2DмGR B=A4PD`EJ B= A3jCp~F?.@ AB=NA4BDP؇E B?KA6CD+Fm B> A2DaFWD B?QA6|D~GF/x 2B>BA1oOP bB>RA1ՉOP #B?%A6DcF )B>A*KD6F` ,B>xA*-'DQFMΘ .B>gA?Dx /6B>A(D[CU 5 B>A4+IDFeI 7B?A5v:DKaE{ 8CB>yA?HD|BF_ :;B=pA3DhE :B?A6SDb'Eƀ :B?EA6D?"1E  <8B?ĈA6TDGB>A42DEEC ??B>ӬA5y=D/F ?AB>A5ْDF ?FB>A?DcC%  B*B>A/rD;E DB>(A0+DVS~Fs DB>A6DFl DB>A0[fDPĪF` DB>A4(DĘF J`B=A39QDWE" X{B?(A7DYYE XB?E*A5 D· ZmB>2A?lDC Z{B>A+NDI7 [:B=A2kDd9EwF [;B=A1}D0C; [KA1TCxF1ٺ [=B>:A1֫D+El4 [>B>QA1D.Dm- [?B>QA1D.Dm- [AB=A2wDA&ME [BB=A1D39D [DB=A2DR_ER [JB>HA1xOP [KB>bA2D!wFC d^B=A48D\E/ hB?anA7HDzR5Fz hB?A6ZD* hB>*A?hDO B= jB?_ A6yDFv jB?A6DF}bEqE lB>A)iDJ omB=.A30DZFgR oqB?.A6mCF{T osB?A6,DF< uB>A/D)F Z x,B=KA2D= F( yB?cA6fD& | B?RoA6C$F B>DbA2{DF  B=A1nD1C& B?HAA5 DD< B>NA2VD'aE QB>5A4DȡEB7 RB>)A5 D#E  B>?A1mOP B?@1A5D!E  2B?=A5q6DDɐ 3B>A7:D8~Ec; 4B>A5JDE B>qA0DONES B>rA0էDOkEϧV B>A0DW3E B>ktA0DQREK$ B>UlA1DNE3  B>u;A0DSfEIj B>IA1fD>F t B>A0 DV&F+ B>rZA3@D%-FF B>yA3,fDE& B>'A3D!Ff B>A.lD6FsM! .B>&A(hD] PB=A4ZD_Eu hB>@A1lOP B?A7cDVQFJ B>DA1qOP B?A6D\E&qz B?VA6}\DB"FD5O B?7A6Dz!D B?KA6D FN B=hA3:D-EV JB>;A9nmDfGXb B>A-D=E3 B=A48*Dv EX B=A40eDE} B=A4KDg;IE ݱB>RA0_CG3 jB>bA4D FTg `B>cA3D HF7 B?)A6D*SFl B?iA6D*\E, B?A6OD(aH nB=A3dDf 2B>A)DP]KC B?wA6KD."D = B?AKrD EnR XnB?AEDr@ 9B?ALVDBAbjA;C9F3BBA8HCEBBfA8`CwEy BA4A;~C F; BAe^A:D`~F> BAnBA;ڔCވE:  BAxA<#CŇE BAOA; CD: BB%A9rwDoAEtBBAA/B@jA5\C#EՎ7?BAA;#KC?BAA;SC)?BAA;NCEK^?B@A5CC=F!T?BAkA53CE?B@`A5C$iEAuBBuZA8CڡDtEB/BA:A9|rC'FEBA=A4WCĪEF%B@)A5FCDHIZBA^A;wCpECIBA]A;cCEgLB@A6D)LB@A5DCUEX=UoB@A5)CDF_UpB@^A5ECJFUqBA;A43CFBVxBBA89CWBATA9pC)EyWBAA9ZC lE XBBOA9CFXBB3A8)C!F7OXBB)EA9jC^F0XBACA3CՅE HxYBA|A<CD'\[BBCCA9(rCwF ϻ\BApA9Ct>DFfa+BAv1A<8CwXCǼcvB@(A6&C#EdBAA9~CgEdBAA9|C}FfdB@A61C7vDFdB@A6*nC{dBAA4EDO1C hBA%A;ͱD LEvhBA'A4CRhBAA:=xCERhBAG%A9CthE(hBBA9sC.F"iBAAC+E0yBA5A:QC*E]JyBAHA:yDE:y"BA9 A;D#F3E% y#BA$A; D1XEִzBA5A;CmE{||BAֺA9yCFBAZeA9CpB,BAmA;C`EFB@=A7rCWAaBB8A9:&C0F _1BAA<>CET53BA}A<*-C܆XEz5BAuHA<CDثrBA0AAI F6BAHsA;C&E(%BA6A;CE\BAHA;vD6E2BASvA:D #E>BAJvA:UDCElBA1A3DE$BAwACQnD>dDBA?}A: CvEEBA9\A:_C6EFBAWA9lD/]E8jBAT(A:C3)E)BALMA:4CCwwBAYeA:CF"]BA1A;XCEk_BAA;CtEBAw!A<CGDT#BAIC[DW8B@A9C DLB@fA;HCFB@A;CcEQB@A;CEB@A;HCLDFB@A9YC)D6OB@CA9dCRC|;BA A;BC E. #B@)A;vjCRFlB@A9"CE qB@VhA46CzE7 B@G{A5~DuD B@JA6DWD  B@>A6CȘDޛ B@&A6~~CEw' B@pA6ɗC rE B@A6D7EG B@fA7;D5AmE B@WA7^D` B@J"A6kDE B@FA5C<E)|7 B@A5yWDFb B@TA7D $D. 'B@A6.CSeEֺ B@A8 =CpDu B@A8Cw`Eb B@5A8C(B> %#B@_A41DEIL (B@mA6DdCE 9B@A79D EB 9B@A7?D;D (B@A6HD&oE߻ >/B@A6x'D&5 E& ?B@yA6zC5EQ ?B@A5wC^HE ?B@FA5pDE6D ?B@)A6VD(E$.k ?B>ۑA?'D޸ ?B@qA6DwE4l  ?B@jA7VwDcEH= ERB@A7DD $ EBA&LA4C# EB@FA6RCq#E/ EB@F A52DħE'o LB@A7-DEE LB@DA6TD/E LB@9A6Z#D=;E UpB@"A6CE ڣ UqB@A6ACeEC UB@A7S_DnbE9x VB@1A8DxE XtB@A6!DE YB@]A7ClE) a+B?ΊA6!D"  bB@ʀA7CC6D cvB@A6C%C,g d B@֩A7bC(Dz dB@A7CS?$ dB@8A6D8E dB@A6[D E7A dB@|A5UCFfq dB@zA7$mC$TEǙ7 dB@A7/Cc?LE~ dB@GA7CEq| dB@A6CEh[ dB@A6CFEKN dB@A6DەE f?B@KtA6DE)> fBB@HwA6C Ea fCB@GA6DFD PE)J fDB@A7CV@ fGB@FA5D@D5i g B@9A68CP hB@A6DGDh h.B@YwA7vD  h8B@YA7oD\OD6 iB@A6_xDDS' i B@kA7#D6f i!B@A6C6DUF i"B@3A6rDQC>* j}B@F|A5DDk kB@A5JCGE@j B@,A8}aCVSElo B@E[ B@\A4yDCLƿ KB@A8D+ NB@`oA4iDB HB@A7C2Et? ZB@A76C&E9 B@A6XD)_E5 B@:A6HD?E5 B@A6}]CEBt B@A6DnEGd wB@.A8-CpD5* =B@WA7eD Eg B@_A7bD)E0i B@YA7DD)@ B@_A7CEdB 4B@mA5D %ET mB@YPA7D ME n oB@WA7K D Ed pB@UA7=fD 3SD-B>2sAPC8_GB>QA$PD-FFAh oB>?ADVFy ,B=tAZCē B=AsC B=A/CƹGEs IB>iA# DUF(m\ KB>AC՘E B>A$LD:ME1B>|rA D+GE=ZB>nAD:t2E)B>&A&D|E0B>vAGDOE?B=A$3D;EhB>A\C|FB=6A#D F-2B>ACBͼB>A&CD_VB>zAFCoEҫ3B>A5gD/'B>A$zD7D10#B>0A&D^@3B=ACfE 3B=MAtC3AV5B=A$KcD4 E5B=A$FD; mE>fB>PA&ZDE?DB>iAD3-E\AB=ZA#DpD$ZB=@Au%CAHZB=A$G(D4E\gZB>A$IYD=ԼEuZB>)A$D9ijEWZB>gA$HD66EZB>.A$D3E( ZB=A$CD=DD ZB=A#D1dEݟ[B>yAߞD;&[B>A$dD<_>Fr.[B>gqA$*7D(F[$B>QA$ BD4 EE[*B>3A$%D6KC`B>mAD,E4$`B>m^A'D2DGH`B>n}AD:EAvWB>A$*D6.E~|lB>6A&DFU/B>A$zD7E /B>JA$*D02FN0B>DA$*D"EF)\ B>ABD#?wB>٫AUD D?B=A#DoBgE ۱B>nA$4zD4 REmNB>[A$D0 E¬H|B=.A#ՇD|tC!B=A$HD>h E\$B>"A$,D5EH&B>XA$G>D:bEB>A$MhD6E@iB>\A$#D H4F:.B>A&DPDK7B>gA$eD2FmJB>[A$D0qEƩ.B>k~A$2D3 EMegB= A8CJ^B>AD\dFGܿB=KA$D6Eڰ=B>o?AGD@SEhB>A$V.D< cCgTB=A$+D_ EGUB=aA$$tDmMFB>ZA[D'FtB=JA#DD$F |B>8A$ĮD7ErB=A$,hDTqyE/( uB=WA$']DWE!wB=A$BDSƻEhB=A$Db8EyB=A$AD4EҧYB=MAtC3AVB>mACB>A&dDThRqB>kA+DF:DB>A%mD_8FHA)D_F}B?5A$1D+;E"B>A+cD-nJF*  B>:A&DRFQ \B?A+D/E&D# RB?JgA$)B>NYA&D{qDEXB?A+cD/ cDCmjB>A,0jD/F|vlB?A+D-WDIFpB?A+)D/M]DqB?A+"D+*C B>jA%lDCTFf&B?A%9[D/E-B>A%D7ME׌0B>A*ICňFcWvB>A%D6:CxB>PA%lD@0D0B?A+D+-B?A+D,nDQ;)RB?hA+D,,D( $:B?A%KD/Em5$;B? KA%YD0[ E&+B? A%~D2F i(B?1A$D4hFo^)B>A)D]FG,B>MA*F)DtFC/'B>A$D3Ff/(B>A%D7Dε/6B>A%sID])FG0#B>A(S?D=F)=B?VA+D,6C >iB?A+dD+AD:TE|B>ժA%DkYF#xEB>w A)ݔDDDcPB>A%D7,bCWB?J[A$D/jF[&Z{B>A,k9DXfF~/ZB?A+FD+(JAI|ZB?A+D/E&D#[B?ZhA$D0ӇE[B>A$^D0'CO[)B>xA'+D{aF[+B>A%:DD%F&`m[B?A%I D0D-^WB?OfA$D0?Fn^XB?A%8D89Fp S^YB?;|A$D-!ElB>A%D5blB>A*DJsCb'hlB>A*9DUdElB>oA*DCDclB>rA*CD^EslB>)A*B.DT_E/B>TA$D7FE.JB>A(D?FrB>o2A*D9߉E ƀZB>oA*DFB>A(sD9FB>A$tD6I{D:AqB?A+D+BjDB>u\A)DA.B>}A'DpF+/B>A&D7F0B>A(DaJDi1B?A%HD-.Eh7B> A%DDFl8B?PA%PD6F[9B? A% D-E;B>A%DCz|FXA$kD4GB VHB>A%aPD\:F7JB>A$vUD4F"WB?A+D-DfeB>A(D`SrD0TB>sA%cD1qE^VB>vA%wD@noE'dB>A%ID>3E~=iB>bA%jD>@mErB>9A%D6&DE(B>A%ilD1dE}PB>qA%IDFPЂB>A,oD>knF=~B>A,>DI,VFMB?A+D/ DݱB>A. DFE&B>A(eA)DM!ENB>,A%D9kEwB>A%D6ЇEiGpB>A&cNDO=EB>ϞA%DR>*F2B>A(9kC F\9B>A%D7DL.}B>7A*D\9ECB@JA"2DCEH}B?i+A$f D$[JFmV B@?A"FDlE' YB?0A%D/Dg5 jB?BA"urDEقB?CA$#D-EXɟRB?A#sDEf\B?A"DC@B?$)A%8D*zSC> B?IA$) D'F HB@eRA1S D B@1A"&D Fp!-B?A#FDEa>.B?4A"DjE4>4B? A"D?EQ>5B?A#^D F>6B?A#9DsFu]>YB@A!D uE=r>]BA A!D>^B@#A"b_DSFZ DB@A!CEjDB@0A!D |ElFB@A!DQiExFB@aA!DlF$QIB?CA#-DE@'QJB?A#D'FcWB?MA#ZD&7F ZB?A+pD,G,D8[B@A" DF(([B@ A#%D \G ki\XB@A"B@?{A"8D F=BB@yA"`DFAB@KwA1c9DCeyB?A#g@DF8B?A"D/AEK}L&B@A:ADxDjB@A;lCEoB@xA;VDCD2B@A;6TC|QEB@(A: DED׬B@~A:=Dr$EB@,A:;DYEf'B@A:EDD~ -B@;A:CEW LB@A:{_CsE%< MB@A:moDWDM* RB@^A:33CE]4JB@A; DE=KB@A:CάEg/LB@A:DEMmRB@շA;&C1mDWB@A:'CCaB@A:9C@UB@_A;DDB@wA< DF@B@eA9RDBtKB@`A9kFD }DdUB@A:+D bDipB@A:yDa*CK2rB@A:^CB&)uB@/A:bDvDUkB@EA:JDa8EMB@;4A9DDB@GA9D1EB@;A9LD `EB@}A:D3B@TA:ZD CE>1B@A:PCECIB@NA; sDC<.#B@lA: CE֕&VB@>A;D9!E `&WB@#A;D%iE"&XB@`A;"D8dNEO[&B@KA;/D r0Eb&B@_A;ދDڟE?(B@\A;D E{)B@A;C%Dy)B@A; oC )D,MB@pA;VC7C8hB@A;C %E?B@ѧA;&7CIEG?B@A;?9C2.C:)?B@AB@rA<$DWE#|iHB@yA:D#EQiMB@MA9DUEbiSB@\A9zD)EBIkB@UA;qC:CRkB@~bA:DuD"k0kB@EA:]vDQKE'tl B@A;پDOrEZlB@,A;xDElB@A:qDԋmtB@ AwHB@^A9odDDwJB@?A;["D E-wKB@3@A;+D{EӿwLB@EOA;zD -DhwaB@>$A;FD gDGNwdB@;A;G=D ~B@A;,CrSEդB@A;LDEB@A:DvB@GIA;D aYE8B@DA;lvD E*B@A:ßCRE0B@A:'C6Ef1B@A:n8C EjEB@+A;CfEB@#?A: 9DoEȫB@A;CiESwB@A;CvDiءB@A;{CE}xB@2[A:_!DBEB@EA;C"Ew  B@BA:"D֒DqB@AA:'{D}MB@R A9DMD'#PB@^.A9(DhD1]E˜B@A;9CLFžB@CA;`MCZDj«B@AnB?zA9D$&f+B?`A:`DzD+4B?A:D EB@TjA4CF4̺8B@dA9UC{E:~?B@agA9>CEk`@B@fA9D'ERRB@gA8DC HfB@:A9ND XEj~ B@A9D)B@IA9DDOEB@,ZA9DELgB@-A7D?F1> B@XA7DEB@^$A9"Cy"E@$!B@LA8[D p3B?A7D9,F:B?NA6D2C:B>wA?DB\R<8B?A6سD'#DFa+B?A6D'cB@xA:DDc B?A:YD%CfGB@F*A6XDVD fJB@A6OD hA7QDF& wmB@U:A7D ]EcCwB@GA9DZD ]EwB@@A9DD^%xiB@VA3dDxkB@RA4LDnDFyB?|A;9ZDWGU@myBA[]A9jCqyB@A7]XB@hA8lDBD.MB?XA6D/nEo _]B@SdA1bDGEB?SA9%aD!Eb1pJB@]A8տDE?5KB@_A8DD3 B>ۯA?3D-CJoB@JhA4D=cEtu'B@lmA8uD=B5B@NA4*DEvB@IA4D Ep<B@NA4AD%E9B@RpA8wD fTDT/B@NA8eD =D[B@QA8qD &DoB@WA8D oD޹B@RpA8wD fTDT/B@Z A8dC[;D1KB@G A7)DżEB@D,A70D1Es4 B?*A=DrFY B?A;a0D$B@A9wD9EB@A9e Dn3E>B@ A90CcE}sB@1A: DE&B@2aA9D :E{~B@TpA8DJCB@VWA8hDxD 8Q„B@bA9$OCZA?D @GB@WHA8DUDB@bSA9:9D dB@gaA8dDEIDeB@_A8vD 0ACT#fB@fA8 DYhB@^A8D B@QaA7DDِ_B@YA7pA?KDBI#B?A6D2F7B@mA9DDWB@Y+A8D?loB@[A7D CPpB@ZA7dDD6fB@[8A24DbBnEi?B@WA8:D۫Dh@B@^A8\D AGB@'A8SD'E5^B@0A85DuED|B@RA1D BEB@~A94~DbE=]&B@A:.D aE{YTB@A9KD-GDlr -B@A:LDD 86 RB@A9CEDKB@A:[PDCXLB@LA:\DoCiB@IA9iDgC]nB@A: CTDaB@A:C)EZB@LA: D D7"B@A90CBu$B@hA:TDDr B@~A9DCB@A: DE.(7B@(A:DD@yB@sA9D)DB@uA8CEl)'B@~A99DOD8B@jA9a0DE@B@oxA93DXEQKB@r A92D EFbMB@xA9MDqE`PB@rA8CE!RB@k$A8C ERMSB@oVA8CE@TB@ A9DcECUB@A9EDME4VB@A9DDE ifpB@A:ADPrB@WA:[AD6D.sB@A9'D!DpB@iCA8DES: B@zrA8ދD ΢E)+#B@A:DC ch#B@A9^uDoD#B@A9L-CD#B@A9^DD̦'B@A9DDf'B@itA9!CKdEU<'B@pA9C׌E h-B@A9lD PDֺ-B@"A9D /{Dm2-B@A9NCtE-DB@A9C-B@oA9DdDu5fB@ A9CEtm5B@A:D4Da5B@A:WD/E҆?B@{yA8+D -?B@zA9D"΃ElB?B@tA9C!EZ?B@vXA9QD dE6EB@iAA9TSD MDj!EB@^A8DDM<EB@hA8DDMsF:B@j=A8nCEKRF;B@fA8~D#6DYFCB@lA8DDߙ FB@A9eDDFB@A9n%CDsAFB@iA9cD}KD$@IB@jA8tDCIB@gA8DD[IB@iA8D Dl#B@kA8D xDŧKlB@,A9ҟCE]3lB@QA9!CuEJy)r"B@A:C=r/B@xA9D ~$Dͧvr0B@{)A:KD5E(ogrB@A:D9>D7sB@A:JCSDI:tB@]A9CSDB^tB@A9DȏC tB@A9C&DtTB@A:bCdCvPB@hA8c&DKuvQB@mA8DE`QvB@d]A8D?E<3wB@bA80DcEywB@kA8C;EAkwB@kVA9iDCwB@f9A8qDAD?wB@b A8DB<wHB@g6A9ZDCTwtB@p9A8rD CE KwvB@m+A8DEcwxB@rA8D*wB@A9DJQDwB@JA9CB6wB@A9HDwB@PA9DbE6wB@RA9yOC{EC\wB@A9D#CwB@A9GDDwB@1A9DDLwB@A9DzDqwB@A9D YEJwB@A9DhE rwB@{A9ޖD 5EwB@A9\D QXA'wB@A9UDCxwB@rA8@DGiE"xB@veA8DDlxB@zA8bD#DoaxSB@jA9HD ERfxTB@hA9UwDWDdxUB@nA9"CΧE!NxZB@pA8KCED x[B@qA8UCE ex\B@w}A8fD 9E.n~B@~B@IA9LD B@A9*DE B@iA9lD9DB@A9vC쉚EB@w@A9DrD*B@yA:ID!}EMHB@neA8DE`B@moA8CDʡB@rTA87CڨE[B@!A9CSB@r2A8pDDB@A9 DACB@npA8pC EKB@A9'DyD\VKB@h{A8DHDB@qqA8oD tE?ZB@piA8CE5B@A9j"DHDB@A9DoDD)B@HA9DXD:,'B@A93CND"ٌ)B@A9D KD`b1B@A:ZmDSBāB@A9otDAOD=„B@fA9e/DkC5‡B@fA8+D!%DDAB@EA:C^?E B@A:yDE ůB@A9C=(E=hB@q=A8Dp1+B@A:C#E)g,B@A:FD  E>TAB@lA9}CXEGXB@NA9QoCF;D=XB@A:CJD]YB@A:@UD-DƲ]ZB@A:5CHXEB@A8kDD2@B@A8DDEB@[A8Ds!D*ulB@eA9}D BcpB@#A90DCs: B@A8=D#B@A9$&D%A#B@NA9RCDx)B@A9.8D DV8B@A91DnXDlG9 B@A9 DDdB B@;A9+'DD{B B@A9aDXD]LFB@A9X~D Ct0LB@A9bD{D[LB@uA9JC6xD2LB@+A9]D1DMR B@A9fkDDeRB@A9CUCD`xSrB@0A9[vC{CJSvB@uA9[(CcD1gSxB@LA9Ce DfUSyB@A9 DD D~SzB@sA9TCDC|caB@yA9D "DcbB@~A9{/Dr B ccB@A90D\D'(erB@\A8DaDx[etB@A8HDD eaiB@A9DjB@|A9DEfjB@-A9_D \B=CWjB@A96DkB@A9CBkB@A89DHC.}k\B@}A9Z+DrADikkB@A9\D!C&|RkB@A9$&D%AmLB@(A8C=wB@#A9QDZvCwB@A9oCw|C%,xSB@rA9!D3~B@A9a2DfcDsa~B@xA9MEDD9+"fB@A9CVB@}A9YXCD,{B@A9D ESG~B@A9fC XTB@A9SD4'DB@TA8:DWC?{B@fA8CB@5A9 D62C0B@A8DN%B@A8D D<( B@+A9CPiB@nA8DJ B@sA8.D:CgO B@@A88DuoCq B@{A8D1@B@A8_DDCbB@yA8D?@;BB@x[A8DEuEB@| A8$DD/SB@pA8kD@' B@A8[D Abo?B@hA8[DǨD?B@A8CWDγ?B@A8tD.D&;IB@YA8D*=LB@A9DqDLB@yJA8DADLB@A8KDlDF`hB@A8cDjB@A8DrMDpwB@,A8D}CwB@A8sDxDDAwB@jA8ߩDDmxB@xA8CB@A8DChB@NA8DD~B@A8DjdDB@?A8B@6A8DfCB@A8DChB@A8DDJ~GB@A8D'fC+B@ A9DF&B@A9ڍDFfB@A8ףD@~DBB@JA8DDB@A8CD"|B@A8D*D|9B@A8!D @DB@A8%D*4D.B@]A92DEQn/B@yA8lCDd0B@A8D rE EB@A8HD~@›6EB@hbA8bDj-MB@oCA8*D^PB@jA8aD?D[RB@kA8tDʀD}SB@tA8;D Et\B@~A7DDB@bA9CQB@pA8}DYDH<B@nA81DB@kA8qDD;B@,A8D[D3>B@A8`DfD}AB@EA8YfDgD\B@A8DELjB@LA8DE5TB@A8*C&E9!B@XA9C3DB@A8CME nB@A8ӘC6XE4B@yA8CEJQ(B@A7+D-DB@cA9Ct/D<:5B@A9D38B@mA9CD#8B@:A9jCD&?B@sgA8&CEa?B@m A8,eDE2?B@vA7C,E e ?B@4A8?DT$DE_B@zA8 CC@EB@_7A8DEB@pA8|DC EB@pA8qD:F;B@hjA8'D}DR{FCB@fA8DGDIB@b4A8|DD/IB@\A8ZD IB@h)A8%DNDLB@{A7CJTEWLB@{A7gC۟NE5LB@A7D"E=LB@`KA8_D ^VB@A8cICCa0[B@ǽA9^CYD \B@A9NDD!`hB@|A83CEEbB@A7Ce B@A8CCBeB@&A8"CDeB@lA8qCD eB@f&A8aDeB@0A8CQD5fMB@A8DaD(frB@lA8UDݳEPftB@tA8=DDfuB@A8{D DviGB@uA9vD^kB@A9NClE6kB@A9{C8DkB@A9XCշEkB@;A9CykB@A8uDD-kB@sA8XJDXDdkB@kA8`DSDkkB@wA8VmDDIkB@A8fC(DHlB@cA8%DElB@\A7DɊEշlB@g{A8D |Drbl!B@gA8f%CEI-l"B@eA8|DMEl#B@iA8ecDoE9 l$B@aA8dpDqEl%B@^SA8@D!D3l&B@mA8>D32Eo=mLB@A8D@EmB@A8UD\ESnB@A8C E7oEB@~A7D oFB@~A7DxEjo0oJB@A7,DrD?rB@A98DoEerB@yA8TCWCFOrB@A9 DDϰrB@sA9CO\rB@cA9QCBsB@_A8zDD4s#B@[A7XC}EDwB@A8 kD}E wB@~|A8?CDywB@yA8C޼GEJwB@WA82D8DwB@GA7bDTYEDwB@nA7DDwB@tA87.D EGuwB@yA8 kD .Dh#wB@|+A8D3DwB@qA8CDxwB@xA9CbD0qwB@A9*D CwB@A9)DeC9xB@A8 D  DxB@{DظfB@A9PCDVB@hA8DC/7B@A8CTDtmCRB@A8~=DDȰB@A8CCB@jA8pC@D"{B@A8)D3E#x B@oA8kD0CTB@A9^CpErB@!A9(OPB@A9/DIDJB@aA8DD_#B@!A9(OPB@A9]CD\B@wA8.D8DB@q-A8DMbCU>QB@A8CB@BA8D6]DJ*B@W B@UA95D2DhУB@NA8DC>pФB@A8fDDDTB@A8)DEEB@A8YCDWB@,A8SCD:0ܥB@A9sCZ2D*hܶB@A9CHDC! B@‚A8YCDDvB@"A9(OPB@!A9(OPdB@iA8n7Dw@EP{eB@gdA8?D EO9fB@nGA8@D yEnIB@A8oD]`5B@MA9r'DD.9B@!A9( OP=B@!A9(OPB@`A8( D ՑB@A9CD@B@A9zDE B@A9oD D?B@]A8C4E&@B@_A8nD pEyFAB@lA8X]DsBE˒sB@y4A8tDE1tB@vA8/CiED BInA CtEt"BHuA;CvF؛%BHA<{D1C@2RBI`A<D3BHCAD,EBI AF:BGHjA8'D F^=EBGqA9C2sD=FBGA96CE=GBGDA9CME(gQ}BGyA9bC[R}BHd`A7\CXAFWYBHdA7_C#Ak0BGA9CE!#}l=BHdYA7\C@m`BGQA8D ETmaBGFA8D'vEoBGgA9;C^p)BHdA7^C՛BۨGp+BGA9CphBGA9C@D0uBGA96CE'IuBGA93C%E&z\BFA4C)DS|BH)A9ҡC yEΎx|BGA9CqF|BH`A9CkKE YBF=A7|(C+EBFA4[.CXCgvBGA9CEZwBG(A9C@GE&xBGsA9CcDͯsyBG{A9C5tDYzBG˓A9CԠDBHWA8CJFX<BGtA9CqE{BGuA80DECEXdfBFA8 D hYEѶ@xBFA8CHEA#BF3A7ɍCE(BHUA:cDЯDUEBHA9kCQD*UBHA9CĽE.BFA7DnFBG7A8D/(FtBFSA7bDEBFyA7 D[WE T#BGWA9CQvE$BGnA9CeF H BGSA8"CusFBGA9CǍEBFA83DrDĽBG1A6BF~!BHdA7^C/BBFA7}SC䡃CBG}A92CDBGA9-CIXBG}A8C]E OBG|~A8cD !C†O2BGA5C(s@/جBGA9]CD'ǥ޾BGA5JCXE ]0BHqA;+D DBHA;ˊDjBHTLA:MDCЯBHjA9C՟E>FBG1A6D{FG4BFA4>CǮBIADqBF"A0YCxyD tBFjA0CADQBGA0gC{2E\G%BFA0CngBF#A0 CwDDFVBFΦA0CEhWBFgA0&CmDBFEA0CzDc2BFEA0CzDc2\BFΦA0CEh]BF5A0C~aE^BFA0NC*EsBF@A0.C{[d)BHDeA CM/7BH%A!(6C"ExzBHRrA !CRF BHKA DCF$PBHA!dC E@Ā$BH'A C%MEˑB)BGۓA CF)BGA! ]CE-BBH,A!CCP,BG8A&{CSOBGA >CEp:SSBH A!C5Cܺw\F :BHADDCGm1BH8AZCinE BHGA{CUF9BHx2AZ Cr tErBImAC/F `BI@ACF!/\BIqA;Cu5BHAVC՞EBHjAӹCzFLBIK%ACXDBIKAԭCD5BIKAˎC:BHNA .@CBBINACÀE~BIZ9AC7F'EBICRACCCEPBIACFBHAtCF NYBHACJFhF]BIh2AnCyEIe^BIACОFO3BIGAcCщ}BIdA"CEoBIUAiCEBHAFCUE yqBH8AImCH9DZzcBGyA $CE BG5QA!C$E1XBG`JA EC<}EPBGmA RCF T'qBG";A!5C|b?EyW,BG8.A CȸDeR,BG|ACEOd2BGHBA kCCsBBG:A CDfOBG`AC\`BGNA C\~BGWOA ]CO E~BGsA #3CEMRBGzA CEUBGLA CEuWBGGA C 'E{BGjkA 5ZCʈEudBG2ȚBBFA" "C`eDL.vDBFA" CC\/UBG}CZCϗ-hBJ2ACiFÈ=>BJ^fA<˚COF*GHQBK A> C*OBKxA<\ClDNhPBKzA9Z)C4CqZF BNGA? \BQEnNBN~jA=ۼClgD 5&NBNA=oB0F4OXBNEA?CDOYBNA=C FUfFBNDyA?; FfLBNTAA?'C F;5oBNA=#CE>.NoyBN^A>iC8^EYpTBN4A@ C^EUqBNFA?C D00riBNA'GCEmj*BNfA> CEuzBNA:uB{~ExˡBN[A;B=CqBM(kA3BGJv?BN0A0&CFBNA0&CGE,WFBNA0' CSENBL9A?kCW]C|NBMAB;B JOYBO A:B`C4Z BOIA:BCZ3BNA;"gBEQZ4BNA;dB'FmBNA:uB{~ExˡrlBNoA<BFFprmBNA;TBslF-uBN=A<BD-veBN0A0C1EvfBN!A/CBNMA/pCgYEetBO A:BlD,BNA/wCoC{BNgA=CJE3BLkA@CDSgD.BLAA,BvzD<NBM,AB;,BE'|NBM&AABN A@9BbD B?BMHAA kB5E,+ BDBMRAAEiB~EB BpBMAAsBYE:i BqBM;AAWBdDn Q^BNGA>C { fFBN1A@'fCOhBL:AA &B POiBLA@CkE^(OkBL֣A@|}C$E8pBL_A@C}F#pBLA?.CHpE zsBLA?_COvBLi_A?JCxE|1BLMA? CEe`|3BL A?CEq E5|BL[A?@CCwD'~BLܢA@C D%BL{A?\XC` E1CBLA@TCCDoBLĹA@@xC,*pEzBLpA?JCqDBLA?fNCX33BLTA@+C7&BLA@vCd BJA=6"Cڤ BJ]A=-CdC6aBKA=C.E3=BJ^A=CFBK/A=CFoq%@BJA=nCΗE%b3=BK&A>Clj9BJ؍A=CF"2<7BK$A> *CE BBKA=CiE0CBK^A=QCHQBKA>#C,CPBK hA=[C"CRBK^7A=Cq BJkgABJ4AۄCbF_ BKjA=CFq(!{BKA>!_CF%@BK1zA>SMC9D'BLA>gCIFJ^'BK(A=C#F01BKA=?CE.1.BK|lA9CFk48BLA>CD:-;;BKA;BF.*<7BKA<óȹF%2CX=BKA=C E=BKA=p=CE~>BKA=CF1> BKdA>G3ChRDABKdA>CC6D>[CBKV2A= CkEۡE3BKA=sCCE.KGBKTA>HCE GBKKA>CBEwPBKA@A> CJkEzaBK*A=CyFoBK`A>wCDoBKҵA=CEJvBLUpA?ICiCDtBLA>FHCѣFBKzA=wCE3BKaA=pC[DBKA=DCeFgC"BKEA=C&(FBLSA?ICBKA:uC dZBKHA=BeDFRBKS$A=zCsyFI|BK}A CflF\v.TBKA=CDE ԫBKzA7*CɅBKACA> CE BK!A=>CtBK0A>ICE]aBKA>qC(RBKdA=qFCEuBKA=C߶EBKA=C+Fx49BL9A?)*CW;PBL1AA$#CmE} BLA>OC,Dw4FBLA?CH ELJ!BLueA?pC<3FXNBM-ABABu.DzFWNBM0#ABNBZDYKNBMsAABTZCbNBMAAswBE܀OEBLxOA?|CgME芡oBL94A?!CEoBLEA>LC_CEToBLKA>C2FsBLA?CHsBLA?~CIBEcvBLgA?JvCxetBLxA>CEoBLbA?LCt_EwBL-8A?BCD0BMAAB BLA@}CF?BMLAAB3E %BMACBE% 'BMzABBEB{ *BM#AB BVE "] ABMABC2BE PBMwABcBCtE ^BMAB;B J f BM“ABSB]D {hBMMABvBCD< ~PBMyABBxE3 ~QBMeFABxB^7E ~RBMm ABC0EC BMAB%B uBMGABB=D BM^ACBj BMAC7BѮE,  BMABC oEN'x<6BMA2ABBE%{hBMIsABbBLD0n~`BM.ABGB!9BMJABQBExĶ{BFhA.CZ>BFN3A. CABFPsA/JCMEc,C BF>"A/yCKD`C BFB=A/Cx EQMC BFFbA/$cCuE*NFBFhA0!CD%RBFiA0CxE 0RBFb|A0!CpDRBF_A/CyE7R BFKA/:C\VD R BFDPA/C LE?rRBFF]A/2CBCM#RBFAA/CdE?RBF0A0OtCgEfIRBFFA/CRBFm(A.CkDnRBF_RA.2C3RBFA0=C%CRBFdA0b@CEaRBFA0zC{AE|RCBFcA2CD׶XBFr/A1}CI6B)]BFMA0TKCD*A]BFDcA0HCs)EXgdBF~A.CoDx,geBF6A.SCYrEBF~*A.CӒE2rFBF`A.tC&ErGBFA.PAC{ErBF?A/CD"BFkA2VCrDXtBFA06CBFHA0HCJ6E0IBFsA0xC!E6BFgVA0 CkD+BFjZA0=CfDeBF1:A0SC$VEvBF)A0qXCYEWqBF/A0QCHEm5wBF#kA0C5BF>A1sC*CaޮUBFLA/aC]9E:VBF:A/vCXfErSBFGEA/:C]EyUBFA0-CzEjVBFA0C~PWBFlA0y C}9kE5BFnA09CqDBF=~A1cCaBF&5A0CBFXA2KC^'E(BFXJA18C$EdBF(A0vCDno?BFi[A2;CE:l޹BFS:A/C*E/ BFWLA/ZCE$@BFRsA/ CD!BFhA.CV/Eu "BFuA.cCRF3BF;A0Cm6BFjA0CJE76BF,A0zCE{6a:BFdA.eC_E6ç>{BFWA.CEmX>BFNA.BCDR(BF3A0YCqD1rBF-4A0LC4{BF A0CD1BF4A0*mCʾD wBF%A0CqDLyBF3A0-{CDBFA0kCiE6BF$A04CHE`BFyA1CBF A03ChDx3?BFZA.CDmBE̬A1BC5C?@ lBE͕A1)tCCBEpA1NC;BEaA1KC D JBEɅA1buCDn OBEɷA1TkCD6BEA1FOCWC6BEA10C%@6BEIA13CPDteBEͥA16CiC~IeBE͐A1-CC:veBEA1.eC-C6eBEA1P C&CjBEA1_CDC/BE,A1"OC6BE̼A1>6CD8NBE͐A1-CC:vBE͚A1*GC-YCBEA1P C&CBEA1@CąC;'BE̽A1BuC.aBXBÈA1'TCqrC?Lk BF&NA0cCjEl'BE^A1CsDDBEʃA1cC׼D_";BE$A1PCҏ3BF"A0kCSD26BE$A1PCҏRBEA1~CRBEʴA1?C9C)BF A0CD1BF* A0^}C&%BF"fA0FCiDBFA1IaC=BE˔A1FC}P .BEɌA1CGD 1BEɌA1CGDBEA1$C.DfA6BEA1+C1KC֫6BEA1nChD*BE/A2YcCFRBEA1CDmeJBEɽA1uCgfCwjBECA1QrCrdBEA1Cr}BEyA1UCIC#ԬBEGA1gCE)xBEGA1gCE)xBENA1UCVBEA1`C+DBEA1`C+DBEA1`C+DBE_A1Co}BEGA1CCq'`BEA1 JCJCK=#BEA1`CGD@JBEA1CBE/A2YcCFBEA1C$BEA1CDmBEA1C$ ]BEA1C4qD ? cBEA1bCDi nBEA1WCD 6BE_A1Co} 6BEA1GC[DA8 6BEA1GC[DA8 6BE1A1CEqV 6BEA14C|E5 6BEA1C4qD ? 6BE@A165C${ 6BELA1!CD 6BEbA1 FCo BEA0MCB߾ BEAA0C#CC BEA1GC[DA8 `BEAA0C#CC BEA1GC[DA8 BEA4Cw ~BF+A2C{CErdb BFA2G CE% BFAA1CBF)$ BF$A275CE BF A2E}CԌHE] BEA2COEt\ BEA1qCfB BEA3CeE =BE@A2kCE PBEA2IClEo [BEA1CE |BEzA1C9Ev  nBEqA1CC:l nBE{A1CDLK BF6A55C UEh #BEA3CQDi #BEA3UCD@I #BEFA3kC1 &UBFoA1{ACÙC-[ 4BEKA2hC-E. 4BF/A1uDkHF 6BEA1CEg@ 6BEA1C%gDN& 6BF A1[CD)! 6BE&A1fCD# 6BEA1CEAپ 6BEA1Cy@E^ :BF5A1vCsF x ;BFd7A2aCFd =BE(A3 CxEfu >BEۅA3CEDTĽ >BEA3@C3EE ?BEٻA3C~EAˠ RABFvA3!C3EB RBBFkA3%CUES-R RCBFSA2CF/ RNBEA2CաFJ ROBE|A3nCEHӍ RPBECA2"C F F RBEcA3*CeBE? XBFPA2N2CےA2+tCo 6fBEA1C:Eh; 6hBEA1ÛCxE* 6iBE$A1CD-i 6BEA1MCD?C0p 6BEA3(C*Af  6BEA2BlCDuzH 6BE A2qCDu 7/BEA2CE/+ 74BEA3!C 75BE'A20CD' 78BEA3!C RBEA3&BEDA4 wC pEݗ>wBEA3wDbF2?BEvA4AaC2D4%D*BEGA2'CEKLH;BEA3ZoCvFKBEkA4wCE.[WO`BEJA3KCۨEm[QBEsA3RCEgfKQBEwA3CsEQBE\HA2zCFRNBEA2CROBEA2CcCRPBEA4QXC& ERBEoA4?CrE;~RBETA3.C>DRBEA3O%C'E`!RBE!A3r5CE4RBEQA2aC&EgRBE{dA2CC'EfjBEQA5 /C1Ed0BEA5_CޫCpBEvA5H3C\EBEoA4LCdEBEmfA4CJEBEpA4-CPE]BEA3`BIFMzBEA4$C5,F$BE"A2[CE/vBEA53CBEA4xCGFfXMBEA3KC=EBEQA3ICCBEA3t[CCBEA4C΢FBEtrA4$DEBEsqA3:D;FBE&A3lCFT^BEA4CFcBEA4fCEeBExA4K/Ci5Ez%BE/A3:CƓC(BEFA3RhC@^Ec!BEA3~CRD(8iBE_A3DaFNJEBEmA4fC9EwR%BEA20C DpRBEwA5CtYE_BEnA4|CְE(BEyA4gCBE]QRBE3A5 3C^DFSBEA4C ]EŎTBEA4C€E# BEzAA2ÐC(E*&cBEA2%OCjEBEA2BrF>1BEoA3OD@(FUzKBEhA4Ce+Eu۔BEA5uXC$BGmBE_A5/CE?k;BE`A3 &0BF\A4DJ"F52BF@A5QCJE4>BEA4BCG|B{?BF A4tC޴E?BEA4W+CZD{5BkBF)A5/CEsR@BFzA5C頪ERABFA4CܟDNRRCBFoA3CDREBEA4CmNE_RFBEA4|CLuEL1RGBEA4pLC[DDlRPBEA34CjC`)r6BF/A4,Cu*EI{r7BE`A4C+EBպBFbA3CCBFbA3ZC~4DGBF|A3C}E„pBF/cBEkA4PCCJeBE,A3[C=D2BE|A4*fC=QCBEA3@CɆE_tBFxA6C YhBDA4ICν6C nBDA4DCsE) sBEdA5CZ BEcA4CDz| BEcA3DCEFTHR ۔BEA5T!CD= ߼BDaA4;Cn BDzA4Cۊ8D BDA5C?F5 ^BDSA4C˴`EF BD A4ChFA1 BD.+A5xC/F BCݜA6vC F.j BC A6D}C۽E 7VBDA4`CCPD< 7WBD23A5]CɇD/ ;iBCA7CF5 ;lBC,A7C4F> @BDrA44CSF @BD4A5uCF{ @BDxA4C$9FHqN F#BD A6+CuF! F$BD A5C$Fx XBCdqA7rCL XBCA7uCpb YhBDrA4wC6Ecɝ vBCA7PCc!E]$ BCЈA6C꥝F BCψA6ϿC٠F. BDvA5Cݼ=Fi BD hA5C*!F BCA7OqCF&| 7BCA7QC..E k [BDmA4-CЊE~ iBDA5Cu F kBDtA61C F)N mBDA5ClF"B BCYA6}C'F *BCA7~CA5E~ BC~A7rC7 vBD|A3׮C,E%u BCA6%CZoFuBCA6GC5HDҝBB}ZA8o&C_D>BDdA4%@Cw+BDbA4nCnEa BE.A3UJCaD!$BDA3C+FtC'BE 4A3mC mFLHhBDA3GC AE{ wBDA3C_E @ BD%A3%CٹF) BD˻A3ܵC΋CBDbA4_CTBD*A5@C7Cv3hBE+A3UCQrCt7VBDA3'C`zF7WBD^A4C1;FD7XBDA4`BC˱F7BDMA4cCDM89BEA3CMFTF9BDNA3C$E{@BDA4/CE@BDA4`CCPD<@BDtA4S&CPB@BDA4>C7DNH:BEbA3wC*%EH;BEA3vCɑHEQO`BEA3C)FJdTQBE%TA3VCDq7]RBDA49CЏ5Cɠ3YhBDA4QCD^nBDA4Z CYrC"BE.:A3YCEhBE'aA3dMCF7%BE2A3T%CEiBE!A3uJCĺ+F/BD~A3CdzwF[BBDA3ʜC-FnBDA3CEb[BDA4C̴CFl>]BDhA3CӍEiBD9 A5:CµE&߼BDA4dC/F,G߽BDA4)CE*;BE0CA3Y8C†-F vBEA3tCfE BDA3oChSF\BDA3CBCA7CkBCA7qD{#BCA7xCC[BG;A0;:C{eCvBG#xA0C.D#BFA-ICcEHFBFǀA-VCiE+ SBGA&(CR-El8.]BFA+6CBG PA%LC EFGBGA5t#CBҼ7BG[A/C%E)!ABFcA-CE ]GBFYA-C^FHBF*A,%Bj]EߨyBGA/BCqEXBGA/&cCCǹBG6A- DEFFc-BGJA5SCD/-BG{A5D:CEZN2vBG^A/]ChE]2}BGP0A0;CdE9w7BFA,CjE 7BFQA.V\CE9BFOA*C E>BGNA/C} BH1A/dCQE1WBGaA.CBE#[}BGA.AC{"#EBGA.2C)}EBGA.2C.9FhBGA.$CDE1+-BHTA.C{vFܥ+.BGA. Ce+iBGA.Cy EU-BGA.YCWQE P-BGA.q:CC)HEj2BH A.jCvcaF 9BGiA/CA/CE̙sBHCA/CCڤBH"?A/0CBETBHqA.C~1F5\BHA.ʪC9E~0BGjA.CuE=#BH8A/r6CE\$BH!A0s#CEz'BHTA/CGF6,BHbA0 sCmFFCBHA0ChRBHA/3yCqBHyA/CFBHLA:ECsD  BHBnA/CCET BHNA/ݝCE%DBIA0SCEӱIBIA0YC=zE^`uPBH6A0CiEG[RBHA0 CEfBI* A0pCDBIA0KCf E uBImA/+CE® BHLA/fCC7BIPA/>C{F5uWBI.FA/CD_HBH7A0mCY/EVBHA0UaC~VFcTBI=A/fkC3CշE#-BI>A/`NC(E$?BInWA.ϊCE $@BIdA.CE[+5BIA.QCEP16eBIA0:4CWEC;'1BHA0oFCBHA0:9CD3"BHA0}CHC(BHZA0CEtGBHA0CElBHA0CKEDOBHA0CiEBcBHA0CD6Z,BHA0CmCBHYA0fCqE BIT A/CFKBIA04CLE^BH*A0CBIJA/!CEw lBILsA/\CҶF rBGA$ CQPBFKA!C;8EM,\BF۷A!CEe kBGA!CCQ BG$*A"_CNSuBFbA"CFU"$BFA!6Ch1/BFA!CEʨ1BG'A"CD6_D[x1BGA!C D 3&BFSA!CrbDK]ATBFA!lC*EJTBFA!CȴBFzA"CrDy[BG$*A"_CNSuBG?A!CM3DFBFgNA"MCD-5BFA"BiCn(F.j6BFA"LC.;F 7BFA";C3F2 /BFOA"QC,E=1BFA"RC@IFz7BG$*A"_CNSuڀBG A!CLEzCBG!A!CBFFA"8CpD"BF6A"DC,BFpA"ɦCXIEqBFe:A" C/sBFA"N:CrEu BGA%Co9F eBEΖA#CEt \BFA!6Ch1 BFA"NAQFo &BF.A!.C 'BEA$#CP BG$sA"C=FE\ BG&A"CB BEA#NCF cBEV'A#vCϒDVR }BEA#CE BF kA#(bCJ qBG%A"aCAE ,;BG+A xCJ^ .BGA%!CeF. /BG;A!CvC8 2cBG A$#Cn@EmE FBEAA#UC?Ec KBGEA!8C> RQBFA"xC}EwJ RRBF4A"CEoC RBEA$C؋E,Ո SBGA&D*C$E - SBG"A#}zCF@ \;BG#BA!_sC;C \CCPFn 9BG?A UC[d]Fo- dBG!A!FC5,E-o eBGA!CQE] fBG";A!5C|b?EyW jBG A$Cgj BG!IA$CZD BFXA"qC F* BG"A#BNCL fDnj BG#A#5CZ Eq3 BE5A#xCFpn BErAA#CTF  BEhA#C˙Ed BEeA#tC֏nEZ  BEA$'Cظ BEA#CߨE1 BEAA#Cw F/  'BFA#CL Dl {BEA#CF&' |BEnA#chCEE_ BG9A%T/CxE1. BG:A$zCf=F^0 BF=\A"ۉC?Eŕ BGA$?ACjEm BG"HA#lCEG BFA# C]F BG#EA#]Cr#E /BFwA!ȈC/ BF90A#CfFg. BF_'A"DCXFP BETA#CP BG#A#%CM BFbA"YCD "BF\VA"CE BFYA"ÄC BGA!+C BG1A%XCk^ BG!A&2CD BGA%C^pD BGA'}"C)y BG mA$cCuOE XBIvA<DYDheBIstAA$ND4B>A$ND4nBLH`A.C%BALA/:C3ɡBP.ABVDBAPA:jQCݙFBAPA:jQCݙBAPA:jQCݙ:BAPA:jQCݙBP.ABV(:BP.ABVBKA0uCBoBPhANjc!G!BUA"*3G BAW!A;5CۂBAW!A;5Cۂ BOAO4B00BEAjyBC+_3B@q@A9D|{5BAW!A;5Cۂ7B@q@A9D|{BAW!A;5CۂXBFABBF A[BWE&-BT$AIA١G)q:BQAXoB*53CgyPbA#DNOGDJe5B@3A9CCtBDA( &NHBPyA6bŲHHfBDcA7`-KHBV KA!.FBjcE-BT[AGOBw=BR%AW>A24CTBUQA$nĿ"BQaA˅BGE({BQAhBBQAADE#BPAIBUEZBPA)B=Bb!BUA":¿+F*ABV 6A ?CYTBBUA ~A2EnBUA c?DZBUrAfB )ES]BUAO,CqBUA C9E"EBU~7A WjYEBUJA n u BUA`-AuEG BU~A ,BF V BUAGAE#]BV,A"R&3E1$BV.A#A )}F?BV&A"Ao86E]+BVA@/vDrQBV.Aț3D RBV.DAi@vCŝSBV.tAx?C*BU A ~@EABUIA gC, EBbBVA!ǴBxVEK BVbA!sB}E-BVWA!6awE#BU;A!#wBWCa8BUA ?DRBVA!_$E[,wBUA!#D>F4xBUA 0@yBUA4BAEhX BV0yA#p@wFBV>dA#G¬NEZyBV;A"AD}BUAAV>EnBUKA AcvEJdBUAA x+EyBU"A yw@bFsE-BU!A 7oEBUA r6@;ExBUdA"LCqF<BUA Do%.5BU*A Q?;d61BUzA 8BeFK=6MBUuA L{AŽ;F 6NBUZA &AEI26WBUABE;BVA6 Dj< BUA tTAsF[< BUA ]BRF < BU1A TBHE< BU=A!BWDE*BV3A"w#EE-}>BV7A"@zD>BV$A!EL>"BV2wA"|?L^EJ) >$BVA!@CfNBV>XA#eA=JELNBV>LA#ADNBVA <0@|D)cBU]A%ABCK wdBU'A lԾmEedBV 9A!_xCPdBVA Y@CŞ dBV A!ct>DYdBV {A!U =dBVA"4DρdBUA ]@fC .nRBUA%x#BK|qBUA$WB%4{DqBUA%#YEW*eqBU%A$ TAkFE&qBU|A% EP qBUA$#E1ryBU]A#AYp{BUMAjBqE{BUHA Bq|BV:1A"7ANEzBUAAʂDBUYA]B&tE @zBUA%(B[PEWITpBV;A#ADKBV A! ')FE/PLBUcA BAEMBVyIC"n3BUA ou@,ECGBUoA"jŠFXBUA!l|AE^̉BUCA%BJe@BáAMS]-BM @f,CJkBܤC-{BM@VC!BnB-BM@xC;uEb-[BM@CNn$EC-\BMF@C@3ES$-]BM @CjE,+- UBM+@=C-)gD>- VBM@hC8(;D- WBM @C!dlEH-BM@eC:D I-BM@]C,2D-BM@䈳CE!eDEf-BM@eCԻDy-BM@mOC'MD-BM @\C DK;-BM@K8CL~D!-'BM@CAc-'BM@rCHYDi-._BM N@mCA DW-.`BM 7@C5(E-.aBM @1`CDƸ-.cBM @BHRE-.dBM8@ECDE-.eBM @C )3E-4BM@ECH5@ -8BM@2_C(-;&BM@4C BȠ-;*BM@7zC>C+\-;5BMP@lC-?@BM@ C/;%D-?ABM F@ZC<[D@-?CBM @SaCEC-?DBM +@+^C$DoV-?EBM @FC1hD-?SBM=@䖵C2D -?TBM*@ѭCAu?Z-?UBM@lCXfD-?XBM*@ѭCAu?Z-MBM@䄠C%SDs-NIBM@8CRjDCzx-NJBM@]COD)-NKBM @C@S!D_Q-NMBM 4@QC Dw-NNBM@CJjDB-NSBM@C3&DKS-NTBMf@9CTI=EY-NUBM@jC$/PBM@FCTmC`/PBM@LCN]/0BEAYB rDݹ0BK@Bb\LD 0BK@BQ8C10BK@޾uB`?0 BK@1B/ CN0 BK@1B5-0ABKg@޻9B[0CBK@B@C10LBK@޹B7C0 BKd@ Bm2CG0BK@XBZZCa$0BK@vBSSC@0BKR@BiVDuk0)BK}@hBD0)BK#@ӷB/zC0)BK@{BvXC{0)BKw@HBD*&0)BKd@-BC]0)BK@XBm04BKV@ĚB?#C04BK.@YB"EDU04BK5@ťB?C04BK@޺}BlCnS04BK~@B.07yBK@޼Bo[BlZ0: BK @޼NBsTA00@BKd@ Bm2CG0@BKd@ Bm2CG0CBK@?BqC~n:0ONBKd@ Bm2CG0OOBK@B0OBK@ BJl`Dl0OBK@?B#מC_0BEAEBWAN08BE-ABBAi9BD.A4>CE9YBDA4,CyX9aBE7A3QC.E.9kBD A3\CyFk9A3{CɏF m)9=BDoA3ޏC7E9BE_A3DWC4bElBCBGPA0C!CnC5BGiGA/CE,C?BGeA/C*8D CIBGiA/C+DCBG]A/CtF~CBG:#A0`C#E}C BHA/CE#C BHA.C[EjC?BHA/CzdUFhaCIBG1A.y BɢFCBH*A.CFC|!BGA.1CnE1C|+BGA.HC9eECBGA/QCD4CBGªA.CtD3CBG/A.C6~E2aBCBGA/6C%ES@CBG̪A.YCmE>LCBH+A/VaCD_CڳBGA/,CD.CBGA.sCwJEUCBGA.PCENCBHPA/FCCBGtA.CqPECyBGA.DC{ XDBEAF,BID4XBF^ASLBOYD$XBEAc8BؔDNXBFAPB;EDqXBEgAHBķIDrX4BFA{BE.UX\BF:AvBzE)XfBFAB՛EXBFAǚBܔE"X;BFԯA=B1XRtBEAD3B3D BXZBF:wABAD]XktBFAPB;EDqXBFJABE`XBF/ACF1XBF\AeBFXBFAAsBDyXBFAZIBΒDsXBFAxB ]F XBF?ABREuXBF[[AB3XBFPAB8BlXՌBF>ABFVE|XՖBF#AqBEnHX BF!SABD]XBEwAfB(AEBDt8*BFAOBE4m ; BFA|BB\`=BFABbBaADXBFAJ>A]ވE#nBFUAJBsE+dnBFA*BNBFAB#BDBFAfBPE: BFЎAvBEBFzAB˲DBFnAsBREA BF?AOBBFngABD%cBFA>BREBFAbBE\v&BF;At BŋDBFA4uBihCo`BFA2BStAjBF A3BNEBQA.BPD6BLBM"@bCVAeE0GBM"@CQC"BK@B>D9"9nBK@B:Ch~"9sBK@BN0 CV$""OBK@ƭBBĜ"OBK@B?"RQBKN@PBAC\"wBK1@ݿ5BFJUD1v"wBK{@JBN4D"wBK@݇BKD5d-~BRQ7AOADhu-BR{AOaF 7-BROAO/B Eu?-wBRAO~Cn~Foy-xBR:JAOo\porFƎW-BRIATQ}AЈ!E-BRuATA>,E -BRASTBxEՅ-BRַAQ[E-BRAQjA;F oH-BRDAPrC*F,D-BRARBgE{*-BRBARBFr-BRϵARBGEs-BRϻASaB2-BRAS:B=,\E"- BRNAOdAE[0- BR_AOA&DV- ABR]AOOA{]E- BBRNdAOAȼC- _BRyAO=A@F1j- `BRXAOSUD,F|7- aBS& ANBqF- rBR|tAOI-FW- BRPAOzB5E-EBT6AHBaEr_-NBTgAGBGe;Fw2-YBTaAG/BE-[BTiAH}B+zFp-dBU |AGe B-wBTAG5BFz-yBT!AG5mE-BTAG6BOE-BTAGDBwF"5-BSnALPLB49-BS$AKB/fDȪ-BS`AL|B/O:E-BSAKAAFE-BR˃AO xB'FY^-BSAMB3Ek(-|BTzAGAxBUVDG\-BTAF4ÌOYFQ-QBTAIN3B#zF -RBS3AIB&D67-ZBSAJlBFz-"BTADpA,G9- BTsAGVFz- BTAIs|BBFH - BT^pAGBgw F]-!BUAF#bFᱹ-!BT6AGp=TFr-!BTAGm9÷xF5--!BT.AGOB/F+-#BR~AO;CjFEc-#BSewAL+B# rF-#BS'AMAЀ2F-#BS AMA7FL-#BSAK!ADEF+-$BS)AJlAG -$|BSVAJ8B*F`-$BT٩AG4BZ-$BSAKBE-W-$BSPAK BlF{8-%'BS_AKo+B"xEa~-%(BS~AL3BEr-%)BSAJAF-%,BSAKA~FDd-%-BS,AKB~E,-%BSAJ'cB[7F-&BRaAN:BF Fָ-/BRaAOBZEc-/BRANwBF,\-/BRtASeA}Ea-/BRATfB9dC!#-/BRAOѳA^FF)-/BR>&AOyB-%FΜ-/BR|AOcF)Gs-/BRpAO.51,BRqAWBIEJ1-yBRhyAW(BDO/1/BRDAV69WEQ1/BRAVwB(Ci10UBR9AV:B'$uD1F10BRaAUB1>E(C10BR›AUBVDș10BRvAUJ]B3Ec10BR AU×BMD/h1BrBR AU|9BEE1BR̙ATpBC?1BRAVEW1BRAVB 'E 1BRAE۱5fBQE<5BQ]AQBCE5BQ1AOHB_\DT5BQrJAQOB HNC5BQrAQA#5xBQAO yBYDJ5 ~BOAOBF5 BQ&ANCF)5 BP-ANLCFw5 BQJAMݏBF5 BOa1AMNBCY5 BOANDc~FQ5 BQAR0 B D4[>5 BQARA?D!5 BPAO@3GFz5 BQuARQAE5 BQscARAEH5 BQN[AQ|B"UEؐ5 BQ<(AQ:C[ FBy5 BQ@rAPdB?F[5 BQKASBrF 5 BOgAOnB|F@5 2BO]ANbBX5 3BQANuCFYC5 4BP^AOC*F 5 5BPAL=]DgG45 8BPANNBPEhl5 `{fFht51'BPAM6hCmG^511BRAO]AC51MBQjAO8>BF-ڠ51NBQʑAO)B E؀51WBQrAQBBn51XBQyAR A?E52eBQAQADX52fBQyAR'B|E`252gBQ~AQB0ZE9Cl52oBRjAOqAFV52pBQJANB*FNJ5< BQAS > EC5< BQARBυE!Z5< BQJARB/D5BQpAR@'D5>BQNAQAE5>3BQAMZB=EF5>EBQ<ANYB^8E25>FBQDANhCFz5>GBQEAMBF-F!5FoBQAO,BtF#[5FqBQQAN*BfEk5FyBQ2ANo B(5E5F{BQ7AMAB[Fs%5FBQv.AM BLDD75FBQ`AJ1 F15FBQaAI¸wEI5GUBQb`AL 9NFjk 5GVBQ]&AJ +EЕ5GsBQ{AM5I."E5GtBQkSAM'"F5GuBQfxAK_FF_>5GBPALBPE9|5GBPAKBEƤ5HBQUDAHA4.CZ5HBQ-AGmA݋D 5HBQ./AGpxAF 5IBP.ANnbë7oFp5IBP2ANXC9UF&5L8BOAQF5L9BP$AOsCFc5LUBO AQi7 ES5LVBOAQCJ}E05LWBOAAQBCQF 5L_BOAPHCL*E+5L`BOoAQCEFH5LaBO`APCk@Fn5LBOAHBίgF @5LBPAIBlFZ'5LBOtAHrB FS5MOBO&ANbHYcF5MPBOmAN@KF 5MQBOAM@Fհ5P4BOqAM5B5WXBOtAH C D5YMBOpAN B_~GC:Ƿ5YBP&AI0C Z5YBOAHC_Bf8CA5[BO] ANXB4B5\BOyAO BL@#CQ5\BOoAN^CsFB5fBOeAMB5oiBQBAS@B2CX5ojBQGARBES~5BR5;BOAP)BwE 5MBQ^ANkB/+F]5NBPqAL!A>G25WBPAM+Cu*F}V5XBPAMlCYF5YBPuALdBsF"55lBQAOKpAWDHB5mBQ-AO]BjE(\:BQAWA:GBQAV(BE:IBQXAV&B]dDc:QBR*AVAMD:RBR-AVDA9C:SBRAVsAZD?~:UBRQAV`AՓD9b5:WBRQAV`AՓD9b5:zBQAV,ADa:CBQAVBBẼ:BQAV- B.:BRAWB C`.:!BQێAXjAYiC=::BQAXKA6DH:BQ5AX AapDہI:'MBQAV,QBE :'NBQAV+BfDG:'OBQݙAV*3B (Dg:(BQAXA.De:)UBQAVO_A,Db:)WBQWAV?AvtD:)YBQAV8AC2:)[BQAV8AC2:*mBQ8AV(B?%D<:*nBQËAV0A6C(:*oBQAV,B?E>:*BQ?AVyAܔ8C3}:+BQ2AXAB>:+BQ)AX&[A`Dt:,vBQAXB":-)BQAX!ALD 6:-*BQ AX@AjD:-+BQiAX2Aq@DF:-3BQAXcBES:-4BQ#AXuAD:-5BQAXz,APDY:-7BQAXVBpdDbG:.9BQֈAXA0D>::BQAV,BWhCG::BQAV[A)D::BQAVLAD::BQAVKAh~'D+q::BQAV,A4Dpt::BQaAV* B0 Cl:;BQ5AX0A ER:;BQ"AXnGAq1DṞ:;BQAXFAlCA]:;BQݫAXV-APCq:;BQAX=sAz:;BQAUqBC:ARpJBSDaBD =:BR AWLB %Ad=B.+D=CC=BEPA3JDC&E=BEpA3?C?E=BEiA3>CE;=BEeA3CsCEk>BRV^AWB5GFBRYAWAGE>BRC6AV/B}nD6>BRN+AVAtZEa>BR=BRlAW&B~Dq>>BRZmAVBC` >BR!AWEIBDKG>BR3AWABDy>BRiAW7BhAeD> BR $AWJpBND5> BR $AWJpBND5>BRAWUB7>'BR-AUuB >(BR1AV B tC>+BR@_AVݞB-,D >+BR@AVRBbE_x>+BRBAVpBA[Dt^>+BR@*AVCBtD(>+BRIAVA{>+BRB8AV؞B"DEs>,/BRd{AWB(ɫDF>>,0BRUAVB,Dd>,1BRRPAV߆BKC>,CBRwAWB8\)>,DBR[~AWAD;b>,NBRLAVBGV>,aBRAW$B'Dt>,bBRAW4BTiC>,cBRAW(WB][D>,eBRAW,B/DM>,gBRAW,B;D>,BRr`AV=BE>,BRdAWB 7DN>,BRXLAVAEo>- BR_AW!BD%iY>-yBRYAVBDf>-{BRFhAVB5Du>-BRNAV3B@D>.KBR>4AVBGCߧ$>.LBR3.BRFGAV؊B,@d>.BR9AVނB,D]Y>.BR5AVݾBNCD>.BR2AVܫBK@WD>.BR6aAVBFC hw>.BR1AVB_YDa>.BR5rAVBe D*j>7[BRAW(BM4DW>8KBR.AVA'D>8MBR+AUnAD'0N>8~BR-AUB zC>8BR2mAVڼBmr@D>A>8BR+dAVB Dz>8BR/{AVYBf|Dc(>8BR/AVBBj3D >8BR/AVBbD>8BR+AVЋBP9CJw>8BRAVB!>;BR%7AW@ B+C>=BRAW4BTiC>o/BR-3AUAD1D8>qBRM}AVߙB+>rBR'AVאCEDOX>rBR$AW?B;C>rBR/AVBQC>rBR"AVyBTE!T>rBR-AVBrBR'AWB cE,Z>rBR)NAV=B)84D b>rBR(AV̽BߘC>rBR :AV-CE,>rBR*AV B{hCɑm>rBR%FAVC 6D!S>rBR!}AW@W D>rBRAV!CE!>rBR/AVتB`D>rBR&4AWrBR,AVBUDw>rBR#dAWBB|K+D,~&>rBR#AWEBaqD$>rBR!.AWABCD >sBR AAW9TBd D>sBRAW7B?^(D>sBR#AWlC$70Ei>t`BR9AVHB 4UC>z'BRBR/AV BDK>BR,AUBucC3>BR1AV B tC>BR0AVBpDBR17AVBhC>DBR'&AUA>BR,AVoBD|v>BRFoAVwBBR>AVMB-Dy>ԽBR+AW"BBj>BR&8AVBqDD4V>BR":AVCAVE3f>BR%AVCPDs>BR AUhAABRMATBCWmARBRdAVAAUBROAVA33AfBQAT BIE6-ABRATmcBn CWAcBRATvbBDEABR"iAU+BoEDABRATA4DABRATB9E$ABRATE@E=GABR!AUB\~DsABR!AU BtDݣABRATAsDYABR /ATAB+rD.cABRYAV~B'pABRkAVw>BD2ABRcAV5AKC AQBRAVBR"iAU+BoEDA;OBRATv/B)KDTMcA;WBRATADA;XBRAT[B_BAeBR +AU4B([cC}6SAZBRVAVUABɀA\mBRNATnTB@D?A\BR-ATfBxD A\BR AToNBZDA AmBRnAV/ABw AmBRAV AE;AmBRAV#B$*0DUAnQBRATtBD'AnRBRATB+ B7QAnSBRATCAJDpAn[BR 9AT.ASnD[Ao_BRAT1@DWsAoaBR ATwB'eDڶAocBRATEAB AoeBRATEAB AoBRyATA0AvoApBRATA"Ay#BRAUv0BFDAy7BR AU^EADQtAyBRAVB"9C{AyBRAVBaBwAyBRAV.BCvtA|BR rATABAoyCA|BR ATQB3D,ABRAVm9BDl,ABRAVx@,DABRAVNAA1D ]9ABRAVK@AABR%AVUBDW ABRAVB"BBC0AgBRyAV9AdBCtABRAVsAZD?~AOBRAVnB5hDa^APBRAVBAQCEAQBRCAV.A6BDAwBRATBF8R@YABR ATBǮABR ATBCBABR ATBNDwABR ATPBBCnABRqAT{BE2EJABR AUKBDwABR AU1eB(MA6BRAVAVAhBR ATBDAiBR|ATtBNEADBR$AUwB^DDAEBR#AUDB 1ACBRXATB`BABR ZATyB3D A BR AUB3#DQgIABRAU"ACsABRAU|A BqABR AUMA}DEABRAUA dCqtC4BR%AUB1DӟC5BR%2AU}ACDEC8BR%AUB:D{C9BR$EAU_B-JD]7CBRXAVB9MD&lECtBRhATBK|Au#CBR AT>A+C'BR)AUACC'BR%hAU|B0"DݤC/BRƺATBAC/BRAR A"Q BIsA.NCE*Q BIEA/RC|E$nQ BIA0FCyE:Q BIPA0C)E#Q BIJ~A/+CF@Q]BI4A0rCxF|QBIA0HCEYQEBHKA0[8CQOBH A0uCEuQBIA0:.C|DQBHuSA0JbCEQBHkA0)XCFQ BH=A/JCFvQ[BH+A/R(CEX$Q"BI!A-%CaE ?Q#BIL`A/%\CȥE Q%BIOA.rCEǧQ%BIPA.CQtEQ%BIA.XJC̽EŀQ'BJA,C EQ(kBHA0,CE#5QDBIQBHA0C!D^ QBH5A0CE'QBHtA0TCEQBH@{A/CbEkQBHA0F)CF:QBIA-}CETeQBIyA..fCQśBHA0CEmIQťBI2A/C3EscQůBIA0YCk&EQBIA0`C bQBI'A0*CRCƗQBHvA0C`EV;QBHӄA0}CFQyBIyA.mC>E)QӃBIOA/CJEbQӫBHfA0#AHCKErTWBM%EAH'C˅TWBMPAH5BLTYBMHaAHyC YEJTYBM/AIClTcBMADBEqTc'BMADfCSbCTcMBMeAF9BBrDTcBMAEyBĵE4TcBMAGB!E5TcBMAG7@B.C&=TcBMiAFNBOwEQ02TeBMAF^BExTeBMmAFBVEJBTeBMAEB0F27Te BMAF|B.XF<nTe$BMNAHC8ER0Te%BMUAH;-B3ErTe.BM0ADjC 7DzTgBLAHlCzStFjTBMPAFBDC8TBMTADC(DTXT#BMQAB-BDT-BMaAB(BpT?BMAFB-D/TCBMAG_B[ETDBMAGBrEPTBM@AHCE}TBM.AHSCE휹TBMT'AHNeBETsBMAEnB@ZYBMAAuBXE{JYBMACABŬYBM{oAB(BEYBM7ABXgDmFYBMAAYCFCX)_E$Y ;BMΣAA;BDY CZY BNwA=CzBGY !BNAAwD!F6LY #BNA@f`BEY &BMAABִE)/Y 7BLpA@6MCFeY JBNaA<υBTE"FY SBNOA>GC%D|Y ]BMUAAmBfiDY BNgqA>1CEοY BNFA?CB塓D_Y BMҾAAB-Y BM AAXB$EezY BL A@>#C-iD'RY BLQAAdCF2YBN^A=CfDYBMyAABBE=Y BMAAnBMB3ABwB֨F 9Y?BMQAB1B?DƹYGBMgAB|C F\YHBMABDB/mCnYIBMAABlF<YQBMACFwB°FYRBMWABN&NFjYSBMAACxtFUYyBNCA?BDaYBN A@J>BᔡEYBN#A@B BNEnCYBMAA9BKFH#Y"BM> ABpBM;E:Y"BM AABE$Y"BM/?AB;B0EYN"BMAC[B8VCuYSBMULAB׽B7CYSBMuAB&C FYV}BMp&ABBEfYV~BMNPABB)C'{E7Y\BMKABrzCWFDNuY\BMABq3FFJY\BM3AACKYFiY\BMABWCEY]BM}AB6vBF>yY]BLeAA4BL F GY]BLXA@(C7sF8Y]BLA@QCŵFwY],BM[oABBY]HBNABMnABC?FY_?BMDAB BEY_BN[A>KC *EY_BM*AB#YBWEëY`"BNEA?CoYaBNBA?\C D"YabBLA@CbDDYaBM-AABD(FYbBNAYe.BMACRC%D-Ye/BMABvEYeJBNSA?C'$F+1YeBN4=A@CqDsYfwBLA@nC~D}%.YfBNA;BbYfBNeOA>CF0YfBNA=|C*FȅYhkBMߒACT$C?FOYhlBMAC)^FYhmBNA@BWF)ZYiQBMkAAB*DYBMAEB}Y#BMXABPB)Y-BMqPABB(E#YoBNA@͜Bq*E}YqBNA@NBٚCBYBLubA?K]CtYBNA"A?@ȀEYBNFgA?bC YE1CY BMAAB5FUYBLA?+C=hfFukYBLj~A?K9CyL]-BLOA?JCɺ]>BL<A?/C%`] BN_sA>CYD] BL A>C" ] BO|A@{A_7GSK] BO^A:[BEB<] HBOrAͨC,E]VBO2|ADBL!A>[C?]MdBPA,HCC]V6BNA=ՖC0]WBOiOA;Bk]ZPBL:JA?+C1]^BO$A;B/E L]_BN$A?yBFÛ]_BL0"A?C>D5 ]bIBOXA:ȰB}eC$]bBN_A;%B;EXI]fwBL[A?CIh]fBOA<B!lF]fBNE!A?$Cl]g@BOAGBC]gBLXA?IC!EEPX]gBLpA?JCm]BL7A?#CD>]BL5 A?CT]BLK.A?HCC4]BOBA;-BC`dBJuAPJD^FVdBKHAOm CE TdBKzLAOoCdBK3AO:ClDdBKZAOcC9dBK5AOCdBKAOS+CcDdBJAPr~CFodBJAP8DE8YF3dBJ-AP"DFdBLASC\EHdBK-AQNCFdBKHsAPЀDfKF,dBKIAQFCDԹdBK%AQ%NCFPdBKKAQD9F'dBJڼAOD5WFdBJAPCE3^dBKgAP ACD1dHBJAODHdBKSAOC#hFdVBK=AR^CCdBJAOD D'ydBJAO8CFnLdBJhAPxDOoFC/@dBJAODDWEِdBK*APC F7`1dBK =APCD)D( RdBK#LAOCdBJbAPpDhFdBKGAQlC$qBdBKAPC EBJANWCd7BK AQ{D5Ead8BJAP?C E d9BJ^APC%EdBKAPC@CDzd BK #AP7CLeBLAM5~[F beBM|ALC'E(WeBLКAKC'UEEeBLAKCD4eBM&ALC,Ce BL!ALQC}F5g e vBMAM.C=EDe [BLAM(LCL$FkeBL,AUDOFeBL>ASC F+leBL"AT7DhFbeBK7AP@CGE6eBKԌAQ?rCEeBKHAO4C|DeBKARDxF)eBKAQ|UCE8[eBKAO CHeBLuARyCDRbeBLtAQlCiD`eBL9AQEC"7PEceBLARCtA;Eq&eBLAROC֤FkeBL}AQrCWF'#eBLAPjDJSFYe&BL AUBLe1`BLAMäSFe1uBLAMC;Foe1vBLRAMk4D'`FAe1wBLigAL.CFf'Fe1~BKȫANMcCIFe1BLbAQC`Ee1BKAN iCe1BL2AAQDFdCe2 BK KAP]"CLe5BL=dAW&\ChC[eN6BLwAMfsC.E eN^BLAPC|CF_eOBL9/AQC/HFGveOBLAR`DZҋFneOBL4APDpFj eOBLcAMYFUeOBL2AN*SC9FdDeP=BL)AP7DTďFeP>BLpAMϣF2eP?BLALDRFePGBLALkC~`tE(ePHBLڻAMCfFG!zePIBLAKC!EͱePBLHALCZF[tUePBLAMPCdFLZePBL7yALՃC+F TePBLAPCFKePBLAPyWCHhF`eSHBLOYAMYDfF0xeS\BLALCE2FߖeU3BL AL5DFQeU4BL'AKCwFBeU5BLoAKC~EtpeUBLIAQ?C[0ESkeUBL=AL#D#gFeXBLALC1Ge\:BLAKQCRDlIei[BLBALCAEBei\BLALCWOZEei]BL>AL_C5PEOyejBL)JAU$nCfEejBL,AUJCFNejBL7AT.CF+CekBL/aAUOOC@EekBL=dAW&\ChC[elRBLAQ^CIEj6esBL61AUCHesBL3;AUaDb'FzesBLdhARQPCFev,BLlATf CFekBLAM^C+JFelBLAMD DWF^emBL7ALDUtrF+euBLyARDD,kFevBL9HAR/Ci gFpewBLAQ0D$-FeBKܫAOC]EPeBLARPF.fBBL1IAVCFfLBL2AVUCF>HfBLGCAUCbF*KBfBL0AXN{CfBL6AXnC8EVfsBL9AXK1CE1CfuBL;AWBin E*fBL1aAVCcF]f:BL6bAWCFZcHf;BL6qAU`CĖE>ufBBL:AW%BaeElfBL3AXcCmD6f'BLAT0C5wCof*BL$ATzC?E f*BLIAU]#D^Fsf,.BL=AXIBCCwDmf.JBL')ATCDE;~f1BLATW C_GCf4cBL3AXeCԔD%Ff5BL:AW{TA4z F6fOBLdAS^7C|EڠfjBL1AV/CduFvrfjBL)AVCAXSCD ofBL1AUGC傼EFfBLpAT8CDufSBLBKA3qCD9[4hsBJA/9CVE5hBJA.iUDbCl)hBJA.9D>EyO-hBLA1 iCL'EhBK5A2rC,`EhBK5A0CBFhUBKA/QC7FAhVBKA/dDdF,hWBKA,CD]h^BKDA.C2F)3hBL A0WC*uE/MhBK|A0CBhDY'hBK[4A/_{qF%hBKOA/YC:F)hBKdA/5}Fwx_h}BK"A1DFV=h~BKoA2wChBKA0ّC;Dh#dBJA-DExh#fBJA.CăEh%BKiA291ClGF6h%BKϨA1\CxE߽^h%BK>A1#TC( E{h%!BKA1H8BvVxEh%#BL 3A11CEψh%'BKA3DEFkth%)BKA27CQ5ENh%Deh7RBKA0NCEBnnh8@BLXA1CPCE[Eh8UBLA0BEh8VBKA1CADJgh8WBL #A/PCqcBD0хh8BLA0_ F@h8BKA2Cj_D;jh8BL,7A.D7]Fh-h9BL]A0'CCEh9BLA.fFh9BL8A.qCR[ Fhrh9BL(A1 C-Oh9BLA04#&Ech9BL@A/HBFF*h9BK&A0C~Dh:hBKɩA3C)UE 0h:BKUA.4K*FĜ4h:BKIA1CԧLh:BL!A/`C,Eh;BKA1Cɸh;&BK/A/ΤC7Ee9Gh;CBL(RA0W!C2Cfh;DBLTA/]CeE6]h;EBLI3A. C4FE76h;LBK5A/<ÍFh;BL$|A0\CmME;h;BLA/}CoNmE#h;BLA/CpEh;BK?A0CCҼC܍h;BLQA0;CE[h;BK'A1S/CFFwh;BKA0IC6C]lh;BK*A/uGCDaF=h;BKWA.T_C]Eh;BJIA-D nE^h;BJA-OoD=E8h;BKBA0GC>EFh<=BKA0CDFbh<>BKA/F%vhCth&BKmA/5C1*F8h>NBK̉A1Cs 0Dh>BL?A0KCh?BJA-cDEGh@BK+A0CĸF,ch@BJ(A/ɄCE3h@BJA,DtE!nh@BJܢA,lDECyhAFBKA/xC#uF&UhAQBK>A1CpPE\>hAnBKA2oC+ChABKԻA2)DǰF@rhABKKA2&DF NhB^BK;A,DeDFhBBKA0wGB0!FjhBBKA-ҶC}E.mhBBKA'^C:DwhBBKA2n0CtEQF9qiBJzA,:(Ci;BJA-DPKF0iBIA.=C{F6i?HBITA.+C`NF"8i?BIJA/C6tFK*i?BJrA,D#i@VBIA,ȟCE#Wi@BJA-iC"3C]i@BJiA,D LF.b^i@BJvA*ӢDsEiABJ" A,8PC*FiB^BJFA-CٍDiBBJiA+Dv5EWniBBJaA+DEJvliBBJR{A+FDEoxiBBJYA+aCF/iBBJfA*ύCɡE,IiCBKbA0CشZiCBK/A0CUDPiDBJ׭A/*CiEBJnA*iCE;iEBJ{A*CE}iE BJmA*iHCbEFiEWBI#A.e CE;SiEXBIA.&CF)5iEYBIA.C`E$iFBJA,g'CxF^iFBJnA*GDNDRiFBJvA+D?EniFBJA+ D ?D€iFBJnA*GDNDRiFBJ.A+ DiGBJgA*rD~XE&iGBJ\A*$D4E+iGBJ^A*o~CuD-iHwBIA-iCFigSBKAC/il BKAIC{iBIA,CFiBJ'A,C}F$iBIA-CJF}OiBIކA-.cCNFiBIA,C㔠F[iBJv/A+-D dE+iBJ{'A*C EuiBIA.mCwEiBIA.fClBKA%y#C?Gbl BKA%KhCEl BKcA$ՕC=bENCl BKA%3CMj#FϵlBK2A%7`CE㊾lBKA%l7BK A+CB4ELl8BKA+VB"FEl9BKA*ClF0Nl=BKvA.C l>BK"A+;C E2l?BKA+CD-ilGBKA,bB&\FdlHBK.A,nCFcFlIBKA+CEF(WlQBKA/HC EClRBK A0C GElSBKA+_SCPoEF1lgBKA.hClBKA.~}C'xFT~lBK[A0+CDSa9lBKSA#D< 6FdlBKTA!DPF։[lBKA I[C5F8lOBKA0ZCDF̠lPBKA,&UCEo|lBK{A(CgEVllBK%A#۽CGlBKAA, C`,EPBl5BKAC߁D9םlBKA*fCSF<l%BKVA(C3Fel&BKA)dCEl'BKšA'}CEܟJlBKA'BC}.F$hvlBLA$1Cb#F0lBK~A%`CsFs3lBL0A$+"CYElBKA C¼FlBKnAC.F(lBKAC+-Ej3lBK.AC喉CClBK؆A ACMF1:lBKA `C|D`%lVBK3A,S@Cql|BKA CDYlBKֈA QGCy Dغl'BK&A )1CElBKA1CLl 1BKA!CC1F;lBKA5]CFLlBKɾA.COEUlBKA~Csl4BKA!+hCFv"l?BKRACEl@BKAbCCglBK6A CESlBKA CdFSNlBKբA kvClJBKAMCDǢFlBK-A C{5D6lBKAm2CF.l#BKǽA1[C_D-l%)BK7A3aC}l3BKACِl4EBKǫAH`Cl4cBKACE"l7BK(A1NCܥDl7BKA3C2CRl7BKNfA0CPD)Ul8BKҝA'C 5Fwl8BK¤A'̂C8_'FdXl8BKA$> :AG"Bl8BKA&CEk}l9BKA%)COFƚl:BKnA,CHE Vl;BK!A"DoFa?l;LBKA1/CCpl;BKA%:CEVcl;BK|A$CFtE}/l;BKPA*Chl<=BKA0^CŁl<>BKA.CUElDduFl="BKUA+C?@Dtl=BKNA%CFl=BK0A%_CtKF2l=BK\A%C[Ekl=BK.A0gCDZ!l=BK AqC%D{l>BKA)D/mF=l>BK=A)D F5l>BKA)ID Fcl>BJ_A*bNCDDQl?4BKʤA'%qCT!CF!dFlA(BLnA#7CF3\lAFBKA.]ClAVBKA16C2 lAxBLYA#CnfEälABKA%CԵFa lB^BKGA*BD'+FlBrBK0A*C*F+HlB|BKA(8C&FA?lBBKA.µ]AFclBBKA*vyCAF۪lBBKA!C(KF7lBBKA ll BK&A}CD/l;BKA!DFLlBKA"l CF9NlBKA/.@ClBKWA-CF.lBKA%lCEJlBKA$C+E~*lBKA%{CF,;nBLjA-NCLḘnBKA2;CJ=nBL[4A.QC ?Fi"nBLNA.)BF;4nBLsA-LCF FEnBLsA.C5FAnBLs/A.MCFCnBL.A-kFCEmDnBK(A38CCӥnBLQA.CnBKʭA4UxCnBKA1CϓD nBK'A1 CD$nBKA1+Crn%DBLqwA-2:C5$En%yBLwA-ieC}En6BLu`A-PCg HFn74BK A2kCDCвn75BLcA-JCW;n8BL%A/C1\n9BL#A/RC$Z^n9BLSA.?iC$GERn9BLIA.UWFln9BLPA-BlEn)n:BL=A.F n:BLr*A.CFC)n;CBL8A/CĠtFn;DBL'A/6 CE4in;EBLEA.%CF0%n;BL"A/ZC(?C7n;BL7aA/ C"YE’n=BKA7CHCaIn>NBL2A/\CX`Fenn>BKrA2CFpn?BL #A0C@C(HnAFBKA1C?C}nBBKYA2mCKEZ8nCPBKA2 C nDBLI&A.'CE8FlnDBLHA.oTC&dFQnE/BFA4cįH*nE0BLA-`A?1C&rFipo"BLXA@"C.Eo=BL."A?C !o=BLStA?JCAD~o?BKA>CPoB6BLA>CD:oWBLA?CDEloWDBLVA?JC}oWlBLA?c CYoZ BLA?WVC`\oZPBL?A?73C&D.Qo\mBLA?C8̘F5Uo]fBLX}A??CxuF*qofwBLA@4C&iF*a0ofxBLsqA?^CAJE޳ofyBLA?C4%0F?ogBLxA?U]CTEGHoBLLA@"C8SC oBLA?C$FnOtoBL?A?vCT FJotBL5A@CۚCo|BLA?}C;bF5%oBLqA?CI[D6dpBKqA>fCSC pBKAAqBFpBKA@CE,pBKyA8nCr$E:BpBKyA86dC/pBKyA8}CIHTEMpBK{A9C^D$pBKA=ACPE/pBKA=N4C^E4 pBKA;'Cko+FvspBKY"ADNEd"G|(pBK A`CWaF.p.BK}A?C%F p/BKA>\CoF_ pABK?A@snC͞VF pBBKA=DFNp&BJA=UCEDDpDBKCA?pC-FKfpcBKA=CoD&fpdBJA=CwDa_pmBKuA=C^ESBpnBJϵA=ooCۃ FpoBJA=CEcpyBJA=CFQpBKlA>[CC/pBKoA=uCDR{p>BLA9jĻGF)pBKQA@3\D"qFxpBJA=_DޤF,pBK\A>vBFLpBJpA=CC?pBJ[A=7CD%pBJSA=,CѼE,9pBJA={CwDpBKyACECR;F @p6BKHA=CņGF W,p@BKYA?ӢD ?/FNZpJBKA@CDFPBp BLEhA?CCD9p BL9"A?'CC p BLA>.CDp"BKA>CĹp_BK_ AE4CDpBKrADCD-pBK?AK2CHp4BK_AINLCjpBKW~A@MD_@Fp#BKAAB EWop#BKA@]C^F,mJp#BKA?aCvOF :p$BKRAAzD_Fip%YBJdA=_C7ZDp%mBKHIA=\?FG9Xp%nBK|A=C?HG nDp(BK~AC?CD+p(?BKjNAE9Cp)BKABvC,/Dp+HBK4ABC+Fp,BKVA@ՓCwE p-;$CDFp?CXAFp=BL6A? CA1Eާp=BKYA;QؔF0p>BK^AAE`8FRp>BKAAC>F.Dp>BKDBKA@_B@aFp>YBKAB:UCbxCp>[BK"A@DCjLDfp>BKA?CEp>BK\A=HBX~FNI p>BKAAB/bFp?BKzA=C:FkGp@BKyA84AC&%p@BKmaA@~D>vF|KpABKA;\C^#pABKyIAQCϪEipD4BKfWA=>Cf/E܀DpDfBKABB[tFn<6pDBJލA=CpFRpDBJQA=Q CFZpDBK&FA>CFpDBJYA=CFKNpDBK{A9CW`pC2GpFBKfA>A'IFpGBKXA?c;CNFpGMBKysA8CzE_ pG^BKwA?IBoBFMpGrBKdADDLeGd pIBJA=MZC EpM BKAB#CCDD:p_BL)A?}C]/pgBLElpBJA=CEipBJA=CJpBJA=l$CgEZpBJlACD3Y?pBLFJA?DCVpBKRAI{CpBKABC:E1qBK A;qBK~ A;BXFqBKzA:(C㴚F,CQ'qBK8A5lCE zqBKA4CEq>BK"A47CǜhEqBK`A=CDD qBK\A=CC~qBKz8A;)CpDqBJA=4C-DRqBKqA=ZCXqBJBA<(CEFqBJ%A<4C匐FygOqBJdA<]CƴFb6"qBJAC}DqBKA9C&\)qBK?A2C2Eq,BJD5AA=ICbjFFq@BKrA=C#Cu5qBI`AaD6qBKABeCqHqBKA:CFqBK5A8pD.V"Fq#BI#AC `EŇq=BK}A;SB-Fq=BK}VA7'C^Dq>BKbA<+†Fwq?*BJEAuFqABK3A;TF4qABK|A:eC+FqABJACCpD tqqDBKjA:CFqEBIA= DM~FQHqEBKˈA4CCÃaqFBKˤA3z#C4Ee qGKBK~A9CJE=qGLBK~A:NCFE(UqGMBK~A9C0F/sqG^BKyA=CۙC6qGBKA2ClE$TqGBKvA3%C5ErqGBKA2C}"E8 qGBK5A6CCF~ qIBKA2C(EFqIgBIEA<ҎD͔FB-qIhBIAC⨴q?BIAA<_CdjF5qBIAC‘qBJA=+CCqBJA=C\CtqBJ}ADOs1BJrtAKa+CZEVs3BJtAKbCWE$s9BJAKDC&Em.sBKAP1ZCsBJAJ/CF 5s#BJAKC1Ds6BJ AGhCtD(ls@BJzAFq]C) ERsBJAOpDqA8sBJAOyWCJFxsBJcAO2D-EsBJAOpDB5sBJ:AOHD,sBJAK=CMCs BJrAKSCE&s BJhAKVCsE@ s BJ`AK]"CyDNs BJxAKc=CEjs'BJUAEptCF`ss(BJ[AFCߧ:EAas(2BKAP0CiCMs(BJkAG"jCmNEt?s(BJFAKFCE4ms(BJAK?C Es(BJyAK7C#}Es)BJAO|fDިs*1BJ|AK@C*E!;s*3BJAKIHCE4q;s+BJ AKJCs,LBJuAOaD +Ci;s-2BKCAJQCE@cs-BJrAK*CϐDs.iBJAJ@CocFFs.kBJrAK+CǰDJ*s1mBJAG`CnCs2HBJANDl?1s2IBJAO^D.C=s2yBJAFCpE~s~BJAO'DosKBJ}AJ@C SCsBJfAKSCƺfEsBJ]=AKxCbCU_sBJ^ AKpCe;Cs"BKFAJZCs BKAPB-CRC;s1BJAFSCTtBJhAJ3E>G$tBK`A>`CUEtBKGA>NCwBr:t#BKyACԜCUF4Kt$BKAC CZE:t%BK}ACgRCD'itDBKlA?:C=tBKs,A=TCBtBKpADe\BÕFEtBK>CAKxtC>wt PBKD!AJOCqE+tBKpKAMCZtBKZ*AN;BG[tBJϏAKlCR,F ZtBKAI! A8s G,tBKDAKՑC oF4Kt_BKg#AF?AL_Ft`BKWAF6 F;ZtaBKWAFCF_tsBKCAIsCxF0 ttBK1AI C|EtuBKIAH[@aF}XCtBKTAFmCxEtBK^AG\kCظFtBKmoADBIFjItBJAKBNF+tBJAJN~FxVtBKAFgD;1G#o tBJ2AH_D "FBtBJAG C֣FA"NtBJAGzC׍tBKIAICF^ gtBKU|AH@CCpE?tBK\qAG]G)FtvBKANjCwtBK&AO٢CpCCtBJAN0CDF?/tBKAGr%FtBKVoAF=CEHtBL\ANCCDtBLnAM-C^F-ptBL\ALCQ3CvΡtHBJAO D C JtRBK>AJCFt\BKAK'CFP/tBK~AM9CbFF%'tBJANCDOtBKAGC_FtBKUzAGfC*Ekt$BK%AJvMC F*At3BJAK/.C)EtaBJ&AJV5C@E8tjBKWAF#xCsDtuBKGAI:CF$ltvBJAICEXtwBKUAGdC+%FEʡtBK0AOC6BtBK7AOC*C+PtBK2AOC$C,tBK3(AOC۽tBK4AOCgtBLAN\hCF}tVtBJEAO D KD@tBJ}AJCEǥtBKAM8C>DtBKFAK;CtBJAMDFfytBKAM#kC, D--tBKWAM!jCutBJAM0CF(ritBJALC^FtBK2AOECsTt#BJ-AJCԼF&st,BKYkAGDvGGDt6BJ{AFDC+E7tTBKAIeCFFt^BKAM8jDGotBKAM1CFStBJjALSD*&F3tBK$gAK :D EtBJAO&D DƀitBJ(AOlCǡ-FZtBJANCy'D/DtBJANtDE"vt'BK_AIHCD"}tvBJ AKLC.F{txBJ.AJifEFJv0tBKoADkCC*F 6tBJAHCltBJ6AHj!CҁF9JtBJ}AK[XCވF jtBKAJ*Cu5DtBK)AJxC˖2Et BK{:ALJCƒF_:tBKHAKC8E7tBK4AIeED bGAt4BK>AJBDFcBtgBKAL_{CCBFsthBKAL{CB|;tiBKALn?Cddt! BK%sAJuD0F?It!*BK+AJ!CCCFht!RBKtAIC?7F!.t!BJAIDEƒXt!BJAID F*t!BJAH$C&D/8t#BKAACŢt#BKA@Csct$BK}~ACC6E|t$BKAKCFt$BJ AHXC%t%BK\AICEO+t%BK_.AH˂CG)t'BKiAN}CCEt'0BKpAMOCC2t'BK;ALC`HBt'BKGALCۊC`t(BLAO-_C CZt( BJAJwBCmFK[t( BK/AG$_Ĩ&GAt(BJ7AHCȨFt(BJ&AG@C>FLt(BJoAFpbCނot(2BJAN΃CCʌt(=BKVAF6Czt(>BKSPAFxCc&E-t(?BK]~AF=CFL)t(QBK'ALEC\EDt(RBKLAKcxC[IF.\t(SBKAKDO|Ft(BJANѣCMDkt(BLAM'C>FRt(BJAK/{CGEV[t(BK)AN.^F}9t)6BK1AN}CFt)BKAN Cw?at)BKԉAMHCt)BJAHFCE%t)BK}AMD3ёF't)BL1ANXPCnFt)BKSAIC,Est*&BJAIC.Ft*3BJAKADpC?t*XBJAJCF6St*BJAICDRt*BKAKѠC9?F't+BLWAN"C|$C4gUt+HBK MAFkC'G 9t+]BKNAId Cj{E޻t+^BKKAI_DF%t+pBJANIC4Dsۚt+BKAI?Cbt+BKAKKBīFSCt,BKįALlCFHt,$BJAL8ICAFNit,9BKX+AGOD'ʋFLt,:BKQAFCREލt,;BKX8AFTCFyt,XBK%AJoBCp~"Et,lBJAL CӃFt,tBKAAKCfoEOt,BJANCE{t,BJIANC:ETWt,BJANXCDcȂt,BKEAJ4C(E]Zt-BKABWCjD9t>BKABmCtDfBK~4ACIC]QE1tDBKA>%CCtGrBKACXC FʜtM BKA>C JtP>BL]ANCC䈵tPBKlAMkCeAE9tYBKABMClD3tWBKAMzCF=tXBKUALCEltYBKALvCKDt~BJȖAO=CqEqtBKshAD{iCP1Et[BKXWAFƒF"{t]BKdAEICޚFӅtBK0ALjGCtBKQaAJ?FGtBKTGAH:DOFtiBKOALz=C9Dt;BKrALDFABUt-C%CuBKkA>J2ClCuBKdAAD3GuBK|ACCBuBK)A?eC7uBJmA=yC E "uBJa3AGKC#CVuBIANCçCu'BJ)5AD"Cu( BK?AKCu(BJiAF ICȣEY?u(BJ.ADD/CRtDLu(BIABhCDFu( BIEAB_C!F#u(!BIAC @C E{u(eBJbcAECDu(fBJ_AEw%C{OE=u(BJg:AECEkPu)#BIABOCVExKu)$BIAACF.uu*DBJACCִ3E2u*NBJACCXEfLu+,BI}A@Cu+-BIABmC&Gu+hBIȸABB&C3El u-PBI gC¾Cdu;BJA=|CD([u>BKwABrYC$uBBJS0AAG2ChE>av>BK|ACCD&v]BK}AC_CvBK=wAKk;C2CH]v@BJAK2CvBJAHCCɌviBJAI]DGF̥vjBJAHC9F!vkBJLAG7CECqvBK?AKC D}vBJ,AG(CR-{BKBAOD||Fт{BKBAKCO]De_{ PBK@mAKpCcD"{BKANFCFHB{BKmAM CW`FPP{BKzrAM$C&lF6o{BK0APD&JFT{BKAODF8%{BKANC F%{BKAPICF ӆ{BKĿAPTC7eF( {BKAOCeFHE{BKAP`B}F^C{BK{ ANCFD{BKY`AN~\CEEi`{BKX;AN$CEm{BKGANqD/mF{BJCALCsE{BKPAL}[C^DE{`BKLAGeKC8{BKAANECFox{BKANRC&Fa{BKJAMy~CF"{bBKANSCF){vBKAMHDf#wF{BK*APBCE{BKAOw CU{BKE1APqCdC {BK+AQCv{BKuAPJC)F{BKAOC7E{BKAOFCgzB{BKFAOD\'E{BK;AOCE˞%{BKXANhCaF"{ BKaAPCXNFp{BKAPBF\{BK~FAOiPCmEr{BKdAOD[EU{BKDAN`C9 F|Ǻ{RBKAMlgCd{BKӥAMC){BKEANFCDJq{BKsAICc&C{$BK@AKWC\EfU{jBKW+AFCDI3{BJ#ANMC3aCnp{BKYfAN^jCE#{BKPAN CFEɍ{BK\AMoCEf{BKl!ANs$CEȪ{BKHANCF7{BKaAM@D0}F#{BKJAPFCӳD({BK*AO³CHEf{BK;[AOCsrE^{BKXANHD#bF*{BK6JAMMB؃Eu{BKNAM0C4@E{BKDAO^ERd{TBK>AK~!CTzCe{^BKHAMfCTF{BKIAQ(,C#{BKAMCJ3FR{BJAP:C>F)d{BK#fAO~CuErH{BKAP CGD {BJANC܈CI0{BJPANCgDl{BJANCmCܮy{BKlAPOC>Fl{BK}AP`C|\Fze6{BKyAOiCF7^{BKLAPCJD4?v{BK0AOCۑC^{ BKy"AN!CSF{BKuAKRCW)F{BKKAJCcG3{4BKDLAJCR{gBKAOD2Ghe{hBKjAMD`F{iBKAM[BAF{! BK)AL/CC&{!!BK[ANDC~F/_{!"BK8AM>CgdE{!#BK9AMChE#{!*BK@AKsCކDb{!gBKAQ+Cb{!hBKQAPCE^ {!iBKAPF_CF {!BKǤAQCM3Emn{$BK,ALgC̞FN{%BK?AKXC{'BKAOCuF7{'BKaAND)SF={'%BKAPC/*FaJ{'&BK|0AOCP6Dn{''BK7AOڍCAEF {'/BKAN CuOkE'{'0BKuJANAaCBE{'1BKANm1CfE87{'9BKeAN*BE{':BKsANClD~{';BKUAOQBF308{'=BKANzBҾE{'>BKvANsC&E{'?BK,ANQCmET{'CBKjAO;Cj Eal{'DBKIjAOgeCF{{'EBKANQʻAFLa{'WBKTAOCn0Em {'XBKNAO IC~E5'{'YBKfANۊCJE{'aBK_-AOCcE=L{'bBKZ(ANC9dE\n{'cBK^ANCEtI{'kBKIAOCF^>{'lBK6AN!C$F)y {'mBK`wAMiC:@EU{'uBKUANlCCn{'vBKOANSCEMU{'wBKWQAMCE{'BK59AMVCyC8F`*!{'BK'AMzC,FR{'BKG(ALCE{'BKKAN(CEw*o{'BK@AMMCiD{'BKIAM#C"E^({'BKGAMҞC8E${'BK?AMMCg#E={'BKEAMCEK{'BKRANpC^(C {'BK3_ANs/CuE{'BKLCANrC!aE4{'BKHAO`CHE!{'BK.ANCCiE@{'BKLLANC+EAOCCiE~{'BK'AOCXWEf{'BKNeANCiDҩ{'BK9wzFd{'BK5ANlCG2=E{'BKDAKuCEp{,BJAODCb {,BJAN#CǮ{,BK\&ANɋCE>{,BKTUANˠCD{,BKXE>{VBKAOWC [DV{WBK̤AMCD4{iBK=ANCF"{jBKXALCED{BK5AQ>Cj{BKeAOCiC K{BKFAQC{BK>AOC|WE-K{BKBAKCE2{BK:ANOCƮF@{BKALW C^ޭF>{BK?AK$@ܿF0{BK)iALCPE,9{!BKAAMCĜ\F5v{"BK5XAL C7E{#BK@AL~CEK{?BJNAL7\Cی{@BJALX2CuEv%{|BKAL!C`{}BJ1ALuCDݽ{BK@tAM4C8E$bx{BK8AMC]E6W{BKBAMnC/D. {BKBKANCߦF{?BK1ANW"CJEp%I{BKbbANiC5D4.{BKKANpDD98FW{BKsANh9CcE@ {sBK7AKCC7{BK~APSBCBFއ{BK/AOYC~F{BK#qANC?E{BKCAN:C7 E !{ BKKoAM?CE9#{ BKk[AMC!F*@{;BKb"AOHCd6E{C(3Fm3| (BQ]A:BOsFQ;| )BP*A6S (FB| 2BQBA]B (| vBOA:B9C| BPA8tB| BO3A:hBC]4~| BNA=JBU| BPGA8:,F| BPfA8BB|E\| BP*^A9tB7E}| BO9A:IB DfDC| BPHA9eBE[| BPIA9BCEk| BQ;A;BStA=ū| BQ/IA<7FS| KBPmA8BEV|BQ4PA<B E9p|BQ)dA;0B=|BQ;A;BU=|RBQ4AyFPU|BOA;!B]rC+|BPN-A9 6B=@E|BPA8BdzFMȡ|BPzA8)B0=Ee|BPA7շBDaT|BP2A9WD)Fp|BP@A9Cv#Fb5|BP~A8JCJ=EJ6|BPA9DxF1D|BPA9śB ME|BP2tA9B8xEr|BPA:#(B3E |BP/A9RB/Eؘ|BPA9!BF1|BOlA:MBfE7|BP3A9BWF{||BPA==B`CqY|'BPA5PG|1BPA4gۂF|;BQA;ߝBnFT]N|A5BEt|ReBQ86A5؇BD|SBQIA2CxRFc@|S"BQNsA3C~EL,|TBQHA7TBrD|TBQA7awBB+AE.|TBQA7AB[~mE|TBQ|A6,CׇeF4|TBQI$A42F$|TBQDA1C"E|TBQA.B B|VBOA:&BF9|V.BP9%A9BD="|V/BPqA8xBhE|V9BPa_A8̹B}E^|VABPA9Ca9E|VBBP|A8B&EY|VCBPA8BHhF|VKBPA92q3E|VMBPA8(C7oEm|VWBPA8tBD |VaBPOA:@(B{EN|ViBPAA9BFlD|VsBP͸A:ONBE9|VuBPA:YBE,j|VBPA:MdBpEq|VBP;A9BdcD2$%|VBPЩA:BLF|WBPA9QB0Ey|WBPA9OB"Dݰ|WBO>;A:BuD'|WBO9A:ڟBE}|WBOkA; Btgm|WBPA8Bk|WBP/A8RBE|WBPҼA:{B E)|WBPA8IªbF(xM|WBPA9B?|WBPaA8B|XBPbA8jBVE|X7BPqA8uB^D|X?BPA;vB),qD|XBPA;B/gD|XBPA8ZBkjE"Lr|YuBOA;BuC|YvBOZA:!B2F|YBP,A4wFa|]HBOA:nBiC(,|^BOA;ZB|bIBOC7A:aBE:.|bKBOt;A:B#E|eUBO>;A:BuD'|eWBOAOA:BxR|IBPA2CX9F|JBP;A4BF&|KBP^A2*QCFZEK|BOA:BFB4|BOA:~BF| BOA:B?F6'|BPA9aBo~Eji|BP3A8uBEy|BQ@ABP1A9BE|?BPK4A9B_EE}BPAA? ӁG@8}wBRAO!,B:J} BRANB4GEV} BQPAHA[E(,} BOPA:BzEӰ} BQ A=yB1F} BQ;gA;Bt} BPA:`B QE} BQADL3*F ,r} BPADBF"1E-&E} BQ+A<HF,K} BQ A=@B5nXEګ} BQ+gA} "BQ AEHB'YcCk} #BQAAE} BQDPaF$-}BQ/A< F^yz}BPA=D"F~}BQA>DFP-}$BPoAC@!BχE%}VBNGA=C Ф}BPADB4CD}BOAFNBG#B}BQ=AGB%/\Fse}*BQ)ADME}RBQ;A;BJNAr{}BR2AN6@ARD1}BQ6ANB7yA}BOA;BE 9}BOAA:iB6F}BO?A;)BCGu}BOA;BIAEx}BOA:B(C#}hBNA=>C hD1%}|BPABB'tD}BOA:ΝBEJ}BO'A:B_;C}BOA:BD9H}BQ A>0D_RF}BPA<BbbDY};BQ(A4AGzA) Fij}IBQAFBB F[}LBPAI\CCr}N4BQ A=xD2&F}NBPA?GB54D *}NBPAAB=&F}NBQ- A<AVE}O&BQA=JBIFt)}VaBO~A:B/E}WmBOA;07B:^}X]BPAAAFQ#}X^BPuABGB7dFpL}X_BPA?gB6F[}XBQA<C}[F~}XBPA=[ }E7$}XBP4AD&LF}BPA@!-B/MF^(}BPA=AE}BP A:yBjsDD}BPA:-Bo,Ey}>BP5A9RBk DBVlA0FAgFBUA/״D1ZqG0BV?A)B7EDq qBU,A,/L@DSrBUA*YB HBUuA,H&@b=EwBVEiBUA+BEBU:A+̗ELxVBUyA+imBJFBUA6 DFA)BU{\A6CnFoBUuA6BSrFd1BUnA4}ÆFBUΑA,:DiFF\BU"A-cFOBU!A,_AQDbBUA5(AwtDdBUEXA6AlBU=A78AߟDo#BUSlA8 CDF,BU1A6xrCFBdBU[A4W>qFyBV pA.~GBF+3zBUA1eBBCFz{BV A.`AF cBUvA1E$F/>BUA3Œ)WFlBVA0]5F}BVA-5C.1FA{BVA.8$B~FOBUA*CxFݖBVA1kB-BUUA7m(B*E(BU80A6AȄCBUA2|MB3AD8>BU>A3ؕKF;BUA4L yF5BUA2@IF:TBUy{A:IB-iBUX9A7oBuE[jBUKuA7AXzEkBUTA7tHBEmBU`gA7A GEt4oBUQ4A7ATD, BUA8הA{7 BUyUA;JB/b BUbA8Bm2BUA)O@uGB+2BU2A+݂AHD7bBUA8W"AzEL|dBUTA=BD(rd\BUSA5sADЈd]BUkA1zB6D)NdqBUA*xB DtcfBUiA1XAFUfBUA4A E3_ggBUA+}BFghBUA+|BâE#DgiBUA+ FAXEeJgBUeA32HEHjBUkA9PADZ'l]BUpA+(AeWEWCl^BUA+B!E-l_BUA+lBZ7E?/lhBUvA9,B*,FlBUA9bk FpBV+A+nGAbTDV]BV4A/·TFqvBUoA27B*CE BUA4FifFbBUA1dFW ;BUA7_t]E(VB{)E`BTxAG1*Brb,Ev{BU/ACt9F-|BTсAEi BF9}BTAC$BK'FTBU^hAAxBNFMBUUABBD/"BU/ AAB=d5F$)BTfAB;B.DsBTAEiN2F'BT$ACRBEBGHA=BCУ BUABgAYFBTADBE3BT}AEB!&EN,"BTAEvBF,BU(A@QBLCBU?AIGCgpF.BUAI4C:E8BUAHB8EBU|ADB8F|BU2LA?BÆDXBUAD?BjE\! 1BUAJhBE( 2BUAI^  Fj BTFAG.B!BUMAFBFHi!BT(AFBwEF!BTAG=ByֱE>$"BUADBzF8v$,BUЅAF?8FI $JBUfBbɺF#Yi BUɃAIVEjBU§AHBw+klBUAHB=}FlBUۧAI~2F15l!BU_ADDB^=E$"l"BU AD$[BzFclgBU0@A?B4wDwg,lBVnAJBh6C/lBUAJB-FrlBVAJ#B9ElBVAJiB{aDq75lBV AJ]BԦElBUjAIbC FCmBVAJPZBromBVAJ{C:EmBV NAJB6Ek@m8BU3A>B0CBG|AB$E4UBGrANByE8uBGzATB8{E%/BUhADwC+UF<BU^ADNC'09FDk;BUJAD/CE<BUAJBuBjDԁiBGlABAL+BGA,BB[BG'xAeBB`BGABwE@K·BVAJ̳B%DOΈBVYAJ&A{MDĶΉBULAIDvFwz΋BV AJBbE(UΌBV MAJBEBGAnBBJBGABoE!BUXxA=C1+F9BUAIC#F:BUUA=_BFU..BUA9jFrBUA:eBFرBUyA8D,bF8SBUA8jAF%BU_AA9z4BFBUGAABdnFBU,A@k%F5BU AABlFJ ` BU,A@Bq"D wBUkA7mAf]D֟YBUNA7B@`EemBU5A64A&BUl#A:iCFmTBUQA>BKgD=BU,BU+A@BTBUTA=B1FniBUXgB͸D^BUADBF\G BUA9=)A/E^K BUwA: C[lF[ BUA9CykF 2!BUyAEd$BxEX$"BUAD:CB[GGBTACXBZ;D5hGBTAE2BDAHBU0A?BFE bBUnA87AoC{dBU6A@¾,Fimd BU3A? C0Ew1d!BURUA=eBF:/deBURpA=B|BwFfyBUAF:mjBUwA:mF jBUezAB*7xF9 lIBUA=uBhCQFHlKBUA=@kE8lMBUA=BND? lOBUA=ADGlgBUMDA>PC F lhBUxA:GD~FlBUUA9IC*F4Gm8BU4DA?ZCDCJEƏ;BUA7AEB E)BUuA9teA+EsBUGA7AC1lBU/A<SBSE֢BUA!}nEbYBW BAX(EdBUAfAx!CKeUBVAf-EoeVBVܖA5E-eWBV`AE/MysBWAA@|DytBWA@0$DYTBVːAn&jC?BW-A7D^BV2ABABޒBV0iAA9D~[QBV._A|̿P5CSBV.EA=@GdAOUBVA?ͷDց%BV1AоAVC!%BV1AAC]%BV-SA!A=B+&"BV/OAmATC&#BV-mAA0BV/mAGD!0BV.DA0A3BV2AتA|42BV.Ar>%43BV%Ao4BV SAB\4BV/AİAUFDY`)4BV1A͑AFCPv5 BV1A.A@5IBVA-5JBV.QAx @ hYAH5KBV!%A-DBaBV.AA"]DbCaBV.AxޣCZaBV.AtQ@0CK#aBV./A@7)wDsYaBV#;AGB+EA(b]BV0Ak!bD&b^BV.A{ADz5b_BV.{ADwDyBV1MAɥA5BqBV1+AAm4BBV/AďABuBV/A?[)D-BV!sA(aě)G)@BV>A%OAUC/l/BV7A'C+>FYyJ0BV7OA(BED11BV8A&յF<ծ^BU]A#Aw?$UdBU]A#AAD\hBU]A#Q Ad iBU]A#@BBDZA{BVBDA%P?լEQ|BUA%בBMeC^}BVCA$cBYD>BBV&A*G"D##1F&kBUA,|CFT:BV DA*n§g#FZ BVCA(BG70EK BVA)DJF$ BV+A'krF^= BV7A)ZAGEbh BV>A&E{ BV>A%AADUBUSXA$H2@{xEБrBVFQBUdA'AY@E7BV5A)lC`FG?BUZA+&A/F]MBV0A(BNBVA.3CnFtyBVA,A-~EJBV A/LABV A,AE-BVA,czA#CdBVA(A&Dc5%BV yA,M1C!F}%BV*qA)CvFA$0BU=A%BCq;*0BU?A&JiA-@EB1%BUaA'rEf1&BU=A% A'EʡI19BU]A#iAATm3BU\A#)A,D(60BU]A#-@l!BNM:BV?A%fB%D7:BVB A%EqA0(EDe:BVAA&(F::BVA)A&@Ʀ F:BV=A&!BgE :BU,A%ϫ@0vCt;BBU]A#\Av;BUVRA'/-C%@NMDeMBV$AQuE{BUOA%BVA#E$PBVZAϻ@ERϛBVAR@DobBVAA;ƯE9ujBVIA?XDBVAh>ICfBVaA A75BivBU A#D7(CF9BTwA"%DEaF|:BTA!MBl#FIBU9A&p@EJBU A&BobElKBU EA&m^(E@UBU[%A#o>^BUZA#AB BU;A&mBA,@E BUA&A!=B EUBU 19BU?2A#yBFDt1:BUA%ƸC|F>j1;BUA#C0F81BUA!NBF9E2BU{KA)E`IE 2BUqA*`F2BUPA'WBĸoF8e3BU\A#B!nE06/BUpA!zAmCE7ny60BUTA# CF!R61BU\KA!=CF&:BUYA'”F6z:BUCcA&LZÅtF.>;BBUCA$Q8Fl{;MBULA%KBLiFQf;NBU<'A%@ZF:T;VBU\A'*B7Fw;BTA&'AB+x;BU A&WA1mBTA$B+>oBTA BFX>wBUy%A) %@tD@@>yBTA&!AVjOBT5A%EIuOBTA&BBJ*D@OBT7A%egC(PE۾ghBUA*C^poBTA%cAE\CyBU]A#AWrBhnyBUhA"Җ0EP)yBUZSA#ԖE>$yBUdA"e•F 4BTc`AhBʲE?$eBU}A)-B?.CpխBTRA!CRB7L BUiA(gFS) BU]A'7B>EBSzAB_EAoBSyAB^CF BSeA=lB\EN@BS'AdBK>Fm@kBY-A +0@H74BXA @gCNQBXA ?T5D,BZA 61FGP*$BXDA6BǪFBY60A sEFDBXJAPAxgCA?XBYjA bFcBYPA2ŒmFdBYA FeBY2AbAF-wB[\A גefFFxBZުAGAbF+BZ A2VA*vB[eNA :ESB[ThA \FO(B[4hA TtB[NA QNEF+2 9BXABogF5xN :BXA ÇWF7qBYZA ×9qF?n7B[kA ¤~Ei7B[`A20c_ E7BY]0A C#FM8BXA.dB)F48BX|AH5F*8BXDAXF-mL8UBZKAQJFn :8VBZA;A$E.m8_BZA^o*E8aBZAa%-DjQ8BZ%jARA8)F;98BZABEƂ8BYBAҶCDFG8BX'A3tu9EB[FA‹&F9FB[A,4,nEI9GB[$AF@DD9BXA ZFs!k9BYA BFr9BZi=AA$խFX9BZ>LAjM@jFII9BZNA%FH69BZ,#APAAF*9BYA2SjAEJ19BYA3Fu:KBZEA^/@QF:pBYqAY%›FJ:{BYgAT|A7E*:|BY0Axv:F1:}BYȘAGA)FwRXBX"]AJAC E!s~RBXoAh'yE RBXAeiEhVSB[{dA4@6CbSBYA v4WCT%BX*A.EbT'BX~A Y9C_T9B[VA keڏ^FT:B[&A \cwaAYT>B[]A "^AFtT?BZAA.EԦTCBYAb#uDTEBYABFE^TB[A&&ElTB[pAczF~ TB[AM:CxTB[^ASCCE[TB[JA=CTB[YA[]^D!TB[,A^F.;TB[fA UDITB[)A[+} E_QTB[A*$FTB[A0+E6TB[A[*E]w>TB[AٽB/FgTB[VABlATBXAI.EHTBXmAE.rTBXABuEڈTBXAF5yUBZTAvAׄF U B[A RFnUBYlABBXEUBY|A#|wFD`UBYABl E-UBX,APA=qUBXA&rF@n}[B[AIRbkE}\B[AeAE%}]B[A{lA/vE^}oB[A3E}BYHAÄUF}BYFClf BXj~A3H@VE BXaAA%eE BXBAJ@qE[2]BWADK2eBWQA{^F* 2fBWrA2fgF#J6BXA,@(F \6BWAK>[E6BXAe)1Ek7BXgA AptGF 7BWHAa[A+oDa8BXFA$AG8BW6Af@rC 48BXAz8BXJAPAxgCA?8BXZAG'CFG9&BX*A@4SJEl91BWALMQ+E%92BWABE'93BW"AA2E9BW|xAZC: BXA p5xDR:BXA D:+BWAߞ_=E[:,BWlA1E{tI:-BW,AuExo:0BWmAaOE4:1BWrA? Ek>)TWBWb8AWE2TXBW[yALF]T]BWUAr;nD2}BWcA^@EN}BWxA E綠}BWAEÀ7BX%AxAD ÀSBXA  F@ hBX/AB)?P& F fBXPlA9a@ÁF:BV.AlHB.BEABѻ\AJgBEoAzB֑2D-aMBEAkB7@nBElAgKBJD;?fBERANBVCcBEABѻ\AJgBEABѻ\AJgBEABѻ\AJgBPAByeBm` BQjAgBi@)4=BQAEBznE>BQACBWQBEF[BQ )AC&.FE\BP'ACLFeI]BPAA;B=RF_BPcABC(lBP>ABBDc BPA ?C$BPLAjBA[BQAOC.KJFF\BPtAEF%m]BQIA BEsEaBQ`A&B1PEs%BQHABDO>QBPABWBQ^AETB2DāZBQcAB D\BQeAkBS'Ca=BPA CXFؿb5BPAUC.?F7BPAXF@BP5A Bg@JBPAXB9EֳBQzA_B`BiuBPA~¦F,vBPGA#BEFvBQNA8BxD BQGA}Bz׉CM#BQWABD=BQKAB}jDBQ]AtBWDm#BQt5AZbBKDX#BQnAvPB}aD A$BQ@BQAC_F)<@BQ>ABxiDT@BPAy3B)xE@BQIA[B} P@BQe ABD@BQe A&Bv DLBQQA܉BU:ED{ LBQS"ABC*LBQS"ABC*LBQ_AhBCMcBQA!9bBpE'MBQ[A4BCVMBQ]HAxB?CŹMBQZAB kD QCBPAWBiBPA|F`6iBP4A|B~FDiBPhAB"BOkBQYzAdB;D!kBQ\~ABDZokBQ\ABD8MkBQ\~ABDZokBQXAB`C|wLkBQaAB'LkBQYABVYlBPAB{?l8wBPABw6FwBQE'AiB|EwBQACF?wBQAB>ErBQ]AtBWDmBQ[YAQBDOBQbA`B0DBQ]ABR9BPA]BDNBPA5B|jOBQA)B-cBP~A*@B&ElBQA B0 B2 BQyAk2B@ BQAM&BeDm%BAA9CCs=BQ=AbBB >BQ~9A12BOBQA^Bb?DX!#3BQAlB@D;#5BQzA:,BT#=BQvAQBDT#>BQAB>CDO#?BQ]AY?BkSE."#BQzAG.BUEE2#BQAtBoDk#BQAB5K$BQ AdUB%hB0i&(BQAdBMoC(BQAB|D1'(BQA_B;pAp(BQ.ABK D`6?BQmAijB?C:&?BQvAjBcAb ?BQAFdBYD@uBQnAdBmJkBQtAIBEWJlBQ}A9AHtD[JmBQ{A81BDlKBQA{A@aKBQABIpA6KBQ*ABDL7BQcA{B-WC7LGBQSAidB@T5BHLBQAi7BrnDkBQA{DB3'kBQAaBĔAmkBQgAnBGEN<lBQYJA8B\KDlBQvAG$BvCURv BQA4$B­`EvBQAAdBWE vBQA>BDrFw:BQYAk.Bl~D6:w;BQADB:2DB$BQA`rB@&lBPA/B^E< zBPABZ+DN'9BPmABCw(BSEOABoC4ABRAA؄C^ BQA/hDLAG uBQA-iCF1BQP(A,^A|FQ}'BQZA+UBWFQBP{A!JC1BPA+CfE+BPA+gC.DBPA-WC4.BOA"CMtFrBOoA!CEm FBPA.C0FBQ.A0lB67Fc]KBO7A 7CA2GB%BQ=A)ӌBf.EBQ,xA*/GCFBEڹBPmA(0BCE BPA(" BBVBQI"A,{DRDFourBQA-gZfFGqBQ5A+e__FBPށA'C$^FKBPұA)<D@}Fv#BPA'3C$E/%BPA$l 'HF&BP.A%wlBRE'BPA$8CءF7,/BQYA+Bz[uEߠ0BQC9A.gGW1BQ=-A*$!B-jBPjA"QBEBQ:A.C_$F#BP{A(!B;#BPA%Bf$#BPFPBP'A&?BD%QBPA&:B䍪DQBPA&B5vD ?QBQA0CEQBP]A0OC8:F(QBP9A.D7FnQUBP}A'"BQkBPA,CE QsBQ -A.OCfF<QtBPYA.D@FE QuBQ6A- c0^LFOQxBQA.AC FOQyBPA.=CPDqCQzBQ=xA-zXynFSQBP$A)D"3E_HQBP׸A*DB^CۋTBQEA1[BD4B9maBPA%$C9|TEfmbBPA(pBBFmcBP A%9XBZE%osBPzA&'B{EYqotBPA&:B䍪DouBPA+lB,qBPA$AF!,iqBPtA%hoBEVqBPuA$BDTFLv5BPA#B)Ee!v6BPnA"B}BQA*kBIEDuBQA+BkEBPA*nDBDGgBP A*1B?+E86BPߨA*2DբESBPA'BPUBPHA'RBW;BP]A'ICtF1=BP A&^BE }BP2A-@^C/7E9+BPA*_B&\F,BPA*jBuE-BP܂A*Q*B;E\KBPgA02tC1gF4BSBP·A&]BDRUBPbA%ңBqBPA&AFX9ArBPwA'/C59FHVsBP-A%vDF=(BPA%BKEBBPA%BKEBP;A$eôcFP@\BQ\A*BIlE:>BQEuA* 4EoOBQXA*?BWFBQFA-sBFDBQEA-;C''FEBQ[]A+OBF{=BQlTA,e@ýE!>BQ6A-XCFoQ?BQNA,cChFj BQAdyB=CFBQ/A`B #)BQAYBl&E*BQA+CF&1=BQA*B\C0@ BQAWB>JEU BQAxBQ WEBQnABӚEBQABEs=BQAՅB6DQ BQAcAOEB_BQAgBQEXw BQAZBBcBQBA^BfBQmAgvBQ:AB9yBQZAk-BEBQAEBQAҴ‚BoESBQ7A Br;EסBQ\AwB&EBQ"E{$BQABoaE$BQA2B;E뤌$BQ}A'BhE%"$BQgA(AmF^$BQZAAEW$BQA'@Ew%dBQRAB|j%wBQ/A`B #%xBQLA\B`?%yBQAL*}DV(pBQABtC>(qBQA;E<(yBQABuE_ v(zBQAdBB(BQA\eBʔCӪ(BQ+A3B?BQIAfBgUzB(?BQ1AfBIA;U?BQAFBG[Ed?BQAB\E?BQAB77E`?BQAB5@BQz~A.#BpOEI>@BQdABt C|@BQoxAaB~EA3BQAcAOEB_A4BQ3AyBDABQ'AlB}̯EmǾABQVAB(EZABQAB%E)JkBQAQBDDKBQAd:B33LBQ[AFBoERLBBQAB7 CULCBQA!B>LGBQ)AfuBLLHBQAzB/uDLKBQABeIC3LPBQdA[BE CMoBQ(AB ERMBQAeoB6)E̫MBQWAjCgEMBQAB[D{qMBQ ABVDk)BQAnBE;E?bykwBQAhuBEkxBQAӞB%kBQA%@ȲzE+kBQMAB)EVkBQZAg?FvkBQ`Az)B!D7!<lBQ.AnB&ZD?lBQAY,BChQlBQ/A`B #lBQAU?̑EoIm9BQ^ABV؅D.uEBQAeBEuFBQAJAהEFTuGBQBAݥBpEEv BQABCBQAhBbD'BQALBv@BRDVAeB -Dхv{BR'A:BMEv}BR` A 7A wCBS0A*@EuBTRA{BETxBSߞAt$Bf41EBRylAAGF[(BRY}A)B EDBRضA&@HF8bBTbA B?FխBTA B?EsծBT5A B4Ef BP(A%9}G[BPAB@I\BPUAJA'EŰZ]BP—ABEBQ[eA$eCwFE~BQZA$x\FeBQZA#CAFBQMA'cHBF0`BQ9A)F. rBQ1A'C\FL0BPA+B{EWBPoAJC_EBPA_EbBPxA\4C3FLLBPA f=C,)F$BPA:BwE0BPA+FUtBPA!|ፚFBPA_FABP~A$BۡDIGBPA/BGEBPA$BmEzBPYABTEMKBP9ABXEBPB F/X\BP1AB/Eꗥ]BQ\AtBD|BPA1$Ep/BQk@A"oTFKBQn#A".UBFMQBOA"C()E;$BQA!4BOD6%BQAhCLF iBPA) BBPA+C$%BP%A(sAuhEBPjA(hB.(E BPA'zF^DbBOA"D|CyEZBPgA "BF(8BP"QA"CDwcBOlA")7C@7Ff BPBA 4qCHwF*BP} A %\FS5BPA 8CK6F6BPvAC'F-#7BPAÅ FVQW@BPJA@BF)qIBPArEBQ*A){B$VF=aBQA'8CO,Ey#BQ A'ڤDE{w#BPA'BEE"q@#BPA'B0"E>$JBPNA#~F$BQAQVBG|EF$BQMABV7%(BQPA'ZB1'%)BQSA&-C9F8i5%1BQ@A(ǵBIE}%2BQ6A)u Ed%3BQJpA'BP'A"tCIF@P>BO_A"C+F)?!BQ{A(BEy6?"BQA(BXEi?#BQA(_OB]+EhO?BQABC ?BPAB{?BPEMBQhA D4FxMcBPA#XByBFOMdBPpA':QAOG%MeBPA!D =FMBPABBE{MBPAA`EAMBPKA9EǻMBQA ]B*DMBQjABE>vP=BQIA$B_rE>P>BQ'A%CĖFB݊P?BPaA"uCpoFӖoPQBQ%A(Oh"EgPRBQA(gPBC˸PSBQ A(4B. EOMPVBQA(ZnlKE6lPWBQA(qAggEcPXBQA(eBPBPUA+NCPBQ;A+NB%DPBPA#BE(EQBPWA&`A¹FQtQBPA%/oB0WFbKVQBPA$sB F6QABQ*LA"BTE:QBBPA# C/Fߴ8QCBPBA!B3FQBPA$oCӽEQBPLA$fBTFiBPυAdCoG%F@CjBQ A+BөESjBQA@g;EKjBQFABBaE}NjBQA BpsDjjBQvA!i8BzE.(m/BPA4A &GEm0BPA>BBCE J}m1BPA-uBE6pmcBPbA&iEBE0p'BPA(B]tDf&p(BPA(OC)FEBp)BPA( >B$#E:qBP A#BEFqBP.A$SdC#Eпq BQYA$BjF8qBQBA%bBDHqBQ/A$>@CXF.1uBQ@A dBtE] xuBQ'A BrEsyuBQAcBS 7uBQPA6BSsES8uBQ"A qB{sEE-uBQAB{\D ,v5BPtA#h2F0v6BPRA"2FDv7BPzA"e*F\v]BPA CEMv^BPA ;BF@yv_BPbAB9EvBP'JA"lCqԝFmUvBO[A"MBF|FvBP0A"CkEF wBPA!TYBF}9wBPA"BeFw BPA C~ F]*w%BQxSA!B6Fyw&BQ"A"FBFw'BP1A .#9FZBQDA(#BzEH[BQHA'B\E0mBQPA'@FXnBQA'2B+DœtBQEA*iEӝBQ%A)'>+6F"TBQ#jA( CFSyBQ0}A)nBYF'BQ A(BEףBQF&A(@COEBQ4\A)ܢ@) EJBQ,A*CkLEBQ(A)WvB`E CBPA)D*E\cBPAA)tD1EqBPA) D2rE-5BQ (A)BE6BPA*&eB>w7BPA)+4DRjE@4?BQA((BDG@BQA(ECUBPMA%֯B`D&qBPyA&B}EsBPA%B[E BPOA%/BC/\BP^A%BNBPsA#/EB8BQTA$]BXEBQYA%#BC*BQJ8A#;BԶEHcBQ?A#WFBtEBPɸAnoAUFr8BPA1-EBMjACwEU 3\BMlA5`CF>'3vBMACs}~Eޏ3BNLAXC:Ej3BN^A;CZE33BNtdA{CcFXN3?BNqAB2F 3IBNACS6E3JBNaA@C0_Ea3KBN/AjoC EV%3RBNiABsF@3BO'rA C8F@E3BOA BF%3BO(A CG]F2f 3BOMEA!)C3F53BNAC!%E[L3 BMA1CCB3 BMA- FR$3 BMABChA(E\Ռ3 BMIACxE3 DBOA 6C%E=l3BNAC'Eԥ3!BM3AnCPEF#Q3'BNA C'эEY*3BOaA!CKE!3-BMA3CwFK>3BNvA$B%3BNAbCXD3BMA/CE&3BMVA\"C3BO A NC lE3BNҐAALCE%UEc3BNfAC ~F.3BNArAR.CEp3BN3A)oBqEj'3BNABRFh%3BN#rA,UCHF3BNyAUCSFA/3BMlA@CFE3BNA PC#E5~3$BM~A1C@>F33BNMbAiCZ^33BMAC4Ev̮33BN5AVC-]E(33BN{GAC ̋33BMAeC*TE133BN1A0CA}E33BMA HC 33BNލAC7AFY 34BMAdC:E\u34BNACCtA ;35SBM,ACvHF35TBMiAC:tEΧ3KpBOA GC/E3KqBO*A!C9iWE3KBN'A#WB13KBN+AC#E!3KBNA_C.\E423KBN"A}C/E[ 3VBN AC$E$af3VBMAC|7FNI3VBNfAC3xE 3iRBM։ACWhs3kBN3A# CSJF3kBMACeE3kBMACrE4Y3mMBNGA;C3mNBNAC'DJ/3]BNAASC ?E23^BNAaC10Ea3_BNwAB0EӘ3rBO'A C.E3BMhHACE~3BMsAlCe6BKlsACE6,BLACK6=BKAiC DF#6A(BL A#bKC}FC6BBL0A"*CEδW6BBKsA +CF 6F2BK_A C%FW6KBLsACf6KBLyAC F=36KGBKuADdIF]?6KHBKH ACE6KIBK\A wCF76KBL{ACC%-6LBKJA͸uF<6LBK.jAACطF^46LBKIAyLCF$8 6LJBKAXoC6LTBK+Am`CEL6L_BK'A4CƆE.6L`BKAC۴F6LaBKAy C"Fa%6M;BLA"CF56M7CE#%6g!BL|hAPC|UE6gIBKAh6C%E,c6gSBKcAB{Ft6gTBKmpADbF\6h9BLGSACEe6h:BLOAKCKaEʐ6h;BLilACΕEX<6h`BL:AüCE6jtBLACD6l BKAPdC$FNv6lBKwACiD6lBK{AC%F6m/BLA2CG 6m0BK[ACF!6m1BKA C E6pBK*ACDV6pBK+\AOgCnw6BKAyC{EH6BLACgE}6BK ARCF'6BKA>.C3E6BK>AikAu}F"96BK1AChEX6BKAl(CnDh6;BL MA"CCb6BL A"{CESp6BK-pALCNE7BMIA8CZ}E7BM?AAC([E7BMIACoE57BMgAoC D7BM-AuC%E#07BM=A CgR7Eܼ/7BM #AQC01-Ck7BMAjC$lD7BLA!CDV7BLACeE7BLцAMC.$+EJt7 BLpA{TCASC7BLrACDO7xBM.UA9C$C27~BM,AA/C-E7BMHEA+C^Es7BM;jACQD/ 7BMyA1C7BLzACBVE;7BLACZzDM7CBM8ACKAE F7DBLAC8F$p74wBL+A C]qvE)d74xBLA*C\FK 74yBLAյCjYE߆75IBMzFA:C?iC }75TBMyA6C76BLA%[CyD푰7JDBM4OAC)7JkBMSAMC&D87JBMDVAC*7JBM<ACdE7JBMAACs7JBM,}A7C3_CN7JBM:A-C:.ET(n7KBM}AK]C8D07KBM_AE#CyjDD7KBMATlC@(E 7KBM+ACA5_EV.7KBLhAxCCER7KBMpAdiC*E!67MBLGACz7hBLAMC28Emn7hBLAC6 %ElfV7hBLAC*.E$7hBM:DAkCeXEA7hBMAtC)WE4G}7hBMAjNCCsE@Ho7iBM-AC69IEK7iBM9'ACF9EVy7i BM'AC.7EYvv7j`BLA CZ7jBMBGAC]%FczIb?BGA$B\nF/ bBG9?A UBMD[bBG?A*CB(EKboBG̀AsBebBGޒ@N)CDgZbBFA 3GCb BGA)9CbF0b BGh\AC:+Fb BGAoB6EBb BFA (CD 'sbBGAB9CUE bBGgA4C @bBGA~CFBbBGFAxBE bBGDAADE>bBG8AλBJ=A"bBGA|C"!SFbBGAAΣBbKBGAhDHbb.tBGA{\B)Eb.uBGAAFݽb.wBGAB.b.xBG;A2iB=D,b.yBGBH'A1Bsb?BHxA"BƜDb?BGAA|E$b@BG8AC"0D}/b@'BGAC">D0$b@lBHAXC-lDj,b@BG߈A-BiE+b@BGAvBE_bABGIA #C?LKDD٘bABGHAB*8EЈbABGAB*CabABGȭABJ,FbABG0AunWE+qbBBGCABAEGbBBG9A )BBbBBGA%B)'EkbBBGgA7BԄ2CbB#BGBA/@O-P9bB$BGBA/NO1P5bB%BGAGAOBEbB/BGBAUBVDbCeBG@ЍCubCxBGf@CEUxbCBG߳@DCbLBH AlB+bLBHABuCbLBG A}B1D FbMBHA7BEbMBHAB D*mbPBFSA 9C=bQ7BGBA(BbQBFAsCYC#rbVABGϙAbBhFbbVBBGAB͉EX bVUBG[A~“9F;bVVBGI AĝArAEbVWBGkAC*qUF'bVuBGC+AB^EK>bV~BGCA`BK1DbbVBGBA(BLbVBG%AFAGE6bVBGAMB)uEbVBGAڥ7F _SbVBGA`&C]FAfbVBGxACvFAPbWBG>AAE1V0bXBGBA/@O1P5bXBG=A`AEƈbXBGCAIXB%KE/ibZoBGA[BPbZpBGm&A}?CDSF^b[BGAQbCE4b[BGAC">D0$b[BG_AC}E b[BGA!MC#E6b[BGAC/qb[BGA<2CDVb[BGJAdC 4E8b[BGACb[BGA2B %Dr/b[BGACDJb[BGAB,[DPb\[BGA[C9Dz+b\dBGyAC >E>Mb\eBGA^_C4Cb\xBGA_CDb\BGSABD5b\BGSA%E.Ǒb\BGA~ASAվE[b7BHEAXBEyb8BG\ABE8b9BHA(BHEzybABGAB)E;bBBGAA.EbCBGAUBE^baBGPABbBGHhAZBECnbBGfIABwEbPBG@DxFjGbBGpABF0ESAFbBGAB=E $"b$BGAhB6MD,b-BGHAaB;:D~b/BGsA5CAB}pb9BG֟ABE>ƄbBGܫAtC>vFbٖBGABDbٗBGqACDEjŋbٟBGAuBdC/b٠BGaAsC4D͍b١BGADBeE b٪BG0A#B#VE*b٫BG AXBTDfLbٴBGA!|C!YE4VbٵBGACM9E:p&bBG`ABlmEpbBGASEHMbBGAFABpA<bBGEA hB*BbBF A 2Ct{bfBG8ABDJDlNbgBG߉ANF bBGJAfrCF4bBGkAЩC F@$bBG{A&CYO8F;kb7BG\ABnD H7bJBGu ABbKBGUNAKC~E׋bTBGEjACEcBI A C;LcGBHsA CC7`EGcHBHA IC.2E#cfBHrUA 7BBcgBHxA iBÀDЊ5cyBHWA +BEZ`czBHKA {BͷMDSc{BHOA B EM cBI@A C9)CUcBHA WyCQvFDcBHA zCcE fcBHʷA C F/pbcBHA B-iEÒjcBH.A 4CEBcCBHNA ܂BC߸_c! BHj>A BDd$c!BHf{A BٞE6c!BHoA BwBEyc(BHTA #CbE<c(BHA B yFqc(BHA ؇CES,c( BHA C XEc( BHA 7C5Ei{c,aBIA C^_gD}c,bBI#A zCgVEfc-BHrA B$EX8c-BHsA B=E+}c-BH6A CGE?vc-BH>A BEuc-BHA IC-mEc-BHA 8C EYc-BHhA CEhc.BH|A C@HEӯc.BHcA DC <EYc.BHA >C ԒEc.BHzA BOEmc.BHA ƒCuc.BHA BCEc/BHA B!C-Yc/BHA nBc/BHPA BFD[c0BH`A zBCT\c>BHjAA BZDwc>BH`|A bBbEhc>BHx\A BEzc>BH6A TpCV$DT%c>BHnA BE#c>BH_A rBEmc>BHuA BoE ͌c>BHhkA vB˜c>BHtA .B%EO,c>BH5A B>c?BHA BE1c?BHKA CTB^c?BI A )C6GF wc?BI A !C1tEc?BHdA C?E]c?BIA 'C3=E0c@BH'A BHEoc@BHA Bw-Ec@BHA }&BYE'c@BHA |BdEK7c@BHA {B~Etc@BHlA dBEcA/BHLA BD.*cA]BHfA oBYqE%cL-BH6A sPB>CcL7BH;A B_;cLBHA C `EcscLBHA CEcNBHDGA 9BD8cNBHYA BΕE=c\BH6A 77BO\cc BHaA |BBccOBH>iA aB'ccSBH<A BccBHEA BUCceKBH3A %C2&FZ cfwBHZGA BǺC9cfxBHO~A qBV5E cfyBH_A ]BbEycf{BHOA VB`DEcf|BHXA 9B9{E/҄cf}BH`HA ÏBʟ EYcfBHA xB7?DecfBHA qBEQGXcfBH5A C{Dξ9cgIBHA ͉CRcg}BH_A BʛDʰ<cgBH A C C,QchXBHhA xB`D{chaBHRdA BnD'ɚchbBHPA B8oE:chcBHOA uBE WciBHA kCcj#BHA cUC/FCcj%BHA TCgC1cjLBHzA VYCck(BHGA QCkC|^ckBHA |B?iE0ckBHtA KBX ED"*ckBHA KC .VElvckBHA =CEl]cl@BHA ۖCfCEpcBH9A oC D ~cwBHA KC Eh,cxBHOA A[C>EBn-cyBH_A DC:EbcjBHOA BzCtbcBHA C D0xcuBHfRA TBºDc0BHgA hB$c1BH`A BΕDA ccBHJA qBMCRcuBI A C7! EFscvBHA C%t|EscwBHA C!EEgBG%@C\cEdgBG@gCSWFgBG@ۚC#Fg%BG@ CpEDg&BG{@_C˃E0g CBGN@xCԄE#Lg DBG=@/C]Ewg EBG&@C:OF1g IBG@hBSEvWfg |BG@$C_E{.Pg BG5@C;nF}mgABGm@%Cm(FNgBBGΈ@CǤDqgJBGi@vºTFgTBGա@TC1FWjg_BG @ϷCE g`BG@*CGg*BG@CE;gcBG]@C@BgjBHA"BME7gkBHUA BĦ)D?.gBH`A;BMD/BgsBHGA#B3EYgtBHA BMEguBHIABEWug(BGtZAѝBogBGbA_BA[EPgBGҌ@CFu]gBG+@GB9JFzgBGAսBA&E\$gBG+AFB}EUlgBGkA1BlEWgBG?@AO\Fo<gBG,@dyF7gBG@tÝF~g%BGA;BNEg%BGA4BDfg%BG@"CFpNHg%BG@gBǮg'vBG@ACFg'wBGC@!CCEtg(QBHAB)E*g(RBH]ABCkg)uBG9AsBmg*BG@G?g*BGy@CDD?F|g*BG@UðFڠg.BG@9dC g.LBHAC+Rg/BG*AbC$g0BH3AgC$Eg0BHAB\g0BGrA CMEg0BGAPByxE7g0BGLAC VEzg1/BGq@VCF g10BGȥ@lCF-Mg11BGq@VCF g>BHAVBhCg>BGȱA%cBg@bBGABjXD+\gCeBG@?C]qgCxBG@iC gDBGp@=B~"E+ZgDBG@uBDngDBGw@[uB5E*gLBH#A:BDEEVgLBHATBɅIDAgLBH'.A_yBҾD )gLBHAgB EgLBHIApBND3(ggMBHAHB E!gMBHABqzDgMBHGAtB4gUBG]@BOFz_gUBG@dCFh6gUBG;@CF9GgUBGi@yC*mFvgUBG@C:F~gUBGʹ@C FgYBGAMBM.EM!gYBGABDElggYBGf@IBEEEkgYBGؓ@QBEkJgYBG@C ʀF`LgYBGߺ@#BETHgZ BGqAC EYgZ BG@qgBG@B=IIE^*gBG@NBE@gBGu@ B}EgBG@3BXE)˭gBGu@Q@KoEg7BHuABhWD%g8BHABD;g_BGtAB[F=gpBG`@CC}gBH/AABnDgBH#A:BBa~gaBG`ABFguBGAwBESTgvBGA&'BE gwBGA[ BFgPBG@CFJxg2BH7ASByDg+BGo@C Ebg,BGx@LCE֥g6BG@/GBÏgCKgBG*@kB .Es|g3BHABCVnBEwAhBCGHnBEـA]}BOEŬnBEAEBELnBEGA·Fs nBEwABWEnBEKzA{BunBEA.@&D FlnBEAضCsFonBE+AkxCsFQ6n BEAYC])EE?n BEۓAB)E`n BEABE'Ln !BEAC0AEyyn "BE1AaCVD|n #BEACE<nBFA CEn0BEABE,nYBE{A9BOD fnZBE-AzB}n[BEčA{B8E&nBEABưCҴnBE AIOB{Ean>BElAaC%D,nDBEk AHCCNn$BEUAB~DWn+!BEnA~BZDn+"BEqAB0Epn+#BEkACC:n++BEw\AB8KE6n+,BE]AKBrEl1n+-BEOAC^E+n0ABEAňBn1BE A$B#F n2BEzACK#]Fn2BEAhD F}sn5BEAFOvn5BE9A:BF_Emn5BEAC: F#On6:BEAfBvD>n9'BE@CPFn9(BE@xCoG7^n;BEACF8xFn=BE\A CBFHn=BE1AByE4n?^BEAB$n@BFGA :7C3n@lBEpAOC8RnB.BEACC$F=nC BEvACj=EnCBEpAC E!nCBEABESnCBEhA}@CuFnEBEAgCGFVnEBE3AL)BzEnEBEA1HDn]FrnJaBE?A BnJcBEA°Eȕ.nߑBEAOCsFC pBFA COnE npBFA C);ERp/BFA 6C\A4E Ip BFޤA 0C =p$BG9UA -DBSup$sBFA Ch_E$>p$uBF'AiCe~Bp$}BFJA ;Cx@ip$BF)ACbp$BEpA)BB5/Fp.BGE'ABp05BF5A B%Ep06BF9A ަBEp07BFXA ̚B{E<1p3BFA CslE,%p:BEAB=qp@$BFA -CqpF BEA}BpLUBFfA CE$pLVBFA %C pLBEAlBeBApQ7BGwA -CVcF^pQ8BFA .YCZVD(:pQBFJA ;Cx@ipVkBFA BENspmBF A 3CGC79pmBFܞA 8oCp{!BF2A C D!p{"BF8A _BDƩdp{#BG A HBFe5p?BF, A-C{z^pBFfA ;TCiA%iBJ9ACDL,BJDŽAC CA)CiEC<5BJApC E\5BJZA6CF75BJdAD{HGHE5BKRA0CD'5BJA:CEM;BHAK>C-Ds=7BJhAKCF"*=8BKyA_C*F =9BJASCrE8KGBKAupCCȨKBJ|AFVC+dLBK(AyCbLBKAMCDE`LTBK#AC DCT[BHA CUF\BHAzvC!gEr]BIACjEԇBI_AKC:7EGBI[AACoEBILXARqCfQEBIAnC8EBJ1A TCa{zCWBK+JAPaCKDBIo AC\DbBI5ACE=EǔBIxA@vC$E=BJARCŎFrBJACVFbcBJA2=CmF?BJrA )CPD@ &BJ*AC}EBFW ACC})BFA)BGA BzBHEZA ȗB{BH3NA BѬ"D4BH0AaBvEBBH2JA3BEhBH4sA BA BD͈BHAA BE"BHCTA *BXE:BGA {fC xDkBGvA nCDuBGZA=BBh-BGDAyBC#?BH3A ClEK@BH-OA BpE*CABH9A BۣD!CBH<A GC22 EN(DBH5FA 3BEEBH8RA DBxD#SBG}EA BںFTBGnA B˪EJUBGbA\ AF[VBGlA CƩEv'BH@A +C+=Fl'BHCA BuE,aBI$A MCk EN,bBI/AA ŶCos-]BH>A B9)DFD-BH:\A ]BEn -BH:)A KBԢ2E -BH:A B{Es .BG,A zC1jF2˒.BGwA ;.BFf.BGfAB0|E8.BGD[Ac BRBf.BGA B!Dg.BGlA CIEc.BGFA BsFK.BGA #-C`eE2.BGA Bp.BG%A ʐB"D.BGpA fCnD.BGaeAC;|FmI.BG\eAwX^F'q.BGCA”xF\,.BGBA/9O/P7.BGdAJF[B.BGAJAkBUBZ.BGnA BE'./'BGLA QABLE /(BGtA C* DHa/1BGA uBMD//2BG-A VLBm E~/3BGA `KCE/;BG1AMnB/YBGA jlB6EHx/ZBGABśBHMA B>BH,)A]B͸EnH)>BH.ARB>BHCAMBQDEh>BH1ARBDl>BH*ABƱE >BH6A XC EwA>BH'nA ES>BH5A >BH8A BPDA>BH8A yB,kDT>BH7:A 5BކDf@BH>A CBD@BH;A `BDO\@BH@DA IC EN@BH@FA bC0Ei@BH?A BPE%pABGd%A _B[@EABGBA/CO*P2ABGA .B7EABGA ,HC8hFZABGA BkE㙢ABGA wC_FABGuABDABGHAʺBBzgABG A GBDgABGA +2Bä\E):ABGl8A /CRFI7ABGiA ѪCFABGsA _BwDABGgYA B2E| BBGNA\BƴEǘB-BGA %Bؔ{B.BGtA C* DHaB/BG\ACYF:wB8BG}A C"FeB9BG+A m]ByF+KPBH5|A /7B#L-BH3A BSD ZL/BH%A @B螸L7BH>A MC=E>zL8BH7A B/E{L9BH<}A ÊBcqE8pLBH&fA BDWLBH3A BffLBH7BA HB{LBH3A BffLBH(1AeBɧELBH A=BHELBH*ABixEyXLBH# Aw`B E|YLBHA9gBELBHAtA `QBjlENBH?/A lBΓE3&Q7BGBA(B8RQ8BFA C"ǡB$UBHEVA !BݩD8UBHDA BBSUE VBHEA B%DVBH>A BD2VABG|A Bř:EVUBGHIAoB]E\5VVBGAaAB>B0śVWBG_AjBVEE\ViBGA BzEZVjBFA HCYEVkBFA BEnVuBFA ~CkTGGtV}BG/ABīEy-/V~BGݫA C E~VBGA %BvEjNVBGBA+vO)P)VBG AUBE\vVBG|A DBED/VBGABWQBGA ~B3FE1WcBHSA 7B`E5WdBHA BEHWeBHA }B~|DyWBGA C WE<WBGA BEMBWBGbABFkݚWBGA “B)WBGA {fC xDkWBGtA C* DHaWBGqAA C&B?XIBGA UBE@XJBGtXA B E3XKBGSACFFxXMBGv[A B{fDgXOBGyA BD;XBGG A"CE4aXBG>A5BhD| XBGEOA:BN\BH4A BDEbg\BH7A BQDݣaBGZlA B[F2aBGE_AFWEscMBHKA r9BĜcNBHBCA dCsxEZcOBH> A CauE.cQBHuA 4BCCcRBH={A BEbcSBH:UA BԮ D-cBH72A ZBWD?cBH4A "BͩycBH+ABVE8cBH4ZA $|BoCA"cBH5A 0B8RcBH4lA wBc{DcBH4A BF"DFcBH4A  BȴC"cBH5A 8~CɧE cBH5KA /B.B1cBH5@A L5CEEecBH8BA FBcBH5A 0B8RcBH6iA G[BDcBH-ABH'Ae BһDhe#BH-ABE)e$BH-AzB2E(ee%BH2A[B sE.eKBHOA 1C=rF+eLBH;A BEeMBH54A =CE;c\eBHByA Bb,D+eBHB5A CF&eBHB A BkxEeBHBA BpCeBHAA ?BZDeBHAA BDE9^eBHE2A B `E6PeBHCA 1BODeBHCyA &cB@DeBH@A iB8Ef)BH:AHcBxh0BGBA(B"B|hbBH;A BEݖkBH@ A ?KBDדzkBH>PA BEtBH;A BED}]BGEA \BLDVjBG}KA CHBGA }C)D3BGDA bBN%C@BGtA oC,6DDBGA 6CE BGA GkBFE *1BGH!A#BfGBI%?A CoEI`BIA CG5E BGA CiE.`BG AB\EBGxAyB̥DUBHA B+D.A*-BG}ABEд8BHAiBDV]CBGAHB_EUBH@OA NB-EqBHEA BE*BH;A@B#B¢BG]A (mC F&]BGxPA LCcD]5BGCA bBiC7BG8A JCC y59BGA iBBhDrBGA ZBESBGgA BIA 7UCIF oABIA CMmFoBBIPA HCFAoCBIA :-BFaz$~BJpA @CD7H|BIA /CFri BIA &CoF+BICA C~FGBI>A CEIBI7A QC_ EҾRBJ`A X7C\tE5`BIDA CC_E RBI0CA Ck D6J9BIJ!FA4BIA C=$EZBIA XCF,BJ@A IC-FeBJ8NA Co3Fm;BIFA CcF=BINA CE<wBHA fC!SBJ;A JCm B@BJA&Ca3uBJA+CF?HOBJA 'C^{E`u8BJ$A [qCvBJzA1ICD BINA C\7DU!BJ<5A #C~rFb&BJAwC3uBI$0A C]D~wvBI A C@DBH~ZAENBӲ=E3vBHA 1BBCǨBHA9BC>BHyABdB۱MC>qBHA80BV&E_ BHAC(EiBH4AFC EBHA~CAB~D?BHABtE#tABHAvB\E ABHABIEAlpABHAJBDCE+PFQBHA9C AE3FRBHAB~NEFSBHXAC(yEUBHAA qBDښVBH=A 4BXC;V#BH=A3xBDjV$BHAIA uBDV-BHJxABElaBHFABhFY#aBHA-B)DaBHArCBDYEjPb BH^AC?f$EkbBHŗAHCcE}bBHA5CEfCeBH]AyBիEu4eBHETA !BeBH>rA ̿B|E*EeBH@A \BFGf)BH?AA*E!NdfEBHm5A^BDfPBHyAEBDOVfBHABPlE>fBHABڑEiBH,A CeiBH8A C CAFiBH~AOvB EgiBHo.ADBT{iBHABEiBHt@ABEjBHQAoBĸEuwjBI>mA CllBHfA$BND_lBHeABbhlBHKA3|Bj :En.lBHRA ;>E­9BHA [B{d:BH͟ABkFP;BHACKEˋl>BH;AFB˴D7?BHƃA$C&PPE>CBHRACE\DBHAXVBE BI'2A CsJE/}BHnAaBIyEmyBHϭA aYCBHEA @cErb[BH@XA_BOBE'BHjABL E~BHX1AnBE^ ݝBHEoA GBiBF?@QD^?C'BE:u@6CCBEq@CUCF8cnBE@C(Fg+BE@C:D-C=BEЮ@"CyzEx.CEBE0@CHETCFBE4@nKCvGBqCGBE@Cl*F/2CPBEK@9{CxtDGCeBG@CCÞDDBD@?yCBԕCmGEBE@.CmC EBE?@CZEETPeBEA@CsE@=PfBEϑ@CbEbqPgBE}@]CoPC8~PoBEС@uCCEPpBE@΂Co`BPqBEυ@Cs=EQ7BE@C4vD!9QCBDޒ@PC8D>QiBF @ZCXQ}BDH@_C!EwQ~BE@CDžF4˸QBE"W@fCfE{:}QBD@.C^ECQBE@#!CE"QBEx@FC| EFQBE@'ICtEPQBF#AmC1EZ6QBE4@CE:QBEҾ@Cv$D&'QBE@C|RD{QBEϹ@NCI[ENQBE@L0CwC7qQBEE@־Cy$DQBEs@!6Cyf!DbQBE̸@ACyCRQBEK@9{CxtDGRBE@8CD,wBF7@DwBEW@ʬCREBEЙ@ C3E}BE@CF%~BE@ִCmF BE;@CqBF@긢CFBF@CFqBF@mCF,BF@޵3DF|8BFRo@)B˙$Fo*BFNG@C_Fy;BFc@dC|rFViHBGxw@CJ G~gBG!@CxEQgyBG1@5EBG'zBG=@7DVF"E[{BGr@CDE$BF]@DGFyBF@D0EOBF@D-EЗc@BG@CD>ZTABG@;CCCF|IBG}@PCuDJBG@ɱCUE` KBG@CEiSBG@W!C#^DTBG}@C0?FG-2UBG@CV'EY]BG,@ToCE2M^BGTr@}C֖F)_BG@uC)E@BG4[@CE cBGQ@*C5F!pBGdt@HC?FR eBGG@^Cb'F9B fBG( @C;$FO gBG?@*C]DS BF@ݒsCEry BF@ܿDDZh $BG@f DE BG{@g>CsD BG`@z)DxFw BGP@YDȍE BGt@!vCEE2 BGV=@D!F BGD6@U@CTF{ר BG>)@ׯD%Fz{ BG[@>1C=lF> BGJ@CNF%x  BGP @uDF BG9@⠎DnFd{ BF@߃C&FBF@wC˃BkPBF@::CC/BFz@CӸAC"C7QBG6@]DmF[RBG.@dBC_ESBG?@уC*?FjABG@DFBBG@@CշFa.CBG@b~C=F9 TBG@%CgC&BFhW@ GCACBBF.@(CLCiVBF@Cȝ!D;BF׵@CEbgBF@Cȝ!D; BF@/MOPBF@ VOPBFހ@CgVF*#BFQ@QC\D*BGT@xC4{BF@HD;NQF6 BG!@ACr E&-BF@DbpFCDBF5@蠛C4+E5uCBF9@NC'kC&BG2@7CCE:BG2?@BF2dBG/k@!qCf vFkGBGo$@C-EkLYBFO#@0C6F-k[BF`@HCF'}BG)@60C3BF|m@yClBG @`CVrCsGH#BF@ |OP'WBG:@؛CŅC'YBGS"@fCsFmt*oBFnW@HCE4*BFC@ޓC후FA$*BF_@CбE,*BF`@7CDFN8*BFd@&FC+E+*BF@/OP*BG"b@iC֕?F*BG+@ŖCF *BG@+CC*BG@mCy*BG@CCFrJ+4BGr@C+TBGr@D Dil+gBG@ЖC/FCx0BG(@0CݫIE1CBF@{wCQE+1DBF@OC@Es1EBF @00C`MEmȷ1mBEO@,CSB}BF|K@CfE?4CBGq@CG#)CBF@3CFdCBG{`@ČG CBG.j@nnC{5E0CBG8@z)C!$EOJCBG7@oB--!EibCBG'@C!FxmN!BG@CbF?6N"BGM~@ C1SEx}N+BG%@C VE~}N,BGZ_@UCP&fN5BG]@CE9mN6BG`@˛CEE`N7BGҍ@кC8 E<N]BG6i@BI1D3N^BG6U@#1CELNgBG$@@LCE#PQBG@tD\F sQBGt@wCD)KQBG@CMEqNQBG@C+.D8QBGJ@C9ETQBG@C]ECj[E8~BG@C]~BGO@栗CCcBFT,@&CIEdBFC@sCnE eBF_@ReC \F͐pBG@訙CYDBF@C%D` BF؅@CbD-b+BFt@CӫBFy@BCBFly@NwCDEBF|K@CfE?4BF~]@gC ECBF^@LC2FHLBF[@6CKEkBFg9@,DEBGY@C} EtBG@PCFASBG0y@ؽC:NVBG(@CEӣkBG)@mC2EL#BG/@}CH1F$BG0@ظCP0hE?%BG:@ץBE1-BG:|@BaEI.BG:@^B3E|/BG,1@ճ1ClD:F2jBG)@3qCE3#kBG*o@rCE 4BG@#CeD:>yBHD,A ŁBǯKCs{BHXBCXxBHAA BRyBHKA CPE*vBH>A CmhD &,BHfA BaD NBHkA BwA¾#FBHeA B^vB*$BHlA [BĠC$BHl7A BtCz$BHiA B#Dt%BHkA BrB%BHhA }BoD#%BHi A pBc(BHA "CD-( BHXA HC-BHLAA |#BWC--BHcA pWBA-BH@A ZBӕDqU06BHD|A NB@C0BHiA BD0BHfqA fB`>BHXgA 8B>BH\A BDh&>BH[A $B#>BHVrA .B-DSwABHlA BJC4^A BHm'A sBZ?CA/BHVA ޤBhDyA3BHfA ~BDA4BHaA BLDX/A5BHeA nB_ DRO8AGBHA xCNBH6A dB"D"vdeBHmIA Bf BH^&A OBзDYf BHUbA CE?f BH_A `BfcBHbA {BDnfdBHWA B힔DQfeBHd8A qaByoDEfiBHb3A uSBt9ggBHaA zBlD$ghBH]A BBDI|/giBHeEA sBBDS/gBHiAA }$B(D~CgBHhTA lBBWSgBHfqA fB`kOBHbA ݪB¼DRkPBHc\A BcC7/kmBHd]A BD:4knBHdA sB{ND=ekoBHcA DBDkxBHd;A Bdk|BHbA qBEl#BHeA LBrBlBHcA BDlBHQA |B9DkPlBHbA PBmEklBHkA BA#lBHkA BȈA̕mBHl[A B4CmMBHA #BtBHLAA |#BWC-tBHLkA BEBHA CyBHȼA 1>C $ABHcA @BdBHmA BBgBHjA B۬DH[BHcA sBvD=]BHbA {B!DuBHnNA BBC'BH`fA B4(BH`cA B^Dr6BHkA UC_;BHWA kBɶDpCBFAq7Cƛ^EH;DBFACEaMBFA"zChD3kBFA CvBF\AXC5F(BFIA6CE"BF)ACEF5BF0ACFBFoA"rwCQjEBFAύC>BFAPC=:CŭBG9A ZCZEBGA HCD!BH A!,CEBGA CEhBH'vA 0CIIFA9VBH@A C-)EnBHPoA ;CZE BH\/ACE. BGoA SeCkF > BGyA KC9 F37 BGA CkF"f }BFDA"Cj+F~% BG9A CEf#6 BGQACDIQ BFXA"[CDI- BFrALICwEE  BFoA7Cs3]E zBFA"CD6vsBG|A]CHEXtBG_A RIC6uBG_A T=Cq!DMBF)A3C/CuYBGA CE~ZBGA Y[C}E [BGA eCE"BEAoCCsBG&A!=4CmqBG A!CRBGA XC~EBFDA"CRDaBFPA!>CRBFYA-*CE0BF&A)LC E\BFVAWCaE*w)BHe)AC/BGJA!*dCEhBGA ۝CEК{BF3ACE+`BFACE7BFtAp*CYD\SBFA"YCEMTBFA"9KCF4ABGACEVkBFNAgC]EgZ BGA!eCeC!"?BH A!qC(EgBEACXEBE'AkC^EiqBF{9A CnFͨBFA^XCTE'/BF A_tCF3BFA!KC*nhFE4BFA!mC~0RE~5BFA /zCFow*BF!]AiCE'~BF$An CAE'BGA!C-F(BH[ ANCcEF(BHa7ACdDVt(BG/AןCE1f(BGnAKVCPEG (BGA[CDQ7(BG A!CWEOv(BG A B"C$n6E)BFACoE)BF1AyCF)BFACoE M)9BHVA C4E2+BG:A CbF#+BGjA RLC7F?,BH@aA lCEh,BGA CE(72BGA!*C!hE33BFAtC\FX!3BFniA*C\DKq%3BFdA&C;Dr4 BF?CFeBG}AC#9ET8FfBGAC{FAFgBGA ?C [FFBFlA]CC.XFBFA^SCVDgFBFdAECnD.HFBF>AC.DGBFvAHCmPHBF5,A"@CDNJmBG=A%CkC6JvBGuA*'C|D{JwBGjACBZiEJJBG_A JCnE`JBGSA UCEgLBG(A!aCZAFOabpBFA!eCoF)Ob}BFA"LCjEmuBGf6A C>wmvBGI A |C1E_mwBGTA ?C5mBGPA C9DMmBGA2CkmBFA $CJD*:mBGUAF7C}CsoBFtARC|mE`)oBFKPAPSCE![eoBFZAHCFcoBFABCdF~oBFsZA$CPFeoBF0AZCdExpYBGA!n&CCE2}pZBFhA!hClESIp[BGA!0CDtABGANCtBBG A*uCeE%tCBGACEE =u BG-A CLD#^2u BGA C$EnzBE"A -C8BH,A#CkBH,A#CkBH,A#CkƸBFkA޵C{UEidƹBFAC EEI|ˆBFluAoCe1DύBGA!t\C9+D) `ҡBG|A 3ECLE ҢBGPA aC EcңBGi@A[C,j^BGݛA C*Fr-`BH5A (CWEx;BH2VA C^iEuBH&A C-EWBHBbA cC"UFBH,A! C BH A!+C EuBHQA 9C8E}vBH4A qCdEٱBG{cACD5uBGqA ʌCELwBG oA YCƕE;BGA YC|6EhBKACKDd iBHAGC,IE]BI AȒCD"uBIjA$C_Cƚ7BIACoE3FBJA{C@E*BIYAjCdEOBIQRAaD EjBK A hCD4$BI~SAJ{CEh<)BJMAB0G[*BJi:A`rAjF^GBJYUAZ~C9HBJVpA_C!DrIBJAjCGSBJA5CACdBJHArCFQBJ pAf D,[F3BK׏A YC`bBK6ACr'~BHACF5ii'BIACb'BI:AjCF'BIjAwD Ex'BIA4CçFD(BJf9AAC$YE(BJTAsC)iBI$rABCKFi)jBIVoAd(DDd )kBIfAC-dEO+BJIACYEKv+BJACF-9+BJtA>CE,jBHyA C Eok,uBH~A"eCyDc,wBI0A:jC}%F8,~BIAC1TF*5,BJxAwC#E,BJ AChEg,BIFACF8+-BH AS_C!CMX-BHAC- BHAOCE2BJABuAFvL!2BJA|CF 2BJAdC `FF3BKGA CَUCǭ4BHl2AC E,4BHhA2oCVF4cBKAACUC~h4BHAAC&DƔ5BIApC>FO5BIAc0CF6?5BImACsFFc6BKVAC(ME~6BJ: A{C5Eh6BJ[A@(CF3 6BJIA*C%D88BJ0AECDʪ>9BHA2CYE(: BIOjAD'_E[IpBJ'ACDCIBJXAmCK9#EsIBJ'A6CEgIBJASC$EcIBJtjAoCGCOTLBK0ACLBK(AUCFLTBK)A0XC݄D~QMBHlAprCDIMBHRACvC!m0BJA2CgYTD)mBJ9uACgE*mBJ*\AC4EW`mBJ]A+C;E|mBJSlA^YC&C@mBJZAQWCěD +nBJDA)C YDnBIAC*B8jp1BIA,C%F+p2BIDAmCF9p3BIַA C E0*pmBJACEXpnBIcA'BokFypoBIA./C8FpwBIFAC+5E{pxBIKACTE띺pyBIsA^CSE+YpBK*ANCD6pBH8ASCjExPpBH*AEuC'EpBHAKYC;AE%pBJA7C-rBIACErBIA:HCE]rBIAlCEFtVBH{AQCRDBJtA(Cu;EkTBJAC^F S BIACXE_BJAhoD!/^E7BJnApAD EBJQAoCcFI?BJ¯ArmCTFUHBK*)A5@AFθ BJ@AXCF?BJACZC3[F3BIZAq1CQ FBIA BCKF?{BICA3>D:dF?BIACFWBIAC@EK9BJ`Aw&BwF̔BK0ACFyBIA}CEBI^ACBI4cACEmBI4A'CE4BIFcBIACyEkBHתALCEBHyACSE'7X BI AC'E? BIfA?CEByBHe"AC8E+\uBHAcsCvFBI A IC&E4ܝeBIǖA*C&DyBIA}3CBC"DAC7Ey{BCFbAN C_B0)BC;XA\CqD4*BC6AoJC [Df+BC6B)_BC/AC.D)`BC, ACwDN$)aBC/A;CFeD-BC6ACFB4bBC;ACD0q5BCEANXCܼC5BCDANSCupCܡr9OBC*ACcF^D 9PBC$ACedD1Y9QBC"A'CWzD9BC/AeClSD19BC)AC>BCDAACC;u?,BCVA[CnFAC?5BC8A;Ci)Dޏ?6BC4ACzD:?7BC1AC2DLBC)ACLBC-AkCuF7D͎MBC4AC|E=bMBC*SAYC$hDqkMBC*AԓCiC/@[BC'AvC`El>[BCcA/CMJE tkBCDAJCD(Jp BC2AeCaD<p(BC;AaCy+Cp)BCA(ARrC%DBCq+BC5ArCg#OD q,BC5A|C'pDhq-BC=AaCzCD[q1BC5AwCrC\qq2BC9AnQCqDq5BC.A;C]wDHUq6BC0 AHC_~D)Iq;BC0ACeCuqAYCvVD rqrBC8A~Chc[Dڢ qsBCA_CpCB!qxBCA&C;EBBDAGChE„uBB-ACɏEUjgBBAUCEl'BAAC&D)BA0AC'E`l_BBRkACѷEq?aBBGnACF _}wBCjADHBB]A_CuD߾OBC&Ac%CDl\)BCFACxNEi8+BC[ACpAKC94CvBCFgACCNBCWAC6aCj)BC:ACBC>AC~E BCQA XCqEGBCSkA CEP)BCBEACFN#BCA CD;$BCAhkC}F$BCACagD0q$BCAYaC}-E-'$`BBAC((oBCArCCl(pBCAJC]E(qBCACOE*bBCA?C!B(2BD !ANCEs 2BDACXEb֛3UBCA^CBxl5BCMAGoCEZ5BCWA%CcDq7BC.AtCF7E2_=BCAvC$aD՟>xBCrAXCMDܶBBAAUdDE=DBCpAC;DBCaA4CFID"tD!BCk0ACD#BCmALCTDD'BCk0ACD)BCA1CE)DBCA9C\HmBCAcCߜD^+HBCAZC.DJBCA CF1JBCnkA C-E;K3BCLmA=#CEoK5BC@AC^EJMYBCAv Cs E MZBCAEXC]Ek M[BC A C^BEQMBCFA CEvXyMBC=AYCD_]MBC=ACDJX{BCTAcC.ES`X|BC A`C̽E XBCfA$C}EOXBCQAnHCHvE XBC\}A C&D4XBC\AbCE H[BC>AC~E [BC8A/C![BC9AACCCh[BCTAoCцDED[BC9AXCfFfOBCSADCPELMfPBCnA>CEfQBCUAECzEHhBCƺA+CqFZhBCACpDhBCACZ{EjBC3AvCDkBCA _CE oBC'ACMEfMpBCACEipBC)A7CǵE?8pBC(XACKE pBC'A&WCǚcEpBC(ACn=Elr9BCCACD'tBCHAk\CkvBCcA CE#wBCACTC wBCA3CsE2yBCnACTDIyBCAHCgSE1vDyBCSAkACOoE(kBC yA;ClBC"ACEy-juBC#A?C@C7IvBCAw'BCAtC,B=BB5AeC]D>BB"A/ C3A[?BB^ACۑ-E/-TBBυASC)BC4A C*BBƌA CӃDnw=*BBA"C"D *BBUAC}Dtc*BBHA+CևD*BBA'fC C*BBAC=DY8BC`"A 0CcE9rS?gBBeACDw?hBBACޚE ?mBBAC/DɈ?oBBACgEUBsBBAZC!BhS|BBAAUMDD+@MBBBA2AMIDF\BFBCOA CBEV BBŗAaCB4X{BCjADHf'BBAPCLD̈=f(BBACE j-BC+ ACnBBAICNOD~nBBAE|C63DonBBAI"C2 EnBBAdCT+CnBBHA+CևDnBBA%FCCo}n!BBA.C`Dn%BBgA-C4B}n'BBA{CؖD|Wn)BB{A)C{C%n>BB.A \ClC%#nRBByA+jCϫDpBC:A[CȬBBAC D}2ȬBBA(CR.DGSBBjA&CoCBIHA,CEZBJ6A+C>EGBJMA+aC[E^BJ\A+2CpEr'BJ#A,?CKE'6BJA/CEh7BJyA/`C:E}7BJA/" C/E03DBJ$A,.CDEFBJA/CCE2GBJbA*0CE:GBJ]A*CCE\bGBIA- CEv^HKBJlA*eCBE*xI$BJTmA+4PDEK6I.BJhA*CaEl7IBJA-ÙD1E4OJ?BJA,D EJBJ@A-D ;EKBJA-PD"EKkBJA-BJ5A+ڼD%9FkHBJrA*DEGnBIA-(zCCE-ipHBJA.fCuF9^pRBJA/_CdCpp\BJA-C1EdʂBJ\fA*@CvDʃQBJoA.CٴEʐdBJA+DC%#ʑTBIdA,CEʙ$BJ$A+CfFrݔʙ.BJvA+PDDn+ʚBKA0,CSE4xʜBJEOA+)DEKʜBJ7A+hC\FfLʜBJqA*gCWDVʝpBL'cA1qC.QʨBJUgA+(DE.{ʨBJvA+PDDn+ʬBKoA/UCBFEʮBJ޲A- D@EʹkBJpA*]CC|ʾBJcA/CڡCBJA,YCMEBIPA-CBEmgBJA,fD*F~ǭBJA/^mC@DoBBAO,CUyEsU#pBB,ADCzEAqBBAhC#TLBBz_A51C+E<UBBAKCq`EW,VBBA\EC?E&]WBBAQCEN}BBACo"EPv~BB:ACENBBA.CpcE !BB\ALWC&ABBiA0qCSCy{ BBATC]E0_ BB4ACpE BCPA-JCJ{)#BByACJE|-BBAC'DǍ.BBvBAC*Ec3.BBucACEYS .BBv5ACGYEB9PBC![ABB%AMC}ETK[>BBAm;CE٥>BB}QA.=CE|>BBACkEI>>BBtAuC$Eb>BCACq FL>BBACl"E$g>>BBAwCh(D\>BCzAbCfz^Daؚ??BBACxE/kN?@BBAsCTER[?ABB݃ADCU4EG?BBʌACr E@_@BB[ALZCA3̪CBBACl3C IBBA[C-XEb$IBBLACCmgEIBB2ACwcEWAL#BBA\CqDkL$BBACi?D^L%BBJACqIF$:L*BBACgID~eL+BBACmGEM BC2A&Cl=qMBBZALUC}AVLUMBBZALUC}AVXNBBuA;CjEXXNBBACEOBCqA>%CaAD>QPBCA:C7r}DţPBCuACdXCtlRBBbA CCE>CRBBBA[CExRBBqA pCsD0RBBAC DIDS BB6AClIE4SBB]AUCE\SBBA*CiDXBB;AfCmDXBBnADCk:D޲:XBBaA[CEqlBBZALUC}AVXoABBAsCNF\oBBBA,CsADhoCBBAIaC„ELoBB`A C;DdoBBA-CwEWyq+BC4A~iC\*B/tq-BC?5A=gCxGBB}Ax0CDSxIBBACeEƍ{?BBAH2Cr{ABBxACF6bnώPA9_C}|B)9BC]A,C`hE:BCA>Ca'E ;BC$CACbD7?BC%KAsCarC@BCAC`yDoABCA,mCaEbהBBYALFC7 A , BBACE>BBALCE;A~BBAC8ECBBlACSE@}B@oA8;TD?ABCwA 9CXDRBFA -Cq0BCoA C7BCbAC^D|\`JBCA zC/ZBCzA _C֮DfBCA JCE3fBCtA FC'EBAACEI6BB!{AHDoyF[fBBpAD- EBBMA!*DxF BA~A!ZCmCMBAFAD>D4Fz2BB6AHD3MCNBBBAZC[E2qXBAAD%ZMEBAA!/ED%F"BB'7A!1CbBB /AMD2;BBtA#D\1FA- BBVA"DF7BAfA!]+CEbOBA4A!DiF BA&A!QDm3E{BA-A!tD5E!ٖB@rA!|D?FfBARAID BAA D6ۖXBA;=A!/DCYBAADCu@OBAAD:DGƨBA/AhD>CBAACpCBB!dAD@BBA#\Dn5E0EBBVA#RD F BB*_A"C[F9BAQA!4DE[BAA!C1GGBAUA uD@#EpgBAMA!C:E[BA`qA [CC1BA3iA!DIE%BBUA"ـD+&gB@'A"5D 9C&hB@?A"GD E#&BB4uA!̼D*D+&B@vA!UDC'B?A!bD4v@^.(SBBUA1D9}DTv]BAA!C麊Ev^BAA!QCE9_wDBBA"+D0xBA ACBoDyxJxBAΐA!ޟC_CjfxBAUA!aC:8C[xBAMA!WC浍EZsxBAA!_CdZyGBAA!dCDPyHBAqA CbEqP{BB3uAC|E ۂBBGgA"fZDEmۂBA_A!CFێYBAnA!zC[C˿ۜABA_A%-CڒۜKBA֍A!C\۴BBA!ކCFC۴BA;A!GCE̓9B@A!DgFB@ A!ODIE BAIfA!sD@DB@|A"SCFGFB@TA!D lF):B@\A"LC:FBB A!-C{6F BC3A&(C塁FEsBBUA"(DBBCCA$wED.F~BC@A$YDeFcBCUA#LDsEBBA#T[D HEyBBA#F\DD`BB A#jJD6EBB}A#CjC BDT/A#xCĤFH DBE>A#MCӀC BDA#:.C*F= BC8A#7CF u BC5A")D;F, BCOA#CERA BC`A"D ET BC6A"DF0 BCA"CE[I BBA#uRD+D :5BCA*{cCӦnDKBCxA( CإBDމ0BCXA#DBCA#D FQfBD%A"@CYyBCdA'C褙F eBC^A%DFj\BB>A#BRDkEbeBCz1A':C՗sBBCA(C̥$sBCCA'KCxDr%BBGA$cD3pF$&_BC$A)vC̸!Da(xBBA%uD %LF[(zBCA%aD1vBBXA"DDZR,4EBCA(TGC Gh4FBC7A%0D F*V4GBCPA' D 4VFI6`BCDA$DJ"F<?BAA!DISC@BBA#DEABBA#tDEMBCDHA#B`DFDENBC+A$ضDFEOBCeA$`4DBYFIEWBBA#PD T*EWEXBBXA#P#DGF+EYBBFA#LD!{F3HBCA* CTF HBCcA'DcF|HBCA%5C,F&HBCA"Da1FiHBCzA"DDJBBYA"DMD4O%BDKA#CE*!O&BD(TA#C4F*O'BDB(A#iC٧8E%8OBB#A#oDp;DJPBB`A#{jD[EWBC A#mzD FFX[BCA%BCwFX>[BC A%C( EY8[BC A%C( EY8fwBBMA#oD2DfxBBA#MDEe|fBDsA#CCEfBDjA#CxEJfBDgaA#pUCDarBCRA'CBE|BCwA" DxsD2BD\A#XCDXBCA#CCUBCA"/CwE|!BCA"DFD.BCA"C12EBCA'fCӠDBC7A'+CхwCKBCLA#+DF4BCA#cD "FEBBA#qDBC@A#DEH BD A#C5E{BDJdA{dCE5}BD:AX5CFQBDAVC:EmpBD?AGC{!Enl1BDAA#CzFBE,1A}C@F@t BEWAXC[AEGI,!BD9ACaVEx(BE9A'1CEA66Cќ}ExB> AaCƄEg'B>܇A+CEÇ(B>ACʀD8;B>ɓAvCCG'DB?.A hCEcB?hbA |CbFFdB?|A ^@CpEB6 B=AwCć B>3AClOE;B>iA`C|DRMB?EAAsCrDe &B>ADEW*2B?ӸA 1D*NEcn5|B?A&C>V6B?bAerC4F67*B>ACŐD7+B>AnCEA>CˆNB?)A9CE^,NB?LAC| DWmB>AMvC7;E'YWnB?ACј$D: ]SB?#mAD F#uh]rB> AC !fB?ZA^CDZfB>AC*CifB>AC灷EEbfB>AKyCL]ECfB>AfC~EPiB>qAOnCmEUiB>A CZE[iB>aAC%EiB>AbC/D iB>ACsEV&soB?.A hCEsyB?,AgCݑ&Ds{B?0A CE^sB?2A }8C?sB?-A iCQ$EesB?1A D{Ep t#B?A A3D(]qx[B>rAxvCѳE~x\B>A#OCFwNx]B>ACbEyB>qA50CEyB>AC,DꎋB>AwCDE B>/A5DaEF"B>AC{;EB>/A UCՏE B>AC/7FyB?AD ErdB>ACE+B>}ACCEB>РAo-C*(F0B>A]CMEkB>A(CD'StlB>AFCEamB>A\vC[EB?ACLEGpB?A=C[>EYWB?)A D,2DgBDiABXC_,BCw A fC&C_7BCA CYD7#9BCA C'ABCA CE,6BBC{A CEPJCBCzA C1EBCA oCF BD A-}C;?E&BDhA4C FER_BDAbB_E39BCMAhYCƱE'BCAC¯F*dBCJAQC]E&BCAFC']EBCAC""EЮBCA C BCZA CC"BCcrA 8CԲDM1BCA 6ChE_BCAGxCJBD%vASB%BDANCCNBD A,AE~l^BD^AB>wBDANB='E>KBD |AjB}DzBD AB}EPΔBD'A$B,D|cBC;A9D"`FBCA7C DM!BCA~CQ#BCA kC8EPlBDAaBĴEJBD AB,DBCA0C!=uD"BCTAJJC&cD7~BD)ABOEuBCAC څE]=BCACE$#BCHA KCEYa#BCA NC3jBD/AbjBE'3kBCACnB݋D\5BCAB3CA9IC{6BD LABD9EBDAzBD!9FBCAsC#XD^A9GBCA8B`E^>BDACEEf2>BCADC&pEb>BCA!C$jE3>BCACĕEY>BCABEI>BCAB*C?/sAoBDA:BJDApBC_AC!jELeAqBDANC6ESUiABCA CFfׅABCASC WDCBCQA CFCBCDBCA CC`9CrDBCA oC6EBCA QCWFhEBCAgCj͉FE/BCABC@E0BCAC E1BCuA@CW;EE:BCADCE5E;BCʖA{C-EzECBD AfC^DmEDBDAmBمDV[EEBD (A^-B5DFBCxA e3COBCA CPBD/ABؿoE*tRBCA _CDmTCBCA5C#8DU!ZBD&A١B5ḌZBCyAYBݩZBD(7A?BEkZBD'NAB;E2ZBD)AvC)MEWZBCA CgE:ZBCA sC`AYF%ZBC A UCgE>b#BDnABeBCԙAwBGE˩eBCDzABķeEeBCzA BzE{f;BC?A"Crj3E/ fEx-g]BD ?ABHEcEg^BCAC $EkKg_BD'AEC >Eptj}BC ACzE>'j~BC]AGdCF,`jBC]A CiEE,djBCADnBpEjBCAC @F!jBCA-Bڏ^E*jBCiASB9DWjBCAH B۱JC؀jBDxALBCDKyjBCAϘC*E^jBCxAjlCmFzjBDAB_jBD?ACRDjBD4ACiEjBDAUC@B[kBCA UCyEkBCA CsDkBCTA -CjFWVl]BCDACEߚlqBD%ABE? lrBDA"AE`k6lsBD&zA BE\/flwBD%A)BhADNlxBD%EA.vB!DlyBD&AA̞E l{BDAoBDl|BDAC$D}l}BDA|+BdE-R5lBDRABcNCw91lBD APCDHlBDAC*E lBCA BElBD AFBClBD ACpgDwBDAzBLDhBC{ACEхǀBCA@NBEvǁBCA#C E0"BCA )C%pBDAsBA4BCAB)kD3BCUABEBCUA;ChE_âBCbA BiEfuBG/AB=D%RBG<0ABȁEW`yBGtAeBxDVBGPAQB>EBGA'B8vEMH_$mBGzATB8{E%/)BGHABҕE!WNMBF'ABAD$[BGkbABE-PfIBGkABC*fSBGZAEBE5f]BGoABEoBGq1A>BE%qBGGABrEBG(AwB4E_5BGA,BB[ՉBGA,BB[BGHA=BCУ BFoA CvG-BE~ABDT'BF+AB+OCJBFsA m"F=BF HACE>E0BEoA$XCBF bA?B?FSBE@bCC]BFPACCZBF8OAC&p{D-%BF#ACӜE†BF$fACEBFVACF3/BFA +C= BEA϶C SCn BEnAB !BEAB0DBEAEBPD9<BEABDyBFA %$CR0BE ADF6YBEABBCDdQ[BE{AB F̀BF|A 9CfB BEAGBE@'BEsA9/C+DYBEdACmEg$uBF[AlEC`A!5O$BEAmCXE,m$BEިA++C9BFANC,UEBd9BF5ABER9BF4GAĻB9E=V|:BE ABgBl:BFAn6CUA}DwE>BEANB?D+>BEAB0RD>BE]AZCAE>~?SBE)ACDa?UBEAOByD?]BE3ABND?^BEABEf?_BEABB6@BFfA 9JC@lBFA.C;EǡB#BF$wACaEB-BF4 ABZETB.BEAwCY^FMB/BF"ABeŠF*TCBE$A,BND6nEBEABFF BEcAwBD0F BEA*C#PDzcF BEKAuBޡhDREI7BFAoC]>BK BFKA *C{D%1KBEDA-(BEG~KBEAZBhE?SgKBEAA#E?7KBE$ABdžECХKBEABbEqLBEAB`C*LBEAtBzBg1 LBEyA,Bʒ-A>LBE\AB;D3LBEA|B#DQBF!AjFC`E}QBF ACs@EnFQBFAmC[kB`3QBFkAkC[L@HS]BFA ,CBSqBEABEEG`SrBEAёB*DSsBEjABsPD\SBEABȨE>q-SBE6A8(B/;ESBE@AC+pJE7T/BE7ABD@J:T0BEAdB>+D T1BEvAqBӤE[$BF6AeBcDss[-BFuAQCgG# [.BF##AOC5E2 bBEDABN0EC_bBEAKBCfGBFAnMCPD4h1BFAECbENhuBEA%B͆%hBEQA-BFA+F/i?BFdAa{FmiGBEAQBZCiIBE)AB<`DieBEAaBDUifBEAdBٮD>igBEKA& B-ZE*~ikBEABoBӬDƜilBEA8BEE=z~imBElARSB/DipBE1ABXEqaiqBEA/Cb,FjiBFAY^C{kD@JiBF?ATB›DBlBF A^BlBF2AyBisE?UmBE1ACEneBEApBDIngBEAuBZDKbnkBE/A|LBD|DnmBEaAeGBϮCSWsBE#A9&B@s BEAz,BwEs!BEAbBE5*s%BE{AC+E_s'BEA)]}@EŖ uBEA8B]EE=uBE}A4BEx quBEqA[BɝEmwBEAvCt.F[iwBF$AfCPE wBEHA`CHx BFA ,WC*CkxBFA ,CkB_{BF=AvCj[EՖ]BF%CAuC~ D3BFAI,CC: BFAC*DxBFA 3[CBF4GAB3DUBF4ABpD!{BF#%A,C.0FMYBEBERA]BDBE$fAqC~E- BDkA$tC",GQ!BEAshCCBDAn*C#F#BDaAC(Eѷ BD@nCJ<)Df BDA:+F3 BD3ABÏFU BD+"A4 B⣏DBDQDATUBEe?BD`A*C9F >jBD@'CaEw7BDACG1#FЬBD}-ACW[F&BDnA,C1EuCBD@@;.CaqBD@CEE`9BCAwCi@&l BD(AX*BLC3BD(AX*BLC3BE3AwIC D|BD' ABQBDY!A@CrE;sBDkA C?F6BDAhCnuEBDoABEB?MA D)LmB?A D:AF=rnB?+A "D.MF+E*2B?A jD&DƤN-B?A ղD/pC-t#B?DA @D67Ect$B?A (D&D_t%B?qA D6FodBA@oAF6BATYA\ D8[EMkB?AGD2Cu3@JB@ @ADE˜lB@zAD pxB@|AlD0o.CMVB@A"D#G'B@A D/IB?"A1DEB@AYDFBA,7A6D<'KE BAtAD8B>}/B@ѕAzD$ EWB?BAfmAD2(C <I BBNAdD'EOBBkAoC[ABAkAODECV [JBAA}/vTBBADI~EB@A}D F6haB@FACJF9^B@z^AgD 8F/]B@QA9D^BLEAJfCF#BKx]AD%:CLEi%BK ABpCBKprADCD@BM AJGBFnEBM-AIBoF%BMBAHD¤F.bBLjAGBMEBLAKBŸFBLݺAKRB`=FYBLAKC+F2PBLAF}+EYBMAI/C6FưBL AGk2F,BLzAFBepEBM=ALxxCjFBMPAK(CYEBMwAKroœqE~BL AH7C E`BK AICG#BLPAGB43F. BMQAKCEo MBL4AG;C,EE NBLϖAFҹC8E" OBLٍAFCEJ WBM^AHCb!DP vBMALC:E| wBMMAHCAE}] BM4'AI\BŰFz PBLAIabCxR3G2j% [BL&AKWBE% \BLAGCCow gBM8?AICCb^C} UBMCAHp9B? ?BK=;AKiC_BKhAE@KCDNBKvADeKCD:BMAMwLCGD<bBL_ANCBK*KAO[CCE BLALC!EZBM ALqD2Fa*BLAOwCk\jRBKAIaMCj6Fk^\BKAJCk \CLzBLAHAC>~C[BLAICUFKBKAJGD9pGTeBKBAFAPG BLrAI(C4F[+BL;%AHC/fE) $BL.?AHCzEUBL3AH,C3\3E$BKBAJQC8E^BKAMC3F <BMALCxF BKiALC.wBKAI9TC@TgD}4BKIAJ$C)hBKJGAL?C/CuFBKpADBKUACCfG^BKLA@LC\GrBK{ADCSuN6BMALYCVE*N{BM)AHCF N|BMaAHqC7YF@N}BM:AHECWF OBL~AKȄD["FOBL,AMgC FmkOBKztAL÷1G-uOBLSAI\C:F=PBM AGCQEsPBLAG:aucFW;PBLAF\BEXP>BLqWAOqCEE]vP?BLALBB$'`F/PGBM'AKC@F'*PHBLALI~CFkhNPIBLAKZCPF2VPBLAHdC EAPBLAH HCG,E!DPBLaALCUDSHBLS~ALYC2FSBLAGCFSBL~qAHH CEFfSBL9(AH`CEFSBLb%AHqC nnETBLxAHR]CkBL$AMJCeFlBLAMCFomBMALFC2E63wBLAO%C_BKĊAJCF>BKcAGD#4FBBK CGmD. CBM7AD`BR E:BNAC3B*ODNmBMALC C/BMAJdC&sBM=ALC$!D1qBMAC[B/EoBNAC:"BaF"d BOTxAMB9C  BOYAM.PF BO/AO7Bh 2BOFALGBIQF2  PBO7AMkB]C8 {BM ADkC0 BOlAM BVE1t BOAJgBs BNAC_mBgCFEV BNTAC(|BFnn BNBAB:38FM, BN@AAC%FnY BNACpBcEKw cBNzACVBDE? dBN ACFB+ eBN^AC=B=+Ew lBNt1ACBw6E{T BOxAM=B>EZ ?BN:A@ACDP |BNðACWBK$ED }BN-ACUBBQBNAC3B*ODNmBN"A@B\BD.*BLӱAOTqCc="BNAC:BqF!ο"BN@ACCiBF~"BNf+ABOB#BNxAC~BDH9$ABNACvCB"C(BLAM>WCJEi)BL~APCsDMPBOAM!B FDN!BN(BsMFP0N#BNFACBTF.NBNvAC?B E3U5BMAKC'VBMaAB?wBC YMBOR\ANZB{sBYBLAOFCZ7ZBN˃AC^Bj[6BN#!ABDC.XFh[ABN7ACBBE^y[BBMABcH0Fdս[CBN&dAALCFY4[VBNhACAB:^[WBNяACe&B{F 2+[BOq(AMB\BNV ACYbB"F4a BN.ACRrBEbBMAE FBCV~bBMdACoBΛE.cBM5ADChxCd4BNNAC"ABrF[ d5BNXgAC,BWF{(dnBMgAMC1CdBOAHKAOFeBNc5AAƫBFJfBN;A@ C|D{fBNDA?C CfBOsAMBۍEo g|BNL2ACB^Dg}BNUABBBEgBNACHBN|FsgBNTAC(BFnhgBNAC:BEBhkBMAC[B8VCuhmBN=A@C9D;BO6AJSBC BOlAJcBwB?[AyD#B?AD,DkB? ADrDmB?A wD-i$E$\B?ʇA SD&0CmB?aA qD.D֓B?rA TD.DmlWB?[AyD#tB?jADC2BOeACXBDZ* PBO(AD`lBgD7 BNA=l B!E BOBA;BEy BO{AE׌BF  BOAI*DZsTF8h BO]AE3GBp BO?ADQB!FX BO AD&BiE BOAEBVF# vBOTA:B FH/ BNSACvTBD BO3A:ΨBuEw BN̞AWmBOA;kB|EتWBO$A:φB#EWBOVAJBX BO(AD`lBgD7XBNIA<B+KFVYBBOAFBǚF>6YvBO0XA:BC1YBOAHA*C F4YBOVAGBȣF!ZBN܋ACjB˳D$[LBNA=+BD[aBNܽACkuBE0@m[BOjAEliBb[BOAFY7BF6!\BNACoB7yEAZQ]HBO+ A@1t#@Gz]BOOZAEBvAE.^BO{A; FBSE xa BO ACBF &a BNACsY»yF毗aA@C f&BNOA<;mBfBNсA;Bk~FrfBNA;BbfBO\(AEcTBf;8F.3fBO8ADBX\E)fBOJADߣBEGTfBNA;B'Fzg?BOAFuB%Fg@BO|GAEKB̃F8RgABOunAE>BFRlgBNȳAC\B34DCBO#AF*B%BNA;(B$E+'BO,AFBFM(BO{AFVBzQ ~BOOAPBBSCTȊ BOpAM։ChF4:P BOAJBEN BO]ANmAZFo BOAN)}_G BOuAMUB.F  BO/AP' Bx(D4 BOXGAOBfJF3[ 2BOs$AM^_R F1  nBO ANBMDB EVBOfAMBFΘWBOAJJ4B>EdWBOAJ_Bm$?[YMBOkAMGCYVBOl|AM}4CFE?F[BO[(ANCEF[BOzAMB1 ER[BO_AJ{Bk E=\BO|uAJTHBqEg\BOAJFBEsi\BO^AN&C+PFl\BOcAN@,>F~!bBOvAJDBrZD)dBO_AJBvC6.fBO~RALCcFO%BO/AOm4E3HBO(AL?BYF)BO|rAL??F3BOAJeBSrF:*BOzAM#)ÒkFӅH_BOTAMtگFF `BOAO‡jFVaBOAN5:BLF0&BL=IAV}CD,!}&uBL?AX_XC1&BL$ATC3&:BL=AXT!CBCS;&/BL=AVC~9C&*BL21AU5C&,.BL>pAXyCR&-BL_yASCO|C}&jhBLAXC|&lBL>EAX=CP&o~BL>AWCxEt&&xHBL>cAX3CXJCBQ'BL86AUC~j'&BL;AV)CuvhD.~':BLFAM CWrDt&'DBLAP[Cy?1C?'BLîAOhCqhs'BLHAPCDg'*BL~APCwCV'BLPAPSBCpDU'BL43AVClE~'BLRAPCD~'BLWATPC~'sBL9AXCg%CW'uBL;AXvtCsD,w'BLTAQ -C_"EI='BL=AXZLC':BL7AW HCFW/';BL2AU3C3E 'BBL=AXFCCDU'BLEAQCOCh'!gBLARCw')TBLAQ-wD#-FT(|')BLBAQZCG 7'*BL>AQmCRo'*BL9AU"Ctm'*BL. AUijC# DD',.BL1JAUZC'-BLAQj=CR~C"'.JBL8AUjCe'1`BL]APDCyDJ'N^BLAP)]CoEn'PBL-AQCW'UBLAQT CUEbF'jBL5AUC!E+c'kBL>AX#zC.'lRBLAQ7C' Em/'oBL?AX: C#D6$'sBL=AVtCw 'sBLcWASnEC<'v,BLpASCFP'BL7EAUC'lBL5TAUKC4C;*"BFd{A5C*$BFA0CB*NBE3A3(FC*O BF0A0)C*BA*rMBF#A0 C@ЁH5BA2A:(DAF53B@ڼA:+DE54B@KA9ݴC>EF55B@;A9C.@D45GB@A;zC!5fBAVjA:!C[E"Ɋ5gBARA:CiEt5BA",A;=DD|E5BAkA:pCFx 85BAI>A9uC35BAA:/D3F![&5B@9A:)Cb@c>5B@|A9XD95B@yA9{*D7Dݘ5B@vA9`D:DQ1'5B@5A9`DB>D5BA6ZA<CՁ5BAIA;Co;5BAA9CpF/52BĄA9oC뉲Ft 5BA9A:CPF9e'5BA@A:PD;F5mo5BAT$A: D Fo5BAA:lDdF95JBAA9XC(E5 oBAGSA:C]E5 pBAEA:|C,#E&5 qBAHA:nCךE5 BA?A:,C^F{ 5 BA( A:yCET45 BABwA:~DynEZ+5#BAfIA9mCyF5B@A:?CE;*5BATA:Cȴ5B@\A;0)ClEU5B@A;CCEϹA5B@A;!C턠C5B@A;%TD@AoF#5B@A: D#E&5B@A9؄DʶD[t5B@ҩA:vgCC#5-B@yA9CD5JB@ A:9C?jC*y 5B@A:I\CqD>5BA$fA:g,DAFYFf5B@A:qDA9C+hDN5B@A9CCF5B@A9CfD׻5B@ A9C:DR85B@A9.C[DY15B@A9CDT5 wBA A;*C:D 15"B@A:SDF5"B@A:D#!F|5"B@.A:CD5,uB?ͿA4DD?T5,vB?A4%D!5?5,BA9OA:KC(E5,BA!&A:DVFI5,BA:A:a%C0oE5,BA;A:~C+Fr5,BA8A:-D9F5/EBAA9rC2mE@,5/FBAA9yeCE-FY5/GBARA9gCF(53iB@SA8LC 54YB@DA9DDA55qB@A9DgDb~55rB@A9{mCLDxq55{B@WA8C.Dn55|B@A8D%D55B@A9"DE1s55B@A9ٳD+D56B@UA:)D%@ 5:BAGA9_Cf5:BAA9C*ZFf$5:BAuA9#C|F*5:BA_A9EC'[FC25:BA{A9yC{FIN5:BA"A:9C7LF\5:B@A:CF>F5:BAA:zCޟFE5>dB@A9hDbC;5CB@A9CE!M5Z>B@A9CjD5Z?B@A90C-DE<5[B@WA9$A;PCF 5bhBAQA:CpKF?5bqBAW-A;C姄E}5brBAXDA;FCD0Qe5bsBAUA:CuqD5d B@A:lCCUv5eB@A9nCD@*5eB@YA9 DC5eB@A9۱DmC5gB@@A:\ZD1Eto5gB@cA:D"EB 5gB@A:1DEB5mBA1A:CMF5mBA A;3CF$5mBADmA:CWEj5s[B@&A9DFQ5s\B@A9C_E#5s]B@A9ޝC)B5s_B@A9 C_C#5s`B@uA9C%D 5t}B@A94D E5tB@A81CYD.5tBAA9DF5tBAA9qCBF* 5tBAA9|6C9FImm5tBAA9$CZFX_5tBAqA9VEv5zB@/A9IC?JE^5{B@A:)zCE-5}B@A9C4Eg005~B@oA9C5E[5B@PA98CIE N5B@A: C5B@ۖA:CC5B@A:C=E5B@CA:DMgF5B@A:CD[5B@A:#Dr2E@K5B@A:ClD`5B@MA9DJEHp5B@ܗA:CVEa5B@aA;kCF ry5B@A:1D15B@>A9ɯDaDj5B@MA9ڌDD~5B@lA9DDQ +7B@qA8DI~D,_7#B@vA8@DT7cB@A8D ZJA7dB@)A8D DMU7eB@A8WCUD9>$7hB@A8aDBx7iB@A8[D mBrG76B@rA8څCTC77B@KA8Cz+DL7:B@aA8]DC 7]B@~ZA8ID CSV7B@A8eDD#X7B@A8ܨCCS5e7[B@A8vjDlCp}7\B@A8DKCj7`B@:A8uwD##Co7aB@A8eD̜Dj7"aB@xA8D}7%B@A8)DCW7%B@A8DMD 7%B@A8wDES7'B@A8ܨCCS5e7'B@A8D 3C^7'B@~A8TDE'6a7)B@A8iDm7)B@}A8OSDO+7)B@A8D73B@rA8څCTC74EB@A8 D-NCF775SB@A8:CG7<+B@A8CTDI<7<\B@A8_D SQBn7L-B@A8Dش7L.B@A8_DϻBz7LB@tA8 DfHDV-7LB@kA8gDhD~*7LB@A8pDD>7LB@A8CrD?_u7LB@A8?CMD1-7LB@iA8CrDZF7SB@|A8LD7VB@A8LD C+7VB@sMA8C٘D&7VB@A8]CPD*7XB@A8DD :7X"B@A8DJ|DKC7X#B@A8DŝCh7X,B@t6A8}DBϽ7ZSB@A8u DAE7]B@JA8FCu7]B@A8DE?7cB@0A8VwDCP7e.B@sA9D T7e7B@A8DJC)7e8B@JA8C'sD>N7e9B@A8"DlAꕩ7eܶA3sD_g-Guԧ?iB?kA6RoD.D$z?B>CA8)RDx*tG?lB?A5D EF{9?mB?סA4DDN?uB?UA6 *C-? FH?B?LWA;jD4RD?UB?UA4DDe`?B?+A5D$8%E߶?B?"A5SeDi/A?B>0A5RYDE \?B>NA4^D*aF(?B>=pA2QDEr?B>A1?DrFaz?B>A1\DFC?B>XA1D+jET\?B=A18DrE? BB?A3Z D)/D? UB?A3>qD5/)C3]?B?^A;D0D D?}B>A3D$E?~B>gA3 ID''F?B>C+A2 DXF=?B>A3D%UEj?B>oA1KD[F>?B=A1S%DES?B>A1DjF8?B>g%A04DOfE?B>yA0 DUE¾?wB>zA3^D&FHS?B?A2D(SC?B?A5DA9&$C&Du ?B@p;A9PC'E/? B@mA8DD"?B@m`A8DDo?B@tA8X#DxDʖ?B@mA8D8D?B@pA8DDJ?"B@jFA8`DDj@?#B@nMA8'D&D)?]B@xA8C`D0?^B@t.A8D&2'E-(H?_B@vJA8DDQ?iB@nA9gDLED?qB@sA9 D Cg]?B@$WA:htCSE?B@A9h?DmBQ?B@.A9D bD?B@yA8GD &}C?B@|=A8NcDP?B@NSA7DE?B@2jA7D? F;7?B@A7;DFs?[B@#A8UD1?\B@/A8]D9DOd?]B@A8iDCs?aB@A8k2DܲC0?B@qA8XDEy4c?B@r3A8DеDe?-B?*A5eD2XFZ ?.B?@A5ƥD%$FF?/B?A5D33UFm? B@:A9DE?"B?A5GDFFA?"B?5A5D$E?h?"B?!VA5DVF?"aB@oA8PD"EX?"bB@n3A8DvEkb?"cB@nA8.D"܋Ey?#B>sA4*D+E3?$WB?qA6?qD0?$B>qA2VD E?$B>0A2KDyFB-?$B=A18QD<=C?%B@oA9FDx~D\?%B@oA9DDE-[?%B@ngA9D Cs%?%B>aA5CDE녾?&B@oXA90DDO?&B@s9A9DYCH?(B@oA9QxC5ED?(B@^A9CVElK?(B@mA9DTmE?(!B@A8U{D?(IB@5;A7H2D3E.'?(QB@pA8yDE9?(SB@fA86D{Eq?)B@xTA8`oCuEE?)LB@BA9&D E]c?)VB@?A7r\D&Eޯ?)B@A9MD&'E`O?)B@A9r|D!Dj?)B@A9hDB ?)B@A9r|D!Dj?)B@A9qRDsCDJ ?)B@A9h=D@ ֋?*;B?A4SC{F^?*B@aRA7ԮD {gC9?,uB?ʳA4\DS@?,vB?A4D!?.AB?A5ҺDHEC?.BB@A5DF?.CB?A5}D"4ME8/?1B@jA8aTCD+?3B@CA8kD/L?5 B@u A8DcDlAN?=B@./A:'D Yy?=B@A:WDE*?BB>A/>DGD?CB?A4ECE?CB?A5D) FT<?CB?ڨA4ȁD#4D2?CB?A5D'/Fy?CB?ƀA5D F ?CB?֮A4D4֟D?CB?.A4DDn?CB?A4DcD0^?CB>IA1DE#EКX?CB=A1$VDE,*?CB=؏A1#DE/?CB>A1qD* E24?IgB>A4vdDF?IhB>TA4RD#nF?IqB>rA3D"FA?IrB>uA1D`FKc?JB?>A6D hF?JB?A6D*vEq?LB@3A9'D lE+?LB@+A9DES?LB@7A9zD E:D?LB@pA8\CES?LB@p;A8?CE|?LB@pA8CJEXJ?LB@|A89Dش?LB@|A8M"DTC ?LB@{A8CX CE?SB@n"A9D DA?SB@pA9D}D?SB@uA9D7?SB@nA9D eD/?SB@ZA7D Dx)?SB@P5A7D Yh?TB? A5Z CxrA1wDX+F*?WEB?-A5TD!QEXe?WFB?A5uDEؑ?WGB??A5pDEr?X,B@uA8QD yD o?X-B@|A8DD7'?YB@pA9D(Dz?YB@n:?YB@nDA9D^DH?YB@mEA9DDDC?YB@mA9I5DCt<[?YB@pcA9CdD?YB@jA9zDEHZ ?YB@VA9oDEh?YB@UA9}.D'EaA?YB@iA9D@Eo?YB@[A9uDDA?YB@^pA9o*DP?YB@oA9YC{Et?YB@nA9MDe\E?YB@mA9sD ES.?ZSB@YA8TTD?ZyB@!A:4D E?ZzB@$A9D=E~f?Z{B@!A9tD"DĄ?ZB@J'A9D Du?ZB@;sA9TDj3E\%?ZB@NA9{bD;yEW?[B@qA9DDn?[B@pA9 D C5[?[B@rJA9mDDțl?[B@p A9D4E"`?[B@pA9fDD?[B@%A: DL?[B@A9zDnED#?[B@A9 D8PE#m?]0B@YA7ΓD#E ?]hB@o|A9CDl5CF?cB@xA8D(C?cB@nA8u)D zEA9?cB@}cA8YDD㧗?e-B@pA8ݑCE?e.B@qEA84D IEXmm?e/B@r?A8VD)E|(?eB@p A9cDM?eB@qPA9D-^C?eB@}A8KD^}Dϒ?eB@zA8DjDV2?eB@{A8D DVk?eB@{TA8C48C_?eB@{TA8C48C_?fcB@lA8D~$E?fdB@nhA8DM`El?feB@rA8,DE E?foB@PJA7D D ?kQB@meA8D@GDc?kB?A4rC1EҘ?kB=*A1DUC\?kB=֯A1)D C&"?kB>;A1|DF;?kB>6[A2M3D(Fɭ&?npB?}1A6SD.ʵDם?p(B@$A5{DEiL?pcB?A5 BCnE'?pdB?|A5"D"aE2?t_B?BA6XD1Emh#?t`B?A6UKD4EJ?taB?A5D]F# o?umB@sA9D1UD?unB@eA9bCEz?uoB@owA9/C (E;Pi?uwB?PA5DFͭ?uxB>.A4D-TF+?uyB?QA57D8sF?vB?A6RD.jEi?w%B?A4%D!5??w&B?>A5\DK D ?w'B?YA5R{DE?w/B?TA4DKBތ?w9B?&A4tDD0?w:B?DA5D&-Fg?w;B?.A4DDz?wCB?A4qDAA1D-BEF?B@PA8^DD>?B=A1DE'?'B@_A8DEKR{?(B@9=A7g$D2E a8?)B@KA7cD1Dl(?iB@.A:~DE?jB@@A:pC^PF/T?kB@.KA:D-E?B@UA6$C#F!W ?B?HA5D"FP?B?DA5ƭD FOy^?B?TA5D'&"EQ?B>A3zD#E?B=.A1R!D?B@nAA8dvD C?B@hA8`DDZF?QB?SvA5YD&8,D,l?+B?[A;D(EE?B@$A9D ~F?B@!A9KD @B?ADnE@B?ckA;uD.r!E{P@B?A; D'E+@B?u2A;9DME@B?)uA;D;?E(@#B?}AlCP@KB?A<.D(E@LB?uA<DF@B@!A:D @SB@LA="DC$@B@"A:D#@&B@NA:D5E@$DbB@DB?*A;D<7qE9(@EB?VA;sD"_FB a@HB?*A;"D;D@XB@^(A<D 1B@XB@I|A=+ED qC$%@ZyB@;A;.DF˦@d3B?(A;D:ҭE3@d5B?!A;tD?9hB<@nB@NAB?pA:DVD@s?B?A:Da%Ef~@tB?jA;DlhFV@tB?:&A;D4^E@v#B@A;=-C!@ЂU@vBALA;V|CBT@~B@#A;9CUVF}&A@~B?A:ܬDJEh_@~B@A:C2tEN:@~B?vA;*wDE@~B?cA: DޙE@~B@3A;+D E|@B?A;DD\E@B?W*A;D-mF/8@B?qA;gD&E@#B?A1A;:D5Er@$B?%A;eD> +CO~@B?WA;pD=tB6?@+B?A;<D!,7F6M@B@:hA;&DXb@B@OA=S!DzE|u@B@OzA=N0DE4a@B@R A=DI4E @"B?A;DB&HB>ӛA,D ]A0DPD+ sHB>A/D" EHB>A1j`CFRHB>VA0hDWDަHB>A0o-DYOC}bHB>`A%(SDSqF)vHB>cA$D3-E=HB?YA3D0u;DH$B?.HA3ןD CaH'bB?>A4PD.E'H*;B?A34D+5C.(H+{B?'A3"D4H9[BAA!DHBB>A0DS2kFHBB>̨A2v"BFHBB>A/D2EQHEkBAA(ND[C*PUH`KB>qA1n]CFSHwNB?A39 D3DK$HwOB?A3SRD2¸D_HB>A*Dl3F1HB>IA$D4H[B?A3AD-IBC^A'CCIIBAA(gDqACJIBCyiA( C BIKBCfA({CEFILBCbA(oCE'tIMBC_A(CrEuIBCA(hC>,F*IBCy2A(>'CсF;;IBCA(gCF IBC}%A(3C2u>FԩIBCjA'ؠCiEIBCA(mC~iFaIBCsA(CFIBCA)/]CHF>jPIYBC`qA'DF+=IBCeA(KC ERIBCBBA&CpF9uBIBCuA'4CqEIBCa6A(7CELiIBCA'êCENI$sBCA({CF(I&_BC3A)AkCVE+I&BB/5A!.ClI4EBCd A(O CvI4FBCA$CI4GBCQnA'(|CuF֟I7[BBA#DrDICBCA)C:oDlIHBC~ A(5CER6zIdBC8A&D#D< I|BCA(-LC"oIBC1A(1!CC EIBCVA'D3F,IBCrdA&jE GPLB?vADLDA%yD3srEOL4B>A%h(D1#/E5yL5B? >A%OD/EnVL=B>A'Dpw2F^L>B>A'ZqDiF:6L?B?A&.DRGDLHB>A)D[EtLBAA!xD L 4B? A pD-[C L >B?yA#7#DJYFOuL ?B?ɦA#DF'L B?ZA $D&bAlDIF0L FB>A;DFS\L GB>AĞDF.,;L YB?PA+DzE6L ZB?^AD;E!L [B?fAD6E\(L B>lAUCӻPEwL B>ACCqF/L B> ACtEL B>tAbDYEL B>.AaAcCœE=mL B> dA-C FL B>IA-C;E|L 5B>gACrEmL 6B=ACFE7L 7B>tAm7CDL ?B>AOC]E4)L @B>AFC F3QL ]B>%AD^EL ^B>FA.D-E~(L _B>AmD*EMJL gB>I{AmCDFL hB>(HACEB]L |B?AjDEChEL B?A7tDXEJLB? oAZsD+bCn7LB?NA$]D'FLB?rA%GD/ENdLB?PA$-D0)E83LB>A+DRCE(AaLB>A*^D^|DLB>A(}DkFKLB>A(ODY|DZLB>A(-@D',FLB>A$D2F"VLB>P^A#D2?LB>SA$DbEJ2LB>RA$!D [%ELB>EA#D8=D{ L(B>AUDFEiL1B>"A$D<F ^YL2B=A$GD=DL3B=A$YD6DEL4B>8A$D:F+LcB?I AڃD%w^EٌLmB?xAGD%FHR`LB?A!S"DEmLB=BA$(@DU+ELB=A$)DP-EoLB=A$!Dc REsLCB>7uA%^AYCERLB>A*vDcEuLB>A&DOExLLB?MAD@F;LB?+AyCELB?"ACڧE{/LB?AKC@ELB?'mACkELB?%A AND nFtcL!B>AVDNF6 L!B? ACFL!B?ACE L!B?A CFELL!B?IANCxEbL!B>JA=^D(L!B?HAN\CEL!B?>A5D#$EiL!B?WAC EL"B?A#3QD L"B?YA#LHD F9XL"B?WA"DO EL#3B>7YA$SD9;dL#4B==A$X]D;ihFL#5B>hA$CDA7BF| *L#>B>ADtFKXL#GB=A$>nD`E"L#[B>A%qD[VFL#\B>A$:D4|E|L#]B>A$׼D83ErL#yB>A(ZiDYEZ5L#B>A)nDbF&PL#B>A(-D>EծL#B?AAn4D'\Fiz#L%;B?yA$9D'HFL%FB?GAD-E$L%OB>'SASCEL%PB>ACe%EҮL%QB>GAC*FcL&gB@ A"V>DF]L&hB?A"JDD'EL&iB@$A"K'D ^E9>L&B@_A"D*AFoL'CB>A*vDcEuL'B@ A"D4FeL'B?ЛA!Dt)ExHYL(B? A#DJE;L)B?A#L1D!DSpL*vB@NA"2FDFL0 B=+A$!DQ[DJsL6B?AD KC=L6B?4AD7wF٠L6B?A D ՒFPXL7=B?NAD!MFL7QB@[A!KDFAbL7B>AKiD%CKL7B>AXCEAL7B>A CEL7B@`(A"%D9L7B>A%ID9$F!@(L7B>ǼA&DDOEҒL7B?qA%<>D-tL7B>AZDFg/L7B>q.AD :FL7B>/_A^C F @L7B>,pAEC8 FÁL7B>2AbCxF`L7B?A!D*oFL7B?qA DEL8B>mAeDDERL8B>|A+C EL8B>ܗA%GD>$AEcL9B?aA#kD(rF L9B?A#KD 3Fbf{L91B?A#D&LFkfL9B? A%YDD0EmL9B>A%aD9EL9B?7A$zD1FP0RL:qB>AD,Ck_.L:sB?A7DBG8UL:B>ACÕL:B?hAD#DF}9L:B>AmD- FuL:B?!A7DݾEcLFAJpD6F}LA B@A!~DLBB?Aa\DΩFDLBB?tAD1FuLBB?]AD\F9LC1B?:bA$D:GLC2B?yA#OD 1Fc@LCOB>A%;D4;E)ecLD B>A&1D\eF`LDB>A&lDG?ExvLDB>A$DGFvLDB>QA\D(tvFIYLDB>#oAaCINFLDB>\AaDeF LD5B>+ AUcC?F8LD6B>H[A ChE0GLDB>]!ATHC_EBLDB=AcCA^C$LEB?ߩA$D BC=bLF)B>AFC>DGLF*B>1ACXF /LF=B>ɃAeCHEɶLF>B>AC=E0LF?B>A7CՙhDYLFGB?AC˗F\DLFHB>AC薯ED%LFIB>AC|9ELHB@cA"D3E<LIIB>?A%D=1ELIJB>A$vD7DOLIB?-A%>D,oDbLIB?#A%(&D,cE),LIB?1-A$D.'ELIB>A*oD\EeLIB>,A)DkIE!GLJ%B>*cAlACF!WLJ&B>A<D6JFLK3B> ACE)LK4B>ABCFLK5B>ޒACBEn@\LKGB>'^A$`VD)FثLKHB=A$G{Di.FxLK[B=A$2DLEmLKoB>A&D`=)DuKLKB>ACf F{LKB>A+9CEhWLKB>AfClEALMB?AD"EjvLMB>JAJD)(E4uLMB>BA;CџEvLMB>!tABC!ACTF6LUB?`A#DYSF LUB?ADFLUB?pA#D!FPLUB>*AyC憢C:&LUB=A$DWUD +LVB?/AND*EqLVB?A%D)HELYB@sA!vD%D7kLYB@R_A"-DWE LY/B@>A")D ;E#LY0B@3A"ID KEBLY1B@A"ND vF [LYB?pAtDDIF0LYB?v$ASD} DW&LYB?A.DW{F/<}LZB?AyDOD ,LZB>AC#L[iB@}A"PDEL[jB?A"iDoD@bL[kB?A"UD7E-L[mB@A"FDDE/L_4B>A*DlEL`UB?.A$/D1F*vL`WB?)A%-D,ED1LbB?GA"#DEZkLbB@EA"&D Y*E(Lc9BAA!qD@D/TLcB>[A$D02pEJLcB>A1A$]D# FbLcB?TAD'MgF LcB?4OA$D2rF=LcB>A%jDD FLcB?O A$đD1uEXLeB>A(XDUbLeB>A0LVDZ;B%LhCB>XA~CtD5۷LhDB>^AsCFCLhEB>^Ak"C)EALhB?ߡA"yD%sELjKB>AClEjLjLB>lA)D&ıEKCiLjMB>zA|D0ELjWB>7AiD#ٞE'lLj_B>XA!.Dq2Ez%Lj`B? A7DF1"LjaB>A\D] EnXBLjiB?wA OD!6EELjjB?A ǀDVSFLjB>bKA85CFLjB>'A'C<FZLjB>SAwGC]E:LjB>*yAwMCFLjB>ACCE=LjB>lAD!JFFvzLjB>fA:,C&lE5HFLjB>kQAECDPLjB>AC6=DҒlLkB>AzC<>EtLkB>GAPCD;Lk;B> Ap:DkFYjLkuAD1E$Lk=B>ADD DLtsB>OAA_DEE´bLttB=Ä́C[^EqLtuB>JAnCD'$L|B>QA3$CCzEL|B>cA`5CH2ENjL|B>AC[EքL|%B?AAAo[D#-FqLB>A.C,C?LB>/yACVqF+a(LB>6ACUFEILB>1A>ChELB>A%ID4+E;LB>A$DHF9LB>pA%MD@4ELYB>*ACmFILZB>A݇C̙DL[B>AC6B2>LcB>ACpErkLdB>u AD pEg|LeB>m A5CD*LB?5AA/CE[|LB?p;AkD+FfLB?BeA{aC3eEELB?JA$D)HgE6LB?.AoCBD}LB??AC`EƝLB==A#kD_ F LB=A#D_FF˛LB=IA#NDFTLB=A#iDuECaLAB>AHDDLBB>`CAbTC EhLKB>A|D HF8xLLB>OAFDyHLMB>RAADZFLB>KA>VCUE>LB>DA.CELB>YA\CHFLmB?A8D rFq\LwB?A D DۆLÅB?aA DFLUB>A(;DT^E%LbB?dA!CA]E45LcB?AC[DeeLB@.A")+D ADuLB@A!~DR1BDA$$CVE|xR9BFbA5CjR7BD͇A#8CnFK =RKBD A#CNqERLBD7A#CKFZURMBDA#*C⮻F RBF/A"CFZR CBE?A#cC E8R DBExA#CݴF]R EBE:A#wCfDǑmR MBE:A#t&CtE\R NBE5A#p1Ck#ElR GBEA3ZC0R BFtA6&DC3-R }BF A#tCFoRBEiA#ŚCͺQEXRBFA1#MCiyROBFtA6"D\R/BD0A#ͧDFuR0BDiA#.D1FM0R1BDVA#!DFR%BDA$$CVDlRFBFA0D!CjCk*RNBDhA$4_CQERNBDA#unCRO%BDjA#}CffRRdBE A3X?CԼR[7BE3dA#CECRanBE{A#C('EV RbpBFĹA!C{RfBDA#C*Fj RfBD۳A#MCq/ERfBDA#{C͊F7RrBEA$C׵D^RtUBEYA#D6FRtVBEA#âC1EFRyBFuA65D/]D2&-RzdBD,A$C2D>RzwBEA#yCFpORzxBEA#bClEERzyBE3A#CERwBEkA#|Cƪ$EпRkBE8A# CE.RlBE_A#CݻF_RvBEA#CC<%RKBFzyA6yD\RBE A#CÅ$ER BEA#ԲCTD5R!BFA# CF8RBEd|A#v3Cֱ ExvRBE_:A#qC SEj:RBEA#C5FRuBFA0%"CB[!&RRBEӹA#߳CD` RSBEA#CEMERwBFuiA66DRoBFA0'`C1RbBEA#CTD- TBHA/MCGs4TBGmA/CDTBGdA/C2NDcT?BG:A8DzTrBH]A0TC9ETBGNA.C~`Ev*TBGOA/hC1F |TBG!A.ՆCEԹTBGqA/C/ETBGm A/CAEx1TBGnA/y$C>EhTxBI3A.8C?TBIA-Cq&DbkTBI^A<=DTBIzbABHwA0\qC8EkwTABI*,A/CSTaBGȁA.cCuE+tT}BGkA8IDAHCT~BGo&A/iCErWT 'BGA.CˍE`T (BGpA/)CcE^T )BG}wA/ClEzT OBG&A. CQET PBGA.]CEBT QBGA.nCz%E1T cBGA.WCu*#EPT eBGջA.+CvDT BGIzA0'\CDƣ9T#BIABIA.CT?BINA0FC TBjBGNMA0#ACY7TEXBIYA.*CZD TEYBIA.CDTFBG/A.CEBTFBGA/)C$ZTGBGA.CyF*THwBIA.&C7RFaTITBGsjA8KhD0CTJBIATZBG4A.C^F<TZBGA.KCi_ETZBGvA.OvCNEN.TaBGMA.sCEZ.fTbBIAA0CLE9.9Tb7BHA0~C;ETcuBGf]A/՝CDAkTcwBGgA/͚CehCo TeBIZsA.CcEjTeBHu'A0P3CE` TeBHA0emC}EdgTg BIA/CF`^fTgBI2.A/BC(E AhTgBIFA/gCXF +TgBI2A/C,EThBHA.C_8FvwTl BI1A/pCuE՞TlBIE\A/rACF TmBHA.LBY F"TmBG A.9Ct ESTmBGA.AChJE^GTmBHA/CFCTmBGjA! CT}BGA.:C)ŠE T~BG(A.aC{mE TBGѥA.NCtEPLTBI@9A/>nCEfTBI9pA/gCC'TBHOA0TCSCEˆTBH^A0YCEKTuBHA0O1CZEYtTvBHA0OCюE~TwBHA0WChF -TBHA0CkF2TBIXA/CPFWjTABIpA<^OD3T.BGA.۷CҷDTBIAF#< UBDA3zCF/U"BF_A5CwU#BF_dA5CmCHU5BDA3gC˦FtOU6BD\A4CFXU7BDA3C"rF#UBDA3C?EU1BDA$oCE-UXBDTA*CUYBDMA+CW(Ek,UBD."A+ D F 5UBCA*C'F,3U BDA3y5C1D U3BD6_A+7CEaU4BCA)ChFU5BCBA*/'CDwvU_BBA8UcC' UBD=bA5C&F)UBDDyA+CMC UBCA)8CCtUBCQA)5;DG vUBCA)GC˰E/UBCA(CpFUBDUA*CCv$UBDU A*iCAhCUBDU5A*CHCaU BD:$A+DCtE9U BDUA*C CRUBD8AA+(+CVESH]UBCA(CɴUBCWA*C[DUYBC@A(CBUoBDA3CϏ}U0BD~1A$C$UHBDmA3C_Cp[UBCcA6YC{xF#.UBCA6\CϡF:@?U$sBC4A)8CjF_U&_BCA):C^U&{BDA3CǠFU&BC~A7sC@U,BDHA5C[F` \U,BDA5_C݃F}U0BDL\?BI!fA3CIF\ BHU#A:D\ BH]A:АCF*9\ BHrA9C8BEߛ\ yBGaA8(D-F&\ BFi=A5CKE,\ BFnRA6(C.E\ BF}A6p#DEU\ BFA6 CEs\ BGRA8D 8E|!\ BE):A3lCGFm\"BIzA< DOD˵\@BI+A?oD {Dw5\TBI>A?3~D \1EG\BIkA?RD jErF\hBIA?KCDE\BIA@oDEE\|BG_A8!DE|\BFA7cCEiE\BF1A7CD5\BFtA6*TD9jDe\BFLtA5ѴCXFv.\BIA?D D#-\MBGA9|C$F \BH{A;0D%{D]\BGA9uD:F/^\BHHA:C)Fc!\BFA7" DuF\BFA6CF/\ BH8vA:.?CnEk\BHSA:RC/FS\BHWA:ˮD\ODJ\BH-9A9C5FE'V\BEsA23CbF;\BEA2U|C.*FF\BF [A1w?C4eFRy\BFjA4#CLFS\BEtA3;CE\BFA7CE @\BG<A7wDBDx\BG`A9 C?EB\BGZA8&!DќE \BG.A9BQC\BIOA<DdC0d\BIYA<DVD0o\BH:A;AD(E;\BGzA8UD1\GBF;A51LC1\OBFP4A5CF\mBEtA3^ CFs\nBEA38iClF_\oBExA38SCFps\9BEA3CFaD\OBIYA=lDlF͝\kBGA9CF\lBGA9CCE\mBGYA9CиEE$\oBGA9xCr-EϪ\pBGA9NC%DlU\qBGA9"CvNE?,\ BEA3+C{E\ BFA0CC\#BGo_A8HDDC\$BFA1pC;E\$BGA8CﰎFy\$BFeA5CEA\% BEmA3?CEe\%BIeA=\DJHFN\&IBGA9CE꼃\&JBGA9|CIhF5\+-BIA>D EBh\+BG;BA8JD~Dxq\+BH$9A9ÚC̎DK\,BGA9{C-Eu\-PBIA?wCg GFS\/cBIA=D*Foh\/BIA<>DD\1:BF^A0CyAa{\1DBF A1CmF3!\2BIA?SDLOD+)\7BHLA:@CxCH-\9BHgA;"DE\;BIRA?uCF٫\;BI|AB=C΢o\; BIUA=@CPUF^$\;BF\A5CCS\;BEJA4CE\;BEޅA3CdEH\;BEA3$C~fEA\;BE:A3۴EӪ\;BEoA2CFI\<BHȯA<*D ZF\\JBIAA?CF[>\NBIkAA=CrxF|V\NBIXA=CB]F~\NBIA?DE\NBF@A5WCqhE6)\O BF#A0 C@ЁH\O/BHA9CE\O0BGUA8&'DEY\OMBEA41"C)\ONBF-A5C$>Cu\OuBH*A9C \P3BIA7wC͒UF\VBI#AiDpF$\WBH2ADQFS:F\"BI%MACD bFe]BHVA9C]BH,A9C%E] BH A9C%DO!5] yBH A9CUC|] BFoA7BD [Fǥ] BFڠA7[D$9FZ[] IBG8TA7VDw] BGbA8HDCE]BCkA7Cw]|BGA8ށDE]BGA8DEK4]BF#A7;D.ZFC,]BFA85CD}]BFA6ޫD׫Ev]/BF)A82HCD{4]MBGA8C%Fv]kBGA9C똬E]BHA9CD/I]BG0A8-DE˰]BF0A7]pBDjA4$fpB@rA9;D 3BѝfuZB@zA9mDAAܮfumB@vA9D NC&funB@q7A9D:4Cff{-B@rA9/DՑf{KB@rA9Df{TB@mA9QDD8-f_B@~A9CDBrBAMA:C CXrBCA7CGCɱarBCeA7"CEf?r1BBIA9nC E r2BAҮA9ghCcFJr3BB* A9rhCF Ar;BBmA8ңCF?+rr)BBSA9z-CEtr)BB2A9yCE?r*BCk"A7wCfyExr-GBBCA8/0CCr-RBBoA9RrChr/GBBYA9CxEjr88BBHA9CF(r8_BBaA7C[Er8`BBiA7ChF;qr8aBB A85CAFRr8iBC[JA7}C0zE;r8kBC5A7sCEΆr8}BBA8cC'D/r9BC>cA7zMC#E~0r9BBA8 C` Efr9BBw4A8]CFr9BCA7]C1nC܏r:BBA9wCF*=r:BAA:DCFPr:BAA9CυErhD+uzAB@OA; DYDzAB@LA;c)CzBB@A;,DD zBAB@A;.DkE6zBBB@BA;eC8DzzSB@z4A9DxE)azTB@A;ؒDE顼zTB@A;ԖCEqWzTB@1A;@C>EzTB@A9C7DzTB@ĨA9LCBDzUIB@A:sDVE(zVUB@ A;CnF6zVVB@A:dD)E.zVWB@˲A:CEЪzVB@A9hBD?`czWB@aA:CE_zWB@VA9CEzWB@A:2CfE zWB@rA:OCEcY4zWB@A9DEQ^zWB@A9C DzWB@ƘA;&C]FnzWB@mA9CCMzXgB@oVA9PDTzXB@3A;D(9FizXB@dA;D zF6zXB@mA<DEDzXB@ovA<DvEzXB@`A<HD(o.EszXB@A<kD&EMzXB@cA;dDD}zXB@lA;C2DCzXB@A:DEzXB@A:7CEVzXB@RA:\DjAzXB@A:,DjC9<zYB@rA9DD3zYB@pPA9:DhD~GzYB@ymA9?DDlzYB@nA9CDnCzZyB@EA;rD E+zZB@)A:@D 7D<0z[B@}A9D$EY4z[B@zBA9D E"z[B@{A9DE(Hz[B@tA9JDeDz[B@{A9DVAE3z[B@|A9DEDz\B@pA;UCçF6}Vz\B@LA;4CFz\B@fA:KCϧKE`z\'B@ZA;C EKz\3B@A;kCNF3Iz]]B@֤A;#jCfB5z]B@pA9D/E 4z]B@bA:CEH z]B@A9?DFEA9DCDUzeB@ A9D7E JzeB@zA9LD`E3=zeB@yA9 DFEK]zeB@A9'DZ DEzgB@A;#C0E+Ez~B@:zA;C+D Ez~B@1A;D üE}fzB@A;HD"E+zB@EA:C{ERBzB@`A;ACFzB@A<DzB@A; DCɍzB@A;WDDIzB@vA;-DC~zB@A;8D9?ENzB@LA;70DECzB@OA;`ID4?E͏zB@lA:%C9zBAMA;CCnzB@A:`^D2.D5zB@A:dD{>E}zwB@@A;wCWEDZzxB@pA;D D5zyB@A;@D SEzB@JA;%fD)E)zB@A:CEzB@AA:;CLEHiziB@HA;D yEZzjB@3A;ED DeE%r[zB@S9A;ID ENXzB@IUA;{DhE=zB@IA;| DELzB@TPA=D D zzB@(A9C8B]z{B@A9?CDjz}B@ΎA9ZCCz~B@cA9C-CzB@xA92C:C&zB@.A;=CC3fzB@3A9NCdD-;zB@A;L'C6F81zB@A:DFzB@A:CEzzB@A:(8D8B[zB@A:>%D C*szB@A;kCFF }B@qA8WD 9D} B@{A8V C|cE}B@mA8IDE8}B@tA8KD D}B@n4A82D5E6Q}B@mA81DE+H}B@qA8PD=D`}"B@hA8aD1D}#B@lA8}CE3P}?B@A8DgfC}B@}A8\DufDE(}B@t2A8LDjD}B@_eA7ΣD Y}B@aA7ѰD K}[B@A8DoD(}\B@^A8nfDEO'}]B@>A8DtE\}`B@A8wDD"_}aB@A8rDERN}"aB@mA8./DE}"bB@l A8\DDc}"cB@lA8sDE2qp}%B@yA8ڦC+Cg+}%B@zA8CxBj}%B@6A8D nEdj}%B@A8w$D7E}%B@ A8C E}%B@A89DEpR}%B@A8DoD<}%B@A8DE}'B@qA8ZD}E^c}(B@A8qDpD}( B@A8gD ?zDՋ}(!B@A8iD-D.}(IB@KNA7D DG#}(QB@iA8lC{Ei}(RB@^A8Dc_E`6}(SB@a6A8#GD_pEy}(UB@fVA8^D D}(VB@fpA8_DGSD}(B@A8gD8D[}(B@wA8C"kC2&})B@tA8XD|EX})B@{A8>Dq+E<})B@|"A8]%DEq}*B@ZgA7D u}1B@fA8cD AD}1B@bA8`D DNZ#}1B@j9A8lkC?Dl}3iB@`A8}LB@ A8]DDN}MB@$A8DDeD}MB@A8D MrE}MB@_A8D~E 0}SB@{VA8KDcsDi}SB@ynA8IDDi}SB@[NA7͝DDGZ}WcB@AA8DDL}WB@A9 DD v}XB@TA8|)DlHE4-}XB@mA8{D!E'}XB@8A8DD R}X!B@2A8D@E`}X"B@A8DzE}X#B@A8CE/}X,B@nA8d#DC}ZSB@A8uvD>{E6}\B@A8DC}\B@eA8aD?B}]+B@kA84DD}],B@fA7DED }]-B@i!A8:DAD}]0B@aA8eDIE}]1B@kA89DqD&}]B@vA8D]5D@H}]B@WA8C,D0}]B@A8ODɲE}]B@A8D.E#r}]B@A8xDpE}`B@A8CHD"}`B@'A8D_}cB@jMA8bDDV}cB@t$A8BD YDG}e/B@pqA8CDD[}eB@sA8\DCYO}fcB@jA8`DWD}fdB@lA8sD[vD(}feB@rA8[D E@y}fmB@i\A8 `DEDS}foB@XNA7D ]JEp}kOB@c8A8_D DHZ}kPB@_A8[DRCƬ}kQB@fA8^8D @DZ}kSB@bA8`]D qCo}kTB@^A8ZDbB.}kUB@eA8^~DxDp}tB@XA8CRDVi}tB@WA8/CEyGW}uB@2A8D&C\_}uB@A8Dq}uB@A8D3}uB@aA8]DC }{+B@pA8sKDvcCu.}{-B@oA8sD%Cu\}{UB@p!A8k7DCu}{B@wA8BD1HEYqH}{B@vA8QbD|Dm}{B@gA8_DD>σ}}oB@gA7DCcG}}pB@ZgA7D u}B@A8FDDߐS}B@A8DۅD2u}B@A8+DD}B@A8D(D>P}B@cA83C~DMcr}B@sA8C}B@A8D EG} B@oA8ƒDDԘ}B@wA8SC%E }B@VA8C 2Cn}B@bA8D aE/R}B@`A8Co Do}B@AA8@DPD}B@zA8DEt}IB@tA8ϑD]EJe}JB@A8PCdQCF}KB@xA8CE {}MB@A8DC}NB@jA8DVC2}OB@A8CDI}B@A8{D nEtq|}B@}A8DCA}B@A8bDDE*}'B@fA8 YD |EmBy}(B@PA7ÑDEw})B@XA7D AE}B@A8DmC\}B@gA8_DR3D5}B@d A8]{DD}B@UZA8D #EQ}B@ePA8^-D 4Dg}B@g(A8_lD`"D}B@dA8\CwD~BA'A;C6F~eBAA;D F ~fBATA;D ^F8~gBA|EA;DRF4P~BA`A<DF~B@fA:> DMGhG~BAZA:DFP~BAA;{CF]~BA`A<$DDF~BA@A;C*F m~BAA:ӎCkC=~BASA:CqDL0~ oBAKA:GCɀHEr~ pBAJA:=C;E-'x~ qBATA:XC"EV{~ BAGA;gDWzE~BAqA<\D2F?~BAYA<[D]F)~BA{A;CkHFZ/~BAoA<*OCWPE~BAjAw~dBAA< CH0E:1~eBAwSA~vBAcAjBF A1]C+ET&YBEA3CDaS&cBEA3t CHE(BFA1tCxE.BEA3nE̥BFA1CBEA3A0ACFEʹiBF_A/BCeEC8 kBFgA/C[EHoBFeA/COD2E}BFA0yCIF&`BGA0]CD\F !7BGA0CEBG-A0fCUF(BFgA1C4E BF6A00C FJfBF#?A0CUC#BFA0CqBEc>*CBGZA/(Cz[EvDBG0A0tCF4ӜEBGDA07C۠E0BFA1"C1 EBF{A1C,D-VBF"A0nCAE5 ;BFSA0 Cx FBFA0CXE"FBFA0]CgD:FBFA0.CE=gFBFA0S>CE FBFA0[CD GBF-A1[CCvGBF%A0COC݅JBFA01cuBG&\A0~CPF3CcvBG/A0C0EycwBGLA0CBEN@rBFA0C}EzrBFgA0(C,EU%r BFA0ChD@grBFEA0CUE3%rBFݗA0|CrE$rBFA0CE rBFA02CE*azrBFA0CE=r&BFŹA0mCCr'BFۚA0.CCKE.r/BFfA0)Cr0BFA0.CbE=/r1BFѩA0CE>jrMBFA06RCoDnrNBF!A06CīD7rOBFBA0{C6BrQBFKA0NCBWrRBFKA0gCqB9rSBF{A0CrD|~BFQA1 CE^R~BFJA0CE܁BFA1^CZE2܁BFA1CsE܁BFA0ClE5܉BF A0CbE9܉BFA0wCEЄ܉BFA0cCa!EӉ+ܙSBF#BK(A/-Clbx@BLjA.LCI:CШx@BLA/0CYE x@BKԢA/HCx@BKA.CME=txC7BKQA/CNF0U=xCBKA/8CdEjxHiBLfA17 Cro0E xJBKdA0)CpECaxMBKwA. CvCvlxMBKA.ChxMBKA/CrbE)^xPBLA0BEks|xP"BLbA1'CJ^CxPBK$A/CCWxS8BLA/yCaExXBLA0C@ CLx\7BKPA0C;D¦xbwBLA0 Ed;xpBL*A1/ACfE}8xpBL A1-!Co7Epxx"BKA/wCCXx0BL&A/CcrExDBL BA/<[Cq8EB42xLBLA/pqCZI!E;xBKA1MC|D{x,BKA.ݰC]EGx6BK܉A.Cr7E xBL#A1CFfCxBLA/ RCgExBK8A/?CFxBLEA1(uCPJ DuxBL:A/ȝC)[FT)xBKA.CmEpxBLFA/CFK7>xBKgA0uCZEsYxBKbcA/XC&FxjBJ7A/C:=xBKA//CmExBKWA/CeFFnxBKA/OCnD@xpBL!nA0X C4E_xBK[A0(CDƩxtBL0A0pB#EĭxBK~A/nANC}C? BFBA/ C #BFBA/ C B?,A I CY0D Q"BL(A0YC/#(BF0A15CXC#0BF[A0C@E*#aBF)A0`C#?BGTiA0C}E۹#BFvA03CqEj#BFA0}CE#BFA0%C{E[P## BFYA/#C7EXp# BF^A/CzCStw#3BFBA/C@QD"#=BFYA/.CeEV#BFXA0gCEbc#BF)A0CJEk#BFA0ˮCCL#BFA0+C=Em:#!BF`A/JCxE= #!BFX5A/AClD#[#"BGA0;CE(#"BG0A0]CՔDO_##9BF!EA0)C!E##CBF"rA0C@CI$##MBF,fA0_BCE2#'+BF*A0nC|E1#'5BFA0@CC.#4BFVdA/ CExi#4BF>A/C2D=#4BF`lA/COE>#[BF=#A0rCzE.g*#[BF0A09LC^C۳#aBF/ A0?JC#b;BFȊA0C_E,#bOBF?A0CȆ#qBFA0@CrE0l#rBFw-A0C>CD#r BFA08CE#7BFA0.-C{DP#ABFu A0C0D$#KBFA0JmCE #mBFA0%CFDL#BFY=A/٭CL"C{#BFYA/CE9f}#MBFGA/؀CBD #WBFDA0zCwE#aBF?A0CEO#BFSdA/UCH#BFAA0MCxEk#BFYA/CvE:=# BFA0dmC!E#[BFuA0COC#eBF<A/CD)#7#ABFA0}CxqE݂#KBFdA0C#1E8.P#UBG$>A0Co#BFqzA/CC` T#BFA0CRDM#BF|A0ܨCSDI#{BGF3A0CuF#BGA0C+FM#BF>A0lCF 71#BG0A0~CA:Ft#BFA0jCE#BFKA0PCqDƭ#;BF A0!C&Ey#BFcA/2C3Eh&<#GBF^yA/*CD[O#BF`A/CrC##BFFcA/C=C״# BG6yA0]mCۗF=#DžBF,aA0OC0DQ6#ǏBF^A00CEIv#ǙBF13A0;gCPEn#BFA0CX^EP#BFBA/ C% BErA3:C&BFBA/ C-CBKA/DC}\C-MBKA.ڝC E}1-PBKQA/EkC+D-x"BKA/$CrC#-,BK}A.CDNy-BKA.[C.3BN2/AC)L03BN2/AC)L1O#BFBA/ C1BPABr#Cj2BPABr#Cj4BQ]A$$BDZ9>BBy3A8|Cb:Y5B@A9?C9y:a5B@3A9CCt>6BC :ASCjO>8BBACj?M!BBy3A8|CbRd9BErA3:CVBFA{BDX=BBACj?XBBACj?XBBACj?[%BF ]A0zCoA!BBACj?t!BAA9CCsvBAP>A9C]2DzsBB A7JC'C{zDBCjxA7oCG zKBBIA7CcKDuzQIBBA7n@CEL}zQSBBA7}CDmz] BCJYA7CDFE'znBCdA7tC^DzsMBC?A7{CãEPzscBBA7bHCx)A7uCDuظzBC FA7VsCMD6 zյBC4A7r~C0EϸzܽBBA7CEjzBBA7CC{zYBBA7ʞC E"zBByA7CE"fzBCA7PACQB>ACqhBQAaB4B&tBAA9avCcE緊&BAlA99C}_D͐0BA>A9bC[yEܰ ?BB(sA9C?BBINA9CF(!TGBB vA9qCeF9oGBBhA9h4CmGBA-A9lCZFKBBBA7CvCSKBBYA8GyC}E~U(BBA9gC.$E/HQVBBjA8ϚCҭC¥g3BBaEA9CܪE=g=BBSA9cC/gGBBbA9CEsBBtA7tCDBA A9nCMVE-NlBA\A9IC(F"CBA{A9nCCE1;BA{A9nCCE1;BAA9}gC E(BAcA9mC1E1#BAA9_C6EsBBjA8tCωD}BBA8zCzF-KBB aA9oC%E3BAA9nC6Ei6BBA9pCF,7BBMA7CC|F=BB6A8NCCoEᬍﰷBB. A9tCEwBBHA9suC(DEXBBA8[nCR|E)bBBA86CD{BB[A9,C[E%9-BAXA9C EipܽBBA8HCMF)BBA80C4EtBBA8 CnEL-BB,A9nyCE_aSBB@A95C!FBBQ A92C߀EhBBA8 CC`ջYBBA7$CE9BAcA9Cl>DOy"BABA; CBABA;CA?f(BABA;CA?f(ǵBABA;CA?f(BAc>A;%C8D.BASUA;OC=ExBAURA;CEWBAT]A;CE2 _BAdA;#COEGiXBAP~A<C CBABA;cC@`GBAA9n9CC2NBABA;CG@dBAGQA9`CpD:DBAPcA:CtkE,BADA9 CCJ BAJ%A: CE$BAWA:C,F8BAQA:-CE6BAPXA:hCF]M(BAA9sXCCC]CBAyA9C8EC'BAjA:DuFfcBA^A:f:D\0F1k{BAnA9CWFߐeBACA9OCj\C:BATA: Cߒ-BAA9CCYBA\A;ʆCDT}BAz A:`8DAFVBAiVA9ɌCɵF w`BAQA:)~D*EBA\A9CEXwCBAWA;C:CKMBAUA;C߿CmWBAJIA;CMD_;-BS|AL0TBD  $BBy3A8|Cb-BErA3:C8BErA3:C5BQAbBg5BQA`B5BQAhBXBQ_#AVBdBKFAQCk3CqBQzA.UBaE<SBQA.BPD6BBH Ał zHN BViAT@9X BQs~ABN:4EBQ=A~TFNBP(AWD@:DCvBJ@DB3BL˜@ڭABJ@DB3 BP٠A&FؔF "BNA:B$BLz@BD>_*BQDBTYA oHnF*DBDAqPCW]@HBSɤA,HFzRBQmA7SBI_F'UBI!\@ACCvXBFA2B#E=viBJ\AkC΢GsBH@sCF<}BLDACSbF\aB@ȧ@DfFBMA4CBTA E"EBK9AC@G2BOOA1DDZRGSEBG~AwCǮBFӣAC EBQ @BvFרBO\ANBD- BJL@QCHFkB?hAD2+@TCeFuvBPX A(ʲB G BOdA;6G^BX[AB=AwDn>I(BJH@8[B/C0u-8BSA mG_9BRqAO)yF7xD!BP@@{{GIwBKw1@B\@EjBAbA!(C}FBM @ٓB pA"tBAP9A!jDGLBK@[BgQBGtLA{BP8FC B?ʱ@QCpGPJZBB=@C-BP@tBOEӆ'B>IA&tD]?BCAtCC*DBBAAcC.pWF_ sBBmA$pD\_FKFt B@N\A7#D f BBA~,Cj% XB@(@C| hB@(@C||B@N\A7#D fB@N\A7#D fB@(@C|B>gAɯC.FB> A&DOF%'B>AAfCA`G(~1B>վADCE"B?6zAD*Eh^"sBCoNA'CMKF3y'/BSmnALTBŢ(BR^5AAaCIBJ}A=xwC۩GvoB@A8rC,E JvyB@A9uC0EovB@gA:D 2'F/,ZyB>A(;DUyB@CjA2XDEQZzBB2A3ChE$=zBBӈA6CD|KB?A/7TD$e0G|L5|UB>A3YD]9GLOB@hA7D:FlB@HA8BD/YE&B@A9DbD@[Z3B@A9aD zE?PT=B@A9ۏDoBAA9\'CFSB?GA4)D_*G]B? A5ID=E:gB?[A5(D.9G B@fFA8xD)FWSBAjA9NC߮FSfBAA.hCBB$A-C6FB@YuA4,DSF/B?A4fG#]B?A55uD q$FѿHBS[A @XBF#CABӯCBGPAD-? ;BV=AA%wDG BV54A Wx9E-' BV1AFoADV BV1ACÃ9D^3 BV4AETDl ?BV5NA /AnA BV+A E–GEY3. BV&A .HkěE` BV2A - ?IE! 'BV4A =ADXyN 1BV1A9ADV BV0MAA4EQg BV.fA!qE6Ή BV.7A3E,d 0BV8DA _ 0BV5A R5C{ 1CBV9A ? +D2 =ABV4ABADY BBV6A ,qB̼Bc BBV6`A +B$D;J CBV,A a&xE@Y CBV&XA CAYfEWA C'BV2A W±FE& VBV'dA cADq] VBV A h?V YBV3AAJ{XD# YBV0QA֥AN@D \BV(?A@sD2 ]BV4A PDL ^BV4{A RkRD> ^BV3A M@Dܘ _)BV0CA^JBv8D# _3BV.AAA.zCQ _=BV$/A@҆QE. `7BV2&Aa @C `ABV1AE!AfDO cBV A@cDq e}BV6@A &BFC+ pBV9,A FADD pBV@AɚAD*5 qIBV4AAAC q]BV4HAAY"Bhx qgBV0AʙAJDJ syBV3AR@nD4O {BV.0A;B BB =BV(|AǕLE~G GBV^AڇAz?E|6d QBV!~AX BV3.A )H?J}C  BQ?ABDCw| BQAB7XDT BQ~Ax>D $BQA BoDDK9 .BQA B`m(D,  8BQA)qBL  BBQATB ^YDف LBQA\IB!:D `BQh AlBE! jBQ\AB4D͚Z tBQalABwsEե ~BQ`:ABE ؅ BQf AADw BQk!AOA% BQ`ABtQEb% BQkYA1ApDs0 BQoA]B6D BQkAA BQe9AC E4} BQIAK}BZE_i BQQA8BEA FBQ]ABEl0 PBQLABxZEW ZBQdABGE\ BQcABV}D \BQFAB_]/ ~BQcA|%B\?EZ BQXCAAVEn BQheAzEB BQczA4dBo"D BQiATB ŇDD BQU/AxC `Em pBQ[ABTE: BQ/AބB+KC ҃ BQeAZsA%D BQ}A`BDW BQ}}A`_B O:BQkXA{CiEw ODBQVAJ E ONBQ]ABKEJQ [BQP1ABa.EB ^BQ^A/BELG `BQMAcB$f~D= `BQA\IB!:D `BQA`B3B73 aBQGABQ bBQJ2A-BWC bBQKABt C] pZBQcABfGkD pdBQdAB%E  pnBQdAݓBTpE qJBQAB5/D`W5 tBQA\IB!:D tBQAcB"AC  fBQyA4^A7L zBQ{A*BD. BQmAB%MD BQJAB8QC vBQ~kABBaCk7 BQLAߡB'|0Cީ BQ\ABNE[j $BQ]?ABDɥ  .BQaAiŶE-9 BQKAUBn]/ BQmAPAcA_ BQ]vA" B2;DCc BQ}}AXBDZ&N BQzA B7-1E;@r BQA BPDl ,BQeAA D 6BQaTArBOb BQFAC64DbG BC:A  CaER BCuA ICEF BCIA CjE'a0BCPaA CEJ:BCN7AnCKE%DBC]A fC0XEѢ4BCA fCc4EaBCIA fClE=BCvA CDjafBCgA >CDǏ-BC\9A ZrCYD2BCA VqC)C>7BC~A-CE!7BCAtCEBF iBCyA kCMBCA+C}@CBCЍA1C{EBCGAkC_D%BCKA1CEɬBCvA GCC՟њBChA EBV9A ,AuKOBV:A!3wA9A!T@TBV{JA:A+VBV8A @ hC0VBV8A ?~DR$CWmBVtA:ȴeBV7IA!_C^usBV*AĴ@htBV=kA!R"AAv{BV6GA !HB#/xQBV8A xeBV9JA ĥ;Da *BV]ABB BVtA:ȴBV9fA!SAq,Bi BV8A AD  BV9A ,ADBV8XA u<ABV5eA UA/l@~zBV4A AG,A(BV8A!?RDBV8A 0A!oD=?BVA AF>7QBV8fA f?"рBpoBVIAs3A֋C1BV]AA_3BVA /DދBV+AwAhQCCBV]ABB BV]ABB BV]ABB BV5\A BE7LqBV5A@DrBPVAGBcBPAhBgEBP9ABu!BQ9A1B"iDBQ ABDSRBQFABp[Eq&\BQAA:A=GEz<fBQYAB96 CBQFABDBQD}AEB{CBPۘA"HBsE]BPABxEBE $BQ>wAKBu 2BQA_BRD bBQ8Al AE]'lBQ-A=1E>UBQKAjB$E>fBQCA.sB7jEqBQ3qA+BW)E~BQABmC)dBPڻABqDUBQEA2B`.mD RXBQAAE_lBQUA.B`@jCPBQUwA|B-BQIuABQ6ESFBQ=cABj-EBPABEBPAuBhBQUwAB?wBQxA{B4EM}/BQTABRDG|BQ[lA2BE dBQIA,RB"4EˆBQRAB~E[EBQ=A#mAEYBQ8 AC$EwmBQBDAkIB-E BQH$AB;dȶBQAtAH`BUE`OBQGbA1#kEkBPۘA"HBsE]BP%AÈB;E*BQ<~AEBE-BQ VA:AFJK_ABQ+AsÄئF&zBQ3ADA'EC+BQBD.AB?BVA@Fc@bBUA o@E%J`BUNA lHEc5BUIA /@EwbBUAބ)EBUA͡AD\>sBV#A "A_Ew#}BVuA ):AE'BUA[AE-BU5A e͸EyBUƧABXCQBUזABA+EBV8A '!7BV8A DBV8A %^C+(BV5A gA^kBV7KA!K@&CP5BVA y ?8D}BV%A iD7BV8TA @AeBU A /B Ej3BU]A@ԇ\D?BU֜AATE[BU|:AB-BUA'B7PEZBUAnBeEd BUoA BD.Eu$ABU A!X@1'0BV7DA rS>fCXf9BUoOABF89BUP0AEBGFs A!X@1'eBV:A!)3AmDwo7BV9A آA8DqgBV.AxAsyBV2AVRAoAavSBUSA@ȅDrwCBV8A ? CWk%waBV7A! A**CuxQBV8A Ao~DRx[BV7wA!%A\\CxeBV8A! B E,5BUrAA6ENqBU}A"BMEM;BV7$A!A^MC"OBVA h@RDBU(:A!0BNREASBU|A7BӾE?zaqBUcA @E{BU~A2B+EHXBU|ABtE"BU:A to>EEBU.A ?EC1BUA )>EZdBUA |!>/>E}PBU~A >Q:wD2BUQA BAk@ESjBU:A D$ELqBUAEXEqBUWA `AE@ %BUvAlB EdKBU6A! BsEEssBU|yAFBEELBUq A)BTF:.-BV,A@wBU$A E1cGBV7sA >?13D$QBV/A nE[BV8A ֔ADBV7A cACE4 BV8A @źCeoBUPAЍB+q.EHyBU;AOA!FpBUWA A D׵BU߱AtEBUAAOKEBU AB˙BV,A@;d9BU|:AB-BU;A)A?ET*BV1"AdAܗA]Jv BVA }AvrE\#BU6A#AHjD4 BUA>BADBUA{AD(9BUCAA0=DmaBUnAnAyD*kBUAAD'۱BUAAYE0# BPA(C?E1 BPA(ahE BPA(^ABHEc BQA(AAEBd7 BQ$A(r2@\IEJ\ BQA(?B$ E_' &BQ5A'E 0BPA(-FBF a :BP;A'}$*F?u BQmA(FEcu# BPNA**>B$F  BPCA((BF BQ(A)YBiFFY BQA)B-@F;5 JBQ=A*8zBEW TBQ>A)BrE1A ^BQ?A)bBUE XBQXA&{{Bn/D6 bBQ[tA%BBD>Aj BPA(BۖE BPA)*CF BPA(-}C`.E BQ@A,BoC ZBPA)SBn3E dBPA)CdF!E nBQ{A)>8jE^x BQ:A'R(C:oE0@ BQA'¨=+EOw BQ$7A'v]B؀E;E[ BP:A&kAxE "BQA%B~(E hBPA0glC!/1Eo |BQA/ґC,bEkd BQ A.BJE/ BQA.BWEˬ BQ#4A.AB?E &BPkA+BrD2 )BQZA%DTBCS .LBQ;A-Q-BmDT .`BQ=A-4B̖ 3BP?A( Cv 4BPA*BF8w 4BPA*Ff ABQmA*A'F s B.BP7A*%BFF B~BPA)CkFQF0 C BQA:A(;U^Es CBQ8A't3C/3E۪ CBQPA'75B,EU" RBQLA)B%6FD RBPA*SFFO RBPqA)qE  SBQ<9A,>C8F) SBQ>?A+z}BO7FP SBQA(B4tE7{h wBQ@A(BXEEK yBQ A(fCET zBQA(B\E!- z BQA(4B@YEr BPةA( C2@ES BP@A(BkE| BPA(J6‡EEY XBQA/2KBEmZ fBPA)CF l pBP-A)K`~E zBQuA(0B+Et8[ BQ<A+@BEii BQ<`A*Bv%D4D BPgA+C W>ES ;BPA(CE5E# OBPʺA)à+FEi cBPpA(pzArdE  BPA0JCE%x BQOA/2CwRE BQA(eJBz(EA BQA(QBۮ$D BQ>A(=C^Ev BQKA'BiDD ,BQ2A(1Bt3EhB BQPA%/B}E. BQNA%BaoE  BPA&%BHF } 4BQNA/tC DJ >BP_A0z$C&1Eo BQ;[A(-B2EV BQ9UA(cBE BQ$A'ڠ@ E BPA)CF BP@A(>n8E< BPA( {VFg: FBQBA'-B=E9EQ ZBQ A%(BCH BPYA'C ݙDN  BPA'BLDg3% HBP:A'nAQE' RBPTA'RBLEK BPRA*B=F) BQ A*`BFg0 :BQC0A&#Bt0E DBQVA&B EF NBQ[{A%BRo HBPA0qC"KE \BQA/JtBEs| BPvA*B{EN# +BQVA&B&:E($ @BPA(lC}E[ JBPA(BeB.CzE  TBPA(ABQFI BPA'B9E> BPA'ANLE0 BQ6bA'C9[UEf BQ2A(y6A7Ec BQA/xC  BQA(qXBE^L BQ=A( :C%E7E BQA(?B=RE[ BQA(=BŕEa :BQHA) Aߞ)E+ DBQA(dBE|^) NBQA(1AuE+j BQA(X$B~ES4h BPA(& BE. BQ A(9dA&Ek~; BQ2+A)xBBEψ% BQA)B:F X BQA(BmE BQA(QBXEY6v BQA'AfE  *BQrA'BsExL 4BQ#A(LpwE$  >BQA(B=E'jC HBQ&A(/BE+p \BQWA(g4C1[E`!v fBQ$A(6B D ȢBPA&,F BPA($'B F * |BQ;A(`B@J@ ːBP$A'Bg+>k BPA&d‡Ez zBQFAA(BE ̈́BQ;A(rBE ͎BQ?A'7CEu BQ@A'BE BQ.LA'T0BC ' BQ)A'FBֶD} .BQ A(C6CE\F 8BPA(.CE( BBQ A'BE qBQ ЮBQoA&Bї@F o BQ41A)'B]E3 BQ KA(@ JEW> BQ A%BʞD&S ښBQ$A(yQC+E `X!]BQ9ARAEg!]BQARB)ND6e!BQASB}B!BQASBPDy!BQ ARBEJ!kBQ?ARBD[!uBQAReAVC !BQ5ARrANDl!OBQARKB nC̎!YBQ ARBE!9BQRARBEET!CBQARՒAWC!MBQ!ARAEʊ!9BQAS>A'E9 !BQARBFEQ ,!BQARAE_"BNA=HIBF"BNN*A>CnEj" BNDA?8OP!EQu"BNKA>CfE1i"BMAAiBSOEE"BMAAvBN" @BMٷAALBoE" .BNDoA?C 5E1"%BNVA>CDDa)")BN;A;BEʵ")BNA< BFZM")BN:A:\BEv"+BNA<;BIE"+BNA;BrFW11"+BNA<B?+EԴ"+BN0A<9B(Ff",ZBNkA="C E",dBNZA> CQ D"1,CщEП"HBNOA>COE("HBNhA>C! E7"RpBNGA?pXCD1e"e0BNFLA?& B:F "eBNXA>C8F!"eBN`A=C "tBN A@BZC4"u4BNEA? C@E"vBN A@BZC4"{BN#/A@A&BD]"{BN[A@K$BpD%"$BMAABE^"BN|A=CؑE%"BNA<BF*""BNJA; BĜ"BN`A=C "BNlA=hBADn"BNA@MBxCη"@BNA=LBބ:CL\"BMAABیQEhd"BNP6A>>C" BNA@ʛBC:a"E$BL?@QBE@$BM@KB H*B $ 8BL @yB_EnJ$ BBL@iBE $/BL1@5B$dBc$DBL@YIC]b$YBBL-@ B!$klBL;@.BLD4v$kBMm@B Bl@L$rfBL@0BeDt$LBL@yB֟E<$VBL@f-B49E$`BL~@AN*D$BL@BC3$2BLܧ@̨}B*Cw3$}A$]BF* 0BQfA"qAkEY* DBPbA$|4¦A,F{* NBQE"A"QC_FO* XBQYA&*{BmE* bBQYlA&JBLER* lBQYA%-BgE* BQA BWcDj* VBQrYA"QcBgD* `BQsA" >AޥF* jBQ~_A!CF* BQ5A!BvxC\* BQZA!BF* BQA 2CepED* BQA\C:Ed>r* nBOA*C3D?a(* ^BQAbBf`B*BQ\A#8BE'E*BQEA"6C6xdE i*BQRA"fCPFM*"BQA%sBEĸ*JBQ9A"ƫBjn*BQ\A%qBcT*BQAQEB\~E*BQA8AE*tBQA eBչF*)BQYA%xB Ee**BQbA#C&m1EO*WBQ6A!BQcE?q*WBQ*VBQpA"EWE*`BQfA"2@‚E6K*jBQ\;A!HhCEFD*BQ]A#\BE*2BQQjA" CrF#g#*NBQA3Ba6zD*BPA yC'XE*BQ^.A$CmF^*BQg7A"@ީF *BQQA$C1EW*BQJA$BpcF*BQ6A$TB9EE l*BQA BWcDj*BQAKE1=*BQUA%JB^(8EnY*BQABBrD*"BQbdA#ZBU>EŮ*^BQWA&BE*hBQUA&IAnEJ-*rBQ[A%cBp{DE*xBQEANqB3jE *BQAAE*BQ$AA"F.Ie*BQ.A$HjF:!*BQ5A!7BlD*BQhA#Bk&ENe*BQRA"B`DQ@3*&BQ"A–E;B*0BQ]^A YssFMx1*:BQDAB* BQIA%)BE۲*PBQYA% B;aTF>?*ZBQ?A$xëF8#*dBQ`A#BfNAE*BQbA 1BbF6+*BQbA 0FEFG$*BQA}BF !*BQDmA&BGD*BQZ[A%B~ZRE*+BQZA%BEY*?BQZ%A$AᾆF$&*BQAccBnH*BQLA$iET*BQBA$MB$E*BQBFC$*?BQJA$}C"F<*jBQ FA&%B*PBQt2A!JBgcD*ZBQA |sBn"E*BQBA"׃BLE_E*BQ7zA"@BkEI]2*BQD(A"9B4E*BQ'A/C E*BQ]A BoF*BQDA]BTE*>BQRA!aKCyF:*RBQ5UA!B%DhP. BM"@eZBE.BM@O@(E.BM^@A.BM@فAlE.=,BN@?B&._BMt@غB E!.`6BM@mA,DS.cLBM@ֹB'qD5.l>BN@B.lHBN!@c~B)DTk.lfBN@?B&.lpBN@BBfDzRj.&BN@SB$D?.BM @lBK%DC.hBM|@׬AĒDxt.rBM@ՆB4C".BMc@_AwDuFC.C.\BM@ŎA8DxH.fBMo@wADE.pBM@ŎA8DxH.BMx@2B+70BFAQ3B`B00BE0AEEB*OC/0*BFMAj2Bt90BEAJ.B*gDtt0BF?A ʃCF0$BEA B"D,0.BFA BD0VBEABHEa0`BF AB+TE0~BF!tABE 0JBEAWBE<0TBE&AB0BECAB1Do0 BEAYBE140 BEpAB:E0b0 BEDAw:BvDN0BF'A Ci0BEPAB^*E0BE-A8BC?[0JBEчA/bBE̽0KBEAEBHDo0_DBFKABяEHE0_lBF-ARBEGd0_BEA"BHCƣ0kjBF+ABt0l2BEӲABuEf0pBERAtB C60~ BF'A +B7lD;0~*BFAB1Ef0~4BF'ABEvE{0ZBFA B,EY0dBEAxB[E90zBFAoBT0BF:pA ڴCgCʼ&0BF8RABZJE0BEZABBD 0BF@AVBB,D|J0BF%-ABEdM0BF&6A |C\?;0BFiA "CůC>~0BF3A+B;Eo08BEAEBSD&0BF&A C<~0BE!ABE`<0BEDA=4BEr0DBE&ABEQ0NBEAwBNeE|0XBEASB}kEDZ0>BEA\lBE׾0HBEADڠ8BM@ӛkA2#D)_9BD#A4PRC2D@9YBDA4RC؃T9aBECA3LVCoE'9kBE A3xC0F 9*BDQA4C9RBT.A}>\BT:A(JD,> BTRA >B@Dn$> tBTTHA AuzHDb> ~BTT?A AD;,>jBTA L~iEv>~BTA J8>BTcA4+[D>BTA E 7Ee> BTA /qtEr>PBTM]A\ŽEa@>dBTUZA|D>BT%A+}&C>BT6AvAqEA>BTLA .AD>BT-uA+JBhE>BTaA A mE>BTPA Z@f>BTQTA Do@8C">BTTA AbĿDvG>bBT|A :Y~D>lBT+A ^e,E E>BT;A ŭIgCW8>`BTυA Al@>BTiAg5D0V>.jBSKA PADf>FBSA @ՖACEp>JBT0ABB E5>JBT+TAÿ^Di>J&BT3_AD>TUBT&A.T}BTA п˦E>W9BT|A AƂC2>^BSA +B>dBT(AYYEY0>dBT";AJc_EHZ>fZBTWArF!DS:>lBTA 3MD>l"BTXAlE>l,BTMXA @roDl>l6BTNA @K)D>qBTqA {xJDE>wBT)Ak³0EG>x BT%AmNEV>yBT eA z%@CMb>{JBT7A !:5UE>}\BT A:BiF.Õ>BTzA ۤAbEkF>8BTV*A KAf.Bi>BBTUeA @iC>LBTg7A 迢E >@BTA >M>JBTA E>BTTA AW}D'2>BTV*A KAf.Bi>BTUA @>3BT\A 3&EuV>GBTA %XE]>0BTvhA avcE>~BT"Al)E`$>,BTGA κ69E87>6BTGA CuFDY>JBT8@A/@KE|`>TBTLAo”EFpY>BT{A W"E'a>MBTSARC<>BT+A7 A#EV>BT#A0’EO>BT*(ACD_m>BSwA CAI>BSA AMB9>(BTAC2EN>2BTA @EWG>~BT}A dEAʵ>BTA 1IE>BT:A Қ Db>BTA 1fEIX>BTnA FdE>yBTi8A /`EsB>BTTA @ԇ1C>BTPA aAmDo>&BT5-AfAJE >0BT.AC8EA>BTQA F@^DĢ>BTPA S+@\gxD~2>BS A 3AzDX>BTQA >DA J^D[f>BT A BEM>BS2A zACEu >BTKA*yE> BTT:A>BTfMA XE?>BT!AAr'EnC>6BSA /AVDv>GBT7A 7@qEz>|BT A @;F>XBTZUAdaE>BTvA J2@E `>BTLAo”EFpY>BTmA %BE>`BTBAA m DU>eBT,ACREt>yBT!AEQFE3b>ƍBT(A2DW>ȋBTJAVD|>BT*A B{E|J>jBTGA MD>tBTKZA ̾KD%>ɅBT7EA[CLF>əBTDAŽ;Em>ɦBTA W*E>ɭBTͶBTP{A PD@>BTPAE|D h>ΒBTeA#iEU>ΜBToA '™E>QBT\ANt>yBTr`A DN@>RBT GA -\Fؕ>\BS^A nAn>EP[>BT>AAD>BTA @E]>zBTA {bP>BTMA ?,3Dז>BTPA SA#_[D>BTOA tz@w>BT,AFpC |E4>BT!AU@E[>BTA AtE1>JBSA n@D`e#>^BT2;A`BE\>hBT#AP Eٲ>BTkA.ϴDb/A 7BJpA CY7cEo;AOBJTFA NC\,DtARBJAUC|ABJAC+DiABJ2A Cn;E}ABJ"=A PCe`EsABJ(A Ce/LEqNABJrPA JCC#JCGxA'BJA gCdE^A1BJA eC\`E/>AMBJ6A fCmJDABJ1WA CjA#BJ(A Ce/LEqNA-BJA gCdE^AsBIA ”CMQE@AmBIA AVBE}AwBIzA /CMEJA5BJA ?ACi%`A5BIA fC0DtA%BIA CaϞA/BIA CRE2A9BIuA aCHȬE~ABJ!A O^ClCBHMA:PC発CBGlA8FDD:DBFAQ3B`BDBDAVBBCoDBD {A@CC5DBD A(+B7D&&WDBCAB(D(D BCNAmBbD$DBDAs]BջBDBDABd'DíD(BD AǯBRDD2BD ABDҙDDkDZBCABSD6DnBCAQBQDBDBCAUBWCD^BD |A;CDa$DhBDABuCDrBD A}PBtDXDBD(AC)%yEΎDBD(@AoBɝE DBD,A!COE o7DBD AgB>DD BDADBRDBD'ABDD BCAB8D*?D*BCA]gBzDBDDANBCDQRBDA"B:pD`̸DQBDAôB^CLDQBD'AƽBEfD`BCABsD`BDA#BfEqD`BDAJBME+~FDiBDA9*BuEIXDiBD3ABXDݝSDmrBDACEsDm|BCAȊBZDEDmBCAiCD8Dr@BCABDrJBCAB'ID\DrTBCAsB D{ZBC7A@mC E+&DBDAjBXEDBD%AѸBdD{DBDFA#BٜjABD^BD%A0[BpCDhBDGAXBDmDrBDA<$B]ECDBCABCE"DBD AB!pEH1D"BDALCq&AD,BD AIC#+C?DrBDrA~D\BDbAoACfD|(oDBD AƝB CDBDACE/LFDBD~AVBEF\DBDA=BH:E;J3HBSAAd'F~HBSDA 7@IH,BS>A wKA%"EvHBSAAA֓YCHBS^A ACHBSaA DAHBTA eaLEHBS7A sjHBSNA {@`tEdH)BSA ADH+^BS|A& AkFH+hBSdA1APFZH+rBSVAtA"E**H.jBSAA A1EkH.tBS6A B 6EwH.~BSCA BM2C+H8$BSK`A )6B+Eǥ"H8.BS2A zBybE!tH88BSA B78EHEBS A bBe,HEBSA 2@pnFGHFBŚA HBS^A dB5KER;gH?BSABA=cFpHSBS$AAS2EbPHgBSA*ARFH,BT A RE!BH6BS A AQ~EyH@BT3A 6KA;~DHhBTSA b@>z}EVHrBTJA '0E_H|BSA @FYvHBSA AHNJBS)A @QDN BEA BɍPN BEA QBЭ^EpN BEZA )BrD#NBEػA TBs3NBEA ABNBDA BBNzBE™A }vB~ENdBEA C ]E6NnBEA UBN|EN7bBEA FBԺE)3N7lBEGA ;.BDuN7BEǦA )BEhN7BEA B'E)LN;BE;A C$zE2.IN;,BEA C,ENBFRDNSBE8A `BԫN[BE]ABBN]BEVAkkoSF fN]BEgA:BDEN]BEA\B 7E -N^BEنA A C^NjBEA BTENjBEA eB\NjBE=A |BEȋNnBE(A C uED~NnBE[A :B$EPNnBEA GB%EUjNofBEA+BķENtBEABE?D9ENtBE%A 9Bj.FU5NuBEy}AØB*ENzBEA{C 2EGuN{BEAd=BNBBEABEKNVBEAB"%D"N`BEƀA EBENNjBEA u-BELNtBEA 6E0NBEA إBBN BE.A BE.XtNBEA Bb@E6NBEA BDXNBFA AFNVBE5ABD(IyNBEA DBɟJDxmNBE@AB:lC;NBEFA BpE=NBE8A `OBE NfBEDA >kBYXE0NpBEѐA BSE$N8BEA GCLDFiNBBE?A C8OE20NLBErA :CB2EeNBEA $BN$BEA B_EN.BEA JBpEDNْBEA 8C@ E%WNٜBE2A BvA1E QBVAipDFˁQ+BVA6@|EWgQ+BVA<ߙD^Q,BVA@|E"+Q05BVUA@EڰQ2BVA6VQ5BV|`Ao(AdZQ6BV~Aϙ?PE~Q:!BVA@X95DQ>BWAZ@ED~QD!BVAh=We!EjZQD+BVvA259EbQD5BVA?DΧQQBV^A*.;D[QQBVA^EFE=QRBV'A%A9QRBVA@=VEQRBVA?@E;QWwBVA)A9XQX!BWAZ@ED~Q\BVHA&@PE3Q|MBV~AQ)A2kDQBVA ?F3LQyBVA@E?baQBWCwAA;CQ;BV%AAhQBVDAQ pQBW9@ACAwDtQ˭BVAA _VDpQwBVAudZQρBVAw@'ERQϋBVFALA@0E"\QBWA&A4@F-QBVA sl!EQ%BVA;A iEQ։BW$A@IEQ֓BWAKWΓED-Q֝BW?gAA/lDͭQBV7AOE0QBW%A27ϯDQEBVNA@bE\QOBV4A@iEdSQYBV7A7mAERBQA:B !E%3RBQiAgBUE-RBQd`AũBHE[RBQeABkDyRBQAc[B$[oC>=RBQeaA^B5|D/RBQfAqiBXRDBQ~AlBDRNBQ'AmB$0EX$ORXBQjAJB8E<5RbBQsAWNBdDYRvBQAXB cE*}RBQACQԟE~RBQmABPuE&ÐRBQ#A`B>v|E <RBQkAJB C\?RBQiA"B1hE xRpBQmA>B4oRBQApBG_E,SRBQ}Ae&Bu{DRBQңAB9RBQtA@,E:R BQJA=BEv R BQ]A*AEXR BQ7AKBE;R BQABgE&!TR BQAB4DVR BQEAzC8^FR $BQjARAoUF R .BQ]A}BER 8BQEAnBu>FR LBQqAB˛WF<R (BQEABhENR 2BQ:ABE|~>R BRVAlB$wE};R BR"AB5F-u$R BRABBE\R ^BQDA@BXE*6R hBQxABQL|EB~R:BQA>]B-E+0RDBQ~AhBRRNBQ~eABRXBQ~AhBRRBQ@A$B0LDÈRBQ]ABEc}RjBQATNB0^%DήR(BQ~AՔBD R2BQ~ARBDYRRhBQZAEB$ JD抆Rk2BQkApgBD>Rk~BrE#RBQDA4BFEgR"BQA-EMR,BQPAyBDHD¶R6BQCA-A\EO7RrBQNA@]8EEJRBRSAJA RD RgBQAC qUECR{BQ|AbB#E%RBQFA=B<6CKdR/BQeAnB2DRBQhxABDRBQiABZ״E VRBQMA\BRtBQhA2Bf JRBQ A'0ERjRBQA_CF tRhBQArB6R|BQUAB/RBQIAoA`R@BR7@A=A =UmBIVe@nC1+CUBIv@C?HCԏUBI/@0CEiC_UBIC@NCobEXUBIP @'Cc9CENzU BIv@C<@E,J)U BIl@CF DzUBIv@CCVU"eBId@UBI{@^FCLEtmUBIu<@٫C2FI U BIc@;CUBI\@JCrENXBFtA v3C%nF}1XBEA?BbX0BEAQBUD}XDBEcAJBYDjXvBEABٿCXBF7AZB7 D[XBEAC7sMEe,~XBEAmcBhC6XBEAeBHD XBEAsmB`nCXBFlA A܁FXBEdAPBDrXBEAe;BPE]|dX BF,AuQȻE X*BF GAoB$E&X4BF.A})B#EX\BF=ABa@EަcXfBF&AB]E}XpBF(OAVBEYX0BF2>A ݭCD}F)IX:BFA BEgzXDBF+AtœAzEXBF7uAsB DlX *BF:A "CTpFyuX 4BF/A mCE9GX >BF-mA DB,E7zXBFbqA BE{BXBFEA C [FKXBFA uBȍjE-XBFd$A bCKFg$XBFA B5{GEiXBFA yA- F<XBFA3B E:~XBFAfBEXBFuA BhEژXBF A BE:JXBFIA CNm{E?XBF$A%CF>QXBFAʑBEXBFةA 'CSF/ XBFLA ŖCUF\XBFDA C7RFXBFpA BF9XBF(A CnOXrBFJ.AzB߮Dm X|BFM0AGBX0BF#A QC°XDBFAdaB}XBF9AtB}qX7bBFA =BC0X7lBF+A *$BQE'aX7BEDA >kBYXE0X; BFAfBqD*X=BGEA B&C`fXL;BFA BnC |XLEBF.A B%Aj׎XLOBG A0BD 'XR`BFA^BC*3XRjBF ATrB=XRtBEAJBoDʢaXZBF4*AyKEXZBF7ZAGBDvOX[BFAXBnX\BE\ABǑEX]BF@uA ϷCYaXa$BF",A oB!EdXa.BF A 2WBEmXa8BF =A PCEE zXgBFDXBFA-BþD1XdBFA B\CW!XnBFȋA ٪BwBOXBF~A 'B7EDXBFA _$XBF6A XC)QEM XrBFBA {FBBEX|BF'/A KB#EUXBF%IA B\vENXBF?ATF XBF+A |BPF[KXBF/ADBYs F8…XvBE\AӏBAXBE0A`BDNXBFLZA XACMIFXBF.A C/F XBF(A JC EX BFdAB^5XBFABDdX BF#ABE5{{XBEdAKB?DXBEA[QBDD)WXBEAHBDXBFA 1BE>WXBF |A J(B/bE5XXBFBEss\G.BOKA C\JBOA^BZE\[C\JBOABcE$\JBOABA\PHBODA pC)E\PRBOA a;BE_\P\BO+A UCE@I\^7BOXNA8BD,\^_BOj{A;PBcEEF\eBOwA8BiDѐ\fBOAͮBEc\fBOA BEZt\gBOACf4E9\gBOArBKEߪd\hlBOӍAq]BjD\j.BOAhBE%\j8BOA BEE Z\pBOhAK+BKxFG\\sBOA5B\sBOA9BVaE4\sBOqAjQCR F\~.BOfA B!E 9\~8BOA C$rcEZ\~BBOA)C=F@/\ BOA|APeF\BOYASBjC<\ZBOuABHEY\dBOpAB)D{y\nBOzABiEco\BO{YA&BkbF5DG\BOA (BCC]\BOQA-BiD2\BORABYE\BOA-zB\%Ce\BOlA=BDXW\>BNSA Cxs\BO4ACT$E\BOAhVE[h\BOg5ACB$C\BOZASBy Eh\BOA mCDs.\BOAB;\'BOmA B \BOA BD|\BOwABf\BOLABgDo\BOABw\BO.A+BE\BOmA -C_cE[ \ BOzA Aa cF\BOkDAA(D쳩\"BOJbABEy\BOABE!x\BOA6MBLD< \BO"A B(DpM\ BOQA^_BNE\*BOyABw|F\4BOA8BDE\BOA BDtE\ BOzA[cB0 E.\BOA BD|\ BOACB!D\HBOhABH}Eq\RBOfA9BׇE\\BO`A5Bn"lDE<\*BOA9BVaE4\4BOzA5BfDD\RBO}@A} B C]\BBOA 3CEE\VBOqaA B]pEu\xBOABDO\BOABjD1\BOABjErg-\BO,A4B;E\BO/A _C^2E{\BOA,C FZ\BOA LC' AE^ BL A@C8EǿH^BL{A?UVCbDDm3^BLA?CJEj^fBLA?C=DǐCu|EI!^VBLvA?m]C;F^8BL A>CdD^8BL A>CCE?^GBL A@C8EǿH^N~BLbAAXBE|^NBL.A@۠C E;^NBLA@{CE^^BMs_ACBͤ^dBLA?zCH>C3^jBLұC^,BLA@/C0TE?^BLAADcBE6U^BL_A@C sE^BLA@CE ^~BL'A@mC^BLƷA@FC' DEh>^BLA?]CPҋC^BLA?~CU$E' ^BLA?{KCWE=w^BL8A?(SCE%^BLtYA?kC;F'^BL4A@C^BMAAeBx^0BMdAA\]CDC^LBLA@EC#0Eُ^VBLfA?5C;eF7Vp^VBLyA?C:jFR_ BMA֏CSE3_1BM!ACtD^T1_BM8ACG(E߃_BM-aAC;AEe_BM%nAWC4_E _*BM FA C-4YAn_BOBMSANCD_bMBMl:A C Dz&_gkBM%ACl|FX_j1BMA}CEnEs_kBMAFDChj_meBMhACFE1_mBM 'A1C*BTp_mBMe{ACEӇ_mBMhACFE1_nBMe{ACEӇ_xBMACME7_y;BM9ApC!ԿEy_yEBMyAqC";UD@_QBMkADC. C<_YBMABE_cBM A C2`DJ_BM pAI!C/-oD)_MBMcSACPDް_BMAb.C"kDd_BM!A C2VEE{c_BM3ASC=J E"¢_BM"AC(DԵ8_BMPA] CQEA_}BMbAa.CCO#z_BM /AC+ C _cBMYA~CE_mBMhACaEV9_wBMI@A3CaɥE_BMwlAHC7C_sBM+AC+lERӑ_}BM$AC<EM_BMAC/ZE_/BMA;B]E݅_9BLAB!C_ABML*A;Ce E_KBM2A:CwҬFhp_UBMX(AyCF_yBMLXDf.BQ.ABUEf.BQ3ABE8f.$BQZAclBXEWf..BQ ACF(If.BBP+A BFrf0BP߹AYCE f0BPAe7B/%bF1ff0"BPOACJY:FzfEvBQeAӲBV@EvgfX3BQYABmeDw-fXGBQUuAB@ofXBQ=ArB D=vfXBQAwBF fXBQAIB,E(fZBQA|B2ΚDf\BQs%ABHF#rfaBQABF!pf4BQSfAoB`EMfBQ>A,BBIEØfBQAC9F}f"BQ8AKB@DfBQAJBDv:fBQHA9#BP&Ep3fBQ%A)BcEHSfBQ(ABdEfBQA?B"fBQ>AMBږFef BQACxE$f/BQJAXBCELaYfFBQz#A4AEfBQMiA8BbD )fIBQZAB4SE)fκBQMANBǮfBQxA(B$EnfBQ^JAeAĆ"E fBQAVBJFJfBQrA oBbF afBPA4C4`fOBPAC2iCBIțACFi!cBIAkCɤFi/BJ qAiCziE i4!BJ3A;CEM'i4+BJA1C\E6i45BJ:ACIE7i9BJ;A^C¦F5'mi9BJ@2AQzCSFaRi:uBJ\5ACDbiCBJpAVC7'EpiiHBI&A`CCEiOBIAC4ENiP#BJ&AC_E PiP-BJALClETtiP7BJ ADChRiToBJAVC;E[_iTyBJAڅCz=iTBJ2A+C/F78iTBJg%A3CohE9icBJAOCrF5PicBJ}AC7FicBJACEKidBJuQACҒ}Cid}BJ~9AդCoinBJ7$AC̺DSiABJYA5gCeEiBJYA*/CiCiBJԷAC Di3BIAǶCEjiBJ8ACQE|i3BJAC·EmiBJ*A3CKDiBJeACɎ&F.'iBJ@ACB+EiBIA2CLLEYiBIACFF,iBIAwCFoiBIwyACdF iBI}DAeC+EiBJ}HA/Ct]F3s5iBJApCioEDul(BE@k1CؚDxl,BEt@C1xF/l,BE@8C9FoEBW#AS EoOBW!A.@^E oIBXeAB DioBXAB6FN6oBX/Az?s4FE;_oBXHiAAsDEut8oBX]sA݂AFoBX,AAPoBXo5ArBӌF oBXAFB%+Ee[o'BY6A BE+Cє2o(=BXAFB*F=x$o(BXBA~$o*BWK#AL@DU/o,BWAbA2F4 o,BX ALA}DEk[o,BXAHWApEGo1MBW~AIA*F]o1aBX6A@D[Yo2=BX{A ]*g*Fwgo2BWAރEQvo2BWA@^Eo2BWrA5?ۯ~F*o5BXUAANF;o:BWaAB.FiHo;BWxAJ`@6EQo>BW(A@gEo>BWADƈoEѣo>BWAGE|goEWBXAEBDp]oEaBXqAUA0YFio\BW%A9Nyo\BWXAn?tEEolBX@AjAOSF oBWmAEͶE*ZoBX7dA=AȟE7oBXHzA,AmDGkoBXWAJ@ZMD_o{BX#A'T JoBWAGueD!oBWiA!?nF(ɋoBWsA~` JF;o BWgAՎ@sE1oBX}Am'¹^?FFGoBX0A12V E+o BXhAA??FMoBXfA BA F`o!BXnA,AgE) oBXAL1ID\*oBXHA=E oBXAPoBX*AB"DCoBX ERo BW1AfcA+F&oBW~A[xAoF"YoBXASFoBXAi?D)oBWA?@poERoBWfA?uFd o;BWA@ E? oEBW{Aܪ@- FoBWh!AA!jF>f8oBWzAoTEcoKBXoAZZD MsoBWAE0toBWuaA,(E0o BWSAAUyE5o#BW=AoEooABX߹A1? FI.oKBX.A R[BC>oBX!A)T9Fbo BX$Ac@DF&ZoBX<_AkAuF3oBW/A@KCT5o_BXAB0E6osBXbA@y$CoBXA OD2oABXAr@ICtoBWAAD^E> oBWA.AX+EOToBX7AA1QDp wBPA Cp0BPAC NEGrp0BP>AEBLp4BQ,A DB4gD]p5BQ)A*Bdp5rBPA4C4`p6DBPA B/Fcp6NBPA CߥE_p6XBPA &C-E'\ pEBP߽A/C,;EٜpEBPAB8C3EUpU BPڡA!vB5CipZBP_AwC4EחpZBP҂ABkYF7p\2BQ,A"=BC!*Dp\WpBP-A!B1TDbpBPA BvEwspBP ABY*E\pCBPӊA B`VVEypWBP_A B%B5}Dk pkBPA 3B6EH-pBPsA B8CpBQ)AUBK$A@pbBPACID&plBQ)AB]c@zpBQ*!AB3CA*!p7BPٸA؍B_AEjGp_BPמA~BR,ETpBPAWBѼEF C!pBPJAKB%F5spBPAPCEbupBQ)AAB1&%AEߏpKBQ,=AB:#D&r~p_BQ*$AB8~5C?pxBPA pBC1D3pOBPA`C+DpBP!AC)EsBHN@CoF s5BHR.@C%E0s?BHK`@ՏC*EasBH`@CQsYBHXJ@yDDJvscBH~@#CysOBH@YC=EsPBHe@2CK3F&s^BI@WCƑE@4sfBGϢ@8 C*BFsiBG@C^FqsiBH 1@UCtF6oskBH@@+yC1EskBH@/CF(skBH @.CgE.slMBH@XCEg^!slWBHA@rCvEpLsoBH@5CݡF|so'BHl@vUCTFso1BH@_C\FFПspBH@/VCEspBH@CכEsuBHz@/C 7Csv/BI @4Ce?C svCBH@ =Cts`BG@C{#saBH@mpCE 2skBIb@WC)Eg5suBH6@eC?_EhHsBI@_C٤E߿sBG@QCxE2sBH^@CEcs9BHt4@NCs݋BH:@ C{EP LsBG?@ C%E>isBH@0aC EȮsBHxz@lCF NsBH?@C^\tBK@ݿBϥDK tBK̓@BEDXE}tBK@ޝB1B}tBK<@ޝBrBtBK8@BDtBK8@fBLDϻtBK@޾BDk.tJBKR@ɐB D}tBK@BamDjtݐBKü@޽Bj;IDtBK@$BDDz&VtBK@ޑ;BD)>t BKL@yBDtBKŽ@B"B/t,BK@Bf EHTtFBK^@ހB)DXqtBK@ B7JAutBKt@CBC}BLA";C}jFj} BM AmC8hB }BL22AC#hEA2P}#BM JAC5D:}(BM*AC&E}) BMpAaC]C}*BM 5AmCJEB"BPA ; BrF9'"BP9A:C$ EA"BP,)AxCE{BP7AƴB;EL #HBP3AeNE#RBP8 ACq!uEL#\BPkA Bg@6E"*#fBPA ǭ؉F'#pBP2%A 6u@FE}1#zBOA0C#E#BPuA aCs$BPuA aCs&BP/A B8EY)~BPA B(;CM*2BOABC"mD"*TA wB˼|Ek,BPA g/@HZF)',&BPEA BɝD.,BP,A C; -BPrAҒBDIE1- BPWArCEV1:BP,AxA6ED1DBP$wAe\C&E`V1NBP8A>CE95"BO)A XCADU5,BP[AVC D~&6&BPA yCEB Z60BOA 2C P6:BPA C&:D`l6BPA C D7>BO-A CD7HBP%A `BC8BPA ` CEB;6BP*TA^B;NBP\A )BE+;XBPFA BThF/C;BPAC nBOA B>xBP yA 96ByD}@BP A PBIBPAhBPBOA K CCEIPBOA } C7CUuQVBP}A ϳC2ELqQjBPA @BGL5C}TBPA1C@TBPA=CB UBP[A B=DKV BP0]A BIE_oZ1BPLAeBpEa@F6qBP,A ҠoErBP%A CզELTrBP+A $BؙEWgtBPA=CB tBPA u/F0tBOyA 8B'EvuBO>A m4BCF xBP[?A wBEIxBP[?A wBEIBP3A aBCUE:kBP1*A BBEN^ABP*ABܿDlUBP)CAR¥EriBP2HAصBCE8BPA VBBPa=A TB,!E- "BO)A _VCCT,BOA (BЃ^BPYAZCoCBPA CEKWBP A C wCQgdBOA <B&CнkBPA T:C,bEZBPA CBNA CL ypBO&ABE/FBPA AoFFBOA CxFBPA SBRTE pBOAC@֭EBOUAB~?DЊBO8A:BꖼD¯BO}ABETeBO:A-CEBPWA 1F5BP2A {[EBP?Ai}BCvBOAWOC`E:tBOA~eC-5BOMABCE= BOՀA JoC0E:BO/A xBEZbBO׸A HC%ENZBOrAB!C%BPA CTFBsBP07AC=ތFi7BP>A B&BPPABEBPA+B5EfBP\AKC H!DqJ.BP-oA -C~pF%'8BPA ˆT+FB75BBP-A As BOABsE0}BOAB EdqBOAdB B0BPABE :BPABEZDBPAfB?EIJNBP':A wB{EnaXBP A 4BܙEebBPJA BZEL}BOABC"mD"BOAZ]CW>E}KBOABD(BOdATC)ZE_gwBPA #`FaaBOA BhE݆eBP0A h¥kEBBPxAC ;ENBP.qACOESBP9bA[yCaEdBP A zBMQEEBOA ׵CžBPA ,CCE.ʾBP1&A AETBP5AC=F BPC@A B E*BPDA BʐDk{BO{A"{Cc̊EdBOAB*ECBOAC :EPh%BOAB짴E*9BP qACEgBPA B_Eq BPA GCpE*BP A CBDXVBO A MBEH`BO A ]IC9DjBO/AMC TGvBOړASBEDxBP-A]vBxBP3AC"UEhނBP4`A!zC9XEJBOAnBךE{Y{BOrA TC"EBOA {C,Gp+BPY"AeBC ^E[$BP,ABXE+.BPVARBМEvhBP"AһB EE&BP4ARC)vEwbBP6A!!CcTEMbBP2ABJE0BPZAl^CE40:BP8ALBED+BP0AzB1EtABP2(AbB!EQc BP$(ARC!REgBPehA6B?EABPA'@CDpBPA! CAWEeBP A3oCSE/G(BP%OA iCE? 2BPA BtED/A CnlD5/BI:(A CrD_H59BI4A ClcrE 25CBI$A ?Cd|EzC=DӔBA@CE_b(BA.@ C+9EUFBA"@qCsFSPBAg@CBE ZBA@`CE͞BA]@qC%\FS R$B@@%C!uES.B@@vC8gF)=4B@x`@$C"BAb@BS_ED"BAFa@=tC"HEB"BALw@zqC!;@s0C(KaE]|BAB@aC[aEr|BAet@(CCEtB@Q@ C)B@@$C)1EUBA@,CcTBA,@CF և$BAv5@pCv}F2v.BA@ysBTF dB@s@CBAI@v~BȓF"BA!@6C*E4BARh@xBrEwBAg@!CE4B@@qCOF>B@@+CEF HBA @CtFTDBAc@BF˾B@@iCpFvBA@C{FBA2Z@%C)DSF^ϒ@BA@C@,EHTzB@w@CIBe*JBA@G@0¾&Ft^BA@pC7 EQBAo@zBFQBNACLDeb]BNACZFK BNA C@&DS BMمAҊC:E7]BNA C3;#gBNWAC/'E!/BMhACEe/BMSA9sCCml0BMAC FfFCBNATC>8F dCBNMAC3euE DBNZAC4FEOUDCBNA;AcF׎IHDBQwBNVAHC+ESBMkAC.$EVBNAC6qCHLVBNDAEdCME*YBO6 A!DC+EYBO=WA!9=C4!F YBO;8A!}C?cEx.Z-BN+A*C5XEZ7BNQA^C'fxD^BOHA!GC2 F(a5BNLAC3}BE0a?BNAeBTEaqBNACDVaBNgAHBYEvvaBNMA BMF+ZpBMӱAA#C+|BMAC(NEnBM/A<E`,yEBN4&A2UCׅE1OBN6A4CvE?BN6A.C"2tDcBN AOC36EP yBN\AC iD9BMA;C=+,FCBN$lAyC2bEBBNaAkCPiEBNV;A`tC8PE BNmoAtCV$F2[BNiAvC0F@,eBNA]C?\E#BNADnC7EBO+A!>C?FDnBNA 4CD>FnBO.=A!CI8F(5BNAyBD5X+BN-AC ÛDBNA{C1v!Et}MBMAC6+E>/WBNAmC**E&BMRACSZ7BN.A1CMEnKBMAC4EڿyBNcBNA{C}ErNBNAC ٩EEMBN&A3C=E!WBNAWCADrWaBNA:C)FHkBN~eAC"F uBNAC**E-p[BTDA An>E dBTtA LAnQEdBTA 6+Eua}>BTEA ]BE&B}HBTA o^ԔCQO}RBTSA oxCcBT7A 0BTvA a- JBTA %hQD0XBT|_A lQBT6A @EO 4BTA dJՆBCBTƷA B&K@E*BTAA >/DYBTUA ?*DMCBT;A B GBTwA )BDE[BTA o\CBT@A AyD&aBT%A ArEFBTA qGGDBTtA gEzMBTNA yAYDSBKFAC E BKfAIC%sEd(BKRA9CXB(BKZA"C E[BK9ABE"#BK.A'CjE6-BK:ApC-EE-'GBJ:AiC(BK|oAqCEf,)mBKABmEF3)BKACzEd)BK ACXF`#9BJx|ACGBK@gATChE %GBK*A*BhW F'GBK1ACPEmJBK-ACuDQBK A9?CKEDQBKA0CDDTBJǸACHDXBKACE؃XBKA,Cd3EIXBKA'SC#E b^BLWAďC2DW`BIDAC8cBJ.AC!EoJcBJҐAyC=dBJUASC{EdBJcAsCzF#dsBJ?ACE@pd}BJACF dBJ(ACBE|eBJACEF0oBKlAeMCwBKAtCN~E:50BKAACF7F$`BKA$CzE>BKA C`lEBKA$CEkBLF5AC+D ƓBJA;CCqBJA C/.DZ3 3BJAClFr&UBL4A"C"D;BIACGDABKAC[F$wړBK! A DCDUErڝBKACUEݕBKDnA BGFuMݟBKFA_C4%FCpBK?AtZC\!EF]BK?A(C\GEu{BKu"AXCFZvBKAChEBK8A0"C͍E#BKxA\CECٲBI}bACBL[/AŔCQBNʃ@؅B3E&;HBN@Bc%E5;RBN@xB1F:;\BN|@Bp.E/;pBN@BwDp <8BND@NBDNBN1@;B\D=y>BN@CBDD BN,@CB[D{DsD*BN:@nBIhD4BNg@wB|_AAE BNik@w+YE+OE*BNg@HB-CQE4BNQ@aUBLC1OIpBNj@ BE&'IzBNc[@3A.\EIBN{@C EUBN@tBfE!U$BNٛ@BEM^BN7@BLlpFg^BN~@JBbD z`BN-@ՏBj`BN@`Bk8EUELalBNuI@B]EccBN@Bh9CcBN@>B0E-!dBN]@EB5mA2eBN@BxE4e BN@KBs-EIkhmtBN_@1BFDm~BNU@W@WEU+mBNd@B8nEummBN`@A4E)mBNK@B1 E47mBN\@B@D" nPBN@BoE1AnnBN@ByDcnxBNc@B¡A'$qBN@BN\@C2FBN_@BZE6BN=K@88BE &BNJ@B8`E\lBNl.@gB2E^vBNg&@0BtE>SBNwR@/UBG7E`mBNK@BVbD¬BNn@ܒBc-D%kBNYj@XB 6E"BNq@ANDBNm@`B Ek5BNc@;zBbA BNf@K(BwDG?BNg@BCIJXBNT|@B̾DLscBN\%@|B@f[D9BN@BtRBNZ@ZBE|_BN@BPCS4BN@BBE%>BN@C' E˙ZHBN @B>EwBNUz@WB]E vBN@CmBgE7nǘBN]@ B#DˁǢBN^a@BUD.l&BNl&@B~ErYl0BNU@RBt D":BNf^@BW/%DeBN@>B2Eh$BN@ǗBQEr$BNg@AC}FE_dքBN0@BĜBN`C@'rBٞBN@ B]Dm:BN@NB BNG@BdEſY*BN3R@9C!+E BN@[BUtaD )BNv@84B@Er@BN @uBFE~BN^@AFD"BN]@B$0DnD|0BN^@A)JDBN@C|BNDwBNo@AZDBNb@BDEӤBN,@4JBPD[H  BND@LB޸,BN,@۷B_}Dѭ6BN`@؉BpCoB d@BNf@ؕBi-BN^BN(@GBD>wBN@>B2Eh$BN}y@cBEv+BNl@jBYk#B 3zBNd@տB?(2BNb@Bg=Bi7rBN@.BbQE|BNg@gA@E:BN@B{UQEpBN@Bn.E'BN@UBo.CBN\<@BC.FBN[@Bk69D{PBN[ @B%޻E(H7ZBNY$@ BE@BNt@MBwʳFpBSF`A+Bu El5!BSeABwF !BSSA&JBRXEH!BShA4BPiEr!BSA[BDS$!BSA?B0.E!BTA3BuE!BTFAɡBrF ?>%BREAA(*BSo'ABJNF|A*BS7A# Fe0*BSgAbBx#E5+@BTl!ABE&+BTABStA BLEz-HBS.AaB\~E-RBSA@BLF1K/xBS'AŒB+F2BT"AqBEP72BSA;BgEE\9BS7ABE #KBF&@BTABBO|A1^+FBODA3gB E,BPiYA*?BQgDò$BO޸A+CF,BOA7oB.E'6BOA5[BF7z@BO;A2ՈrfFBkBO5A0tCGFLC.&BO>A1: CEM*BNA/CwABOJA:B)5DBPA8FBǟF3BOA7B EWBP 8A95^CF8lJBOgA;KB)CBO?PA:SBǀE>"BOgA*AC+3"BOIA:ԖB{D'#6BO'A:B#@BOA;BFn#JBOA6BS;F9&BPA+C `D&BPl!A,GB85CH'BOA(C%'xBOA4iC;aH'BOA4ZCbER+'BO|SA4GCh|E-){'BPA8GBP)DBO9}A2BF'g)NBOA2DC*F ?)XBO!A2"F:-rBO *A0AF-|BO A0nC=LFt{-BO4GA0*C~HFu/BOmA-fCWHE/BO{"A,CR /BObA-e>CV+Dl/BOmA- COE/BOmA-sCW$E*/BOoyA,CPN;Dڞ0BO9A4CH0BOA4CBEn0BOyA4g C.1BOA+ uC[u1BO>A+H6CGFE1BOA,=CkpEP1BOA+iCR.Exc2BN(A0C3BO@A3BOkA-#CHxF4HBPLA+GKC4BPz0A+h7C,4BPA,JB B5VBOTyA3oBF5BO@A1JCEqI5BO/A/dC4E!b5BO>iA0=C E5BO`A4,C͑6FBNA1LC5cEE6PBNMA1sCFE>~6ZBOA0|dCBFi6BOsA1%^F6BOKA.C}C#FP6BOR"A.ACdEh7"BOA+C\E}7,BPzA+q$FWz7JBPwA+LBw<.F7;9BOBA318CFY9BO A1zBF(&9BOJA2[ìFxC99BOA4tEC2`:BBOA,CvHEC ;2BOyA5B?F>.FBN@A/OCֆEn3LFBNA/!CEwI(HzBOA2:6C KBO A5XC}F/LBOvA64BN%EMfBOA8tÚFɝ<QBOA*Cf}DSGRBPXA+TC9T BOjA; B\E6'TBOgA:B~qEQ:TBOaA:,BE%[-TFBOA4aC9E,̖TZBOXaA.]Cm%Ej:TnBO^A1C,QFV1TBOPA,#CHAF[sU"BOـA7,BDVBP}YA+CBˇCVBPA+CCT\MBPCA,BOzA,`COqE;WRBOA+ˍC&EǮBBN`A0DD"LBNdA0+sC0E\BPhA,@BE=yBOA4C^5EPBO#A;FB_*EŴ&BOA;B Fק(BO%A;'BBPA8GBPBNbA0CFpBO.A7ByX8BOA7^:BfD&BBOA5|BL4EvhBPLA+BrBPA+}BBC_A&x&C=BOg A-CPC -$BOwA,ThCE#'EnbBOA*TkC62EJaXBP A8BbBOtA:B8E-lBOA4C.BOA:BTwE BOkA;-BvEFBOŰA:B)gEˊwBO[A:BADOdBOWA3xBuEB@BOvA4%CrDaBG` A HCiEmaBGWuA gCl>DRWN BG6A Co{E NBGFDA CbESNBGuACNBGaA AC͍DGDBFfAczH)FσNBFAdD40FYXBFA*CO;CBFFACAEiBFACE*cBFA CwBF A~CCBF_AbfB=E%BFAVAޙF BFA2B&DE-8 BFAC օF۵@BFARhGABFțA1C F)KbBFRAKjC^E4EKBF`ACCt`E!KBFACE\NMBF5AՊBHDYPBF0A C#PBF5A XCW'Dg'XBFASC+\E7pXBFAG/CS!A CYAtnBFA;BEnBF$A CunBF(A)B,oBFcA"BEoBFDAۏBXE5oBFRiABFE>qBFsA HBcTBFA(BtDBBFJAOBևBFIAkBʭBFABEmBFKABEwBFǍAfKToF+  BF;AB%rEBF2BA1BcE!ZBFtA ڥFfdBFXA ޙCFaLnBFAژBEÛBFABEEkBVqDA?EB,EBVx~A5A`:D.%AKBVCAe2A@5DQBVWAAeD QBVdWAB 9E? RBVaA@c&Dk&TBVyA>fi2iD2V}BVZ$AAMDjVBVRAAD`CVBVKA+A DZ6WmBVAEWwBVA\EضEWBV2AHB/E/xWBVrAQDEA\BVAO8;BR_=BVA@Tb5BVAվ믵ERV\bBVSA˙B@D)bBVA!QC1}bBVAxE_8cuBVA@ڂ!CcBVIA9_E:XcBVA!E}eBVAAmEeeBVA3$ڇETrBVA|@쮴@ps)BVUABDcsBVcAAdDWsBViAA D wMBV7AZ=w{BV bA@ŎDvj{BV$)AD@˗E-BVAAzC BVdA @CEb^<BV}4AXaA$uFDyQBV/~AlAD/BVA@cF=ؾ'BVA (ۉEq(B1BVyA>9|pDK;BVA(P>E/kBUAA?GEBVAiA+BVvA E? BVwA@Y6C4BVwAGAtSE&eBVGAyAD߂YoBV= A_A:[D6yBVBAfAuD9kABVJAvASBVzAFQE8]BVkAAȽEHBVvA@iDTBVA@TBVo}A?BVd;ABTAAENhBTAAKEFEJNrBTlAhARFAN|BTe;ABAGFaZBUhA`AF#<wBT5AkaAkDK|BT8AUA7 NEBT֍ABEHGBTjA>A7EBTAkA0 ENBTdlAĂA#EJFBU+A"FAE4WBUmAtAEEBTAA,F#˜BBTAALBT'A%AF9LBU AA%nFjBTwA(@,FBTJA [@O/TEBTTRAPEBTOAADo(BTAAWE2BTGAAԔEBT0A]EnxcyBU&AնAlBT9A4ACp2 BTQdAu?E"eVBTpA @E?`BTmA~@AdCFwɅBTAmAy[AeKE?̀BTTRAPEBUWA A@OE)BTADKAp@FRlOBTơAAဣFqcBT3AjAgE0BT& A@Ep՗BTFAAWr^D@VnBTAU`Ar0FtBU AA,E愧BTmA+Ag~nDiu6BTBAADz4BTAwA7A(,bE[BT8"AkAE46BKAC,LEwW;BJAC/BZIAwêGB[ An˜EB[:A18F  BZ9EAn@$FyBYIA EC ъGS!BZhAɀzE|+BYAB2FL5BYeA.%FF?BYkAEIBXAµ=E`SBXABNFl]BXA yEqBYD$A>dF`:{BYAb&FOu}B[mAAbۮFn'kBZAפ@tF1'uBZvA_FU)'BZAo/0uE߮ 'BYdAsurE܌'BXA/F %'BY(A %D0F(=BX5A#[Es(BXAiC<F(BXABtF\ (BXA>"F/BZ bA?$E/BY`\A èْF/BY* A  .PF/BY+$A C5l'FK/BY/A A2,E9/BY5A BE?1BZ3BAϿR-1BYY`A.R6$F߄V2B[AWA&F*82B[UAV|q8pF52)B[A(AiD+FMDgBZA( JEMBXAuEcBEWBXA>ZDE[EaBXA1ӵE哾OBZ4?A.CsʩSBYfA3FSBY\A%BE[BYvAQra BZmAEaBZvA2:YF!aBZsAT@&vE[fe#BX7ABt%E<e-BXAWEoe7BXxA*ZCIdFks[BXA4FҶ9BZ۵A>_ECBZAZ*AFaBYAyF-%BYAuBDqBXA;bC FkBXxABFNBXAdOeF)F#B[A BF\CBY"A l`jfQCBY3AAiFM1B[GAwF.7BXAٓ4EsJABXABkhEKBXڻAC]F3BZK A@BoE=BZ>HAHIFGBZmAK?FEABXA>b8ES.]B[mA[NJFIgB[GqAzBF]qB[#NAL§F#BZ4qAvA.nEݪ-BYA(A~EC7BY)ATLG#m_BXAWBFO9]sBXA.CBFABYA)AFOBY=A ,(F3BZAT FBZbA|"nE`BZCA?A_F/BZArH@_ET#BXAΗCJBM֋A CgEX>BMAPCE8BMA\CElU%BMArCPEeBMA~CFPBMq@CBQAxQBj4D:BQA^BH@D:BQ#A/:BaWD?BQA]B DsĘ?"BQ%A BE@BQ@ظBkE@BQ-@ϚBvzE@BP@BECBP@EBCD*׾HnBQjSABXHxBQR`@(BDkHBQC.@BiI"BQA4!BKCI6BQA[B`OEW7/PfBQAB;D2PpBQAB;D2PzBQABVBQ ARB MEYBPI@Bϖ0E9*YBP@nBۨEH5YBP@~BչE[$BP@'BxE&\CBQA7BzE<\kBQuAB}Dv`jBQxtABEGE`tBQaABEѭfBP @3B}EnfBP@GB!FfBP|@B63E<g|BQfABωDygBQbA{BmZEnRBQ A`B'DGnpBQA BY2DxoBQ ARB MEoBQA7BDKQrBP@YBǐEr&BP@ ,BV~DSӺr0BP@CBeEu(BQqAH|Bs1E!xu@_BR]ENBQN@7tB]ȁDw]BQ @>Bgs0E BQ@0BraESDDBP@BEݴBQA7BDKQBQ^FAНBƈBQ[AABYGDP BP\@BD{@BR A)B BQXA&aBbC4.BQInA* B!E}BBQDAB?FNBQaPABڜEBQ+@ B5kFYBBQI@uBÙE 3BQpA@hEQBQA$AsNE2BQAxB}CI&BQAƝB/F BQhAF'EoBQrJA.66FsBQAvB$CK%qBP;@R3BcE|ZBQWA|BRCE#Ó^BQABsC hBQABDkABPf@0B-D!BQ(ARDB|BEJ [BQA BfbDBQ 9A!`B]EmBQhAB)C\յ́BQK@b5BƆE~BQkAxBi8{DXmBQA^BI D.$BQABnhD)@7֞BQ2@8BE}֨BQ@BEYTeֲBQ@BEYTeقBQ}MA_{FsٌBQnA$&BQAB{CRBQ@ABsB?pBQE7@BZE4|zBQD@ BDU ܎BQ%@TBE%ܘBQA BB#C5ܶBQsABΔDjDBQN@TBE4BQEyA4BD"BQ>ATBGEq֗BQInA* B!E}BQ$<@BCǙ8BQmASBE#q#LBQk\AXBxEABBQ AHLA6DځBQ7@[pB~cEB}BQN@EgBwE6zBQ>@BEjBQL@ B>D\0BQACBB~°XBOAJ B%@b°vBOAJB@aBO!AM(B(!A-BOޅAMBİC[7BO4AMWB׌ D\0IuBHA BBE 'IBHA CfbDy~BHJA nC'ÇKBH A CrBJ@C9F?MWBJm\@qjBEBJ|@Q_C55eE " &BJ @݋CHF_U&BJk@GCFPC'BJg@{BCD4E 'BJi:@RC#j!E'$BJl@FGCAOE}'.BJ@ݱrC=C4l'LBJ8@ތC)'EHw(BJ@3Cb E1%46BJ@ވ7C" Eha4JBJX@C-;EA34TBJ@ClREO4^BJo@ӝC@lD~i4hBJ]@CGrWERC0BJ@vCACLhBJ@C8ENj5LrBJ/S@' CCcPBJN@C9z2E PBJ @WCaD8RBJ/,@EC+kEbjVBJ#@bAC'wEWBJ&@ZC1C$]4BJG@r\CEaNBJZ@ᠣC6E\aXBJU@ƸCUDi/*abBJe@oXCFDE7cc8BJ@0_rFlBJ@CgE)Dn(BJ@C>D2ntBIL@+CdLFDpn~BI@,1CBJL@C6'F֞rHBJEB@mBEHvBI@碹CBJ`@ឪBE^%BJ[@ឩC 4EĦBJe;@BAEPIćtBI`@o%CADeĈBJ@ CFēBJ|@UBoēBJI@0\C3aDFĚzBJS@C.JF %ĚBJ@vC3VC]ĚBJV@ CT"E*eĨHBJ@8C85E;VĨRBJ@! CgCNĨ\BJc@(Co~wļBJ:]@C 'EfĽ8BJ) @C8EDlBJ@|CiEfvBJra@C+EƀBJ@gC'E 0ϐBJe@C|F,4BJ3@4CDDBBJ(\@CFӋFBJ3$@C[EFk BJ@ݻ'CaES6BI@)MC^qE/ACyE'B?A#ClC,*BQxmA ~A 14E~ZBN=@B =BNT@dBvbRDh>BNPm@jB'D.aBNV@B*BNTP@qBv'BNw@gBoBNQb@^B9wD<BNTP@qBv'BFAQ3B`B&dBOWA BwL?BNA $_B KEZݑ?BNܮA %ZB؉E>?BNA +eBzEFABNA ;TC\E;ABNA jhCC]ABNA C:EAsDTBN)A (BJDD^BNԜA 3C=ElDhBNܢA ^C`As vGjBNLA R9CpF lGtBN?A C]AFQy2GBNTA CEvoAKBO6A 5BEFKBOlA -B7A /B̰DE\XBN0A cCoEKIXBNsA C|cE 4XBOTA ,gCA CmzBOTA TBPTFzBO?A /BEuzBO sA -bBEuԌBNUA C4EԌBNA CRBEԌBO A  B\"DՓԍBNoA >BE>ԏBNA 7 ByD7<ԏBNܡA C' @QԒjBNVA =XCcEԒBNA 4C4#EgsԒBN;A `C9ENԖ>BNdA CFq+ԖHBN?A SB+!F|CԖBNVA w`C&[DYԗBNՏA ZC)-UDTԗBN2A E]C E!ԚDBNUA BBjE*TԚNBNvA B@E_?ԚXBN-A BdžEc<ԚBNA CDԟBNf9A CBCԡBN}A C(EԢBNlA (BmmErԪBOA m/CFԪBOLA B2F bԪBOJA rA2EAԬBN:A sZC-EhԬBN\A oC5SSEfWԬBNA hC#F9 ԰$BN A IC FD԰BNCA C(E԰BNA [/CC԰BNjA DB C3 ԳBNkYA C5ԷBNRA C'5C4ԷhBOVA B^EԷrBON#A nBFBԷ|BOYA BF2\dԸBNA BC fPEֱԹ BNԟA 3jCBBM@CopDyRBMS@oC\D\BM@BB8Ep"w lBMz @oBEa BMp@}CXNE#~QBMSI@3zCW2DBN0@7BICE"pBN@B|BMW@죎CD(BMQ@!C){Dv?2BM\N@VC;%5EB.BMc@C BPBM @;CxEuMBM @ B7EDBMl@|BNDtqBM@)B몑EiBM`i@)C+D+BMZZ@CTE_"BM@C ܬ"BMz@NC HD&BMV@틢C YX)6BME@Cxs*&BM@=CCE 1+DBM@oBmE,BMN@COlE, BM@BFf,BM @'7BXE]O9BMS@`Co FY9BMN=@鮶CfWEn9BMg@hC='Ev":&BM'@&ZBkEok:0BMJ@CVEN::BM@LBME:bBM@VCEL;BM&@_B'D`l;BM@+BcDT%X<8BN0@B3@zBM@|C _E>@BMk@ZC~EOom@BM@SBE>@BND@LB޸J*BM@LBOD\@BMtb@KCfE0m\JBMg<@;hC%DvQ\TBMtZ@w~C DEbk>cBMN@CFJdBM @)B^DdBM&@_B'D`ldBM@VBл%EhBMo@BSE/hBM`@LCOE:hBMl/@C6#bE?<iBM]@HCEE@}iBMO@0C.g.DmBM@+SBEہmBM@BD7*nBMG@ؖC)F$Q|n BMr@ﲘCM+EnBM@6BEڈnBMf@E.B ;EpvBM@BzE_lBM_@kCD/dvBMX@C΄E_؄BMf@UC!JD&&؄BM@}BsDx؄&BM@B+D-;،BM\@wCE]،BMZ@hCCT،BM]@=,CkD،BMY@>C5DFؗBM`@nSFUsؗBMG@CWEzvؘBMg@hC='Ev"ؘBMR@fC'Fؘ&BM\@C8;C>ؙBMz@BڄDyؙBMw@BID>ؚ$BN@B|ؚ.BN3 @<*BCC,آBMJ@bCB3D8آBM@c,CآBML]@nC:bآBMZ@뀃C#rE9آBMb@jCأBMJK@KCA|أ BMH@+}CJnD6.Yأ*BMwK@C)=EM]أ4BMq@CE?أ>BM|@F^CEأHBMv@.C*YEOأRBMwM@C#E%]أBM@+;C EcأBN@"C LCZLؤBM@,B(kEM+MؤBM@BUD7ئxBM^@kC:\4DئBMW@`CV˅شBMfP@4fC M}D`WشBMb@ƙCEDشBMb@C{DP)طBM{@=sB*wCKؾZBM@haBOCiؾdBM5!@C^C+ؿBM7@ICEeBN1@;B\D=y\BM/@|C"pECfBM@B/D+pBMG@BGE2ԚBM=@CZMD‿۰BMx@BެEacۺBMp@^B%DBMd9@LC\DxBMY@ۖC`-EBMf8@*CHME1@BM@zYC7D\mTBM@헨C{^BMp@DCL.ChBM@BCRjBMg<@;hC%DvQtBMb@C{DP)BMn@bB(sBMH@fB D*BN@C!BM5@kC]CBM5@7C_iD@BM@VBE3.BM7@C>@F%~BM@BM8EсB?D9AD QE8B>tACC6L&2B?s4A upCF&A~CFI ,B>A CF3A,B>AEC7E,"B>tACC6L,,B>qA)C ~C6,@B?oA D @Eq6,JB?LA :DE,TB?~A DDCE0B?FMA8LCES0B?5A CF {0B?eYA jDlFM4B?HAC8F^͹4B?L%AyCUFX4B?1QA C@-F x4B?ZAD~D@u5B?VACEy5 B?VACδnEN8B>A6C?f?B?DAD `DӋbAB?mA D=(FAB?~A D2ElE}B?[cACDMEB?H;AuDDEB?H;AuDDQzB?18A fDENVB?DyAwC/DVB?NA4CEWLB?OAgD kICs WVB?RAA|CcE9BSW~B?WADE!WB?SOACyFxY~B>AоCɫDCngB>AtCEnqB>gACD&Cn{B?ApaClEoHB?W AöCE,QoRB?U&Ax.CACVD>ڹDB?7A DZ\EbWB?KAD3>FT6B>A™CFAJB>AC/DŔB?TA=CIE B?.A D3)D@SfB=A)C#B?jdA DE*ՍB?c}A D"ElB?nA _D DjB?xA%C'L0B?SZAD1EB?\A6CEB?[ ADB@XA8oDCJەB@f9A7JD m>Co/ BQF/BBG7@B0FEMDBGz@nLCDFBGF@)CEָrFBG+@ӔCLsEwBF@ЫCE^@xBF@ЫCE^@xBF@ЫCE^@BG3@֯CiC2BG>j@3C BG@GCBG@.-Cs"BG@]C[jDBG%@ק;C3BG@ұiCDc2BG@CE-BF9@oCBF@[CEBPQAUB F!z BPA `B`E)އ BP?A jBP>ABD-^vBPA %FB޸DhBPA HoC)"Er]BPAiBоC0BP͚A C1ҀEoBP-A @B*iE9BP ABhEBPtNACErBPmgA CC?TBPEA$BsE-0BQ AۦBADBQ AۦBAfBQdNAiB2-pBQA$ABRgAORAʲMFZBRAOB ~DD2BRcAOLBo!Er2!BR]AOBBۺDa2+BRANVAMwFLkBRݹAOYB J[9BRAOB ~DD BR#AN!BjF*1BSxAM_AhFN2BN$A0iCQDCgBNA/Ce DꩠBNA0|WC0DŰTB>@arCB"B?Ӈ@?CLCm_'|B>}@CE'B>_@C E'B>@4CEz'B>@ {C}F!'B>@AC9^Ȩ'B>@C WEn6B>}@bCfE|e6B>}@6C}'Ea6B>r@@CDP"6B>@vCD6B>=@C%DG6B>@dmCbE UL B>}@:TCVE<LB>p@ zC/Dr;L B>~t@@CJENO]ZB?O@'ClFg]B>:@a_CWLxB>@CE֢xB>@\CEhxB>A@pCPAE^zB>@RC DkzB?W@zXG:}zB>@CtF#%zB> @`CE]zB>@_C*Et3zB>/@tC?D-{B>@?~ @CEzu{FB>}@C>Em@.{PB>@_C‚~B9B>@xC$E9B>@yCvZEJB>@,CEQTB>@CEvDB>~@_=CEqNB>t@ C|D)XB>~@UCEwCB>G@`gC>VB>R@<@CVE97FB>@.CEI7PB>t@PCEQOZB>}]@,CjEilB>@tC!\B>@C6bEffB>,@bCHEF݄B>@3CCx(B>@.CLFig2B>@BCĖES"xB>@X/CEvLB>@CI FGfzVB>@,SC_FL0B>~@3CEz7B>}@HCEnB>@$yCE;B>4@P CŒiDB>@yCC2(B>@ZCTE>lB>@ZCjC]B>@nCmBmDϛ'NB>С@\CJE +BXB>A@>CξbF9BDA ~D GaG2 fBEIDAB^F; pBE'AC_LF) zBE3AZBQ7F9 BDABEz BDAB2Ee!1 BDAvBF]ESkBDݤA B FvB"BEdA Ct E,BD`A [BC$ CdBD@A }yCbF%BD~A [BF; BDxA ^B,F+BDXAeBƯE-BDbAzBڱ7E_BDS^AUBEUӅBDA6BEBEA#Bϐ/EBDޡA\BG4F?WBD AiABfF12$pBD]&A ClWDZ$BDA _-CDH~BDA B}JD,BdHBDFA 8BoDWBDA QBvFbWBDA =ZBȏFDPW$BD'A '9FOT]BDYA fCjl+D?d]BE2ABY^|BESA#BEy`BD]A BBE=o iBDdA"BCE)jBDɝAmBеEj BDdA+BቂE^D jBDjA B~FaXllBD-AzBgElBDEt2BDA eCC"D$/VBEL,A LBB!DGz`BDA`BEBDxAB;E)BDXNAfBQ"EBD:A "[{F,BDQA ȸC"BF/BDA B7BDA }FvOĄBDeABGXB* ĎBD.AB1EYĘBDoA SBOE*AʜBEOAKB mE˖BDABW!FurBERA0BgC>BDAќBSDARBDA BЂ BDA BEuBD0A _Bk EBD׃A Bϙ~E2BDGA _Be/-E*hBDA kC!mB.BD A>=BgDvQBE|A B FBDwATBÑ7FUGBDaAyBE`BD&A YSC$`5BBDsA \C$1CBBEA BeE*@BE0A BBytBD]A a:CtlDQ9~BDA SLCCLBDwA bCDTBD A VC wxDTABDsA \C$DCBpBQ2A0BuRBQGSAjBRUC 2BQ$AqBD*BQGABIkC*=$BQ@XABT1D S>(BQAABI(D6a BQ9KABcqVDnaBQHAB'D aBQagA_BCbBQHrAB`b`D@bBQCABR Db"BQF0ABAJEl^BQ4Af1BxBQYCAtB8hE`^|BQIAoo>DBQHA,LB EBQHABxEm BQc ABq#D>BQ5AywBD,l BQ)A0FQBQ: ABerEyBQAABTvMDg$5BQ*ABSEuBQ=AԂBV|D]BQ9 AB\fDBL,@1CeE=PBM@CEnBL~@CqSCf=RBL@C QFE\BL @+CFg6~fBLy@C`qF5pBLҟ@&CGFCؘBM@eC,DQ"BLH@C%UEv=BL@1C#3EcxBLi@CFBL@ C$F&BL;@CEvBL@CgE DBL@PCznfE  lBL@&C*SE/BL˳@BiqFbSz/BLj@BC̠0RBL@ùCEM0\BLd@'CWEO"q0fBL@Y2CζDX0pBLð@XCQEL0zBL\@%uC E9(0BL<@OCAE$5BL@@! Ci76BM@VC@f5D6BM=@]KC.&$Dy6BL @cCDP6BL,@נCYE̋66BL@C_E7VBLz@CC7tBLY@]CfoE^^H7~BLB@_{CZ&E7BL@;-CL:E7BL^@HC^'}EY7BL:@-CHE녌7BL5@#C#EΟb7BM@Cj+E7BM@oChE,7BL@䡗C$s8 BL>@CrjE^8BL@bICzE8BL<@zCBv"EWG 8FBLj@"wCdE,:8PBL9@#C =E8ZBLw@CcEw8dBL@OCnE8nBL@.CEGx8xBLۇ@C#E~8BL]@CEnH9BL@<'CF#z9BLW@8qCE 96BLK@CD9@BL@搫C*E]9JBL@^CPF&CrBL@BD.0]DBLъ@XCFNlDlBL@uCv]FNDBM k@CGmQBL@Y~CeEQBL@aC E{3QBL@⅔C,^uE}RBM k@CGm]BL@RCjE]BL@CEKbzBL@7CPCAe^BLԮ@=C7%hBL3@šCkJĊ(hBL{2@ CG C?iBLn>@{CZiBL5@CeuC\iBLԌ@WC3hD2iBLa@6CeE鑲iBL[@vCNnDXiBL@l_C?iBLI@4CELx2BL@CE]x@dCt{EBL-@B;DLgBL@;CUDs_XBL@C])BM G@WC E9CBM @C EϻBM @>CEJrBM>@CBLJ@iXCEBL@?0CRbE BL@IC ))EBL@'nCH:BL~P@vCY BL@sCi}ElLBL@s@CVBM@CX0E`BM>@CBM@ CE@ÜBM@ ZC;EˊæBL@CY EF'BM@P@C!(B~BM@CC9,D BLǒ@>$CF)^BM@Cyo|BM @NC"`@E+ņBMx@6C6E ŐBM@Y\C3E)ɴBL@C EɾBL@縌CqE}ُBLO@UCG}EY2BL@穿Cn!E@C?D<6XڼBL@kC) DbBLg@ C5ExBL@r?C'[E&BL@C݌BM@CˏE)ݖBL@nC:aDݠBMq@ BD(ݪBM0@|C)yES BL @<$ClED BLb@G C\=E:*BL @CObE^Y_BL@貯CfBL8@葔C)DBM $@CDԝ.BM @IC"4BLv@7 Cy>E4~jBL}@5CaEyLBL}@CX+E $hBL @ BU/DPpBL@CsDUBL~@CqSCf=BL~@CqSCf=BM@$C)DM|BLv@ C4D6]BLJ@\CcBL@CTDr5BL|@iZCkgFU&BL@GC\ EBLBl@C G(tBLn>@{CZBL@B|F > BL-@3BJFW{BM@OXBEBEPA A3?E͙nPBESA AEPBEA xyCWE*PBEA RB9 E׌SBEԊAGB E{ETBEԊAGB E{ETBEMABEuBEABuBEAB}=DvBE^ABFM BEATBbEԍBEAFBJrDӣ/BETA B EEBEܻAB(E?&BE7A/JBEʢ BK@ԹBV (nBKP@nLCE -BKN6@'C+VER/ -BK>@AkC)E -BK;@)C E 0BKI@YXADDFJt 0BKU@BTC{ 0BKI@ʲZCj 0BKL@¤]\Fd 0BKJ<@ZCbE 0BKO8@AE 1BK5@BvD = BKk@?BdE =BK@z7B @BKN@+B,Eǽ @BKh@ϣB6Eo1 @BKw@ЇXBT{ BBK|u@B@BuF BBKm@^cCK|E8 BBKY@BOF8A BBKHR@ZAIF BBK9@DBZDfu CDBK]@ШDBKCn CBKt+@tB2F  JBKS@WCE(= XBK\@Ф$B#D XXBKY @ͥBҲEk XbBKXX@ΗBgD}L XlBK?^@ICqD ^fBK4@B C `@ECHE C BKM@GC+cES BK=*@C-ګFp BKv+@:BE5 BKxU@B`B8 BKb1@ϫVA?^E( BKVS@cCG#EW BKV@΂‘;F t \BKF@ȾBlEͩ BKw@ѣ BE5^ BKe/@ЏHBF BK@Q7BƱyEi` BKZ@ΝB4Et $BKS@ρAxBEz .BKX@nB"D "BKW@ BEDA5 ,BKW@nBD 6BKO@\BEѓ BK<@@ĻC_ZEF BK9@DBZDfu BK5@_BMnE BK[0@ΉB,CA BKEk@ɶCHsEj BKF@ȃ@ΏCE BK;%@#CC $BKM`@C*ץD!P PBKIM@tC6\1E9 nBKC@[C:Eg ÖBK@j2BO4ECl3 ĪBKF@yB,)E> ĴBK:@/CԐDg ľBK>J@aVCCDg BKH4@#B;F%`l BK20@gCBX BK<@̯CŠEf  BK@]CAI! $BK2u@άBC 8BK4 @7CE BBKJ@@pCB?C LBKC@[C:Eg ~BK}@Ac`F59 ܈BKa|@KBB< ܒBK@ӍB<&E  8BK@ɝnC%E"S BK>i@T+C D BKN@-C$DE X |BKM@ǴxC*D3 BK?@eCU^DX) BKG@&CEE5 BK@vC NBKi@ωOB'E&  XBKW@Y6CiF$ bBKZ@B٨CcBPvA'|/{QG 2BP$A(]C D):BP#A'W*BsEj BPZA+m(aF2_BOA*TÁO:F0BP^FA)B E"Z BP*A%BJ(BPA&9B2BP2A$}BCXBPA)ݨBEhBPqA(BƀE0ErBPsA(YB{ZE|BPA(]OWEWBPbA%ظBBPA%OB DBPA%HBŔ BPA(TiB49 BP'A'cB@' BPA'AC,EUj BPZvA'B*F d BP/A'ٮBdBPGA)BxBPA%.BwQE;BPA%|BD7/BPZA"BBgDx;""BOxA)C`Fn"BOÞA)"C.w?EK"&BOǛA)iC[ۑEl"NBPA*WCfqFG"XBPbA+42F.e"bBP-DA*[BeF}&BPA,~BcFo&BPxA+$ FD6&BP|A+FU'BPA)PCF1'BOzA(eC~F\7'&BPA(rCF N/BO|A,*CQU1BOeA*CecT3tBP?A)*BlF3~BPdA(~BOEBb3BP A)GCF$3BPE7CoTpE20BPYA*hB-BPA*YBaCO) BP)A( BD|BP A(3xBFIˆBPA'3BFsːBPA'OB_E=BPA&B=>BPmA)BQEHHBP"A(B1DwRBP A(JBzEhBPOA+C'PE;rBPA*@BFb>BP)A(FGyBPA( @B-EpCpBPA'1BE$zBPA&Ct`F*}BPPA&eB[nE$BO|A,QCfVCw@BPwA)C"]F-iJBP9:A)ùBCFFgTBPA*BF'<bBO A)lxCDexBP>A(WCpEBPhA*SC=/FL BPFA*BFT_BPnA(CFBPN6A#zC ?CyBPA;B{CBPYA9B4D BPUA;|BXcEp BPEA;DBGF j* BPBA:BRQEyBP0A9BaEqBPA:nfBE.qBP\A?hBD-?bBP`A9 BflBP)(A:!BdD/@vBPOA9?BֿD BO1A:oBE =BQAC68B8T^E!!$BPA7zlB8ED !.BP=A7>BIEE!8BPA6SG+F)!BBPfA4 jCFO!LBPOA4CE=!VBPA2/Cv[Fg!`BPQA=x!eFj!jBPAP!BPA@;@AF@t!BPeA>#BRF#!BPA?{BPMFiA!BPA=BjDD-"BP(A97IkF7"BP1A8C]F ;{"BP:A8CtE"BO A:BJE˂C"BODA:BEi"BPA:|BH1E"BPMA9BLEF#BPDA85B6Dʯ#BPj A8tvB!*\BPVA2CPF$@z*fBP)A1CJ$FL*pBPA1CAE^.:BPA3'CE8.DBPzA3C )DH(*.NBP%A2 C'zYE̼.BPrA1CwD楚1лAG>9zBPBA9elBSEH9BPBrA9 CEH9BPRA9^hB>'DABPA?]BLVF&3BBPA1AC~DQD~BP^A6B2DÆDBPmA4HC SC?pG&BP`A8)BʈE G0BPOA8Ce|ErG:BPbKA8%BD;KGBPA;pBlc~E\:GBPA;7BhiEwGBPؕA:B*E MfBOA7OBʯMBPYA9B4DTBO?PA:SBǀE>TBPhA8uBҚ5CkEUBPCA8fBEU"BPhA7ӫBPFa[BPA8:IBF/\pBQAC'BFyX\zBP~ABBxxFV\BPAAH|Fjo*`BP@A49C"EaBPA3`$C8EPa BP&A1NC,FaBPA1(CENaBPA6)AEaBPmA6 B|0E(LaBPsA4BF taBPBA8BI]EOaBP[A8BŒ*E6.c BPLA6/4BFc>6^F}l~BP;A>:AF&SmBPA@B?qmBPA@BD{5nBOA:BnBOA:ĴB-.DKqBPA<ȢB<rBPMA9( D%FzrBPA7NB7EHr(BPA8UECrFhrZBN A;B!ErdBNA:QBZEhYvBBPA6B=|F=1vLBPĄA5 CMEvVBPƵA4 C HEux6BOA7^:BfD&BPpA2˂CEvBPA31C6EbBOAA; B[?E`rBPA:0BRqE.|BPA:.BRFBP'A9BrEXBP>A:B[E;BPrA:B@1E$dBPA;[B\EnBN}A1CqBPEA:DB DGBOA:BE(BPA:iB,Ep%BP/A:(B>UDJ <BP5A9rB DtwBP`A9 BfBP[kA9BV,BP0$A9gBEJ3BPA7 BDBPA7B'F!BPA6]0~F0 BPԞA2B-ICŎBObRA:BtbBOJA:߽B]EBO_ A:ߪB~YODI BN=A= CvBP4A8yBoEU HBP&A8JB3BPA5BEc BPA6HBEBOA7oBuEp2BO A:BJEPݘBNA;:ByC϶ BXTAB AE y BXTDz\BX[{ABAAq>D@)BX\AB2`DaiBX\AB%Bv6D2BXVAB%AH~E< BX\AB>:(D~KBX]AB2A\D|͟ϳBXOAB ARDMiBX_GABD{?CBX]IAB2|Br̽D GBOQB@燢BeaBO@j@rC)x=EՅe NBFjA YC"6E{? NBFdFA (C6OEl. NBFA CKE NBFA ?CvF$| OBFyA a}CWD OBFA ?C@9E~ OBFpA CC;Ex OhBEܤA CnGEY OrBEA ڏC7DEf O|BEA VC|Ec6 OBFY[A CE OBFEXA C7WEKO PBFSA uC,Ee PBFA -C=OEZ PBF"A rC}6Ep VuBFBA JChplEU* VBFA ICEgEj W[BFA )Cu=xE XBF A 0C=wE@. \ BFA QC]E= _tBFA CCVD  pBBEմAQLB zBF#A .Cjh BFA BLCP:D/ BF@A ICZSEq BFA *C3EI BF}A 7C3E! +BFA ;$Cr|CW BFNA PC>5$CX BF$A TCkjDV  BEA dCRC BF*A 1BF~N BF".A hCtMzE1$BPAc$BJ$U BP0AUABC$ZBP`AB($BQA?BDR$bBP7AC9?&BNAA?~C&BMLABsBn&BMOABBX&BMAAgB D&BMLABB錐EdI&BMABCEu&BMAAB^Cn&FBN;gA@;CDj&BN6A@lBE8&BM A@kBEw&BN A@BZC4&BM AB BBaE/& @BMABBsCynT&jBM=A@BХZEbS&tBMAAB~EJ&BM'9ABaB!D5& BMdAA\]CDC&KBM@aw6E3('BJ.@㓣B;eE7u('BJ@QB~#E'F('BJ@fB^EԻ('BJq@bB%EpJ()BJ@snCE(1BJ@LB߬oE(2BJQ@Ba(D׎(2$BJ@BFE(28BJ@CFAE (2BBJ@wBE4'0(2VBJێ@%@-Ev(2`BJ@B~`(2jBJt@6BFZD(2BK @BsдD(2BJ@hB>rA3-(3BJȉ@`BL"E{6(4BJ@5C< E(4BJϽ@BEc(5BJ\@KgBN9D(5BJL@8B}|DS(4(BJ:@BE(ZBJ@CM[$E 9(dBJ@㰗CW CF(vBJ @8BD-E(BJ@z CCYY(pBJǧ@B`wC (,BJ @VB)E(HBJ/@}B5Ek(\BJ@C1ΜEr(bBJo@džBzE(lBJ@FB&5E4D?(2BJ@@HBE{à(BJ5@6Bg($BJ^@7sB|E(.BJ@8BPD~z(BJ@BTDx(BJ@vBD[(BJ@3BfݱAF?(BJ@RBh(ӚBJӳ@妜B<1D:)(ӮBJ̀@Bb(tBK @Bg(6BJJ@wB~(BJ@_BfE(BJF@w B?؟D-(BJ@hyB4E(zBJw@ZC /EMɲ(BJO@[BMP(.BJq@}B1E%(8BJF@䔨BDE8@(BBJ@vBND|C(LBJ@*zB5Ef(`BJݘ@BR[AAAF^?38؜BSA l"B'Fn8FBR?AuFU F18PBR7AAYD8tBSs{A>B F 8~BSLgACB E!Ft98BSz!A_A=FD8BREAB_qyF28BRoAA:\E_8BRxAAwE-8BRn-AmAgDP8BR7ABpX8BRAJAa D2z9 kBQ׆AO A߱E 9 BRhpAOtF,9 BRAOu@:F gc9IBRk/AO>AB/D99MBRNAOAD@c9BRJAOAƨ9BQAOEA9BQAO8AnE*9qBPABB9BQYAO^B;NEB]9BQAODB"F9 BQ=AGbASFU9 BQAFBEH9 BQ#AFZB;Fj9 3BQAE >#zFx9 =BQADֽDPE9 GBQ AD1ɞPFDA9!BQ#ACBW*E 9!BQACdBfb F09!BPIABCJF]º9Q/BQ:AGPAȡ:F,<9\pBQ0AEBGVE9aBRAOAEWhE89aBR@AOAtɧD49a)BRbAOLA|E@9a3BRnAOA&cE9>9a=BRD$AO~AFEF9aGBRbAOLA|E@9cwBQEANB19cBQ#AN]A;D<9mBQADAFT:>9mBPTABƣBQo9BR{AOAnI9BRLAOCAMEP9BRfAOAEi9BQ\ADE9ABRAOA_A:B[E;: BPA:B: BPA:BeDɳ:TBP A9ПE\:hBP A: BCE<BM}s@A;<BMp~@IBPE/C<BMm@ lB<BM@ӯAAEeL<BM=@BU;D1<BM+@m;B*EA<BM@n@Ҁ@B%EZ<BM@EBN%E%<BM'P@kAvEA<BM4@8BOD?y<BM?@ҷBG E_ <BMBW@^B E…<BMU@BA Ek<BM8@AE6V<BMM@BcoBy<tBMG@1A{<~BM=@JA:E<BM3@PB+nD BlD̋ @Ҡ?BTyD]6<&BMH@B#2C/Q<̜BMRh@yBƱDh<̦BMH$@2Aԯ7D<̰BMX@ ?BD<BLǎ@]BmEGʆ,GBHAC%F(9GyBIAC EfGBIA;C˵FGBH6A_TCtEVGBHAWCsEÚGBHmA.CEvGBHACE|G BHdA/C'F>RGBHAtC F^GBHACeG}BILVACGBI8A'CFlGBIDA]C9EcGEBHACE^`GOBHGAtvCǻFGBIK A DCD;GaBHkuAuFC~CGBI`A C(Ea51G;BIcAC EQGcBIdZAC\ ERGٛBHկACEGBH6ACdD2GBIFA"CEGBIMAC.CKLB@N@oC}DcK B@r@]C"\CoK5BX=A\FKBW3A@}-KBXLA._A#EdLBKAOCΆ(C ]PBKM@cBM E.PBJ@CXPtBJJ@)5CYPBLR@$~C[oP BK@ށBD|fP*BK@B+E4P4BK@BEP(BK(@+BuEP.BKf@Bf4JDP.BKbo@LBD[E_5P=^BK@zBƀE^P>BJ@h.CP E!P@\BK@NBԍDPhBLK@CVDJ=PnBJLw@CE5PnBJ@eCPnBJ4@IVCيE*֗PstBJ@C 1AdPBKu@嶂B>'EYuPBK~@BWEPBKzo@_~BDPBJ3@-CnPBJ@ZCiC7PBK@ pBEnPhBK@/BdEP0BKu@BDϏPDBKzo@_~BDPBJ@CqHPDBKn@BEC=qP;BKo@BnLEVPBKw{@mBEtfP8BKc@ñB,PE[BFdA8*D{!B[BF-A8$CDI<[QBFA7DFr[[BF}A7C;E=[eBFA7CE6{[BGA8'DD/[BGA84D_Ep[BGA8 DER[BGmA8GDJD][]BFA8+wC[kBGGxA8DE[uBGCA8!8DNES[BG0A8aCF,[BG/A8D%Eb[BF"A7DE6[BFA7ٺCEw[BG A8%0D@E[BF>A86tCZ[BFA6EClE[ BFͧA76DވF2U[3BF9A7UzD(F[BFZA6C8E@['BFSA7dD F[1BF7A7kCaBQoARKOAEm_aGBPZARB9DkyaQBPXAR[BDE>raBQ/AOBF naBQ4tAPi&AϻEWaBQ/AO[CE5aBQAN%B3ya1BQ(AMWAD`a;BQbAR$BED^aEBQ]ARmAڑETaBQ5 ANǧBwjEaBQDAMFfhaBQ5APC@ EڕaBQ>|AQt?BE$8aBQ9AP"·yENaBQgHAHMBc DzJa-BQgAHhBl{1CaBQnAM@F!GaBQuAL¡GE3\aBQvAKkjFVaBQTAL'EaBQDAMBJEaBQYALP1F=aqBQaAH~AE,pdaBQ۩AO A(aBQ_6AKUAFg0aBQ_AJ;:AwEpa BQ?]AG2B cFFAWBTBDĆa)BPAWBcgCKa,BQY;AR .AEEa-BP<ARg:Bp1iD a-BPAQB[,a-BP$pAXtADP&a-BP=AX0XBB$EE„a0BOԯAN$BwFYa3BP_ARBYHD/a3BPWAR7B-lE(<a3BPZAR[:B\Ea4BPRAWsFa4BQ\ AR0}A-DjEa4BQLAQAEyYa4BQbAR&qB%@EEaa57BPAP ~FQa5ABOAQ\BֳD3a5KBOAP| Bx_F >a6BPAQlBPgF|+a7?BOAOo4%(7Fua7BPb(AWAF8 [a=NBOgAM^CF-xa=XBOAL_=B`CLwa=bBOALB|C*Sa=lBOإAMBEa=BOAMTJC1Epa=BPDAX"+Ba]dDtsaBBO!AOCoFaBBOAM>CFCFlaB%BOAMQA£FfYaBMBPKAQdE E0aBWBPAQ9l{E_aBaBOAPCFaDiBQbAIT4EaFBOAP'2CDmF*aFBO0AOALF)aFBOAN}tFa!aGBQAMW@FiaGBQuAL6E"`aGBQ_mAJBoF ٔaHBQ_AJAE~aHBPdARKB@E;zaHBP3AQnCMFhaHBPhARB7D]$aHBPdARKBE;raHBP`7ARXBraEqB_aKSBQANB'DsaLMBOANyBrbyFiaLWBOoAMFFaBPJAR50C4E aBOALC0ˊEUa%BOAQXNB|C{aBQANB$DaBQAMڢB/ERaBQgAHӿBeKdCaBQ6AOAEFaBQANABLE`aBQ!AN?CAK1EaBQ=ANBR}GEWSaBQ9'AN*[B`[Eg;aBQLAMgAE:a5BQlARAADaBQ3wAQ4 B'cCaBOAL"BbaBQADdAE~a8BOAMLBͰFLJaLBO[ALBүC#ZaBQ}1AR[BEa!BQ}1AR[BEaBP@AX02B!EZaBPGAXGB`sE2AaBPAWBBtDa BOALjBDDbϒaSBQBeAGUB2(F-^a]BQ9AG1AF:(XagBPAAR)CupF aqBP"-AQBR?E̾a{BPAQ=|!F3a;BQuHARRG@q:E~*aEBQSAQ|B:lFaaOBQlAR?lAne&D,;a1BQANACaǟBQ_ AJ*FBnq8E!aBP4B,{DxaBQdzAI5BCE'YaBQb AHDB7jEqlaKBQ4APM,AWo(Ea_BQ,AN:BK0D}aWBQ`AI@F\aaBO%AM(B1akBQA~AGB'XF@awBPcAW3GaGBQAN%B3yaQBQAMB[EaBQZAKas vF aBQ[AK"3nE!a/BP*AWJA:!EvaCBP'AW_ECqaBQ-ANBtCaBPAQF uaBP ^AQM34E@iaBOAPP"CF)a#BP hAP56K~FqMia-BOAOW?C&NFH{a7BOANoV$HFgaBO AO(CHOE4{aBQ5AMBuE}c-BMzA=BCD,cBM}AgDC8DNcCBMAC`cCBMrACEJcbMBM|JASGC;D#.}ck]BLkA%CCcu!BMAãC.D/VcBMACsE&^cBMAChE cBMIA/C5RE@GcBMkAC.$EcBM gAC0{_BcMBMZeAu]CZc+BM AE?CZ5D)coBMA6C0cABM`ACC8cBMAP JcBMAChiE]cBMAIC9KPDQcBMA{ZCuYEˉcBM ACFc}BM AmAC2BcBM#AC4Zc!BMAP CEAcBM}APQC@DpcBMz0A9 CDΖcBM|ARCMDccBM1A]C\EFNcBMlVAC$EMd BI]@@GCh/CdBJ$A@C-EdBJ$`@CddBIX@ eCEۜFdtBJD"@CpDBa]d$BJ@kCEzdEmUBHA BûEXmBH+A C1.E'{mBH0A C0uD;\mBHFA C#REu(m#BH:A mC'm*?BHA L|CTm4 BInzA Cf8C^mGcBHJA CKDXmGmBHA YC E(,mJBI $A vCFXE7mJBHA jC&LE{ymJBHA ]C,(hEVm BHA ;C 22EhmBHA C Ex\mBI A Cc$DmBH0A _CE dmBHJA 3C YEvmuBJsAbCmBGzA C{Co^m{BIA CKgEX mBHA CDуmлBHӏA yC mBI^A CLEmBHBN'@aBEnXBN7@BsnX BN6H@BT EWEnX4BN7@A4ZDSnarBM@BDnl BN)@:tB;?D>cnl*BN(@&8B+Cô3nl4BN(t@]BDenlfBN@BnhBM@AD),tnrBM9@ A#CnBN:D@B$]/n&BN(@BGD(nBM@BD㭃nBN:D@B$]/nBN@BO&DnBNr@bB9rDJn6BN"x@kBDnJBN@&B$|CnxBN'@ەB CpB?A;VDAED]pB?~A7D3$Dip B?AS0DWcD;fp5B?FAED pUB?ADjFP6"pUB?Ai&D9 DcpUB?AT$D< C$ [pUB?A`LD:Lp_B?A{Dd4EGp_B?FADD<ٱEpeB?.AUiDCE,peB?ZAzDRF tphB?A5DRjE,1pqdB?AwDVEv%pqnB?A3DGEgpqxB?A.DN.F"='ptB?AςD[E&ptB?ADE!E2pxvB?<@˻CޘppB?A D(p8B?'A 5D'phB?AQDTE[}CprB?AJDU]7EE p|B?=AJ>DQEf@p"B?A]D3gEPp,B?A,DjwE%,pmB?H;AuDDpxB?!A+D*شpdB@ @}C=@pB?[ACp*B?AGD1DtBOA BBbF5 tBBOsA #C72E,RtLBOPA .C?EbtVBOׯA 'CWF 0^tjBPxA BpttBPA BEmt74BOJA !BE2t7>BOA B6F'$!t7HBPjA 1BE et8`BOA C7@EUt8jBOA CUhDt8tBOA C$D-t>nBOA BVCNt>BPA 3BvEYt@BP#A -A%tEt@BOA F~B mF7fXtDBOA BϰEtFBP,A BRDŲtG$BOA #C&FEutG.BOvA C'ZEMtG8BO:A C'+E(tPBOA C%EXtPBOA b­QF UYtPBOA uBVzF tR BOA 2C EtRBOȗA CLˇEb tRBOXA KC E^tS|BOmA )B]DBtUBP A B[DrDtU BPIA ǍBz)ERtVBOA bC5E7tVBOcA sCw5DftZzBOSA oBJF~tZBOA ϟBAE?tZBOPA RCI_FBt\BOA |BÛE t\BOMA ~ClEOt] BOA  CEt`LBO@A CUBt``BOA =C ctmBOA C_tBĒtmBOA BDttqBP8A 9BtsBPA ByB{E! tsBPA .zE tsBPA jBwEɉttBOA B~EftuBO.A BhExtvBO"A BDR'tzBO_A %C SyCqt{BOA ڶCPD<t{BOʘA }]C ̣E6t{"BOA C'Eapt{,BOOA ACDEt{6BOEA UC}FxtwBOA -CtLBPpA cBDCtjBOA #CwEttBOA CE.t~BOlA 4KCyKE*tBP A QEtBO2A iB-E AtBOA _BCF#tĒBO–A CUD>tĜBOA B&D9tĦBO/A ԛBȘDtBPA Bq'tچBOA C|EtڐBOA |iC1E;tBOA 5CEtBOƸA CEVtBOA ɤB D~tBPrA ^BwD tBOA |CEK[PtBOA CEdt&BOA aC `t0BOߜA CtNBOA CbDtXBPDA zBCtBOA ABh4kFtBOӮA B'FtBO#A B<>E]tBBOaA C $B3tBOA ƸBE%_tBOA k C-:E t BOA {C[EɨvBMk~AC@BN@C-vBMTAB+B+C+vdBMyACBpvxBM{jABB̷/EԿv*BMKIABBBDbavhBMOABBXw?BHjA {B9DLwgBH|mA zBvwqBHnA BשLEw{BHA dB%E)wBHQA IB^dD¬wBHG+A ˋBaBE*w BHNA ;BEԺw BHA B\E/wUBH0A |BaHwBHakA BϚ&D/wBHA @CYwBHpA BCw3BHXA BD.wGmBHmA 5C!DEwIuBHrA Bdw\IBHAPC"B`w\SBHAC!xLCwbBHlA mB!E)DwbBH{A tC%\EQ%wjBHAáC CwjmBHAC!xLCwjBHAC%C,A ewjBHA;CKChwk BHAC"C=wk!BHAC&c@pwl9BH]A bBΒowrQBHeA ɴBжwsBHUA BsDtwBHGA BDOwBHS$A )B!wBHQA IB^dD¬wBHA }B끑Dw?BHbA |;BMDwIBHA h3C&]EؿwSBHApC"OCRwÑBHGA BDOwyBHA"CC8Iw/BHA |Bd$YE^qwCBHAǝC"C*UxBH7@MIChsxBI@.bC8D]x(xBI&@T9CE,x(BI'@hC~WDOx)cBI@1CCR7Eox2BI@淺CVEv rx2BJ;@̩Cb EY2x9BG@zC,x9BG@C9E(x9BGq@lC؛Fdlbx9BI@_zCEִxABI!@B!WEZHxABI@UC3EPxABIc@;CxHvBI-@b2C E^HxHBI@WrC\Eu%yx_ABI@}CBHxdA DCF=BG ACu6SF 3?CBGABD/BGlA BUDBGA B@ E DBGA cC7ʸE].DBG}A vAEJBGfAYBnDJBGAi:B'D\NMBGCA CEnNWBGjA 2C B~UBGjA nCCãUBGA BC;EXU#BGA CvE XBG;A!CQE4lXBG EABB$EeXBG2nA#BߘE|[BGd=ABD`[\BGpTAB[#]BG|A KC]BGA Bv]BG|A YCDn^3BGGAu0B^GBGQA{B"CߍcyBGZAB3CwcBGڀA1bEfIBGeAJBfSBG#ABRdDb[f]BGv ABfhBG]ABEsO^BD]A*BEz%]BDoABE^BDsAPBڂE3:^hBD{A`CqzEUiBDӪ@AFCkoBDAlCEoBDACBDٹA}CD GHBDxAhC.F7RBDtAWCE߂BD AExB۠F^0ߌBDռA<~C{EfBDؠAxC&EȮpBDeA6CEzBDABɥEBDAA7SEw%BDBABfEzkBDՖAזC E4BDAuCV]~Fl5BD6@OCԹIFBDsA YC#LBDbAC >EѵBDEcA@BEƯBDKA} B9E1 BGFA ^BZ2BH@A B_BH( A /BBHA BJD1BHA {8B6DmBG.2@CFPUD#NBGv@BE'XBGK @]CzٲE BG@B[E}xVBFOA05CJLD$MBBG7@HC=DFCMLBG(@C6hC+MVBG5@B$FvbT6BGK @ECQ.|EBT@BGE@+7CnETJBGEBOiAYB0kE7%BOAYNB" Eng/BOAY[B1gEf(CBPAX;BD6EBP(AXcQA' BOAY^TB1UDVUBOAY\B2OEX‹_BO AY*BhBES'BGYA BP?/BGA {B7EBH[A BD'EBGA BZKDqEBGcA B E4IeBGA B\JDҔeBGKA iBѬE\gBH[A BD'gBG^A Bv{F tBGA gBՈEˠBGA B١qE[fBGA ĂBʈDDNSBGȐA )BED]BG6A cB EBHjA }BDhBH A B)BGƑA īB)yBKp@1B!Ea|BK@B$E݃BKғ@!B0"DT\#BKL@WAEf #BK@qA5Ec5#BK@B2E7$BL@pB:D&bBK@BMDh&BK@aBB &BK@CBDHDc"HBKܼ@u@>E[2BK@+BEj.`BK@BrC~`BKA@BjDN`BKk@B;DJ`@BK@DB-EP`JBKu@ BE,hBL'@a;B E=ڂhBL'a@uBEoBK;@AmPD{QoBK0@j#BxmD}pBKݧ@+BKBCqsBKޭ@ঞBDsBK@VB7KCHsBKѫ@(A/E6WsBK@-BGwBK@؛BJD=wBKӱ@ADQBK,@°AD,WBK@ADkVBLD@ZAH>DfRBKѫ@(A/E6WBL@BNBL@BVA90B EE@BN@BmD oBN@1BE;c6BNI@hBӤC @BOAZBEfE OKBOTAXB1BOLA+KBBE>|uBN@b(B|oC/qBNw@tBDÁBOAAnB*EÕBO6{ALBEWi?BO+tA5bBpEN.\SBO&*ABOExgBOAfBEUBO5ABCEUBO,AQB,EBBOCA4BkE"BNABWE.,BOeAJ{B\EJa^BN'@4BÎDBN@ˆB EêBN@tBUFYBOACF%*BOAr}BE4BOS AZkB_C 7(BOpIAYfBh0BOAPklBBBO!APQB]-BOMAPyBVBLAJC}C;&BLVcA#B4PA'BLVcA#B4PA' BLVcA#B4PA(BKACCoQBK{|ACpXBKKA$ CֶElYQBLVACEebBLVcA#B4PAqBLWACQDOtkBKНACiD]GBLAC)!C*BKACFCBKAɽCveD)BKACDkBLRACeF?iBLVcA#B4PABL0~ACnDHUBLAʺCF$qBKACE BKACM&EcBKA CkF0QBLhAXC7Dd[BLYACaBAfeBKA3C7BLACt~D0ABLdAC#HD*KBKA8C\RD҈UBKxANC7EZiBL AC/BLVcA#B4PA1BLAC{Du@SBLVcA#B4PABKAW:C)E;+BK ACʺF;BKA"CmFkBK•AvCְE.e$BKAC%F GBKA"CmFk BKACSECBLAJC}C;BL01AǸCD*6BLAC'-EHyBKAw[C`F:$BL@2BZLBL@طB+EWVBL@BE+9`BLC@׺BZDBL@)BEABLA@B-PBM@ AEpBL%@ԖyB'˞CdBL@هFB! DrHBLK@ _BCF(=BL@(B>8BL@մBDBwVBM@١BXDZ`BL@=AIEVjBL@؞IB!r@BLU@:B?,2C6 BL@eBiDo^BLt@iAB.>CBL@Ԋ+BytEjo"BMV@UAkC6BL@4#B -DPBL@BQ:yDBL@؞IB!r@.BMU@N%B xD0BL@.B ToEն0BL5@-A6D1BL@@B Ef1\BL@jHBD'2.BM @բ!A)GEDy5NBL@AE%5XBLc@٢B D}7 BL@ؤBGD !?BL@إB40mD6INBL@ٙBJBLP@!F?EJBLg@oAޗC` ]zBLG@B|D(]BL@)dBD]BL@BKDdl_nBL@IBC}_~BL@ZaBOϸE-_BLޏ@ԯB)SEV_BL@ݓByBa_BL@~ADf:BM <@մB m*CgjBM@B DlBL@l=B&ElBL@BM%EDlBL,@ BxDbflBL@cB@DpBM @ՋhASD?~BL@ yBđAmBL@`BDC BL@ԠkB׿CxBLɬ@Ծ,B%\D;dBLՍ@ԿB C>BL@ BVAkJBL@BGC1BL@BSD?BL@ [BNBL{@BۄH2BOa A!CM‘ERlHC0AEIG]BOA"2C/.F]BOǥA"FC$xE>zFBP8#A"0CFwBPAB-DwiTBPABD pBPA+B&fsBP|;A!#CA|)BPABeEABPAoBfEcEXBPHAXB#E/RBP A"CFAr"\BOzA"svB+F;HufBOA"CE\F E"BI1@C-CBI@-C BIl@kCZEq BIu@kCOqDBI%@SCDV@"eBINb@C{VF)cBIk@bCl6E,BI61@CHIKEm-BI!9@EClE4%-BI?s@C)E-BIH@CCQBI@'@Ct CYBIM@CiOE_KBIl@CF DzdPBI@C^)Cęk5BI%@YCsPBIS@ CwvCjsZBI6@;C&BI@CbDPBI:C@ZBGA@CV[Ej6dBGn@>CmEaBG@:tCC|*BG|P@_CmFD4BGe@VCErZBG@Cf+EhBGѮ@б*C{ZC͉rBG@:8CE)"BM\@ր6B:E56X BL9@)C]BM2@C&BpB 2ȑBL@;C5ȸBM3)@?A@ěBDeA#jCSDl0BDA$5C$E;V:BDl_A#UCER?BDA#D#FCA-ExBDA#/CM%F-cx%BDA#CحrF.~x/BDA#C6F4ɓBD`A#LCLDozɕBDA#CXSEɖBDQA#/CΙF_ɖBDA#CC F&ɖ%BDA#CFɤ5BDA$ wCUE{ɤBDA#=CF ɤBDA#ۚCE,FɤBD|$A#%CFaɥMBDRA#wCݸFUɬBD?A#C}EɭBD7A#uCӯBEnɭBDB3A# CXEtsɮ BEaA#HCEC{ɯCBD;A#e.CFĥɰBDmA#C_QFK$ɼBD A#zC耤D,ɼBDA#CXlE,sBE"A#IC%BDA$1C)D\L#BDA#DEN-BDrA#CίFfF%BQAFW%BQA BEF#%(BQA?BeEf%2BR gAYBK#F%BD-BQ5AAvSEK4B4BRWlA~AB/E3J8BRk2A2BDUh8BR_AYAEL?"BQZA{:AeDÒ@iBQښABAmFa@sBQAA-F8RABQAZA.ETJBR qAB"DJBR eAwB^][BQ΋ABGEc5]oBQZA{:AeDÒ]BQpAWAøzE ^NBQAA!E&7^XBR DABtFQx^bBQABrE _BQABE]`jBQ^AV BbBR A5AtCdfBREAAS@F5CdpBRe4AO#FJ?dzBR6ABiEgJBR[rA 3BDS^r:BQ]ABt^ERrDBQIABw6TF trNBQAب@Fb^vBQ`A%AN FovBQAB[ER΄BQz*ABֻE]B΄BQA@f-FO\΅TBQAA-F9:΅^BQAJAD΅hBQAKB}F0ΉBQZAB=E=YNΉBQWA tBkE ~΋BR.AuBtE΋BQAB΋BRAqB=nyEΌ BRvAkNBEsΌ*BR`AAEdSΌOBQeCABkEJΌwBQ_Aoly)EkΏJBR_AA,DE,ΏTBQ׿AˠBB݁.Ώ^BR)NAzATEΛBR]tA&A*E &ΠBQ߯A0BxUEΠBQ8AA-8DXΡ~BQAk@F58ΡBQOA!_BEEΩBQhTAwB^E:hδBQIAtBzE<ο]BR-AAoGE{οqBRA AE οBR'QAA,gE"BR7ABiU/EYs=>BRC`ABjDa,HBR) A sAǻF;RBREAUA E>BRjA|B9BQ_AFBZE,)tBRASAXz#DPBR3AUAwE^OZBR[|A?^EdBRIIA BEA߸BR-A:RAY"Fb"BRABLFhBRt2AB qDfBRHAńB DUBRCA)BʞF@:BQA/²gFlBRHA;BjF:2BQAF`BQA6BC:FABJSAQ+pC}E=@BJAQ'CE6BK'AORC@L BJAQĭD^ BJBAPɴCyFLy BKAPC쌫EՑ BK|APv3C}yEP"BKgAPrCvF,BJAQD KE5,BJ*AQ!D QEa/ BJ ARr6D56BJ6AQ D|F"B(OBJؕAQD%D,OBJAQ^D~fE DZBJAQCObE]LZBJ»AQ%Da[:BKdAPCRDv^BK'AOvCD@gBJ,AQD9cEo0BJƳAQchC_ED oBK'AOCբA! t&BKAPCME%,t0BKAP/CEYX{tBJ AR@bD 9EN4uBJWAQD`FP}BJARx D%xD)G}BJ"AR@DENs}BJARaD"ZDԣ}BJARE\D!E;ЊjBKcAP"[BGlAB}mE #KBGA@BrE,$wBG9A4B'yBGADqQZBHAvC5=DuZKZBHAC2>C^GBG]AqB~D jrBGAB?E`rBGȀABD tBGAB%tBGABbE%ZtBGAHBvMBGcAB DdžvWBG*AB2EG vBG=A{CE x_BG;ABaxiBGAhB+dE&axsBGAC E~BGKApDBс3BGA&BK7@CB_>DBK@ݨ-BVCݔY>lBK@B B?DT>BK[@܈zB4Cػ@HBKX@Bj D@RBKg@BcE:PVBK2 @ٓBɝC]P`BK'M@BE`W"BKT@cBC_BKs@-BaC!m BK@JBoEr BK`@ABXDHҍBK@B~D/wґdBKMQ@+rBD"ғ@BK5@QBc̐EғTBKNU@2BZEZҫBK@Y1BEC3ҬBKne@9BcYDfҬBKY@Bg[D1ҭBK@\BJұBK@:B 7ұ"BK@TlBDKҲ BK?@0!B\EVҷXBKi@v Bk~ER ҷbBKcL@~B,iC$ҷlBK_@ڣB7EJҷBKrR@eBE4ٝBK?@JBEdTBK@f~BRE:lRhBK=@ܫBi5DjEBK@RB}CI|BKC@~&BeFE7zBK8@Z BܗE-BKJ@B%BKJP@'BJ#EHBKON@NBrMDu:BK3@^kB֡D"ޒBKD@B2EBKU$@؂BEWBKB @5BحD_6BD4A+CCi"CyӘ}BD$A#C DGtXBG>@ܮ^JGS.BG1n@CpgDK1hBG+@CL%F[{1rBG/4@zCEɂ?BG"r@lC)D7BzBG@|CxF9>BBF'@C=)F/XBBF@MC\EBBG@ܪ D߽F{DBGaU@CeF DBG:*@uCF DBGG@{CEgEBG=@mC%E :EBGV@/CF>EBGu@ѳGC\EDFBG+@PCniEsFBGGs@"CpPF' FBG($@ԙ CoHEr<NxBF?@C\? BϾ0aBG@_D`cBGs@D$cBGo@C!cBG@߷Cb;F z|BG@%C9D /BG*@C[˅BG7@I-_F0ԀBG(o@CZԅZBG@CԌBF@JCREԝԌBF@ܢD ŰF6Ԓ*BGq@n2CDC!ԞPBGs@CeF ԞZBG@tCEʊԠBGF@nC`ԠBG[@$C}E<WԮhBGe@|C_EԮrBG@UCEJ?ԳBG~h@nCD;BG/)@/3C>EfBG"@٩>CE_5BG.@ CF=4BG(@eC6EmBG/+@.;C EBG/)@/3C>EfFBGze@ID ExPBG@CAEeZBG @۶CEBFG@;DD*#BF@ݙC 9E=BF@D&5BGo@C!BGBKB@ %BnD/ HBK=@B@bD#BLK@BC5 CBKS@8BWrDo"pBK@ۍBnWDnx#BK@qB=7C#LBKt@a2B:LC'J#VBKh@B_`E.#BK}@@B6ME,{G$BK@ sBEN-$BK@C)ED(BLK)@?C.6BK@}1BJD:BK@؇ BC<>BK@H`BcaEg̟>lBKz@,BtFLBKЉ@oBYDw{HBK1@:BsD]HBK@TB|D%TO\BK@ BOE&([BK̀@B;C[BK@-BB/&EmBK0@WBD@m BK@{BEhnBK/@fB5DPwBK@ B }BK @څuEaP܄BKE@TBQNE/I܄BK@۟BUR E܊0BKz@nBD7DÅ^܍ BK@פBuDuܡBK@BFܦEh!5ܡBK@B#=E'LܥBK@٩BdBK@ؾhBEK&+BKa@E¤skE1cBK@]C^pE[ŀBK@ܖxB5xDZ<hBK@+BLDl%CBK@NުE"BK1@شyBތwD~~,BK@كCEQ.BK͈@B9%^D:8BK\@ .B8wDDBK@AJS E;!BKK@ݝBMкD8/ BK5@'B8-Dµa:BK@ځB)DYDBKθ@s@BJD>VBK!@MB8BD BKN@,BBF4qA"ClCjݦBFEA!ACݨ;BFA!CxJD2YݨOBG[A!ChCʄݰQBGxA!CEoݰBFLA"[CeC֪ݰBFA#!8CdݰBF8eA"C^C͞aݲ'BFVJA"CKDUjݲEBF9A"CFABG A!CsCˎsBG A!C`mBE+A$VCUC21BGA!~@Ci{DlaBE)A#>CxRE#kBE A#CC*[BFgA"YC]FeBFA"V[CDuF oBFA!CwE\yBFA" HCd5DzBFA!CwE\BFA"|2CX`EBG^A -CFBGACcD1wT^BEup@C~ EѶ ބBE@rWCc-E+ތBF@ CsECތBF,@YCDBoތBF"@[C.vE;BFG@ܨ&DIDҳBF@D&5ӬBF @CJE200BE@ECŨ/C=BBF@CDƒYnBMB@~A7BCMAwBEcmEBCuAڶCeCEIBCA, B~ETBCAmBEBCAB5OE\¸BCыA `BDBCȣAC{ BC!A C9E~i BCAuBD* BCÃA.BEd BCARBᒱE6O BCA CW.E BCwA WCWEj BCMAYCwEQ?! BCBA{)B}DC`\BC[ABD!LBCACTCyQBCA/%CC EBQBCAC9nE WQBCABfrE,[zBCrAtCV!DjBCٌA~Bs`E5jBCCA#B EQk$BC!AoBEb~r@BCAeB^rTBCA1CUEvpr^BCA]gBzuBCSA 1CgCGJuBCABjE;vBC6ABEav BCAdDBE@0xqBCfAeBExBCAy;BgEV BCAP/BEsz BCABpEO9虠BCAKCD,BCņACs@UDH6BCA xC_E> BCACGAmEBCACIFY诼BCA|BپD0bBCABEBCAnCFE,%BESA^BCnBDkABVE.BCA_RB$ERBC AxRB"CP'BUA!@,BExKBTeAA3zD] BTAtRAD^싯BTUA S_hsBTAAٗBTA Ypj2BT2AACBUA ?DDk/OBTA2vB&oCnBTQAACEP|BTNAB\jBU/AQ#A|E`V`tBU A A$"F& BH1A uBߒo[BH/A Bk'E,}eBH"EA mBgxERoBH'A BLKDFN2BH6A BvFtcBH8A B]DBHEA B/D.BHBA ¸BDBH1A BߢD)EBH'FA BEWOBH/A Bk'E,}FBEA B.2ErBEAHBJC3'BEUA]|B$EV6\BEA8B=DDJsBEARBC%*BEMAjBB7]zBEAB{BEA-B^mEgBEpA½BʱE,rBE%A25BDBEDИBBEЛAC*QEr!_LBE˯AaBuVBEArB.+|FBEABAEA BEA BlE8BE_AB)EBEsA[BѶDeBEA*B!E`2_BFA BF{BE+AC QEQBE۴ATBe}EBEASB)&ENUBEA B̵E?BEA_BT{BE]ABBBE@ABJ@E BEA B+rEyBE/ABbEj2BEAB6FBEAXBDBEXA CCÃ?vBEA+iBPC=BEmABQD0l"DBEtABܬpBF7AB\BF8A CC4nBEpAqB3$C?Y\BEA"ADEbvBE%AB۠CEbBEcAEBȎVlBEڞAIB]DBL@TAl{E^BL@EAzEh$BLV@B3E7t<8BL@6OAIDv" BL@@XAzDϧBLA@MYBhsBL @h1†cE BLa@ӺAHDaBL_@?A#D(BL@|BsxC @BLB@cB'?/'$BL@بB eC/BL)@oXEK3BL@AID5XBL%@BbD5bBL|@B:N5lBL@BD 5vBL@5B @DHBL@6UAHEbNeOBLu@ˢB*C˘OkBL@:B=@ jBFBL~@)AE3ѶBL;@d,B ٘AҌBL @գB`BpBLF@dBEeFzBL@A?EAGՎBLR@B6DSEբBL@zxEBL@QA dCH$/BETA CкDd :BDAA ^CjEWz:BDE[A ACYnEˢPBEA tC4E`=9BMCJ@7NCkEL;BM+@+&CNB ;BM@CFΧ<BM)@ECugEM|BM)@UCp@E6MBM/@]C_kDNBL}@CLRBM"@(2C/fEUBL@XCC<'UBL@CƇiBMOW@߽C[ZEQ7iBMC@CM[DBi$BMK@CajEp:BME@ CxD8{BM9@\CYED{BM@BE<BM U@8CeFDg~BM @CDJBM z@yXCgE̍BM@CRE1EBM @>CKXEBM @CQEE/cBM @p)CWS&C $BM@NCJ)D.BM t@{VCXBM@$BE]BM#@敐C$EBhBM@4CDBM'@taCg"F#BMLk@zCG4xBM!@M8CEFBM R@ЖC6EKBM :@(C|ENDBM @}C\DUHNBM&@WC@bBM@WCBVE%SlBM@C&DcBM@LCqDxBMH@Z~CypEQBMH@Z~CypEQfBM" @smCkEypBM@#Cd-DMzBM:@._Ck#E BM#\@pqCE^+BM '@bCo}dD/ڎBM B@C^EDBM@C`E(PBM/@tCO7ZBMb@jCdBM1@C`LBM@RC2cCB]BM.@C3B!^BM@oCEvhBM0@sLCfmErBM"@^CEj*|BM@ C,DɾBL@ CCfBM@S@CFԆBM?@mC[ԐBM3@CXDԚBM@KCRF+9ԤBM9@QC]O'CԮBM1@겏CU@2D^ ԸBM9@C^{E !BL0@(hCPDBM,@*CM~EiNBMD@݈C;LF'XBM/<@FCVEBMR@xC<3EL|BM@CgE˷BM@l'C.DE&BM@fCEBM @CAEEn$BMq@9C>E;.BM @3C~CDbBM= @xC\wBM+@鯎CE-BM$@C~yESJBM@[CsE BL@C"BM@C}Ek,BMo@d@CUDETBM1@x C_EBM@BE@w BM@BkE4BM@嬂CL͑ &BN _A/ClDs2 GjBN:A pC0C XBNARRCT iBNA;dCd+Ci  BN8A C˘D(̳BEnwA#z8CǰCaBDA4dCXDBDA*CE BD7A+NCwEM 'BCA) C 1BCJA)1CضFAhBD*A5C*E8BDA5CEEHBDCPA5(XCZFlBFyHA"C)CB+BEA# CC@FBD-3A*DšF+BC_A-rDDG@?_BCA6kCܷNE 9BCIQA-\DECBCE[A-BDYE:BCEnA->DE_BCuIA"Do]BEZA#pC+EgBEkA#yKCяAEz,BCYA-p(DwD-BC2|A,CF ^-%BC(fA,FCvEh֣-/BCA,CxyE .[BCNA,HCrkD@.BC)QA,GCkE_VF.BCa(A'qCDE_-/}BBA4C !DBC A7'CqE'DBCA6 CEЮDBCA6ECӋFwEBDTZA*UCmLBC8A&D^LBC:rA'D oOBCA*&ECF0FO!BCzA'C>+FwSOBD&4A*+D F(OBCA)DzFOBD-A+ #D}F u[)BC?A(CC+ksBDA*CKFENkk}BCGA'V+CDE% mBDA4CF.anBC6A7DCHEꌫnBCA7wCEئQnBC A7LsCEْqYBCA6.CsERqcBCA6{|CfEqmBCA6TKCڙOF&M)r!BD0A4R-C$E4kr5BDvA3LCBrBCA7gCU%EJzrBC A7'CqE'rBD_/A4mCǞSErBD; A5/CFgͦrBDAaA5Cֹ5F[}FsMBCzA7gC.EOsBEA#t#C[=C sBDXA5zCǁ5F1K/tBCSA-o5DES/t)BC-xA,CDE`tBEA#kCE.xBE>A#TCVD#xBCA,2DLEdxBC0A-D<{DoY~BD-6A+0C][E*i_~)BD1OA+5C>7DaBFA#(PCyC19UBFA#&C6Cz9lBF.#A"nC/BE[A#DFBEA#xC$FT+BEahA#qOC7FIBEŜA#*CDNE.cBBݯA4C'Ds.BE1A#y!Cd\EkBE(A#CF5 BE5A#e=C4EBBA60C|FQBCA)5CeCh2KBD։A3CNBCA0CxBD `A*CcFZ]BC/A*rCC֎F[BDtA*}PC^WF!@BD.A4C_jF{"BDd A48CEBDA4iC(3EBE1A#CEar BE`A#oCzGFBEhA#C΍EkXBF A"DC!FBE%A#CrF& BF>A#CFi$CBEWA#CΠF DZBC]jA-\CNDRBCEA-YDE)BCURA-{CEw@BBA0;CxBD A3wCD1EBEA#CFVOBEA#ClFumBEA#_CFkȉBEXrA#{~CD&=BDUA*CrC^KBEA#tC9FgsBEvA#CFwV<BCA)NCF~ BC}A'CHFrBDFA+8fCCʐBCA)(fCѣFE|* BDEA5C1%EBCA6&AC{BC|A7CECBCZA7uC-FBB؛A5qC)F3]ZBDA*S0DFaBCOA'uC{]BCz^A*uCF.wgBCA,bDD7qBC|A(=6CFkBEA$CEDBCA*C0F,dX#BCA)kCoF+-BCAA))CIF [BCA,)D;D!BDU+A*C}BD8>A+CHEÙBE@C EnBDS@hCBD@@x}CXEaBD؊@QPBD@&xC *E8GPBDʅ@oMCmQ*BE^@CCǭ'CQ>BD@jC'E2;'XBD@PCgbdBD@ Cƪ^bxBD@|C|E[*&cBD@=CaHdBD@/:CpdF}dBDҏ@JCbE{d&BEI@BC EN@iBD@Cf5E-iBDu@ Cbm}DViBD@CC$EiBD@=CHEiBDֲ@CErEQrBD@3CxEvBE}@@CԻPEvBEk@{CӒEtvBE@CP#FwBE@kCD!ixBEf%@V CȖEM@4|BEz@BN@AF,IHBO@崦Bz1ElHBN@OBbhC|BN@?B}DYBN@tBD#BNF@?cBXsEUDBND@kBDBN3@ZBvE"BN@;B%E,BNK@ HBu3Ee6BN@HBE>@BN@2$BwTD|1bBNN@BTE^4BNlX@BDjalBN}@sBZˀBN@\xBDfU`BNb@A_F o~BN@B`DҧHBNLL@䟣B4D:RBNE|@OB\E6BO9@\oBKDBN@eBʭC$BNh*@ɫBDqBNE0@B@LBNn @8B`C'BFw[A6RDqCQBG _@ߣD!5BJ;A0CDy!_UBJ֓ABC.CT#=BHX[ACmEo.#yBHXpA NCIEí%#BFѪA!CͿA>#BF4A!CC>H#7WBHQA CD%#NmBGA!- CƇD6#NwBGچA CnE#NBGaFA >CR#PBGAhC/;#daBGMA C#kBHA!ACu#lEBHA dCٰ8F<#lOBGA CF#lYBG*A C3FEE#nBGA iCSxE}#oBH,zA LCEy#oBHDA CE##oBHX4A MCʕE]#pBGACEod#pBGA CvDɩ#r]BGoA HCsFv #rgBGoA RMCF #rqBGtA VVC>F%#gBGACEEY#qBGyAvCDh#iBH&A ѝC F #BGA C#BH( A!uCDb #ABH?A CE#KBHBA wCǂEyW#!BH9A ZCWFY4#+BGA HC½EF#BG+A! CC72#BGA *CσE##BGA rC^jFNC#ABGwA LC7Ei9#KBGюA ZCųE)#UBG{A CFA#?BGA @DF%~#IBG}tAyC4E2#SBGA iCSxE}#BHA! CuEg#BHA!vCEDP#BH-A C7E`#BHA! >CdEs#BHA! >CdEs#BH0 A %C EŚ#YBGA ˻Co?Eg#cBG A iPCE'#QBGA ]^CEw#ˁBGLA nCLsES#aBHdhA^CzD,#QBGbIA CߓF}#BH#A fCͦF%e#BGA QC_Cu#BG~A C8,BK?@!HCkgF b(,%BJ@yCk 5EC,)dBJi@MCS%F,.6BK9O@C2ҺC,.@BK)~@&C$Efp,.JBK5@CC͌E%,.TBK6Z@TC2D+,.^BK)j@"\C$E1vM,.hBK.@qCt?Evc,.rBK@vCzD(,.BK,@4iC/ݪE,.BK7d@#C:Cy3,@ BK@iC,=D|,@*BJ@C=Eʟ,@4BK@*jC4/E,@BK=@Cd?C,@BK@Cw4WCŨo,ABK@@@C5B',CBK'@S`C@2A,CBK@CIE'2 ,CBK u@E ,EBKz@CYDY,EBK@=C ]D ,EBK!$@C5Eg,RJBK@ÊC;ލE ,RTBK @DCqqmE~,VBK%@CBХ[,VBK5@lCC=,WBK"@].CLJ@,WBK@ávC,dEGe&,o,BK@CBDgu,rBK@[CL?D9,vvBJ]@nCE,{lBK@]=CCe,BJ@C[o0E,BK@}.C."C3,BK]@æC-ۦ,BK @CprE[, BKh@èRC-GICԅ?,BK@ÆC.qmC`?, BK@rCypE<,BK,\@ƥC D,2BK @RC_,BK,@_C7D W,BK!@C$DWa7,$BJ@ *CCE|,.BK@~CLGDU,BK#@C,D,BK @ČC,D,LBK@ C$J=AF,@BK@ðjC-dCb,JBKj@7CE,TBK@³CaE-,^BK@ C/JSD,hBKO@3,CaF:E?&,rBKQ@Þ7C6Cm,`BK7@enC5vC@,nBK2O@ƎC"IE,BKC@7C.ەE\,BK@C* D,BK@C- sEer,XBK@CcѩA:,BKH@]C,BK*j@%CBE ,BK @CHE,BKm@H!CGOEȞ,LBK8@|OC;D2 ,rBK@WCmEA,BKY@Ch@E-,BK@cC=-DoA,BK2O@ƎC"IE,BK<,@*xC!5bE_,BK=@Ce'E4b,4BK@cC/,>BK@CB`ED<,HBK@@ijCCB|*,BK@CJiD,BK0@rCIc@E*t,BK'@űIC(RDTT.BF&A CVyyCRe.NBGY @C+Dq&.BF#A Cy.BGA!C>_E..BG AC0^E.BG5WAVC fF.BGR{ABCBF".BGE.BG#AhC!aE{f.BGYqA4\C|E.ƖBF&A CWB.BF%A BC[.BG>AC Eb".BG;JA1CF&.$BG)AU+C&F.BG A@C0>Elɴ.BGA9C;EJ.BFLACLE.BFA.NCFVEQJq.(BFvACBEf`.BF#A QC°.rBG4UA7CE_.|BGEh 61BJe@^BxiD%461BK @BADxm62BK@`BtcEȫ62BJ!@b'Bz62$BJ@B62tBK @GUBrE8562~BKc@ZBcD62BJD@_AEe62BJ@DBo_EK62BK{@BA'3Ed62BJ@SBYDl62BJT@YBlzE9al63BJ:@C#B (E63 BK#)@䭑B̭Eg63BK@IBTC63BKFc@,BEEv64BJa@7CCoۉ64BK@CE(64BJ@BuEr 65BJ9@:BDC96BBJ@wCWF"6CbBJ]@C=EG6ClBJ@C6CBJD@`C7E~6DBJ@}C\C6L@BJ@C&^DJ6TBJP@FBÄyE?6VBK@IBTC6XBJ@,BzD]@6^BJ@|C^MF6^BKF@\Bi,CѾ6dBJA@%BۋdE#6eBK@BgdD16eBK-9@*Bh[E4j6z&BJ@tBU|Da=6z:BJ@^Bh6zDBJ@QBvDCdy6BK2R@䈹B*E6BJ@Beff6BK@BbTC6TBKD@2UBhxC6^BK@0B&EGC6hBK@5Bo=)E2P6rBK@PBl:E-6BJ@jCW6BJ@SBtdEy6BJΧ@@C N6BJ@Cb B36dBK@आBj6\BJm@xC)UEHp6BJL@fBRDf6BJ%@OBMC 6BJ @Bb$E %6|BK @Bht6BJ@ѬC .D7WN6BJ@{C*C6BJ@IBmCW6BJO@RBeB6&BJN@QB%D6BJ@9BD8c6$BJo@ўBEf6.BJ@B`yiE 6BKH'@B|6BK;@LBp7D6BJ-@LBCEt6BJ@ B†D6BJ@RB^ FI6BJ)@CDؤ6BJb@⌡BDv6`BJ@5MB,lC6jBJ@XBiD|6tBKU@-BA E6BJu@fCE~06BJ@zC6iE U6|BK"w@xIBgEԀ6BJ@B D6 BJ @ɳBND'6BJ:@k8B3DF6 BJ8@XqBisBDQ6BJܥ@BD/ik6$BJ@BD6BKi@zBgnD 6^BK@8`BE&s6rBK]@CBfDu6`BJn@Bt498CBBAC?8.BBA~C͑A+BBA-C~EzASBB`A-O/CE77ABDVA*CC0gA9BC,9A- DhC-ABC'A,D:A"BC A'JBYF 7A"BBvA)D]rGSTUA#BC-A%rCV:F8fA-%BCA,qDC9DBA-/BC A,sGCDєA-BBA-CG)FA-BBolA-_CpNFfKuA-BBlA-ilCҌF@>A.GBBܕA,٬COMF67A.QBBLA,CSTEVA.[BBA,CnEA>BBZA,CEЪA>BBjA, CK;EߌA>BB A,}HCŧE=ɟAB!BBY^A-rCқ{C)AEBDU4A*`CC9zAEBD:A+CEd`AFBBzA-dC=D=ALBCW2A'D FmbAO!BClA'X,C9EAOBDSA*ClBٜAOBCyeA(8OC"F{APBB#A,ϾCFAYBB'xA-nCAYBBiA-mCϞD`A[)BCA()C9FWAxBCA,CE^AxBBA,CnEAxBCA,CEA{BDVA*wCŁA|BDUiA*ACCA|LBCA#d:DD(҈A}MBCgA'CkDgA}WBCaC7JA BCO A&g[CևABCA(CE4Fb`ABChA'eD%P2FQABC7A'hCᶍFGAIBBMA,C EO=ASBB%A,,CE&L!A]BBA,C'F ABBٞA0CʌD,ABDA*ICކF%AgBB5A,2C D}AqBCA(]C(MDA#BC+A)C̀'CIABCmA'CEA'BCxA'CkEdAUBB.A.CkFYA_BC`A0YCABBFA-$D#FCASBCjA'C[fE 2A]BC}jA'2C٘F )aABC8A&D^ABBA,CоFFABBA-2.CѽEWABBA,CE^AQBB A,CHEdA[BCA,udCsEIHLB@@nk G$ LB@,@CFǰLB@@ C_F!EILZB@A@RC.rF8QLFBA.@;C&"CľLB@ @~:BTF&L$B@@C&Fq&L.B@@BC }F[L8B@@gC@VFLBB@s,@C|F LLB@k@vC(EqLVB@kC@CjF,L`B@d@XCFLjB@@CF ELtB@4>@CgFKL~B@ m@JCELB@z@C;FLB@i@kKCE%]LB?@*oCDLB?@C}ELB@5@%ÉBF|LB@"8@mCoE4LB@n@CVzEdLB?@yCoE*ZLB?@]CF&8nLB?@C̯En"!LB?|@CELB?@hCLE\4LB?@CvE)9LB@2@JbCAFH*LB@L{@CٷFjSgL B@>@\C[F 6:L*B@H@ CF,L4B@K@C9KF,uuL>B@mX@M|C51,E=GLHB@a@`CB?ELRB@E@ CjFL\B@B@Ca/FTLB@Z@CEnLB@Y@CZELB@9@RÁFHLB@ 5@ӘCF>LB@" @CFGI LB??@CmFLB?N@]C "FJ L B?@C_GFL"B?c@vChL"B?@tCO}F(L"B?T@KB2FuL&nB?@CQE‰L&xB?@WCREUL&B?@Cj8EdmL'B>@LCuEL'B?@iCܗDL'B?;@BC]"Ei':L(B?p@%CF jL(B?F@~C#F!L(B?@C~:]F|L(B@ 1@C:IFAL(B?g@tDFL(B@y@5C0F(L(B?@DF=L)B?o@ CF L) B?\@.D^FaL)B?}@CPE^L) B?{@CEdL)zB?@ CFL-B@Qf@CiF4L-B@9@C6˝F)لL-B@<@B)FuܧL-B@e@k0CzEÅ?L-B@@CqEwL/B@@:jDaF2L/$B?z@C# Ft}L/.B@ b@ BãEHSL1,B?@&DiF0L1@B@ @BBU\F IL2vB?@:CiFK-L2B?M@C^ƫE L2B?0G@&Ceh@b Ch0E+L2B>@d|Cj SEo L2B>-@@@CC-;oF.L3 B>@C D7L3B?@}CtNDPcL3 B?@6aCkEL3*B?@1D7NFL34B@'@{LÏzFL48B@s@$C2RL6B?s@HC!EtDL7DBA@C(^L84B@ACFjL8>B@ZA*C?ENL8HB@AӺC~F%#LADB@?@GC_rE-LANB@<@PBH+FS5LAXB@>L@C`ELLGB@@C&aELGB@@YD%DFF#LHB@@C6'C= LHB@@tC&Fc^LH.B?r@C'ELH8B?r@C'ELHBB?A@~C[q'LHLB?@C@C}E1LHVB?K@ECdqELH`B?@2C"sFTYLHjB?}K@BC`EyLHtB?fR@ԷC>FLHB@4@LCCvELHB@%}@C;EwLHB@3@aCZTEs,LHB@@!lC-Ea% LHB@י@C/SEqLJrB@S6@`;FLJ|B@b^@BePELJB@`@8B}F/5FLM)B?@#&C܂B@LSB@1'@DŽBlFLSB?@(CFi&LSB@ }@iD![F5LXPB@!A4CELXZB@r@CoE7LXdB@p@JnCF߸LXxB@<@\Cs@C2JE-,LbB@@bC5ELc1B@mc@]CS! FxLd+B@@lC:sEyMLeRB@@3{C`FLefB?V@{B`F*LfB@j@\CTFLfB@m0@xC-FѳLfB@ACtF?Lh"B?t@p Cƍ ELh6B@]@C2;0F$2Lh@B@J@ZCz.F?6LhJB@i@C(\F12LmB@@]dCdF}KLmB?@CHFGLmB@ @"uQ6FLnlB@N@TL:F^Lo*B@;@X$FmmLqB? A#CmE:LqB?m@ CeE'LqB?͹@&CGLqB?ä@CI7Cc Ls&B?@DpGFLs0B?B@0CX@$Fv=Ls:B>@Cp\KF7fLsB@@CFYLtB@AAQ$CɟBsLtB@JAcC˞DoLtB@ÚA_}C|{DpLxlB?@ CF LxvB?I@sCELoLyB?@E1CtE& LyB@ @]C̿OFLyB@+@ CFLzB?@CrFuZiL{B?c@6Ct_F/uL{(B?@+D$FL{2B?u@C2fF!L|BA"z@&C6KLB@@CE}vLB?@GCCFfLB?&@xHCOE|JLB?@CE3 HLB?۝@!5C'TF9MnLB@@CzEGLB?@+C]Fw(NLB@)@X:BVF?)LB@;}@4C&"qFmYLB@7@]]C<IFi9tLB?@CEKFSgLhB@K(@yCiFbZLrB@K@>Bn9FQ$L|B@\u@ǚAmF/LlB?d@ CFTLvB?j~@BCZ;DoLB?f@1eC[E,wLB@8@CNQFLB@(@%0CfܾF#LB@Y@)CFEڱLB@@CɿMFGLB@@C5ZF[LB?(@TCF<LB?@C_F LB?@B8 F#L&B?`@DCbyEL0B?@$BMFCyL:B@,@AP FLHB@@M;C-XEH'LB>@CdeEteUL(B@'@:C,EL2B@@C[ELLB?)@>CX%DĤjLB@@NJC FoLB@@SCGQLDB@@PCF@LXB?»@cyCbD)L4B?(@yC3F<L>B?l@CUFLHB?@oCF bLB@s@LoCFNtLB@V@;AkF5zeLB@g@+CIFL4B@@OC\F.pL>B@@B+C##jFNLHBAI@CL\B?@XCt)F <LfB@*@{UC:NFuLpB@w@]~CDLzB@ j@ BE<LB@$@C=VDC8LB@@NC89DVLB@ F@4BxDiF"vLB@@|D@F WLZB@r@&?Cw{LdB@@RCh\E-LB?@CEsL B?@XBrF _LB?@xC jE$L(B?`@nC DL2B?@#CUE L"B?:@CNF:L,B?j@`2C޽ EL˾B@@_FriLB@HR@C6PFN=LϺB@ @!C=7F1LB@!@|9CXE^LB@#@C7FL B@@9[CFiaxLB@b@0C:EÌ;L(B?@9CblnFLdB?2@&BG{iLnB>b@PxC6ELxB>@32CgsELРB@ALCxF>TLЪB@AVCFKLLдB@~#@C=\E LоB@t^@ZCcF>:LB?h@C,LB?϶@'KCrYFTLB?t@3CuoFLB?p@C F9wLB?@wCF&LB?E@rCTE@wLB?@KC0DPLמB?Ǫ@qC?FRùLרB?n@mD }{F6 LB@ @ٙC$EBLB@@KCR ELB@?@!CYzFWLB@;@[~C4'FZULfB@K@1C~FFMILpB@`H@xCLxF2LzB@T@Ck5FUL؄B@w@C@CO9jE%\ L؎B@5!@CFpLؘB@wy@1wCVF DLۤB?@CF2p LۮB?@C-FHL۸B@1@+8&ZF̛LB@V@@{Fu=LB?@COE*L B?-@CYF5LB?@CELB?ڽ@fC;E̛LB?վ@CFL$B@ /@aCFLPB?&9@(BlFDLZB?$@VC8{FLdB?W@9Bh\ELB?m@Cf5ErTL"B? k@:CkVD+QLXB>Y@VC>ELbB@,X@ CtmF$>LlB@X@REFBLvB@GE@JCFE1LVB>@C D7LB?8 @JECbEh<@LB?y@CrF«LB@@ABIFRLB?@CKE\LB?@DER_LB?#@nCEWyLB@2@oCF9]LB@)a@CFLB@6n@CEL+L:B@+_@CB0LDB@ 4@QCKCYLnB?@{CYFLxB?3@BH FRLB?@4C)FOGLB?B@U0CZY@LTB?B@CxE-LhB?@CiRDHLB?@HC FF<L(B@a/@FChF=!L2B@H@|C:FA6LhL@B@BAC!xFL:B@5@Ç$FLDB?@CIFZLRB@S@bJC6F[L\B@;@ CDLVB?B@DFML`B?@CE5LjB?f@ Cú[F<LB@>@DCy#TF)LB@ @C?oFT<LB@@CyF16TBM$@ CAGDoTBNT.@Bf+TBN$@&BD_TBN{@BBN5*@fBE{8\T,HBN(H@^BDT,RBN76@JBzEJT9BMr@솈C9T:bBMն@\CE0ZT:lBMg@n CVEooT<8BN.@2B;,E>|$T>BN{@}HBf~E)[T>BN 4@B ET@zBMտ@CDNzT@BM@B;DUT@BN>@EBxD%TVBM@ C#}E1<TVBMg@d CE9!TWBM2@NCMEB/T[BMw@4C%QAcT[(BM@.C&`D T\BM@Bm@D*T\BM@C ETalBNI@@9B EɆTaBN5@;GBãTTaBN#~@tB,F}tTaBM@C];ENTaBM@'CE@ TcBM@fC%VE UdTgBN'@B_XDTgBN.@3JB~E$`TgBN- @5BTgBN @=BEB~TkBM@C/CTkBMh@y'BCC~Tm~BND<@POB]E`TmBN4@BNk @2-B6DDTBN&J@BDKTBN^@S!C EL=TBN4'@EBSE̜T\BM@]{BݿCqT&BNf@BEVT0BNJ@BDO<T:BN_@B@2D4TDBN)*@BDEOTXBN7@BmE}STBNFV@xB‚E >T BN\@B[C%TT*BN @FBjE4TBN@.EB`%DT6BM@#CES9T@BM@peC PEpkTJBM@BnmEEKkT|BMui@C(+CcTBNk@BpsDT|BN@HC;E| TBM@?C+=)ERTBM:@C77E6XTBN@BUEaTBM@"C?E;T BN@CET*BM@ CEN)T4BNA@CRCTBMu@"CDTBM @CEj2TPBNQN@BxDCATZBN/j@CH pETdBN*@mBYD|vTnBN8y@NBENTxBN(8@BqC*@TBNO@B}fDȼTBN@dBzDҞ_BF~A7D:Eo_BFDA7COD_BF0A7C9_;BFA8+C@3hBK@\C/Fh BJ@%WB9`Fh BJm@KCNEhh$BK^@Bj@ D$5h$BK/@nBD4dh%BK,@B~}DJh%BK'H@_BQD΁h%&BK.@BDD'׸h%:BK0@@&EVdh%DBJ@BENh(BK]J@BEh.BKE'@,BEh2VBJ@C 0h2BK@IBTCh2BJ@B3FEh2BJ3@xC F4h2BJ@=CkEͧh2BK/@DC 4E'h2BKB @B0D霡h3(BK@.oB<Ezh32BJC@ BEݏh3@@ChvBJ@C&]E,h{bBJy@C~FlhBK+@B[#D%hBK@EAԁ[DhBK0'@EB+hBK?@MB*E~"hBK@@BGY^E4hBK,o@&`BNiEh"BKGi@Bt E8hBKW7@*B/ChhBKN@B?c+E%oh*BK9@1BbD )h4BKX@@pBEh>BK @澮BEIh^BK@BR,hBK.@p=BeEAhBK6B@RB5E4hBKD@[B;EhrBJ @jBEFh|BJ@M!C=EhBJM@"BE:Ch,BK%B@pBRWD47 hBK_/@BlEhz>hBKR@濭BE֋hBKM$@àB[ExnhBJ@BdžE hBJ_@C E¾hBJ@CDhBK@lBF;hJBK@_BQ1E*$hTBK#&@BVyDhBK+@bcB`E6hBJL@BFhBJY@筕CHKMEhBJW@9Cr%E5hHBK@BEhRBK&@mB-Eoh|BJ@B#DohBK%@榜B7oEҧhBK*+@2BwE mh8BK\@BUCPh@BJY@VC pXE>hJBJ@;?~F&hTBJ@B`bEcYh^BJ@̭C …Dg:hrBJ/@羾@RE\phXBJ4@IVCيE*֗hlBJ@ CdWYEhBJ@cBۆEݼhBJ@泑CC@E:hBJ@E& hBJ@|B̰EvhBK+@h_B|EV?bhBK/L@FBD+Mh(BJ<@MjC0DA^RFo$hBK@pGC_Enh.BJZ@C0mD>BL@ A BLG@ B|j$BL@A/A 1*BL@BsbpBLe@:!B1DBL_@CBD;/1BV=A!SAjwaC(v{BV6A !#B! 7BM Ak'C CC.BJANCZE#/BM}Af#CbE YDCBMA C;EJBK,{ACMDSBMAClԇE/TBJ:AiCWBM~lARGCpE~5XBMOA8C=EY[BMRkACi"E?YoBMBAwC&E@cBJIAoCDVdBJIA]Cd}BJASCDiiBMAl?CEmqBM(ACw|=BJAp+CE7G}_BJA4xCDJ}iBMeA6C]!FWGBMiAMCO;BMAFoCicHEBMlACVE5BMXzAVCq'BMtcAICE2wBM[AC.F9ΧBMAACȄEA~XBMAC3F7BM8AuC3EBMA2CCo]µBM;ACD2I¿BMgACeE#BMlACVE5āBMPACE5]BJ/AnC]EBM>AΗCJ{BM=A=CEExhBMTA3CbE^BMkAkCxE BMFpAaCۥEBMEAC٪EBM@ZAtEcBM#@ҢAaDABM@UA̴TEn BM@ӟGBqE%XBM@ӪBW6E%bBM@ACD&/"BM@]AOD1΁3|BMM@=B?E!3BM?@ B#rEQ3BMV@BRE}B5zBMq@^%AEt=BM@wBtsDtYBM@qAYBM~@ҫAw/E|`6BM@B3|Dg0vBM@ZgAElvBM6@ӔA>UEvBM@ӀzBEAI{BM!@҃AħD$pNfBM2@I*A֤cEpBMĐ@6|A#DseBMW@aBbBM@ՆB4C"|BM@kB4BMx@ AjDsV(BME@AD?(2BM@ZzB [`E4WPBM-@HB =EUEBM@ӌBcDx(BME@A7CvBM@BJx@CjDHBK V@&C5EXBKe@CrxD 5BK@!C.E RBK@HC*DBK.@4C)D6BK1@]C?AduBK G@LCXBK9@ŲCJE;BK@«xCD6D0BK @ٺC8~bEgBK8@ŕgC37C֫BHq@SCGmmBIQ@?'CbGE.wBI/@CKEպBIL@CuFjBI?d@\CWBHi@oCzEΐWBHU@CEBHG@WC.Csg-BI]@CdCAfC_7BISy@m CgZCʞ@_ABI @C'nEk/_KBI@DOC EwdPBI@ꘜC8 CkIBIH1@ C)ZEDlWBHv@;CRDPOpBHy@CsPBIw@XxCHuBI@3yCOF,NuBH@iClVD'uBI @CĊG_w4BIn@> CD\hkBI@.C DuBH@CbCFyBI/@7CBI/@>Cw`D&RBI@LCm`DBH@CоD^BI@fCf@bl BIR@NCbE-BIE5@{C<0E9BH@2C5{EnBIa@lC:C4BIj@BCߌz BI@SCEgBH@^CpD,BI@ CiDMBIw@`C\BIn@C~,EcBN@@ B<qDBNF@BIɠD=BBN@@ B<qDdBNFW@ۅBPoLD".@BN;@1BoLBNЮ@ʽB'qE5A^~BN@BTEg5_BN3@wBCE!\aBN:@hBD DAfaBN7@nA'D'jBN;@1BoBN8@UApçE &BN*z@|BCCG*BN;@1BoBN8,@ASB'+DbbBNG@OBG?E"&BNH@wB1ID|BN;@!B:f BN:D@߽B+xDWξBN@BTYHBNH@BNRBN;@1Bo܌BN;@!B:f>BN7@ލZBLZDHBN3@ݗBw@E=S &BN6@݉xBDblDxQ^BN@fBYCϣBN;@1BoBN<+@h B4ZBNG@:&BYDzpBN?@[B?+DbNBNG@OBG?E"hBO`@cBE9KBOl@遴BCA BOQW@YBEBP @Bx^E+)BO@BzEGBO1@BmsEfBON@[VB_rEBOG@BL PBP#[@UBPDaMBP@B;gvE/BP.@c,Bb+0BO@B)y0BO@BvmE%1BP@mB~E[1BP @BvE-bBO@PBuER*\BO@DBkmREhfBO:@=BfC>BO@lRBWDBO9@qOBfDCOBOg@/B;ECEBJA#ZCCx5BJKA~CpE9BJgACɛDLYBJA-CC%LcBJA.pCF'LBJڽACETRBJlACDjRBJA*CE3 RBJA.=CsYEdSCBJ*AnCz]Ef=.SMBJ^AP"C@ESBJACE}SBJǪA=CdFT BJAOCTDŲTBJ7A{|CZBJ@A˜CC__UBJ ACTDG+nBJ ADChRnBJ?%AkhC8E!`u]BJAwC uqBJ~ AUCDABJZA!wC )BJAJC69BJACöBJgACOFBJQACEBJ>A/CCBJA׼CDBJbACi+EAjBJ)A>CEdBJ AeCTE7BJxA;CmF!BJAuCFT'BJA-C EzBJ AҢCjEBJACEBJrA7CٺBERMASBB zBETA™BCDt jBEfA`B̗C"BEfA_uEU BEo>AB:D~BEAsJBulEY3BESAB[E4.BEJA SBD@GBEjABME?GBEfA $|EGBEjABEIKBEiHAA0EYBEj&AbB}q]BErqABΎV^BEaBEj AIBqE+HBEgKARB#DKeBEzABB!DBEAt(BRBRVBE`AXB'F`BE9AlCu3F4jBERWABEBEiAHB#D%gBEtABÚmEIBF(A CU1BEAt?B܊Dw߂BDA#CuBF9MA RC&aBNBEnAzBmE3BE#AtBEJHBEdApBHE]BEAvBQB+BEOAwBgB!BE8nA gBD$BE[nABDbBK@CB*7&En BK@TBbDlnBK@B;C7!lBK0@'B0>w!BK@wBC!BKE@4B Cu!BK@B5D!BK@,"BBWCf!BK@vBe2EE[C!BK@߀B CI !BKW@B^En#8BKw@'Bg?}#jBK}@HE8#tBKne@9BcYDf$BKx&@aBrnC2$BKs@BOD|$BKf^@6BzD|82$BKT)@ B$ Dy^$BKm@B^%BKp@RA`C%BK@8B;XDR&BK@l$B.^E{9&BK@?Bv(D&BK@ὰB_&BK@&B&9C,'BK@Bz!C/(VBK|@%B2BK#@BZagEA2BK@>BvE{dJ2BK:D@mCz%FP_:BK@PfP^Et^:BK@B;C7@BKj@BqXxE!CBK&@ߊBWELCBKo@gB\hEH~HBK@iBW?IBK[@߼BCyCnbPBKT@CBWWEtnPBKr)@/B|^LE ][WBK@@|B JEW"BK@`1B8Ez[nBK0@:AC0iBKR@BȴjPBKp@3B}zDEjZBKy@BqoBK6@&WBT(E) oBKu@ ET0EoBK@mB:D~EբsBK@4EB vD9IzBKI@ BEsn&{BK@NBeaEŜ{ BK@;BhEBK@PB̝*EBK@B`EQNBKc@&B9aDd BK@㈘BnBKk@/BgΔCBK@ަNAnE~\hBKE@æB+GzC[|BK@KE"<BK@9AܽEL BK@?‚E;BK@្B{E9jBKd@kjB8EiI-tBK@;BFF,~BK9@J7B$Eh8BK@yB:}DBK@߂/BSFeE{RBK^@CUEh\BK@B YD&tBK(@3_BzQEk~BK@߁7BE`vBKP@'ADBK@߱CzC;BK&@ߟB.DNZ1 BK'@@BC}E(BK@nB56CE6wBK@vBCFBD@CMQCwdBDu@C]KE\ĤBD/@C,]E%!ĤBD6@?CeCZĤBD@ClpDՄīfBDь@FCI9ClBC(ACm%IBT^ANBHE?IBT0AN:CrF94jIBTkANRBSmAEVABTpANGBKDVKBTmVANFBsiDbr BK5AP#CBFԔA .C #E.BFsA HBcTBFAA BuBFAB#,D1BAmA"!D(~FrBA~A!CFc#BAA!)CF[QBA(A!CMC BArA D 7D:}BA]rA!_CLD1 B@A" D 5BAZA CE^;BA2A!sC9EEBAA!CĂFcBAA!sCqD"OmBAA!1CEBA|GA!oC F)BAA C=qBA4A!D!EZBA.A!D*cED9BAA!DFOe<B@A!HD%E^B@A!D/BACA!CًEVh?yBAA$CxA'BAMA!UC EVA;B@8A!RDEC\AEB@A!iDEZAOBAA!ŽDoFH3CaBASA!wCBEWDBA sA!ĀDvEEBAZA CE^E#BAU:A!gUDrEjvE-BA`A [ CEBAA"CcFMlBABA!}DXTtBAA"/\DTl0FGT~BA`A [ CTBATA uC1TBAZA CpE^B@A"D E^B@A"D/Eb^B@ȽA"DB$E$ _BA[RA!g:C+E_BAHA!@D!FU_BAVA!uDSLEI`BA9A!:DCD"`BA#HA!bCFIE>fBAEuA!wiDjDg>oSBAPA!CGE妽χFB@A!DXEc4χPB@A!pD !mF;ϘCB@A!D&DNϡBAA!f6CEvϡBAoA!n CVF )ϡBAA!hC[XE٨Ϩ[BAOA!eC,FqϨeB@LA!D SxF5ϨoB@cA!D yCFgϲeBAyA!TC-D#ϵBA?cA!LD E0϶BAJA!$CErBAA!vkChEH0BAk1A BDF,:BAA C=q!BAA!ҕD;C]MzBAA!aClE!BM @ٽBԁD ~BL@فBsDBL@d1BmOPDiMBM'@ىBn_E;VBM @ٔjBBM<@ٴBypDqBM I@ٷBLcD()BM@B DL)BM '@բBBz5BL@ؙNB=zGCALmD76572BB7A!D< EG8BAA!s@CU`>BAA OD.xEa>%BAA1D&pE>BAA C=q?BAA C=C%?BAA CC+(@-BAzA!WCESWMlBBPA\&D:F MvBB0AD/~CMSBA A!DS BAxAuDhUBAAC+dUBAABD.E;[hBB A?D[EO_wBBAq$D'Ekrh_BB AD,KQD@ _BBA9D2NF#`BAFhٵ|BBAe0D(ǹF ^ٵBAEA!usDLoBAAWDFiƃBAAҵD!EȂBAA$CHBAHA DzF"xBAHADE BA ARD-fF;#zsBAA:DBBBA? D7lCQ!BA7A!dDD(IBAUA"KCSDOBAԼA Di'BAfAcD3]F"(BAәAJD0E^ lzBAA!CjEVBKą@bB\HBK@5BDY4 HBKI@EB7C BK@ծ/AEO BK@8AEe*!BKY@=BdPBn!BK@knB@BD!BK>@BTNCw!BK@`BT=D"pBK@BiDha#BK@r2B6FETD#.BK.@ݱBYuEն#8BK@B5EU#BBK&@MBDDңJ#BK@`B^E$BKÃ@+TBDo%BK@1BwҢEa%BKZ@ԫBgE=%BKM@޺BKE&BK@ݿBuCD (BK@+Bm}D[)FBK|@bBEl$)ZBK'@թBEc=BKH@+4B E*=ZBKˇ@܂^B1D=dBKɀ@B:iD6=nBKB@&gB'C>BKx@؂JB:@>BKx@؂JB:@>&BKO@.B0BK0@ZBLJ>:BK@UWB>EEPO;>DBK@ܓ^B~UE >NBK@BPC>XBKW@?"BiEg܈HBK @C ’IE<HBK@uBrE-HBK@ɨB|EL!IBKT@= BfDIBK@`B1JBKE@6Bs!DC?_BK@8AE'`,BK0@ݔBVEDfBK+@OFBE gBK@IFBxE gBK@,AeD̮m BK@BDDnBK@OBbEarBK@,B}=DuwBK@ݙsBpD]wBK@2B~MDs wBK+@ݞBgC yBK@!ARDwyBK|@fBEnA7)}BK;@ڙ&BGDm3BK5@xBA?C &BK@ݦ$BD *BKt@ܷ(BB1DBK(@B,6EylBK7@޼BBM\BK"@YBABK!@DॴBKt@TBE.;@BK4@޽BvBK|@޿B2hD7dBKH@ cBDELBK@@BCהDy(BK@+BDo2Z2BK@BoD7VBK@B%jEvmFBKa@#B4DUBKP@AɸRBK@B^zBKC@ߋAZYE-#<JBK@޿lBcD%RBK@UBBgD4BK@&B<mDBK,@ߴBvD8BK@߯"C(D+BK>@߇HBfDBKH@_B(xCZBKC@HB;3DݐBK@B5bSBZ BKy@ިB\AQBK@B>hCz,BK@(ZB_DD BKDz@߮BbpDDiBK@YBEDBK1@һA#%D8BK@ބ$Bj$D.NBKQ@>BknC.6'B>@Cs6B>@C6B>Z@%CE XxB> @_ChD]C{B>B@_C؏{@aCғTAs$x{FB>@ZCîC (XB>@^C#ZB>R@amC>|B>:@a_CWLB>:@a_CWLB>@CD:B>@C!Du(B>@VWC5E7 BGxNA C6DABGmA CE;KBGu*A ,CKDrUBG}A AFBmEBFA QCHCkBGgrA!nBzCUBGlABDyBGAB E$BGA^CjFGBG[AIB?HEF#BGA BnZ~E#BGzA -/C"E-#BGmA "jBVEi$mBG~ABD$wBG^A,BE(&BG|A CA+E0&BGyA CbC)D\5&BG}A }Cs)wBG@A BE:u)BGAu}C%E)BGA SB5BGFA BDw.6BGO7A zCEyEZ{6 BGG$A JBD>6[BG!A W_BwFz]6eBGAA 'C@zN=;BG| A 9BWSD =EBGxA 0QC%xD =OBGiUA BEf=BFABF=FD/BGgJA B㕜CDBGrABtChBGBABmGBGuA n[C&0E_*m[BGx#A 3C)Dkq BF>A BϭENotBG>A BvMBGuAB BBF-A =B%E>'BFA OBIEmBGvA lBËELxBGwA 3C nD!BGnABŏDBGpAEBGg:ACDoBGpA|EےyBGgeA96C& FҗBG~SAC$0Edz+BGzA )BmC5BGz.A *B CYSFBFA zBڿ CPRoZBGA BǣEBGA ʞB7DBGAB%uBGy;A C0xE(BGttA C9\DIfBGrA oCk[EgZBGOA CES>BGKA oCEK%BG.AjB FjABGnA Y8C0ES4iBGêA QByEsBGA BxyDqBGA RB4DBGA ʸBDDBGA B "E?EBF.A BǙEm>?BFPA B3E1a IBG A BܡE@BG8DA FB1BGTA "C BGA FBBGjA ƋBE93kBG)A B:De?BGAqBu EXJBGwABFEBGXA%Cg\F"WYBGv$A s%COCBFA #B(|D}-BGBA]Br)E6BG3ZAKCyE BG)ABxEBGYYA<>B(FBG OA BaBGhA BE_kBG A TB D=mBGpA CPŅFwBGe[A C2E0΁BGUA A~cF@ӕBG|A ìCQD/OkBGA B "E?EBGRA5.BՉBGACBABGAhBBG'ABEABG53A /B}BGGA CBFA8BtD  7BJ!A O^Cl2sBIA /_CbpqFu2}BIkA *C?Eq2BI~A J C,f%4BImA LCoED4 BI$A -CfGFz4BIA ;C0fyD *4gBIA ʧCVE.c4qBIoA C?bE3~W4{BIA CNCNHgBJjA e4CC?Er" LBJ8ACgCwRIBJ[?A 2CyENR]BJIA lCwERgBJnxA FCSDdR{BJ\A Cw8Fm>RBJ AC ElRBJIAsCtE:6SMBJAzCHPEcGBIA aC'jBJJA ĶCCYFjBJrPA JCC#JCGxmoBJ3AmCBJ|ALlC&EBtuBJeA CYF yLBJiA#C3DBJq ACyF6BJBACW>E^9BJ\A \Ci`ECBJ8A Ce}dEMBJVA &CzEBJs6A nC[DBJ:A C`gaEwBIA 0CD BIA =C7w B?@ CG@L B@0@CJuCon B?@ȜCOE ZB@R@ GCE- .B@U@3C3~5 `B@qR@kC B?Q@uC>WFDW B?@_CJCw] B? @C B@M@CH B?@ԗC8s B?j@CD  B?}@rVCdD^< B@@c&CF  B?@%CEi' B? @ɱCP{E B@L@kCSVF B@`@.-C:DO >B@]!@CS RB@:+@CD?Cǰ \B@@dCIN B@2@CgFP< B?q@pCje4Db B?@C֔F+ B?s@>CFԸ B?.@UCE$< "B?@D7sF "B?Y@BCFUc "B?@CN^F &nB?@@EC`D4e &xB?x@Csf$D@L &B?S@ C' (B@x@nwCuF? (B@@"CDE> (B?@goFC\ (B?@7CFD%CJF )B?@CNJFkY ) B?4@CFE ) B?_@lxCB )zB?@CiEm!G -B@L@Q}CJCf /B@5@ CfqBT /$B?v@cCf~Ej! /.B@<%@C 1,B@$P@$CE( 1@B@/H@S+CN 2vB?e@ QC\# 2B?e@^ClFE 2B?&@Cd 2B>8@NCfD8| 2B>œ@Cf& 3*B?(@CmA@ 34B?@=CDi 6B?@~Cp]C  84B@ACgQEo 8HB@A&CȾ ADB@2@AiC?D) HVB?S@ &CO2rCp H`B?:@KCwFVI HtB?@sC>5@ HB@AAaC.5 SB?3@8Cn SB?q@bMC_ED XPB@uA \C&F XZB@h@QCڏEC XdB@h @C@Eb ZB@(@ACGE8 ZB@3C@k6C6)C@ _B@AASCCC$a c1B@v@4?C= efB?@ECOCF  fB@}A^C~ mB?@55BE mB?s@CoE nlB@F@C=  o*B@k@aCH&E sB?@>8C yB?@wC|ZD̬ yB?@uCZE z yB?@4+CCE# zB?@CHFDs! {B?@*CKB֭ {(B?@Cs6E {2B?@CLD B?4@DCnNoF~̔ B@o@CM,lE9 B?@CrsC^ vB?t@C[hC` B@t@<Cb#&B) B@@MCCf4| B?@I|CL}E  B?@ӖC'T%E &B?@~CE- 0B?@CoEU) :B?r@* F.% 2B@@tCJCQ B?@VCgC~ B@i@C? B@r@&^Cw B?@ CvDx B?@f&CH4Eqy B?ޓ@jCcxDk B@ A2CHqD \B?@C;E_ B?@&?C_/E"J B@!ACᑁEj03 B@AtC1F( B@AC(FJ9 B?lj@C^Fx B?4@CmFof B?G@H5C/F0/ B@u@dC^F  B?)@KC-Fa(2 DB?p@mCF0 XB?@(C#SE/ 4B?D@5;CCF >B?@eC EB HB?@rxCX{Em2 B@b@GCσClA B@A`%C 4B@s@&C{|CC RB?@YCgE* \B?@!CC\F0 fB@'@+C>b7F} B@ @:tC=p! ZB@_@@CqE dB@@OCF r B?߅@Cj@FD# B?Y@ChEEY) B?@'C.8F (B?@CD E} 2B?4@2B0mF.7 B?j@CpLEX "B@@VCF, ,B@@C2F ̮B?<@CsC ̸B@0@/CQuwDP= bB@ϡ@nC$ lB@R @C(Fcը ͔B@W@mCF;qCp ͞B?@XC,TF5 ͨB?OY@NCfHdE2D:.Z (B?f@MhCEGx dB?@=CnFӅ xB>@CeD_C  РB@0AxoC  ЪB@ADCh0B дB@u@#CLE؀ оB@o_@xC@Dc B?M@B"HF4T B?e\@*C[C מB? @OkCLC רB?@ B?@C+ B?@CC B@ @C B@@UC{ fB@~[@PC9ۙE> pB@[@C@kF ;_ ۤB?@HQC ۮB?׏@BwSE- B@8L@7CLC, B?@CA 8 B? @`C{ B?@C` DN $B?@"CtPD|z7 PB?>M@CVl ZB?C]@= CTV dB?AZ@.CVGAB+ lB@0@jCE< vB@18@sC>D1 LB>@zCDU B?@(CF nB?k@C&F| xB?@~ËFɗ B?;@]C4F| B>@(CWZET B>@wCWAE B>@CgE TB?G@CmSFH$ ^B?@CwFE hB?#R@gCTF5E*  (B@n@]CPEI @C1 `B?@dTC:E jB?@CFu~ B@+@yCDǣC B@@`Ci B@@-Cd EmBGPAB&4C&0BGACCFDuBGA9BDðDBGtA@CjtE rBGAzCF~Dm,xBGAC g%DRxBGABC$)#BGABeD BG{ACLDݱ7BGAB_DKaMBGA ~B3CnCBWBGA C~CܞBGAA8CBoBGAaC7Dv)BGAC{C_%!BGd@BrCĒ%@CQCk^ZBBAChWE;eZBBACqoE~ZBB}0ACڲF XTZLBB:A|BE{Z`BBAGYCZ2BBZw@C d]F"^ZCd9=BEA1mtCRA~tBE_A43CODnBEA1>C8BEցA1CDBBF A4EC]D%BEA3@CEEBEA2)CBExA3<wBFA4C4D7 BEA4CDBEA4xClD5BEA3+CEFZuBEA4oC}{E4WBFfA5ͼC&YBEA3C6?EF&cBEZA3CE+N&mBEA3CE.BE&A3FvCԱEԪE?BEA1oC1UBڅE]BEշA1CCB:G BEFA0CAVdM#BExA1C,EɦWBFOA5vCÆ,DXcBF+A5CEq]BEֳA1C@`3g BEA1mwCANpBFhA5hC\OE){BEvA2CΒD^{BE4A1CD-}kBFWA5 C׬\E6D}BFS@A5Cؠ;F~QBEA3E@kBEA3KwC2EEBE~A3C^BEDA3?CKCBFUA5CDz]BFA6QCWF!)gBFQA5̎CF,i3BEA2.CC!=BEoA20iC)C£BEρA23CBENA2 C%D%POBEΐA26ACB]cBE?A23CB!>'uBF-A58CPF#-BE'A4CEwj҉BF A4fC"MDچBEϡA2/CC٥BEWA1CD?ٹBEA1x C.C"BEA1y`C/CoBEA1CEA$ E*7BR?ABoFldfBRյAI AEV dzBR}AoBogJBRzA᭿RFeBRA}=B}BRAAUGEBRVA^4B 9E$BRյAI AEV  BR%ABAtEBR0ABTF'^& BRBA7AMEk}>BROAB8R߸BRkAsBB,GBRmA7AEq>HBSA#BEAdBOA(B0CᠦB>A(DV[B>A& DKcdB>A%ޓDAy B>A%HsD0E2M /B>A%9]D/E:` 9B>CA%8D,7E CB?VA%D2 F U B?A&DRFF B?DA'$DmE B>A&>4DOE B>A&0DT E;UC B>ӯA%0DFהF )B>A%D(Ec)+B>A%+D.D)5B>A&FDF)SB>A%LDFaEFs>B>A%9D,!H>B>A&;)DPhCDAB>A'DbXFAB>PA%DBF2AB>A'FDXXAB>A%DO E;AB>dA&z!DZhBB>A%7D& 3EpcB B>A'7DhE`B?B>A'FDXXCB>?A'DYBD,C%B>A'>D[ tEuvC/B>A'Dj>ȄL?B?AA%ADUEF(hLIB>߱A&/DNF,LSB>IA%4D0yEL]B>ؚA%:D,`EOLgB>A%D)ELqB>A%+D.DeLÒqB>A%qD7&DökB?_A%=D.@D [B? ]A'-DWB>A%IVD,#EB>hA&0D[RB>A%{D7tF7bB>A%GD1EB?0pA$ZDAFj$B@A$MD4EANQB?tA!+zD7EE @B@A$5D'B@X?A"/2Dm1B@J{A"4_DE;E;B@UA"$DEB?A!D.F#i,B@ćA!DPB@"A! C B@A!DeE B@{A"D,F0B@7A!6D FaB@A!D NF6 B@]A" nDTFNB@]LA"CDAF`\!B@kA"CVF.+B@A"2D&{F9)IB@ A"?*D%E SB@SgB?A"~DQYEyqB@A"DFFv{B?A#DD@B?A"DZE>B?A"DKE2leB? A#MD%B?A#D!UDtB?A"DEP. aB@Z]A"-ZD01"iB@A%1gD-F[k"}B?KA#pDn3F B*B@ HA&+D>LF @+B@A$DMF:j+oB?A!_DvF0$M+yB?wA!tDY^F5+B?JA!aD)E .hB?IA#7D Fh/&B?A#'D&NnF5H/0B?A#x?DNER3B? A!uDF<3B?A!GDtnFtC_IBDAtB\6CYBC_ANB.E#bBCABqGE9BDTA!Bk|DgX^BDA"C ChBDoACOCjrBDAC @B`|BD/RABSE3BDAvB5EaBDA{B.IEqiBCoA{C\@E mrBCA:B EL#?m|BCAUCDEmBCAUCXGElr,BD*1A_BF(r6BCAB@GD Ɖr@BC\ABcErJBCAueBvrTBCAxBE0zGBD_ABKBEbzQBD AWBܬz[BD*2ABмF/΋BDA^BEM%΋BC(AC"D΋BDA}$CLQDΥBCAuBu>DΩrBDArCsBΩ|BCArB:^ΩBDAwBBs RBDABcEV\BD AguBBJBDuAC CzBCATC|EL BDA{B]DCBkSBBtAC`FwU BBȆACnyEUBBAC?9EKUBBAk CwE\UBB&AeC|EڥUBBAECE3UBB8AgzC:E-LUBB A8CDEZ7VBBA&BIE+$VBBEA3CeF=V BCA5C}E/=VBBA#`CR)EX;VBBA+gCrE!E~XBBjA= CBEYABBPA>CtEw7YKBBASCDEaYUBBACjEYBBIAȕCPeEnYBBATCBYBBAlC\EXYBC`A}CCOYBBNA CeEYBBACVEWYBBACgQD|[!BB#AC{k#EE[5BBA8C0NE%[SBBAiCFFsa0BBWApCE iBBA$ClkEiBBAopCyE"jBB=AJNCxEKj5BBtA^3C3nOBBMA[C'o BBAޮCtkFProBB-A7 CE8Lo!BBAaCyDoBBAЛCRE dpkBBAlCoE*t>pBBIACDvoB@eA9;C?IӧBCjAcGCi DFxWBC AK)CnAnBB\ACTELfBB%ACsDȎBBACCBBQA=ClrB-j9BBAANlCdCvBBACEZBB2ACCέ/BCACgC/nCBB&ADCCsV3BBADCZE[BB:A!C5zE>eBB^A CE7ȖBBӛAC|xC ȠBB~AٟC~PFfȪBBŁA\3CCMYլBB_BBA8C'iEoBBiACqBBACp!DY/BBAm;CjNC[BBaAlCԣPE*BBACXE .BBACDX*BQYA%BID BB`AgCD` BBڨAߕCE BB.ACeE!y  BBLA]CDE +BBּAHC3 5BB%A.CY Ep -BBA-C .SBBլAKCQEƹ JBBAACoDE JBBͼAa=CC QBBAJ"CDn []BBALCl/D [gBB݃Ao/CDFE7 [qBBrAbCE-H [BBA'CD' [BBA/CwDev [BBڒAPC~aEh, [BBqA[CԺDT [BBAC F.! [BBrAC>E${ ȠBBAC%D_) BBACDX /BBfAIfD' _BB&AC  BBQA(D7|E}b  BBQ(AkD0gEuC  BBXAKDC6 ZBBmAAD* dBB cA2D;F BB8A@D DZú BByAyDmDr BB! AۈD>0E(Ey BBA%D<]D낯 s BBBAADz.CN s@BB@LA"3D?Dn sA BBaA#_D<'D< sGBB4A!DJCT sGBBA!]hC׽EH sLBCDA&gCF%  sLBC5A)D;G 3 sLBC&A%4Dt"Ff sLBC]A#5UDD[ sOBAA!zC sOBAA!YCEg! sOBCAA':;CD sTtBAA!a`C덟D s[)BC9bA&vDCzp s^iBB;hA"CΈDV soSBA A!YDF5 sBBA#iD -Ft s5BBGA#CEs{ s?BAPA'I*C}/ sBB,A#D sBBGA#D1E} sBB+A#^D E4 sBBXA#DEdT s BBHA#PDXEp s4BBqyA#4jD /Ec sHBB|A#IQDDF  sBC[zA&pCC + sABB!A!CE8 sBC)A%DF\F sBB%A#D ˅ s!BCA#D%-FMA sIBBUA"TD,A7-y sSBBFA"a3DE{ }BAgA"pRCJ }BB%`A!|CC zD70 }BBA#}DK C. }53BBACלF;' }5BBkA"AC)F }5BAGA!>D l }6-BBSA;D.DI }6BAA#nC-E }@BBGA"jBDE }U BB(ACҏ }^iBBEA"]LD#E }pBB>A" pDD(j }tBB,IA! C9zD?? }BBMA"#D }BBVeA"ۻDeCw }ABBO A"eD }IBB1A"|2C>FX  }SBB!A!PC{Fy SB?J6A4SD BJ@B BCAzCx/E 'BCJA|CD BC8A2C9  BCJXAC2lDߎ SBC6AC*D' {BC(A~C/E BC/AgC;Ei| BC1ACUD˪ BC%4ACb E- BC5A.C2D' BC:A_CVCJ BC';ACnEq BC0wAoCƍE 9BBKACT CBCSApCw MBC+ACpZE7 WBC-AChtE  aBC3ACD{EY BC4AAgCr-D3v BC$ACbsu BCgAClEf BC'ACFEvP  BC'^ADC~ =BCAClP BCA(C&E, BCEATC}žE;ep #BCrA*Cf\:E YBC A>CoE cBBAIC{EFp mBCAnCE1LY BCGZALGC'cDc BC 1AHCj> BC,ACoE)ح BCEA3{CvzD#X BCBgAuCDDz ) BC*ACqE  -mBC*AC]E`& -wBC.A`C'3@E) 7BCApC 86BC=ACD? 8@BC4AKCn>D0x 8JBC>ACw DBBAeC|D: EBCAAC!C FBCC~ACE F(BC@AεC%C9 GSBC@AUCD G]BC4AeCt$D TBCLACJD-I TBCIAwCD UBC1AgC]D& UBC.A Cp0ES8= UBC3 ACOIVD VBC$AsCiBD VBCBACCtn W/BCCAC{#DRr WCBCL8ACADW WMBCIUAo ׹'BC!ACh9D1* ׹mBCDAqC=D(@ ׹BCBAC#Ca ׻BCAA֌CD7R ׻BC40AcMCzD8  ׿+BCApCE- ׿BC$ArCk)E ׿BC%+ACnYC&  /BCACE 9BCsA{;C6Cr CBB@A~ C~eDa BC0A:zCmQCV BC2mAKCkDyO ;BC,UA4CuRE8 OBC.AoCm BCOAZCyEK  BCA&CEEe BCTAb$CE=Ŕ BC A8CpE> %BC^ACE /BC ADCGEC BCArCxCj _BCFAFLCD^C iBCDAC84D\ BC4A CxD 'BC1bACvDS ;BC BCgACE ᶉBCA}uCBV >BC AC|4gDŠ HBC.AC+$DVz> RBCKAȳCsDW BCAϪCDx BCAnCBD !BC;ACP+ED CBBACL ‡BC.ACen ¯BCvACD BCACӝD  BC{AACsCI: {BCAC1D1 _BCcAC_A]` iBC7AC'DO qBC%ACcDDqE {BCARPCA BBAACq!CW BC+FACGD* BC/!A>C|EZ BCA%C0 EN  ;BC'AHCkIET+ EBCTACEa OBC*&A?CLES  BCAC7E'2P +BCA3C3D# 5BCZACD ?BCAīCD IBCACD SBCACp`Dܹ[ ]BCfA Cg$E0<~ {BCANCD BCA*C@.D % BCAӥC[D BCA C+uDW BCZA%CdE BC%aACk VD$ BC"ACb^C BC"AC~ @" QBBA;FC ]BCArClD gBCACxZE z qBCACD {BC ACQސDg BCACrDӋ BCeAѼC\QD  BCA)C&GD BCAQCvD BC(AC BCMABCcC' BCAIJC>C? BC)ACC, BC,FACUDN BC(ACxTDmM 9BBCAAuCQ BCACkBɭA BC*AoBE'S BC4rAzC"  BC!.AC D yBCA@CwjD`[ BC.AWCpDP BCBACwoDtV/ BC!lACjD` BCA3tCC/ 7BBAz5CCُ BCSA>,CsCz$ mBCA[C}MCC (BC()ACivbD  -wBC*A ZCs 8BCACs0OBr IQBC$ACyڤD I[BC VAChD! VBC ACjME9 V!BCpAHC@ZC\U VSBCAqCsC VBC-*A!C|D!D VBC*AC~D9Y WBC&A C\[E  WBC%ACD* YBCA/C{# eBC,A.CDr eBBAC6PC f9BC4!AX CogC#* v3BC*ADCkC |BC(8A@CxcE1 BC&MAhCC: BC,ABCAsCkC HBC,A IBCyAӉCwHiDw1 BC%A;CqPD2 BCvAkCrSD? BC!#ACjc5D$d- BC%ACl} ]BCACl&CGCo {BCACimD; BCvACp A'< iBC'+ACiPCzZT BAEA9nCڸFC3 ꅵBAA9nC ]*BQWYA$BwDd y*BQx7A!-BRT BC]AžCXED  BCLAC$C% iBCAqCA6 7 sBBAokCX1 BCA*5CC3  BCAgCyHC H BCA4CHE7p; BCACs )BCA"CIE>߃ 3BCZA C:}%Ea^ GBCdAQCTE( QBCAeC EI [BCbAC*EG;j eBCACЕEշ yBC,A!CDk BCA$C,E BC@AϯCYF< BCqAYCD BCAAlCgAD5: BC,sA|CQEG BC`3AWCJE-J BC]5ACEl BCAACdAC_ BC)ATxC5F BCDVACeCE #BCCsA߾Cj.A/f &BCÕACD0: 'ABCAPCEX 'KBC0ACoE F )BCACs/DA" ,BC;}A*C|E:: ,BC&AAYC^EA^n ,BC#AQCEG ,BC}A,PCߓNC;|D ,BCA*5CC3 >MBC,ArCtDP ?fBCrAqIC'EA8m ?zBCyaACϞ BBC@OACheEB FBCGARCBEЂ RvBCgAC7EE RBC`ACDf RBC`ACDf RBCFAXCD RBC7A:CpD7? SRBC+A6C JE"q TBCHAIC`E) UBCWA+xC޺ WkBC@#ACgC5 WuBCWACDf YBCApC0 YBC;;AeCDq YBC"AVCE(x+ Z1BCqgAUCx D` Z;BCnA)6C0tD- lBC-AC[(E„ l BC,AMCEƬ lBC3gACQED l)BC?AGC1 l3BC.fAXCEg lBCA|C"EO lBC7A:CpD7? lBCAk CEee lBCfACdGDN lBCIA@CD lBCAOCukGD<3i q)BCAC`E- q3BCCʚE! /BBAqCӨDL /BBACEza? /BBkACpEcCw /BBACE{4 /CBBAB$CQEA /BBhAzZChEs /BBAyCDZEt1C /BBACEx| /)BBACM|D /BBACĂ- /'_BBBA]C / /'BBACE# /'BBA 3C4DR" /-BBsADCZ /.5BBdA\C> /53BBAiCѡER /8BBeAC`~D /8BBA63CuE1 /8BB9ACQE /ABBA&C(Exr /ABBA/Cn%fEŜ /ABBAr|CEDɢ /ABBAwCEg- /SBBAuC /TBBAD`E /TBBAGD2ݥG0| /U BBACD /VBBAACB* /ZBBA`C-E /ZBBA!CՔD? /ZBBARChqE1M/ /ZBBHAOC{E/d /ZBBAGCU? /[+BBAяCzWD /_BBBA]C / /iBBAӦCƌD7 /n;BBbACOCH /nEBBACyEf, /nOBB2ACΝ(Ft /oBBÌA CsJDtv /QBBtAZC֮D" /PBBtAZC֮D" /kBBeACܴE /BBACD%j /BBNAEC0! /BBACEw{ /BBA3oC E /ȠBBSACxZDE /BBtAZC֮D" /BBIACgE( /BBAPChE /BBArCE1j /BBACĂ- j*BQFA#:BAI>6 GBGAjBDZO BGUAA]EM BGA;zBBuE -ABGABkE # vBGwA_%BJE[y BGWA9BC'  BGABE(j BG}A>BCV ӳBG@AB`#D B?TfAKfCE  BBJAբC#Eʣ 9BBAZCiX BCAC BCA C=DX BBQAnCVE0 BCQAdCDP 7BBpArCs KBBACE}~ UBBACE _BBAMCF iBB!A%KC*D BBjAFC ?E_K BBAvCQE] BBACD1= BCA9CAܱ 'BBACɵ 1BBANCE'| 5BBAFC9D 4 SBBACgB BBAD2BEl BCAzCWE OBBAxC6E  (BBACqCOQ ,BBQAPSCܣ?B .BBAClJ FBBAPC_D4Vi NBBAFC}/E NBC.AaCqD( VBBAvC} ]Bs WBBAJNC= Dè WBB7Ai8D{Eq WBByA=C'EZ WBBOA CC WBBAKCbCDJ X BB{AC҂ADp  XBB AYCtE> XBBGACU~ET XBBAC/FG XBBAH C!Dv XBBA(D F$U XBBACrD^) XBBIAYqCüyE_ XBBA]C` XBB}A;C'.EP; XBBۖAg_CzKE`= XBBAUC\C|} ]BBNAJD Ee| ^BB`A:CE0 ^BBAOCbEл0 _ BBAC$E _BC+ACFr bBBACD3g bBB7ACQE( cBBA+?CEI m-BBAwChD1 nBBA C#E|C nBBsACE ' oBBAClQE_ qQBC AC9 qeBB|A2Cj*D qBBACD|  qBBߨAdC*EZe qBB,A)RCp"E5 qBBAD LOFG qBCAvCACEq BB"AC1Ea BBOAˆC E BBoAC BBA C\A* $BBȵALC'LE %BBAC %BBƩA;C|BQ 53BBAsC"o FBBtAC PFd FBBA}C,E WBBcAC C+1 XBB8A>CvE4Z XBBOA(\CErx ]BBAEC  ^BBўA"Cz[D  _ BBذA#CъD[ bBBiAC̤CQ jBBIACE% j!BB^ArCE/0 j5BBȴAEK% oBBA{IC~Eq oBBAIJC:E( tqBBȳATUCEkW t{BBA0 CDfC tBBlACD tBBAVC7Ep1# tBBA%CDنM ӱ9BBAM;CC BBvA!jCo լBBƍAoCE BB0AC'cED5 BBZAC}E0w BBAўC`ETQ BBχAUD[Ea BB.A5CQD 3BBAeCBEI QBByAEC?Ad BBA4CDD QBBAC eBBA?|C9E&D LB@V@eC?ERXpxRBKA38EGAoBGA91C+DAyBGA9C۱D5ABGOA9CABGz A8 C E׶EABGe6A8DF{ABGA8CFABBGPA9CC7ABBGaA9C E1ABGA8cD'E1 AeBH8A:BC;E`AoBH>BA:xCRExMAyBHH9A:(+CպE8AABHq-A;/D FNkABH]A:ݟCEqzABGA8CENABG>BA7D CCABGc]A8YD9EqAEBGA8vDXE?+ABGA9gC֥EABGA9ZC7EAYBHgA;'DEVAcBHcA;CE&AmBHXGA:GC冢EʝA7BH%A9\CņD#AUBHtA;sC`D+A[BH0MA:(C䜴FAeBH(A9CE*ABGHA8CFcE@ABG@A8qCNEA9BGtA9CE\}ABG-A9}Cט}D$ABGA9kCOE A BHhA:C}F8ABG؝A9C$EĄA3BH}A9qC/FAABGA9zCͣFABGA9oCTD*ABG`A9CCABHTA:CQZFi4ABH"A9 ChDQABGeA9C F6vAaBGA9*C&NCAuBGA9CFGE#UABH&A9gCEABH A9CEMaABH6A9CDޢABGoHA8GDE:{KjBIEAFR KBHДA_ACҝ:FU[{B>ACm`AoB>ACӯErAB>i(ADh.G) rKB>~ADXF B>۬AC䳺CT~tBONA A(BҎVLB@^ @xC:yX-BC"AVRCaEY:BCבAC~PBCA#wC}%D-lTBCA"Cy-T#BC+AWC|ٵCƍT7BCA+@CDdTKBCAC}EtdBC+AWC|ٵCƍdBC9A5C7E;7k/BCAC>lE]k9BC3AjCyZD;GkCBCפACTD4 BC A`C8EiV BC+AWC|ٵCƍBCACD-V{'BCApCE )BCAiXCfEہKBCҪA%CUBCA#wC}%D-lBCtACJE#+8BCAb8CEcqBC|AC7CͰ$BCtACJE#+8-BCA=CD>֞IBCA5CD8SRBC~DJ>@BJ΀@ACEtABJ@@C$EהC0BJ@MIC.YENDBJl@3]CvENNBJ@ݛCC 0nOBJ@xC;dOBJ?@?C&EϺxOBJi@jC ĝEE&P`BKP@3BCVBJu_@C9SEVBJ@C%PEWBJt@qCBBK@,B`EBJ-@ަC&EI3BJ@ݗ|C͖Fm$BK@U}BEBK5@rDByEBJ@ܬC-CEBJB@RC2BJ@ܑC NEaBK@ڃB xFiBK8F@n BؗE`ddBL @C{E@dnBLy@I!C2ExdxBLy@I!C2ExdBL@f@CBO\dBL@@zCC=E^FdBL_Y@>SCt;E"dBLI@FC7mEdBL @ B@DJdBLX@aAkDld$BL@kAhD+d$JBLB@CE+d$TBL:@aC'E% d$^BLB1@dCM D̝ d$hBLD@oCSE BL?@@MBa1Dfd3^BL+F@@B:E:2d3hBL n@jB*E/d3rBL*@BB@CD)"d6BLyt@A4C Exd6BL@XC%d9@BL@XC%dWBLB@⺏Cd"Ecd`BL-@nBMDIdb\BLR@DB6EPYdbpBLV@E(B{E8dhBLS@CfvDudhBLc@uCaD<dh8BL`[@XqCpvEFdhBBLm@Ci=E"dhLBL+@BXE^dhVBL@uBa Pdh`BL*@LB/QD>gdhjBL.@BDqUdhBL3@_@BwJdhBL@\B8CdhBL`@%Cj{EsdhBLKK@]\Cm:E9$dhBLP+@ʹCBwhEdiBLT@CwEqZdi BLV@ C58FBlwdiBLW\@{CcEdm\BL9\@CpE@H.dnBL>@aAD Hd|BBL5@p'A];ddNBLO@㲤C=E(d:BL~@{CY<dLBL@A1>REidVBL@eB2E;dBLP@NBlDjdBLF@GBjDedBL8@}B'E8dBL4@QC~ER2HdBL@cEBG0Ed]dBL+{@BEt DdBL~P@vCY dBLXh@;CEYdBL@dBIk@Eb҃dBL$@CyD.sdZBLJ@LB/DzdBLXd@KZBhDGdBLt@BwdBL-@B%;D1Nd BLr@KB<D{RdBL@bC;`kEdBL'@_AcEWd݂BLi!@'B}EQodNBL@eBZFdZBL6@pBxE)!dnBL7I@ᅫCEdBL@p[B}OEddBL@`B,EqdBLF@GB B?4AWD3U7@ >B?`A%WD']qG>B>vA&6DF%U~>B?TA!mDvGAB>A%oD6C7BB?>A$AD-E->B B>A&D[KCBAA ,DGrCB?hA(}DBVFCB?A%PD1LEJEiB?A#D'EwEB?nA#AD Q EGFB@9A#sDRG B@9A#sDRL?B>A%m|D1LIB>AA%D5F]D% L]B>A%D2.DneqB?}?A tD!}B@A'DqB?@A$DFfF^{B?)A%2D:2"FzfkB?dA%8D(ڄB?OA$D/E=ڎB?<|A$ߨD,"EeڢB?9A$D,RLB>A&FDF`B?%AND.YE{B?>IA0D)fB?A%D1-EB?e^A$xD19EM'B>AaDDGk>'2B>݃AYD 6EPס'7AWCNE~'FB>%AyCC E]'yB>pAtrACEԻ'B>AC\Ey'B>CA_CgE@e!'B>QACb7F'B>>fADFȩ'B>AC_E s'B>AD/fFV'B>%A)DeF'B>A~ACE:'B>7ACzE~c'B>YADJFװ/'B>ȉA5Cu C٧' B>AQICzC֕'B>tA/fCE'B>A4C6F'EB>AݻC EE|b)'cB>uACq-mEŵF'mB>AaCyEbg5'B>@1AXDsrtF'B>A~C~E1'B>,A"Dfa Fl'B=ACoA%A'B>{AZCafEa'B>AsC2Ev-'B=AC( Edư'5B>_{A;mCɵEW'?B>)AɌC)0F'IB>=A1lC|+G$'SB>`A*CjE.Am']B>WACqrEE}'gB>^ASCE/'B>CAAC4RE'B>RAxCDc'!B>U'ADG.'!B>AmD XE8'!B?wA+dDD_Fg'"B>} A/CGí'"B>mACߠAM?'"B>TAjzD' '#wB?+AbD Cv_'#B?Ai_CXEpM'#B?'AnD XEq7'*B>mAuCҽTF?'*B>E AD)OFG0'*B>EACͽE\ ',B>eAAYCE?',B? AD%C]',B>fA5CFLf',"B>ЌAQ0CD',}B>AwD yEE',B>tA_DEm'3gB>A>D,'Av$CaFR'3B>KAoCVE'3B>+oA`CHF '3B>LA8CEtm'3B=OA4C&%'3B=A/CäDu%'4B>A3D '5 B?G:AD {E%P'85B>\IAlCQEƅM'8?B>=A|CFK!'8IB>kACAE3'8SB>NAD Em<'8]B>KAπCҨ!D'8gB=AGDbE v'8qB>?AnSCrFI'8{B>HAC,^E-'8B>,Ai5CV)F'8B?$VA? CFK'8B?ZAEsCF/'8B?!ADE;ot'>B>PA]D%CH'?B?ARCE'?B>SA;hCWF'?B?)AjDEמ'D B? AD)3GEs'DB=LACnFY6'DB?A۞D D'EsB?9A,DNF 'E}B?H"A^DgF2'EB?&AD5E'EB?sA1CF<'EB? ADF'JB>AXC'MB>"Ab|CD! 'P1B>,ACEi'TB>AC {E_-'TB>2ALD*F 'TB>^AsCץE5:'TB>״A2fC+EW@'TB>́A`CxoANEDKJF?'Y~B>-ADdUFL'YB>pA CzG/#'YB?wA+dDD_Fg'[B>AӐCE=w'[ B>SA ChF'[B>AQC&E0wX'[{B>~AcCEXB'[B>ҥAhCE'[B>AfCfF,'[B>:ACkF'_B>AC))E,j'`B>ACrEoŖ'c~B>AηCӟD׀'ngB?AC߸Ef'n{B?MAԪCE6E'oHB?^7ACC8'oB>ACÃE q'oB>:1ADF'pB>HAbC&E`'pB>ADC9Em'pB>%A)^C C>'pB>Aj*ChD'rAB>|QAACBDA]'B>VA+|CNJE1'B>ھAI/D : Ej'"B>E4ATCnjF:',B>rEAr;C-E'OB>AA@F'mB>%A+cD F'B>ACFc']B>8AfC:EB'gB>\AC혴F''qB>;A߫CdFl'B>"ACE'B>A2DmFxt'B>eAzLDFl2'B>ACԙpE{0'B>ACmE[9'B>N ACF֏'B>: ANpD9PF'B>LA~CF'6B>)AC|D'JB>qAlC"DF'B>ZACD|\Fl' B>AhCFZ*'fB=A)C#'B>Aj]DLE%'B>GAD gE'B>ANCԫE'ACEȌ'FB>DACE` ?'ܳB>lpA*C(G4'dB>TAjC&f'B>pA}C8 F'B>AĩCݳEd'B>ACrE1~.'B>ACFD'B>ACmEs'B>A88CȣE'B>ACmE{'B>vAvCE51B=OA4C&%1B=jA,Cʃ31B=jA,Cʃ31,B>vAD]D1,#B>AAD%dEz1,_B>A]D ?E(1,iB>AaD 13B>A#D#}E}14B>?AFD EQ14/B>AD D5 1>B>$ADAD5 E 1FB>!A5D1FB>7A(DCפ1VB>WA]D mEQ1B>AWDJFA +1B>eAJD kF= 1B>ݏAuDBFc1B>A/D &Eo1WB=vAsCǮ1B>4A4D |E%1B>aA*DErBKACDTC'xBJ@B"BKA=C|E(_"BKA=CD*" yBKr@A=CѱD'o*" BKJeA>(CEV" BKA=Ck`E&" BKA=sYCzwE" BK@A=zPCEEb"MBLA>jCdF o"WBKA>CEa"aBK(A=ߕClE":;BKqA=C OF08":BK&A=CiF ";BKjA=:C~{F90"FBLA>C"GBKA><>Cm=ET"PBKVA=n%CE"PBNvXA=cCF"[QBK}A={C6 E"c"j0BK`A=C=E"jBK#A=NDFU"jBKVA=C\oF@9v"mBK3A=CE"p BK@A=|CgEn]"pBK}A=Ch>Dd"p BKjA=:C~{F90"zHBK2A>)C#OE""zRBKA>CD""z\BK:A={CE "~BL<:A?,CE#"BLA>:C&NF"BLA>CF-"RBK$A= CEӱB"\BLA>CF1\"BL A>C4E"BKA=CAEK"BK/A=pCaE'm"BKA=CmE%"B?kAD)xE[<"B?oA DtD F "B?UuAD#E "B?VAD!:E";B@*A"$D Em" B@0 A"2DtRE8"+B@fA"D m"gB@A")7D hDC4;""B>AhHD"OE"#B@A"DD"#'B?aAD*yH"+yB?A!~D)"+B?}YA _GD=E C"+B?=A|D(E"+B?? A>D*`E\"+B?2hALD2jD@bP"+B?&AOD)E<"+B?& ARD.$?E"+B?A|D, {Eȴ"+B?A`D*FE"+B?'AD!,FN_z",B>lAZDOEI",B>A$D f F`"3B?KA!DEdF";VB?:9AD,Dq)";`B?EoA D#sF9I";jB?EAD'KEb">B?A#D!"D B?AD(|Eh"MB?gdAD!D"`qB?A!.D.w"eqB?>{AjD!9Fl"e{B?ABD)E+"B? rA$D)En"B@fA"D MbFM܌"WB?A!bD7CG"7B?dA!`DH"ϧB@"A"GDCdF"ϱB@A"3D(CF"`B?WA#DE"B?lA TCD:wF'a5"B?fA ND'9F=]"B?:A D+E3"seBCACM"sBCA"LDE]i"sBCA"ٳCEO"sBCBA"C"E>"sBCNA"D BF$l"sBCA"CfVF"sBCrTA"D.E"sBCtA"rD2E 7a"sBCA#$D 'F{"sBCuA"QDD@"sBB_A#ʾD .%"s"BCA)C>5"s&BBtAD#"s'BBxA_Ds"s('BCA#DUD6=S"s*BCAsCcC"s*BCAzCj-A"s*BCAOĆEpM"s*9BC[A# CF"s+QBCLA#7C\EN"s-YBBsADCZ"s.gBByAnD1"s?BD /A# CEO"sCBCٓA"Ci"sBCm.A'C0E"s_BCSA#D,/F "s}BCxA"FD E"sBByAnD1"sBD|A#ϔDF%"sBCA'C<FP"sBCA"D/F"sBC;A)C;,Fյ"s BCA(mCөFcR"sڬBCA"DC"sڶBCiA# lDbEu"s-BC(A)hOC\"sBCHA*CCھ"sBBA)C֫F/"s)BBNACclPEc5"BCLA!qCG+"_BB ADCa" BC7ARCB" ]BC9AC݆" qBCARC" BBAGCD" BCA4'CD"ABC3ACәjC"}BC5A][C]#C}"BC$ACLaE7="BC?ADCUDJ!"RBC0 AUCʯiE "SBCAMCͦE"UYBC5ACqlDF"WBBACɚ"hBC#JAICE"hBC.6A\CE|"q BCFAuTC4D)}"qBCFAuTC4D)}"qBCMLA%/C;E"qQBCA4CCm"qeBBVAODDኝ"qBCAkCC"טBC^A$C%"׬BC"ACB"׬BCAبCc@Ej"׭ BC)AiQCDf0"׭>BC+A~CQ"׺{BBACAl#"׾BCCACG CVͲ"aBC*wAC/EL"ʼnBC-wAxiCE s"œBC)ACģEչ"BC,ACɩE"YBC !A)CW$Dz"cBC9AC݆"%BC-5ACyF%"BM.@{BpC-#;BCKAC)DrsS#;BCWA_CuXEM#;BCBA:CǽDC#;?BC?AbCC 6#;SBC>AC##;gBCAACDD#;qBCBACD0B#;=BC{AFC#; BCApCE #; BCAC*D#; ]BC>JAC#;$BCA[C+#;-BC`]AkCz#;.BC\;ACeDg#;.BCrAJCsiE5#;B@BC\rAC+DjD#;BJBCMxA9CcE@d-#;RBCNDA%C*ER?#;SBCYwACE0#;TBC^A:CkDV#;VBCAAC!C#;VBC#MAPCWE #;o+BC9AC}P#;rBC8!AC$C~#;BC@ACʾD ]#;HBC8ACCf#;GBCA[C+#;GBC3A\C3D{#;'BC_AɗC#;BC>lACC#;ƿBCnbA%C~#;BCIACD#;BCE;ACD#;BCMdA CE>_#;BCT3ACEI^#;BCCAC"D#;BCZmAC XDh#;BCQ'ACD3#;BC[$BD2cA+:C D²$BKg@Bq&#BQ8AعE&%LB@-@CJu'leBR AA=D 'BRbA7A{E q'KBR̲AA-JDA58'_BRAX'A7F'sBRARA&YE%J'ZBR[A'/1BR/ANNAj'/4cBSQAL2BREc'/4mBSALBE '/4wBSALwsC'~F2'/IBT#AO% B'/KeBT4AO,BK-E\$'/KoBT-AOQCE8'/KyBT-AO, C0EbL'/VKBTPANB:E'/XBSAL7C rF?NN'/XBSqALB eoE,'/h9BS7ALRAԹE'/hCBSlrALz;: F'/BSALCAa]EB'/QBSz@AL>BcE2'/kBS ALB垬Fd'/!BTPAOqBUD?'/+BTANBT3'/5BT.AAO \CPeFz'/YBSAL=¦oF'/BSbALCAF'/BSdALq#FF7'/BSALW+;.F{P'/BTgANbB@EF}X'/BT>ANC/FAy'/BTDANBCF:Z'/{BTAO}BC'/BS7ALRAԹE'/CBRAN AER'rBCtA BsE'rBCbABu$E41'rBCjA#BnDɋ'r ,BCyAB^E'r 6BCPACBLEϹg'r JBCR9AyBC+'r TBCokA sBFJ'r rBCwAB툴'r BCRA{~CsDmQy'r 0BC)AQBaE:'r 2BCCAB͙Dۓ{'r PBClA^BPE'r ZBCerABgEbHb'r dBCniAߍBE'r\BCAB窸Fs'r$BBSA?BF3Dh 'r.BCOA NBA[B^pE'rBCSAB2C(~'rBCQ'AKBlF u'rBC2ANBzEVF'rBCA5BDq'rLBByABE 'rBCA:B4EF'r4BBABކE(BRO\A+B/5Cs(BR=AB'J-E(("BR-4ABEc(t(RBR|A#?C2,BK Y@C_2BJ@-C2,BK @CzK2BK @2CM2BK Y@C_28BRA}B D!2BRAnB28BSAA_uB%EQ28BSAA_uB%EQ3-*BQ7A!}aBuhC'3=B@JA9D-4BAIA9oCDԧ#48BRAYAL}E 4LB@)@C>Dq4-BAA-Cې!4-BA.A-bCLDy4rLB@)@C>Dq43B@A9DD4BAA-pC57DBDAqQC@s57BDAqPCW]@5BP5@BtDKD6BTA '?޻E397=B@EA9DU8/BA^A!`JD$DGC8FBLo@eC}9 B?@C9 LB?@?CnDo9LB?@ C~fA9 B?@ {C/9LB?@@-CsCڦ:1BAґA9pC`DJ??LB@ @GCRD4?fsBH,@o&C@BKg@BqBBAA-סCBBAA-ЊCBBA6A-֛C؃DtCtBONA A(BҎVFbBAA-pCFbBAzA-CSJC1Dz?I:BK2A>S C@CIHBJǚA=XCE߽IHBK;A>C؄EkIS1BJ A<CFEHIYgBK"A>5CʛE@Ij0BK-?A>O9CFE3"IjBJXA=tCũEIjBJ|A=CjEXIjBJˤA=TC@F8-IkBIA@CDIsBI.A C% E6IBJA=DCˡDRIBIA>AAδGEq]N-BUpA~AFN-BU5A[AD)N̆NBTQAs\(N̯BT6AADt~NBU*:AAnaEnNςBT7AAE3ONBT5LA`AE N:BTAO#AC/NBTYA AN E~qN,BTO@A]޾pN6BTFA@mEPLB@"@(CE EPLB@>@CvPBKg@BqRjBFAQ3B`BVBC)A%CVBC)A%CWBC)A%C`BL@TB"AA f%BC(ACm%g8BSHAgB0*DH+gSB?xA5DDLggB? A5DABgBOA40CyhB@A7ZD\DXճhB@A7ZD\DXճi BX\ AB8EAƨjPBKg@Bql BQ2A&Bhl BQ2A&BhnBB&A‰CY7oB?JPAZ{DCovh'B>AC/voB@bA8WDE%vo B@A9 fC vo2mB@]A8 CРE;vo2B@]A8}\Ch|E9vo3!B@$A8.DTFCV/vo3{B@A8CEvo3B@A8*DD~vo3B@9A8'C7vo9B@|A8D~BAvo;B@A8hDEvo?B@A9O-CߢvE(vo@B@sA8DEvo@B@ÔA9D.EvoBB@A9CKD*lJvopB@NA9uChD6cvouaB@jA87DEFHvoukB@A8DE*ęvouuB@IA8D<EBgvouB@iA9DE_vouB@A8RC:D vouB@A9oYCEvouB@A9 DW'Cw~vouB@A95PDiE-vouB@'A9j=C\Dz3vouB@A9sZCvouB@^A9C5qEI_vouB@LA8KDDDovouB@A8yCE,'vouB@A8DJ[D}VvouB@{A94D$D r&vov[B@A9\}CݤDvovoB@A8DB^vovyB@8A8@CWC4zvovB@A8IDE`^vo}B@A9zC2B(vo%B@xA9eCfDvoWB@xA9] D E_voaB@A9CgEVevokB@A9CE)|voB@A9 CBIpvoB@A9pCEq̸voB@A8сC?|DkvoB@ŃA8DA(E)voB@ A9 C1C[voB@A9CjEgGvoB@A9 CC;voB@A92CЃlE [voB@A8D E&voB@A8^CvEO6voB@A9 ]CvoB@A8D(|DvoB@VA8C14DvoB@.A8 C2voB@YA8?CjEHvoB@A8CCh9voiB@A8Q1CXsvoB@A8KCvoB@A8CEvoB@UA9 CPDi9voB@/A8DnQCL=vo'B@A8DC 5voB@tA9\9C|CvoB@A93rC<{Dvo%B@MA8C=EhvoB@A8DEvo|B@vA9 RC;D0voB@A9 DDvoB@8A8CKBvo[B@HA9ARCL$CsvoB@A95DuE"voB@*A9#D¦ELu^voB@A8~D 2Er*2voB@^A8iCQvoB@A9XTDPBDMvoB@A8"D9eEnwvoB@A9 C@vC!vo B@A8D EOvoB@A9cD2EQvoB@A8DlEJLDvosB@dA8CBEsvoB@A9HDrE>kvoB@OA95tD ~EvoB@aA9iD vowB@yA9D'vyB@SA9C\gCl(vyB@A9 DbCB&vyB@TA9DoC]vyB@A9;`DmvyB@A9iD;A 5vy B@A8CBW vyB@A9X_D^?vy2mB@A8D/ vy9%B@'A8CXvy:B@A92Cvy;UB@jA9Dv/D5vy<'B@}A9DdvE\vyEsv9%B@ A8,CcvA85CvBAA:QrCgCvB@A:@wCD vEBA1A9CF0avOBAA:P1CCӖvYB@A:C6E%vcB@A:7DE[vmB@A9CyYEPwvwBAPA:EC}EevB@̘A9 CZvB@FA9TCcEvB@A9ACCDvB@A8hCCcvBAsA8*CKC:vB@A9(C+DT v'B@ȏA9CDC[vEB@UA9_C{EDavB@@A9C2EAacvB@A8rC۸D] vcBAL A9zCW+vB@A9CEnvB@ A9 [ClD 0vB@ǸA9 @CDt#v|B@AA9}DEbzvB@A94CXfEYv&B@A8VCBEiv B@A:)CY-E_@v3B@ A7Cv6B@BA9DCD1v?B@A9CE ~vIB@A9RCԵD!BvSB@A9ѬD_ EvݙB@A93CtE5vݣB@ A9CEeķvRB@A8T~CJkEŒv\B@A8Cg%CvB@PA9(D [Cu^v[B@A9\DZCvB@jA9TD ;CGvB@5A9-DC.vB@=A9D O{C]vB@#A9-D CIv%B@TA9$CCE@v/B@A9}C E̒Zv9B@A92^DDZEv[B@A9CE2'veB@~A9CD-vB@%A9"C~E|vB@|A9CEvB@'A9 CrDPvB@ܝA9(DۨE7Jv B@̘A9 C/v'B@A8`[C~E}$v1B@A8CExv;B@tA9^D EvEB@A8C ESBvYB@$A9-CE?6vB@A9QCd{vB@7A9D"EFzvB@oA9D/A'?v B@A9(CDPv5B@_A91EC9Dv?B@A9NC|D_"vIB@pA9Ck~A?vqB@A9C_D+v:B@TA9բC?}vkB@_A:'C.DCvuB@.A9jDEl5dvB@A9CCjvB@A9CvEvB@ŸA9mCEšvB@ A9\DDźvB@ԢA9CwjE4vB@A:)CUEl6vB@A:AC\ELyvB@A:C.8EvB@ȁA8C[E3 v B@A8 C%/Cov B@A9CDt&v )B@-A7CfESzvB@.A7ȧC^E@vB@A7DC7F5vB@UA87QCEv@B@A8CC-AUvB@dA8CГDXvB@̔A9 0Cv B@ A8SCEqKFvB@A9CDy?v3B@TA9&C7X@:vB@A8CcTvB@ A6>CCvB@؜A6+CvB@A8jD@OvB@{A8K CE vB@)A7JCuF vB@A7wCtE/vB@\A8tCEzrpvB@8A8CUEvB@cA8GCfEvB@A9CC&YvB@sA9 C?C+=v)B@qA8QCv3B@A7,CXE]v=B@4A6DDWFvB@̝A9 CP@-v B@`A8CajE*vB@A8CDkv1B@A8CE6vBC(ACm%yB>A(DVyB@#A*|CZFkfyDoB?ZA(DDyDyB>bA(D_Fu||yDB?DA'$DmEyGB?nA/D0N3DEyv B?ԯA/D2(CyyB?A'6De"kE=;y|B@A.D#HbyB@:A+lXDCbzyB>A(DVyoB?*A/D*dFYyiB?A'DD\iDy_B@)A)zD\yB@ A(GD |yB@A(9D;yB@A'cDEL{\yB@A'ѐC|Eϗ y[B?A'?DIsDyB? ]A'-DWyʧB>A+DNQqDy B>A*2hDm5"FByB>rA)DL`WDP\yB@!MA+DDSyB@A*pDpDjyB@')A*FDyB@]A-tD+Ehy.JBASA-GC/y.^B@'A-~Dfy@}B@CA- CF1Py@B@A-D]FyNQB@A2KD yQB@u A-D DcyWB@A-DrD/ijy\B@A2]D >fyFB@A-UCDfDyZB@ֽA-9D!D/ydB@A-+DxlD$IyB@~A.RCdLA/=oDyE~yB@<A-_D-PyB@' A(܋D FDe`y"iB@"A&OD Cy&\B@ XA/eCj!Fy&fB@RlA.D Fb,y2B@)A)5DDty2'B@)A)OD=yGB?A/(VD/0DtyNB@A2AuD eC yQB@YA-BDoeE҆'yQB@FA.D4EȡyQB@@A-DMEyWB@#+A(D.E:yWB@"SA(DE}F yZFB@=A-ɮDYE%y\B@QYA2WD6FȘy\B@`A2cDBE5IHyt|B?A/OD&0~CmyuB@4A2CD mD$yvB@WYA2SD Eyv B?\A/-D0D3]Oyy+B?QA2D C{9y|B@(A.C~F6y}B@JrA..mD F;y} B@"A.CF4ڛyJB@2A-ADEyB@yA.D"E&yB@A-UQD ͚C yB@hHA-DmiCtyB@wA-oDwDeYeygB@0cA.C@F*yqB@TA.{D]F?(y{B@OA. CDF,HyB@T5A-D@z,E7yB@<3A.DaLEjyB@AZA-D#ENyB@#A,DF'yB@!DCwYyB@UA1XGlFyB@A1ܕCGFLyB@5dA/ĐD\F%YyB@!$A2C}DmEey9B?٫A1DRTFhSyB@A?xD"Cqz\B?tA6DD-MD%AzB>8A?D@zB>A?ǻDCzB?A:3=Dx GRDzBBA4C:CNVz/}BBA4r|C\D5zBBBdA7C-D lAzBBBLA5K6CtzBBBA2tDPMFzKBBA8SVCϴ;C$zBBA2 DuEc"z$BB*A3D ROFz.BBA2CE`z8BB\A3*mCЇ*E+wzBBBA1CFzLBBVA2D%EzyBBBA5oCE- zOBBA6C}E~zYBB؄A6VCENzcBB٭A5 C F`izBBѬA6UC^DzBBHA6CC<zBB=A5C EzBB A5CEz?BBA0DֹF 'azIBBA0]XCCE\,|zSBBwA0tC7E\z9BBjA4W"CmCEH %zCBBA3D8F^tzMBB߲A2DBSEzYBBA3i.CDQ|.z}BBނA3MCϤEDzBBkA3bCmz_BCA0BD7zBB A4 CLCszHBBA7YC`zKBBӴA6C|BDzQIBBA7_C oD̡Fz] BCJA7rjCEuz]BCBA7|sCaElzsmBBrA6ClDtzsBB}A7oC9E5ռzsBBSA7lCE(LzϔoBBA7CrozϔyBBTA5&C@'EqzϚ}BBEA7CDzϚBB.A7pCEQzϚBB>A7?]BKEzϚBBQA6~CkFOfzϠBBѩA7HBE<zϠBBA6AF]zϡBBݣA7 CE7UzϢ&BC A7VCD4zϢ0BCA7_CԶEwzϦBBBA7]CiD8zϦBBYA7>CD>zϦBBA7wcCEzϦBBSA7C*E\$AzϦBC]A7X)CqEMzϦBBA7WCRE=zϧBCA7`CE7^zϧBC#5A7\CD2AzϧBCXA7CF!2zϧBB*A7zC>E˔zϧBBA7CyEzϨ=BBA7CaCzϪBCJA7nCrE[zϪBC A7TC1Czϯ BCA7SCDozϯBCA7PC zϯBC A7TC1CzϯBC0A7iCfDLizϰ BC)A7`CEzϲBCGDA7CSDzϲBC0A7jOCHzϲBC'A7fCOE(z϶sBCvA7uCE2zBBސA7CzBCA7YCE8Pz5BCSuA7wC[EbzBC>A7wCՑDDzBBBA7]CiD8zյBCXA7PCEME)OzBBA7/C2E$ zOBBA2`iCzzYBB)A7CEnspzBBA6Cu{B>0A?DA{'B>AC/{BOKA3BGD |KB?-eA/DfGf|KB?A/-D)6F> |KB?!A/WlD4dCd|KB@#A.-C}Fa|KB?`A/cD3D3wj|K!B?A4YuD|K!B?.HA3DQCX|K&\B@NA.D%Dn?|K@B?FA23CjE|KMB?JA1C3Fw|KMB? dA/YD\"G p|KMB?A/pD,"F6p|KNB?WA2[)DF =|KQhB?.A3UD|KUB? A3D[Em|KUB? kA4DA3,D3D<>|KWB>bA3%D-VDF2#|K\B>GA4D+LDi|K\B?,A3DF|K\B?.)A3WD {CN|K\B>hA4XD.RF7|K\B?XA1w/DE|KeB?A3-D@Eڝ|KeB? A3AD&h|KfkB>zA2`/D'G!r|KtB?_A1yD%!FhM|KuB?"A3tCyE[|KuB?bA2kDD&'|KvB@*]A28D ||Kv B>A2~DNbFs:|Ky B?A3FDm%F|KyB>A2cHC;F|Ky!B>(A2CkFD|Ky+B?A1Y*ÙB+G|Ky5B?~A2NC/G+|KyB?fA2&CFY|KJB?dA0fDPGGI|KTB>A+PDGD^[|KB?A.D&6EX|KB?K%A4ȣDK|KoB?A/S8D31|K_B?W`A4DF|KiB?>IA4ND)CVQ|KB?A5GtD|KB?`A3sD!C|KB>מA2'aD`RFy|KCB?AA4&ZD/GE|KWB?KAA5:D5DQ|KaB?KoA2DF ;|KkB?T*A1D֖Fh=|KuB?hA1XDz-F1|KB@A2GDgE[|KB?}A2D6)ER|KB@ A2D%D : |KB?oA2WC%F@|KB?eA2 eC6EFFG|KB?MA2D|FM5q|KB?A1;D/F|x|K8B?R5A1"DF c|KB?eA2DF_n|KB?A1 DF5k|KB?uA3D0F|K/B?&^A1IDZF)|K9B?A1:D#F°<|K#B?KA5`DUD P|K-B?IA5,D![UDǩ|KAB?KHA5DED|KB>8A4DRFI|KB>VA3ECLFw|K'B?GA3D wC|K1B?.A31D;F;|K;B?"A1DFk|KEB?VA2i"CFXFM|KOB?A2DBG|KYB?u{A0D)dEJ|KaB? A3DYEe;O|KGB?_*A2VC[AFF>|KQB?4A2 1CHFz6|K[B?J A2"uDFK|KWB?lA2D5G,|KaB>A3==D*TrE|KkB>A1x`DG,E |KuB?yA2VDF8$|KʻB>A*7DPYCF>v|K B>A+n DK>|KB?=A/gD2qE%cM|K̯B?2A2mD1XFDnJ|K̹B?A2D$^Fy|KB?A1DQFR|KϼB?JtA5DgCȪ|K B?.A3y=D"UD|KڗB?JA57D1C|KB?>zA4G7CDFD|KB?A/dWD0E r|KEB>dA2D|KcB?A4VjD r-|KB?DA2DEE |KB@A.{D#eDť|K#B?A.D,cE]|KB?,A3DAb2FfY|K B>A2+D@P}FU|KB?+ A2DC Ff\|KB?A2txD" F|K'B?}pA1CMF`|K1B?PA2 CF|KB?A/>~D/?+|KB?9A3DE:|K5B>A/,D\CѺ|KB?GA1D&F"=|K%B?E@A2OD?mF\,|K/B?=A1D+݋F ||K9B?@A2EDkVFM:+|KCB?A21Ck7Fw|KsB>.A2D/E9{|K}B>A3@D/QKDQ|KB>FA3'D*-DC1|KB@A2EDC|KB?A3;^D'DV|UB>A2 D9Gjʱ|UB?[A5D(BCw|UCB=ԟA1^D2C%|UB?.\A39D"qC|U!B?4A4'ED!CA7|U!B?A4rD$E5y|U8TB>;A+?3D:WE"|UBB>|A+8D:.E1|UIB> A);DV7BDި|UM%B>/A)DOQEX|UMB>lgA0TDwtDiT|UOB>A)^DIE=|UOB>/A)DOQEX|UQ^B?jA6-D*!|UQhB?uA5D18EF|UUB?:qA4D)"Flo|UUB>A4+;D'FF\ԕ|UWB>$A4sD0FP|UWB> A3pC4FTT|U\NB?gA6D,bQD7|U\lB>!A6D@|U\vB>A41|D+|U\B? 7A2?D_GN3|U\B>A2CGK|U\B>A3CG.ت|UaB=,A1ZD!E$|UaB=bA1OD~XC#o|UaB=A1|D8EW|UeB>˳A3zDZGj|UeB>gA2GDޙG|UfkB>A3C-DG_|Ut|B?A3^D,DP }|UuB?iA6(D5o|UuB>A0frDZh|Uv B>fA2C6G&|Uv>B>dA/kD$F +^|Uy B>A3;D(qFٹ|UyB>A2_D&QFj|Uy!B>A2D/EQ|Uy5B?"A3D+aEI<|U|B>|A2D<F6T>|U} B>r%A2DclFpM|UJB?-MA3D#mCJ|U{B?A6D)q|U_B?H|A5D$F$:|UB>A3\De!GK]|UB=؉A1u DoD*9|UB> A1 D}FGK|UB>zA6D CdN|UB>FA5TDi3Fp)|UB>lA2kC">F@#|U%B>AA2KD%Ev|U/B>IA1)DEEl|U9B>C,A1D&E|UCB?MA5D&F*w|UMB?>A3D%b-EA|UWB?YA4D/'TFGl|UB?PA5GD!DX7|UB?FA2D ́|UB@A2ED1|U/B?A1ÝD# Fw|UB?S^A5QD#yCj|U7B?YqA58D\E -|UAB?A5DdQFf3|UB>SA5@De(Ep3|UB>پA3ED-5EV(|U1B?$A3D2FԛN|UEB>DA3=D)u|UOB?LQA5tD)|UaB?A4MD(C"|UB=^A1kD%CDq*|UB=HA1Da@ܺp|UWB?XA2u-D:G"-|UaB>ȣA29rC*EF|UkB>A1D"kjF4C|UuB?0&A1"CaG|UB>DA0D1~F|UB>A+DJްB'=|U̯B?.wA3 D!A|UB?EA2eDD#|U;B>A0iDrE|UwB>}A0PD[F5|UͳB>sA0Ds!Eͮ|U B>A6DD BN[%|UcB?oxA6=D+|UB>A3uDG:|UB?A5+sDf|UB=A1D{fDU|UEB>A1DF|UOB>A1cDKFs|UB>A1D+DAE@|UB>EA1D)&|U B>A1D+ERP|U߃B>tA1&DZeF=|UߍB>BSA1DG4EA|UߗB>SA1i]DP(E/|U!B=ׅA1 EDD.|UB>A37D*hDeY|U B>A3hD"nE,|UB?A3%D(Eؤ |UB?5A53D yC<r|UB?^3A6)D'UED7|UB?nKA4"DWFi|UCB?A3(DE|UB=A1TD$E|UB=aA1M DmE/'|UB=A1DDgq$E`|UB>LoA2D!)'|UB=A1D&Fc|UB=A1DE3|UB>2A2DMb0Fl>|U#B=A1WQDxFݕ|U-B=A1WDA2D2[oE |U}B>PA3*D,V{D|UB>A3D.jF$rq|UB@BA2{DSC}2B@A81DR=}B@A8|D ZD}B@A8#DB@m}A8Dy7E'B@A7e;CԜE"B@A75DJE;B@A7?D &D+[ B@˫A6CSE~s B@̫A6xMCp B@A8CyD B@A7DCrB  )B@_A7DCIiE B@{A8kLDKEA B@UA7CZDo-B@A7CE B@͋A6rC{6D]B@A8,D E.B@A8@DOTE0<;B@A8D6DgB@A7ICDRVB@1A7D+DB@IA7DCDB@A7,CC sB@]UA7D G)BX$B@A7D%E:%B@2A7#CyE#d%B@A7MD K#EE/B@RA73CDqt2B@A8.D#C 3{B@A8C4uB@`A7D-?B2d9B@A8D/CBB@wA7bCEDgD/ςB@RA6PCσSB@A6{DD}E^φ#B@$A7D[$DφAB@gA7CFDCQφKB@A7XD)EEnφ_B@A7oC&D#) φiB@*A7бCDgvφsB@ZA7'D8D/UφB@fA7DBφB@lA7rD =vEnχOB@A8gDDχYB@wA8G\DDsχmB@dA7MDRDχwB@SA7D EgHχB@A6DBCχB@pA8ClD0!χB@k!A8D(DVϏ)B@=A75CvCOϏGB@=A7:CϐB@EA7AD \ϐB@xA6úD#CiϑmB@?A7 DE@ϞB@_A4D ZϞB@eA5#D {E+_ϡ B@A7ID.EpnϡB@2A6DϡB@A7xCDD\ϥ'B@A8qD,CNuϬB@A7\DſD4%ϮB@@A8D|ϰB@A8.DIϼB@A7CE2ϼB@WboB@}A8+C EҰbyB@HA8oiDEߝuaB@pA8DFPD~uB@A8DC)uB@1A8k,D TEuB@A8ICCRvyB@A8D _D1HvB@A8CC+ vB@bA8DvB@A8 D@EQ@w-B@A8D2BwB@xA8ZD,D$wB@A8|HDBqEwB@YA8DDwB@A8r1DWE.wB@A8`DEbxmB@A8iDEJhxwB@~A8bD`E#xB@-A8qDYExB@A8D E)-xB@A8D DxB@A8qDfE9xB@6A8D)D`yIB@u A8DV DdzzB@vA9 7C}D2z%B@zLA8D C/zMB@y!A8ɂD D9&zaB@wA8DEǭzkB@|vA8D&E zuB@wA8CLEUzB@2A8DeDzB@@A8dCDDE zB@{A8XDCE$zB@r A8Dk DRVzB@sA81DD|zB@nA8uD~Eu.zB@A9C-CM)zB@vA8DeFD&zB@r}A8eDEstzB@pA8uDD4{B@uA9&DDĝT{B@rA9/EDL|B@{A8Co}فUB@jA8^DBفiB@CA8ZECCuنB@A8D:-هOB@A8j8DkD@!هYB@yA8IDLDهB@nA86QD QBmOهB@pA8NDC9هB@uA8E]DFهB@pA8_~DD'^ُB@xA9&GCQD3ُB@yA8Da{D ّcB@A8DeD&٥'B@A8}DTE[=+ٮB@A8ƺDlZٮB@&A8DDFٰB@A8cCERټB@A8D^ENUټB@!A8iCDNټB@A8DD B@nA84tD Cy΅B@zIA8֫D>DΏB@w&A80DDΙB@yA8DDB@~A8a_D:nEQB@nA8ZmDiE,B@qA8XDEV8%B@5A8lDE2B@rA8DD'B@qA8DDD,#B@eA7Dv(C77B@fA7D8CB@FA8D EB@ A8^(DE&B@A8D)D^B@fA7[DhCl'B@eA9BDDl 1B@A8DD(B@pA8TD BltB@uA8|KD#IE(aB@yA8qD OE "kB@tA8RDER"uuB@tcA8;mD n0E+QLB@A8fWDE'1B@~A8fSD$E$#B@rA8D@DՉoB@A8oD1 EayB@MA8sDD*B@~A8_vD_tDB@A8~D!"E(hB@A8DE* yB@A8D 7OB@A9D Dt`7YB@RA9NC5D7wB@A96BDD /_7B@|A9D,Dq8B@A9AD q?O8B@rA9jD`C!<]9B@sA8tDLA 9B@A9ICDƼ9%B@A9DCEEc o99B@A9?DCDQ9B@A9:C 'Dg 9B@(A9AC D:B@OA9CD;UB@KA9~xCBDO_;B@XA9CJD<'B@A9ZDDE OwAB@A9tCeB@IA9xDOYDB@A9D Dr)B@}A9D%E*W;B@=A9 D?CkݤB@A92SD OE'B@A9C"D_B@=A9 D?CkݤB@CA9 D3YD GB@1A9RCENB@1A9DƨB@A9(PCD dB@8A9CDzB@EA9-C D;'B@/A9C1B@uA9x6C;B@A8D]DmB@*A9 C`DwB@A9TDVE&8<B@A9D1EI?B@A9J D BU3B@A9CvE$3B@A9eDP3B@A9tDnB,3B@[A9](CEC4N3B@A9iDD|"3B@A9njDaD39B@%A9ܙD C&39%B@A9ZHC B3:B@A9C"C3;UB@A9DCC693<'B@A9C4AD^H3CE3VB@A9 D =(D3pB@A9C3vQB@A9 CE$3v[B@A9DrE/q3vB@8A9CuwDPe3vB@FA9y4DdRD3wAB@A9YD[D;3wKB@A9>D D$(3w_B@SA9SC1?u3wiB@A9DE:e3w}B@UA9CDƯ3wB@8A9DE3wB@A9DBcDTz3xB@OA9DkE73xB@A9zD>E l3x'B@]A9pCE73xB@A9UD^E 3xB@;A9DWE93xB@jA9$C E-L3xB@JA9DXDr3xB@A9D-xD3xB@eA9CE n@3yB@A9SDD 3yB@A9DjD@ 3yB@A9DD 3yB@A9D C'3yB@A9DDE3yB@dA9D.E3yB@A9DO}3zB@A9v4D3Cb3zB@;A9DdCY3zB@A9D`E 3zB@A98C|D>'3zB@WA9DhEA3{=B@vA9C>E3{B@A9DyEv3|B@A9qCCD+y3|B@A9|D ZC> 3|#B@A9uC3}YB@A:"CjCb,3}B@x A9D&xD6 3}B@A:CDC3}B@A9΅D)UE3}B@A9C+DӅ3}B@A9CYiE# 3B@A9Dg3B@A9DYBή3B@A9gD!Dw`3B@A9DD03B@A9DDfp3B@A9QbD15Ck3_B@"A9υD D3iB@A9DϋC%3sB@aA9VD_DܹG3ՃB@A9gDC,{3qB@+A9cDcDz3B@UA9DCCL"3B@GA9CfA3EB@ZA9Cb3CI*3OB@A9uKD?w3B@qA9DTD,/3B@A9?DqD"3#B=A0ցD즘3YB@A9eC9A3eB@=A9ѫDE3oB@A9D |E3yB@A9dD {OE1T3B@A9D;E^3B@:A9CDbL3B@A9|C E83B@A9{C- BP3YB@A9(C`ELF3cB@DA9XD D3mB@A9C2E?W3wB@2A9DE}3B@VA9DD$=QB@lA9=DD=!B@A9[D DBp={B@zA9D =/B@A9KDQD۵=WB@}XA9D~DV=BB@eA9@D=FB@vzA9D =FB@A9DsD-=jB@~A9DD2=jB@A9D-D =xB@A9D=xB@!A9DND<=yB@xA9D Cb6=yB@A9LDD2,=yB@%A9JDDO=yB@?A98D DΝ=yB@};A9DDfh=yB@|A9SDLD={=B@/A9gDC$8={yB@A9DIpCt=}B@z>A9DD=GB?A7mD!=;B@rA9DBC1=dB@A9DPD#d=xB@~A9DjDO=B@A9AJ 0=_B@A9۹D jA=iB@y`A9DGDNB=ՃB@A9DE=ՍB@|A9Du=՗B@zUA9D |}D!=B@vA9CvC=B@|$A9D&D#X=B@xA9DߒDN=AB@}A9RDC{Dw1=KB@%A9rD+D =UB@wA9ƤD$D;=KB@{VA9DDu=B@| A9ހDtDB=eB@} A9D?=B@JA9Dd]D"=B@JA9D _?B@YA9ACdSC?m_BA]A94CBAA9gCոFS&BAA93C\F<0BAA9g C%FIBBA4C`CN?BB:[A9"C!F#I?BBqA8SCdZGBBA9g8Cߢ E`GBAkA8OCD!X'GWkGBB2A9vjCRF(&sHBBA7[C#E{IBB`A8bEC\D;ŕIRBBA8IBC#uKBBĕA7$C`KBBA8/CeaEHU(BBA9pCD,3VBBkA8CIdEީVBBA8UC&F g3BBe`A8C˘E:g=BBdA8C׆E.JwgGBBjA8CrEsBBA7C9EBAs$A9AC?DBAA9hC蕰E|BAPA9C!F(BAA9olC;EsBAA9pCELzBAA9|C]WFu(BAA9oCᮄD#YBBA5C|DsBBuA8tC8EDH}BBA7PBA3+DG] SB?E~A5?.CF^=SB?A5iD.EBi9SB?;A5HUD1E/lSCB=BA1ezD^C徏SB?A6D&iEhZSB?A5DxFSS!B?A5D LE^S!B?A5DQBFhS!B?fA4D @FkЛSQrB?-A6fD(ESUB?3A4lD FSUB>A4CؿeFGHSWB>\A6/D%PEe`SWB>(A4=#DlhFS\B?A4QD)3FBS\&B>PA5RDV*FSS\0B?A5D%rEP(S\lB>A5SDQFbS\vB>6A5FD&FS\B>^A5DiIFlIS\B?XA4D-YFxS\B?-]A3SD}BS\B?ZA5*D &E&SeB>vA2DuG SfkB>QA3D~GT÷SsB?fA54DKF+N(St|B?ױA1D:uFSuB?tA4KDNFZSv B?UA4vDE nSy B?BA3ZDB,F*Sy!B?A2 DDUBSy+B?T^A2hDSy5B?aA3&DIKFBSyB?RfA2\D F;(SJB?A5$DA_S{B?A6RD*aC*SB?CA4D!ES_B?GA50DSFkuSiB?>A4GDSsB?TA5DHDS}B?GA55DVDSB?IA5DE%ESB=yA1LA5^D\FܗSB>jA5ǧC(FSB>A38D(.CzSCB?AA4CFFSMB?J}A4CF0SWB?lA5Dt FSaB?[BA2R D +ANFSkB?dA1CD D;[@SB?8A6ZoD)"xE0x\SB?@A6_D(ER@SB?dA5DμFSB?nA3D'DUISB?dA1D!FCS8B?O'A2DESB?o"A2aD fESB?A3~D2EeSB?A2DE0S/B?l9A2C WFS9B?p*A1D"E*]SB?jA5DFTS#B?qrA5։D0KF)S-B?RA5 dCFtXNS7B?vA5DDgFۀSAB?/A5D[)FSsB?\A4)Ds FS}B?śA4D)FvSB? A4 D'FySB>BA4C+F{SB? ;A3N`DdEyS'B?A3YGS1B?FA35CG S;B?A2A3D=GR|SB?A4uD#(FSB?dA6SD+^SB?A4mDH!SEB=ڌA1DC,SOB=OA1aD#CXScB?A5g,CvFPSmB>2A5D6BFV,SB?ZA2iED SoB?A6]AD)E ͱSyB?A6CES-B@A6DXC<SB@A/"D'^DtS B?.A36MDĖE^SB?+A3LYD'MEP8S'B?>PA2pVDgS=B?ђA6ByD%F+SB?A5dzA3;D +GkhS}B? A69D^BO SB?y A5DLF,S%B?,3A3\zDD+XSCB?"A3D2,EfwS#B=A1$DE4SsB>A3D.CSB>A3BD,TS{B@A6 D˅SB?5A3g9D/0E/]B?/A3ԂD aD]B?2A5D"AEd]!B?5A4$D!zCb=]NzB?SA6ecD)p]QhB?k2A6D E]QrB?\A6epD+]UB?PmA4CfF8]UB>0A4.D`tF_]WB>jA5D.D5Xg]\&B? bA5kkD/IEH]\NB?A6D,ELq]\lB>A5VD]\vB?A4gDF3]\B>A5Y'D; E>I]\B?JA4׿D!WFU8]eB? A3D+eC]v B?A3}D'q]y B>A5#DDI]B?LA5!DEMt]_B?eA5DZF]}B>A50DPDY]B?A4 D'B];B?"A6amD-C*]B>A4Ddj]WB?>A4yD!uE#]B?JGA5DQD?]B?A1MGDHb]B?A6^YD18b]7B?7A4˷CF̀]AB?#YA5,]D%F]B>A4ֺDABC]'B?H?A5DC%{]1B?2A4DF5Y];B?FA5$D]B=̒A1"YDք;Efu]B?|cA0DqX]cB?HQA5DD]ڗB?JXA5Q\D.E ]B?I A5^D@Eb1]B?A3oDl\Ed]B?^A6^D,CG]B?VA5'D%cF[ ]B?4zA4#D#]B?zA6ND.49gB>A4 D}GgB?A4D!LjEv1gB?A6UD1jECgCB?+A7fD"DcgB?9A6C^\FZgB?A5*C;PFm)ogB?A5WQC9G +g!B?A5nD .EQg!B?TA4QD4F g!B?nA4D]gNfB?ҝA6JDGʕFgNzB>ϺA3DGVtgQ^B?A6CVFr gQhB?>YA3D@RbG)gQrB?_A5D2:F4gUB?IA4DOD+gZB@A6»DEdgZ B@?A7DD]*ZgZZB@KPA7DCg\B?e|A5;D/Fz;geB?tA5DKF}qgeB=A0޹DOD5ggoB@|A73D)BegkB?A7SD#D⪕gkB?=A7ID$>DugkB?A7C D"D}2gsB?RA4/D -xE_gsB?A5]/D:2FnguB?A4C$G]gu&B?xA6 CF<gv B?$A2DmDgy B?2A4D$DqUgy5B?vLA4D.FygB@_A7lDY@Džg8B?A7lmD$UC֊^gwB@=rA7DHgB@vA7dD +F04g{B?A6C!Fe27gB?A5iD FgB?A5\AD7MD|DgB?A7zDEZgB?A7D3ڼE6NRgB?A7"5D DEgB?A7gD ERgB@A7&DJE4BtgB@cA7ZD 'E]CgB?FA6ID$FOjgB?A7HDF [gUB?#A5'CߖFg_B?A5msDFgiB?A4CC֞FgB@!A7D rFpgB?A7DAVFgB?A6DqErgB?1A60DuDRgB?A6DFŞE٤gB?8A61D>FcSgmB@"pA6D D2gCB?dkA5'gB?A5NDE}g̯B?GiA3aD ighB@?A7DѸCsgϲB?,A6hD yE'gϼB?A69D:FogB?A6D2R7EԎgB?A6D"E9gcB?PA5CΠ4F%g*B?hA6#D/TJg4B?A6D${^E`gڗB?s A5D^F6 gB?fA5DwFgB?*A6D0HF$ gB?A5ѹCևF$ gcB?A4 D)BgoB?A6,C:FgyB>hA3sDjG~g-B@ A6DEĚgB@A6D kE:gB@"A6DEGyQgB@#6A7JD>EN=gB? A2վDC|XgB@>A7UD}Bg3B@A6D# g=B?"A6D@FE4gB?9A6.D6PvFAYgB?yA6EGD,&Eq0!gB?A52_DF(giB?6A6oD4C: g}B?A6ŀD֡DngB?A6kD)EAgB?A6D)UDh?gB?A6|WDQLE}g{B@A5.DOdE7gB?A5pDENgB?ӶA4D&{F4 gB?A70D"ZEDFgB?*A6D)AF8@gB?A7D!E/95xBLo@eC}BLo@eC}QhB?{A6_D.MDG+6UB?4A6aD+B?|LA6SD.?B?A6\D+l=E7B?A6[D&d;B?{A0zHD/C~/Q^B?A6_D-dC+B?A7mZD!CRԷB?A6]D*E  B?{A0~_D*ID3~B?jA6bDD/C"B@e[A8sDE/B@lA8sD+EǢB@m&A8C$EfB@}A8DNFJ%B@sA8BDkEaB@rhA8$DE_mQB@y=A9DwE+B@|A9C9E3B@~A9EYD@,E>7B@A9^DUE&B@sA9'D SDB@sA9gD @Dy/B@A99DBa% BB@sA9:C]E LB@tA9CwD#I VB@y)A8D EcÖB@A9wDRD%1B@xA9DE J2 B@x>A9Da]E 12B@yaA8D9EIMQ3B@lA8$wD D7wB@zA9@D7E*7B@xA9@gCA8"DEb|YB@idA8 D CDZB@UA7?D /EZ B@lbA8(DDZPB@iA8dDZcB@hA8D7DU>EZdB@iDA80$C%E ^m\B@^A8D@EiaB@sA9DDAaB@q7A9DíDwbB@pA9JC"EOLdboB@rA91DybyB@{A8KDDt B@mA9QDnHDl}tB@miA9~DDOt!B@m,A9DeDBv[B@A9|C5>IwiB@A9DD}}xB@A9CdDxB@A9f(DLE&xB@(A9C^Do$xB@sA9:D3EExB@A9D}DexB@iA9CD7BPfZxB@A9DxB@A9D3D.y?B@A9FCKUE6]lyIB@z A8D rE|}yB@}\A9ϹD 4E8yB@!A9CrE9vyB@A9D4(EXl!yB@A9/DcrB"lyB@yA9OD ,VEC E O!|-B@A9VC/|sB@yA99\DSD/}B@uA9D CaB@o,A9tYCmEI@kkB@lA9sDDxuB@mA9DED1B@sYA9D!E9;B@vGA9dDE$<EB@o^A9%DYaE8OB@{A8ID˧CѦ mB@`A7D HwB@YZA7 D LE|B@khA8D D$>EL B@lA8CD֡EsیB@bA8^DHExB@_DA8>DZErB@wA8D aFB@`A89CdGE!B@gyA80CuEB@k3A9OD^hB@{DA9nCΓEI}oxB@uA9>DƹD B@~ZA96DDB@yA9'_DUDB@|@A9DFmDƉbB@zA8D83EYlB@zA9 DHEAB@F/A7$D .DJB@A9lD SDDB@kA9v!DlZB@pXA91C) EB@A9¤DEd=B@A9DE$B@A9D EQxyB@NA7sD 6B@qA80?DoDۜ B@pnA87 D ,DB@kA8:D E5*hB@lZA8"D]C΅B@{A8DεC_ΏB@v>A8՟DK E_B@GA9DDDiB@|A9iC ETsB@A98CEѧB@mgA8fD Ez>XB@k#A8L"DeEP%B@y!A8FD=՗B@qA9)DC`qB@A9DE{B@A9D Dd݅B@A9rC{E B@wOA8ڃCJE5B@nA8#Cu=E%B@EA8FD#B@jTA8FD Eb-B@VA7D EmX7B@^A8yDExfB@s0A8DlCgtB@lpA8#gDDfB@fA7[DhClKB@\A9=dCf@D?.B@A9(?CB@HHA7.D5C4 B@gA8GCZERLB@YAA7DsEMB@lA8b|DxEQaB@o)A8[D3`E!_kB@mA8M[D*ЈEuB@rA84DE{=B@GyA7A-D F.^B@A-~DE7T@}B@mA-YDԢDkMHuBAn3A1DDwEJBAA/ׁC'WB@A->DfCaYBBA-Cb)BAoA/CDb3BAnA/CDb=BAmA/vdCZE+ fBA>A.#IC[E$fBA$A.CC[F%s1BA A0sC{Dr(BA7.A3ICvE BAA-SD+ErBA~NA1C E|BA{A1sC9F&XBAA01CxEEBAA1C ^DBAA1=CFIB@A-D4FL;6SBAA-sC3E]BA%A-C\DBAA0dbD5/EBAuA/*CmEP BAA/D5FBAA0VCciFBA6A/|@D+ͪFBAXA/|,CEB@A2CD uB/B@A-D8%FBn=BAA4NDl[BA&A4CEBARwA2C>EsOBAdA2?CtDÂ`BAuIA1ƑCڵF!BA~A1wCF\9BAdA2?CqDeCB@A2D pC)BAA0C!GBA;A3[0C9TE݋QBA=rA3BbCC[BAvA1ֈDuDA]BAI&A2DBAAA3(_CyjCQ?BA{%A1~C~F!sBASA1WɅBAzA1C2Fz>ɏBA*A.MC]VDPFəBA~A0#CdE‡ɭBAA.{CMuC-ɷBAA-sCGRD{KBAuA0)>C`Cu-MB@A2D a@b}BB[A3/DzE|/BAIaA2CE~B@A-DrD/ijGB@A-ɡDD oBABeA.*C*ADZڍBA.*A. C&E])$B@A2D A7BAA-C'F~ABA!1A-C>EKBA MA-CEyBATA2CFa!BAEA3C*CׯFiBA[~A2[RCĀFpBAA-CMED B@A-[D F!BAaA.C7BAcmA-C 7ABAfA-_CCE5BAUfA-C/E̠ OBAA-Cd Ek[_5BAyA-CoE\?BA:A-CݣdD#BA:A/&D! EȶBAzA-CRD0BAmA-CEDм6BAo]A/}^C-E>BAA-JD *FkH[BAA.sD nFJBAA-l|CF*_|"BAsA+C*.TB@kA-cDj=DHBA]A.:GD!cFHBAA.CEQI BAA-/C|E,JsBA/A.7D'4E#!JBAvA.UDFaOBA A/n6C>E YOBAkA/+C DYBB A-RCD<$BZBA}A.[C꣼F ZBAKA."C{F7[BAmA/xCE>\BANA.hCF()\BAQA.?F:BAA-L[D FN>RDBAVA,\C FRәBAA,C4E13ڍBAA-D+3.BA3A.Ch8BA0A. C.E,BA%A-:CBAA-CZ3E)=t BAA-CD8BA}{A-XC@E?BAdA-C~D(BAA-CD2BA{A-2C?EVBArA+D FW +BBu!A-jC;%ESBBA-^CVRBAA'YbCE׉ BAA#UDwE~  BAA'-D BA;A'CW:EBAҿA#-C#BARA!C<7BAbA.CƨBAkA!P(C™C3wBArA%kCJCBAA#AD `Ea*BA[A"CzFsBBA!QC>BAA!D EMBAA%CECBAA%U(CEH&GBAA&0CcXEm/BA*A'7D1F0BA`A&&*C6Eq BAA(5xCyFBAA&C EM!BAZA&D VE ?BA@A-kCPBAA$ CE>bX%BAA%B`D NES/BAA$CR|EB$1BAA!CDF;BAPA!CE^xEBAA!pCPE@L BAbfA![^C "BAA(_CDm-BB3A-vJC$EI-BB4A-wCމjEaq5BB&tA!|CS39BA/A+`$D[E>BBA,CbDS+?yBAkA#C@BB9A!DlChC-A'BANcA!|DIE<A1BAۨA!#CPDzBBCA0BD7BBAA-$Cy=F 9B!BBA-CǪF0EBAqA#%D ̝F.7FBBJA-C̡Fs4FBB(A-SBCuWF-IbGBAֻA"#CF)0JBAdA&ߏD(F|8JBA9A%DE"JBAQA#D`~F;eJBAA&Q CHxDKBAA$۬CKLEGKBAA$CR|EB$KBAA%&CLBAA$-nCEzLBAA$,LCbEnMBAA$?LCTENOBAA!=CeTtBByA!VC&PD&lWBAqA(6CpwE^@WBAA)C?F0YBB hA-xCmE_,0YBB A-COFXYBB]A-wC#FN[BAA%j CyEw[BAA%U(CEH&G_BASA!jbCa9BA1A-j D FlaCBA'A-lCDfBAA'CSE]fBAYA%Cܼ3EAuDBAA':DEuNBB#hA$CF:x;BAA-GClcE}xEBAA0CF=сABAA&MCE=шBBEA,C6DhэBAA-C;DэBA=A-zC9FEWэBACVF ӭBAA)CkaEFӷBA(A+jFDEsBAKA*D@EBBAA*D8E8HBBS{A"ƢD?BBVA-cCGFM"+IBAןA"C㦇SBARA!C<kB?fA1KD FuB?fA1KD FLBLo@eC}B@A4OCF B@DA5gDF BACA4C\F% BA;HA3sCFb#B@{*A6> D EF:-B@A6DD17B@_A6+CdE B@A4C Fn1B@t\A5DϐE";B@A6e.C EB@۸A6jCD_&pBAA5C"E#` 1B@A5ݠC0E1BAA4C4kB@A7DQCs4uB@!A6DHD4B@A7DF?05B@A6DE<1'5B@jA5KKDE467B@nA5DBB@A7*CD BB@A6ٜDCHuBASA26ClEz$_NQB@A2eD O ZB@jA6CE/OZB@u:A5_kDF;ZB@A62DQE;9\B@p{A2-=D+CFmL\B@A2=DFa\B@pA2DjTF8t|B@QA2JD EvB@ZA2a"D _E=B@уA6CDPB@A6CZDZ B@؂A6+gD7KCjB@ưA6CaD SB@qA5DlD| BAQA4$CȴB@˟A6}CE\+BAA4nCT)D5B@A5{mC>E?B@܎A6wCDmESB@uA6NDsEj]B@wA5DE=gB@wA66D#E4B@g A4Dl^E#B@kVA5rD #B@A3+D4C/B@A7 DPkCҎrBA^A2aDBE?MB@A5-C2E}aB@zA3!D:DyB@A3)DDϯ`B@7A2 D,dER)B@A6-D lF_+3B@]A5C.FN=BAA5%D;F hGB@A6 D!F2QB@MA5>sCƏF[BAJA4QRC?F>EBA3A38CIF3`OBAFA35CmFXlBANA2tCC@B@YA3!DxwF' B@MA2>7CzF#>B@bA2pCܗF}B@aA1?CF B@\AA21oC$CE'B@LA1DzDB?A2.DBn{B@/A28D EB@bA4'DE6B@UA2RD GFJB@A2DD]B@A2D A=3BADzA3|CwUD&GBA0XA3CdDB@aYA4DDB@baA4D r EoBA$A4&DBAA4`BDWEB@A6DD覦B@A6}DME[rB@|8A63DFLiB@kA5DC\HFB@~A6PD EiB@txA5MD rEx9B@A5DLDN*BASA2gDUE?B@A5jCE6B@A2TDDMB@A3D+:FWB@rA4Dz@FCϝB@l'A2DFF/B@A4D#uFGB@A7 @DC1KB@A2D >B@A3D?PE<=BA A4ƲCDڡB@~fA6_D WEk#ګB@sA5&DEwbڵB@A5DLDN*B@u4A2DE҉$B@A2̳D1E]B@)A5lCrEBA!3A41CbFBAA4lCE7 BA?A32MCyESoB@A5CE7B@UA7{Dl1@VB@A2D Du]*B@.A2D \D+#B@A6CHD*B@˓A6CD)B@W6A2DiAF?B?12A0tDB*B@A5CzEh\BAA5CELoB@҇A6C CB@nA6D%GDA4DiGB@_A7DDRB@T^A7D 8PEr?~B@A6\DB@\A7D #B@]EA7D B!1B@(A6'CF\B@zA6)nDUFB?gA4wD)E,E3B@aA85D >E.3B@OA8K*D EY3B@RA8eD6?EM4CB@]BA8CD dDe4MB@V@A8W|D dD{J?7B@@A9DuC $@B?A2'C~GF#VNB?<0A1ED$G4@pNB?A2dD6KF9{Q^B?A6źD%OpE/YsB@ZzA7\D E5vYB@aA8D>EVYlZB@8A7[DFCmZ B@;(A7uDFJ5ZPB@_A8DCtEPBZZB@1NA7D6n/F9_ZcB@\cA7DeB>/A3[#DjpGsSzgoB@!A7DlE2rgyB@A7D-kD sB?A5EDE?t|B?rA1DBtG6uB@A6DwEz4u&B@#!A75 D|F:uB@7A1DEǢuB?sA2DFFvB@U'A1C5F=y+B@A1.@խFy5B?ōA2 CmEtWzB@^A8\DrE畜} B@IA17D uq$B@*&A8.D+E/8B@tA7TD%EB@gA6D"/B@!A7DE;B@.A8D|DB@>A88 D Do_B@<A7|6DmB@WA7D toDҤwB@PA7D EB@WA7 D fEcl<B@]aA7DDB@?UA8 CqExB@V=A8D2XEB@8ZA7\D.DnFOB@BA8BDCozB@86A8+DEmB@Q_A8WiD 7DXB@@A88D H>DՐB@\A7D Db}{B?A6DFLB?A5;D$1}FB@A5Cl=EB?A6D#4CB@QA7=D fEAB@XA7 D`AqB@A7 D|C(B?'A6D(+DzUB?A4[D#wEOf_B?A5DΠF&iB?KvA4*DD>B@4A73'DF%B@-A70D@F1#mB@&AA7!D#zEd;CB?LA4?Do{DPWB?IA4DDnB@!A7*DFB?SA5>jDEv1B?A5g D:DgB@6A2DDD}%B@[A2lD E(B@0A1CTF(B?A2DE!GB@'bA1bCFB@.A2:D+E+`B?A2|nCB?TA2QD"CJ9B@ ,A2vD r'E9B@AA6DfDB?A2RD CB?A2D %-B?JdA4xDePC7B?KVA4ԼD DsB?A5=D~D'B?wA4ND!@C֐1B?JA4DBD~$QB?TRA2D`B?\A4KDj[GOB>;A3Z@DxGy(B?5A5PDE8*B>A3D vG|B?A5D7ENuB?A3D7B@A7DpE6zuB@\A7DBEuTB@LA8S-DjjD7^B@CA7C]EhB@EA7D%FΏB@pA8D V%B@`A7_D$D/B@VTA7CD 1EʒWB@BA8ADwC%cB?JA4[DTDqڗB?JA4,D ]DrB?LA4Du^B B@+A6Cm|F|xB@A66Cu FOB=؅A1bDcB?KyA4mDC%B@LA8YDTD69B@L A8YD7YDѺoB@)A6WDFQC+yB@-4A79DtF"5B#B@QA7rD(E-B@70A7IDtF'm7B@SA7*D ECB@;;A7cDDJF?B@2A7?DNF%oB@8xA7S-DLFB@A2DB@wA2D(D"΢{B?A5DGEbB?A4הDEy"BNA:BB?.BA34D!JB?d9A5D!2{EB?A6!D'Z%E:B?A6D1oE戉B?trA5>~D'1@`2Cɚ\B|2OB>]@`[CTRB?,A3MD" YC?W|KB?-fA3D!WB?,A3D"S3BO@ B)BD=GA+YCCkz|BJ@B B?~@CC2|UB>lA2QD!nB?FA5DK1B?fA1KD F[B?fA1KD FBKg@BqBC(ACm%xBKA6CoCΉLgB@FA7oD2D՟á"B?)AtD0C7BJ@BBP@BgC*:RBQPABI`D⢛̘3B@A9DD[ BQ2A&BhڸHBSA$@$ڸ8BSMAAEB@A6D!7BP^@B<2oDn ܰBA@SC1oB@A6D!7fLB>@kCBC)A%COBC)A%CBA\A!bDHC,GBB&A‰CY7BB&A‰CY7BB&A‰CY7LB@e4@ C B sLB@e5@~C B:[BJ@BBHFNA<=aIBR uAUB9X8BR uAUB9XBB=`AaC6FF!BBAUCE6 BB AC^BR uAUB9X /BBv%A8jC޶E2 DBR uAUB9XBB AC^BB AC^BBkA~CC]k'vBV0A"spA|EE''wBV-A"FҜ'yBUvA$0C>G4'{BW5A(sCF'}BVA%oBEz'BU/A%BCιF|'BWsA G|'BYaA Yú F/ 'BQ>A$CXG#'BQA(bB~C@x'BPʮA$CA3F'BO-2A%DN+D,(BQtADrGp(BP}A#=B;C(BQt>A!BzCȊ(BK~ZACDc`(BL`A C\C?)7BTA A=fFp)9BTW~A?XQ);BUA *lAw:lFbjz)ݒF6NBQ}YALxAӞFTINBQ]AT{BWOBQAMBAFAO&BRAO׊AAF5OOBK>QAKyC8Rw9BMik@pCLbREBw@dqBppE{swUBN.ABOwVBO5AEBjwWBO@EBeJFBwgBN) @ B*F&*<wBJn@CIoCzwBLV^@ζCYHED;wBL…@֟i>E7wBKT@xC1uGU{wBK5@sCGQwBK2@o=FBwBJN@DRwBK@ݘzB/;wBJB@ɃuD?1FѳwBM%0@YC9CFFwBL@=B#-EPyBM@C|EkyBN@_CE@}BGX@@CwBMBEcAOBJHFZBFA:CAF`BEABpBF7Af@BOƕBCP@A&jC6PCrIB@E7A8D nCBD`AߤC#%D@BDHAP|CkFBBjAC CgB?АAD 5qFqB>iAzCdzCcBB3Aĭ:G*CaBA)A.ÙC bBAA5CFeBA'A! C:G1zkB>A D7FnBAsA0ECU;FoB?A $GMřBISA:KpjIށ)BbB?AND A'mkB>AD8FޚIBHf;A:΅IBKKA*%&I^(BDlhA OH BFMyA#BLBEhA!nIuBAOA!Y9DWmB@FA.D HBl(BL`A C\C?pBELA BHqwQBPA ߫C3$'{BWcjA)@ߍPnBAA-ΕC3nBA}A-CBO$A%0 D<D7EcBBSA\CD['cBBACD>cBBz,A|CKE+VcBBACD>lBB~ACJ^CdۂBBg8AC9E ۂBBSQACiXDlۂBBSA`CD[qBBACCBBӊACw-EdBBڰA%CERBB#AC鼍E=jBBA C'-EtBB-AֲEYpBBoACDivBBA CfE^9BBAC5C ?BBA_C0B[?BBBAC2D?BBԗAډCyE=AdBBACEJBBsA'#CEj3JBBA!D749B>A!D7<'BQA(hB>wBJ@WCbDBK@+cBwLtBKy@PB|BK@@BjBK@@4B\BK@=BiyBKf@ B}BIv@4CXBB\ ACD6BBotACEBBpjAɖCKBz(BBAXAClD숶(BB"ZAfICE)h]BBBAo,CP0E6h]BB2vA&CD-DdEBB3A1CDudOBB#ATC;'E]dYBB0AgCNuEXiBBA{zCk3?x8mBB\LACGDQmBBDaAjC> E5BB= AaCZAbEBBYAbC_Ea,BB$ACzDRc6BB#yACcD1@BB"ZAfICE)hJBB'ACL3D)BB>:AqCE"BBDAaaC˃D@VBB>:AqCE"BB3A1CDuBBA-C5CBBGA`CgDBB"AosCn;E BBgANC`#DԔBB cABC CȔ =BBA\CE- GBB A,CkD S oBBJAfjCzD yBAyA C"DQ BAsA/TC(E06(BB AhCAE'`6S[BALA)zCDDgBAAFCgBAAؼCRdD_iBBAwCETLBAAHCDO$`BAACtRDH !BB AC/E)\5BAsA/TC(E06?BBA\CDZeBOA(ZD6feBAOA!Y9DWmeBOUA(tD7\J.BNA B@B.BNA BlA.YwBK@M~B\gmBJ3@jC]BBJ/@lCj?- @BJ@-dCOE)5AUBJ@-_CbhEBJw@5CJE%iBJ@\C CiBYPA!AABJ@CD*BJ@HC̑C`͉b(BSAAvdB=KACŒ s6BB6A5nD |0BJ@CB }BBJADEa }-1BBAD_E5  }-;BBADEG }-EBBuADjEnު }6BB*DAC\E }6-BB)gACE3' }[BBJADEa BAqA,+DM BJm;@(D5Dk 6sBNA^tKBE 6sBN2A^B}q ,0BJ@C= `BRtzA B  sQBNA^tKBE sQBNA^tKBE /BBb3AXC\ /BBACSELH /BBACCF /BByA\C6E /BBwUA CAE? /BBtAhCET /BBAJC:E  /BByA0cCDψ /BB~ABCE Z /;BBAzzC#Cb /;BBsAC4D,T /OBBuAw.C1Dx~ /iBBAgC0E /iBB{ACDm /&BBACCF /kBBAzCfEwJ, /uBByAzCE /BBq'A4CA! /ąBB^ACDH /ďBB^A5COrD9 4BVAR/Ay wBK@`>BDD? ,5BOFgA^^&BA` d5BO&A^ BxD_ HB>ƋAD  BDiAlCBOJA^q6BdVBOsA^m6B0!BOeA^B]V !BYǐA*A#\)PBSBAM}A(PBSoALBĜrBVAR/Ay'BO,XA%Ds{CleBRTVAjA@adP:)BO*A2B>C}8wBL@< B+3BI#@9CS BSoALBĜLBLAXyBBQmA\GABK/0A:܍IFʼBFIA%BS0HޡBFD9ABv^D'BH%A=SŋBIBBf}AGD9IDM?wBL@< B+3t?BK%@KzCBBb3AXC\`IBBjAC'DBB AYCE o BB ACəA,g GBB ACəA,gBBLAaCAhoB?4AD+!Xhw5BN_@AyCZhwTBNH@?AhD=WwBJX@ůCT$Q:BB ACBo%1wBK@U&BBq' :BB ACBo'uBVJNAAWDp'uBVJAA-DOF'uBVnpA E2@IDnc'uBVgAA4y'uBVUAA/>|DG'uBVe:A@yALCz'uBVbCAg@NDϛ'uBVdABE7_'uBVblAԃAD'uBVaABDW'u-BVg[A@B6RDQ'u/BVA,BvEj'u4BVA~@\]DU'u6BVexAH A#hs'u8BVk A 'u9BVA ID'u:BV{A O?B'u;BVmwA / 'uL>C ['uBV%A U@πEvd;'uBVA DNAmID!|w'uBVZAA{CyH'u}BVA}@n9B'uBVA}@n9B'uBVAn6)EGb'uRBVI,A+@~AD'uR1BVV|A 'VEăG'uR2BVLA{@\Dg8'uR4BV_AhB*DE'uR7BVeAαA>bD>m'uRMBVdAAFSD'uRVBVexAH A#hs'uRXBVgABZ'uRZBVpA 6A E8'uR[BVg\AnAD'uR]BVA EA8Q@'uR_BVDA aAi7E'uR`BVA cA%CnEKf'uRaBVJA Ao?&'uBVnA ;A_;@'uBVA AUwC,x'uBVkA 5^A6fF/5'uBV/A D&AB铳'u3BVe:A@yALCz'u*BVbA׆^EF'uBVj*A=CDFh'uBV@A!AM(D'uPBV9A DIA7L'vBV7A BLE&y'vBVmA 5mB0D}'vBV;A`D_K'vBVA"CA'vBV2A܀@D'vBV.AMDJ'vBVSAB@rD 6'vBV@OA AD#'vBVZAԯA=D'vBV>!A bPD2D'vBV;/A `Dxg'vBV8A &'A 5B[D'vBV9A @áCn'vBV7A AE!VY'vBVLA/Ac`C'vBVC*AZ'viBVA!\o'vlBV0AABNCQ'vmBV-AG>BWn'vtBVVAljAhD'vvBVDAAkA D'vBV-A"כA'vBV;AZAWO'vBVNuAARYC('vBV>A<E'vBV/A@D'vBV6A #A[Co'vBV9A ? D j'vBV<6A HA 'vBV $A!OhEe'vBVzA"ADvyJ'vBV8A 믾?Cx-'vBV>A!V@B('vBV8fA Z8BB D)'vBV!A"cE'vBU]A"hSpE6'vBVIA?D49'vBVA!BkEJ'vNBVA"vQ0TEu'vQBU5A"}#'vR BV57A 1'ASDb$'vR BV;pA 4B}ET'vR BV7GA?3D{'vR BVZA"kADD'vRBV1NA*@ aD'vRBV/FA$AR'vRBVJ.AL{DQ'vRBV D'vRBV6AA>E<'vR BV:JA \ A J'vR!BV6A AE''vR"BV8A TԂIDo'vR#BV8EA DAB Dm@'vR$BVA `*E'vR%BVF9AUD'vR'BV'A!Aw E֛~'vR)BV9 A @@ VCx'vR-BVYAѥAI'vR/BVZcAA'vR1BV[dACʔ'v@BV>A 9N ED'vBBV9A bTFDn'vCBV7A +AAYD 'vDBV0AA-'vEBVPA$?ə'vFBV6A OBBDo'vGBV8A 벾׮CI'vOBVH?A A*Em'vwBV0A !½Cy'wBVd|A;@E3DCb'wBV] A A|D'wBVBV8A __B8YEZ'w?BVA i6B E 'w@BVA eBEf'wBBVA xbBw$@E 'wCBV[*AoCk.'wDBVqA JA`B'wGBV}CA""A:[A2'wJBVA"bB+B'wOBV}A"֨AE˻0'wPBV{A"B:4Eg'wQBVqA"2BhE{!'wRBVvA"XB8E"'wSBV]A# Y\E ='wTBVnA!A>E&+'wXBV[A AD'w[BVuA"HA}En'w]BV'GA"e,Et'w^BVTA#5BfF'w_BVZ8A#&eB |E'w`BV;A"AE61 'wbBVBA!&nA_ A!V@aB7'wtBVN{A `E^'wBVKA#$B$D~'wBVRA#GB$L'wBV A WBEq'wBVAA %"D 'wBVgA!ڵA&6Dc'wBVA"pBD%d'wBVg2A!@xaB>m'wBVj%A#B [DX'wBVtWA#AE'wBVUA x0$E'wBVaAABDϚ'wBV=A!TVAB{w'wxBV A oA<D4'wBV:yA 5ACu'wBVA ;sA:2D1&'wBV{A"A&D'wNBV)UA"RAE6C(0'wR BV9 A ^lAC'wRBV:GA U2BO'wRBV>A .DY'wRBV7DAMAjEL 'wRBVSA A D-'wRBVE(A .AD'wR BV'wR.BV`A A C_'wR/BV^AAeD'wR0BV`AqApR5D?'wRZBVlA ln&D'wR[BVjRA YD,'wR\BVzA yAWEI'wR]BV"A dAceE/'wR^BV{A pbB=E/n'wR_BVA exE['wR`BVA YEE_'wRaBVA :HA/D 'wRbBV9A lEQ7'wRcBV:A RABү'wRjBVsA"$BE1w'wRkBVA"|BE,*'wRlBV:gA \JAB'wRnBVuA" A[ƒE g'wRoBVyA"aBEHo'wRqBVrdA"dC3SXE-'wRsBVdA#.FBCE'wRtBVeA! A? kC u'wRuBVnA!A hE'F'wRwBVnA 7>66E/@'wRxBV[7A sA>DB'wRyBVIA IBEN'wR|BV,A!wܿPE՗0'wR~BV6A" A.E뫓'wRBVZA#,zBE'wRBV$:A"9!E&'wRBV6A"klA `>D'wRBV=A!T$AWiC(N'wRBV: A"A;EG1'wRBV1A"/A'wRBV6A"GA,D'wRBV>A 3!B~D뙈'wRBVuA :A_E-'wRBV>iA"@~'wRBV:A"A E@'wRBVs:A#WEk'wRBVWaA#0B`REɄ.'wSBVA WAMC$7'wSBVpA#vB/u'wS BV{6A"BuCELr'wSBVfA[@C;@*'wT BUBA";d'wT BV[AAtEgY'wT BV9$A @~A'wUBVrA Aj/'wUBVA# BC'wUBVq A!A:E1'wUBVE<'w{BVj;A È~D'w|BVxA RCAd EX&'w~BVNA d)A=EN'wBV^A BAyEq'wBVA S:A7TE['wBVA ?AډC7'wBV}A ['En> 'wBVSAFEQj'wBVPA \AQE'wBVA fDAwDy'wBVUA!ygVF$. 'wBV=A oA1Eh{/'wBVqA"OYBD%'wBV}JA"yy@|ET'wBVlA!zAE'wBVv-A#ED'wBVzA [BfE'wBVbA B+E 6'wBVH.A BB,Es'wBVRA!MA.(FX'wBVOA Aք B^'wBVtA"+@s_C֟'wBV4A!rBFF'wBV;A"AE 'wBV@A"݅AF 'wBVBjA R@Et'wBV\A#DA0E1'wBUA#:@d'wBV?A ABD5'wBVeA#n>VE'wBVBOA cDA'wBV/A"hA{CE/S'wBVz^A q(CX'wBVt7A#/E'w+BVv,A#MBH4hCG'w,BVA"(@ ,ESuM'wdBV1A!7ANsFc'w*BV_AT@MD˨'wBV A MAaqDR'wBVA d@`'wBV=A!T$AWiC(N'wBVRYA#;AyE"'wBU-A"!ºE@h'wBV8yA fbASCa2'wBVvA" AUD`9'w~BVhYA#-B]/'wPBV@A 7Z'wWBVG%A BcE{'wmBVOA#'A#CE'yBUAXwZG-'yBV A"tAVUD'yBV/AA"'yBV2A^[B*9B]U'yfBVA"8AOD+'yiBU^A xEO'yjBV/AA(Cu@'ykBV-A}AڬD@'ylBV0jA5_b E`'ymBV-AmAOrD<-T'yoBUA hXwD'yzBU`A eQE 'yBUʘA xxНE-'yBU=A BIFՑ'yBVA"ֶB$C['yBV.AdAZBȥ'yBUA!IBsEq'yBVA"A[E)4'yBUAB4E'yBU}AoBE'yBU~ ABWYDy'yBU}A{dB 'D3'y!BU$A%)A,E|1'y"BUϐA&]BESx'y#BUA%$@L}D'y%BUA%jAz0E}'y3BTmA"A;'y5BUgqAAES$'yMBUA&"B *BZ'yVBUAYBE8t'yBUڹA hʿE.CK'yBUͮA&XuB.E1'yBU|)A"B}S'yBUXA#@FEtb'yBVA"B tC'yBUvA$VQF'-'yBUzA%_6=2E'yBUwA# {š6.D"'yBUgAAED #'yBVA"٪AIC:'yBUA x@E'yBVA"zo'y yBUA%X@' 'Ek-'y zBUЧA&aBAEC%w'y }BU-A%k.E3X'y"BU}AThBD'yQBUA#(>DR'yR BV@A"jž.E'yRBV/{AADB'yRBV2cA_EB'yRBV2A^vB7B'yRBV1AcB,oCM'yR}BV A"ҿA'yRBUDA kE'yRBV A п7P'yRBV"A 'yRBV.mAmBpeD''yRBU2A pAEǍ'yRBV2AeRA'yRBUA hR/OE=C9'yRBUjA s6@%}Eq'yRBUA ww@Dq'yRBUUA ]`@ Dה'yRBU-A"!ºE@h'yRBUNA# HcDkP'yRBUA"׷Dm 0'ySBUA @tE%F^'yS"BUyA"~zD} 'yS6BUA }D~'yS7BUABD'yS8BU}AThBD'yS:BU~ABC'yS@BUUA#:~=E'ySBBUA&oAMD_3'ySCBUA%ZD'ySEBUA& A]DD'ySNBUA%jW;`Er'ySUBUfA׌AρE&W'ySmBUuA&cAϟ'ySvBUZAPBE[Z'yT BUA"׷Dm 0'yUBUA 6@ EO*'yUBUvA%4E''yUBU@A-B5E%'yUBUZA#MATC 'yeBVCA>AD<'yfBUIA$AYEޟ'yfBUYA#}A-yE 'yfBUgAeAVEW4'ygBU!A$BjF9Y'yi7BUA i@<'D'ymBVA"IB _D'ynBUA&.0BD]#'ynBUA%fHR'yqBUA%Gx'yqBUABE2.'yBUfAAfE:9'y-BV A"IcEd~'yBUA &ja#DA'yBUA ;HbUEiaS'yBV0AA'yBV0AAϴ-BR'yBUHA {@OKDS'yBUAGBE'yBV A"OBE'yBUA"mEu'y.BUPA uDFI'y0BV.NAGACaE 'ybBU;A&NB3sE=D'yeBU8A%TAE'ytBUnAJNAj'yBUuA&cAϟ'yBU~AB~E]'y)BU>A"DfD'yBUA"b6'yBUA%uF 'yBUPA A&E'yBUY A#A+nEc'{SBWbA+=nEMk'{SBWQA+ mnC'{SBWZA+@D]'{SBWVA+ )BK]E66{'{SBW]gA*;xEr}'{SBWPA*Ȧ D'{SBW]A*bE'{SBW\AA)/A'A'ME"'{nBWA+AϛUE'{oBWYA+AaaE1Y'{oBWU,A)r{CL3EY'{o BWA+oBE'{oBW'{#BWbNA*V dEǸ'{'BWOA(CFF3'{-BWZBu'}ABVA “EBO'}BBVA PACX'}DBVsA m=E'}JBVA#6B;E'}OBVzA#BKEh'}PBVtA"AkBі'}[BVu}A"B='}_BVeA#{B-\E'}BVA !'}RBVA @HqE'}RBVՆAI/A'}RBVնAKzA.Cs)'}RBVŏA ?EO'}RBVA HjA'}RBVgA"B%`'}RBVA$BHF'}RBVgA#V1€bEp6'}RBVɘAˤsE$'}RBVkA%B.EC'}RBV|A%ūBE^'}SBVAbfC#z'}SBV!A B/-E^2'}SBVA$sB.sF*'}SBVQA%BE'}UBV|AJ@ArCCR'}UBV^A@ȴ'}UBV̽A[QE '}UBVFA `j@E:"d'}UBVAB\EƘZ'}UBV|A#vB~D5'}UBVA"t'E'}UBVCA QB kDZ'}UBVA"xBwDL~'}fiBVgAc@6E'}fBW,$A&*ADT'}mBVA">'A'}n@BVPA$5B'F0'}nZBVA%PADO'}n^BVA%ڱB'}BVAA Eu'}yBVAs@XD:'}BVA 3AxD'}BVA %B:GD4&'}BVqA &AGE@K'}BVA P A@{iD'}BV1A ^A8-E#S'}BVaA }A'}BVA"7sA{'}BVA"BiD'}BVA"pBD<'}BV1A$ B&ccE%0'}BVUA#RBD,D'}BVwA#QBB '}BVA7BEB'}BVIA¦E_'}BVANA=Eȷ'}BV$A mAѳ'D'}BVA$B]|^E'} BVJA"BC'}BVA"A֜XCk'}BV{A# %)F'}BVՕAKABj'}BVxA%B-'}BV{A%BE'}!BV&A^AcCDS'}$BVA 7vf%E'}%BVA$$B7ZFJ'}+BVzA#y@Ev'},BVA#/BND\*'}BV"A%SBE'}BV#A?/XE3B'}BVOAxAD;'}BV̰ArB(E<'}BVSArgE-'}BVAA fE&'}BVEA 6AWE*'}BVA NmB<&?Dl/'}BVbAj@'}BVAASEP;)'}eBVA#B.WDq'}wBV{A%BE'}~BVtA#)BhCH'}BV1A%{B4=NEU'}lBV}A$!`BDWE'}mBVqA#Bg;Eo'}nBVA$ r[FN4'~2BVA8@E$w'~3BVAj@KDL'~BV:AmZ\E'~BV-AFA{B5up'~BV_AJ(ArC5'~BV4AB@-D'~BVA@2Em'~BVVAATE'~BVApjA:DK'~qBVARA]dIC8'~}BVyA]f@*1D8'~BVuAIM?unEM'~RBVA@zlCxiy'~RBV9AG@B P'~RBVAAV'~RBV%A@'~RBVĢAAB'~RBVA=8mVEQj*'~SBV$AB6(E='~TBVAp=1D"G'~UBVAcBEsEH'~UBVAxD'~fiBVbAj@'~rBV`A]E^('~BVApjA:DK'~BVŐAAGrCA'~BVA,/@ EH|'~BVAiuAo&D5'~!BVA7C4EF%Z'~BV߈AB"F ;'~BVVAsA'~BV|ALAB'~BV-A!|@90Eu%'~~BVDAqAeDs'~BVA? J'zBUվA m0|'BUIA%x3}F*N'#BUA%Qdy'=BU6A%nA*D(]'>BU.[A%%A E;'@BUA&kAK~DP'ABTA&@ۡCw'BBUA&ɿ|D 3'CBUbA&h@EI'EBU3A%RAD 'GBU~A%`(?ME՗'JBT2A$x JEn'ZBTЫA"r8BD'jBT A#@.9E\'BT3A%E]bB'BUfoA%ir'BU8A%iA E'BUY A%jaxE7'BTfA(SBF"q'2BTEA"BaFg'S{BTAB'SBT A"{BuzE^'TVBT~A'BVE"'UBT A%ҫ~E'fBUKA$ A'fBUA}A%Ao'fBU9A%w}EF!'fBUQ=A%E'nBUA&@C'nBTA&|zAz%E'nBTA&I B DE'q!BUKA&K@-@;'UBUR8A$\5AIտE /'eBUA%dh'}BU;A%4@ATF!'~BTA%BA E<'BTA&.CBwXC='BTA%A߶gE~$'BTA&qAE 'BU WA&+B E+'BUcA%jLE ['BTA#2B+Fe'BUA&"B *BZ'BTRA"?BGE1'BTA rB"EL'BT|A"OBFI'BTA%B#Dَ'BUYA&=B&E1'BTA$`B}F,;'BU;wA%}F'BUOOA$WwFG{'BUA%Qdy'0BUcA%k0IE M'BT/A(B'BU~A%gLȴ'BTA&cA]D/c'BUfA%mDA'BT1A%\A.FU 'ABUA&n@C'LBUDA&CA`gEI'MBUA&AGʥEP{{'NBTA&'BD 'OBT&A&A#D'7(BWsA F? '7XBWtA M ?1Bu'gBWrCA Qp@A'PBWsA XsD9'QBWvA IZD+M'BYu*A kB)loFX'@BYEA"ByE%'ABY>oA!ASE!'BBYBA"AY#E7<'CBYHA"HE|'DBYKA"0AD['EBYJ?A"BED('FBYYA!7BoEIy'GBYgA!uB̟E'HBYeA! !E'IBYT8A"BE5'JBYcA"&BE_h'KBY\A"IxDm_'LBYkA!Bi EX'MBYp}A!ZAZE3.'NBYvA!gAEC'OBYr~A!cBG[E'PBYhA!CEp 'QBYm!A!F3BO9E'RBYqA!NBkJrF'SBYAOAwEg('TBYo!A BRF7d'UBYLqA!AD8'VBYN'A!yfAcD'XBY6A"0B!J'ZBYhtA"3B%B8CR'\BY\A"]BE60']BY[zA"N3B6[Ela'`BYA!+@;LF^9'cBYvA!5A{'nBY5A7@نFDR'oBYCA A3EF/'qBY|A.%{F9'sBY8APvEd'xBYACFGMC'{BYAA8AAEO'|BYNA!9BCGD]t'BY@A!ʧB'BYcA"HBE -'BYvBAA~'BYCA"`CzA'TbBY9A"eAC&'TcBYAA"7AGE<'TdBYEA"H"2ED'TeBYCA" tEC'TfBYFA!AB@tE'TjBYXA"#B=LAET'TkBYU7A"l@?Dj'TlBYg\A")BgIEa'TmBYj+A!M6wZE*'TnBYmA!ylC'TBYvA!{B =Bu1'TBYcA"k@ C+'TBYvA!AwoA'TBY1A A KF#'TBYZA }A1F+'TBYAC*UF;'TBY~A _B!=F $'TBYA7l//F6'TBYΣAT%E 'TBYAE''TBYJ6A!@&6E4Qt'TBY[A"*BD'UBYdA",^AQDs'UBYj A!TTQE'oBYDA!B D'oBYRA!AiD5'oBYWA!B*WE',('oBYQJA"@D'oBYlpA!І>ڢEZ''oBYWnA!xe[E'oBYQA"]@s%Cs'oBYHA!AvDf'oBYHA" AhKE[^'BYDqA!PEb'BYF2A!jAiIE#D'BY9A"AHD'BY>_A"4LE 'BYIA"U%D'BYMA!BvE'BYHA!A @{EF'BYkA иF<1'BYT%A!AEe'BYNA"@ xDDU'BY]YA"?D>'BYJA"ۂhEX$'BYkA!BA`EM,'BY`A!HB3E+'BY~A B!F8'BY~A C/#E{'BYfA BF$g;'BY^,A 5MF'BY`A E"'BYwAA\*C'BY&A =ANgFT'BYDdA!A;'BY:A"@ Da'BYLCA"T E)d'BYVA"lA+E 'BYgxA"G?UDt'BYsDA!moA0E˛'BYyA!&HBXeE8'BY]A"wOAD;'BYHA *J#[Ḙ'BYACF FG'BYXABqfF:'BY#AC|:Ft'BY+A %{C FB.'BYeAm?NF)s'BYAFJD('BY_A BjFl'BYL.A!AT_Cw'BYaDA!dC`E~'VBYCA"02E 'qBYA7XDJE'rBY?AԨ?6WEk'sBY?AbA@~Ef'xBYA٠@8`'BYɯACjY'TBYAeF&'TBY|Ad?][E'UBYMA@Ej'iBYAA3DJCC'kBTAcBEb_'lBR2AlA\F0:'BSAܲBq'BTUABEr' BSƛAYB[C'$BR[A A6D]'$BRسAAEU>'7aBS8A@i@F/+'7bBSdA@B! F m\': BTA B'SBT!A!QB,'SzBTA B &Ew'S{BToA ;B>E-'S|BTZGABԼF _'S}BT2DA6By^Ee={'S~BSvABE@M'SBSABEY F'SBSMA _B@QEF'SBS \ApAFAl}'SBS'ABCF-'SBRAA!F\L'UBS`AwB/'UBSKA5B4E`'_BRA_ACȦ{'gBT2DA6By^Ee={'g BSyEABbuER'nBSBA-*A$F='sBR˧A"OBOۗD@'BS bA1JA FM'BSqPA`Be8EJ'BSeAfBE-'BS'pAAA[F`An'$BTHAQBD4DS')BSkAӯBSEc'BShAØFwt%'ӡBSbAoB%|F 'ӢBSz_A*BKE4L'ӤBShqA{BSD _'WBSziAPB[r']BTAڵBN)D'BV3AAB7 Dys['BV-A#@Ax'BV1Ah"A* D _'BV2=AYAD['BV3A8hB oD($'BV@ABKl}D,'lBV3)AU A$D bq'wBV;XA AXAB'BV@AGARDw'BV.AB D 6'yBVDA'RBV1CAqKA~TDR'RBV;AZAWO'RBV7AQD 'RBV0AgA>EG'RBV2%AcADM'RJBV2YAXAyDػ'RBV5ArQ@%?|D'RBV.3As@1C.b'eBV3AEB3FDW'f3BV;XA AXAB'5BV4*A2B(Ck7'6BV3@ALA7L'DBV0AA@'jBV4A&BBR'BV0AAtE'BV; A~AX`'BV4A%DAE?I'BV4GA%B!'QBV2ASUAD#K']BV2AS[B NC'^BV3A8AD$\'UBY A 3,!'BSDA)ը@n@G<'+BSQA)/B$DY <',BSA))CB E'-BSIA(QB$y{E}'0BT@A(B[e?E*'1BTJmA(JB,`DE'2BT#A(B]atE'3BSA(B*jE{_+'5BS؎A(C#F 'BS%A)B QEK'BRA+BYE Z'BSgA)AE'BT{A(BI@F'EBS)A)/BDE'HBT |A(B:Z"EP'zBRA+BVkD܊'BTd6A(_B"D'%BS%A(\BB[E0'BTA(BףDv'BRA+XB'BT=A(&Be]E,^'BQA(zB'BQ&;A(wBQEPj'BQqA"C]@B{Eװ'BQA RB``B'BQtA!`BoEP'-BPyA TC '2BQA(KCEp'tBQoiA"B[ƓFV'vBQmA(>CaE&*'BQQ A#dqCFF8$'BQA)B>F'0BQQA# BE>'1BQYXA$5B,EB'2BQ_0A$C.FUn'4BQ9A$OBC͆'5BQhA"#’MF%/'6BQVA&*yBw7F '8BQBA'uBEr':BQAA'BET';BQ=A&BRFM'hE}'=BQA(-5Bn"D(p)'>BQA'B{D'@BQZA(fBEB)9'BBQA(eB;d'LBQA(wBnCҔ'MBQA(BD 'OBQ&`A(FChE3'PBQ0A((wBՃEE'iBQ9A(NC7E('xBQbA"CoAE_'zBQFfA$tÏF5'{BQ0EA(BMwEP$'BQ7A!4BJD=''BQ<A#AcE&'2BQ$A((C xE~'7BQA)"AEAT'YBQUA& B'BQfA"B2E'BQBA#DBDy'BQMA#C#E.c'_QBQFA$gQ(E̗'_RBQ^A$*CiFii'_TBQ2A$g’`EV'_UBQjjA" Eu'_VBQEA&(ǾF'Ĵ'_ZBQ5A(^C}JE^'_[BQ"A'B>L"E'_\BQA'C>QE}'_lBQkA(A:Eb'_nBQA)80E'_oBQA(fBD6'_BQ4A(BdEl('_BQHQA#'B"E~'_BQ>A%,SiFa'_BQ qA(CE|0'`RBQ|A'ޢBC'`WBQ A).SjE'`BQY8A#3A7E'`BQYEA#BV+-Em'`BQ>A#yBE- '`BQeA# B8YE'R'aBPA'MAE~0F{'aBQA'C=5E6'sBQ/A&C<2F) 'sBQ;A%"CMFP'sBQA'B EAP'sBQbA$crF%'tBQvnA!qB_E&{'tBQuA!BEÏ'BQpA"F;K'>BQ=`A%CaUFT'@BQDA&͠Bt*FJ8'BBQKCA'' BF(l'2BQ_=A#x`B^CR'5BQlA"BBmatC>'wBRCA*BWA'_UBQjA# IB\D1A'a\BQOA'vB`DVf'aBRCA*BWA'aBQ[A%t-BD'aBRCA*BWA'aBQZ A&7B]?C'㭧BQWA&0BgyC'BQ]A#BiD8:'㯰BQeA#Br'{EU9'㯷BRCA*PBA'BP6A) ,F'BQA(]XBDuk'2BQA)5y@F)'BPA+8Cb D31'/BPA+*B%zFX'8BQ5yA(lB'BQ A'Br@EWs{'?BQA(4AEL/'@BQ A(E!BpE('BBQA(iB0E 6i'CBP\A($6CgEK(h'DBQuA(cBIEa['EBPA)/`F<'GBPpA(>B'Ep}'HBP݀A(BE@'JBQA)oBM(E4'KBQ A(C *E<'LBQ5A(#l$ELi*'MBQA(wBEz'OBQ(2A(8 E'QBQXA) 9|E/'jBPsA'CFK7'sBPA)`SBPClji'tBPA)GB)y'yBP'A(:@Y#Fx#T'BP A+B\qE,'BPؿA*EB'1BPA'Byk#ELM'WBQA(ZBxAE|'jBPA)uB)Dqw'BPzA,MBnE'BPtjA,G=BƂDuY'CBQkA(NBqFEN'VBPeA'zBoiD:'WBPA(OnB'vBQoA(zBsDTnC'%_BPA,LC?tE̛'%`BPǶA*BF-'%aBPxkA,G BخC'%bBOA0FC.=G~|'%hBPA+BF>@'%mBP^A,9B܎Cz'%BQ A'B xDw0'&BP)A* CCE}'4BPA*]BMC92'DBQA&B'DBQ nA(BD,'E2BQA)A3E'^RBQA(kAEP'^BQ FA) hB&yEU'_OBPA*Di#Ff2'_\BPA'w4B֑F2 '_]BQ A'֋AmE[v'_^BP_A(C4KE|2'__BQA'B̓#E'_`BQ 3A(VBEE|'_bBQ _A(nCEAo'_cBPUA(AB&*E!F='_dBPRA)IAՄF BH'_eBPA)~BFz{'_gBP׳A'2B :Ejs'_hBPoA(rVBiE~zB'_iBPHA+C E(E`'_jBPunA)RBG'_kBQ IA(C?El'_lBQA(mB!EZ/'_mBQ 9A(LCE]r'_nBQA)BE'_oBQ=A(EBǧEL'_BPA)BiCM'_BPμA)C'/FSM'_BP,A'uBL:EYe'_BQA(CoE;'_BO%A)%C'_BPA+>C~EY'`QBPA'6U4Ez|'`RBQA'BF%'`WBQhA)#ZBEh'`wBQA( ,B+2En'`yBPA'^B="Eg '`BPA*LB?xFY'aBP A'BDLֶ'aBPA(YBǮ'aBPmA,Y`BNFE'acBQyA(ByoE('aBQA(PB!E6'qBQGA(jB)~A 'szBPΏA*hB(EYn'sBP3A,sC8?F s'sBPA*2CJF'sBPa{A,'䮪BP-A)B ED ''BP:A*: jtG'䯃BQA(DBE<'BPA)BpD_'BPrA+yCHF4Х'BPpA)~8F:'BPA+AE+'BPbA,yC5E'BPA*d@:FmBo'iBPdDA,ABVCu'7BPBA)CnFa'rBPRA)zF X9'BPA'B F'BPA c9C(PD'BQCmA sB8'BPvA#=B!D%'BO|A*,C.udF'BO(A)HBo'BO^A)C0E'BP IA(fCBM4Ea'BPA)Q[C"F<'/BPhA+CC*-qE'GBPbA'B+D؄L'nBPA%BaD*'oBPA%BEjO'qBP2A%mB%F+S'sBP±A'wDi#F'tBPhA'CFA'uBP9A':F 'BPnA"Bq~DY'BPwA'WB HEp'BPA(.B}Q'BPA*+lC%RFq'BPiA)dBF! 'BPA(.B}Q'BPFA)MCFD#'BPgA"eiBt' BPA$BF2'BQM^A!FK|'BQC A $`Bs'BQA%cHAģEo'BQ30A%“+F:>'TBO A*IC.^EeF'YBQ!A&[@ _FF#z'nBO9A*pC+yFDq'BOA*:C0IF o'BPA'ܞBOTDL'BPA)EB9EV'BP؈A%3C#FC}'BPA'@FcD'BP!'A(IC9X'VBPA'C,F'WBP)A(4{B:Do'#HBOfA)lGCF]9'#BQA%jB'%BQ:A B|O'%`BPjA+C ~5'%hBPA+CC'%mBP"`A*Fj'%BQ A%BD='&BPA ?C&D3p'4BPTOA*Ef'_BPA&_BCC͎'_BPA)`/C ] F#'_BPA(BmEa'_BP@iA(@B)NF'_BPA*C1r F+@'_BPA(/,BiDd{'_BPzA(X}BE]'`-BP|A#CEX'`3BP]A%BEc'`4BQA%BE '`yBPQA&:CxNFL)'`BPKA( BFd'`BP^A*mBĜE:'`BOA*}CAmD]|b'`BPA%B҇ET'aBP| A'DB?E 'aBP?#A(|AFi'avBPA'ˆF.h'awBPA(BF4]'qhBP0A($CB7Cr'sBPA*zB)D/'sBO|A,XC_^'sBOA)C+'sBOؿA)C {FSw'sBP+A%YBˢ'}#BOIA*CRC'傾BOA)ECZE'BQWNA"xBDPA'WBP^A#hBߋmFdB5'BQyA%ƦC(H'EZ'RBP:A*CrCc'_BPnA"B'5BOA)CgEX':BOɥA)PBCE!m'BMyACD'?BMpAeCDzP'@BM>ALCUE' BN&AC#D/\'BN7A FC1CN'BNۜAiCZDw'BNACUoD*'BNAC,_'BMAC+bET]'BN@A!gCgD@~'#BNAzC0D?'$BO A$C'$BOOA#~CrN'$BNK!A|C A5'DBNbAiBD<'^iBNRABC?3D'` BO$A *C[7'BOA#\CE: E5'BNA$~PCQ!E'BNA)rjD$`E{'#BOA&AD pE'$BO6A$pC_DطC'&BOrA"SCF F'?IBO^A'TeDG$D'BBO,A%D/Ef'DBOA"CF ']BN@A#C]Ef'^TBN"A$CcE'^UBO%A%D=_Et'^BO$cA&Q+DFE'_BP\ A"YfF2g'_BOA"iC Fn'_BOA"JC`Da^u'`BOA"*CjEȿ'`BO A CEGN'`BOp^A"C? E'`OBOGA!DCT"DR'`PBN.A)D'E+'`ZBOn'BOA"phCj='BNA)D! REU.'BOA&GDE>'-BO A$ɩC+'BBO A$CkCJ' BOS{A"TCF[%'=BO)A%oYDtE,'BO.A"R$C"ZF 7'BOzA"tC'F(cBRA'A0TF^(lBRzAAE(BQfAA)gE'jS(BQ{,A&_B\[D](BQ|$A#2BU}DoU(BQAB([2F.(BQyzABG|EK_(BQ4AB\fAk(BQӢAA*F0u(BQkA]{BE~(BQcbADBDQ(BQ^A5B/|Eqo(BQ/ArBEP(BQAB"v(BQtABS EU(BQEAkBܛE2(BQgAaBmSE];(BQABA6EW(BQA{BcQE*#(BQAfBS EPDR(BQe;A[BaNEJU(BQcDAKlB~Dab(BQkAmBwD(BQJhAOFBE+&(BQNABHE(BQAAB@SE!^x(BQ^eAC8 Ep(BQ`A2BTEɁr(BQNAG8C2FE3(BQmAԻB E!՗(BQXA B1E>M(BQdAƚBY<4Egl(BQAkYB8E^(BQ*ATB#Ep(BQA$B;(BQPZAVE5(BQFrAAߦ|EGT(BQ4ATB)E9e(BQ1|Am[B')D!W(BQqA[oD*F(JBRrA"MBNdF(_BQACFOڟ(`BQJA{B D_#(rBQ֙AB.K}E(tBQ AAEm(BQ0A0*B`HDv(BQmA"B#D](BQ@AB`MC 0(BQA2BnE)(BQ{PA!BUlE/%(BQdVABFo(&BQKAUCwQF (&BQA.]BEz(&BQ{A²Fg(&BQAB.;Eظ(&BQF)ATC2E(&BQ/A|0BMCo)(&BQAԏhEi(&BQuA$ABF7(CeBRA"FhBO@ET;(D?BRCApwBRF}(SBR@A1UB2@F%(SBRAAgEұ(]BQWA8B\Ey(]BQrADB7n D%(]BQABd,DJ(]BQz+A*MQEM(]BQ~uA8kA6_E(]BQABs;D (]BQ}A!TB{X(]BQ AڭBuxBZ(]BQAp&aE'(]BQy0A:!VFkV(]BQmlAwBE[_(]BQOA\E(^BQ&+A@VEQ(^BQA*B[J!E(_BQAVBC(E(_$BQdABE-b(_UBQaA DB]AC (_BRDtABFG](_BQA8C%ߎD(_BQACV,F&(`cBR-A~C*F(`jBR|A"BUAF8(`BQgAv%BoExw(`BQGA*Bu&D<(`BQAB-FE9(`BQAAڃ(`BQcAcBfDkC(`BQA Bi-ET_I(`BQABL]Ds4(`BQhA"&BAE4v(`BQ:AC E[(aBRUA#`B^ZC#(a.BQA@ Fm(a4BQ ABs(a7BQAByU]D2(aHBQeJA6BzME+b(aYBRA6B6уE^ (aBQ1An}B_D%$(aBQ?A$BsyD?`(aBQjAIBsW>DY|(qXBQ@ABVEh(qrBQAC~B''DBV(quBQ A|CyTFF(qxBR{A `BE EH(rBRA5AjE(rBQ5A"Bm(sBR[AȈAQE3(sBRUbA|A|!DDx(sBRZaA7AnEۜ(sBRA"BG(sBRdA# DBh(sBRܚA#;BeE(sBR\HApAESm(tBQGAE(tBBQAaB- ESj(tEBQt6A!2BSh(tBQA :JF!&(tBQ=9A BzE}(tBQA8|BE'(tBQmA B^'3F E~(tBQpAcB2MDm(tBQAAmCZFp(tBQ/WAzB#C|(tBQA6nX Fo(tBQeABE(tBQkAsBoOEp(}(BQAB54B*(BQt.A!B}(gBQA0F;(BQAB"mE/#(BQbAeBoDps}(XBR5_AYB FIL(BR4A!SrB E(WBPvA" |C (BRIAo:AF@FC(BQb7ABu$EU7(BQf^ABP(BQ~AeBYXD(BQ}AB#E(!(BQABdtCEu(BQAQBG6mD(BQASB40Ee (BQiA"{BE Vu(BQcA%BDF?(BQȘA"BE(BQiOA'BNE(BQGAByDV( BQgHAmB+DQ(BQjApBGVEUm(BQ~AbBQ*Euj(BQJA9B EՋ(BQTACc F (BQk;A>dBjr(BQ>#AhBpEH(BQKBA$?E/s(BQEAeBC 0(BQ]A:By'EEA(BQACF (BQKAWBEѾs( BQmAVBV`E -(!BQA.AE((#BQATB56MD2^($BQOA9C/8E ((BQAB;F K()BQABEdS(+BQA2AE(0BQGA-C`09EF7(2BQAR%B:zEA(3BQ1 AyBvD[(@BQ3ABC(ABQ8nABC(FBQACThE!1(GBQAjEW(HBQACεFdR(KBQAdZBC}DzV[(SBQAfBBxEj~(UBQApCfXFt(VBR:A@gB84Exp(`BQf|AB&s{D@(cBQQ/AkBxEQ(dBQRAB-Ef (eBQTA\TEӭ(gBQBAf/AE(jBQABOZ3EO(nBQATBIh~E"D0(oBR_Al+AED2R(BR AA:FnӶ(BRDA"qC;F*R(BQA UMB@ {E_ (BQxA!fE*X(BQ@9AԃA AAD(BQ?1AM BIuEg( BQPA9AC!(BQAB$=AECI(>BQ4Ar?AE(tBQppA"BQGlC(BRIAAдFYF (BP AC*F#(BR{A"vtBGtE*(BQMAKAEUI(BQHA]A4D̶(BQAUEw(BQͨAK BE&(BQUAYBk90E/(BQiABJ:D (BQABCC5(BRgGA#AEۂ/(BQA15BYNE!(#BQV/AwBZE(NBRA+8BP5FO(TBQmAVB*@E(WBQHA*B<^D(\BQJ*APBc6D(hBQ`GAoC [E3(xBQ$ABBE<(yBRAB5uEg(BQCA7B=C(BQFEAjBC;X(BQ;AB?$E1'(xBQE;AACE3I(BQA3BBZEX(BQ0ArBB`xE (AjBE(`BQGAWB,Cr(`BQK9AhB_(`BQCiA#BuWE}A(aBQBA^B$D<(aBQ8ABVEM^(aBQzABEdZ(qWBQY8A 1$B,Ef(sBQ2ABCH(tBQg|A%CTFY*(tBQr=AMB E:, (BQiA#gB3-TDI(BQEABvpD( BQfABI5D,(BQk^ADBbaH(BQ9oAWBmEMr(BQ|MA¯.SE( BQlYABex~D($BQIA:XB8T#E:(ABQ? AkB(FBQACNE(GBQ~ARXE(HBQAB E¨N(UBQfrACiF-}Y([BQD A+C$xEw(dBQSA@E)(eBQW7AXt Ej(gBQEABvCP(BQxAWBQ*E_(BQ8AB|E7(BQ:( BPxNA#NBD(&BP A 3C|pE(2XBPAѷBE(^BQ1wA*8BV(^BPA{BH}D_'$(^BPA{BH}D_'$(^BP=ABBdECO(^BPABE3L(^BPAmBD+(^ BQ6TABiCE(^!BP|ABRICnY(^9BQ?A z0BPA}BnE9(?BPAeBžzC(ABQAGBF!(YBQ1A CFv(_BPfA" šwF/(aBPAP-BPREn(cBQMA2C|6E$(mBP@A KC %E(BQAByEB(BPA CgE(BBPAB3E(BPmA"v!B)JE -(BPNA"ϯB1EgN(NBQ8A! ByD (BQC A $`Bs(BQBABtEiB(BPAZGBųEoL(ΘBPAkBTD. (BQ}AgB\ElD0(BQmFA#B(BQ~7AeA.E(BQAB-D(BQ{kAB;(BQ|*A BhmC(%BQmrAOBs3A"(*BQ~A=BAgD(_BQACTiE䭩(BQcA#RB(BQgA#1B](BQ(BQAB9qEC(BQ~ABFES2(BQ>IA ṡFC( BQA4Bc(E! (eBQA Bs\)(BQAsBFDvE(BQ*A9BX{Cˉ2(BQAAْE(BQԥAsBdE?1(BQʲAjBGqD(BQ>AB@EeL(BQABWE(rBQƒABxIEha(tBQ AB Ea-o(^BQABHjB*=(`BQhABmhE?(`BQ͘ABJV(BQAgBBjE2(2BQ5An?CEEHc(BQAB%o D(BQABHjB*=(BQA$BHE (BQAWBpIEoz(BQԝAQBVEfų(BQ;A*BG.E7g(BQi AB' Cb(BQfACBD(BQeMABkE C8(BQcARA(JE"b (BQNAC dEPb(BQOAɟBE/(BQW AбB7E{,(BQTArBE2(BQAA @YZE(BQ:]ABRD('BQ?A8BE(yBQGAzBF7(BQ5AfA[RE(&BQJA&f.EYH(&BQ4AiBkEG(BBQj?AjB8fhD(]BQcqAӇBm{E^(]BQP~A.CV%E%(]BQMAK}qE(]BQf/AACEqZ(]BQKAKB`E-(]BQDSA~B|(D~(]BQMAC@UE(^BQKA)xAWE (^BQ>A_BsEl\(^BQ4=ABpE (^BQB &BY*(BQNCAB6`E\(BQ5Al_aèE(BPAB4A(4BPqA"zC)Eڦ(`CBPAABEa(aTBPA"C D(BP&A"vC'FC|(BSA_AE(BRA] B9Z(BR|\A2{A.F(BRfAA'EX(BRAjAO(BRkA@ rEX~(BSA AE8I(BS9A!@F(BRAAEf*?(BRACA]Eȏ(BQ A&aUFR(BRAB3{!E (BR gABD&Ȇ(%BRAapB0F w(&BQ̺ABE&F($WBQYABTE($cBRA\KB@E]S($BQvA=BXPE*(7BSAB4F0%(7BS]9ABWtEW|(7BS_AuBEs(8BSkAAEo(8BS`A56AwFOz(8BSxFAHA mFKv(8BSAy`A(9BS%AA?E9(9BS:AA3EQ(=BQA,Aff(=BQ}A9Bhs(EBRVAvAD.(^BQ6ABҔE(`BQ}A9Bhs(`BRJAACF(`BRf A*AFFUc(`BRAGA!-Fv(`BR|A9~@rF}K(`BRA8ArF)Q(a BQ+AB`E5V(a BR'ABEiFPd(aBR.XABE(aEBQABhOFU (aFBQ.ABER(rIBR4ABRE(rwBQABAm(rBQAB BE(BS<ATB%yYES(BSeAB:$(BSzA'B)E(BS'A5 BTWE (BSNAAdED(BSA0ADCHWh(BSAAX!(*BS}vA)ALOD(.BSAmBJUE.(BR~Am@9F)(!BQ.ABER(BQnA AF( BRKA_kA]EMR( BRgA"?esIFc)(BR܏ANA:F\ ?(BRswA TAE(BRIAA#D^(BRFAAR E(BS A AYFZĢ(BRA(?n)F7w(BRv0AA*ES(BQSADB*`"E(BSdA-A(BSAfWB b-E(BS(EA1BwFaH(BSmABIE(BSAA(BSuAcAK?MF (BSeA;B' E{;(BSOAuA F (BSA,A%nF?έ(iBQlA2A F{(BRg{A@cF*U(BRAxAEmZ(8BSǑAdBd(BR>^AB#Em(tBR,A$BF(BKCAQCmF(BKAAihC2D:{(_BK&ACF(_BKAj}CF (_BK=AC:7EP:(_BLIA\C4Eq(kBKA JCnCB(qBKAIChC(sfBK-ACՄEq (sgBKAxkCtA (t:BKACuFc(BKAsCF'(BKQA"CIBPA܍CEb(BPCAԳB$Eҷ(BPAB)ED(BPVAQBE2(BPfABoUD&X(BPiA+\B7EH(BPAP4CE陰(BPAՍC*vE{(BPAO)77BT A tB+E s)77BT A Ns8Eݫ)77BTA4[eF)78BTUpA KB]C)78BTwA (U@ȔE0)78BTVEA B DB)78BT9A PD3)78BTA )/E)78BTvA CQ)78BTd A!sEڭ)78BTh A f =EL)78BTA AE>Ed)78BTA "AejEt )78BT}A ¶B8JE)78BT A K'AF)78BTA ]ET)78BT4A/CB)79BTCA AҥEd)79BTA 7WEQM )79BTTA B4Ba)79BTTA >BB^)79%BTA *AF:)79&BTA @awEϏ~)79,BTu6A mCDˁ)79/BTA cADb)79@BTA ¨Eڧ)79QBTVA eAnE:`)79BUA \AQDV)79BTUoA B%C)7:1BTA =—9tEp)7GBTcA D)7HWBTA Ao%Cxc)7HBTA ~TEO)7HBUA E@d E{)7IvBU)fA eA/)7pBU,A -@77D)7qBUTA CŪ)7BSAAsFR)7BSHA -BpE)7BSlA BkD)7 BSA BD?)7 BS_A 7@!QC)7 BSA @| D)7BTUpA KB]C)7BTVEA B DB)7BT3A C)7BTeA*'WD)7BTA T)7BTA REif)7BT1A )7BTcpA.ELt)7BTNAA )7BTA @OE>)7BT8A qA#=E)7BTvA Gk9}9E)7BTmfA AE4)7BTrA E gD)7BTA ]ET)72BTzA ObCj)7?BTUqA LBC4)7BBTTA B$B )7EBT6A %@w.lE )7KBTAXD)7SBTA MD)7`BT A I³>EAp)7iBT>Ay@ZEq)7qBTA RcAHE# +)7BTtAA Df)7BT~A_A4 @E(')7BU A )]AæEVU)7BTUA BCV)7QBTGA I[?=E)7wBTA BRE@)7zBT AA 4=4NDV)7BTqA p>ѼEV)7BTA ?ftE)7BTpA G2A`)7BT~A Yr>E)7~BUAZA ]l)7ӑBUFVA L2"C)7'BSA '@旍)7,BSuA ifAE)7-BSA *Eަ)7.BTA @dEn)7BTTA B B )7BT}A 9iE)q)7BTwA lC=)7BT MA ǼEİn)7BTvA etbE0)7BTmA *BED|)7BTk^A E3)7BTA ZoAE8ECA)7BTqA A*@Ew)7 BT{A VA[EQ)7 BTA &nDh)7 BTnA uE)7#BTA @#EЬ7)7&BTA LR4EFf)7EBTԵA !A6GkE.)7eBTȴA KA5E))7jBT A #DVF)7wBTv[A :EW)7ՀBTpA eUEE)Q)7ՇBT$A @)7ՑBTA AyEw)7BUA @pmE)70BTjA H7Cn))75BTTA B B1<)7qBT A ?8pE~)97BTA BGEBQ)97BSAYBD)98BT1A.F)98BTVAA`E*8)98BT8>AA/Em)98BT;A&CdF)98BTKA“vXEWo)98BT AJB:ZnE )98BTPACT^)98BTLAYE.e)98BT^AO*QE*)98BTtA4 A,E#)98BT]Ab_ElhL)98BT*A+EB)98BT"AAw|F -)98BT#A% NNE)99BTTAfE&)99 BT:A44$Deo)99+BTOAEhBfV2E!p)99.BTP A. )996BT0-A.C?E')99:BTcAAEd)99;BT*+Acß"Fʀ)99JBToA,Y1Co)99KBTqA\?ADx)99NBT|}AUAdiE=,)99TBT AF)99BT&7AFB&E)9:hBSAAFDf!)9:BTABՃ:F )9GBT'A?:S)9LBTyA@AhRE))9SUBUfA-AG)9SWBTeA3Ai"Dj)9SXBTeA-AFEF()9SYBT.AwJnߨFg{)9SZBT}AC~ FC.)9S[BT A\բFK()9rBS1A1DAFk0)9zBS]A+@?E7)9|BS׀A*OB1#YF;)9}BSZAP*AEL)9~BSPAFAEZ)9BSAAAEK )9BSyAB6?F)9BSAwB@EF)9BSAv@gF)9BSAbAvEc()9BT-2AsFE)9BT"AEF)9BT*zABrEr")9BT9xAarpE")9BTA k3xYDA)9BTVIAzBQEP")9BTA oo:ND)9BTZA AdERI))9BTSBFO)9ӜBSީAAF)E3)9ӟBS|A6Aʄ2FdV)9ӦBT Aw• rE )9BSAAj(E)9BT?A_8@/9Eɚ1)9BT#AE#BgEu)9BT&A~7L)9BT2AZBL-EN)9BTFOAy9E9)9BTYA}@5E@l)9BS!A mAF\)9BTRzANC)9BTZAA^/rBD9)9$BT(A`$F2)9%BT)gABqE)9-BT-AEF)9XBTSA7B)E>)9kBTA  %E&1)9tBT.tAXD)9vBT*HA; mE)9zBTA BE|G)9{BT$FA܋AEm)9ՊBThA| ˜OE)9ՋBTuA,BAE)9ՎBTxA/XCΡF)90BT#WAKC7E)9BT),A}E-:)9;BSAABKjF<)9>BS.A3Bb.EC)9FBT A+ E})9BT/AAH$)9BTA`j@㵜E<)9'BSAA#sE)9BT|=A?ApEQ)9BTrAsBFR)9BTeA3Ai"Dj):BTRAÛBcWF4tg):7BTY@!?YӦE):7BT@\< C?):7BTC@-,KF!5):8IBT>A7B<):8vBTLAWfE;):8BTdAD E3):8BTA>FDS):8BTA>BDi):8BTABj(E):8BTADBjD&):8BT8AB E+):8BTA=BeE):9BTf~AIB=2E):9BTikAoA E #):9BTBAfBB:iE6):9 BT+ABKD):9BTDAWB;ET)::mBTJAB;wC#5)::zBT+ABOC V):qBU:A :):BT.@)ZE):BT@?D- h):`BTAcDcj):aBT@E\Z):dBT@%B F ):iBTpAB)Ez):BT@lfEW):BT@MgEж):BTAZ D nq):BTA A):BTAB܊E):BTbABL):BTDA2B1UDJ):BTy'AB iE):BTpAB۠E):!BT?gASfBP En):"BTK?ANWBK,Eg):(BT1ArBJEJ):)BSdA#}AC˨):BT7AeB6'E,):BTҟ@NjE):ԀBTn@2HC):ԂBT՟@7knE):ԶBT@EO):BT@E>3B):BT @!`zuEĺ):*BT'5AB04BX):1BTAZBΐE ):6BTF _);7}BULA 9-*E!);7~BULA Q$D);8BT0A AQRGCߙ);8BTUA *@);8BUKA ,Au);9BTA u@FkEpL);9%BTڤA yAD*B$);9/BTwA _B);9QBT/A [.AFD);9BUA IQ+D);GBUnA MEИ);HzBTOA oAqE);HBU A @@EC#);ZBUA a=q);pBTZA VAnREJ);qBUA @TF4));rBU*A @FF>w);sBUDNA = CF2);tBUA 5T&Dy}Z);BU1A OG2E);BUA u•FT1);BUA O1');BTA aAp);OBTA TaARD);BUMA l@wpF);BT!A GAݳDm);BU]A K!EӨK);ӐBU1A $A$F8);ӑBU$A F*);ӒBUOA uE7");ӓBUwA BgFrv);ӽBUA 5T&Dy}Z);BUHA Q  );ՑBTfA 3Aj);BU9A MJeDA);BTA VBA|);]BU A j<%Ey)<7BVA B;F=)<7BV:A 3d.F1)<7BV:A HֱD)<7BV5=A )JE 6B)<7BVBA fE~E)<7BVRA IMPE:?H)<7BVSEA 3(EB~)<7BVA @YDP)<7BUA nAIE9)<7BV+A E)<7!BV A E;<)<7$BV~A ^BF)<7(BWfA `AsEE)<7ABVPA y7D;)<7BBVoA w0#EY)<7FBWqA ^Ex!)<7GBW|{A 0@zD&)<7HBWrFA RDhEU)<7IBWA F)<7JBWA WSC} )<7LBWA & *Cf-)<7MBW;A uznE)<7SBUA {D)<7TBUжA aEL)<7UBV;A fB`F7x)<7VBVA A>F%))<7WBVA U&F+3B)<7XBWPA RBpF#)<7YBV/A lA0MER)[)BUnA lME:*)apD)1F0)<'BW|A @Cp)8#BUz@=BT)>8JBUxl@BY8MBUh@ AI.F)>8NBUax@9jBiE+Dh)>8OBUvQ@8A/SD@)>8PBUj@6UB#fEY)>8XBUA@AEe)>BU=@A|7F )>jBUd"@YBθE!)>lBUhJ@5VB) F ^)>mBU=`@*AGF;K)>pBUOD@AZF)>BUf@4BF )>BUx@$BZCI])>BU_@YBE)>ԊBUy@@B1S_DG")>ԌBUj@jAtE{)>ԍBUy9@BY&DE)>ԏBUx|@ BYTC7L)>ԐBUo@AE)>ԬBUxp@ B^C;)>ԲBUY@bB)>ԾBUS]@ADMk)>.BUl@/AbHEV`)>w4 ?OBNA,/;C 49kB>A!`AD:E{49oB>oA!&cD E7wVBOMA'B$Z<wBL¦@B9AMDB(BQA+ B+D>?J9BB ACBoM'BYqA #AQNBRAVmD*NBRAVBuDDqLNBRAUB E2NBR;AVӓB:E7NBRAVnƈD⢳NBR AVGADxNBR'AV;A?E'(NBR.AVBHaHNBRAV{fADٛNBRAV $B53RE JNBQAVL#AC{SN5BRdAW$B|XCi9N=BRAWB{-=E /NDBRAW$BJNKBR9AVBB`UNgBR #AVeA0BjNBR AVXBNE"ZNBR AVQAեC/ZNBRAV$ACu +NBR!zAVBrESNBRAUuACB!kNBRAV~A$D3N NBRLAVtfB'xE &N BRAV#.3D5N BRAVgZ*uCEVfN BRAV*A:D`mN BRAVAUKCPKN BR-AVԯBH N3BRAVrBN3.BR/AVjBjDtN38BQ;AVbADjN3GBRAUܪAFID3N3NBR)@AVƜB}E-HN3`BRAVwBzqDs0N3BRAVJ@E%"<NUBRAVUBELgVNUBReAVA&EFNUBRAV, APDsNUBRAVBEqNUBR AVRvAD{NUBRAV 0BJGD(NUBR AVWBDNVBR-AVBբD"NVBR AVflBjD3NV#BQ`AVCABWNV$BQAVF]A6s/CNV]BRbAVAׄ!E.uNVmBR*AW\B ;BNVoBR.#AVB+NVBR"AV=NB8EՖNVBR,@AVB1EANVBR-5AV3BG 7NVBR AVv^AKNVBR=AUAUE8NBRAVSB%dB%NBRVAUAԎnC NBRAAUrCENBRAU8EdrNBR#AU ETNBR!AUwHQjErNBR(AUA[lE&NBRAUbB0bNBR+AUeA/NBR"AU9AqD>/NBRRAUNJB4QE :NBR)2AUAA~E^mQN BRAV&AqC'6N BRhAUO$BCN BRD\AU?ñE"teN BRBrAUSBtCNBR;2AUBKN9BRDAU3B2DN3BRUAUBxEN3BR&AU[E N3BR9AV BH4DauN3/BRLAV_C+E'N33BR AUVCF5DJgN3FBRDAUjADjoN3XBR3AVlB=D:N3bBR8AVG"B BDҪN3BR&AUqAE/NUBRAVEsNVBR"AU$BDGMNVBR1AVN BEF[NVBR:AUCENVBR9bAUBEkNVBR3+AUB"RE0NVBR%/AUABFE6^NVBR,AV(AC'DQNVBR(AUbAآD9NVBR(-AUNEWLWNVBR6TAUpA8ZDEKNVBR@AUA1CNVBR5cAVkiBdENVBR6AV8B9_E %sNVBR=HAUADZDNVBRC}AUBLzC͵ NVBR@gAULA[@aNVBRAU]B}uDI NVBRAU|AA$ENkNVBRAV,BVNVBRAUBPvE NVBR)AU3B Dc}NVBR AUVBi!DhNVBRAU?HB@DV!NVBRAUUADiNWBR*TAVAzE?NWBR B8"NQBR)EAUZB6ވF ?N6BRjAU|A94BlN7BRAUBRN8BRAUB5DNBR*hAVBPCzNBRAUBl/DNBR1AV2BGNBR-]AUk3‚EnANBRAUBWDE 3NBR<@AUBEPNBR.AVAE"NBR1VAVlCXEUo5NBRJAUBjCgNBRAVIACMNBRMAUqoAƤC.]NBR[AUKC gpE_NBR ATv>E2NBRAS(AnCpNBRATEpCNBR EATAQENBR)2AUAϊ#E}NBR7ATB NBRjATwBJOE[NBR}ATc›hEuNBR?AUmrFNBRATlufyE9\eNBR|ATI‘ENBRATi9B}D4/NBR'AUpEFqNBRGLAU!^L ENBRASAǿEQNBRAT4BEE6NBRAShAUnC'E&NVBR&AU :BMEYNVBRAT&bENVBR:ATg=,E`sNVBR AUCpD)O;NVBRAUZBy~El-NVBR ^ATBDsNVBRTAT91BqDNVBR'ATAjELNVBRvATAEQY\NVBRAT~=9D`NVBR"AU'#§ENVBRATEBNVBRASBFC NVBRBASځB98Ey֥NVBRZASKARC>+)NVBQATB?E[NWBRAT@p@EosNWBR jATNBD сNWBRAT@ɡDNWBRAUBDVtNWBR&AUBJF]7NXBR AT^BlDNaBR ATrBWExNkPBQAT}BN&BR+AVBJN'BR AU6+@B~A&N(BR AU*B{d@^NPBR AUH`AxLDx'NSBRATU BWBSN큰BRMASADN2BRAT.AE_JNE NBRAS%BjqEe-NBR1ASAFYDqN BQ=AU1BABNBQ}ATBtEiN'BRhATB"xD|N/BRNATSBxA=NQBQPAV%ArCIENBRhAT^f»Ea,NBRASL@NERiN9BRATBD8gNBR ATBmDeNBRAShBC#6NBR sATB]DN%BRASj`BqNmBR ATwyFIE%NsBRASB5 DC.NBR 2AUWB& DwSNRBR9AToRE-#N6BR AUcArBHN7BR AUiABN8BR AUIgA1+DNKBR[ATSB}NLBRATU BWBSNOBRATzBqA}NPBRATnnBQCSxHNBR AUuAVNBRAATEA˦EzNBR!AUBlA~qNBQBAXãABLNBQAXo%A}eDt$NBR ~AXN_ArNBQرAXAjE2NBQAX,AEG9aNBQ$AXA4E\4N2BRAVAsD'N BQ5AXBA#EN3RBQAX^A C_zNVBQ-AXlB zENVBQݲAXWOA5?NVBQAXA'E\RqNVBQ&AX`AĜNiBQAXA>!D/N0BQAXNVBRASBoD:NVBRASnAɇCBNWBRAAO} A)ENWBR*AOs3@)FKdNWBR AOk3AEΚ'NXBR\AOAC2EMJNXBRAO`ADNXBRAOrAEQNaBR ATB%~2D;.&Nk BRK`AOB-JE;NBRASB\DN9BR[AT~'@DQNvBRASvtA{NBRASBC?D<NDaNBRVAOAHDNBR\CATxA CNBR4+AO{@SE{NBR0AOnA=D"NBRAO<6BNBRASqBA7D N.BRoAO*F%IN0BR5AOkfAXE6bN%BR7ASB$D$NϢBR:"AUAKNϣBRS"ATAHBoNBR"ASfCAAlNBR7AOxA*:IEJNBQ\nAL B6E!~N jBQANh5AqF5NN kBQAN"A FwN lBQ!AMA F k"N mBQ-TANAk\E&@N nBQ2APAYE9M3N oBQ4AQzA%EҟN BQ3AOBHV'E N BQ4?APCE sN BQANAӸEæN BQ3KAQ>A CpN BQAMAF70N BQ;FANB4REF^N BQ\AKA5?N BQAO!A`D8N BQFAMB(ERNBQWAJ}AњC0NBQ3AQ A)E {NBRXAREFkNBQCANgB\6nFoNBQaAM A1N3BQ4ASTACN3BQWAJBAN3BQM ALñHF4N3BRAOgKAowENWBQAMArrF,GNWBQdaAL)R$Fu%NWBQ4AP$AF%UNWBQ4mAQB PEį NWBQAO45AESNX BQ3AR~AOyDSNX"BQ+ANCApDxNX;BQ1ANoBhEONXBQsAM AENXBR AO]ATNaBQ]#ALî\F8UNaBQXQAJ"Ai~DhNaBQ\LAK ANaBQtAMB٥ESxNk BR(AOnA NkBQ+AMkA(F@ENkBQ4APt&QF2FNk:BQ/AOBENkBQ1&AO:4B*EIfNBQ0AO#A"MGE NrBQ1AOAKE w NBQANeB0FF(INBQAMAEb`NBQ~`AM`q]%ENBQ2UAOEB\E͋NBQ5APRɺEoNBQANB/NBQAOfA\NBQ4APŦAi C^N+BQ]AThBN/BQ0KAOŭC.EN3BQ8ANA6ERN[BQ?AMBhWF=NBQmAMBDB!NBQ_AKpFmNZBQ2AO@`0ENυBQ1APZ9B F5NBQ=AMB$HkE`%bNBQ0AO8BE*NBQ=AVmADEfNBQAV15AgDAX i@F6i-NcBQ AY_AFsNBQAVASD2NBQEAX'B C>NBRWAVwBBMNFBQAV3&AD NGBQ)AV+LALDNHBQoAV*ASDɒNPBQAV)ZAaC&pN\BQAU9BLAQNBQAUÑB/PC9NOBQAV /AE!CNPBQAV,BnNQBQAV*.BsE= NRBQAUADNYBQAUADN[BQAUAaE2cnN\BQUAUүAE\lvN]BQAUqAC>DNNgBQAUwABbNBQ9ATMB=ENBQ`1AV;SRE NBQ`}AU]ADSNBQ@AYB:yNBQ`AV;ŠkkEN=BQ=AVmADEfNBQAU„B1&C}NBQEAU//FNBQ}AT}ADN'BQ6aATVAoWD NσBQ'AVAwh|F ,NBQ4JATA@D#NBR/AU|lAC|(N !BQAUg!EN "BQAU)BJD N $BQpATBVxEs$N =BQkATrAEכN ^BQUATxJP?EѻN _BQ;2AT*^¹5:E}nN BQ^AU{A!CdRN BQ]AUnTAqGDmN BQwATBDŮN BQm]ATfBmE!N BQ2AAT.A5DC`NBQ4AQAtD>aNBQc~AUaD2F8IYNBQATB"HE5\LN3BQ4AR-EN3tBQ4ASN2BQ6AS@EѮ NBRAVaANBQ{`AU!]AHFN BMAW^BDNaBQtATA/NgBQɗAUAFՒN}BRAT{ BQB<NBQBAT*n5nEqNBQ1ATAEYfNBQ3ASA]NBQ\AUR AQNBQb_ATCelaEsNBQ[AUAVEuE&N+BQuATݲAaFf7N6BQ3AS&@ELN=BQZAVAgC45NBQ4hASiAΕC[NBQi%ATu$EN'BQJASAB% FANRBQ4ARUE漺NυBQ4ARA#iDʘNBQrATB${EǀNBQAUx0A7BrxNBQAUaB0D+NBQAT!B7D.MNBQtATB&DDm:NBQATҏB NBRATiBG:AYNBQAUEB@AƦDNBQAURBNB?NVBQATAD3NVBQAVA޴YCNVBQAU9A{`DNVBQAU&BD0NVBQAUB^lDNVBQAT=B!CuNVBQAUfBC5CcNW BQŪAUB/)NW1BQ׸AV(^A;NW>BQkAU'B/VDkNW?BQAU5ApDNW@BQ(AUPKB4eCNWCBQAU)BJD NWBQAUÑB/PC9NaBQAV)ZB6;C&g3NBQAU&BD0NBQATعBaEN+BQkAU'B/VDkN/BRuATZAB[ABON^BQ"AUmB- DxiN`BQAUYB+mNaBQAU)BJD NBR AVUAC2NBQBAXASN BBRA[qBMEY9xN NBR AVm9AB E32N BQWA\gBXE!N BRA[qXB_?EUN BQ}A\Bb"ESN BQGA\B0E|2N BQWA\ADN ;BQA\B(vEN3RBQAXUAXN3|BQA\8BRE4NVBQVAXAhNVBQAXA߮CpNVBQ0AXAD,*vNWoBQA\BR*F&4INWqBR A[BBdE(rNWtBR*A[BDCNWuBRxA[UB'NWvBQږA\rBSE3NWwBQKA\XAt]ENWxBQ:A\rBBE2)NWBQýA\T BSdFdNXBQΛA\B>E dNXBQA\^BA}EJbNiBQKAXOADN`BQA\BnNEaXN0BQ֋AXANBQAXA^,E N@BQKAXA"*A NgBQIAXu?ؾDR{NBRwA[B5/~FkNBQdA\RBAEF%۵NBQvA\koB]FVNBRA[ǭBEEXErNBQ/A\[tBDHE"N BQA\0+B9,D1N{BQiA\jBJ"NBR 2A[BDLF$%NyBQGAXAHnBtNYBRi;AVۮBNZBRjAXeCEN\BR]/AWSBDr5N_BRkAXbBVqDOcNBRlAVΑB D< NBRj6AVӁB!6D#NBR~AVB NBRnAVA_ˣEnD,N RBRAWBF)aN TBRtAXBcN sBR\AToBƨ@N BRAW&:b9E٦/N BRrAVAlE rN BRAWBzDN BRpDAXBuE+/4N BRnJAY/BZI_E!aN BRAWBF "N BS !AW3BhE(ՠN BRnAXCMEtN BS7AWVB3E*N BRɜAWiE/N BSAAXFN BRAWKjB)UEN BRRAWLBE}+N2BSAWAD'N3BRrAVB,hCNVzBR]AWB*yENVBRdAW>nB#W NVBR[jATgA_A2NWrBRyAVD€ENWBRhAX:B>uEgNWBRbAWlCREٷTNWBReAVBENWBRehAXrYC E9QNWBRoAXBLE;&NXBRx|AWӃDMr1F#HNXBRo4AY,}E NXBR.AW9w]EKVNXBR$AWsBEFNX+BS AW BHF NX,BRAW gENX-BRsAW ¯F$NY#BRbAXnB=cC$NjcBRU*AW(BdD#=NkeBRqAXB7hDNBS;uAWA/NBS,AWAxD%JNBRbAW>bB0'MC9wNBRqAWC+FgNBR:AUBBW#NBRAW"BENBRoAX9BWyENBRAWB3DNBRAW$eEѬNBRrAVxA4E NBRrAWBUOENhNBRNAW!PB NBR&AW KdEN2BRAWiwB4}E1jN7BRdrAXCivrEN:BR AW hElN?BRAWBFRNKBS>AWMBvENMBSNAWCBF.-NbBSRAX*AE„NCBRaCAW?gBD9uNBR_mAWR EnvN.BSAWA NϭBSkAXzJA"NBRAW3B>MBC:NBRAXDAEL NBQAWAlENBR~AXRZAEQkNBQHAWREx-N5BRIAWNdC)DإN6BRAWc BaDqN7BRAW9_AE3)N8BRjAWQA1E&N;BRQAXOBҔDdZyNA=Du`NVUBRsAWxNB!CNVWBR AW=BqoDDNVXBRYAWB"2CtNV\BRAW4BuBYNV]BRAW.BN1CNV`BRAW% B4B,NVbBRAWB=BNVvBQAWh‡QFE|fNVwBQAWaA݅DNVBRAXPmB CJNWhBR`AXO:BDFNWBR AW}BkEFulNBRAWBzDN$BR AW%BENpBR GAWB#9C݌NzBRAWWBbiQBiJN|BR AW:BcD F!NBRHAW()B>k@*NBQAWAxvDpjN&BR AXRHAD}N/BR )AWB fCWN0BQAWAď?D#N1BQAXA(EwN2BR xAXNAxN4BQAXAE&jN5BR AXO!AYQCNuBRAW"iA>D6sNvBRdAWBBfU@O!NwBRAW 6_E]GNxBRaAWdyBا\DwN{BQAWBa%DN}BRxAVA&BERNBRRAW{B"wNBR=AW@B^/DnFNBRAWWJB*E]NBR#AX&B4HD MNBRAXP"Bo BJS2NBRjAWBD0NBQAW AelDOdNBRAW`BDzDMWN$BRAW_&E.zNDBREAWABDRMO2BRAWmB<%AjO3 BR/AVJAED7O3 BR*AV7BWDO3 BR*TAVE-NO3BR%AV^BD3O3BR:AVB {O3$BR8VAW+BuUEWO3&BR\AW Eh5O3/BR5AVAM0D bO3HBR7AVA&EDoO3LBR7zAWBsIDƨO3NBR6AVBCϛdO3UBRKAVݸB"MC5gO3XBR38AV0CE2O3YBR-rAV~BG9D>O3ZBRAWNB2 C~O3bBR7AVqB+%DEO3dBR,AV#‡EO3BR?AWazB>BuO3BROAWB@EdtOVUBR|AWmCfEOV]BRAW$kBV~D [OV`BRAW*B-EO;OVbBR AWY BEOVcBR"AWCƀEtkuOVgBR#AW7B'.EKOBR?AWۓEmdOBR-}AX-B!D JOBR7AX BXB4OABRAW4 BSBTOuBRAW* CEFOwBRAW0B4BD0ߊOxBRAWB8C0?OBRyAWxBJDOBR)AW'B3%E^<OBR!AW/BEIvOBR$AW=E\OBR-AW BE.OBR2\AW'‰٨EC)OBR8AV .EZzvOBR$AVBBOBR/AVx¾fEzOBR4AVdBDOBRAWBEOBRCzAV?BFE OBRHAW>Ba8E}qOBRd#AWqCŜOLBR!1AVBrDCOUBR!AVqyDOdBR0+AW B=E/OfBRMAWpE.^cOψBR5HAVh,B}E y>OόBR&AWvBE50OϕBR7AV}B*D1OϘBR0AVYBEOϙBR-QAV<}BFHB9OϤBR(AVBI*D(OBRAAWB6E$lOBR>AXB@5OBRAWBCUOBR1AX"B C O BR4SAWB#/DOOBR8AV@"BFOBR2AVV6B?9CܾTOBR2AVB0DfOBRAVAᕁ?OBQXAVKfAڇDzxOBQAV7ACOBQAV;AaCTnOBR*AUA,/EOBR AUAE?OBR AU$>Cm?oCOBR ATf)BvOBRAUlB'YE#9OBRAUnAhsOBRFAUAD\OBQAUBIDgOBQaAUB5wOBQAUB4TeDXOBQ^AU/BHc0E# OBQAUAODľOBRAUA%OBQFAU1BROBQAUBǼDo O BQmAUPiAC`OBQAU%BSXBO3BRrAUavAO3,BQAUB'zD&O30BRAUBsO38BQAVCAaDB ,O3GBRAUQBdHdDGO3JBQAUtBKmC OV$BQ]AVDAbNOV%BQAV8AӂDOV:BQAV2eAnOV;BQAV6ANC|4OVBOVBQAUMB(bDgOVBQAUBDBC.OVBQAUmOB@ZD߃OVBQAUbAD3SOVBQATB8 DOVBQAUtB[/B@OWBQAUB7QBӗOWBQAUB$fDOW BQAUÑB/PC9OLBQAUzB+1CD -OgBQAU|B]D$OBRAUB=qOEBQAVMEWOJBQBAVfA1>$COZBQAUƁBMeYC$%9O[BQAU.BhpICO\BQAUUBUC.OBQAU̦BwChOBRAUZB:pDVOBR ATcB| JO BQ}AUB]D<O BQAU_B35DOBQDAUB6DOBQoAU%BQD&OBQAU{A:DZOBQATAmOBR=AVB*$OxBQAV/ADQZOϊBQ%AUBHfDZTOϓBQAV^AB-DzO7BR:AUB31D(OVBRdAUaAA׺OBRAUAyOBRAUAΚCoW(OBRAVB)^B4AOBR;AZB:EDoOBRAXP!AWaCuOBRAXQuA:C rO;BRUAXPB|C'OZBRjAYoB]BD뀧O]BR7AWACqO_BRhAXC)=^EO`BR+6AWٟC|=E|SOaBR'AX3AE#_OdBReAYBNEkO BBR+A[\ BUE nO JBRR4AZ&_BNEՌO QBR+A[fB4EfO TBRgTAYct=EO UBRKgAZJB](E6yO BR>AZBF ɱO BR'MA[lBCELrO BRAXR4AٓCElUO BR\"AYBcDXyO BR3A[pB?DO3BR`AYߡBTD"ʂOVBRtAXQAKCcOV[BRiAXPAA{OVcBR'=AX1B~D OV|BR8AXB:yDOVBR%AX ¶Bl1O5BRAXR4AٓCElUOBR!AWB CھOBR4AWhBCOBR"AW3BzEOBRAX„KEw$OBR&qAX:)BQAEWROBR1AX%BQCOBR62AZBdLDOBR7AZ(BcEOBR?AXC+FjOBRUAZ bBS^EKOBR A[BBdE(rOBRAAZuBlÖOBR A[D4B6E?OBRAXSAA7QOBR6AZBCE$LO BRA[дBLxDO|BRqAYNBUOBR\RAWB.ChOEBRyAXOBuDOόBR"JAWXB+OBR43AWF^E~O BR2"AXB!OBR^8AY&BREOBRKXAUA֒%CqMOlBR@AUB^D[ĒOyBRFAUNJAgRE+OzBRMAU/BBD^OBRHAUK@BDq/OBRLAU]AיE<$OBRNyAUBE*OBRm(AVB-ёCwOBRLAUprBoDz8OBR[AT~,@K DRjOBRvZAVAEO sBRY AT]AD"O BRP&AUCCErO BRI\AUAbEO{O BRJVAUOAE,OVBRF%AUGBюE2+OOVBRHAUQ͏TEOVBRVoAT@`EOVBR!AVB+OVBR&pAW5B;@VOVBR\ATwB ZDOWBRLAUpADyOWBRMAU CUEDsOWBRMAU6AEDOWBR?LAUs{C%^!EOkVBRBAUoADOkYBR@AUԢAhsO9BRXATAtDO=BR:,AUA{BODBR&AW6BT@A8OfBRDUAU?AaBOBRQATNB@::DO6BRVVAT APOBR@IAUB"]DOBRIATADD'OBRHAUrm/E@`4OBRE*AUjBE)gOBRN AU#ADXOBR7AUzq'EBOBRX'AT@DOBRN'AUA;-DOBRMATBErbOBRT ATBIBE3OBRTlAT:BY;E6OBRJAUAIOvBRGATA’zDOψBR9sAV!A-OϣBRXATcȎDnOQBRKAUA|BOWBRT;ATA{DOXBRUATB E8ROYBRX"AT@bDOBRmAV0B6D2OBQ;AVbADjOBR9AVAljDO C"OBRAW6B9D7tSOBR~AWzB*[BOBRAW.wBOBRrAW6AOBRAV4AܹAzOBQAVyA^.xD 9OϜBRAW$BJOBQ2AV*AxED{OBR>AV &ABOBR2zAUBCDOBRHWAU:AxlBKO BSANkB-O BSWAM!AhsOBSznAL3AoOBTeAYڜBgwZEOBT:AOBHe`OBTDAY=B&EyF&O2BTuJAWOBo%O2BTAYtBF.O3pBTMAN;BBAM'O3BTWAYBAF "O4$BTAN{CFOX6BSALBB.FOX7BS{AL^AOX8BSAKAOX9BSAL(AݢD[NOXPBSALNB E 3YOaBS|sALUS¯tF"ǻOaBSAXBB+ɹE;ObBTx;AWBЭEObBTwAXCFOb BSANBGF;vOb"BTMfAYBbFcOb&BSANfyCAF~OBSAXBNDcOBSAYB(zF4OBT'ANcCF`OBTxDAXl!CEOBTxAWBmh2DOBTpANGB'=DOBT#AYBbF#BO,BT>AOB5iyODBSALiA/nFlWOVBS_AK1B OWBS6AL A!OXBSšALA֮O8BTS@AXſFNKO@BT)QAO)CFqOBBTPAYByF oOFBSAN*B!,O_BSڳAY(FA9F O4BS,AY?BKyF2OϰBTeZANpqB)EqQ~OϳBTwNAXCFeFXCO BT;AXƉÇ5FOdBTMAN9B3ҌFȕO BQ.AClB+BO BP_A@.BXGOXFBQ ADNB %KFZOXGBPA@^Bv(OBQAEB3haCieO BQ]AC%BmO}BQBAO BPWA9$Bc#O BPevA9oBkdD(0O BPsA9eB3RO BP1A9AB<8E -O BQ4A;SAhDO BPJ.A8[BVDOBP'A;wDB:E^OBQA=CEO48BP>oA94#C@E<O4BOVA6#-BFF3_O4BOA6aBgEO4BPA9,CaFpOO4BPA9C\$F|BO4BPA:BpyOX&BPA=mpBOXBPXA9BOXBPy,A9#BtiE#OXBPA9\BtFP%OXBPSA: AOXBP=A: EB%/DlOXBP&A9œF]uOXBP`A9BcwnEDROXBPhA9rBvOXBP8A8BUOXBPA9VBxD=xOXBPmA9rBhOXBPuA9˘BUD[OXBPA:EtBF7OXBOϵA:B;ENOXBP+A8C]FR:OcBQLA=ӡESRXOcBP}A9BAOXBP)[A9rClD$FO BQA>DBFO BPBA@d1FwPEO BQjAEBDBiO 3DO BPfA:$A(_FO BPSA: AO BQbA;R-CU4F*O BQA=&MBݞED;O BQ /A= ]F=ƨO BQ%A5B,,FiOX$BQ|AD-Q@lF2OX%BP>AA*-BKFOX&BPA=TD>OF}yOX)BP/A@ A=FOX/BPAAQA(FYOXGBP;ABe!B7\OXHBPAABG E5OXBPABC!FQ~OXBPA9:BJE1OXBPA: _BpDROXBPFA:g@\EƘOXBQ-A=ɺBY^E OXBPA>GDQlFDOXBPmA;?CHFOXBPA:)}XEOcBP4A>8DB} DOcBQ *A;CFhoOlhBPA;BZEdqO3BQ AEB*E:OBQAEBZp]D|O\BP*A@1AHDCzF1ONBQAD*AEOOBPA@ BFzOZBQADB2AOhBPkA?uB3DOBPA:{FOBPA: B. Dj>OBPA:jEuOBPњA:BEHO BQA<BFUO BQABZDWOBQO BO]A:BGCwOXBQTACBPJOaBQ8AN,OAjqDOaBQ9AEzBh>wODBQ^AE9ADmBvOBQADlEfA@O BPAD>A>E`iO# BOfCA^BDEO# #BO?sA^&Bw=F*OO# %BOZA^JB*EO# /BO?A]B Fr9O# sBOrA^ B|Es@O#BOFbA]CPpFuyO#BOn~A^BۢEGcGO#3BO7A^KBEKO#b BO(A^,mBSCFG)O#JBOL}A^CgjFZO#KBOYA^BbEO#eBOi A]C\F(ۻO#oBOA^rBaОE.gO#pBNA]GBmDNlO#BOuA^hGB?,D9F~O#%BOA^@F" 1O$ BSPAWB JHCO$2BSAXADIO$2BSlAXT"BߪF:NO$2BSAXlPTF1 O$BSlAXT"BߪF:NO$BSaAXJ8Au#F O$BSAXC>'F-mO$BSĆAXB./O$BSAYO%2BPAZPAFO%2BP?AYB(2E{O%3BPAVA9O%3wBPJrA[ Aۛ'F0O%3|BQPA[BAELO%3}BQ2AY(SEO%WmBQGFAZWGF-ЫO%WyBQYA[rAjSBELO%W{BQ@XAZB#EO%W|BQ?AYvB_ADO%W}BQ!AYճF==!O%WBQ1mAXB5qDkO%X BQ_GA[AkõEnO%XBQ@AZB1?nEO%X0BPAYebB',FMO%Y*BOTpA^BD6D 9O%Y>BPfAZWB'@E7CO%Y?BPtYAZDBqFR$>O%Y@BPnNAZjB*F#.O%YBBOA\IBK=FQ"O%YCBOA] BUKtF]UO%YBONA]BxEJO%YBO~A^JpB9O%b BOA]BaE2sO%bPBOA[BTF#(O%BPAW`AAYjLAFO%BPAZBID:O%cBPyAYyBH3-CO%BPAZZBQLDsO%BQEA[A[EO%BQGAYh,FvO%BOUA[B\F}O%BQ?AYRB1DoO%BQI5A[qB WEn=O%BQ>AZDZBkEZO%BQpAYFm$F9O%BQ4AXB+Dpi>O%BQCAYouAEO%BQAAZ_AE4O%4BQAY2B%EEO%PBQfAYE¨F@O%^BPAYB?EO%_BPAYA'FO%bBOA[g#ByF|O%cBOA\BF;^O%eBOIA^mCBGoO%BOA]4BFABO%BO*A\fBNtFO%BOnA\CBUFGO%BOA\I-BhDEO%nBPA[F\B;ͦFjm?O%pBPBA[:B^ F;98O%qBOA\6Br\)O%BPMAVAraEO%BPCAWAbEo%O%BQ AV~AO%5BP^AZlnBDvbFm&O%?BP`AZmBGF O%ςBPAYB7 E8wO%ϷBPnAYBBALFbO%ϼBQFbA[>B\EIO%ϽBQAYN‡FGfO%BOA[BOCEO&BTCAOBIO& dBRGANnBFO& eBRNAO(F?rO& BRAW5`B/f|D*O& BR,AVB!UED%O& BRAOA EO& BSg]ALMA܏F9O& BRANBF;'O& BSAL~C_FQO& BSWALBؗE O& BSAMEA]EqO& BSANLAF^nO&BRAO@ArO&BSi:ALbA?}O&2BS ANj B#FHO&2BS|AL0FA|O&3BRAAVB+O&4BSAMYA'FUO&4$BSANK.B Fb-O&WBRAOŠErO&WBRGAOB\FF:O&WBR AUAO&XBRAPB3EO&XBS#AMA`FlO&X$BQ#kAG6A-O&X4BSANVAFO&X6BSqAL1ktF'O&X7BSk8ALo1ϸFdO&X8BSJAL5¡E O&X9BSALyJkF`?O&XPBSALqBDWmO&XBRAP/AIO&aBShALJAFBSGALAoFO&TBS1>AM6A F;XO&VBSALB-g!E"RO&WBSALX4GYNF0;O&XBSAL4CEO&YBSAM6C`@FёO&BR AOxBL%F "OO&-BRANSBFO&.BRAP!}BZO&/BSxALASpO&BRXAOAxuEwe$O' 'BMA]BNDYO' 0BNnA^בCaF<[O' 1BNaA_ fC EGBNA^ԺBjF%O'BNA^OBWEiRO'BM`A]BFfO'BN`&A_0BdO'3BN AY'*BrE2lO'3BNAY7BɾEO'3BMAX6uB[E1O'3BN=A^MBa'F1O'4BM)AX^B"EEO'4BMAXB+EKDO'YHBMPA]BڢF?O'YPBNsA^Cc.FNO'YQBNUA^CEͱO'YRBN A^?BۂF,BO'YSBMTA]BLFdO'YTBM;A\KBZFjɦO'YUBMA\En&O'Y^BN{A_C TE~O'[BMAX)B0aAO'aBM6A\BdFO'bBMqA]l+BxCrO'BNoA^V/BO' BMA\ƚBF&O'BMAXFBFQO'BM}A[֊B0JE}O'BN"A^z?BߞEpP6O'!BMAXBxFBN-A^BLO'?BNvA^tpBN&ExO'ABM!AXB[B?E'!O'BBM'AXBEAtO) }BOmUA:Bd?F $O) BOtA:bB܈8EBO) BOP_A:`AJA9ǯB<O)4BNݚA;:BYF8:UO)5BNLA; B/A=O)?/BNA<~uBVF0DO)?BNA=,'B{E(O)XBP'A:%3BDO)XBO%A;!B5D5SO)YBO!A:VBLO)YBNoA:B(EjoO)YBOA; BweFO)YBOA:]B"F`gO)YBOAA:ڌBuEk0~O)YBO(A:*B EO)YBO=A:ҩAF+6!O)YBOA;OBJvFW O)YBN A<.BE@O)^BOؐA7)BGO)BOȊA6BW O)BO^A*C_O)OBN˂A; B(EO)BNAmO+BNA1`C-9O+(BN^A0,?C@EO+]BNUA1 CT*DBt0O+BOnA,CSEXiO+iBP\A,8B`O+КBO'8A2I8C 7E5O+ЛBOWA2j˜-F^?O+МBOIA3%Bnj=FXO+НBOEA2OCE2KO+ФBO.A/aCEIO+ХBO@lA1FCJE^O+ЦBO>A/CEuO+ШBO5A//(CPEN\O+ЩBOCOUBN?A?C D lOUBN>A?iC xAևOUBN>A?C OUKBNFA?BؼyD5E=OU۫BN=A@ZChCOUBN4A@!CYCOOV tBNACc DnOVBNfA=NCCǺOVBNKA>C'qE]OVfBNA;BEOVBNU A>(CZDdOVBLFA@|CsOW lBLYA@CSFyOWBLsA?d C:1EdOWBL+pA?CoOW5}BLHA?GXCmOW>BLA?wC@fFdOW?BLmA?TCKlE_OW?9BL7A@C @D2OW?BLȕA@NC# EzOW@.BLA@v:CȡDx%OW@BLA?#CIEOWZJBLA@CtDaOWZLBLoA@BC~OWZBLA?6C5(F;OW\BLA@BT{OW\BL<A>CgF;OW\BKjA=CaC-OOW\BK A>d C=qOWBLjRA?J\Cr"gE `OWBLJA?8C@HEئMOWYBLXA@'C'EڄOWBLQA>CB5DF OWBL-nA?C]DzOWBL$A?7C5| F+ OWlBLDA?9C;.E7OWBLA?ČCDFOWBKA>CfC|OWBKB4A>D%CFtCyOWBL A?CAOD7YOWBL^A?IC}'EyOWŌBLA>5COWBLnA?^C5.F 9>OWBLʑA@g:CLFa*OW BL(^A? C{EOY BNA@B窿A@OY BNdA>-gCgE'OY BMA@pB\E5OY BMAABMQAArBخETvOY?BLA?BCG]D>OY?BN,A?D?ƒBןC2EbtOYBLؚA@C@EOYWBMAB4C2EvaOYSBNKA>yCEOYYBMA@B@E OY]BNFA?FBΞC0OYgBMAACSFAOYhBMoDABBoWEQOYjBMnAB1B͔EUOYlBLAALC FrROYBNzA@h B[EىOYBMABB+tCD8AOYBNE-A?BDCOYBMABWCEUOYBLoA@BC~OYBNvA>[BШFpOYBNFA>FBCOYBLڧA@CEhOYwBMAAs`BEuqOYBMABC-E/O`BL!AXCMD{O` BMLAX\B텥D;.O` BM@AYoDFxO` BM`AX&B݋,F3qO` BMCAX/BV^F/O` ABL3AUiC]/O` GBL>AXDCK#O`4BM3A[C=E-jO`4BMAXBɺEiO`5BL޸Ol BLhA[KCODȌOl BLA[CeEdOl BL`A[@.a!EȪ9Ol BLHAZNC3Dh Ol BLGAZ;C3EJOl BLr+A[C,_C"MOl BLA[CYbPCnOl BLeA[FBTCҗOl BLjAZsBE@;Ol &BLTAZMCffF= HOl 8BL`:AZңCdF<Ol ;BLA[CH9EwkOl =BLT-AZC_F[Ol ?BL=gAWCE=gOl CBLkAZVtB*EOl GBL>AXgCOl HBLVA[BOl IBL?MAY(mCPDOl KBLA\C8mE ͵Ol MBLHvAZUC/Dʾ%Ol PBM(A[^BFOOl BLA[C7,COl4BLAXRC DOl4BMZuA[}C CHOl5BM /A[CF{yOlABLA[ǸC aOlSRBLSAZACYF6MaOlZBM]A[BѵEhOl[BLRAZC4{EOl[BL\AZCJEbuOl[BLTA[DCGRrE5Ol[BL^AZCfhF?Ol[BLzA[C>E;Ol[ BLYA[B>{E|6'Ol[7BLszAXCI[E9Ol[]BLBAXۢClF2>Ol[cBLLAZ+B9,EzOl[kBLtA[CeEq*Ol[pBLA[C=FjOl[BL{A[CKxEf7Ol[BLnQAZ:B,E+OlaBMSA[}C'TCtOlBLХA[Cf F' OlBL8AUCyOlBLuuAYBGEgOlBLA[C EXOlAXCGEOlBLG7AZ'CkwOlBL>KAX\CwOlBLA[CE$OlBLA[DC#FOlBLzAYBpBgOlBLA[CQDOlBLAZyC׀F-OlBLgA[C'OlBLA[C\N EZOmBKAaDB4DfWOm4BK`BccEFOn[EFO{BM)AZ=CkFZ*O{BM{1A[CkwDO{BM@A[׃B4fFDO BKBAJɂC[E=zO BKY/ALDCFjO BK!ANzCDEǁO BKwAMBCyFSO BKANCE*O BKAPCVE bO BKƸAPCE!O BKAPKCKEO BKAAJCEׁO BKCPAK5=CxuE!O BKjAQ$CAO BKorAMٹCHsO BKh{AMvC'E OBKmAO=BDqOBKANCKpFR|7O-BK?AKCeO5ABKAQRCDFqyO5\BKAPOC˿DeO@BKMCALW[CۣE!@O@BKAOjCF DpO@BK]AN8C;DO[BKIVALC'E/O[BKbAM85C=qCDؐOсBKOAPDCdXF OWBK_AO;CD OBKOAJCFAO5\BKAOC?\OGBKEAKŸCzD`9OBKFAKCboOќBKAOBDwOBL)AU CdEO ?BL:AVc-CjEO ABL2AUCC۽O GBL=AXCEO UBL1AVCFiO \BLAScCO BKAQC:E)O BL ASGaCF,uO5ABLAS3\CE`ZO6BKARjCOABLcAXtCViG-r O[]BL=AXC+DpO[aBL=>AWCVEvErO[uBL2AV*CFO[BL-AAUVCEO[BL;AV*C0bO\BKARCϙF$O\BLVASICaEݣO\#BKc"ALCFCJOBL<AV@]Cv\D:jOAKlCyDTOBKLuAIoxD[EAOBK>vAHCCNUOBKDAJ~CMO5BKOAHC:EOBKIAJ1C$RD㰄O BKLAJ 2C"O BK@AK5C;#OBKJHAILD*\F*O#BKU^AICE9OgBKK~AHCx'E%tOiBKPdAHBCEOjBKR6AHCi=-B;zPOBK^AHICh-C}ܚOѪBKMnAIHCT*E?OBKCAHCkFBOBKs A=9CEOBJrA=iCVFOCOBK*A>?CoEkOBLEA?CC\OBKA= CEOBJ\A=2CF;{OBK8EA>SCaOBJA= CtE]eOBK A=CD1OBK?A=CwFDl)O5BKIdA>%2C8EVbO?hBL&A>C2F'O\BKA>CF^O\BK1A=C¸F+O\BKA=[C0fEaO\BJA=ZCtWEO\BK!A=5C SF-_O\BKA=CaE/[ O\BK*A=CbFPO\BJTA=}CM/D.6<O\BKZA>CDSCzO]BKkA>"6C4FkO]%BIACXOBK6A>yCd*E8OBJR_AoCtFO]BK^KA=#CCx@OBKA=qCGF]OBKA=CTECQOBK`A=dCFOBKOA=vDC`FmOBJA=tCKfEOBKFA>CEOBKBA=XCD( SOBJƥA=lCբNOBJA=րCmxFOBJA=C%O%BJ*A=CۭD}O4BJA=oCF9n3OBKPA>1CQD3OfBJA=0CxDOBJy.A<~CEEyOBKW_A=COBKA9"FC1rOBKA>C8OBL7@A?!CC3O'BKA9"FC1rO5BKA= C|ODZO5BKQA>CDO6DBK+A>RC(O?BKA9"FC1rO@=BKA=CC@O\BK\A=`CBDO]BL A>sCtFQIoOueBKA9"FC1rOhBKMA>(C3CO5BKA9"FC1rOBK@A>JCO]BKqA=dC<\DGOkBL8A?& COBKVA=4C~COBKA=~C>D/OѻBKA9"FC1rO BL&A? Cm7C&OUIBNEcA?wCCOBOA(ZD;qyO4BN$A0^D3O5BNA0DߨE'O?IBOA'JD>DyO?BO6A$pC_DطCO`uBNA)QD4O>BOA'2D@DMO_BK|Az@CMCOBJPA<C?D0OBJDWABJ[A=εC;FhoO>BJDAD%CFtCyOBJFBABAA CD(ueBAAD YER'ufBAƟA ^C[EuBAA C>D]w3_BNQ1@\BJEΊw3@lBNV@뼥Byiyw3BNo@ԞA33w3ZBNJ`@럧Bcw5BN@`@X@DS:w5BN@AD(#w5BNO@\BKuDiw5BNV@A06|D8w5BN@zA_EVw5:BN<@xAPD|acw5;BN@Aȓuw5ABN@3AC/w5dBNm@}A`TtDXw5dBN@VAXD@= w5u4BN@ԄBǯE;Ηw5BN@ٮ8w5BNp@VuAqIEw5BN@AD% w5BN @EAxAw5BN@AAw5BNY@ԤAsBKw5BN@ԊB lEdzw5BN@ATDPw5BN@ӇAЊDcw7BM_@ B)~D5w8kBN˰@NBsN D'w8pBNV.@3B2F_w8tBNu7@BEw8vBN~@TB Jw8zBN}@,-B1iE5Ͱw8BN@*.B~FEw8BNW@2DFxw8BN@wB̸Fw8BNm@]/B6Ew8BNs@BEçw8IBNK@`BwEqw8KBNQ_@B5w8LBN^@n\ XEw8MBNe@olAn^E«w8NBNE@vBkFE0w8PBN}@fBEEw8QBN[@Bw8RBN@B掫Cݳw8HBNR@4BSEVw8IBNW@$BE{w8JBN@BE =Qw8UBN@ BUE}<w8BN@LBEi:w8BNu@BYFw85BN@BrB?w8SBN$@BYEAw8;BN{F@BofUESw8@KBNo@ߐB^EQ{w8@[BN@RBo#Ew8bBNr@8BfD|w8bBNl@)BE1w8bBNq?@ BnEw8bBNy)@BEɣw8bBNyb@|B'JEJw8bBN@'B ELNw8bBN@Cw8bBN}@lBEz*w8bBNO@*B%FVw8bBN@/B.]CRuw8bBN_@8B~[Ew8bBN@BiC:w8bBN1@!BtNhFY|w8ckBN[@}BA#Dlw8cmBNgU@B[Cw8cnBN2,@LBƩEסw8dhBN@lB'LEww8diBN@Cw8dtBN_@:BoDORw8duBNr@,B;yEHw8eBN~@4BIuEnw8eBN@Cw8e"BN$@fCoDJw8eBNW@B%EX>w8kBNj@BZD`w8NBN$@D-CDZw8TBNm@]/B6Ew8BN@rkBWEtw8BNc@jB\w8BN3@eBEw8BNg~@Bbw8BNk@~gCLaFKw8BNs@nBz*Evtw8BN}@.}B7Dw8BN~@trB YEBw8BN[@BA_4Dݶ w8BNc@BWDWw8BNe_@BuE iw8BNd{@95aEQw8ABNz@BBiEw8BBN@BuD'dw8BNm@WGBvByFlTw8bBN18@6zBE]w8BNt@BEHw8܋BN`@4B36E+:w9 YBM@@BGw9YBMt@]C(Ew9tBM@OCEww9zBM@zCGw9~BMp@ CXXw9BM@zCGw9BM@IBEww9BM@BC5EJw9BM}\@B%BLw9BMa@qCw9BMp@8C%KEw9BM@BC5EJw9&BM@ZCDGw9=BMx@8,B$E\w96iBMl@C8EjrOw9BcBM}]@ACnW@9w9OBME@yCCw9OBM|O@FCNEw9]yBMV@WCW8w9dBMl@'B[Ejw9dBM@rBEVw9dBMg@1BEnw9dBM~@B4CEw9dBM@;BT'C,w9e BMu@체C1E+w9eBMw@ƓC8Ew9e\BM@QBD'w9BMe@TCBO5D6w9=BM@rBE;w9BM@HBBEw9BM|@NCXE Nw9BMX@6CF ۹w9BM @qBMEDtw9BM@rBE;w9BM@eBϲQD3w9BM?@mCEEw9BM@\CFkw9BM@B+D(w9BMx!@CoERw9,BM}5@>uCZB̑w9.BMd@2CBBɀhw90BMyS@C8E|Sw9fBM@/B.E2w9}BM@iBw9ҩBMY]@kfCgLEw9]BM@rBE;w9ޣBM}_@BC\@-E'w:BMv@$B1Db}w:BM@XCjEMTw:BMn@B Ew:BM4@ꯒB,E aw: BM@gBE*=w:.BM@WB3D=w:#'BM@]BoCsw:AYBM$@Bj Dclw:A}BMG@@+EEVw:dBM@B;D59Jw:dBMz@=BcE-Vw:dNBM @B賈C;w:dTBM@xBC<`w:dVBMy@Cw:e BM}Q@@C1w:yBM2@CBmEzw:BM@BsD;w::BNT@PB@D"W`w:JBM@B"ADw::BM@5B}TD}w:;BM@C &Etw:'w:?BM@ꐶBЧw:ܡBM}\@BBu@w:ݙBM+@WBD/w:ݽBM@1CEvw;RBJ@Y5CmxCw;OBLo@QChADw;ZBM @vBCw<BNF@fB~w<&BN2@BecFtw<'BNZ@vB<"ECRw<IBN[@}BA#Dlw<LBN_@:BoDORw<NBNg~@~NBzACoTw<^BNR@AE8hw<`BNM@BDIyw<6=BNQ@ B|+w<@1BNU}@BZY'EwC Cwgw@BM=@CDCZwB=BMz@vC/DZwB@MBMk@0C! CwBdBM@zCGwBe\BMo@WCwBBM@J(C1wBBM޻@CCJhwBBMr@:C#q'wB܍BMJ@aCp D0wC5BMʖ@CE\wCUBM@lC8EwCfBM@CDnwCmBMԙ@CjC`uwCpBM6@C)EhwCqBMߝ@uC(|EmIWwCBMɻ@5CqwCBM@^B)wC%BMž@m|CEd ,wC=BM@պCE%wCcUBMQ@bWCyE^EwCcuBMe@HC E|wCdhBN@BwCdBM@!VC2EnwCdBM:@&CEM@JwCdBMB@B?E3_wCeSBMד@QHC4EnwCuBM$@eDC}xDwCBM@$C E0wCBM_@OB-ESwCBM?@LCE2PwCBM@պCE%wCBM@8EC E?wCBMȪ@ζCOEˆwCBMF@ﯕBJ=AyPRwCBM @BCTwCBMڣ@`CEwCeBM}@2CE7q]wC|BMٔ@CDwC}BMz@vC/DZwC]BM@^rBDwJBO6A WCE+cwJ;BPNA CwJ=BOA mB1{DwJ=BOxA BDwJ=BOoA 6BC|wJBOA C v CTwJBORA :BB!"wJBOA ʹBwJBOAA B*EwJBOA "C9$EBwJBOA CD\hwJBOA BDwJBOA K.BEFwJBOkA vBBESwJBOrA XB_4ChwJBOA 5BEwJ BP#A ;BDDwJ BPA dC5 E<%wJBOA B-EVwJBOA B٣]D<5wJ;BOA BEqwJBP~A @C CwJBOZA Bw)DCwJBOA BնE.3wJBPA B{EwJBP*A C*aEMwJBOA BEwJBOLA PBqyE4BqwJ BOA ABhEvǽwJ"BO-A BD?ǢwJ#BOvA sBEJwJ)BPZA CBBE)wJ*BP8A &HB(:EBwJ+BP-A CBEwuwJ.BPA GBRDvwJ1BO8A TC +DMwL BP A BjEwL HBOApBecEʿwL}BOAgkBؙF9wL'uBOفA hC*DՔwL'BO{WAB]BgC@wL'BOMACFFEѢPwL'BOA ~TC G^E8wdwL(SBOACDwL;BOA 5CwL;BOY_A&ByEwL;BO_ABDEswL; BOhAPBTE_kwL;BOoA{BEGwL;BO|_AbFBX0D6wL;BOA,B Fc_MwL;BOwAxBiEZPwL;BO>A;B BwL; BOA!BޗFQ wL;"BO A CEN8wL;#BOrABEwL;$BONA V%C#,Eq_ewL;&BOׂA JCE swL;(BO(A XC%pD/wL;)BNA OC C,wL;*BOA2Cy/DPwL;+BOkAGC*DwL;PBOABzD믠wL;{BOA1HCE =wL;|BO ACBX 8wL;BO:AABFwL;BOA fC$OD4wL;BOA*BE#1ywL;BOA C5BOhAB߂D4wL]BOA~B&D㬁wLnBOP^As>B"wLuBO^]ABrRDwLuBOPABחEUrwLuBOA B WEwL'BOQABAExwL+BObATBb#D YwL,BO]AKB,DwL-BO_ABbwL.BO^A'BEWcwL0BOnAuXBE.wL1BOnA 7C:5D}wL2BO|APrByOE` jwL4BOnAAB. E#wL7BOsgAByEwL8BOABDSxwL9BOA%dBD?wL?BOA,BwL@BOA!jBcxEjtwLBBOARC6FLJwLCBOAǕC1 E])wLDBO>A BAEiwLFBOACC FBk=wLGBO_A +)C5DVwLJBO6A_CHEf$wLKBOA&C D$YwLBOYA6BE^ wLBOq`ADBXFEZwLBO(A XC%pD/wLBOZA]B셋EywLBOfAFC SCwLBOA BꅋC^wLBOMA3AB7E FwLBOABmE[ wLBOwkABE<wLBOvABME#۞wLBOkACH%EnwLĝBOAvBP%D!wLGBOP&Ar"B[ETUwLHBOUA3B EBwLKBOa AB#.EwLLBOYAFBF_D5wLMBOP$Aq B EVwLNBO\AwPBiC2wLPBOiABEPwLRBOuAeBDwLTBOvAB{ECwL`BOAB%EwLbBOABvCwLcBOA3B*}Ep wLdBOEA.C|EawLfBOA :C#>EwLgBO؍A iC.?E:1wLiBO$A4COD2wLjBOA)6BVE wLkBP7ACpC`4wL׼BPA^CDA_ZwL׿BOA fCKQD`>wLBO_CABAE)}wLBOnAoBE'5wLBO0A CWBwLBPA,tB@wL!BO"A !ICdEewL%BOA.BEʁwL0BOA(BEvwL BO_A FC 6CRwLBO2A C$DwMBP!A3C'EUFwM BP "AFBwM BPAmB;FwM'BP/Ap`ELA"wM'BP'A ? EEewM;&BOsA ڊC--C&wM;(BOA C%/EJwM;-BPABʌXE GKwM;.BP*At#CE wM;/BP8A BE#wM;0BP{A OBձ+C^4awM;1BP A A>EwM;2BP .A AE^rwM;3BP+A GBqEV,wM;4BP`AB>EwM;5BP A5BDQqwM;6BP*A Cw]F=wM;7BP3Ad>C'*EOwM;8BPZrA vBIFwM;9BP-ABVDIwM;:BPA ۾@0F:wM;;BP)A M­EwM;BP,A LtF wM;PBPA= B3wM;QBP2~AhBsEH(wM;RBP.A bBqEwM;\BPA C;CTwM;~BP(SAB $E wM;BPwA 2]C;kEwM;BPyiA CEwM;BPA ?BEwM;BP?hA LC-pF=xwM;BPA6B wM;BP~AXC=ODˮwM;BOA خC1D{LwM;BP+6AHJB,EBwM=BOA rC6 Ep~wM=BOkA BXwM=BOA rQC|E7wM?BP2hAB(EKxGwMABPASByD0wMYBP AXCxFwMuBOA 6BF|EwMuBP#QA+CETɯwMFBOA CM&wMKBPAC LwMMBPKAjC8E`zwMNBPABDkwMOBPA LBkF swMPBPZA rBtEVnwMQBO~A BYE'>wMRBOJA Cdk=EwMSBPA 'B*EtwMTBPVAC)E26wMUBPAvB!6D\1wMVBPAP.COEwMWBP&AB^EWwMXBP#AC2HD#wMYBPABXDl"wMZBP A B]wMsBP RA HFC %Ej"#wMBO%A ~ACH}EHwMBOjA A@ EAwMBP4;A wBũ$EҰwMBP AGdB$DmwMBP AB8&E vwMBOQA 2Z@E=wMBOA $B pE\wMBPA C =wMBPSA _BŢD%E!wMBOA (C7EwMfBPAvCF#wMBP'A(C mCM/wMBPtA+BUD; owMBP/%ACB(EwMfBOA C<-DwMkBPA0Bo DawMmBP!A2B!EC)wMnBPAuC;EBP'ABU=D#wQ#=BPWAk-B D3,wQ'BP`A \B\DwQ'BPbA YwBxEwQ(!BPKA [C ESwQ;:BPwQ;UBPeA LBT?wQ;BPJ\A B0BPjA <fFh+wQB"BPIA ˆB]EwQ]BPPA $B7eEwQ`HBPACAE wQ`^BPܜAxBqEˈwQuBPA B9qEwQBP(A vB͐8wQgBPwA B[:C*wQkBP[A CJAEBwQlBPN/A JlCkEwQmBP/A BFwEfwQtBPA BjDwQvBPA B*nDwQyBPA " BD wQzBPAA CwQ{BPA 6BEDRwQ|BPA sCB=E wQ}BPA "B E 4wQ~BPA AB~E57wQBPA 1:B0B`wQBPA KBRPwQBP,A 3BPE~'8wQBPDAZC EÓ|wQBP&A C$(TFwQBP@dA BӶD_wQBP{A BSE~}wQBPmA ӴBE[8D wwQBP+YA 5@B]@EW)wQBPA D7C!EpwQBPDA 2ZFwQBPzA 3BwQBPMlA BD̦wQBP9A B *E{nwQBPSA DCQ'E%wQ\BPA%*Bn C_4wQ_BPͧA BkEumwQaBPA AEpwQBOA CC[wQ.BP#DNwQBPkA CE[@wQ}BPgAG6BwQmBPA _C WOE&wQrBO?A eC82owQׂBP[[A mB{QEvwQ׆BPvA BP|'D=wQ׉BPA BsEO"wQ׋BPr]A 7BFE!wQ׌BP[A CWVEiwQ׍BPA bBFEҦwQהBPA B'5E,O4wQזBOA SC0wQםBPA |B>$D%#wQסBPA B6>EpwQ׺BPA 2C3CwQBPA B@E wQBPtA BDIwQBP04A 2BDGwQBPkA5B}F6wQBPA CE/\wQBPA 7OBTE6jwQBPA B'5E,O4wQBP`A ][ByEc.wQBPNA BS2D ywQBPXSA oBNA QcCW&C=<wU>vBNYiA<CUEwUZ.BNIACFwUZ2BN$@~BPoEwUZBNXA*C `Eu wU]BN[2AC DwUuhBN1LASBnEwUujBN6AtsC EpwUukBN.AiC DwUu~BN5AC nDwUBN/ACD[<wUBN+@xC8FlwUBN64Av|C 9REJ'wUBN-A?C 0Dl5wUBN5AwTC EUwUtBNGACF@swUuBN6]AZ;C *EcwUvBNSA CwcEwUwBNXAC{FwUyBNWAxCF wUzBNXA C EаwU{BNYdA3{CEٶwU~BNaACFZQwUBN[A[kCFawUBNQA CkaD wUNBN$W@1CffwUBNYACIDwUÈBN)A CpOF1=wUÊBN.sA@C-E=uwUËBN.AHC*E9nwUBN.A&BVzE* wUBN.AiC =wUBN/A4C4wUٔBN27A9PC_E{wUٕBNAVlBFwUٖBNG4AeDC@El~\wUٗBNQAC'D(wUٙBNI+A˿CEwUٰBN^AC8wUBNVuA5GCE1wUڶBNSZACFwV mBOfA%BE(wV oBOOmAXB{wVBNa@BPEwVBN@Bj}wVBN$@BC4cC2uwVBN!@ȊC 4{wV'uBOnA p~C2CcwV:BN@B}E5wV:BO%`AHB[EV9wV:BO"Ae B]{EFwV:BO)EABE$wV:BO!sA&C@[DvwV;BO[A BҖaG'[wV;BOPA]BmZCwV;BOOAwBE~wV;BOABlB5AwV;BODbABEzwV;BOO>AVBywV@[BN@I|B9]DUqKwVZBO[AB`9Ee$wVZBOIcAFBEƊwVbBN@1DBs3@9}wVbBN@B,FMwVuBOBWA~bBؒEwVBO AB]F0QwVBOA$0BgE$wVBN@CBņEhwVBOAE5BpFswVBOABmCNDwVBO(ACJB1kwVBO,/ABBEcwVBOEA^BKEbgKwV BOFABFswV"BOAAq(BE>~KwV%BOaABDF(wVBOCA7BoQE]33wVBN$^@ NCFfwV BNY.AVCCwVBN@ BZEwVBBN@@Bf?Cz=wVíBN@LBpE_wVõBO>A@bBEnowV2BOA+BǘrE2wV3BN @bB EzwV5BN@B6EhwV6BN+@B7B)0gwVBBO.AvBsEwVEBO1!ABiJEdwVGBOLcA B#wVMBOP^As>B"wVBOJ9AuBEwV,BO.AlCB.EwWkBNܬ@FBPwWIBO $@qB;E 3wW)BO1?@xBe dE^;wW)BO9@Bk1wWABP1@BOE>wW>BP@BAwWABO@aBwEwWwBOI@BKhD wWBPs@,-B\ wE2wWBO@$BnAEwWBO>@\Bo ,E)wWBO;@$BJ^FwWBO@BmEwWBP4@BhEjwWQBOuK@rBJDwWRBO]?@CB^VFƬwWSBO@osBuEMwWnBN&@HBBjE*VwWBO?%@B\`D6wWaBO@¯B;2ErwWBO@|BzwWRBP@YmB=E }wWBOk@]BYEʵwWBP@BEO wWBO.@BE3wWBO@/ ByE_SwWBO@ՈBwWBO@Bq}[DcMwWBO@"BwWBO@B{~PEzwW BP#E@/BEwWpBO@mBpqwWrBOf@KBZdE6@wWsBOD@BMDwWَBO@'BQE2wW;BO9@Bk1w[BQ\[ABZ*D?w[ xBQA6BrEiw[BPJ@ B=Fw[)yBQuA\B=w[gmw[w[=BQhAwBEJw[=BQ"y@BEvw[= BQ(@B+E<";w[=BBPϐ@,BNE*kKw[=FBP@^B EABP1u@BTmw[>XBQA@BD.uw[?HBQA6BrEiw[?BQ[ABD}w[?BQmA2B Dw[?BQ'A BSuw[?BQӝA>B2EMw[?BQS@BKDw[A6BQJA@+BVEw[ZBQA RB1EBew[\BPD&@6A?E@vw[BPI@tB:XEȞQw[BPqA@BJE5w[BPV@OBD6w[BP׼@ BDw[BPm@SB4q#F!~w[BPJ@֫B@Ew[BQAB5EMw[BQ,AgB]Ew[BQ:ASBOCүw[BQ6@5BFw[BQoABN*ELFw[BQA\BLEiw[ BQABuw Bw[!BQoABN*ELFw[$BQU@FBrDw[&BQWABYE/w[(BQ.@QBUdERw[fBP{@BkbFDWw[qBQ@ռBvKE:w[BOCA BǷ0D\w[hBQqA$B&Ew[BQb:A;BE7w[BQuAJBt(Enw[BQk>ABE9w[BQ)A̒B3w[BQSAYB)Ebw[VBQDA BE~2w[&BQ1I@BrF+w[BQ}ABE\w[BP@~ByE6w[BPt@{wBE~*w[BP+@"B7w[BP#@B2 Dw[BQAB5EMw[BQ#/A8BKET]w[BQ2ABvE[#w[BQ:ASBOCүw[BQ1/AHB3Dw[%BQ ABC+w[2BQM$@B>gmw[7BQR@B+E)w[8BQhABD˾Hw[ABQZAjBs)ERw[FBQrAB?E0Jw[KBQ(@Bx49w[قBP@BIEtlw[نBPez@!BEE'_w[BQP_AlBTRE\w[BQhyAJyBBw[BQXA{Bw[BQYA_Bw[BQVAHoB:F.w[BP+@BbFiH:w[ BQSAYB)Ebw[vBQMAMJBzD%w]BP@7Bw]=DBPى@ZBw]>BP6@_B6F,NMw]BP @BYwgyBN!@&CBD\wgBNO@vBpE#,wgBN@B.AwgBN#N@kyBEwEjwgBN$@5oC:E wgBN$@Cbwg:BN,0AOC6שEzvwg:BN)A7CxEDwg;BN)A8CE1xwg?BNAB߄xGuwgZ2BN$@BC4cC2uwge!BN@'CnE wge"BN$w@bCZwge#BN"y@BAEfD7wge$BN(7@\BFKwge'BN#@.COE;wge(BN"%@mC8Ez#wgeBN~@BbwgBN/A=BxD*wgBN)@zBxF 6WwgBN(A&CZE wgfBNPA 3C]FSCwg"BNj@UBɤADwgNBN$w@bCZwgBN}d@BjLwgBN!@%#CCE-(wgABN}@ZBE>ywgCBN@^C&EwgDBN#s@.C#E"&wgEBN"a@C3uwgÈBN#&@j/CERGwgBN+AC,EKywgBN(M@@C5EmVwr BOxA BAEųQwrBNWA +CA:ECwrBOBA B-EwrBO"lA ;fBE:wrvBNzDA4CETwr=SBOA MC/%D wr=[BNA7CD iwr=fBNAYB.Ewr=gBNA 8Bӗ F Cwr=hBNz}A-nC E`wr=jBNesA!CAsD 1wr=kBNA /BEƝ@wr=lBNA BΑIEWPVwr=oBNA BܶE0wr=pBNWA tCBNmA #BES <wr>7BNΣA C#Eiwr>;BNA dC;9E>Cwr>>BNRA JCP$Diwr>HBOfA //BEP+wr>fBNYA FC4CPwr>yBNA C8VC;wr?BNA zBoDfwr\BNA xCEmE$wr]BO#A CE;F:wr]BOA CIJFbdDwrBNeA{C>~E"6wrBNA BLEwrBNA BWE>ZMwrBN}A LBD]wrBNA ѽCWEJ{wrBNWA pC`wrBND60wrBNTA =Ch~E7wrBO A /}BEawrBO6/A 0BwrBOM\A &BB!D*wrBN&A CJFwrBOA ACDrwrBNYAiiC#E띹wr7BNA 7B Eg wr8BNA+BLD{wr[BNA C?JFY&wrBNѮA RC&Ed-wrfBN6A 4CUAD؅wr5BOA UB:Fwr6BNfA OC5UEPwrA4C;CS+wr٨BNA^B Ezwr٪BNiAC=7zEwr٫BNcA%BɡE84wr٬BNAVCF|wrٰBN`*AC5{wrٲBNA B EIowrٶBNjA KWBrD!wrٷBNA ŇBzElwrٹBNɈA C6dEwrٻBNA sChD%<wrټBN3A \C7D͓wrBNӒA nB-DuNwrBO A MBdDWwrBOA 7B1EMwrBO_A B#E^wrBOA NCiwrBOkA HBE 0wrBNYABMC{4FJ(wr6BNA BC-wrwBNA {9C[E֟kwr{BNA DC,DbwrڈBOA B8DSUwrڦBNNA CNFNwr9BOpA B3wt:BNc@=AgCJwtABN@AV'D~bwtBN@bAwtBN@5AIaBbiwh!BKe@Z/ByXwBK@\BFwBK@/BAD0wkBK@VB?w'BK=@$Bw=BK@VBD玨w4BK@BX&wnBJ'@[ECjwBJx @ʅDCG`wBK@ٟB CwKBLp@lwChIEqc\w BL7@pB">D?w BLK@kC2UE$nwBL @ǩB1E.UwBLn@۫BDxEwBL@B>wBLhF@Ce[&Eφkw,BL(y@ SBSw.BL@ޕBH2D w0BL'q@Be[DBYlDw@BLM@*BWF]NwEBLA@V?BwGBL-*@t?B`1wHBL*@>BYlDwOkBLt@CA{#wWBL?@ދBHxESXwYBL*@>BYlDwY,BJV@ᴰCMLYC__wg/BL@EBE6wgBK@l B4#DnfwhBL@B>whBKG@{B9CwhBKb@ +C9l"E whBL @݂BCʪE*whBL @B3qTEnTwh'BLM@C8uDکwh-BK@טBwh3BKO@qBwhLBL*/@AE5hwhYBL3@ڶBEfD wh_BL@BiEenwh`BL>b@BVElUqwhaBL7@pB">D?whBLI@&C4xD:wBL@(yB:zDrwBLhF@Ce[&Eφkw.BL-*@t?B`1wLBL>@9BDwOBL@[B7ExwBK@݀,B.vwBK<@:B&D~F1wBKt@ ABYʫEQw&BL@'B4DFw(BLm@ܭbBHE *#w.BLb@BYlDwpBL%(@.0BDywBL@BiEenwBL?@iBEcwBL5D@bBwBL+u@BC7w͘BL@%B9E*DwBK)@QBhwBJ@C ;=EտRw BJ(@ܖ0CDw!BJ@cCE w$BJ]@=CK`E1w%BJ['@PCO.EJw(BJ@lC$w,BJ2@C_Ew/BJ@ޱC,:4F:w0BJ@^C,WE9w3BJts@ߔC=D$kw4BJ@kVC"rfEwBJ@iBhEUwBJO@ތC&QE" wZBJȥ@BfC!wBJ@KB}yDR$wBJv@BslEֹwBJ@ZBDeZwBJ@YkBoCwBJެ@wBr$^DwBJ@PB~KDypwBJ@BwEwBJy@2NB]gDwBJ@tB\EYwBJ@8B^&fCwBJ@BaE1EwBJb@BZE7wBJ@B-wBJ@BB[GEwBJo@ BcEwhBJ#@oB_3DwhBJF@2B{GDwhBJo@剎BY'DI"whBJ@8BX1DIbwhBJ2@B]TZC2{whBJj@㛔BkCmwhBJ/@:Ba4`E*A whBJ@IBe`E0whBJz@6Bj0CCpwhBJv(@B7-DwhBJ@]BVFEKwhBJm@6BaDwhBJ@P7BaD)whBJ:@ BwAE94whBJ@B}lDwhBJ1@7B|DwiBJ?@ⳘBvCDCwi BJ"@+Bg[jEwwBJ`@bCFbw BK @2;BnDdw`BJK@:ûsGywaBJb@ݘ]C dEˮ,wdBJnH@YCAECweBJXK@uCQJEcd7wlBJ @e CiwmBJ{@dCWEswnBJ0@&CG-1wpBJ>@=aC+D9"WwqBJ~m@>C4DMwsBJv@߼CAPFEwtBJ@=C#HE׋wBJf@ UCDwBJ2@BiCLD9w BJX@lCUCwkBJ@CvC;wBJc@FBzBeDwBJ@⨆BoD(wBJ@⇌BlzEu/PwBJz@BfEf`;wBJ @PEBnvaCkwBJ@ℐBbDIeTwBJ@ BkwBJ@wByAMC-awBJS@B_uCwBJ@Ba#EGMwBJ~@DB`%EwBJ"@,B_EMwBJ@vBdgE)UwBJ@[BfE wBJ}@楂BZEcwBK@Bb6D+wBJu;@ʅDdwBJ?@{B}VDswBJ@&BnD6wBJ@ B_MD.w BJ@AB{%D )w,BJ@2Bi0iDwBJM6@vCKwBJJ@GbC:BNwߨBJS@DC^N.E4w\BL-@԰B:`Bi3waBL@ B"BC"wfBL@ӠBlqEdwgBLV@ԬtB(ODwlBL@ݛBsF{wpBL}@9E@wqBLsW@ҘBdDYwuBLg@O`FwxBM@<ArwyBM@BD}w{BM @B^E:wBLfV@ДAFyTwBL@әB0AE2wBL@ԳBW}wBLy@C:FOwBK@BwBL@ϩB>CB[wBLdz@͔BbUDwBLO@7BԮ>E#wBLK@vpC܊EOwBL@@NxBEŴwBL@ґhAcSxFGwBLV@͚B=E,nw BL@1BciFw BL@-BERw BM5@AD6wBL @%B wBKX@BffwBK@EB>EM"wBK@ԉ#BD;jwgBK@YBeEG4?wjBJ@mgCBvCFwmBK@уBNw{BL<@ΙBODwBL#@ͬ?BԞBwBL"^@7UCEwBL-@CJ F(wBL0p@WCf]Fc);w$BK@1UBEE\w%BKW@6BݲwNBL1@lAr%FwNBLS@ԦBWCwNBLv@ByuwNBLA @ϐAFY-wNBM{@ԏhB2COwNBL@ϩB>CB[wNBL@JÄmFnwNBL{@ӓpBJhEwNBLu@ӝd‘IF"'wNBLO@&CeFlwNBKd@B:^wNBL@ϩB>CB[wNBL@NBcPB^wNBL@cEB'wNBLz&@FB~$1EuwNBL@BwOBL@w*B Fr*wOBLF@C/AF1+wOBL:=@JC`tF)wOBL5@ͬC*jF;wO BL0X@oC'FSwOBL^@я)B3wOBLS@3C ߗEswOBLh@}DBEg;wOBL{@BE2wOBLv@EC-%EwOeBLr@{~C<wO}BL@ԄnBCF"wOBL\@DHB֔CR wRBL`,@(BDwRBL@~VB`EF@wwRBL@1JBXD0wXBLo9@7BoE wXBLx@BT5/DwXBM @pATEEKweBJU@ʉCtwfBK@ҶBwfBK@BD_wfBK@Ә B<D!KwfBLr@BET$wfBK@6BF ~wg>BL'F@C4߷E( wgBBK@>C=vEضwgDBK@CXB& CwgHBK@C?PEۡ!wgIBK^@oiB$EKwBL@ΑBWCԊwBL @BlBwBL$@ӅcBu=E(owBL@BB`Dy,wBLS@7mdEwBL}@!BMwBL@eB#xjE_wBM@̑B vDGWwBLq@Ӈ$B%DLiwBM%@WB-wBLiu@єVBRWE*wBLf@BPC4wBLJ,@IkCEwBM @pATEEKwBM@ϷB eDHywBLq@/C F=wBLQ@16B{EGwBL@;B,3E7wBL;@pB5JFEwBL,@}BswBL@vBDwBM z@AE2w BJ5@;C w"BL]@yBmDbw#BL3@؀C65F9 w)BLe5@͊Bw1BK@QC0GBVEw6BLwi@7C8PEDw7BLq@(BF+wJBMy@%AmD"wnBL&@΢BwBL@߻CtwBJ@ɧ|D\wBLuH@ĩB}ZFwBLb@xB0C*wBL @oCFZwBMy@%AmD"wBLb@6C oEw7BK@&zB:E9w8BK@vBfEJ wPBK@5B\wBK@1BD„(wBL @6VB5F'7w^BK @IBFw_BL5X@YBF40wdBK@BCK whBK@$)BEQwZBM@ԔBDqwkBM [@jBwlBMo@ԋpA%D wnBMo@ԋpA%D woBM,@Ը]A6CowBJ@ɧ|D\wNBM@{AoEz;wNBM#@cA5EIwO&BM&i@ԍAKD˻wQBL@eBDCwgBM'@ԎAnDwBM[@ԙ]BMwBM>@ԂBElD2wBM&"@|A*D[bwLBM(@[A;wjBM+@ԧA!wBLӥ@CBeE#gwBL„@%B3TD wBLi@?wCRDOwNBLw@آBMuxEH'"wTBL@iBC&KwUBM@wFAEzwVBL@GAEbUwWBL8@D @kEWwZBMm@{vB#Pw[BL@ճB36Ew\BL@ՁC@xZB7EwmBL@zB)οDlFwBL@u B#vE0IwBL]@ BR\F0wBL@B.EwBL@zKBlDywBLR@B/\4E'ރwBL@֮ LEv8wBL@vLEwBL@ԄBZdZw\BLd@p,B<ǨD߰Iw BL[@Bv1D:"w BLq@ِB'(EzwN+BL{@3vBeD:GwN/BL@G C]E*wN1BLE@:>BogE6wN?BL@1BwNWBL˚@BDwNfBL,@1B9DwNlBL@BwNnBL@kF wNvBL@\EwNwBL<@{d@rʺEXDwNxBL\@$BhDكwNyBL @ՌBGBDxwNzBL@/BYwN{BL@ֆ_GEWMwN|BL@6B(DnwN}BLg@ڴ6FJwNBL@ר7@7E SwNBLp@=BEYwNBL @B:#Eo3wNBL@ԨB*F#wNBL@ԵBEj5wNBL@pBD wNBLT@_BTEXdwNBL@׾:BKE &wNBL@B`;DeՇwNBL&@׫,B2CC?wNBL@rvBYE-jwNBL{@B"DRwNBL@OB?1EWwOBL@+kBe3D<+wO}BL@֨CRFB'äC}waBL@q C5EDwBLJ@מ>B4@DEwBL-@RbBuDgwBL@hpCrFF)?wBLr@|BE8wBM@ԂBZEEC<wBL@t|B-DwBL@BUdE4DwBL@CgPEO"wBL @A@CWvwBLt@JiBCB@F swBL˟@BF>wBLw@C EXnwBL @"YB;EwBL@P“EywBL@f wFwwBLW@^? pKE?~wBL@WY1EwBL̻@=BQcElHwBL`@]7?tEnhwBL@`B QEC@wBL'@+(9EPwBL@.CNʿBwBLԈ@"C!cE wBL@B?NwBL{@BJ&AÙuwUBL@BA<wBLx@MB[Ctm1wBL@/B1aCwBLp@زFwBLq@ԐBEwBL@ΏBePCFwBL@3$B3hE^wBL@@B{E2Aw~BLe@ֈ%BbE wBL@JBD!\{wKBL@(BW^5wBL@BzDMwBL@أBB[IEZwBLA@=B*BL`@oB>8EwBBLK0@/IB>AE}wVBL±@zBLDQwN0BL@خB1wNlBL@XB|wNBL@ԯB3wNBLa7@ٽB/DcwNBLs@٘B^ZD6wXBL;@ڄB0WEwhWBL+@kBJDEPHwhYBL@@ڡgBODwhZBLS@*BCwh]BLI@b;B69wh^BLQo@(BCEwhbBL6@ڭB6nE waBL5@Ԑ B_wBL·@B._D5׺wBL@ԡ0BWwBK7@BiEMTw BKb@cgB(Eϖ wBK@*`BlwBK@A׬fF wBKI@C.F>wBK@֐gNF XwBK@"AۚF&wBK6@n;BEk{wBK.@٨BE$wBKmH@-OFwBKL @BEwBK@]NFwBK|@%BVF/wBKAb@6BEwBJ@ۯ~CSEPGw(BK,y@|8A}#F(w,BJP@܏C )w5BKX@CCVEwgBKy@%4BwBK@NB7RBwBK#@By_Eϰ)wBKt@`BeFܗwBK@aÏnF ~wBKp@BC2kwBKDy@ݩBl`DwBK3@h}BZ\EqgwBK1@BHEkwBK@]Bh*)CTwBK@BBkաD3wBK@BeWEL8wBKG@ݜB_dEW9wBJw@CZCEPw BK@ݶVBB w$BK@GB FJw%BK@}B1;D1w+BK@^BE]wBK0@ڍBMwBK@,BcjwBK@5B?DnwBK)@B'E;@w/BK `@6CtI+FMw00BK@BokpE՛w8BJG@~C,DbwX2BK=@6B`E0-wfBK@գsBFDxfwfBKP @Ҳ/BGwfBKa6@DB6wf0BKk^@C.FCwf1BKv@!C5Fwf2BK?d@C%Fwf3BK{@BZDb{wf4BKd&@9#^i/Fwf7BK.@٫BʳEAwf9BK94@BBY`ELwf;BKUg@2|B:E>wf=BK0U@ٚBsE1lwf>BK@ڬB4 Ewf?BK@`C1E4wwf@BJK@CL[DwfHBJE@?B]FwfUBJ@BEwfwBJ@'CdwfBKb2@~B3nDPwfBK@)BDWwfBK@ʛBiwfBK@ܿrBnYERawfBK@>B(E'wfBK\@TB3EVwfBK@"@ڜB34B;4wfBK8@0BBwE wfBK-@޷iBLTkECwfBK~@hvBdw BK@߫B`aGEIwRBKV@`Bm E^lw8BKQ@<*B^wPBK@@‰FfwQBKm@C$TFGdwRBKG@էSFp$wSBKkZ@֠A!EwTBKy@ؗÎm1FpwWBK@C]F:wYBKM@B+EUJw[BKl@BFDw\BKI/@9B5E.ƕw]BK5@lBmE-w^BJ1@HCPGRw_BK @;C~E8whBJ՘@#CF~9wBKL@B\DwBKd@ZBfȩEwBK@۪B{SC?wBKe(@BEy wBK:^@sB:UD⹵wBK;@B-wDm-wBK*@ҝBI'Eh\wBK@OsBcD5wBK @!BgEw"BK@ڳB3EOwKBKc@BkEAwUBK @\BohD'wdBK@],BD-'weBK@VpFwhBK@FBfDMwjBK<@5BHwkBK@ШB_f DE`w~BKf8@ mB#EiwBK@نBtwBKJ@2BSD,wBK@OaBuwBKb@ڨB]Eww9BKQ@6B8DEwwcBK7@;B,gCz,wpBK |@@Bm~E/&w0BK@"BrqC6wBK@ّB!D`wBK@)BיEwwBK*@54E wBK@5BvEwBK@" lEwBK @شCRF)wBK@םB/yEwBK@֏BDwBKQ@BDw8BKp@ݴBbCWFweBK@ѠBKD;qwgBK]@'BDgwjBJj@CDDfwBK_@EB]EUwBK@{CR0EwBK@ۯAȓ#E\wBK@8BXڧEgwBKF@?(Bq9EtwwBK9@BZ55EYKwBK@3BE$!wBK@" BS DewBK%@eA틼EJwBKy@B+wBK@@ܦBE֣<wBK*@WB9wBK@BAFCDw BK9@C0B=OgDw BK!@KB{DV}w BKc@ݲnBqDXwBK @enB[E `wBK;@H}AEQwBK@B~6QDwBK@B[!6DAwBK @*B[Dmw$BK@QϳEw%BK/@֍Bd.w)BK@Գ@BFw*BK1@q~µECXw+BKc@سB Ew-BK#@7BcDČw1BL@8B/C+w2BL3@CB8nFrwdBKӾ@ڎsBZE^hwlBK@gB&v E,stwqBK @AEjwrBKT@iEJkwsBK@ݳB:DwtBK#@ݪDB}DTwvBK%@ܪ|BEwBKK@sB;}EqwBK@ۀ@K@E wBK @&Bl`E8SwBK@@Bw%DwBK@B FD|OwBKY@<}BEgw06BKֺ@ݰB:~w0BK@ڴF/wOjBK@B`CweBK@և|BD.޶wfBK'@BwfBK1@ռPBc8E wf1BK@~BE$vwf[BKė@BeDswfBK@٧B|=DwfBKL@7Bsd CwfBK@ݴBQCwfBKH@B!0EqwfBKO@ٷB6 D5wfBKl@ B`EwfBKa@ܫBxwCwfBKU@ATE)5HwfBK@VBADZwg BK%@uBSsEwg$BK@ݘoB4n$DG3Uwg)BK@[AEwg*BKH@¤Bq7D=wg/BK@59Bwg4BKB@[?BZDjGwg5BK@ݞBnZE`wg6BK@+Bz^Dֺswg8BK#@o5BvUE5wg=BK@ݸMBfDupFwgBBK\@՟"$E'wgDBK@FXwgEBKC@BRD\zwgHBKu@XA"FwgIBKs@ԝ B0(E^wgJBK@\0CwEq8wgKBKD@kaF5YwgMBKB@ !¤EQwgNBKs@*%B Eo8wgBK@يBEwgBK+@+UBWPEiwgBK@܈[BtD,3wgBK@ٚ`C9IEvwgBK@?@ZE MwgBK@ݡ;B`DŐwgBK@A0vEwgBK@B^E"wgBK@+BE׫wgBK;@ۤ+dE|wgBKi@ۯB+E>)uwgBKO@qBwhBK@59BwhBK @~)EX"wh^BK&@4Bw~VBK @ݺBj Dٷw~BK@ݢB#VD:w~BK@EwBK$@F@A7QF#wBK-@٬dBD'Vw7BK@>BEw8BK@ԉ#BD;jwPBK@rkB3UEF%wRBK@I: |E7wwTBK@BܬwhBJ@̀CwxBK@BiqCl?w{BK@B yD2w|BK@HB(DrwBKz@ >B4XCOwBJ'@yC4!C{uwBK@BdEwBK@BoDd+wBK@AB RFMʹwBKk@C E3swBKz@xRuE5wBK!@ۧbBn)E[twBK@0BXPED=w BK@BO~E5w?BJ@ȢD 9w@BK@޳Ee,wEBK@݀,B.vwIBK@h^UEq́wJBK@BEyUwKBKd@^B:E@wLBK)@ٽBAdDJ\wSBK7@}B49wTBK@XºCDo]wrBKA@\C 6EmwsBK@™C=&DfwtBK$@CcETtwuBK@iCI%DIwvBK@@CDGwwBK#@¦lC*D.wxBK@CwBJ@BCR;F0wBJw@eC F+(wBJ@/DF wBK1@ļE>FhwBKT@żE!=FswBK(@AFCLDڗuwBK,@C %EXVwBKt@ʪZC GtwBK @ē6B BK@(EEw/wBK @AIF(w/xBK@5YBrF "wNBLx@B\wOBL@!#BEnwO BL(@B\D?1wQLBJm@CwQ\BJ@CUD塘wWBK|@ȊC=1F%wYBK@JCYRdDwYBK [@[JCaE*YYwYBK%@`CaDF=wYBKE@BrFweBKu@DC2B/weBKV@£C3̀DNweBK@0CPEUweBK#@CO^EdweBK@ C*D{PweBK@•C^VERZweBKn@fCQE5hweBK&L@ĥBʪE weBJp@CV^Ej|weBK$@>CbEkE2weBKs@CE6weBK @eC1E)weBK@ånCAF QweBK3@CC|ElweBK@ZDCENweBK@FCN2E^weBK@`pC_{E weBKW@rCʐE$weBKa@$CIDkweBK@QC^`EweBK@.C+weBK_@CL>CF)weBK_@,CkFEweBK@MCMFvweBJ@ĚCWFweBK'@jC0~ E<~weBK @WC.tEweBJ@dC|FzweBKm@hC/E weBK4@¤CTzDweBK@]CrFweBKN@ŀCG'FweBJ@~CDuweBJ@ tC9EweBK @a~D*-OFweBKS@}mCCeEvweBKO^@7C DweBKSN@sClEweBK@TC.FcweBKm*@V%CbFIےweBKb@ŁQBSDweBKc@\pC,@Er,weBJn@CCFweBJ@LwCFx.weBJK@YCF dweBJJ@ʼnuCjGF weBJ6@CEqweBKz$@4B[DweBK}@Ļ\E DFxweBKH@(E_Ewf BK3@ÉcC#D ,wf BKE@ǫ$C E]wf&BJЎ@wC"FG wf'BJK@PC ЁF@pwf(BKs@ȕC6,yF@5wf)BK@CdFwf*BK(@ŜCF3wf+BKa>@Ʒ_BJEjOwf,BKN@]C MF:wf4BKx@ЕjC ZD a5wfBKR@%B0Eh[wfBK@ɼcB×Ev2wfBK@ȢBUEOwfBK@tBjE|ewfBKu@vB5?wfBKY@ː4CpGwfBK-@̦dBFwg>BL @8BD /whBK@̞B`ZE.)whBK@ŽCD("whBK)@ȳBF&?whBK@mB3FwhBK@ HB'cEawhBK*@ABzBWwhBK@ǰCEw}3BK@ƀ9C͗FICxETwBKO@ȲCD6wBKG~@5CCE&wBK@@C?EwBK@IAEwBKi@Ä0C ,D>YwBKu@C EswBKl@kCLwBJ!@PFS7wBKO@4C,0E3wBK&o@Ŋ)CGwBK#S@RC]DF wBK@CvY$E4twBJ@&C~FE wBJ@ĵCYF,wBJ6@CmCޥ)Fw BJʜ@C\qFcw BK@@C 4{w BK~8@ıE 5FwBK@DnFw)BK0@ß5C=QEw,BK"@XCqFRow8BKx@ВBuwFBJ@C$EwGBK o@WCF|wHBK@țB8EWBwIBK @=B FwJBKL@)C~[:EMwKBK^@}CF wLBKb@RC E10wBKRi@ɦCODKn4wBKO@ȤvC:YwBKҘ@LB.FO%mwBK@ʘ!BE,wBK@ɨ'BFwBKn@BvhEgwBK @[fB|F3wBK@bBbExwBK @KBSEswBK@gBB΅FǎwBK@!BsFriwBK@"B5EwBL@UBj"Eglw^BK@yBFLww_BL{@B +DwBKX@ǎB/EȰdw BK@w3BJ@Ο Cw8BJs@TCwBJ)@GC!aE%wBK@K CiF$%~wBKT'@BvFZwBJh@C mwOjBJw@ʄLDςCɽBwPBJ+@DƭCtwQLBJy@VDEUmwQNBJv@ɚ1CE9wQTBJx@}DCµweBJl@ڍD)hwfLBJ@[C'wfNBJ@'OC&`wgBJϻ@Cj1hwgBJ@ C z^wgIBKI@'vBgwfBJV@ϗDDwlBJJ@CnCwnBJ @.D-EwTBKN@ԙB]Dn wBJϻ@Cj1hwBJ@̕0C;wBJ@,C!]EuhwBJ>@GCFwwBK'@C F{w BJ}@C ~E8hCw(BK@bB-bCQTw)BJ9@VC%qw0BJr@҄9C2,w?BKw@,C*Gw,BJ@Y:C"BTwBKp@żBC.wBKw@ŶBdwBK@& EfEw{BK@cBXCJw|BK]@F`BJWF,Mw}BK4@$2B[CԅwBKz@˸BwEwBKh@ABB&`wqBKű@ BC wrBK@eBWCwsBK@˜kBEpwwBK@ŎC }w/>BK@ŝ7CywYBK@aB?Dz7weBK@\BD*weBJ@C)C,wf&BJ+@C!DJwf(BK@ljCwfBK@lBXwfBKO@ǯ C ^wfBKt@ BvDDawfBK=@BdZwhBJ|@vCxD)WwhBKW@BNEw whBL@ͣ'BuB;JwhBJ@C1AdwhBJ}@CCJ[whBK@BC55whBJ @VfCQCwhBK~)@cC DbwhBKã@BzF whBK@ͦBF>whBK^@ŒaB\DcwhBK@BNDwhBK`@ȥB41E wBK@BD wBLGt@hBrC)SwBJa@ 0Ct CXwBK'@CbEwBK@=CswHBK@H{BwBK @ABΗ#Ew0wBK@BD0wBK@K]BD]Zw^BK@-BfDRUwBK@HB$C1wBK@=bBh C'wBK@$B w BKl@ŃDC¢DȫIwBK@)FB DTw BK<@ިKBBcwBKZ@ިBaBB/w8BK@BDDw9BK@ބBj$Drw:BKɿ@DBE#w;BKR@,YBD^\wEVKwBK@$CLSF5wBKI@SmCw/F4wBK @xB*XFWwBK@CjRCusw BK(h@|CGCɂw BKB@ƐC9EL4w BKs@C4wBK@ԪBD&3GwBKz@ֱ'BIwLBK-)@LAC9EcPwMBKH@l C$9E+wNBKo@ӳBF_wOBK6#@ϷkBE=BwPBK/C@aC]EJwQBK:[@CTEon3wRBK@ͳC>ȥD;wSBKA@MCExuTwVBK[@sBF)hwWBK@7C9DFowYBK3@}#C EW=w^BJN@ҪCC5dDEw_BKYE@͎BE,w`BKSc@·ABw BE:-waBK`@JFOwbBKV@`B1EwxwcBKG@BE$5weBKs@NBWFDwfBKQ@~[CE;wgBKv@"CPF5+uwhBJ;@}CMF+ԹwiBKp@ҔBpFwjBJ@DFO^awlBK"~@B;wmBKo@(CF9w~BKP@#-C D\wBKm@$xB'PDwBKj@!ICSF wBKA@FBREw$BKN@͋B DkPw)BK@(#BwBKL@DBvDw/wBK@łCVCNwQLBJB@@C'F:wQNBJ@BDwQSBJS@2DPF'dwQ\BJ@.CEYwQBKWZ@Bq9D7wWBK@Ȭ=FweBK+B@zC:=DweBK&@Ci؈B7weBK @ŐkCS weBKf@{C^G+weBJ@ź(C/CZweBK4M@nC/3CYpweBK$@ŐRCDjDweBJ@CpDweBJ@CЬDweBKB@ƲC0 E weBKS@BC\E=weBKS@ʘCh5E-weBKR@CoEweBK!@ŤnCI3xCZweBJ"@CKXFUweBJ@fCLE'weBKp@BÖweBJ̭@GCPoF>wf BKS@CUCewfBKm@|BӔF|wfBKT@͗BbtEwfBKf@[JB}^FP*wfBKh}@?eF/KwfBKv[@7)8FF:4wf'BJ(@CXwf)BK)@6C_Eu wf*BK2^@[-CH}EMfwf+BKMf@ыC5DoCwf,BKB@fK{FitwfvBKTm@ʂIBEwfwBK'@̱OCe1FT[wfyBK@XC $E{wfzBK@ΠCwYECuwf|BJS@ωMCmRDbwf}BK&&@OCF Pwf~BJ(@CzwfBKb+@ξ<\EƞwfBKC@BDwfBKf@M³FKowfBKXY@τB|F-wfBK=O@RC!E.8wfBKCC@CW'EbwbwfBKc@6B0EBwfBJ@CY*FPQ6wfBKN@/CFDwfBJg@E{C#EʨwfBJu@^CejDU5RwfBKtY@mBۢEwfBKb@'BwfBK@C5CwfBKVa@˯BBTEwfBKm@qBF^wfBK@,2BrID wfBJo@ӧDC,E2|2wfBJ?@ʹC3n-DawgBBKY@ґBиFrlwgDBK@ԻkBDNswgHBK @ԮB\wgIBKZ@ RBNEwgKBK@|dB0Cp whBJ@hCdwhBJ@C@ESwhBJ@ˆ7C˷C`Yw}3BK'@ŀCLCw}TBKl@tCmEzj w}BJ4@JaCF wBK{@أB{dwBJ!@rD "wlBJ@ǗCԏiF*RwnBJ|@C3wsBJĒ@-CeFDewBJ@V;CٕPC wBKT#@cB\FET-w!BKL`@RCq F+wBK y@/CKUwBJ6@QCyC wBJ@CwBJ@C"D7wBKNx@6'C+%EG zwBKN @ C7j[E(:swBK@OClޠEkYwBKSP@CF4nwBK\@C≫F"wBJb@A FrwBK.@zC1D'wwBK"5@Ţ+CRMCwBK$@EwBK@ůCP wBJԆ@SCJVwBJ@>Cw BJC@ RCwBJK@C4Eq!w,BK9@uC.ENw0BKu@йFB*FZw3BK`@BdFw6BKt<@zNBF,w7BK@טC eF%w8BKy"@nžFF(wGBJw@C|0bwHBKQ@ʜBwJBKAq@ŃCA~5wPBKvo@ҏ@~EwQBKDq@sC rEwRBKv@KBEgwSBKi@̖B]CwTBKvs@ֽB;w`BJM@ϣ$C4ID.zAwBKF@CEwBK.I@cC EZwBK2@{BE=MwBK#@#CE RwBK(@ CYE*wBK@ C'E`.wBK@]CtIEH>wBKT@CE|wBK0?@nBF3,wBK @1C;EwBK%@CPJEd$wBJ9@@CDwBJ@үC.<wBK6)@CƚFOwBKW0@g4BzEwBK3U@̀JB6 F(\wBKX2@˼A1FuowBKS@³nE朗wBKDi@BECDo%F wBL@]CkEwBM<@C\zD}:ZwOBLx@qCG9EX|&wOBM@罠CTEwOBLژ@U}C]EJwOBM@|@B0CEwOBM2=@CCR0EwOBMO@-CvEwOBM3@a'Cr9DWwOBM^@FD{+7wSBM*@DC[lDwXBM@CbDLwXBMW@S(CrYEWPw`]BM"T@(CsEw`xBMF@RC+ESwe BMf@qCBިDweBMi@PCQMPwhBL5@Y}uFl4wBL@Cq>DaPwBMA@CD;wuBLӗ@,CADP_wBL<@KWCwD=wBM @CLЫDNNwBM @C@PD wBLC@ECQLEOwBM?@ZCI8DiwBM)@v9Cu+D*wBM(@[CJyqE.wBL@5C1iDwBM;@ԧCeD_"wBLb@CC.?F0lwBM:5@C%DSwBM&q@CvE)wBM!e@ECN[dwBMF@RC+ESwBM @8|CjwBMS@Cw E4wBMY@[FC2q@CwBMA@=CBYwPBL@CwBJ}@C=wBK(@`#B1wߕBJfa@dCGDwBLP@B1Eڛ wBL@tBE;wBLȊ@,CBOF8w-BL°@ۚoBE Dhfw6BLX@ݹBpEw7BL}@oBFPE ,CwBBLn@CC2w^BL@ jBG`wBL@fBWAq\wEBL@C@EF)wMBL@NByD RwRBLғ@CCX~F wUBL@ßBZCzw\BL@1B3ƨwhBL@ݱB8#FwtBL@ܛS@$FLHLwBLϨ@'2C,B"OwBL@YB-REQ)w\BL8@˚AjwOBL@BL@rZCEAwCBL@CڡDwHBLw@CDݺwIBL@CEEwKBL@ͽCy:ECwaBL@C;wbBL{@CE`NwcBLp@lwChIEqc\whBL@XB7C?wBLw@YCZDQwBL\@⡝CEÖwBL@aCW6w'BL@C,)wBLؤ@ Cf\HB~w BJ@̠(CArZw BLC@CaDjw BLx@ CKdDw~BL@hC[]D(wBLY@sB(BpwOTBL@(BW^5wOXBLD@|Co wOYBLD@|Co wO[BL0@gBCGowO\BL@'C(DDwO]BL@性C E#14wO^BL@n$CxD`wOhBL@CwOiBL@"CE/wOjBKns@zpC`G*wOkBL@ňC} wOmBL@ܳ!B1DI^wOBL@C;wPBL@CwVBL@)F KJFi]wnBLh?@BFfwuBLԑ@CG8DS2wvBL@C8Ew~BL@rZCEAwBL{@CE`NwBL@TCDwBL@C,)wBLs@kCwBL@:CTD)%wBLC@CDҡwBLH@CmEwBL @ذBCzwBL@`aCwBJ@NCȯDF*w¡gBL@dCE7Vw¡BL@.CNʿBw¤BL@BeCw§BM @`CZFS(y}BM٧@Bo4DH2Zy}BMH@IBEUy}BMq@4BLDuy}BM8@ BPDy}BM@ⵆBSDNy}BMK@)BGCy}BM@}BagDUy}BMҵ@BB Dƅy}@fBMg@%qBXE"~y}aBM@j`BD}y}aBM@⧂BDMy}aBMS@BDG:y}aBM<@JBy}aBM@B7Ly}aBM?@kBzDSUy}aBMA@BRy}aBMŀ@ .BEdYy}aBM@xB8Dsy}aBM4@BD=y}aBM@%BESy}b BM@3B D7py}oBMw@SBy}BM?@mBy}BM.@ⓤBBiy}BMK@տB'y}BMy@,B_Dgy}BM@βBEBEy}BM@ƉB~Cy}BM@B D4ay}BMq@ BEey}BM@BmyDWy}*BME@+BBݎEOyBMp@KeBEyBL@ΜB#yBMX@BQnD^yBM@䋉BXVE(cyBM@ڝBEa8yBM}@BD yBM4@)B ESCyBM@C D)`yBM<@玠B:^yBMv@,BHDyBM@pByBM@hBY_;yBME@BaDjdy BM@aCDTQyABMy@9Bu=E yABMk@qTB'D lyaBM@B;yaBM@@uB}E* yaBM@(7BYEyaBM@wByE2ŎyaBM@RBXTyaBM@BMv!CycBM@B EydBM+@B'D5yedBM0@sBDyoBM@BB&yBMp@KeBEy5BMa@Bt EZy6BMz@7BT'D3yBM@B CөHyBM@#BcSDxyBM@6BTDˮyBM{@½B{fE(vyBM@PBs3yBM@vBE=yBMJ@CB@뤍BKeoEyBN-@VB yBN$@B:EgyBN@9B6AD2yBN @p.BbD1yBN @BEw;yBM@ڜB,DWy5BM@~Ce?@^y;BN T@C7EY'yFBN@.C(E#yXBM$@eDC}xDy0BN@BUEOy4BNB@OB D+Zy3BM@FC)E0>y;BNT\@휆AEIyECBGAC8DmBGA!C|>C BG]A VC"[E- PBGJAC˃Dm5BGAXChBFACyDflhBGACC)BGAC4bE%-hBGA ChVpEaBFAQC_GmBGAdCiD.BGIA=CsEDBGACCpEBGAyCDdBG&mA!,CEaS BGsAmCE-J BGAC]E73BGA3CMCFBGXA CzE=oFBHA!9C"EUGBGA!7CC}q}BGA%CE#KBGA ߸CDD7BGA qCWD5BH-A!;CD>|BH.DA!CoC BGA +CME BGA!+[CJD&aBH5A CiD9BG&A"COHEɠ/BGgA%oCBFFBG$A"CI.ClFBGA(*D F;FBG*A' CF%FBGA$`B˓F]fFBG%sA"C}SEjnFBGfA%"WCRF4 FBG"PA"CE`^FBG%A"FC^mE/lFBG A$CeFFBFA!iCkE6FBFRA"C8?E*dGGBGA$CF DGBG"`A!CbEk}BGA&1 C_CBG A(Cs5FE~BGA'0CwNE@BGfA&CFg&BGcA$CfJFYeBG!A#Cz9E3BG PA"\CF GBG$A"CI.ClBFA!CpRE2BFA!ܛCsEBF)A"APCDQ`BG A#C<EqBGA!iCUE+iBGA'HCrF:BBG&A!-?C,C.BGpA(*DF-)BGA%COFBG">A#RC^5E)BG"A#0pCTQeDyBG~A![CʞEBFwA!CF`EBF~A"CyDZBGqA$iCRFU BGA!C2E=8FBI?ACEX*FBIA•CgEsKFBIAeCAEkGBIfA%CBG BJYA)CC_GBJGQACdC]GBJ| ACӨE( GBJA CCvGBJ AC rDDGABJFDALC#E0GRBJ]AC-B ZGcBJ&ACqDCkBI]wAECgnBI]wAECgo BI]wAECg~iBK/[A8CPE`BI]wAECgBIo4A۱C}^A qBaBHCA LBפDbBHE(A mB0CjuBH>A Bfj|BHCA LBפDjBHC4A WB 'DghwoBHA{A }MBvEBHBtA bBDBHCA dBE#BH?-A wBDoBH?-A wBDoBHETA BEBHEA B>`D"_BH>^A qBBK,A:C'FBHSA>C2D~BFBHlxAuCDFBHA73C@(#DV)FBH]AlCeCoFBHA CD8FBHAC%EQFBI ACaDFBI+AC7DN}2FBIHAC CEFBIVaAcjD_D?~G}BHAS~C`}DH~iBK-8AUCiBHD[A CLTE4BH`ACD{MBHSA\C!E;BHfAMCN[D0BHĮA2CEٔBHtACDDdBIAۺCDBI3A[CzB$BIK8AC2C -˽BK:A[CVPD1BHMA Ψ `BHfA BlVBqdW jBHT7A _BDH BHYA B#Bҙ BHd A BՀDS' BHb+A BFB BHbA sBlC7d BH_A Bں BHeA pBMDcП BHbA VBD- BHe]A ~B D  BH?UA AGB.  BH>TA ZB:D| BHCA 5cB E7 BHAA BTA ZB:D| mkBGA C BHQ&A BDi BH8wA hBE[ BH7A #B0C BHC[A 5BֱE BHWgA BAS BHeFA xBC BHcBA B̓E  BHLtA BuEM{ BHctA pBdZ BHfA B%B!b BHbA tBTE C= BHWA BSIC BHYA B BHXA BلD5 BHCA B;EL BH`A rBtD)d BHaA zBDJ BH_A xB9 BHbA B DK BHEA Bk LBH?A uB׎Em߿ MBHEA -C{E NBHAA BA ^BѪBe iBHYA VBiBHklA B)EDQiBHgFA 1BDMwiBHg{A }fB;D&iBHfrA mBD3\\iBHcmA pBʎiBHaA BvBHm3A BBBHm!A BҍKB60BHlA BCxBHhA ~iBFDBHaA jBBHgA |B!DBHXA 8BWDy<BH]=A 7B8EvBHlA 2BCZPBH8A B᎜Dq 7BH=A wCH+E5O,BHFA BkCGBH5A pBɬpC>GBH>A B_E |iBH7*A BaBmBIa@zBCmBI @B8E+mBI{@F CI1nEUmBI@#BقDԾumBI@B/EmsBIx@Bo0C7qmBI@eB#EgmBIiA@BWDHmBIǪ@bzBnBfm.BI@Bc6Ev/mީBI_@C EtmުBIj@BaLDmެBIb@̄BVXDxmޭBIKI@Bg]EgmޮBI(@By EnmޱBIc?@ѾCQZEbRm޲BI^U@ CTE m޳BI@BXE;m޶BI|@SBK2E m޷BIu@+ C5Fm޸BIF@Bz?{E!WmBIy@+BMDmBI@GB`wDmBIz@C/LFmBIr@OB*7FFmBIT@C~E`"mBI@CZEFmBIU@BEI7mBI@%B EOmBIX@zPBuDmBI@xB}JE82mBH@C)F?mBIa@:ByE)VmBIr6@MnC?YD6mBIm@$CTE2emBI@^C(EFE n=oCBG@]CVFuoCBG@ C.mE]oCBGx@CF)oCXBI'@틮CžGpokBG"@.KBiCD okBGUA2C6DEokBGؤ@uC~CcbomsBHl@CDomBG/@Ct1FomBHG@%C{DomBGJ@vCEtoBH@ B?SEnoBHN@BMuE4o BH@BvECo BH@@G|BEwo BHK@B2uE+/o BH@~B1Epo BHM@.BEGoBHS{@C1BMoENjoBH@<&BExoBH@FZB[F<Io)BIT<@lUChXaD"bo-BI;@Cq-Eno.BH@v@C|E^Qo/BH@VCo0BHڣ@oCDho2BHE@@CqEBo5BHZ@CDF+uo6BH@-D͆E?o7BH}M@GVCBG@-C E8o?BG@,CҡFkoxBHz@gCFoBGRADCoBG$@~WCoBH@sC1F<oBI@lC&D>opBHS@ C%{E;oBH@CgC~oBHx@C^o*BH/@BFo+BH@ǭBHEZoo-BHf^@ȊB>IEo/BH@ BtDqo2BGz@eB`DrgoNBHV@sC3E»oOBH@ʱCþ1D+/oPBH@uC "E1RoRBH@BICMFPoVBH@C; EwboWBH@-DvE}<oZBHSl@CEzo[BH@U_C !FЎo\BHV@YC͎F?3o_BGT@pCEPqBJo@ CzCq8BJd@C#sG!~hqBBIj@C5qBBI@C՘CqBBIw@큥CDqBBIO@kB#F=qCBIF@/Cx"E{qC BI?@C,EqC BI?s@ "C4BEcyqCBI9C@YCdRE٠3qC"BIkU@#CEƿqC1BIwP@CSfoD[qC2BI%@VCdqC5BHkV@RDuqC>BF@CZ?CqCUBJKc@;C?E:BqCXBI*@PCʢcGNqfEBJM2@CMjqmsBHR@C7Fb0zqBFO@$CևsDgmqBFC@ګ&D {qBF_@FD Ĭq BHC@9C)fGqBI@CqBIC@CHE hqBJV\@CNDUqBI@aLC2qBI@lC&D>qBIv@CxEqBI@qCbaD;2asBHABED.$pBHAB[=DP9BG@CRERtBHpABNDuvuBH ABElwBGAueBөEyxBGAB,E|SyBG@^BǏFYM|{BG1@a>ET|BG@A!:F'~BG@F&BXE1BG@BDkBGAB-BGA^B3ES5BHAUB\CBHuA/$B-LDySBG@w B)cD!BG@CBݚDPBG/@CzEm BG]@INB'4BH A.BʙaErBBH@3B;BBH@BCcBBG@7BESCBGh@C'DWCBG @C>EUC BG@CwEcC!BGr@_FUD%BHAUB\Cj>BGs@BkEikBHABr DXkBGqAB{DlkBGۂAa@EkBGȓ@V¨E6kBGY@:E( kBG@BXD*kBGl@HCzEkBGߚ@#BDgNkBG@=UBהEkBGA-4B DInkBH ACBEkBHjABUETmBG@;ME:mBGABELmBG@vHDF7EBHAxBQDSBH AGB9EyBG@B E>BG@C"D7M@BG.@5CNΎF-&ABGT@*.C eEEEBG9A]BD?^BG@MXC EIl<BGABgE4F!BHAMBRDƊBGUA[BmDsBG&ABnEtBGA7BC01BGڂAbEBG@IBEnEuGBG@WAE1BGs@BkEiBGM@e?H9ElBGA-BEvBGԪA BqC$ѥBHABlDBG)@T-B(E:L!BG>@BDBGAB D'BG@CňF?2BG@\TBE``BG@3KCy[E5BBIA C2UF"D`BIA =6C&tD?BI8A /C!_Ei_BIA |mC6fFrkBINA B?EϮkBIA EC"ZDkBIA 2C:=EPBIAA GCmE3BIA 9sC8XE$BIsA uC(zEBIA 2C%F#BIA C E\+sBJ[A'C'E&.BKAL9Ca F:_BI8A mCUCBJI^A "C"FJBJaoA yCsE ^BJ8ACF?pBJJANC&FL$BJAC6F@BJAC Ed+BJ/AdC?yEBJACܽEywBJA CcFBJACEfKBJA"C8DF>[BK!ACɳEخ<BJ:A CeD PBJAC3ES BJAj(C E`IBJAbCE TIBJA CIBJ/A CYakBJAATZCEkBJ,AW,C EkkBK2AC5E ahkBJ)A akC^eElBJCGEb'BJA,CF2s(BJXA?C@E̜)BJ A]*C ID5*BJ}A^CT!E{E,BJ ACNE/BKAKCǓ5F1>/BJ}AL.CNVDCќBJbATCEEiPBJAtC~DŸzRBJ[A RCVE%BG@FB/BG2AAxCEd{BG$A܎BRPD|BGA@IBIBG@B@BDFBG@ZBDBGc@tBO~FV\BGg#@CqEڅBG3@BFE%13BGr@gCE{BGo@"CxFpBG?A)CljFBG&ANC!ZE3BG ArC4EBG0AC;`CEDBFsACIzmENBF[AGC]2E BGX@Z,BoFCC5 BG^P@C[xE C)BF0A5CEJCBGW(@CBִjBG3A?bCEkBG@>IB}kBGK@TBqUE8kBGYA4CE.|kBG/\AC IkBGDAvC ZVEךkBGK*ACFWkBG-A (C;F=OkBG)A[CIF^+kBGAC;F?kBF[A~CPb|EokBF\Ai~CBEsNVmBGz@B Bm\EWmBGQAQC 1KExmBF܄AiC-{EdBFA7}CHTxBG@"BBGUACwBGS4A>CE/BG@BEzBG@mĊFsBGr@C4EBGCiAC 4F8BG0AC&tF.ABG A^C9Dl!BG@BCw2pBGN@ZBYC BFAXCGEoBGAC9φBGMARCMEdBF\Ai~CBEsNVBGA^C;C#BFA,qCPE(8BFACKE-BIsA 8C>/BJA !C^{EOBIxA C+F_BIA "C/DȌ/BK(AsCBJA FCX\F BJxA OCxTE~VY!BKzACuF,IBJ"A SMChDjBI˫A HKCWE nkBJA@CCZkBIA ",Ffl-BJ1AC E/s4 BIˌA tC*OF6BIA }C7+sE+eBIA ESBNE BIBA C4tFq9BIA CMC%fC BJA CN$gE"_"BJpA :C{C1BIA C@1Dp2BJ `A C`ƁElBBJ-A O{CU!F,BIٶA gC4qE.BH̐@bCؗvDXBJmAC BKbAUCikBJKAmCWE-oBJWACr|D2YoBJAXpC&D%BJ2ANCv KBFq@ޠCD6BFp@޳\CHsBF@ڬD D^ PBF@ُ+C3HEtDBF@ٹC0DmDBF@CDnpBFʮ@ۡCF ySsBFѪ@D&DzFBF5@aDqCxBF@0DBF@CaDBF@{C&Eg@BFI@6&CD#BF@;ACgC BF@@KDsCjP4 JBF@0Ci4FE5]DBF\@oCώLFdjDBF)@DEiDBF"@݌CEDBFr@ޭCEDBFnV@PDFjE{BFb/@xiC 0E E|BF_a@;]CEvtE}BF_@φC^FE~BFf@EjCkEUjEBFg@ C+E8njBF@W'Ds}F|BFf@d!DDg BF'@ 8C!E3BF[q@CESBF@ۊD FKBF@מDQE3BF@ݦMCSFZ6BFp@ޅC|EYL*BF_@oC\E?BFg@OCDז}BFj @VD CHDKM)BG}A ,CCyM6BGA iC M=BGA VC#__D7kBGxA .C(9D k BG~A SC@DkBGvA iQCxDhkBGt A CkBG)A hCBkBG}A B`PAqkBGA C!D8k!BG2A /C 7k3BGstA0B*C˧kBGmYAMBfmkBGlA ;C\B’BGb,ABsC]ZBG}BA gCm@xGBGuA BCDwHBG|A C=D{JBGRA 2CBCbWBG}A jC.Cv\BG}A [BՁ]BG-A C!Dr.(BGzA CDB+BGvA oC DC,BG|A C-0D1/BGwA 42C;D[m3BGvrA ^4CiDt5BG{sA !CeD%=BGA C#]D@ ABGA C v2CI>ZBG|A C DBGpAB\CyBG{?A BDХBGzNA aC GBYBG{A C3DgBGtA &CCa=hBG{DA `C-vDiBGxA 1C61DUwBG}A ;C Cm|BG}2A B Cj}BGcA CPDABEzAUCjF>[BEABdE,BE1ABƝ/KBEvA\BE1RQBEAtZB*EMVBEikA)BBD-9yBEA OZBABE;A WBXEBE]AdEٰbBEQABD+BE`A #BfFBEiAB)5EwBEA PC1JE{IiBE$A BBCMBE[AyBpC~݅ldBE[kABDlvBE__A[BּlBE+A gBE\]lBE@A /wBEU=lBE@vA F#BǬE lBEfABVE4lBEiAB(ElBEYAABNEnlBE8A !gBsE}ϚlBEK AAEzKZlBEmAxCrE럨lBE~AB)lBEAvBԔC+-lBEnApCEllBEAs'BE#)mBEAt3BqE$ _BEVAB>BE;A CF]BE0A B.D}2RBE$AtBBYBEXABíD9BELABDxyBEABʻBEA=BVE}BEAtFB˟IEBECABxC%m!BES AZxBCcBEMAT€uE+EBEj~AvBTEGBEjA3BjEDBEWACkEKBEjASBXBErACCaEBEAjBET BEw ABкEBEaAB[Ck/BEArBQEhlBG`lA BUD:*lBGdAߖBݳ:EdlBGr.ArBéXDĶlBGdAߖBݳ:EdlBG$nAB\RDlBG!AB/DlBFABRlBFAB>EmBF:AB¯BG;AB¹REs=kSBGAB]>DeSBGstA0B*C˧BG^!A̹BEBGFxAQBgD ]BG3iA&BzcE)BFA BœE8C:BFAկC0BGZA؂BEfxBFABE_JTBFACMSE+BGA@C=8RBFAXC[FBF6/ABC(\BF3AWBFEFaBF7_AB[CXefBFwAB5E6hBFAVBHDHiBFASBoEXZGjBFA>BE^VkBF=JAlC FulBF4dAB^EfmBF[AC tBEAB DwBFA=CWEսBFmAC9CBG׬@CYHDekiBFA CCmDiBF6 A–VEj9BFbdABUYEkBGQ&AC )7kBF'AC5EkBFϩA}CQ!EulBFgAQBDlBFAeBPERalBFXA5YBEBFQ\ABW_EBFkAkB` EBFf2ABJE mBFl!AB`D7/BFA7bC vF(9 BF@AdBhBF5A/jB2E\f5pBFA CCPTE;gBFhADPBEӇ_BFAn B>Eˑ@BFTAC&FazBF7gA B E\BFiGAuBbE^BF AfCd~F2qBF6Aj~B)BFUAC4BFfABՇ'E BFA(BvFBF@ABD;BFBAB'BFABEOBEAJBvfBj\QBEAcfBD@UBEABѕEsYBF4/A(ZB}uEt!\BFQAV C%/F ]BF8A@BoaBF ;A.BE<kBF0AQBbC3lBF0ABDH6pqBEAB;EtBE#AzBEBE1A1 B8MEdBEAHBC BEEADBDBFuAIBODIBBF7(ACXEqiSBBEA4BjEIhBEA 4C;B*L2BFQABL4BF2AʗB ESL5BF/ AdBL6BFAB~L7BF$A nBiDFLuBF7AyB DXLxBF3A?KBE2Ij9BFJAɹBCpcl BFBABDl#BEۑABaBpl$BF AcBPlGBF bAB3DWluBEAYBC%lxBF/ABCͪlyBF0ABDH6pl|BF5A*BxCĿlBFABdEةlBF6>A3C*EUlBF?AlcC)1ElBEABDylBEYAKB= E,!lBEWA5B9@lBEIAsBClBE8A{{A$TBBFAB=C9EBEABC' tCBEAsBDAYBF0AQBbC3BF#AaB"E69BEANuB{rE7BF1AGBDVBEsAE/BؓBErA gBc%BVgBFAMB-EBEAM_B:EBEsAE/BؓBFAB&BEABFE'BEȲA.BEq BF5A0C]E@*BF/ApBPEBFAB~BEAnBA[CPBF@;AVB5Dp"BEABPEүBF_AB,BEȉA(BKD VBEABB F BEEAeBsME1GBEAI?BBE+ABhBEjA5BҿE!BEA BgZEcBEADBEWEBF1ABE3BE AfBkEmrBEAAB.Ef"vBEAB3CSwBFATBE/BF&ABXEOcBEA\bBoBEA *CwE@BEA Bƙ&BEA8BC],BE#ATB{>E};BEAoB|{EmyBEA C D}BE+A ޷BPE~BE4A (B.Ey BE7A pBNE:BEA BI DlBEKA ԱBD<0[BEΎA`BjC5BEA iEIhBE A C kEIiBEA 0CqIoBEA B4E L@BE&AABEN\MBEAB>DoMBEEA }C%Ef%MBE#A ƽBVDCMMBEA B4.E KMBEA C DʴnMBEA U;B)BH[plHBE AB}lIBEAB[lLBEAB5ETl[BEABއER*lBEyA B,DlBEA FB3DlBEbA "BNDtlBEA BIDB1lBEA BrElBE>A :BHEFBE,A zCn"EBEA EBJ;E iBEqA BrE*&HaBEDA}BDseBEA 5`BݛD6kBEMA >BB?E(BEA Bs^DlyBEA ߜBDwBEA @BDD>JBEA аBPhBEmABֿEG{BEAgB!E9|BEABDXChBEA ʤCHhEBE.A BC8 BEA CsEf΅BF&AB0BEA X5ByD7BE A )BfDq3BEA fC+&.E$BEWAmOBBE|ABgDX BEA NaBEuiBEAA ZB׼jD36cBEA BCDBE}A BIDE BEF`A eBBFS BEA TCEy BE'A !B !Dg BEA 3CԅE BE A iCF(Z BEXA ABNkD( [ BEzA (CKEPq BDA CMDF"G BEA rB E% BDA ܭBDE BEA,Ce)F1) -BDA CET BE OA MC@\ BEbA C,Eb/ 8BEA BȹD: "BE!A 5dXF%E "BE^A zCiEe $BDA0CKDӾ ?)BEgLA `BE~k1 HBDUA~C3E IiBESA MBUE=H IkBEA fB`E$ IBEl_A B9E IBEUA !BE& IBE|2A CVE[ JBEeA йCBHD‡ KBD AY+CE lBEA )XCEH lBE)PA Bå` lBE/A Cx>F _ lBEbA .mCE lBEA B+Eˤ lBEyA 3nF lBE!=A !B9E)[ mBDAwCZ~FW mBDAyDi XBEA PCb>E.P BE-A 9B`E,6 ۸BE#aA BHED BE'A !B !Dg BD@ACoDl BE-A BӠEԛ BE(A BɞE> BEoA BE BESA ӿBEO BE&QBEA9B)iBF#AzBMExl7BEAJBvfBj\l;BEA¯By}D3l=BEڣA\BeD>&lBBEgABgE$#lDBEAɋB,lEBEڣA\BeD>&lFBE AB Ef}lSBF4AB6ClTBF6A-iBwDalXBF9ABč(DlYBF)ABSbEvFolyBF6}A BBlBF7^ABC%lBEA :B޿}lBEA>'ClnBEVA˜BFER.BEAB EfxwBEUA@B KD[BEރABøDawaBE1ABƝ/BEMA`BqE[[BEA 9BDhBE8A B޿}?BF)AHBQ+Ei6WBEsAE/Bؓ[BEABYDmbBEAؓBtEusBF6}A BBtBF3A^B0E.EuBF7A$BmEuPyBEAzsF{/BEA>'ClBF'"AB,E7҆BEYA0AFzBF7^ABC%{BEߜA%BD$ BE.AcBABE٠AMBBFABjUE<.&9BK%@KzCI;BK%@KzC)A/wC 6DFƎBFA8/GCB谳ƎBG3A8=DdF>)Ǝ BFкA7=CF#ySƎ/BGDA8)D.ƎGBFA+$C/EƎKBF?A/lC#EƎBF?A8DE]ƎBFgA- &CƎBF/A-=#CYƎBFF'A/WCѵBJƎPBFlA/CaƎXBFA1kjCkE7ƎBFA-vC'E ƎBF#A,s‰ݫFKƎBGA)jCDGƎBFA7)CERƎBFnA7~D&1F[ƎBFEA5\[C3E%ƎBFA7DvFqEƎBF,A0vC%EgƎBF2A0/C!D_"ƎBFC,YEƎnBBF ~A/C1Dނ5Ǝn\BFHA/2CC"FƎnBF.A5!6CDƎnBF׮A-CƎnBFA.`CEbcƎp@BF;-A/CEƎpyBFAA/gC"EEJoƎpBFA*2)CER ƎpBFA. CԃE%(ƎqBFLA1qCƎBFA*% C6EOƎBGA(k3CEƎBG A(wC{DoƎBG BA'! CRDƎjBF|A4CiF6yƎ-BFlA/CaƎ:BFA/ƏCbƎBFӰA-CDƎiBFF A/WvCPAƎjBFF A/WECE)]ƏvBEA2Y}CJjDƏxBEA3^CNƏBEA4CF /ƏBEUA3UCNwƏ BEA2 CRƏ 3BE׏A3CCƏ BE3A3CcuƏ"BEA0 C^Ə"DBEfA0C\(B&ƏNBE^A43CEMƏfBEA4:C7EaƏmiBEMA0CݏBJƏmBEA3QCEvƏmBEA3A/թCBƑSBEA3/CE;xƑTBFA/iCF<ƑxBElA3"C/;Ƒ|BEMA32C D2ƑBE5A/}]C]DzƑBEA3aC7EƑ mBF=A/@C{Ƒ nBEAA/zC|A_Ƒ BE]A5DTBFƑ"BEA/C2CySƑ"BEA/vCMƑ#BEA/mC[DƑO BEA/y4CC4#ƑfsBE|A3=CIE}:ƑmoBE>A/թCBƑmrBE|A/xICC!ƑmtBEsA/CbD ƑmBEA3(CrD9ƑmBEA3ZC3DƑmBEeA3DrC+E>Ƒn(BEA3C=XD濝ƑnBE>A/թCBƑBEdA/CXƑ&BEA/y4CC4#Ƒ.BE>A/թCBƑBE>A/թCBƑBE>A/թCBƑBFA/ƏCbƑBE\A0C)ƑBEEA/vC BƑBE5A3&C7JDɡƑbBFA/ƏCbƑrBEvA4)C#+D4ƑBEAA/zCA{ƑBEZA3̰CZƒBC&A6QDFz)ƒBC'A7s}CGFƒBC$vA7eSCHE8ƒBCAA5aC:A%7ƒBAA9yD ƒBCuA7]iC^CJƒ SBCvA7UGCXE^C!ƒ"BC2A7fPC0FQƒ"BB[A6CVCWƒ"BBLA6CBDƒ)BAiA9kCNDNƒ)BAA9yCdUBƒ)BCaA7{CE{ƒ,BC`0A7~CwEúƒ/BAyA9C EtƒNBBA7CEͣƒNBBAA7x)C[EƒNBC?A7tCƒeBC_A7xCdCƒftBD>A5(CƒnsBCA6C DƒnuBCA6uC/FƒnvBCA7|TC EhƒnwBClJA7C*EaƒnxBCAA5Cy7ƒnzBC.A7gC(1ƒn}BCCA7nC*E_ƒn~BBA7ECDAƒnBBA7iCUYElƒnBBA6)CƒnBBCA7CE))ƒnBB@A7mCDdƒnBBrA7CO9CŵRƒnBBA7oCyCSƒnBBA7ŹC6CƒpmBC A7VC_CƒpBC0qA7oBCEE9vƒpBCAA5aC:A%7ƒpBBA7tC{EjtƒqBBLA6CBDƒqBB[A6CVCWƒw%BAYA9.CeDƒw'BAA9Cƒw4BAr'A9C(KEƒw5BC}A7|Cƒ}BA["A9CXDozƒBCAA5&C9ƒBCAA5aC:A%7ƒBCA7aaCE\ƒBCA7_CVHCIZƒ]BCAA5aC:A%7ƒBCA67CbaFv<ƒBCAA5CpƒBCA7WCD>ƒBB[A6CVCWƒBB}A7sCEQEƒBBA7dFC’EDƒBC0A7qCsEƒBCGA7sC7ElƒBCA7]CDƒEBAuVA9CEE[YƒFBAiA9kCNDNƒGBCyA7sCEƒKBC?A7CFyƒLBC(hA7 CώF4ƒǦBA3tA3GCL)ƒȜBA> A38C=EP;ƓBIAD.Ɣ,BHzA;@DQ'ƔhBH(A9LCjEƔ=BH;LA:L CF ƔpBGwpA8 D7EQsmƔBH*SA9uC9E\ƔBGVA9CZ`D-cƔBGڊA9C0EF xƕBCA)aGC'DyƕBC[kA&/ClcEAƕ;BCA)d)C ?D^ZƕmBCA) CDƕBCA'CFv/ƕBEnA#|C0E[>ƕ BCz4A'Cޯ^Fl%ƕ BDA#C}E%@Pƕ BDqA#CDEvƕ BDA$'C-Eo;.ƕ!@BE?A#XvCE(ƕ!ABEA#CDƕ!EBE.A#C9DMFƕ!FBFA"CEƕ!GBEkA#Cײ"Eƕ!BCnA'CErNƕ!BC&A(CF2wƕ!BCA(CEƕ"6BC^hA&$CDƕ"BCA) mCEKƕ"BCA))C0 Eƕ"BCwA'C[Fƕ*BDL,A#)CEDٛƕN5BFAA"C-uEztƕN[BCA(+CA)CмaE6QƕBCG`A%NOC?Fƕ!BAA D ƕ$BC=A#:|DXEƕBCZA&mCYEE"ƕBDA#CSvEB >ƕ2BDZA#UC)Dƕ3BD{A#٬C,E ƕBEA#9C봲EƕBF-A"[CzBƕBF0&A"CtDƕBFjA"CE8@ƕBC\ A'CJF .ƕBCpA'RC2E\4ƕBCA)2CC̈ENƕBCA(CoEfuƕ$BCA(Cۥ5E0ƕTBCl+A'C˽E-@ƕBCqA'FCuFYIƖBGA8&LCDΊsƖBG.A89KD^FffƖ"BG=A85D +CƖOBFqA,XCEƖeBF1A,CmE\ƖmBFA,C1CƖn"BF2A,OCo/E4ƗBFAHIDFgƗtBFAIDƗBFAHlRC^EƗ BFEAH=DEFƗBEAA8C%,FƗBFAIDoAZƗBFsAFD X CƗ!BEAACF7CƗ!BEAB9CƗ!!BF&?ABC,E9.Ɨ!#BFiAE1FDSƗ!.BFAH`KD FƗ!qBFO$AD2/DEgƗ!rBF1AHChFu;@Ɨ!sBFl%AE^D7\jE'6Ɨ!BF'AC"^CFƗ!BFkAEGDJEƗ!BF)2AC$*CeEƗ"cBFCACD-E9CƗeBFAC#gD=FYMBƗf)BFAH]CF=Ɨf+BEAB)C(Ɨf>BEͰAA\CHEƗf?BFAHyDٓFt@ƗmBG|A8gD #CƗo&BEA:DƗo+BEnA;C1ƗoACBCƗpBF4AC CUEF/ƗpBFOAG}CvF !ƗqBFQADnDgƗqABEA:DƗBFABCRD6tƗBFAGoDF;ƗBFO$AD2/DEgƗCBEAB)C(ƗOBEA;DD[CƗ^BE2A>-C&Fm]Ɨ\BEʫAACCw`Ɨ_BEtABiC֥EFƗnBFsAFsDF, ƗBFO$AD2/DEgƗBF,AG;DF1ƗBFmEAEDZE$ƗBEACҤDlƘBDA4TCݾƘXBEFA3KC]iEyƘiBEWA2@C@BƘxBEA3wICTƘ|BEKA35CCYƘBFFMA5XrCxUEfkƘBEA4CDFƘBFA4CtDƘBE|A5TC1E:ƘBEA6;CEEWƘBD\A3z*CȡEvƘBEA3y CFs"/ƘBD{A4CVFJƘ BEA5?Cڧ6E^rƘ BEA8#DFlBƘ BEA:A3LCDE Ƙ RBDcA5CFbƘ SBCA6H/CUFqƘ TBCA6eCիwEnƘ BEmA3;CVNEfRƘ BEA79C_FƘ BEeA3@C*NEOƘ BEA37mCiD*|/Ƙ!BEA9DX*EƘ" BEzA3*UCzECƘ"BDA3}C]Ƙ"BEA4CطEcCƘ"BFA4xCYF 'Ƙ"BEA5~C]EƘ"BEA8C.EzƘNJBFA4CDƘNsBD0A3CNFu"ƘNBEjA4crCwƘfBEOA73D CƘf1BExA9DuƘf8BEA:(DƘftBDgA4^CGSƘBE´A4'MCCiEsGhƙBEA:D.}E灔ƙ BEPABCEƙ! BEA==iDFv]ƙ! BECA;23DCEx'ƙ! BE,A>CΦMEi6ƙ! BE$A>'C}F^ƙ!vBEA=C|D'Bƙ!}BEZA>Cƙ!BFEACZDdƙ"BEA8Y;D  ƙNBEyA;ZD!4C4ƙf+BFuABtCƙf-BEAA9Dƙf1BEA:(D4zDlƙfBEA:D:E*,ƙmBERA:7DaE_ƙnBEA:Dƙo&BEA:fDRF۪ƙo*BEA<7IC6E7ͩƙo+BEA=C-Fƙo,BEA>wCΗEUzƙo-BEHA<~D?JE5ƙoBE,A>C;dƙoBEA>j+CxEq_ƙoBEA?6CF ƙoBEA;0D 7EaƙqBEA̅C{E<(ƙCBEYAB.NCqƙDBEA@HC7 ƙMBE`A9dC8F&ƙOBEnA;C1ƙ^BEA;{CMDEƙFBEA9D!ƙJBEA<CD*,ƙKBEA:XDOdEաƙLBEA>j+CxEq_ƙMBEA=D:_EyyƙZBE>AA6CɚƙBEzA;"D$D1ƙBEPA=7#CeEƙBEA< D)*F6@ƙ4BEA<@CET ƚPBEA3Y#CwƚBFRA5|lCǖD6ƚ WBC˜A73Cƚ BE5A3S;C\KE"ƚ"BDA3CɵqE!ƚ"BEGA8XD7_CdƚeBD^A5CsƚnBEA3[CEƚnrBDnA5۠CD(XƚnsBCA6xCE'D=eƚnBESA3cQCEl7ƚpBDA3C!E@ƚBD"A3CƚBC1A6CMƜtBF'AIODE9$ƜBEA?C'EƜ!BEA@rC*EƜ!BE9ABC>DjxƜ!BEAA*CLƜf)BFAIDYHƜo/BEA?:CɰE0#Ɯo;AZDC(F2OJB@MA?D9yEq]JB@NADEJB?AX@D (CJB?YAPD0uF RKDB@7AC^=EIRB@EADڊB@zANDVF$B@:AnCF7K:B@AD'EJBEEA B}JBEA =C-dBE6lBE0A 6B#lBEYLAdBLDfBEA B:^BEA A*B{[Dm(BE,qA B+EABE~A !BBBA[UC֪1CA#?BBhA(eCEQa#@BBAC E}#ABBnACǕEى#BBB!ACǼE#IBBAICCs^#JBBKADCխE=~#MBBסAC˜D#NBBСAGCID#XBBAqCD@#cBBAZChDJ}#dBBAC\1EʻV#fBBcAC7E#gBBAl}CӸ_EA#hBCA\CxE!#~BBAC6EK #BBbAFCv0D+m#BCA;ZC[#BCA`NCR(E_ #BBEArYCvE##BBA̘CB+Eכ#BB1AtC`E2$BB,AjiCyE2<$(BBIADC>CDs$BBdAޝCmESR% BBABтE$% BCACD %CBCCACEbG.%JBBACCϙ%BC ;AC1E%BC]APCDC&BBAËCE!' BBAKCD :BB"AfKC9F5wBBjAC$Ej6JBBAD%CFF (K(BC A_CE8KBBBACE9KBBArCFKBBA`vCEYKBB2A0eCE KBBADNEFF%KBBAM%CDKBBHACkDKBB0AICDbL BB A~D EL*BBAC~E/L.BBAACDEL5BBSAD6EfL?BC=AGCJEWvRBBA'CCq_BB.A C1ZE{bq`BBܺA_CҍFqaBBXACǎEqbBB3AFCEqiBBATCIExqjBBAkCE6qnBB՜A%CsmE"qxBB#AvCǖuFQqBBɟAC8+E%qBBAeCuE|qBBAC9EqBCIA'C~E-gqBCEArCYCE qBBwAChqBBAsCNE_sqBCvA`CׄErgqBCA;ZC[qBC!A6CFqBC A_CE8qBBʱA>8Ca6EqBBDACnr/BBǟAkCD}rIBB'AnyC̡ErgBBHABCxC8sBC ACEBs,BBǐACʏEscBBkADWFsBB#A@CDAgCC Ei\bBD+ADCjAE#BDACC<D5#BD=AaC&_sEh5#BD@AP C=~E͝F#BDAbC"#BDsA}C"2EY#BDA\CEJ#BDAEC!"D#BD?A>C SE^$BDAAC}FE˒$BDkAPBm|F%BD\AC0E%BD"A CeE΀&BD-AhCE~&;BDA~CAzEQ6&BDA2C4qD:BDEAn$C$E{;dBDAC1DEvTqBDA~CAzEQ6qBDA9C+uEqBDAtCfqBDAIGCSEY)qBDArC,EqBDqACCدEadrBDA]Cd8E̜rBDAB]EsBDBACJ˥E`sBDLAC,Elt[BDDACwEo<uBDA]CbE BDAICqEإBDAKC"EqHBD|AֵCvZE},BDAHCE㕪JBDA&C EyuBDDA1BηE}qBDAWCDBDL AvC{F BDDuAgPC CUEΝBDA}CךBD AWCwDgףBCADCVF_!פBD9A:C=E=81BDzApC2EY`LBDqACKoE!"XBDABnFH$NjBDAC:E؁BDpACuEk@BD2AEoCK&EOBDAgC%FBDAZC}FBC8AebC BCAXTChGE#BCACxzF#BCdmAC{ ETa)#BC'AhCzJUFo$BCA&CcEsf%BD A-C ET&BCA^Ct]rF :BCA9CJEc;aBC7AU^CD5<BCAȊCbpbERVtBCAWC~-F*Gt2BC A nC|%FtBDAƎCC'BCvALCܔEBC[AC:F>=BCA^CtDCBCA*ACM7CBC=AEC7DBB'AΝCl3#DBC.AC*Di#EBC$ACW!FmP#RBCMAaCC#TBC $AC\EF B#UBC0ACa7D/&#ZBC[ACu?#[BCkAC)}Dm#]BBAC5VOEN#jBCCASbCٱEA#kBCRAwXCzEC]#nBCeAhCEN#BBA:C]Cl~#BCNAHC^E%!BC8AC D[Fd%$BC gAKCmC %>BC)ABCZ~ES$%BC8RACgC&BC^AC^F m&BCGAoCD:BC2*A#(CqCbk:BC AAD-Cr)E*:BCF>ACM7C:BCACM7CBC:ARCSADo#BCWA"CDXyBC,A*CXΉD~\BCA[C%F(.BCkA$CipsD' BC6A C~BC+ACcsECSBC A$CNE#BC5AޱCRBBAHC3EژSBBACPEvBBApCn C4pBC.ACZx(AbABC4A[ChyXBCPA(C+ DbBCQAMCDyBC6>AxECd@ BC&`ACdJEp\BC,JAC\BOBCU}AC{^ODBCdlA*C >ETBCA3CgPESBBmA|CoKC[BCTfAiCCTBC?AkC,E 2BC]{ACBCNAmaC[EW BC:A~CD\CaBC-A*C|BV~BC62AuCeUCU BC cAC\iF r5)BCAxC`[Fb*BCF>ACM7C/BCE&fBBAC\ D9 BBARCcDy BB".ANCEO{.BB ACNJDnBB!AC.ERhBB&AC!A2LBB('ACdEZuBB AQC5 DzEBB A+C%DBB#wAͳCEuH"BB !AC֤CI01BB"A}CAEWBB"vACEzBBAC?D: BBA.C"XE(BBB('A&CgE]FBB$\A*CȭqMBB*A|C C(,BBMABC~SD̔z.BBACEvIj6BB++A]C唜DlBB'AcCvEEBBABC?BBDA CDBB yAMCMDseBBACaDBBAC E]BBZA=CEZ^BB޷A iCJ?hE5\#]BBAbCfHD3#^BBA C4Ey(#_BBA C@D3#`BB:A!.C Ejw#cBB\A9CC#{BBA}lCÎEnC#BBACKE#BBƗAX C"E#BB…AaC~DL#BBGAC0Eo#BBиAC5E[q$(BB{AyCLF| 5$)BBϗACFM$+BBA9BBUACCbEBBAcdCDrBBACwkEBBBAoCXEkzBBAC FEBBACS4ERlBB/ACABBArCIEUR BBA+C E:BBACdE iBBACXEGkBB\A&BSȄsBBZApCId>E/E-BBaAŽ+F6BBACsmEpBBA7CD3BBtAED FOBByAaC%EYSBB A,C8E8BBRA'lCYCIBBACEJRABBRACE4BBAUC)?E\BBԁAE+C~C BBA(C̪:FשBB ACEBBEAC47E\[BB ACuE~hP BBǠAMCӨ?EOBB.A&C(RBB^ACG-D,BBA-ADG4BB۝AoC|BBA1C,E'2BBA#CC qF$wBB3ACCyXA`C„ZB=^AC $C}B=A?CwBC 6BCA#CgE58 *BCuA#]C&Ee *BD kA#C|ED  VVBCA# CD| VWBCA#CvE1 xBC]A"DFXDM@ BCA"lCDqF BC!A1CEh8 #-BC'TADCfEV #.BC%cAcCo1D #/BC!kA[CE #1BC-AcBF"w #2BCUAuC{?Cmi #7BCAC?F_ #DBCA$BHFE%  #EBCYAjC F0?C:k $BCACC %JBCACjTF){ :BC^AB>F C >wBBAںCҪC  K@BCA5CIB&m KQBC&BAcC5EVW KYBCA>YCd&F KiBC&AmCDe" L:BCA_CA2T LLBC+A(uCjt*EAC M1BCA,BzF- MBC(A|Cy{E(f MBC4AqCXC4 qMBC AC}ClQ qNBCJACAADv qOBCAC֡{C蝩 qQBCwACthF_ qZBC4AzCchA. qdBC/AGC_HD5 qeBC"A3Cc, D+ quBC.ACY@ᯓ qyBC0A^xCJLD s^BC4AwCcV sjBCAB1FT^ t=BC!ACE# BC#AWBE bBC)QAC qBCACD yBC,A)C_z4D(lu BC-A*C|BV BBAwCA֌ BC4AztCc KBC+ACdAzCxEڶ BCAyC:C? BC+gAOCaE# BC)ACtaBJn BC4WA7Cg Dw BC&A-Cau kBC/ACB/R BB~AFCDuP qBC~ACb?F  BC ACCP\%tBC AC/#|BBbA6CM]D#}BB:AC%ZBB=A5*C4D' BB~AݙC^]E xL BBA2CDjL5BB2ADqjBBAbC qBBrAYCTBgqBB A3CDBBpATUC\eB^ BBbA6CM]D,BBxADBBbA6CM]DBBAITCDNBBA`C5F$r|qBBA+C>EgXA!JD7WC?RB?PAD D,sB?AJD|EF$sB?ÀAwD IEƶ B?A D ׾C]B=AC͸&DL5B?=AeDIB?AID4HDfB?=AaD D[B?APD?(FCB?nAaDEaFIA*CŘE,*B>ACnEns+B> ACBE+B>2YAC ,Em+B>iAƟCE^-"B>6rACţWEd:B>-AyCRZDotB=IA?C+EtB=AACWEtB=YAȜCdtB=AxCgdFCtB=@AzC5DtB=XACEtB=A\C,C!uB=LA hD=AObxB>AC}E7xB>AD E!y.B>AdC:E@y8B>($ACEay9B>2xA̬DFTs{BB>oADjE|B>1AeCƧC/B=ALgCA:B= ALxCB=A6C͘EI/B=ACʞD9B=ACB=ACEB=RARC܆?EZdB=PA CǀB=ACYMhF YB>ACܲF$2bB>+ACDBBA8C䌴FBB`A"җD:F,BB AD:ҏ7BBHAZUD+.vEжBBACVEBBA]CwDDF#BB]A(%C{>DQŗ#BBA+CwD{$DBBYAlCήEsd$IBBybA|D2D.$LBBA=C$bBBFA C ELy$BBA D3 F7A$BBAbC#EE$BBWAn\C:E+$BB͒ACEM$BBˀAtC~Eqf$BBʥACgEVs$BB\ACE_($BBɧA;C'7E $BB_,A=DPEd$BBFADEX\$BB\A.DR Ej$BAA 0DG.%wBBAGCvOD_&%xBBACuE>%yBBڠAxCh%E08%{BBAC9E%|BBAIC E2.%}BBA+VCUE%BB~AjC/E&BBAyoCAEqa&OBBRMAKDG2E&BBRAZDcFZ&BBrADEb&BBAYD*F&BBvmA~DDW(BAAoDCo(BAAulD IdD*fBB AD dE._*BBAfA"<DF*BBk|A#'ZDTHC+BAA!C?:BB AqD E1:BBZADE:BB.fAD@$EACF+sBBAC͏E%sBBAC(QB <sBBA:CD $tE"P~BAACSGBAA0CF5BB/A CF*BBLACHKDBB}qAX[DCƏBB[A^HD]gEXBB-)AW~D9)xCŪFBBACE]BBtA̵DBBA(CvGhEQt0BBǴAXCEp BB=A)CvDHBBA# D95F?1BB-1AwDCvE@BB+AY]C1E o_BBcA B*=FF$BB_UA D#E-YBBA;C!EWgBB"AD;nEBBAcCE}BB'ASCjDlBB~AULDtCqBBATCfEZBBAFCWF8BBA,aC~iEXBBA`CE5BB>AJC)!E;tBBAC~EՅBBsASDFL@BBӅA7Cd~E{QCBB܅AMCbFq BBܻACYExABB|A)KChZE\BBA[CsEYBBCAD: BAADBAAmVDDBBAWD*EDv* BBA[ CFb`BAA#W+C⏺E)BB.A#DuC}cGBA.A#?&CbDn=#BAA*DNBB*ACE(BB_A3D8uF؂BBKAVC%E+BBpAjbD'&D%BBDA@Cy=CBBAF C,EWBBņACeC"BBA!DCDzxB@!A"zD NC-|LB@B|A":GD RBAkA C ^A=B@KA2~D"WE8%B@nAD/E1 %B@YAD9PE%B@KABDYE)%B@AD-yElk;FB@kAD/l CLB@ͱA D [EnRB@AD$7B@̚AD0EB@݊AD.wE;B@ϢAD:/Eo4B@ A݊D!DB@A D:E6^cB@A8k4DE1^fB@\A82DCՎ^yB@pA9DhR^ÇB@iA85DAD^ÉB@HA8D Cn^ÑB@A9DC}^÷B@A8D C~^ăB@A8XD^B@hA8(DJ@x^B@ZA8DUDo}^@B@A8bD^BB@A8|CDW^DB@}JA9DC^ȄB@~A8TDB^ȆB@WA8[D{_'4B@mA9kkCC(N_'AB@_A9ÝCxD跨_'B@A9C>E2n_'BAAaA9CPqD _'B@A:.C퍹C;_'B@A:CjE_( BACE4_+B@A9HCCN_+B@A9eCjDJ_+B@xA9MCD_, B@A9wuC D")_,7BAeA:DCbN_uZB@A9g5C,_u`B@A9C;D _uB@6A9CE_uB@iA7C_uBAoA:O CDJ@_uB@WA:&CD_uBAA:IoC*EgG_v)BA A:(CUE_v*BAA:;CȄ_wB@A98CD_xB@A9CD I_xB@iA8CBDa_yBA yA:YC_yBAuA:DCJjEoh_yBA5A:fCE_zB@A9CwD5_zWB@A:,CE_|#B@WA:&CD_}BAZfA9PC1DD=_}BA, A:']Cۢo_zB@UA8CC+_~B@dA9C#D@}_B@ A9SCt]D(\_B@LA7Cʠ_IBAFA9Cx'EGs_JBA/ A:RC}E3_EBAO4A9/CD,_B@A:C_B@/A:4rCFE(r_BA8A:=nC4sE]_BAGBA9C E_&B@A7CD`_,B@A9CD`:_6B@A9CEő_7B@A9}CD}_,`)B@BA8a?D0CQ:`)1B@A6DEY4`)B@A6wD =Egx`+ B@fA3D 'ZF3-~`+B@A70DE`+B@A7MeD bDb~`+B@A4DF`+B@+A43CNEc`+B@A5RC1FT`+BAA5:C EC0`+B@A5D FM`+B@4A6}D E_n`+B@A71JDE`,EB@A8D%C``,[B@^A5lC9`-B@ޛA2^DB`-)B@A6\!C0E`-+B@6A38mDIE`-XB@A7D˫C`-kB@LA2*dD `.B@'A3SC<F,e`.B@A8@DDyV`/B@zA6(DCuz8`/B@yA3)DQE3`VB@nA6zDCaX`uCB@$A8UDX`uWB@A7D ;E|k[`uxB@A8D]`uyB@~IA86C4D`uzB@wA8CΖWEW`u}B@ A7 DܾDە`u~B@zA8CE `uB@_A7D .D<2d`uB@A8 DcEjQ`uB@SA7{@DaD R`uB@A7@D,GD`uB@A7DgD<$`uB@A6D Et#`uB@A7AD5EW`uB@A6DE"7`uB@dA6p7DQ]EY`vB@A61CET`vB@A6nCC`vB@A5GKC@`v5B@ۧA3CEh`vBB@kA5CsF4&`vB@A3D|F&d`vB@ݨA3DE`w"B@A85DuMD `wB@A6DEK%`yB@A5C>E[`yB@A6Q'Dq`yB@HA6W4D7 D`yB@ A5^C~EE{`yB@A6kDE`zB@pA7.tD D|`zB@gA7D D$`zeB@}A8FCDg`zBA-A4aD`{xB@q A8/D `D̓`|B@A3DE4`|B@/A8\?CtE* `}B@A2\nDDo`B@vA59C&F K`B@:A6nD)EF`VB@A7D`cB@A8/DxC`wB@A6tD>EM`ØB@wA8DE39`ÙB@5A8gD+T`ÚB@A7wDE`ÜB@~A7DDZY}`ÝB@A7gDJD`ÞB@A7uD1*EKn`àB@EA7hDcDr2`åB@pA8fDs@Ct9`æB@pqA8UDC+`üB@A6)D_DyGI`B@ŮA6DҥE`B@0A6UC`B@A8/bDC]1`B@A7SD@Dv*;`B@A6C$|E7`B@A7D5E=`B@wA6CE"Y`B@A6]CxE#`B@A6DREa4`B@A6\-CJE E`$B@A5ZDFV`2B@A5DEr`6B@A3CCEG`B@A5A`B@A6lD{C:`"B@=A6CY9Ep&`$B@A6ɒCDZ`?B@x^A8Fa,ABAsA.7C_DHYa,dBA5+A3~CDE]a,iBAwA1|DC1a,uBAYA/1:CEkia,wBAxA.%CEC+a,BAA. CC\Za-B@FA1ǨD$ E۲a-kB@uA0D ՞FVa/B@A1DJF,waVhBAnA0*C+bC,!aVBArA-CEDC2 auBAmA.TXCauBApA.TZC =DauBASA/C3EauBAZqA.WCE_auBAjA.CUDmauBAAA/U%CeEjauBAuA./ CDvauBA}A.CD[.auBAA.CD auBAA.>CoauBA^A/A/a7C[FC2avTBAA0̺C휋avBAcuA))CDBY/avBAA)fCfavBA A2*CFayBAXA)֕C5ay+B@[A1׏D0SEûay`BAg3A.C_eEaycBA$A/[CHCZzayfBA]A0bDFY|ayBAA0CjF)ayBAA/ CzE XayBAvfA.*C.B5HayBAcA2@=CpayBA$aA/Cz"EKayBAkeA.kD.ECazBAjA.CEeaz]BAtA.BAj!A*?CF;33a@BAQA*CaKB@A1FDEDaǀBAiA.^C(!EaǃBA A03D°FOa BAJA/.CEa BA8A/q4Cہ*F? a BApA.e3CE aȗBA}A.JCvDPaȜBA4A3wCq D| iaȝBA4 A3~C-D aȩBAA/dC( E^aȵBAeA/ ,CEEaȷBAxA.&C DyaBAvA1ݱD"C7a#BA{A.ChD1Ha1BA(A+DD'bB@A5ƆCSRDX[b'B@A8MC}$D+b')B@A8 CCUb'/B@CA8CDb':B@dA89CWiD/Ob';B@A8 CyEFb'FB@iA8D)ICU/=b'IB@aA8PD/ D?b'JB@A8xC]Eib'KB@kA8CC8ib'LB@A8TKCDb'MB@A8.C D֍b'|B@ɲA7CDCb'~B@A7dCEx6b'B@RA7CťD b'B@A8jC}EP<@b'B@GA6ˣCF:`b'B@$A6.C D]+!b'B@?A6c+Cb'B@ӷA6*DEhb'B@dA6CؗEFb'B@A7CEjob'B@ǮA6TLC"Efb'B@ƍA6CGCb'B@gA54ZCGEݿ:b'BAA5 CZwECb(BAA4RDmoC-b(B@A5/D E ;b(!B@A4DZ!F9cb("B@nA5QDE6@FKb(jB@}A7iCBb(~B@A5ƗC6F b)B@A6DCEb)B@A8fD@Cb)B@*A8D[BD;Gb+BATA5,CpE4b+B@A5xDF }Eb+B@A5yCeE1ib+B@A79D8sb+B@;A8CDb+B@ӗA7sC E\‹b,B@A8DCESb,[B@bA5+CLbVlB@xA8CbVB@A6>CEbu6B@A8vMDcC bu[B@dA89CWiD/ObufB@!A8eDDo%buiB@A8 D1E bujB@A8EC֧E0bulB@A8CӨEGbumB@tA8SCxE%bunB@A8CPCbuzB@vnA8DDbuB@PA6D -EebuB@A89CRE 2buB@A7D]D[/buB@A7DI'buB@A6yCE%buB@A7CEE)$MbvB@lA4ӘC Ebv!BA6A3k5CHFCgbv(BAA4D DTbvABA4*A3D'FoFbvBB@A5ɴDF9-bvB@A7fC';@^xbvB@wA6ZvC'EU؆bvB@A8kCE!Obw"B@XA8YaD5C7$bwB@ϔA6aCgEbwB@A8CbxB@dA89CWiD/ObyB@ѪA6D EbyB@A6/^DECX(byB@A8D JbzB@A7D|EibzB@A7C;D}XbzB@A7gC;dAzubz}BAA4L(CCQbzBA7A3CEzIbzBA!PA4B CbBAD{A3CBDDbB@~A6gCӚvEJnbB@A5WCqE%bVB@A7DI'biB@A8DCb{B@A8[D1D߷bÆB@A8+DdbÉB@JA8DFjDIbÊB@`A8CxC?HbÌB@A8c CwZEbÍB@A8DC~|E}bãBAA4nDWXDnbüB@zA7;C 5F wbþB@A6JCAAFbbB@ŏA6y@D`+EͮDbB@A6 CE+ibB@A8D CɀbB@qA6C/C}bB@A7[CDޒbB@LA7>C~Emb$B@A5CFb6BA A4CqbbBA uA4fDZFIbB@ȀA7CۄEfvbľB@bA5U DcFHbB@wA6mhD!WEbEBAX|A9C,C @bƤB@ A8D~DA*bB@A8CUDx*^bB@XA5sCl+Eչb"B@A6DuD3b$B@A6NCE~b&B@A8CDGb'B@؆A7C6EZA>YD HcB?8A@FCEÔcB>TA>Dl{c@B?n{AAaCDmbPczB?ІA@idCr#ENc)JB?:AACC?c)NB?_AACE@c)OB?NBAB2C<2DTc)QB?]AB C_E c)RB?EhAAJCCbc)SB?wnAACzE!c)VB>ABCEuc)WB?9AAC5ECKc)YB>ABRCC"c)`B>pABDuEMAc)aB>AB4C߳Eec)eB>ABvC5E%c*UB?A@CEfHTc+B>[A>ID9zDc+gB?AAgC5DKc+yB?lyAACLD+fc+zB?HAA|CDc+{B?gIAACC#c,B?5A@OCFc,YB?AADCD\hc,B?4AACgEVCc,B?a7AB /CDgc-/B?N|ABCscUB?uA@{C(E9cVGB?q0AA#Ca E)cvB?QABCDBcwjB>AB{MC)EcwnB?LQAAuCEJcwoB?9AACݹEcwqB?fAAC^DcNcwsB?qcAAeABD?F{ccwwB?!AAeCE:scwyB>\ABCE5^cwB>tABDEVcwB?:AA/ CFE"7cwB? )AASCۄ:EPcwB?PAACEcxtB?A@CzF UcxvB?AAiC)EcxxB?A@w(CE|cyB?HABCCcyB?`AA C,CccyB?VAB%yC DDNcz?B?A@C&DczB>AB&gCdEb czB?VAB%yC DDNc{OB>ABCގEYz?chB>ABAAվCʠcŐB?AALOCE_ecőB?qAA0CD cœB?x6AACl$DTQcŖB>XAB#CF@cřB>ABD FDcšB? AACѭE҉ cťB? AACѭE҉ cƕB?AAFC)E5cƖB?+AA`sC㵷E-cƗB?~A@C`cƢB?A@CDʜc_B?ҒA@[C>E$OcɬB?LA@C)F`޹cɸB?[AAIhCCEdc˖B?ޢA@&\C97d'bB@cA8uD bd(iB@A9DC}d)'B@,A? DDjd*kB@ A>D E&$d*B?A=nD w@ d-B@A9Ddu8B@|A:DdvB@,A? DDjdyB?A=nD w@ dÓB@A9ZD/D2ͮd1B?A=nD w@ eBAQe2BA%{A!gwDn]EҳjetBAACbC*euBBA#CD E@ZeBAG_A DEڕdeB@+A"D}eBA^PA!`]DD;ce$BAAJgDFٸe&BAAD!dE,e(BAAjCsE<#e(BApA DX;XF%}e(BASA CD%$Ee(BAA q?CE7Ce(BAA! CF0Je(BAøA!CEʗe(BAA!!Ce(BA:A!CEZe(BAA){ÎFe(BASA(CCoO5FX:Ye(BB?A%boDF!e(BAA"D(0Fe(BAA$D aF_e(BAA CHF%e(BAALC^Fe(BAVA!5bDE*e(BAjA4BH>Fkee(BA-A'DmF e(BA1A#YCRFACe(BAWA4CyF ]e)5BCTA#9ZDƶEte*fBAA'DE Qe*hBAA!C F#e*BAA C?7Dje*BAA!C?e*BA'A!]ADTEe*B@A"D QMEz+e*BAA!DE燣e*B@^A!D%F`-~e*BAQA!KCwFe*B@A!DE?e*BB yA!C8F8tee*BB/A"1CoF{&e*BBOA"DEe*BBxA#=D1sFRe*BBA#LD`E+e*BBA#jDE.e*BB0A#FD TOEe*BC%^A#YDYEte*BC\A#4D/NDFe*BAMA%QDFOme*BA6A&zPCfFT?e*BAA$±CښEe*BAA$7CECw|e*BAFA*7DfEv6e+BAA(}CZCe+BAPA%C2F[ce+BAA$DkDIF%ce+2BAEA$tC̬Eyle+3BAPA&CF e+4BAA'YDiEe+NB@)A!C$Dj\e+BAA'\CEĠe+BAApCՍEe+BAAoCCue+BAIA"DZE@e+BAPA CDoe+BAkA(CcEte+BA(A CXFxe,BAAC9De,BBAAzCgE e,BAABCD0ZVe,BAA X5C2Em e,BAApC\Dye-BAԕA0De.&BASA C{E@e.(BA A!DzEBe.BBA#kDݑe.BAdA'CEe/BAśA#]YCEdQZe/BCEuA#WDETre:BAMADCeMBBՀA#dDUDEeMBAҸA D !C$eMBB)1A#]CXF,^eRBBPA"KDeR BBtA!YCoE'4[eVRBAqA!CEgeVZBAAA%UCMEyeVB@VA!HDF:.eVBA-*A!JCplF /evBAATCEhevBAA!CrF_;evBAA CE,evBA A CC}QFevBAA!,?CϐF`evBAA!_CʩFhZevBAA CKEaevBAA" C=Fb evBAGA!6CE evBA\hA C?Er evBACA!DDevBAA8C!BevBA+AiDwF^PevBA9AD|hF-evBAjA*~sDC\evBAA)`jCpFHFevBAƀA#[CbqGL0evBAA'DCF6evBAA&CwFjVevBAƂA$ǹD#wEMevBA3A#mD!F0evBAbA!bC=:FY]{evBAAwCIF/evBAA!*D%^FSqevBA7AD#?F2'evBAA&hCF&'evBAA(LCevBAbA"'DFevBAUAxDF`dewUBCwA"FD\:D IexBB?A~9D"5"E exBAɁA!̈́DvFmexBAFgA!DCE]exBA~A!D lexBA A!D;Eg0exB@A!ЁCdEexB@A!DWF.{exB@EA!D `EexBAIA$+CMexB@SA!\D Fj$exBABA"T`DiFZ exBAA"YC*xFk^exBBjBA#$?D?E>exBBA#JJD F%KexBBbA#a&DGE2exBBEA#PDOaFXlexBCA#GDE-WexBCA#;}D OPF !ney BArA%CEey BA!A"CAF_ey BAA&ChE eyBAПA$CEᷡey'BAA%CFmey(BAA* D{D]ey;BAA$-CڧE6ueyRBAA$P+DEϘeySBAA&daBʸ'F-GeyTBAA&CT@=xeynB@"A!~D EeyBAe|BAA"TDFѳe|BAFA) DF BBe|BAA(YsCREe}BAA#DÁ#AGxe}BC2A#GD D'e~BA~A D LFeBAADCeBBA#DTEpReBA#A!DDeBA"A!:DleBBA##`DNmC‘erBA A!kwCDJ~oeBAA#[CEޟ/eBArA CYE͊eBAR|A CE QeBBA#$TD_e$BBA#!DeBA!ACE;I"eBAA 7DEPeBA A BD-Es^eBAA MCEmeBAeA ԚB| FVeBA6A yhC}eBAA CEόeBA A!|CFeBA͆A&UChF$heBA՟A$D9$G e BA?AZD "F0eƨBAѿA!D?RFwaeBAnA!CLeBA-A!DEOeBA>A CDeB@A!D FeB@A!D CEYeB@>A!wDöeB@A!D(Em2eBAA!hChDeB@A!D i EteBAA"CFooe BBiA!CEI<e BBAA"9cDoE"e BBbZA#DuEce BB!A#wDExeBBjA#!DfeBBA#FD:E-eBBA#LDZE/eBBֶA#NDOEyeBC")DEg(sB@Q2A=RD cE/g)B@'A:PCCg*B@A:HC]E+g+iB@kA<"DD|.g+B@uhA;rpDDsg,MB@qIA;zD.Erg,sB@d1A<:D#ESig-B@pA9DhRgV?B@^A=!VDgubB@yA:dcCSCgucB@,A:,C_E=|guB@A:D%DguB@}A:ԭD:CguB@rA;XDLjguB@wiA;LDcEmguB@pA;ˑD{CguB@iAfD EcRNgvB@WA=cD JHEugwB@|A:DE gxB@A9կDZD|xgyB@cA<DƜEvMgyB@YA=]D=Eä_gzmB@mA;%DDfgzB@]A=.Du?EgzB@JeA>%D VgXB@A:7DGD?gyB@0A9DD1g~B@A9MCDSgÂB@A:0DCgÃB@A:+sDDzgõB@xvA;QDF8göB@yA;&DFVgB@΀A9CgB@zA;"DDhCgB@tA;DfDgB@jcA<=FD]DqgB@iAWA.QDEWEsFh* B>FA*6DW4E(jh-B>(A+|DDEkhwB>A,D4DhxB>A*D`CtVhxB>A(DYDCp hx:B>XA#"DVh{;B>A)vDIDJ3h>B>qA,aDA*BDOFm:.h1B>nA-nfD.UEhLB>A) (DW?F&hXB>XA*SD\XEfkB> A!:D@kB?AD&E2D譾k)B>ADEEkB>A!D:E8wk5B>A#FDb6DOsk)B?FA$D,;DXk*B?!"A%*D,E2k*B>A%e/D2!Ck* B>A'`DHEzr/k* B>A'D^\ Drk*B>aA!DA!DDFmFxk*&B>]A"ۑDhCPmk*B>ɊAXD vPEއk*B>Ay-D-Fk*B>SAsDD9Fk*B>A^DFcYk*B>M A" Dj`CEk*B>I A"DJ\,Ek*B> A#UDicd>ok+B>)A&A,DPtDk-B>A"BD1FV/k-B>tA!.D?E:k..B?pA!JDeE9Ik.B?sAD%gEGpkV2B>A! DF.ZkVTB>}MAϸDDm)kVUB>>A#@DY DiVkxB?6A$D,bDթkx!B?A%FD,Ekx#B>uA%xD5#C$ukx)B>A(mD`q(Dfkx:B>A!D Fkx;B>7A!D){F#kxB>`A'C'FwkxB>A:+DFAkxB>#AHXDFVkxB>AD!FRkxB? A cD02F7`kxB?*AwD.pCžkxB>LA"ʇDcErkxB>AA"DJJEkxB>YA#2DR!DbDkxB>A#TDgB-UkyB?A!pDDzky:B>A%DEDk{4B>gA"UDHFk{B>^A"?Df k|B>TA6ADFkGADVkRB>A 5D'%EkuB>.A#@DY^DgkIB>A&6DHF+kZB>A D;tE k[B>A!D/,FakfB>MA"1Dg}EnkB>^A6!DF 'zkB>ZA4D VDړkB>AgBDFkB> A#@D[ED<kB>hA#7DTDmkZB>՜A%D=xEkTB>c A";DlZEokB?A!CDBCojknB?A D{dlB?A@CXGC l,sB@\A=:DHDp۠lUB?}A@RC1lVsB@A?TDuEb&lxxB?A@MC4EOlyB@YA=zD C" lzB@D]A>dDCEl{B@ ?A?D#Fyl}vB?vA@KCPl}B?A?CD9lB@%A?2+DE:lQB@(qA?[D(-E lƗB?}A@RC1lB@A?I(DE=m{QB@9A>DC7'mɸB?}A@ CC}nBAmA)C+E4inBAqA+CF|d n'BAA/CDųn'BAA/&ECr-n'BA0A/RCCx-n'BAgA-C[Dn'BA4A-|CPEw5Xn'BAWA/LMCEEn'BAA-C%n'BAgA-C$E,n'BA5A-cC&Dhn(BAOA2GDXF߀n(BArrA1ӊCF1n(BAA0RCREF|fn(BASA0C)Fn(BAGA30GChF[Bn(BA A4H8DCIn(BA4A*]D YF4'n(BAA*hFhDn(BAA*1D+Cn(BAqA,9C`F#n(BAOA,DFn*BAA*ϭC _F ՝n*BA^A*DF/`n+BAvA,|CFQn+BAA*~CFn+BAA-hDa*Fvn+BAlA+CDFNn+BAA0"C_;En+BAA.C>F fn+BAA-QCⱜE<n+BA A- eC8!E~6n+BAnA1C=E3:n+BAA-D9 F1n,dBA6A3CFin,iBAvA1$D@n.BAJA4;DCMn.BAeA)nCEyrnVhBAA0HCŷEnVBAA/DUvF.wnuBA=A/ICGEgnuBAtqA,-CTFZnuBAA-dDPD2nuBAA-lC_qF#:nv!BARA2%Cj2FHjnv"BAzA0cCQFFnv#BAYJA0 qD*~FNnv(BA7*A3DNF]јnvABA`xA2ZC1fDnvTBAA0C!C,FDwnBAA*+CgFnBAA)vPCIhFEnBAA- CR!Dşn>BA`A*UDORDFV4n@BAmA+ߩC%FE7nABAzA*kD$FWnHBAA,DF&n}BA:A0c9CHDUnBA3A- CE nBAA-CDWFn4BAbA.CD?nȗBAaA-C|DknȞBA_A4QD})CSnȤBAsRA1$DcD%nBAJA2D=FnBAOA2cCYFUnBAA,CnD n'BAgA,MC#aE2fn1BAA, jC۶E?nBAZA(TCrbERnBAA)C6nBA-A(dCoB>A`D7nFӨoB?AD(E'3oB?,A>D =o*B?`A$D1xjEpRo*"B? AD"\Do*B@A!D}Eo*B@DA"DFo*B@bA"&{D+F]oo*B@A"ODvFo*B@J>A"n@DiF To*B@eA"UD&dEo*B?A"ND5D\\o*B@ A"D5E? o*B@@A"6DEo*B@&A"DڣDJ-o*B?A!MDcF,ƭo*B?NAfD$].Fb[o*B>@A #CF o*B?S~A D Epo*B?NAfD#QERo*B@iA"DuDOo*B?PA!DE)-o*B@\VA"eD Ewo+RB?]A#nDfEo,OB@ A"D8#Eˮo-B>'A!t@D6.3D7Wo.,B@}A".DEo.-B?A#d:DbEo..B?,A!pD@oVB?AHD$ݘEoV2B>A fD6qoV]B?v8A DDE05ox B?нA#AdD΄F/oxBB? GAED0›EmoxB@A!DADoxB@nA"DeF oxB@*A"2D VEdoxB@! A"[DE-oxB?xA"DEtoxB@ A"W>D[E؁oxB?A"DGExdoxB?;A#SDUEYoxB?A#BDELoxB?A"D7EYoxB?A D.EoxB>3A,DAoxB>;A\DFoxB?XA D%WFPQoxB>A 7D" WF/QoxB?+-AqD* EJ&oyB?WA!D,NE'oyB@FA"p(D0lE֠voy)B?aA 3D$YF'oyrB?)A"-DYCnozMB?6A#nDMFNzozoB@A"ODkDo|LB@cWA" D pEϦo|MB?A#DEeoB@A!)DzEoB@%A"D ToRB>A D7osB@sA"0D]BEΌoB@A"WD Ey]obB?,A>D =oB@A!eC EœJoB@A!DjEվoB@XA"DF" oB?A"D2E'<oB@GA"rD EoB@A"`D%E]oB@ A"SDbEʟoB?A"CE`oB?A"NDNE2oB@xXA!DE1oB?A"imDjB CoB?=AD**`EoB?IAaD,H{E o3B?EA!D3FVo8B@IpA"2iDFkoIB?A eDJD+[oȏB@\A"D0ܘE ToB?A D yEeomB?CA#r D!E\{pB?+A3DDp"B?_A5D2 F.pUB>AA3D$LEvp_B>D ǵD'jxp'B?A6qcD$ffEp(B?-A59D EĘ*p(7B?A6HtD22 F+p(8B?8A59DEt-p(QB?*A5hHDmFDp(RB>`A4{D1F@p(SB>UA3pD!WF"p(TB>NoA2D&߿Enp(UB>+ A2)D'5Ecp(VB>A3pD$-gFD p(XB>A3 DF?p(YB> A1h;D-:ETp(ZB=JA1XCD.yCp(B>nA1D+Ep(B>CA4D$b"Ep(B?ޱA6!D&ET2p(B?A6YsD--Eeǜp(B?A5D{p(B>1A17D,-E?{p)B?)/A3D~Dp)B>>A57DD( kp,bB>UA09DS!D)p,lB>2A2{A3(D Eάp.B?%A3הD~!DMwpVB>A0yDS^ExdpVBB?1xA3DKpuB?A6a`D+ABEpvB?|A6D&uC+pv B?A6D#EspvB?kA6^D&F Xypv;B?WA5}UD+EpvWB?fA6fD$zEmPpvXB?"A4C4FEpvnB?A6_gD#}EspvqB>A5?TDcEʰpvsB>mA2\D#BF@pvtB>AA2YD"fE<pvuB>4 A2D%gEPipvvB>A1D+TE+pvwB>\A2-D#D,(pvxB>iCA2D$pF]JpvyB=-A1[?D1`Cj;pvzB= A1UD.iBapv}B?[A5D&xE9pvB>A1D+ E'pvB?'A5D EvpvB= A1U|D-_A-pvB?bCA5ޮD-F`pvB>A5:D:aDxpvB> A5I0DF 1pvB?\wA6 RDF5pvB?%A4D QyE pwB>jA1D*EZapyB>]A4DE_pzB>A/zDEˎpzB?|AA5G}C>F̜ pzB>fA0gDY^D(Fp{EB?lA64D-pBB>A4 D)bD&p/B?)A6gD(BFN p?B?A6cD%gpwB?BA5ˋD!:(FMpxB>A4:ChnFj]_pđB>A4D&F)opĒB>A4NDFDTpĘB>(A3D$FFpĚB=-A1[5A5GDzE;pB?#A5D[EbpB?/A6YD*EQp;B>kA1D*EZ|peB>WA2ĩD!!CjpoB? A6D!ME@pB?NdA5DCpB?lA6 D"CmpȰB>#A0b\A5JxDDvpqB@pA8},DkqB@cA8uD bq'XB@$A8?CE%ڍq'YB@wA8GKDDU(q'aB@rA8N&C@EDcq'bB@h#A8gdD Dg[q'cB@SDA7;D=Dq'fB@iA8YDME 1q'gB@pA8OD*EkS+q'hB@WA8D E]q'B@A6xD\q'B@3A7AD9E^q'B?EA6D#JEq(7B?A6^A6ȥD)EiqB@mA8BD\WE {TqcB@2A7;sDtF$;qB@A7?D F q5B@FA7DMyCq?B@mA8(DzEi'BYIA!Aˇ+9BF7(ApBqCQO&BRAP*BffڠBFfAB.=BB ACBo['{BWGA'A]DC 'vBBfCJ  +aBцןB{!En /EBs_BCK UB42CY BB\ +aBx܏B:Dn BB\  +aB+_BrD/ +aBڢBID +aBmBJ +aB_BlxFCgإ iBBB8 +aBڢBID %BBB8 2+aB0BCۤI  +aBڢBID 'uBX F A 'uBqA- 'uBoGAȼDO 'uBpOA9D8P 'uBV YQBEl 'uBW~B 'uBWBcv 'u BOUB H 'u B\<A^D] 'u@BV. BҍE2 'uBBWͿBIEO 'uCBV'B!E: 'uBdUB$DR 'u&BVl @&9Eټ 'u'BUBDEIg 'uB|/B -Dvb 'uBvUAWD 'uBz BXD 'uBlBBcT 'u_BQZs@yE= 'u`BPT+Bi0DED@ 'uaBRF_*EFf 'uyBhrBhE#E  'uzBbz>B E  'u{BgkBE / 'uBV;Ž>BF/ 'u2BQ /BR?D>V 'u3BR BFBE 'u4BQbBcKEs 'u&BjbtB*0! 'u&BnAA 'u&Br;B SzEd 'u&BzBC 'u&Bu"B CD}4 'u'bB~%{A{VCk 'u'BQu6BF 'u'BPX?TB`>Es 'u'B\B EO 'u'BQB^C{O2 'u'BS.B>iE  'u'BQeBciDd 'u)Bg> cB1 'uAB} B4DZ  'uABh BJi?E:x 'uABURAۗYE  'uABQ,BD>  'uABPpHfB]EaB&E 'uBBS}vBEV  'uBBWB*D  'uBBl[BD=, 'uBBjbtB*0! 'uBBoHB2%E1R 'uBBV\B=CE\ 'uBBVBޣE92  'uBB[ BCT|F^ 'uBBpJA}D 'uBBsږACV 'uBBXxuA;oE8) 'uBB]+aBljE6C 'uCB] ]RJeF(7 'uCBW:1BE 'vBBeQD k 'v5B+}B#(Dri 'vBB{CF_ 'vB<iB>E- 'vBZB? 'vBcXBSD 'vB'BnZD  'vBkjBq 'vBZEB = 'vBjɗB4C֝ 'vB,BMEZF 'vBVBCD> 'vB"UB@gD8 'v&B/rBĘDh 'v&BBAC2 'v&B{B`B 'v&Bo{BP9X 'v&BZEB = 'v&B|BC 'v'BΈB=D 'v' B_BwOC 'v'bBNA\D( 'v'cBC"A 'vABNBEE1G 'vBoB/rB! 'vBtB3-B7E 'vBuBU{B9aEaij 'wB_1B Cn 'wBS1§QEۯt 'w BTB 'w'BP8kARDD 'w'BOA 'wAB\qAA 'wBBTB 'wBBREBW]D 'wBBSBlff 'xSBBS0AI 'xTB +BЍ@L 'xUB/BوsA( 'xB{BقB 'x#BqpBr 'x+,B#B԰ 'x+-B$(vC D| 'x-B#(=BրC{I 'x>BQB@ 'BWzB C 'BQf8BL ''BS>֎BzE 'BBfA RB"V 'BBWB&C- +aB B0DL` +aBB~D +aB֟B +aB];B62C? +aBHBcAy +aB{B8!D  +aB#BxDM +a#BB[D +a#BsҵB1C +a#B`JB*E+R +a#B{'Bdž[C +a#BgB~1D^ʿ +a$BΖB +a$B֠BD& +a$B2B:D~ +a'B֟B +a,B¯՗B Cn +a-MB%ܠBT +a?-BiBDr\ +a@BݵB2q(Ca'' +a@B*5Bm{Dv /EMB\RBC;, /EVB]5BC3 /EWB_[B /EbB+C%7Ch /EBe1C& /EBq;CC  /EB[BsDF# /EBCKPCPCD+ /E$8B^BgCN /E$9BcB /E$:B_`BD / /E$B 5CӟD /E$BΖB /E$BV\BBʖ /E$B^BD /E%B֟B /E%MB?BD8A /E'aBVBkBB-Dћs /E>BcB /E>B^XCb /E?B BBE74 /E?BEYBDg/ /EAB~FC# :B7t!Bz :B:z|BFFY. :B %²\sFm$ :B@B'aEr :BMi¼F :B+$^B= :BY AE :&Bu7Bf]ET :'B]BBzϣFe :(B{HBFEk :)BG =ByFs~ :*B&( Ci×F{ :+BT `CCuFB :BW .BE} :B|; Ft :BL4BЈ!FD :HB( BkHD :ZB_ PBjFb :[B< FBCI :BW P'BbE- :Bf BDW :#BW @XE} :$BWj ӲEi :BV ABMDJ^ :!SB+H pC_F :!TB~\B; FI :!vBȮbBba?EN :!B9ByE0 :!B .yBD1  :"`BX F A :"iB\ TBuZE :"jB_y جF] :"kBVL k?h/F  :"vBnGA :"}Bg B`; :"B[ .B;E~ :"BEg CXVF~A :(^B1BZDO} :(dBDB,j :(oBf `AEp| :(wB 0BF :(xBU*BF :(yB >+BMF<8 :(BM f@C[F5 :(B7%BDD " :)BYJ wBEz :)Be g(E :)Bd BEd :)B]T _BCER :)BthB{E< :)B> sB :* BRSBˏ D :* BZ dBoRCς :*|B ZB{Cp :BBb ABx :zBB :B7u$C7%ET~ :B&CpD' :B@CkMrCC :B7/crB*F :BMC;E : DB-CA : RBԒCe@F] :!EBpBLF :!FBRB<Ehu :!tB-CDH| :!uB_u{C RFǓ :!B}mC}EEb3 :!BnCP7C* :!B#BBCX :!BmzCTJFH^ :!BŀCDLkFw& :!BCF.NCܙ :!BaCb^-F& :!B5:dCFw :"B>dCFD :"?B`Bν;EB< :"@B!CH` : FBNCWCM : Bl+ }zFUW : BeAB`F$+  : B}IBE :!EB:3BAEhO :!uBޮCC  :!vBVGBO4FE  :!zB8 B‰Dʞ :!{BBER} :!B KBZ :!BinB8C :!BTZJBF*D :!B~߁BhF :!B|C F|uN :!BUB"'F*K :!B*8BE :!BBfAEgJ :!B6C0X :!B[C< F  :!BkF^BlF/ :!B/bBSaFPN :!B}e BF2\ :!B֠NBF& :!B\KBuF! :"BBuBE :"B`ٟBF: :"BCCTaoEkH :"B K(C7 :"@B4]BƇDS :"BBwuC#8E :"CBTCC 2 :"XBBF'S :"YBEC ]F :"ZB CjF#) :"[B_CGFrM :"\BH`CWyFgg :"]Bu-cC vFU :(^B RCZFk :)B!JrCO+ :*|B{ 8B"DN?5 :/5BBF`\ :/6BJBET :/7B^DBF ;zB|Bw o[B(4BCm o]BJѥB`DX7 oBNBr oB4ƼBE oSBNBr UB B D&  U.B#CC UOBKA)/B UPBKA)/B UQBK@ABPzB UnB^BѶF]r UqBjBeB҂t UrB}CD>W U Bj,+B XC U7BS ZCֻE U:BٹBr- U[BqB#UE U\B# BEfW U]BAEC U^B`BC!FS UB?)B/ UBKxAاBs UBR~D UBWCAI>+D UBSRۓD UBY A] Cg UB"1BȽBFh@vC*+BFh@vC*+BFNx@C/BFPe@CE e/BFVz@C)E /BFM@PCkD/BF@ϘC 38BF@OcCDAm39BF{@ŸBCoD]3:BF@“C3DBFq@ť7CS3EBFq@ť7CS:BF@ >C:BFsU@ŭCN:BF@XMCYy:BF@XMCYy:BF@pC¬:BF@YCF:BFm:@~CuE:BFN/@Cy:BFT@C;+BFi@C_E;.BFh@vC*;HBFP@CD;]BFx@«C];aBFNS@ CBۅ;BFYv@-oCfD ;BF_@ ]CeEP;BF@XMCYy;BFV@C;BF@OcCDAm;BF@ >C;BFT@C;BFS+@/CĸD;BF\b@WCoNER<BFq@ť7CS< BFV%@wCd<BFL@CЃBFS+@/CĸD=WBF@'C=XBFS@:C=YBF_V@\C?=^BF@'C=_BF@'C=BF@XMCYyOBFsU@ŭCNBG p@G C#`F[23HBF@Ȗ\CE;3JBF@ȆOCVEy971BF@CJDѱ<7BG|@ξCS>F?7BGp@ΕC\F7BGz@ΨoCRuFb):BFW@,CE :BFW@,CE :BFR@ǧdC${;BF@ȆOCVEy9;(BF@)C'o;)BFõ@C?;mBF@ϱ|CYCe;BFM@Σ[C3XE4A;BF@vCCoEmف;BF@vCCoEmف;BGE@ICDHE0;BG@p@C/aD;BFNS@ CBۅ<:BGZ$@V}CS^<;BGZ@{CLrEtB@CC M4BF@C[EMBF$@TCS_C?M #BF@[C}F OKM $BF@ѓCCCgM BG@сC[EM BFi@j C^rEUVDM(BF@C>ĎD M(BF@͐C6}D.M(BFk@sClCBU]1BF IBU4A@eD BU09B1YF:BUĬAXEQ:BUMQB2EW:BU!%BF=E9g:BU ůBS)EH},:BUKB4cE1:BU WWBTEi^:BU"ñB;dE:BU AmBrE):BU~čA5E8:BUİBFyE:BU AEP:BU( 0B1xD\Ѫ:BU'BdcD#:BU(2BADd}u;BU|)A] =;BUCǤB9;{BUB zEE;|BUŀBD~;}BU#aaB-zED;BU._CAQAā;BUnj B#E;BUIAeD);BUGƼAA};BU+ŬNBGEw;BU#ƨB07E&D;BU3%uB4E-;;BUN3MAC;BUŢCcEBP;BUo^CEk~;BU:\DB-EDg;BUPB0KEq<BU+tŁAVE<BU/ɦBY=Ei<BUAH,E?< BUm:BehDYTTA 5D ==7BU?VAD=>BU'ABB=`BUwPƊAX=aBU.$LkAD)=fBU#S BdE=BU,%B F;)=BU=8AEE7@=BUBţ€RE͕=BUhƑC E[=BU`uBO0D{o=BU;ڶB E=BUuB] DAn=BU&BREbc.=BU#ÿjBK^6E=BU#QvBE=BUN3A6tDs=BU>QŶbE:=BUNdAPDf=BUM,AZD=BUCŧAh5E;=BU;>B#aC,=BUBEPr=BUbBZsCK=BUZśQBΌE{3=BU Bه E|3=BU OJ1A"E=BU dGBEdy=BU0$A)sDY > BU/YĎCMw>BU%pßvA8B2>)BU6=E>*BU#ýBL3 E>+BU; qBEWm>,BU.TWAuE}>-BUBLE\>.BU}E>/BUKGCAE|ӿ>0BUUB2PEe1>1BUB:5E2a>2BU{BEK>CBU$B?E?>_BU#l5B>xBUQB#iBDNP>yBUĆBkպE->zBT&f B+Dh>{BTLB3E )>|BUMąBoAE&e>}BTA&cE,Ϛ BUA۳+A]BUKGAqDgBU6ŧA\HEHtBUĬxB'E_BU#yo)B $MB%BU(@ EGDBU"D@Ec BU2tAfEAQ!BU)53B5E&p"BU&ŏBEq#BU-Kż%BjD9pBU#ҖAMEH "sBU$Ã>/BUvBjBU,V@YB/BU*+8A+x BBU%dĢB?Exu BU;b4xAMD0Rp`BU'4BiڐDiaBU'KlBigmbBU']dBcPCBUeBRoBUvBjBUXyC FBU]ť6BE;BUsXBuEXBUƽjBDBUwBD\VBU`݋BDoBU,zčhA5E2BU+#B#Bv˼BU.Ƽ;BDS BUƯBEYBU=BuODXBU ĖZ@AD:BU'rhBn@:BUPAC''BU.BcBUǟ*B*DBU1@UELBU.āAgvDBUĪB?Et@:BU D|BE<BU*HAxQE*BUĹBGVEi|ABU Mm.B6.EV4$BU/OĉAC_mBUCƨADmnBU?yPA'EG qBU6%E!rBU/CAeDx.sBUC7AH?Er}BUKǯB?E)-~BU!Ǟ2C+OE.BUyB鵄DRBU&5BlJAoBUJABDkBU'íBAFE_BU$BSE|BU, eAEK BU(C!=D.BUB̸6D2'!BUĚ BE"BUiƨB~qEH c#BUeŝdB)EjdBU77d@lDvBUBҕDBU%:A]|EBU..A E+4BU,AxWE[=VBU@LBUeCpBEO|MBU'WIJADTNBU%ZB4dDOOBU$͒B;9D<PBU6oB[E]BU@i/ADj_BU.ĞB 9bEC`BU'įtATD aBU/r@DѱBU5rA'OXD88BU.iBkNEBUŇB1IEavBU Q_BoDUBU0zĥCPBU,ĎAskEPBU8w'@rMEBU1tIMZEiBU TM;bEFBU"%|B"ǎE #MBU"0Bn[DBU8 B-DFBU!P-{BDBU0*v6@+D{[BU'ۨBD^DDoBUBDPBUذBۢDp BU40|zA̢Cka BU1|>)fCBU3@LwEdBU-wBDEB#  BUOCA/,BU?BfAE-BUEQƢ7ADBUeA)0@U0;BUUyIBN;BUABCBU*[VAB>_BU%OÂ@4CJBvBUB{F@j[BwBUBh F0gxBxBUڗB!xqFx'ByBUدBŢE'BBU֗BEǔyBBUlAF_BBUnϳB"DBBUo|BElBBU6B׷EE[BBU2B4DywBBUUBTBBU_}JBq;FCBU9HcByCBUtBk:E[dCBUnρBJ}CBUphB\ۃD0CBUsBRCBUoĶB^"D&CBUqseB !CSC BUl SB=CDʛC!BUnϺBIw_D,ݎC#BUn B&iCkIC$BUnϨ B }C&BUӊB0CxC(BUKֲ]Bq]DC+BUEkB`~`EePC,BU{}'BPVF8C-BUݛBSF:jCEBUr7Bb+EMCFBUbFBCGBUvBF3CHBUrJBKE$CIBUv%(B`[E;CJBU#ӟkB&sELC[BUo|A"El#CiBUppZAQZEfCjBUsBqcEh hClBUn`B,0D9cCmBUr(T+BvE:;CyBU(8 ZBPDnCzBU4B"E1C{BUCM8FCBU{BfEvCBUA.BCBU9HcByCBUDBuALEEnCBU:xBE=CBUuU B(CCBU@FB{uDCBU:ZBwC7}CBUu>BXCBU}{Ӵ;BCtE4CBUv+ Bt&DT@CBU}Kӭ`B\VVDCBUFB DCBUx5B2-CBU_rBhFTCBUKc/Cv˓FėCBUhBF)CBUpBuFF[aBUϡuFQaBUEŗB>6EaBU}|F aBU*y)BUF*ɯbBUF:BOE0bBUBP2E۠b#BUKB]̉D b$BU C>0Eb7BU~B\-UE`b8BUhIBxHEboBU߻BE.bpBUƱBbBUpBdE9$bBU3ֺCTF*bBU|7B?}bBUdB9D[BU-8I@QA(BUALBU0O4@,GA BU"Ø,A]EKBUGB\BUR{BhE1 {BUBtc$B?EBU'@۶FkBUrЗBEK|BUmAS@SBU)?ACvDX6BUnkeAC,BUlqwAVBUn|Ba C&BUnԗBnWDlBUoQбBe*SDpBUQQwByDΫrrBUIBiS1DMMBU, đNAffBUoBC 7'%BUPA ʼAna'.BT>i?—B'4BTE'8BTqÌRF'9BT CbF':BTfÆcG '=BTCF4'ABT@ A'VBTAF}'BT"چE 'BTqØ_#F>">BU"^Q7G 8>BU_?+BZ>BU ~D?FϽ>BT2F>BUDB\>BU{ÇF>BUV*EB7>BTDXEG >BU' _0HxFs؝>BTM7ßFk>BT(nM?6>BTB0F53>BU, ڷD$^FO>BT)M>BT3 ?CXF=f>BU&|BF& >BTnCRFrS%>BTV AF">BTF B/FEo>BU mDxMF{>BUTeBwEWͨ>BUAчmF >BTBCDFt >BU2DM6Fꠅ>BU FDF/K>BU } gCF7>BUJ4XAJ͹C>BUT/BѐF >BU ] LDG&F>BUK CaE>BT; rB YE;E>BU o B5E>BT!ԕP%@:>BU ~BؚCD>BU^C60E(>BT#?r@>BT@|SD~~>BT U@EY>BTFt9EQA?BU4)A89DRx? BUY%A~?BTCBFg?0BUGA"1?HBTGCHFR?IBT EQF?JBU DXF ?XBU| jACK?_BUB8E7u??`BU: pBCE?aBU C2F)g?fBUOB6?hBUr+BHEI?vBT^݌@DZA?wBTFD/?{BU/(}AC2?BU Av?BUSBgŢ?BUYuBcUE%?BU^?B?BUR`@^?BUoBVEK?BU,3eAZC?BU rANyC?BT(@w ??BU \ACR?BT  >?BUQEAdC6?BU2~B8F?BTEuBF Y?BU*"Aj@BU "@BT$ BlDCs@BUI?/gG d@&BUC,GBbF@(BT\7DFGRE[@)BT %ADasF֚@:BTۡxGC@;BTԺ[WG@AGmBUABGqC:qhBU1sBXF gqBU !BwqBU`PB$D)yBU siE _Gr@BULݿF$;'.BUbAEJBT[~@ȸ(E=Ǐ>BU-,AϯE />BU<mDTv>BU##N@gbEb>BTW9wEX>BT =@zE>BUCACl>BU+ AF>BT|>xJ 4G>BU , >NEhy>BU+/ʷ})F/>BU4Cj֓E>BULÙWFJ>BT/ -*A0EFUu>BUk(B^E>BU @E s>BUfZUDT!>BU>}D8>BUHGE!(>BUG{E>BU,¸E_x>BU!զEx>BU+ 5A>BUsD>BUi[&mE'+>>BU\UE>BU Ͼ ||FK">BU)*B<ޡDz>BU$}UA 1E>BU `fU7F<>BT¦YF.V>BTXG>BU?ܬ?>BU&Z6EY>BUiQC@&E >BU"8>C>E>BUARD>BUk .2D>BU;c?DtR?BU+d5E?BUJ0E_A?BU$|KEL(?BU"HEP?BU'Iګ E?BU+ BEx?BUi@) ;D/|?BUaOC(5?BUL;DNm?BUY-@&?BUg6JĜ?BU$Ž,&E?BUlAD?BU& ExH?BU"VAvD'?BU7 B@D?BU$7>IE3p?!BU#LBˠELK?"BU*^BDlu?/BU & A/ XF1X?0BUq/-F?1BUE?5BU38AVvEL?6BUo*eE?7BUhPEC?8BU"M1V-]FI?9BUˆkE@?:BU SD/X?ZBUr …iE?[BU@ B??\BU.ymE.g?kBUu(?#D?lBUcAE?mBU A4>El?nBU2ADG ?oBU@=Di_?pBU(ACE?qBU$3u.E#?rBUXAoD2?vBUW{A7?xBU!@ EP?yBUYANDEhQ?zBU @QDFj/?{BU'r@E,?BU TBF-g?BU:Jm3FcD|?BU+p`BwNCԱw?BUdl A9@??BT5@jE}?BTq~B Eז?BU)eVEBD?BU"3fָiE<?BU8pAШD?BUFAD ?BU#B`E7n?BU'nD?BUMA D?BU gA$ E["s?BU"PEB?BU i'cEkZL?BU&Lڏ:$E1?BU$2=CCE ?BU'0LlE/Yj?BU –Ex)?BU)=z@SE v?BU*RaKEX?BU"P78EE?BU"%A Dį?BU04Dq?BU$@B?BU!aGeEl1o?BU& ,ADXE?BU,RoD)?BU+E AE 5?BU'1:C?BU'G1EC?BU'B D7a?BU#lA*Ei?BUQٰE_J?BU%{B&qD?BU AG>E6N?BU)#AD?BUF[_B:F?BU. A_I?BUA\DU?BU AC?BUxAdUCC?BU'oBXF?BU!nF?BUAGAE!?BU4Ϡj6E?BU*µ\E!?BUyqEhl?BUQ{B+X E$?BU@bA;D2?BUQQ@7DJ?BUDAK;E?BU AELG?BU BէE ?BUr@51?BUyC.F?BTD9%@cEx?BUtk}G ?BUCc̮E+?BUJBjEf?BU:xZD^)9?BU²)E?BUSpAwEx~?BU$kB+EY?BU&z@@E"+@BU!!BCEB@BU(ެ5~E6@BT؆ .BQb@BUߔA@CZ@ BU+p`BwNCԱw@ BUE @ BUWGz!D.@ BUTzD:t@BU"%"EJzn@BUT]E@BUL$MEͽ@BUAC %@BUnAEXD4U@BUsAQDU@BU rA@YD}@BT BT_F>BTߟ) FC>BTECdG>BU&CGݬ>BU @KOEjE>BU2Fk>BUI @ԦF&|>BUO)DDu>BU0A[C33>BUw 1bAMkCnӄ>BT3 LAEVW>BT #|CêFXI>BT CIiFz>BU!ҾD _GT$>BT/£\F4>BT*F>BT w@Ec>BUl:F>BU,!@)Ae?BURwвDsBF`6?BTwbDQ>.G*T? BU!P_~F?)BU>BG?*BU!T2Fn?+BT2fkD?/BU KA7SNEMP]?@BUAE -?YBU5OA?aBU lA?kBUiAy?vBT0*n\?F?wBTJzC1G?BU 76A? ^D#L?BUF-AޟCJ?BT1Z@I{DD?BTǷA C?BU6&@yOE+T?BU  A D:?BUfY~B9"E"X?BU3OA]D_\?BU; bWF?BTwO@+F踍?BU^D^8FA?BUcl7A^?BU"7zG g?BTV&PF?BUYg3ABy@BT) 1uA~E@BT$ PA@BU ?7L@(BU BvFD2D@:BT8y%|BU%AD- BvBUskBcCϴ0B{BTب3BF B|BTBE9>B}BT/قaBEөBBUnӧB )EBBUBBBTBE7eBBTB+EcvBBUԿB CBBT@_BB@DBBT(B-HE0BBTw݃SBE$DfBBTBgF&iHBBTOևBFD'BBUBCnFBBTՖBD[ZBBTBT|LBqmDoaCCBT9B4EШCQBUE4B#DBCRBTB&EZCSBTbBDXCTBT#B/{EbCUBT̟@-BK}EE}CVBT5B@AAoH(BUoA͠mB7BT؁B:D,IBUD A<{CGJBU- ACZϏBUiBj1'BTBCGBUD B06CGBUSBkBU`MA:BUrBAlEqƄ:BU`ȃB"KCc:BU`~ȁ4ACi:BU`AԮDO):BU_Z3B,D:BUbCB$LDFg:BUguAC@F:BUg A4Dk:BUf A471D \:BUc XAD"+;BUiAŀC;BUg5BDp;BUhAB WD*go;BU_tAZ^C;*BUbȈ A,C.;;BU^AAt3iD[;;tBUbA3B;uBU^kArCy@$Dc'=BUb6oB)DO=BUd ȀA=BUi(A+=)BUb!qAD=*BU_F$AD&=+BUc3:AW0Dk=0BUc B LND6=1BUiAW D,=BUfDcAhs=BUanAtC{->=BU]IǮuACT>>BU^AmD=N>?BU`A;DErBUbf ACCBU_e"AњCLLBUa{ȓAv C WBUfBABBUfC'AN~C`%BUcpUJA CBUcoh@BU^H A+BUb A!BU^uB7HVBABUaGAɅBN\BUg( CA7C߯_BUehA[FD BUgAC_NBUfB AqD7mBUgAitD,BU]îA.BU\.ǧB1'C JBUcBA@DVC[BUat APQC\BUcPAMDHDBU^SveB#lABU^uB2cB)BU`wB xCг/BUa,v.AzC%BU_LAIBw=BUcST@ C0BU^ACft1BU^ AžC2BU`A9Dl3BU_KjB Dr4BU_fAAٱcC5BUaGzhAʝD BTBUc/HAԝD4UBUbcQZAДCbVBUcifAYD:}WBUg)ACXBUf@APB*YBUf:ACBU_ABUa( AHACBUa A RCJ.BU_4AYA37BUdQAWDgBU`AĤDTBUd+BDG~ BUbXyAD&)BUbA`*D•=BUiBA*D7BUa}ȂAg5Cn8BUaȁwAnLC꽾9BUaȣxADDRBUaotA"CSBUaf Ah!C*U]BUdNB$cBU_LAmB+SheBU_hA9%BhBUahAnDXBUf@A:DJ,BU^{v B)xABU^ubB0BUgF%PAj]A>BU`syAyD>$BUdWAC~!DFPBUa\Af!CұBUd$;AI[DJ-BUcsA9CBUf(AtDeBUb4|AԯC\BUcwȦAKQCeBUc*(AuBUcST@ CQBU_LA);CܬYBUeO-A|DYZBUd9ALrCq[BUe?uAУCBUb,AD.XaBU_wB CBUc|@BU\ǩADCF BU(Ă0A&\E :BU{^ƋEA^D9 :BUHJCFdU :BU{ƌAdD  :BU(@[B+0 ;BU3Ve˜C!/ ;BUg^A@ ;BU3MA[EB ;BU!ãNA;ɫEf@ ;BUPUBpDeMe ;5BUvA&D ;6BUrnY;ACz ;7BUr{XAko)Cu ;_BUr"K@ PC/s ;`BUr>@QD\] ;aBUr//Ap ;BU770@K ;BU ÎgAESbi ;BUrPA)T ;BU/"AΪE> <BU/Ȭ@8s\Db/ <@BU,R0Agp BUuuVA@D{k > BUwƐADOX > BUxƐ;AD  > BU6R68+GED >BUÔBs!D >)BU/ AA\C >*BU4A@}O >,BU-:5AD= >-BUÍB@ U >0BUÎ?Bݴ9 >sBU77@@ٍt IBUB 'GB BU=AAhs "BU%K B /BUrw@A;d 8BUx8TASD 9BUv*AE9 q :BUxƌA6D[  pBU!qB2pED sBU#éA4EbT yBU/| 7 BU+'@E?. BU$br[AEp vBU.ļA9X BUr/@QD BUT AE BUuH+A v:D3 BU(ABtHÊ BU0->E $BU05AD>EWj bBU3*f@$Ep qBU/hĀ?D>O |BUtA:pCt BU$ì`Ah$B4G BU)&AE~h BU-EO@\@5 BU/ Ĝ dBUVcJA8TFp BU&l.>333 BU/Ġ@C BU.?a DS BUAuC Y BU1iBAMD0 BUGh¯1F+) BU|ƌ(AsCM BU&ðYAy PBU2Y tA/_E1I ]BU+B9w@EE 3 _BU0=AD[ `BU.<[AO7BG BUrSAj BU/iģ@_yDw BU,~ĢAFHEUf BUzAģ BUQ~@.F BU0I5:|?+' BU4}2@l5E5 BU/Ā@BJ BU-MA?wA= BU2 @R BU36AV E~J 2BU!'BJEK"BU(Yʑ]B D6":BUX<>BgED6":BUOˏB|E":BUX bBKDu":BU: BFP":BU\uBE"; BUJ B\E-r";BU\ȋB";BUBE(\i";BU%ʄBaH";'BUn͉B[B";)BUj;ΫB7";,BUaɹAhu";8BUUvO}B-";:BUUOBp{D(";?BUm9̒AD}";BUaɖADLv6";BU^.5AP(E9";BU[oSB-DX)";BUm<ֿE>4";BUk3JB'E";BUn͜BEeCL";BULC$(IE!jB";BU_88BNEL?I";BU^FBSE[#";BUccBBD "< BU0BŹWD""<.BUB˱BgfDC"BrzEWG"<1BUD˺BTLEK"<_BU1eBD"<`BU3'BD"PBUk;B3jD">QBUj\΄BANDi8">UBUmKݭB@H">pBU xB">qBUhBheD! "BBUԑB!"BBTkIB D#Ӫ"BBUӧqBTC2"C#BUnw|B"yQDO"CEBUnϨ B }"CBUS mB|E"BU[ʯB"RD"BUX bBKDu"BUWt;B|o"BUl̐BO.D"BUi BIEC"BUm̻BbD"BUG(Ba,Dd"-BUǾB".BU+ʠdBD{"PBU_LA|D"QBUa9ɬVAlDr"TBUa<ɩhA[#DY?"BUBUTD%e"BUIBEH"BUT̥|Byu"BU%ʁ#BDj"BU@}BE_"BU)ʗ`BzD1"BU.zBSET"BU"-B÷gE_r!"BU+6ʴB٤E"&BUi0̮BHEPy"@BUU;_Bv)DR["BUJC/Er1"BU#BnE9t"BUBHD["BU]fB[C"BUU̎BC"BUU;_Bv)DR["BUGԄBUb"FBUbnB Eo"GBUV ,BD.a"HBUbBvEJ"IBUUgrB/Cu"JBUV ,BD.a"KBUa5EBHuEK"fBUikBD^"gBUWHiB{"hBUi?Ai;D"kBUixmAD_ "lBUlWͭhBHD-"uBU1eBD"BUiPf@D뛟"BUkpBD?E"BUb!$Be;E"BUkB7|DIP"BUm$'BdYD"BUkxBmD8"BUT̸BxC["BUaCEwF"BUfBC"BU'+ʋ}BD&"OBU0B8DHA"lBUb BE(M"rBUTBD"xBUm̤B%s)DZ"yBUl}BL`8B-d"zBUn Bb%DP "BUkZ\BwsCAt"BUf6_B;HD"BUKB%Di"BU8B-GDq"BUny͔B, D"BU[`B*\DH"BUW˴LBC"BUY)A_DC"BUPxBXu"BU^SB,EA"BUW3B~B"BUXBBD"BUaIlBkE|X"BUWΜGB}"BUWu6BEi"BUF+B˿ED" BU&WB[ERI"1BU_hgA @$BS"BUjXAy8E "BUB\6EBU?,ӪC4+F64>BTZBF74>BUT>B^F84>BUy BDCD_4>BUA?WF(#4>BUkV7baF}:4>BUB\n@dF4>BUg݄CfLFCi4>BUOBq0`F c4>BUqn#BE{4>BU!ҬC\E4>BUQA`Fu04>BU9PÐSFo4>BUId~1Fyj54>BU~ BgC8@4>BUbBC4?HBTQlB4?_BU BJE.4?eBU\TWBF%D4?fBUR4BEA$4?gBUHٗA_EX4?hBUZ^BCrkEh74?iBUSB kF ,4?jBUMNANJE4?BUV3BE4?BU:B E4@BU (CE;C*4@BU\W\C DF";4BBTx=B4BBTo݅WB4C)BTBsB~E(4C*BT BDq4C;BT/7B2hC4CBTOR2E4C?BTᥚBLE44C@BTq|BE' 4CQBUaB[DP4CYBT!hBpD\_4CZBT ޶B6Dǎ4CBTUӱBTE<4CBT =BOXD^d4CBT%`E4]BTSDB@Fs4]BTB Ef!4]BTPB E34]BTFQ BE:4]BTtnlBCn4]BToֈED24]BUQ+sA̰G4]BTBDv4]BTo0A?:F H'4]BTqBEK-4^BT ЈB\AEB4^BT\CBE_H4^BTH4BU6r:F,&4BU_#FRp4BUG ¦jEB4BUn F8k4BUBQGFaC44BUBFoas4BUJC#G94 BUuHaC C5Eè4 BUA+`CaF4BU?C 2F{4BUîg Fq4BU*?$F24BUE"Fo?4BUZCVkFU4BU:wB#$E4 BUE f4!BU,HÔOXFRH4"BUY5MF624#BUND3E4$BU7W:bF24%BUgIBXE4&BUBF+W4'BU~3O¾Eys4:BU3"HE '4BU5HA)4^BUbBEv[4`BUpːB4fBUzB EyA'4gBU&3AD4lBU6¾kF 4mBU}CoFءa4nBUBaAF|lD4oBUPlBdoE4pBUBS,uE4qBUAaHEB`;,BU`IAD[`;?BUnGEB;DB`;BBUiB-`;BUo1BP2-`BU'AB'@md`=IBUjWBsCԧ`=JBUhXV2Bm)`=qBUjsZB@D9`=BUWYBl~Cm`>)BU0Q4?O`>PBUl4ͺ^B[CZ`BUm9EBCBF`TBUaZɮAx|D`BU.m{@ `BUsE~A^`BU)-ADU`BU.ġAJT=B`4BUmB7DEDЅ`5BUk8B?uB2&N`hBUhq·B=`lBUo66BmCV`BU /BaKF .M`BU;B` BUoW#Bj`BUiRmBKܶC``BU_BA\s` BUlx̕UBycCl`#BUc8pAC`&BUaɓAoDW`)BUj8QBFC׬`,BUiUBs?C`.BUk3NB)H`?BUnY]BVhs`BUcVA:$bBU=ŚASE]b:BUB{`BBgFI΄b:BUcVAnAJb:BUjBb:BUg *A5B<b;BUiC޿B 7DbTb;BUX5mADYOb;BU[ wAUDJb;,BUbuɞAC7b;;BU_(AMC<2Eb;=BUgR6Agj|AHb;aBUO4\A1,F 6b;bBU4Ő@Db;cBUp~ATDZb;jBUcͿD;b;oBUc:ȶ$ANEb;zBUadtANb;~BUF ;Fb;BUbBEAܑ\Db;BUFŻ(A}F b;BUYLJASW.Duyrb;BU]kA~D<b;BUbɋ@%b;BUo4njAb;BUmfǦA 6Dwb;BUiݡAЯ@DGSb;BUiKACxsSb;BUTE?AiDJb<BU/!qAECb BUsIQ@b>+BU$ÿAymb>0BUFÂVBɿ}b>1BU]TB(1A$b>=BU\DzAb>?BU_d댐Bb>CBUÍBbBUc{AsDjf,bBU[/bBUAAfE bBUYǀVAnSCTbBUYwrD>yabBUFŬ)EbBUY)M%D0DfbBUe"t@bBUg9%Al=mAebBUÍB߂ b/BUr>@+C(b1BUppHAbQBUcW'A}-b[BUm$ǩRAU$C[b\BUiLA=DOb_BUgA4DbyBU.~B^]bBUi0A =bBUi[ABCHbBU[ǝwA:UC2bBU7*ŐBF7(bBUXǒABD.]bBUZǍAkDObBUWGfBDbBUV:A#DsbBUsQL[AQ|CPbBUq|hAD=bBUo4njAbGBUF1$jA6LF.pbHBUXuA9DY)6bIBUZBwA5D~bBU^BA8D4PbBU]A߸DSbBU]:AuDo!bBU_dA*mBrbBU^7tA8{/D*bBU^+AD&mbBUUWLA,BAiabBUQ&AyDqibBUR,@DSbBU_mAb"BUnIǙSAkC(b&BUsEtAP/bnBUA-mA )CΩ b|BUlAK?bBUg=#ASB6bBUc!@hBbBUd@cD4b'BUm^ǦAPjbBU.ķAzA@PbBU_;ACW9bBU`@CxbBUd`IACC bBU]ǻA;`|CbBU]eǽB%bBU`2sBVAbBU9O@0 D;bBUuƏZBU: A3CN! BU-Uz4BN!RBU-RAmC.N!lBU.7AkiA0=N!BU6)J:-E2N!BU8A\)N!BU.T׿A*N!BU.dTABS,N!IBU-PNAN!|BU2oGA]XN!bBU-d A{+C-mN";BUaAȉAC3N";BUaȔZAsC21N"ûBUaȔpADC\*'N"BUc1AeCCN"BUc3AȿC)N"BUcCMAN"BUcBFAzPN"UBUc@$AGw!C#N"FBUanbBrDOaN"GBUaȆACN"HBUa}AgC8V&N#:BU.TɿN#BU-R^B\N#IBU-OAA.N&:BU,cPQAbDmN&:BU+PAbDYWN&AEN&=BU0OyA=D`N&=BU-ĵAD{ˆN&=BU0LAKWDX N&> BU,Sc/A! DN&>"BU3w6bA(8EN&#BU+dTBH3D0RNN&:BU+ EA\/DMN&lBU0>ӧ@0EN&3BU+mOB=DPN& BU,=@wN&BU-MAhsN&IBU,#PxA#DiN&JBU*0/A;C;6N&KBU*KLAsD.N&|BU/VH@ІEN&bBU.rmAAqN&BU=:AB[N5:BU.T?I_@6N5;BUR/,A*hlD%DN5AMD!N5=DBUXMlAEKN5=EBUY}2AURB)N5=BU2Z>GUCN5>_BU`x AN5>hBU`[Ag C!N5yBUUiNAnN5zBUQ@/[C4N5ĦBU\ǮBAC]N5BU_xAt/N5BUbFABԇN5œBU_Av@ǡN5ŔBU^Af|CKN5ŕBU`AS%CN5BUV^ A:)DgN5BU[eǚ$AD N5DBUN*.Ae%CCBN5EBUS4@TTD,~N5BU_MA0D%N5BUc5FA7LN53BU-MAEAN5BU?ŹA]FE'5N5BU]Wǽ~AQgD=dN5BU]ǷqAq@DGN5BU`=MAJC9N5BUam0AjD&1N5BU]AǧDB;N5BUWg@2D%N5BUZǍA&CT{N5 BU[?Ǔ@ϴC=oN5 BUAÄ EN5 BUZljT@_KhCqN5BUJ!M%N5BUAAuWOE3N5 BU^AnN5 BU_ AaRCN5IBU.jZAnZCyiN5|BU. RAW3B9NN5BUQ'HAC(lN5BU_zA,A0N5BU^װAbDDN5BUa$A&SDN5UBUb AKON5bBU.kUAAuGN5IBUc2$5A;iCN5KBUbpQAzjDR N5BUU_MA*}kB"zN6:BU>~3A(рE*N6:BU-wCBA?N6:BULDꃿ%DCN6 BU-į2A DX0N6>"BU2[ >CqN6>GBU6òA)(CN6>YBU@_ A`!fDN6>ZBU7xAEzN6>[BU>LDAIDcN6BUQ%)@CN6BUhBUc?!ATAϲN7RBU-QA0AfN7lBU3l8?bN7BU5CšA(N7BUcO&AN75BU5Ů+SN7IBUc2AOVBHN7*BU-(C@MAE\AN7+BU=J=BN7.BU,$2>N8:BU,*3tB4c0lB̞AA0l)VB̞AA0l)`B̞AA0l/EB̞AA0l10B̞AA0l1B̞AAB3cAqAMCF)+B5AACBzAiAjFLBՂARA]2F4BA&AjqDDBAgAm@EȯB+A~AfF DBzA)A\Eݕ7BAs@FB(AqoA1FH@qBlA@Ab!VFC$B`Az@eeFc$vB\A(iA`$xBA ANE{$BAA1D,BAA,Cw/>B\A(iA`vBAjAmvB/AA{ v,BAAWGxB*AwDx6B+AD {xzB*;AD dx{B*;AD dxB*mAuD#DxB+AD {xB+JA2D EHDxB+JA2D EHDxB*ADRCy=x B*A D x B*sAoD*D߆jx;B*WA D&Dx=B*A4D)gEæxAB*HANDAD'xBB*AQDQ$~E=xCB*AQDQ$~E=xGB*(AD)#DDxHB*A9D.ME,xIB*A9D.ME,xMB*hADRIDOwxQB*A D xRB*ADxSB*ADxB*ADxB*}AD-D2 xB*A DFxB*ADEx:B*AzDB(x;B*-AhD }D$ҠxCB*ADxDB*9A?DL\D9xLB*TA-D"=D8{xMB*mAuD#DxNB*A D xOB*;AD dx^B*lAڹDx_B* AaDD^x B*AzDB(x +B*RA D x ,B*A DFx -B*AD EEx .B*A*D ex BB+JA2D EHDx CB+1A QDPEx DB+(A@=D:pB(AiCFt{B)AAD9}B)A?D/]E[B)AAD9B)A$CB(=ASfCCuB)j|ADWEhB)j|ADWEhB)j|ADWEhB(AVCE5 B(];AQCERB(vAV0C4\E4{B)@&AD B)5AD LE'B(JACB(ACFf]B)dAC)EbB)dAC)EbB(JACB)dAC)EbB(*AVCiF%B'ACF9oB'AC1EB'EA1CEB'ACƤEu)oB'ACƤEu)oB(A1CFB(AtC+#B'ACƤEu)oB'AC+DB'ACEB(AtC+#B'oAC+E0}B'AC_EjRB'CAACymB'zVACYB';ACCt#D<B'CAACymB'LA:CGC7xB&,A]C=iFGB';ACCt#D<B(!pAwCE kOB(*yAmCEB'AC_EjRB'FArC EB'ACeFUB(!pAwCE kOB(-ACLJB&%AOIC8?YF ?B&Al2C@E#+B'#CA;CS,B'A:CMDi/B&AcsCB(ձACuB(EAqCyXB(zAQCA8E|B+!A[DYXB*{HAn4DHEB*wBAh{DLVEYB*vTAfbDQ[EMB*}}AyDUD7 CB+BYA D>5 FB+rSADDiB)AD%1jB)mAD< FxkB*GAHD#=GP]{B)*AHD 0sB)ʌADV F0B)ʌADV F0B+ZIAD0B+QAHDF35)B*gAD9T{,B*uACDCE5-B*GAD@M$E.B*`A6D;6B*A}DYOE'KB7B*:ADJ߅FNB*lAMDFCPB)AD%1B*ADZ B*gAD9T{B*iA1D@EB*gA&?DBMlE|B*-A}DDB)(A|D+KEtB)?AD4FfB)?AD4FfB)AD-\EdKB*GAD@M$E~B)?AD.CD`B+EApGCjEz,%)2B9ApjC+SE:%)3B9$Aq~CD͉ %)3B9 LAp>C0kE%)3B9mAp@Cc%) B8]AgSCi%) B8[AgCCπ0%) !B8aAgq/C7xD%) B8HAgZC%) B8W AgC>V%) B8SAgCʠ%) B8X^AgCD%) B8Q-Ag6C$Eh%) B8X^AgCD%) B8vWAgC!%) wB8XAg CD%) yB8X^AgCD%) ?B8]bAgCBlב%) @B8vWAgC!%) B8AfA}C^%) B8vWAgC!%) B8HAgZC%) B8HAgZC%) 'B8OAiIC%) ()B8HAgZC%) (3B8Z4Ag6Cy%) (5B8[AgDC Cn%) (oB8USAgCV%) (pB8XAgCCX%) (qB8`AgvMCD4m%) (B8nAg7?CrE%)B8QAfWfC> E%)B8~Af9CEM%)B8AfCEy%)B8AffC3D%)B8/Af,(CM,E{%)B8BAemCrD“%)B8AeC{B%)B8AeğC|J%)B8Af CxE%)B8Af CxE%),B8AfCDwє%)-B8AfCωE8%)-B8AfCDwє%)-B8AfCDwє%)QB82/Ag-tCYX%)7B82/Ag-tCYX%)B82/Ag-tCYX%)+B6A]ۊCEH Q%)+6B62A]C0 ES%)+B6A]C%E@M&%)+B6A]CE]G%)+B6GA^CMsCW<%)+B6TA^fCYCy=%)+B6A^4CB68A]C@x%);3B8AAi53C%);B85yAi/C%);B81Ai3CDV%);B8-JAi7C$B%);`B8-JAi7C$B%);aB8OAiIC%);cB8-JAi7C$B%); B8,Ai7C%);QB8,Ai7C%);YB8>Ai=CEd%);ZB8;Ai2CD%);B8OAiIC%);"B8AAi53C%);B8HAi?rC_D+%);B8OAiIC%);(B87eAi6CExT%);+B8;Ai2CD&dB'`|AC|&dQB&Aw*CAHF/&dRB&hACuĸF&dSB&`AC`fF&d[B&Ab9CST&d]B'ACo&deB&=AKCrGE!V&doB'P2A~CE&dpB'HAuYCE- &dqB'C.gwB.žiB>DP%.gxB.žľBD8.gyB.žœB%~DЬ.gzB.Dž$CD?.gB.#žC#OD].gB.,žһCE.gB.AžɒBiDQ.gB.žBRD}o.gB.žBwD].j]B.@žBCʐ.jhB.žfBZB!.jB.žB־.jB.žBAf.jB.KžvBE.jB.žBAB(9.kB.1žB D~+.kB.žByC.kB.xžBBbC.kB.}žBtHDF*.mB.žB.n9B.žB.nB.žB_BG.nB.žB`B".nB.{žB.oB.Qž?B:.oB.žBGgB@S.o B.$žʩBξB8.o B.žCfD(#.o B.ž BD^.o)B.žjB΋{Cl.o*B.8žBΆ-D +*.o+B.žCD2.p.B.fžiBT.q\B.žʅC.sB.ž,C'B.tB.vžB֓PD!.wB.ž˦C%D1.z}B.ž‘B0JD%.| B.žÛC C_S.| B.žKB Ak.|B.%žSCuC.}B.žžBzB&D.̕B.ožBDEr.̕B.LžCoDH.̕B.žUC_C;.̕B.žeBBuE.̕B.wžԭBE0+.̕B.ž$#.̕B.Bž1BgE^.̕B.žC 1D*.̕B.už C=DB.̕B.dž"BKDEN.̕B.Dž؛B^D .̕B.žC[vDq.̕B.žB1D}q.̕B.žPBgDW.̕B.XžԧBvDW؛.̕B.žCj EGN.̕B-Xž?'GN.̕B.žBﭽDԸy.̕B.žJBZAp.̕B.žB .̕B.ž~BcDsj&.̖B.žB.̖B.žؾCqE k.̖B.žByE .̖B.UžzBDw.̖B.žBD.̖B.wžB(Dm.̖9B.žWClD0.dvB.1žB!D.dwB.žcBNeE/,5.dxB.vž0C.Dr.dB.ž^B1zD.dB.sžߌAEL .fB.žBY.gFB.{žԓBܥC .gzB.žʑCnB6_.gB.ž-B@a.i9B.žB>.jB.JžnCzD9.j"B.JžxBªD(3V.j[B.žBݰDz.j\B.žϙBhDX.j]B.<žB7SEM.j^B.žBzRD(.jhB.6žBחiD.jtB.|žnC1B-.jyB.JžȤB$Di.jzB.ž(BD@^.j{B.ž+BD>.j}B.ž[B.BӰ.j~B.žBrC'.jB.žkB%C4.jB.nž2BBVt.jB.]žBȚD+F.jB.yžrBBÔ.jB.žbBDW8.jB.žՔBEs .jB.žOC ^iDw.jB.žBH@C-.| B.žC!D$j.| B.žB D.|B.žÂCD|.|B.žSBD.}B.ž֋BD5.}B.qžC lC(.~B.ž̀B D%.~B.&ž̵BD٢.~B.žB߷D.B.žBDvCY3.#B.KžBհD!O.$B.lžxBߍC_z.cB.ž=C Cx.eB.#žɌBbMAkm.iB.VžzB]~DU.jB.ž_C C6.kB.~ž%BbD? .sB.ž4CCA.tB.žC={Cg.B.ž!BnC.B.TžцBuC?.B.PžџBq D .B.žQB E4.B.žB_Ec.B.XžB)D#.B.žŬDBHEB.B._žC0B.B.žGCaC .B.žCTD5.B.žƎCD.B.žbC%D'E./B.lžB׍.0B.ž.BgC ".5B.žjBD쉜.6B.žtBvBDdH.7B.hžB4DY. B.ž~BNB. B.žB!B.B.WžC#3E.B.ž`BMCY.B.;žBɰD>6."B.ržBݟ#Cч.B.cžbBC;[.#B.žǼC_}D J.$B.žBbTC.%B.žB#YDk.CB.žB^C5v.KB.ž:CXB@.WB.žÔC;AA.XB.5žtDxIBk.]B.žCRD.^B.žƚBkE._B.ržuC*E .hB.%žSCuC.B.ž}B.B.žB䱪.B.žˁBZD#.B.pž1BqD.B.}žB!0C9.B.žcB BWH.B.mžМBD3.B.žύB:E).B.žB?D0,.B.CžMB@Cl .B.`ž>BCo8.B.ež,BѣBG.B. žhBD}A.B.žfBM+D1.B.FžCDҟ3.B.MžÄB~Dm.B.ž_BsD.B.žC;C.B.žpBDM|.B.ž˅B/9C.B.Fž˛BխvD[.B.žBݱDH.B.žBD8.B.Kž̶B맚DY(.B.žCGE*'.B.užЂB؟E>7.B.žCE_Ea.B.žBˣC{.B.pžȮBDDG/.B.žɢBdC"_.B.ž BD1.B.Hž/BDy.B.*žyBDE x.B.žBADW!.B.žC,D.B.!žʥCY&EH.B.žyCC. B.kž\C|qE. B.}ŸB}E\(. B.FžB*EaÑ.dB.žB1.eB.ŸB>CB.B.žzC E6|.B.wžބBCk.B.žCɫD.B.5žyB/IC&.B.žӱBE7.B.žmBsE.B.žxB@E!.B.ž E%h.B.wž4CthE%.B.ž1B>DA%!.B.žB.B.ž BD.B.žԓBB.B.žC#zfE#.B.žC=jE.mP.B.žBCE g.B.žB̼bAѰ.B.3žպC PEE(.B.žѩBgE6'#.B.nžwBlD%.B.ždCD;E7.B.mžBǻDӅn.B.žBϮwDQ.B.zžC!RCr.B.žBC.!B.žB;D."B.sžBDaO-.#B.'žòB[LD.+B.žtCVDu.,B.vžBqDY.-B.žBND_.B.nžB1B.B.'žDBDV.B.žmBE U.B.*žնB,=KE>.B.žBnE:.B.žBB!?DP#.B.žljD.B.žC7D.B.žŖBC^.B.žƑB~^MCm.B.žBDZ .B.?žBr^D .B.;ž-BIC̜.B.UžԝB۾Cd.B.žHBP?.B.žˉBܬ.B.žΘB޲D8.[B.žCCd.GB.ž=B،BW.HB.žB_D9.IB.žBIC|hb.oB. ž*B,D-D.pB.`žBECIi.B.\ž.CDbm.B.žB'.a'B.rŸB)ICO.a)B.mŸXBC.a-B.|ŸCDPE<.a.B.oŸB֐Ew@.a/B.pŸ4B70E.a1B.rŸ6BEA.a3B.xjŸEBDX.a7B.xžB#F .a8B.xŸBD{@.a9B.Ÿ )CEW.eWB.]Ÿ%BD T.e]B.aŸ$DB͡CE.fB._Ÿ%&BҴ CS.fB.aŸ$DB͡CE.fB.]&Ÿ&sB**C.fB.fŸ!BIE?l.fB.]KŸ&RBECˉ.fB.b$Ÿ#B Ei$.hB.ŸNCEp.hB.wŸBUD.hB.sŸBD:.hB.Ÿ CEh[.hB.,ŸC>QE;9?.i9B.2ž!BC呫.lB.;ŸBEV.lB.|ŸB 7.}B.~_ŸFCq6E.~B.sŸBdELq".B.zŸB D޽.B.aŸ$DB͡CE.B.zŸ BTlEB..B./ŸBB;.B.>ŸBXE[.B.ŸGBhC;K.B.ŸAC{CAX. B.|9ŸB-D v.eB.ŸB&DhG.B.žB@֊.B.džkBS3@t.B.žBEC,(.B.Ÿ;BqD.B._ŸC+E .B.ŸB-Cs.$`B/žC0B .$aB/žC$vNDT.$aB/žC$mEs.$aB.žC$0`ElK .$aB/žʽBEK.$aMB.žÄC Dy.$c^B/ž:CA&=.$c_B/žvC CmJ.$cB/žÆB].Dv%.$e=B. ž‚C|Dx[.$e>B.žCPDS4.$e?B.ž’CZmD.$e@B.ތžCED .$fB/žC)fVEB.$fB/žC-+D .$hB/ž CLnD F .$hB/ežC2LD-b.$hB/'žC3;dDd.$hB/SžĿC8*SD,.$hB/ežƋCIE..$hB/ž\CB))EC@.$hB/BžC']9C{|.$hB/žVC. Du.$i B/0žvCYD;.$lžĚC kD\f.$B.ž—CuE*D.$B.žRB2E.$B.&žC i7.$-B.݅žCBf".$/B.Kž‹CYCbi.$B/tž3C$=DG.$B/HžÆC4qCZ0.$EB/žɮC(C;.$GB/ ;žVC#TB[.$YB.EžUCD29.B-HŸXByD.VuB-Ÿ}BC.VyB-\ŸxB F<.VzB-JŸBxYE.V{B-sŸ8BbE.VB-MŸByEc.VB-tŸB\E.VB-lŸ*BֽEN.VB-p|Ÿ$BݯE&.VB-f-ŸTB箖Dri.a)B.aŸ#B)DX.bFB.%ŸUPC'0BU3.bGB.&ŸUC ;.cB-Ÿ[B⡏E,.cB-ޔŸ\YBړE+^.cB-˩Ÿe2BּE-H].cB-TŸeBE,.eUB.VŸ*IB-Do.eWB.TŸ*BrDJR.e[B.XŸ)9BaEn.e\B.NPŸ.IBXE3.e]B.OŸ-BSBEX{.eB.JŸ0tBKRD*.eB.MZŸ/BJE .eB.BŸ3BEmX.eB.DŸ3cBD.fB.SdŸ+B$Dx.fB.GPŸ2qB9 D.gB.,ŸDBE^.g5B.7Ÿ9$B.D2.g7B.5Ÿ;BD..g;B./Ÿ?@B٣EJi.gS.sB-2eŸjB.B-iaŸ B@*CW?.B-pŸBDj.B.ŸKABER.B-^ŸXCUC.B.[ŸToB|Ea.B.ŸR+Ÿ7UB[E5G.7B-Ÿv!B Eh.8B-nŸ{YB1Dς.9B-Ÿ}B`E_ ."B-Ÿt#B60BƋ.B.žB@V.\B,Ÿ4BsDO.,`B.žC1 DP&.,`B/ *žC ,EG.,`B.ž;CEO.,`B.žC,EE  .,aB/žCH,B.,aB/ ž C,LC%D.,aMB/žwC&.,eB.mžC-KB.Kž‹CYCbi.,eB.YžB˥Df.,fB.žC!r0A´.,fB.ɧž̓C#D.,fB.ɕž϶C-Dq.,fKB.hžCGD).,fLB.žC(HC.,fUB.žC{DRȶ.,fVB.žӒCtDs;.,fWB.žC{DRȶ.,fsB.ožC\A.,gHB.žC|.,h{B/žC&DB|.,hB/ž˙C$ eC Kl.,hB.žхC_Bv.,kVB.žBBv-.,lC4YDf.,7B.žȸB E$].,8B.rž]CE"l.,9B.ŖžBDEi%.,B/<žC3p>CJ.,EB/ žCwD){.,FB/ž CyEX.,GB/ WžZC$EDLkR.vVB,6ŸbBE.vVB,5ŸhB~ErE.vVB,ŸyB5E.vVB-BŸJB/CQ{.vV[B,ŸxwBDlv.vVB-?ŸBBֹ.vVB- Ÿ%BD.vVB- ŸBC.v_B,^ŸC>-jE.v_B,Ÿ.YBPVF0.v_B-tŸVHBFJ.vsB-84ŸB-.vyB,ŸWB] E .vyB, Ÿd7B4E.vzB,žsB>vE,].vzB,ŸBXwE+.vzB,UŸ$CZUF2.vgB,7ŸBXC.vB,Ÿr>B.EyH.vB-Ÿ^B=Cy.vB- ŸBjEW@.vB,#žDZB.vAB-Ÿs;BHjF3Ug.vBB- GŸiBjPEr.vCB- MŸB߯E.VB-WŸDgEw.VB->ŸB[F)3.VB-WŸBEU.VgB-FŸQBіE°.VB-oJŸB~ZCLO.VB-DŸB.Bl.VB-ŸsBsD 0 .VB-2eŸjB.WB-W?ŸNBmE +._B-84ŸB-.B-iŸBuE.B-M>Ÿ@RE .B-]ŸBRBl.B-JTŸBEY.B-\ŸBE71.B- #Ÿ)BaC:i.mB-c#ŸBjD%.葩B-iVŸ#BD9D'..L0B+]C(N.L_aB,žoB .F.N.L_bB,xžB> Edjl.L_cB,fžBCE.L_kB,0lž7C1cEbm.L_lB, ž,C65 E.L_uB+9žC5YC4A.L_zB,žs6BzCGw.L_B,žntBTD[.L_B,žyBOE.L_B,>žSBV.L_B,žW#BĚEJ .L_B,LžOCE.L_B,ž^B\jEPq.L_B,Tž$C1&E|.L_B,ž0BNE.L_B,7ž)BUPE2.L_B,r/ž=oCINCך.L_B,u+žA9C=EJ.L_B,dž=C=3Ev.L_B,@žQC ZE}.L_B+hQC6ED`.L_B+eqCA/BJt.LgFG.LtB+PC$F/.LxB,mžoBE.LxB,žnC EI>.LxB, ž BE7.LyB+Pž;C2w;C .LzB,ßB.L|B,ž_B]E9.LB,Rž=C?~nEh.LB,$cž-C1F6j?.LB,F~ž=CC(eEW.LdB+;C5E.L[B+j7C*E.L\B+2BC)EH'.L]B+j7C*E.LyB+žyC1De.LzB+ZžC3ED.L{B,ž)C3WF7.LB,žB6C:K Da.LB,žJCE4.LB+žC5S.LB,o,ž?CC5Eb.LLB,mž{NBDኟ.LB.5žUB.'tBKDb.@.'t BKDD62~.'t BKDyE0R!.'t BK(D}EU .'teBKDE .'t6BK8(DhR.'t.Ch|B/]žjC.+P@ڲ).Cl.D\B.žZC .D\aB. žlBC.D\aB.5žBB .D\b B.žCCO9.D\d2B.žBmD4=.D\e>B.gžkCo6Bw.D\e@B.ž(C bDx.D\eB.žQBʁ9B.D\gsB.žĎC .D\qB.ožB?.D\B.žČBWwC9@.D\B.ž^B0B+.D\1B.žC$.D\B.žBpC.D\B.EžZCBFFP.D\CB.ž}BSB-).D\EB.žCBbB.D\IB.žBـDrP.D\KB.Gž[BDlW.D\TB.žBƭD .D\iB.žĊC lBl4.D\}B.ž!C@q.D\B.žCC.D\B.:žB˯D .D\B.žB,:D-э.D\B.ž BC.D\B.ž.C*.D\eB./žyBh;DXf.D\fB. ž1BϭBD.D\gB.OžBy9D.D\B.*ž+BD`z.D\B.žÇC@hp.D\B."žB0D8a.D\B./žVBD.D\B.'žBAh.D\B.Sž/BfPB\.D\B.Wž(B4BR.D\#B.}ž¢B͏AI.D\B.ZžúCGBA.D\B.6ž4BWAz7.D\4B.ž#BQBH:.D\9B.Sž§BπTC .D\:B.žDBD1G.D\;B.žBYDe:!.D\B.3žC2DT.D\[B.žC$6BhšC V6 BhšC V6 BhšC V63B6BJBO 6BB8B6D~B6BOxB6VB7nB j6ބ)B7nB j6޸B7nB j6,B4WB 762B7nB j66%B6Ba[#66'B6B}AsZ6;B.5ӌBY6D~B6B}AsZ6JYB6BU6JB2SAP6NKB0B6N]B1/Aƨ6NB5BRo6ˆB1iBBLZ6*B1hA N@s65B1iA/6/B$IٮB6NiB$'B])D6N{B)mۯBD@6N|B"8B}[DvI6NB#؀B'm6NB&ZًBFU]C B6NB&ڽB`yDsk6NB$ۣBuD$6NB$HpBDDt6NB(zۊB;A 6NB$'BkC.T16OB QAj6OB%FBqD26OB OA!B76OB fACͱ6OBC"D 26OWBvC#D(6OXBSC&Dj6OYBlCD6OZB:CD\6PB+BZ6R4B+NB 6S"B]C%C6TB(ۥBCB6TB$ dBB6TB$=BBo:6VB+Be^B6XqB w݀B!6XB6׊B6`B%FBlI B6v!B*I۽B#T6v"B&#BzDO6v#B yܥA6xB*׊BD 6B5d,BoCF76B' B^\D7$6B&JB0D.~16BC&;d6B%;BYDJ6/B'B_$D!C:62B7!B"6; B/oӫB6;B1iA336JOB.5ӌBY6N?B2كBRDi6N@B-\!B]C96NAB1Z٨B9D6NBB0[B6D-6NIB4A}D+6NJB35n] Dr~6NKB5+B|D#6N]B1ЍAND#E6NgB$^B6NhB$_BB'I6NiB$B B6NB,CD6NB,b-BDuu6NB'JB)AD7 A6NB'ةBDr 6NB'[BKD6NB0Bc6OB'B_DV6OMB OA!B76OBC-&f6PB/_B-WE?6PB-3ظB|D$6PB/nC Da6PB.MBLXD-6R'B0JB^/&CO6R)B/BBjE6R1B0Bn`D56R2B0ЗBtD6R3B/B~Dp6R4B/A wRD6SB6WךBBV6SB6Bw6TB0C^oE6TB.ԡBٹRD26TB* ֻAD՗6TB$4[B/*BU6T;B0lBxm6TB4BB=6VB2QBs4Dď6X5B(B19Cs߽6XJB8Aه+6XB5BRo6XB0LBLC 6XB/͖B6XB5D6XB5:Ե VD 6XB5@-D16YuB/B?Dq6YB,׈AADUnW6YBC+6BfZ6_BC+6_BC+6BfZ6`#B3ֱBB*1O6v"B)0ؠBC+6xB1AB6ysB/B,DxD{6OBhBf36OBcBiWDC6OBB"DP6OBoB5߳D6OBB"DP6PBBrD6PB7B.{D`6PBFBD06PB+B6P BIB;gDC@]6PGBB&f@B+6QBB%A26QBrBAC;j6S"B5B=D6SB3B֛CM6SBBw(D&6TMB/BD6U5B*BJ6VBhBf36VBVBbC|6VB'7B9D{k6VBhBf36VBhBf36VBB6zDT:6YBC0`6xB5B=D6BZ>BcDOs6B=fBSYE6VB],CD6HB5=16HSB5=16HB5=16HB5=16 tBš2ZBVB6rw6A @aCU6rz0SA ByRDK6rz0TA& {@MD6rz0UAx 7C'Eq6rz0{Aа @ҁE6rz0|AЊ- As=E- 6rz0}AН  @kE0S6rz0A% Ef6rz1AOw (Eg6rz1A2 AEr6rz1/A @E W6rz10Am 8?T+Ex6rz11A B E36rz1YA$ 8@uEX:6rz1kA4 j@N{6rz1lA] s^Ed6rz1AϠ %@6rz1A  s6rz3BAV @E D6rz3CAЍa @7 Eq>6rz3uA; BnE6rz3AБ t&F)%6rz3A2 @WEN6rz3AЃj RAEkF6rz4 A BT[E.U6rz5rA JBMREW6rz5AR SBV}EZf6rz5AU O@ C&6rz6A  @Q:EI6rz6Ay $Bk(E~->6rz6A4 @7hD~6rz8UAϑ @3!EE6rz8VAe }AE@6rz8WAϩW C@sqEfPl6rz8Aϧ :A o6rzVAϽ =x E>e"6rzVAϳs 1@0?ET6rzVA g@Et^6rzWAP A>{Da|6rzX A` AE~uv6rzXA 8 bE~6rzXA AET 6rzfAfN @ܰE_S6rzfAπ @QEO6r{8A\ >z6r{Au ~?dZ6r]A mAI6r^A- A D6rcAvM E|D?6rdAYb O^BPa6m8B>QA'vD 4~6mB>GAJD *{6mB>_@OC F6mB>QA'vD 4~6m B>@Ͼw6m B>]AACr;6m B>QA'vD 4~6m& B>QA'vD 4~6m&B>Tz@C0c66mB> @CuO6I_BWšEqC?D`6I`BM#šBYBE`2Q6IaBNšFCYC6I2BEšBCD6IBY4šEB:RD'6IBTšFC|6IbBGš8C.EŚ6I BZšEBڕC 6IB8šš?KBs6IBš3B&f6IUB\ šDbB4_Aa6IVBDšABEN 6IWBMšCLB:3EJ&6I)BVKšEBD#6IB@dš@ BBT6I}BUšEBsDF6K#eB|šAROC[6K#gBšBKC'6KBzUš@yDnc6KBš"@*-6KmBš&ӅDLI6KBšTAѭ@CV6KBš]AA6KB]šv@z6KB{šNAt6DM6KBšB6KBPšBѺB/@6KBš(AzEk6KBš/BYBC"f:6KBHšUˆNE0݃6KB-šdE+6KB"šWAXA֢16KqBš%=?}DIN6KBš~BXxC 6KBšAZnCRY66KBš+BLCג6KB^š:¦Dw6KBšY@:|Ci6KBšA\D(>6KB_š:E@=6KoBO6š'ACl6KBšT@6LKBš+A*EL6LLB š$/4DP16LMBš.B]Ef6L`BšcmDa6LBx(š!ChECA6LBpzš$kASD|6LBJš-B6LBęšDB6LwBu[šBQE{,b6LyBt:škB玸C|#6LB?šmBC _6LB5šWBQFM6LBIšd#B,Dp6LB~šeBCF6LBš|TBꗷD6LBšXBk(E6LBšB2D+͜6LB1šBbC6LBšBkD*6LBšdBF6LBSš4BF6LBhšCED=6L+BušB0C]G6L-BušBDX6L@Bv(šC%:Eyx6LABvQšXB2XpDT6LB{dšB7Ea6LBu@šB*8DR6LBu:šCB;EH/6LBšBppC36LBš-PAD746LBšwBځID6LBšBjDL"6LBTš0Bs496LBšnIBEHh6LBšoB~YvD)6L:BpšCZPC6LDBSš;BfD! 6LuBšBE 6LwB|šB)E6L)BšlBn/+E6L*BšnC4EED6L+B#šnLBD(6LHBušfCD,z6LRB|JšBݾE 6LSBx,šBC6L]BšRB-F@6LBš @=D6LBšBDD4]6LBš@3D`6LBƏšgBD6LBašiB>D&6LBTšhBD6L.Bpš#A6LBrxš%CDc6LBohš@C;EN16L(Bšp9BBD6L)Bošo CCD&6LBš{E/f6LBTš;BsgC16LB šn}B9 Da-6LBšlB^]EN6LBšoBB(6L@Bš@6MMB!š3BEA6MB5š<BZE%6MB'Xš>C6MB;š1^B6M'Bš7KB|IC;6OBQšAdEAG6OBNHšA6p]D 6OBQš'AC76PGBSšCxE6PHBršC+-E6PIBn{šnCDlq6P]BušCZD6PBwš/C(E"6PBpxšB~s\EF6PBlš?? ENw6PBsšsC^E96PEBkšC uC>6PGBkšCb A~6P_BnšBLEVW6PBšϣB>C76PB;šTBFDJ"6PBšUBD6PBšϣB>C76PBnPšB}q6PBuašC~C6RBbKš+bBEO~p6RBfš'yAdhD6RBa]š-B/fEq=6RB~4š{ASE56RBpš?)>D36RBv`š?6RBmfš@CZEEg6RB_šx@E$6RBeš@yDt6RBhšf@XD࿊6RBSš0A%qDR6RBNš.B1,DQc6RBPš/BWD/6RBMš-mBRDa76RBLnš-BDAW6RBTš8B`NDg6RBRš5E;@6RBSš;B{D)6RDBU$ššBTšB͍šIBjcEC6U?BšRCNQERC8ETf$6UBš4BaGF&M6UB7š0;C*E6UBš@sCAEZ6UB#šKB!EZi6UBšHEBCE>6UBšIC DQ6UB kš(BHD6UBš,Bt%D䲼6U#BIš0hBPEU;6UB3š3BFEUO6U/BšP(BFwE-6U0BgšMC`EBo6U1BšSBwE6UbBAmš@BxCU6UBš1ABE}M6UBێš:B-+E6UBšKBCEp6UB7`šGfBBD6UB7šHBNGD6UB8cšIBD6UB š&rBpB 6UB š(YBDD6UBušo6UB+š\zBtREQ2a6UB7š@-B9KE-6UB4š=C4dE,6UB/š@sAE 56UBE_šIBE2<6U B5šLC >D؛6U!B:bšOABgCX6UB6yš6BλB6UB8š.CEO6UB>šBBE6UxBRš:B$Bҁ6UgBš@}BUFD/6UhBkš?,C D6UB-š'B6UBš/BC6UBš-LBpDд6UB7š1/BhɊEIM6UBš7BOYQE6UBš>:BVOE0QC6UB#šA;B#Dh6UBlšBjl6UGBšB{D46UHBšBmD6UIBEš XB1E9|!6W#gBLš$BCZO6WB<š0B0ZD76WBšB^dMDbi6WBPšB-DKl6WBš\B+}CQ6WBšBDd>6WBmšBAC6WBQšBB _w6WBšB2j8DLJ6WGB}šAB&6WB;šiBID-(v6WBšBDj6WBš ApZDi26WBSšCBC.6WBSšCBC.6WBšBCcD06WBVš:BD6WBšBbDP\6WBxš\Bm0D6WB^šRBZgE 6WBšBx6W!Bš~BRjD}6WGBš$B,!666A& p :E666A @@%HE 667A kD}n667Aο EO667A @lqvE"667Az@ 4B4fE 667Ay @@??B3$%668WA\ m@(DFp668AyZ 7@V668AΡ %B-EA=66:qA& CEd66:rA# X@DH^66:sA/ A0E;G66;MA΢ @|EJ66<AӨ ;?ULjD/66<AΛ A)Eh 66<Ax !tZE(66<A @ńTDS66<AI ?&E 466<A @1CD66AwM  %?%D%j66>A΁* BD66>;A9# HBތEQ66>=A51 A %>C66]A 0@F? Ds66_A  \?KD뀩66_A7 A 66_A @lI66_A7 A 66`AΌy ?1DA}66`A· B@Dv.66`A· \Bv5Dߒ66`AίI @A$Dr66fA .@2"E$697*AY >[CU697AZ A_Cl697AZ ?:C6Q698Am y< C*s698AY Y69;aAx 4AݥE69=}A^ ?|69=A] @CY69bAp^ z%5E69bAzV K?R\?69dAu wش:?n69e%A]# V?W5CR6B ׭Ÿ7BA6B ŸhB#6B (ŸhTB=q6B ŸXBu6VBB ZŸgB%^DW 6eB )Ÿ$C / D6kB ӛŸD,BۣFwWA6lB Ÿ%WC|673fB )ŸB*_C&673kcB žC67QB xŸrMDg >Gmf67QN}B "ŸKBFl67QOCB chŸD7F67QOEB Ÿ"BpFp67QOMB ~ žxBp!67QONB 7ŸBo1Fx67QOOB HŸB+FQ67QOcB ~žC"uCD?c67QP)B žB9;EQ67QVMB ŸbzC͡F;E67QfB Ÿ B&DK67Qg B oŸKT¼F67QgB ŸFB -F|67QgB ŸTsCFF67Qg!B ŸeCFxG67Qg"B Ÿf BAD67Qg+B ŸdJC.FS67QgB žC 9DĞ67QgB (^ŸB|PD%$67QgB _NžPBF;67QgB ȸžC67QgB vfžYBwF67QgB žDCF67QgB Tž8BFZ67QgB žFCCF:67QgB žfCBEx67QgB žCE67QgB XžCio:F67QgB ;Ÿ 3BbFg67QgB ŸCrɺFO67QgB 4ZŸDCW_Fw67QgB ~ž2BHE3%67QhB ZžBc(F@67QhB ož.Ce$F67Qh B ŸCeFU67QhB žC =E67QhB žA-Eb67QhB žeCoFF+67QhB @ž3C67Qh/B ̮žC(CM67Qh0B žCE 167Qh1B gžASFB!67Qh9B ʖžoeFJ67Qh:B žY9F?G67Qh;B ŸnC4)F67QhCB ž C DW167QhDB xž\ˆ&E~67QhEB "ŸiCLFU67QiB aŸ;C@F/C67QiB Ÿ C2Fo67QiB Ÿ5CoFSl67QiB ažBC:AT67QiB ŸfCֽF9;67QiB Ÿ(gC'XFDko67QkcB VžBaC67QkB Ÿ(BuFm67QkB Ÿ-CE67QkB ŸP5CF)67QlB ŸGB@WFN967QlB #ŸhFBCy67QlB ˂Ÿ CtC67QB ˂Ÿ CtC67Q B [Ÿc1pfF67Q B bmŸ}FR67QB qfŸqC,Cc67QB VgŸaJ2F R`67QB QŸ@F W67QB ^Ÿ9FV`|67QB 8ŸbD#F-67QB 3aŸgCi8E6D B oŸC66D |;B 5ŸudC[16D  B qŸC,6D B pŸ,@F 6YBšCED 6\Bšq9C >F 6^B>š&BNEYI6gBšM-B˨Eb6hBdšoB&F#26yBvšf$B*FU6'B?špB[DJ,6'B=šqBcC66'B؜š`@BE6'"BBšU.BLYtE6(B˜ 6YB9›C0`C6hBCšq\Bn6'B=špBD6'B>šnB6'B؜š`@BE6B›MšH_6 yBVšXB{6e^BšBQEGl6f( B@š&A%:CT6f+-BšAo&D6f+5BšBLjWD{L6f.B8šBEN3Eq6f.BbšBc*FGU6f.B͢šBX}F6f.BšBE6f.BšBYE^06f.B>šB#EBC6f.B7šBLHE6f.B_šBZEu6f.BšBXL>EL6f.BšBgEr.6f.BšBO!E,6f.BšBvAEYF6f.BšBxLDj6f2BDSš B56f3BšBeE>6f3Bš6B1F j6f3#BܩšEBP^C} 6f3$BšB 6f3.B|šBU%6f3/B~šBT7ZE f6f3UBš]BIE_6f3VBšښB doEƹ6f3WBš]BIE_6f4BCHšAE=%6f4B?`š0> vE76f4BQšB~:Eau6f4BšBE ܔ6f4BšBDh+6f4B šBEf\6f4Bš9BEa|6fJCBĿš.B;~HF86fJDBšB%6jB™ApCg46j+UBMBšAD16j+gB/šBz E/6j+qB,šZBrEz6j+rB'šB)eE6j+sB;cšBC,FA6j+{B(OšSC!F26j+|BBš}\C2/E[6j+}B%bšB0SEX76j+B^šBe-Cc6j,Bš9A5EAR$6j,Bš*B*EZ6j,B šNjB-`F?l6j,%B_škAC6j,EB{š-BEOJ6j,MB4šjAOHFF!6j,NBš@jE6j,OByšBnF x6j,WBš.AyE"@6j,XB™Eʧ6j,YBš ?AqF6j,aB™ɏEƝ6j,bB™z„dF256j,cB™eAWF,Nk6j>Bš43@6j>B_™A_Dn6j>BB™_A9X6j?Bš/A0Dt6j?B šR~Ay6jJBX4šB=Cm6hB>špBD E(6'B>NšpBD(6'B?pšp B C86yBAšPGBZ6Bȵ–oAo6Bȵ–oAo6Bȵ–oAo6Bȵ–oAo6Bȵ–oAo6UBȵ–oAo6XBȵ–oAo6=B"Q ,C%`A^6=RB" 2CϷD6FB% TCD'6FQ B% A TCD!6FQB% UUC&E8L-6FQB% TC]tEv>6FQB% TC]tEv>6FVB$Yc ECEs/ 6FVB$d) HC!D%e6GB(vŸnCDe6GN1B(( 6|Cx6GN2B'U WCCE6GN3B'k ZC)C6GNEB(e9ŸCڢF6GNFB(\ BCEfw6GNGB(v KCEt6GNYB(wŸkC"FRe6GNZB(WB ChF186GN[B(Zk CE6GNcB(cŸCސFr6GNmB(ŸC'FMp6GNnB(wŸkC"FRe6GNoB(nŸI}6[O6B(ŸaC\Eq6[O7B(ŸCjTDm6[O?B' N&CF.eR6[O@B'E ZCCVE:6[OAB'  ZC E6 B":ŸCq6B" ŸČE/6ӞgB"< CeE6ӞhB"whŸC2EBh6ӞiB"weŸQCD6ӞB"oŸCE 6ӞB"lmŸ=CfE^6ӤB"ŸCuEʨ6ӤB"lŸ\CC*6ӤB"vŸCE׀6ӨB"s C=D6ӲB"yŸ CWwE 6ӲB"nŸCE6ӲB"kŸ߼CEW@6ӲB"cŸCIE:tu6ӲB"jŸCp EBw6?B"ŸCE"6AB!PŸvC`6hB"MlŸXCrE;6|B"MŸkCJEF6}B"QŸIJCiԮEV6B"vŸCD]6B"FŸbCE6B"&NŸC%eE!6B"7ŸCmF6B" ŸC|nDRU6B"Ÿ&C E 6B""Ÿ+C"E 6B"#ŸrŸNCs7DP6B"%ŸC+D|6B]C6B]C6B]C;6YeZB$šC3w6\BšC(EJ]6\Bš{cBE6\Bzš~kBb6\cBšC"{E8V6\cBLšCE6\cBš:CGEd6\dBO šTB!EWī6\eYB*šRB"EM6\eZBš.C[#6\*BDšPB-D506^BšBqE-6^BšBy=ET6^BšBxB6^BšBN2 F 6^Bš'Bc(F[6gB$šKBEc5m6gBqšN_BjE P6gpB%šJB2D[6gBšK$B%D(6gB{šL/B̏EMw6gBšMrC-E6gcB,šLHBXeE\6gcBšJkBEA$6gcBHšITBD<,6gcB šHBC&6gcB)cšKvBlhE*׶6gcBšIBVD 6gcB#šKBEr0 6hBešhBQSF716hBšqBC6hB/šbB6hBša/BE]6hB(š^;B?D.6hB+š]aBDen~6htBZšp)B}6huBBšm-BEewS6hvBBšm B.Ed^6hL:BšqtB˼Dt6hL;BjšpBʭEf6hLs6hpBX7špB,$E6hpBmOšpBֽD{`6hpBhšpBDu6hpBzšpoBE6hpBTvšoB{E)6hpBpbšpB-mE6hqBWšoBD6hqqBxšpB6D326hqtBšq)BXE q6hquBšpBE6hqvBu=špBD6hsBšnoC7DEge6hsBXšnjC Dg6hsBšpBdEO6hsBhšoBD%6hsBLšp.BoTD7N6hB=šqBOCjpt6yBqšbBDF@d6yBšmIB TDG6yBšstBE- 6yBušbC tD16yBzšdACЭE6yBšfIC $LEB6yBvšb}BE^6yB} šehCGWEGh6ymB=šOBD)#6ynB:šO+B6ypBD|šSBډEp;6yqB31šMBW E6yrBC 6ycB<šOBۅD6yd6Bgš\%C4DyT6yd=BcjšZCLD1tw6yd>BašZCC6yd@Ba!š[NCEAè6ydABYšWB?EE 6ydBBa!š[NCEAè6yeYBštBC6yeBšoDBE76yeB}šeCEOy,6yeBšlB/}EUW 6yeBšyBVD<6yeBšs*BJD\6yeB;štB벽EGi6'C*B›C=RB|6'C,B›pC,-D 6'kBY›C,̋6'l.B›C%PB$-6'l/B›C.[#6'lWB›gC=.A(6'lBp›C1C6'lB›C7nC$c6'7B›C0MD1hY6'B>šqzB=@ӳ6'PBGš]BDWw6'BšaB̷DC.6'Bš^gAE@b6'\B>šprBLDA'6'.Beš`BhB76'pB5iškEB:DL6'pBOHšpBU6'qBC šn@B3Eq46'qB=UšqBq'6'qBš`AE 6'qBbš[BdEy6'qBš`AOVD6'rwBš`)BID&96'rxBš_B\EJ6'ryBš_hEJe6'rzBPš`u@šp BD®6'\B>šoB6uC4_6' B>%šnrB6'shB>špB2Cz6'siB9šn@Ew6'BšnCrD6'BšoCEx6'qBpšaBD 06'(BškBE&6'(BUšlB XEu<6'(BšnB>D6'- BUšg9BD,6'- BšjB D6'-BWšgOBđE'6'-BPšgBLEF`6'-BSšjBDE}6'MBsšeBIC|6'MBšaBD\ 6'MBԦšbQBD&6'MB<šdB;ELC6'pBNšoB޶F6'r2BšmBчCM6'r4Bšn#B6'rBšnBжkDi6'sB|šnhCDm6'sBšl"B+|D36'!XB[š^BE,@6'nB*šbaBE:q 6'pB^š]RB%D26'pB*š\BB E{=)6'rnB#:š]BEg6'rpB š]hBVE?6'!.BRšaAj,ZD6'"BšR B9D֘6'"vBšTB{E(O6'"xBš\BhDY6'"qBš]Bo#ACz6'"sBš_B C|6'"tB'š]B"E 6'"uB{š^BE /X6'"vBݓš^B-Ed6'"Bš[B-6'"MB>š\+B[VC6'"MBߵš]B0;Dơ6'"cBšOB$Es6'"cB:šQABEO6'"cB2šTgBHD6(Y2B-+š/B\6(c\BJ™A~[E(6(cB™?BE6(cB™)AD6(c+gB šR~Ay6(c,Bš4:A.E^6(c,%Bšn=q6(c,0BšqAK6(c,CBš1B8 Cr6(c,NB ™A6(c,WB™3AxDKD6(c,YBXš z>7C6(c,bB™ADv|6(c>B šPAx6(c>Bš^A)!E6(c>BGš2AQDv6(c>B_™1AVeF'&H6(c>Bk™Գ98E:6(c>Bײ™B\6(c>BҼ™B75E[6(c>B ™A6(c?Bš6AƆCX6(d+ BnšBBBrAf6(n(Bš։B="E3m6(n( BXšBX"E6(n(!B]šzBE6(n+BšB.LvEρ6(n+BšB6(n+ BnšBBAV6(n+!BšA`E8`6(n+"Bš̝B;|EC6(n+#BšڬBE6(n++Bt#š@pBE6(n+,BlVšӿ@F+ 6(n+-BšjBDaE 6(n+5BsšB"iE 6(n+6Bhrš\A}E 6(n+7BYšAoF6(n+?BUš[B WES6(n+@BJšiB EI6(n+ABfšB1RE6(n+IB_šB)hEY6(n+SB\š B34F F6(n+UBnšCB1OA#D6(n+gB"šrDFNFv?6(n+iBhš)BF6(n+qBKhš.B+Ez[6(n+{B< šARE*i6(n+|B9šzA1fDv6(n+BZ šIBJFx6(n+BvšAE 6(n,'BšnDOD&6(n,1BdšxBD6(n,;B šSzAvDy6(n.B/šB#~6(n3$BGšB?*Dm+6(n3MBšB`, C[06(n3VBšBEP6(n3B0 š",C'6(/B6˜aRATDZE6( ?B8˜a`@NCJ6( SB6˜aC6( B4˜a@6(=B˜&6(?B˜ZAw0CU6([B&˜BE|6(\B˜ U[E6(]B<˜CSF%46(fBRv˜@ίEO]6(gBS˜Auj E6(yBX.™nAvF6(zB`™qB%C6({Bw{™})AvE6(KBW˜3A-E6(LBL˜>ArIE6(MBb˜uf6E6(UBQg˜+6(kBv{˜>+Ea6(lBr˜@E6(mBe˜@о*Eo.6(}Be˜I@QD6(B™v@$KEj6(Bf™tUA FKb6(BV™nVF6(B]™pAXuE6(+B™AqlE\6(,BR™ByEt6(9B?˜ةA!vEjK6(:B6˜AE36(;B™`A. EM6(B™M2F0e6(B.™Z@@?&EGP6(B™JA3Ek6(BW™?[1E6(B™BA_E6(B™!BE6(Buu˜fAMDv6(B|™ EE>6([BĮ™A\fEb86(\B™AF@06(zBx/™`l6(B&V˜$%bEC܀6(B1˜AYEb6(B™fBE6(B™YAJE6(BN™AgfHES+6(7B"T˜YE6( B#˜AD6( HBwe™R_>D7`6(B"˜RCv6(B˜AJ6DNP 6(IBA˜ْ@D^ 6(KB8˜@EQ6(TB%0˜NjPC96(UBE˜m@V6(}Bq˜/A/-6(Bn˜+HF06(Ba˜TEl6(B™#BTE6(B<™aEF6(B™ A2MEy6(B™E6(B™@>6(B ™.2BMF%6(BG™<@EX6(B݋™1@rEF6(B™-1 F(p6(/B?2™a@ E#6(0B$M™Vj?VE,Q6(CBF™eJATE6(DB*™YAFT6(EBG™d9A+D3jb6(Bl˜V@#D#o6(B0™[T.tE=h6(BM™AIEf6(B™A=D3%6(,bB™|AkFa6(>B:™A }E/6(=B@˜XAâ?F T6(>B˜B|>E@6(?B ˜@-CE 6(GB˜¼0EDn6(HB˜AEߐ6(IB!˜?B2EoX6([B"˜TA/6(B˜Epo6(BǘA`jE.$6(B˜j!rEt6(B˜s|F6(6(B˜kB;E6(B˜Aq~E8ذ6(_B˜(@/ E W6(sB;˜QADXL6(]B˜3C*A6(^B˜wXDk6(BR˜ſ6((B$˜†B_#E56(EB0˜̣A*SE(6(*Bb˜B8 E6(5B˜b@o JDb6(6Bn˜@lE.k6(7B&˜ɣE6( B˜t@K+C6(%Bֱ˜'7D.+6(&Bѓ˜A 7D46('B#˜mE6(9B޲˜8AHeSDD 6(B˜@E'6(B!˜mBA46(B>h˜bREi6(B7˜c:8Db6(pBk˜n4@?6(%B5˜aA@C6(/B6˜`μCҶ 6(BG˜c@ ?}6(BR˜dLb6( eBۅ˜-A6D Ɨ6( gB˜lAff6( B{˜fݿ3^E6( Bs#˜gE}6( BwK˜f"%MEG46( Bj˜dF!EH6( BM˜bE!6( B`y˜cBE~e6( SB9˜b%A%gUDMT6( TB8k˜a@C6( UB9u˜aAD6( B4˜aB6( BV˜da)'Eao6( B4˜aB6( Bj˜d;48CW$6( B˜gd?Cl6( By˜f$?E16( B/˜nB=E#6( B˜l\E \6( BO˜q A8YDt6( B˜pEAEPT6( B&˜x̝E6( BP˜pPA@E3]U6('B`˜ywcD~@6(9B˜mLsF 6(:B˜BdoEnm6(;B˜ AEh6(B4˜ajA˶6(EBQ˜dO@M6(FB5M˜a?СjB6(GBE˜b5AEEsx66(B6˜aEf;C3.6(B7˜a|,>CJx6(B64˜aU%C6(B5˜a!G@Q6(B4˜abQB6(B5^˜ae@+6(%B6˜a@1C-E6( BZ)˜dM6( SB6˜aQWoC6/B rsŸ]C9\DtW6/~B tŸ{C86/~B qŸvC;;CG6/B qŸvC;;CG6/B rŸMCnܸDX6/B rwŸCE2sD3M6/B rŸMCnܸDX6/B pŸC>D(6/B rŸMCnܸDX6/B"8OB䉀@I6/B$`B^6/B$`B^6/^B$`B^6/jB$`B^6/jB$`B^6/-B"OB BB6/.B"OB BB6/B"OBB~6/B"OB BB6/eBœ Bs6/rMBœ Bs6/rNBœ Bs6/rOBœ Bs6/B"OB BB6/aB"P,Bꭑ6/-B$'C J6/cuB$'C J6/jB"8OB䉀@I6/ŀB$'C J6/B$'C J6/B>C ĬD6/B$'C J6/bKBXC 6/2B$'C J6/ǴBXC 6YBl›gC"F7;6YC*B5›C@FAo6YkB)›6yCD6YkB{V› C9NC/6YkB›IC-ؾC6YkB.-›7hCC36YlB›>CFL6YlB›C}F6YlB_›C%1EF6YlB|S› C;d6Yl$Bd›UjCUDs6Yl%B1›7CnC@6Yl.B›CbD6Yl/B›C7*D6Yl0B›B(Dۤ6YlCB›YC(1Cy6YlLBZ›\BȼF+o6YlMBA›B\C.E 6YlNB[m›JC D6YlVBy›Cs(Ez(}6YlWB"›4C|D96YlXB:›=C2-YCWb6YlkBf›gC#Do 6YllB=W›:CqD _V6YltBg›egCAEպ6YluBeC›] C ^Ey6YlvBn ›lClE.6YlB›CD6YlBJ›C40D<6YlB›C0DF$6YBoH›tCΠE6YBi-›iCNK6F6YBq›w0CEӕ6Y6B>›C5D"DW(6Y7B›C Db+6iA @^E6i+A A!C6iRA. 1@a6i)A+ >%Ct`6i+A SDy&6iA A8DL6iA Eykv6iA A-C6J6iGA 0Et6iHA B GsEA6iIA  R@]E<}f6iRA- O?+B46iSA() ?6YDz6i+A f?9Dg6i-A p'lE6iA A,DF6iA A,DF6iAG O@BC6y!N6BCD6y!N9BCB.c6y!N:BCB.c6y!NBCB.u6y!NB[CŁ@l6(sB" /yCEN6(B" )gCտFzN6(B"T &CEg6(B"S 'CČ|Eg|6( B"U ,~C>A6(B"J ";COE6(B"0 C@E 6(B"Y CEc6(QB"( 0\C DA6(RB" 2CC6(B"~ 4CD 6(B" 3CtD%+6(B" 2Cl!D6(GB"9 4CDӲ6(HB"Q 4 CbEE6(IB" 2CEH6)B#a CC΄iDI6)B#F DCEut6)B#4 EC/E6)B$? <Cr6)B$I ACE-6)B$* ?CF 6)B$ ?CFFW6)B# ACg:F/6)B# ACg:F/6)#B# B~C!F6)$B#! B CE]!6)%B#h A*CTVEl6)-B#g A*COF?"6)/B# 9CEСC6)9B#/ > CzYDi6)_B#5 62B"P 1CAD@Pd62B" 0C@E<62B" /C E7Ũ62HB"y 4C|j6mYB›C2D3C-6 3B]C6 B]C6 2>B]CZ^A2t66B".¨CsAPNvBCANBC>ZA_NOBDkBNBC A NIBCdB@cN]BCN^aBCBESN^kBCA,NuEBC^pANv7BC?VNv]BC. A\&6NvBC.FA^WNvBD@SUNvBCAcNwBCqHNBD1NrBCroNUBDI BNv]BC(1@NwNBCB  AB[D7 VABD&߄C* 1A*sBRD5iD, 2ABD6'D~ ABD.D+ ABD+E ABD+E(A=B|D1G+(A=B|D1G+nA=B|D1G+ jA|/BEZC=_EM jAWBGxC:8E jABLoC9NDS jA1BI6C6Dո_ jABJC4ƆD j& A fBKC3) j&ABM)C@ j'&ABAJCvDg jAABHCC7 jABEC>EU jA^BG&C8DuAE jAB>&C>ݲ j9ABM)C@ jlA_BBeC>vDg jAB?C?K jەABM)C@ lZAqBZ%CUpCD!r lhAaBaCTR liAfB^CXD loAcLB`CEcC9 l{AxtBX6CSfDk l|Aq BZCL}D, lAd'B`oCPYD0 lA`BbCRLDZ l!qAfB^^C` l%A䀱BUCXM l%AxTBX'CQj l%A}/BWCPD~ l%AvBXCP l%A|BWCN+HCޖ l'AjbB]OCRBDM| l'AeB_COED lMAwBYKCLD4> lMAtBYCQ5D lN?AtBYLCOȴ lb!AoBR)C^ l3AxBWCQD !c lqAdB`CGcYD: lAg8B_fCG۱DC l¨AkB\CY o bAjB>(CZy@; onAPB>Cf6DA@C p4AVjBzCRΗCcW p5AU{BtCQD< pAUBu%CQC?d p5AwBuCA p AwBuCA pAwBuCA pAwBuCA p>AU4Bu>CND٣ p?AUBuCPdCP' p%A[B~C`7 p%AUbByCQY D& p&AJBvCRDF p+-AwBuCA p6CAWNBpwCH!Bk p6DAU4Bu>CND٣ p6EAVBsCL"D pXATBvCLtXD? p_AVfBsCO CDS pAZZB}C[ȜDYȂ pAYB|CZDN' pAWB{CVD# pAVBtCOmC8p? qAZBhCODwZL qAY BlCU8E qAZgBi&CQ%DDj qIAVBtpCOpD q8AVBpCM"C0 qTAY6BjTCHj qgAaBbCZ{D qhA\BeCQD, qiA`BcyCT3D q XAxTBX'CQj q AVBoCI1DD= q AXBlCKdE8 qAVTBs_CRRCOj qiAY^BkCUAD qjAW[BpLCPX)C& qkAYBkLCUD݌ qAbBa CO{D?ы q{Ai}B]CHDo: qAZaBgCU> q>AV!BsCRDT q?AVBr CFDG# qA]ZBeCQgCr< q6CAWBoCDC q6DAVyBq5CUAF q6EAV;BqCKE qMAlB\CZ qXAVkBq-CR1 B$ ? q_AWBpJCOC qAYBj CNW q2AlB\fC[xC q3Am7B\CMr+Dv q{AZBhCTbD'n qAWBmCL&D qAVBqC^CyC vA%BBg9C;׬D2 vA,Bd CVCx vA.3BcCZB vA*TBdCOC  vA.BcCV vA+FBdlCUݼCP vA-\BcCajIC?b v =A+BdC` v2A%BBg9C;׬D2 v2A%BBg9C;׬D2 v2A(IBeCDD"c vNA-\BcCajIC?b v`#A+BdC` vf'A.3BcCZB vA.3BcCZB vA!BhC8 wAPBC~CAX wA]BGC>z wAeBCCHh1 wAPBC~CAX w%APBC~CAX wAPBC~CAX w/APBC~CAX wAPBC~CAX xA#BACFR x1A#BACFR x3A#BACFR x9A#BACFR xUA#BACFR { A"$BCF)DmL { A$ZB C:$Z { AB,C6D_ {A5 BtC? { _A0DBڠC7D _ { `A+pB٧C5 {A䖩BCB>5 {ABC@ {0A$ZB C:$Z {8_A\BC7 {8aABʤC:YC` {;A5 BtC? {BA3oB۔C=ץC3J {BA1B۳C<# {BA+BC;D|`z {yABC@ {zA?BC;  {ABC?D25 {A1B۳C<# {hA B C7DJ {iAzBRC7Dv {ABC@ {%A+pB٧C5 {0A"BC8=CA {1A~BЯC85 {A'BC7C~ {A!BՊC6XR |A9BC/eF[ |!AACBrC6S3 |%'AbBC=3 |&+A޽B+C:rEX |,XA%BC7 |,YACBrC6S3 |,aAABkC2}E |<)AB*C< |AˋB/C1DY |ALgBC@.V |A;&BgC7/ |ěAB"C5 |AABuCADIJ |ۊAB)C6fCr^ AB=CcCQ ABTC_j ABTC_j NAPB>Ci<@ha VA䑣BF@CWRZEE A BECR ArBFCUDf AJBEC_9@C M aAB?UC;cDv bABBRCgD cAB>CJ-Cu A%BHbCi@CZ7 7AsBRCxhE^ 8A䖸BQCdD6 9AXBLC_E ( :A䘁BPC}uD; |AyBWnCWL ArBFCUDf "ABECk޸ 7AB@CgTDr ;AB>CM_C8 Hz A BECR AѰBN3CNE-aE AJBEC_9@C M ǓABACPDҰ ǔABB-CfDN ǕAkB>zCY"iCE AcBJCY` ABOCgC| !A8BFCh'^D` "ABJCOEC #AWBB CgDx գAtBFCU&D7= A)BCCb^E! A>BC~CZgE A BCCoDT ABAC`DK A䙙BLOCSD`= AxBLOCP"Da_ A䏺BFC(BHEFk AoBR)C^ A B{'CBE rABxC@, sABt{CD< A BgCUG D0 ASBjC7^Dw A BgCUG D0 AoBhKC=< }A Bg*C9(Dc p ABv6C@DK&  ABpC0 WABtC@emCmf XA1Bw;CG % AB{C8kDQ % AB{%C>nC} % AB|.CEs EAHBlCC5 EAB}C3ƸC˽ EAB~C8tD1 EAB{C3|C GjAQBKC= IgABpC0BC  IhAuBo4C5{D4+h IiAuBo?C/1 J;AGBxC7L bABlC8qC? cABBrC4|D}M cABrC?" uAuBo?C/1 ?AB|,C9{ ABpC0 MAB{ C3 PAsBv8C D[ ABgC8|CN ALBf.C>4CP{ ȡABzC:C^ /ABuCCD-* 0ABuCCD-* AB$C8Z?D$DB AABt5C@@ ABrC?" ABuCCD-* ANBt6C?ԋD+Ő AB|bC6D[ AB{$C>;C; ABzC= ABUC3DY ABC;2D' ?A7B}C?XCj EAB}gC?gD FAQB~C?DC GAB{%C>nC} AaB~C?.Bj AMBC=a APBC;яC A7BCD Al>B CED A`BCgC A^BCD AgBCGDiy AdB=CHD7 AlBCQWDD9D |AtB9CFf }As BCGSC?Ö A`BCbDsz AawBCchD< A[B~C_NCݚ AdB=CHD7 A`BCgC xA@BC3C" !{ArBCNzDv !|AkBCHHDT !}AmBVCSDf cAi#B[CIw  doAcBCeC  mAawBCchD< oAawBCchD< CAmEB|CrJ=DVZ EAkB;CSDG AqBrCJDCԱ ArB:C[FD- AaBCHQDk AWB}C&;D8 كA_BCenRCYL A]BCc AגBL#C=H aA嶅BGC9G bA嶅BGC9G A嶅BGC9G /A嶅BGC9G >PAגBL#C=H AגBL#C=H AA嶅BGC9G BA嶅BGC9G PA&BC=y A_BC? A BC> $#AB[C>~ $$ABC> &AxB\C8}q &ABC9JD) 0A BC> 1EAxB\C8}q 2ABC> 2ABC> 3-ABC>{D{u 3/ABC>!D*b 3KABC:$DUY 3LArBC=D+| 3MAZBğC>,VD CwABC>DV~ A&BC=y lA]BC9 mAB[C>~ oAB[C>~ aA_BC? 0ArBC=D+| ՙA&BC=y ՚AB[C>~ ׵AB[C>~ ׷ABC> 1AnBC6Dl  2A BC4uD 3AB&C-}Di wABC+TKDe ACBC,(CDg ABC9' AB C4cE h ABKC;6D0 AњB3C;D! &sA䰥B%C0n5Db 'ABC4|Ck 'AsBfC1mC 'AyBC2CѦ +rABC8 CϽ +sA]BwC6/ 1ABC*~ 3AABCG `CР 3ABC3:D<  3ABC&JDnDb :AoBvC:D!J =AnBCA.C辶 DABC@ DA BCBJD= ABRC1VB)H ABaC=Cx ABC*jdD AB+C5WiD& ALBC>DI  ABC#4Evx[ AB^C/.BR mA8BC,[C' nA4B!C*>C P zABC<~w {ArBC,%D% -ABC< kAGBC=DR lABBNDj AtB C52E A䥿BC ,ABC9 .8A BC<> .AABC6+gC .CAB0C){WD(  0A=BTC2Di 0A}BC5*D;Qs 0ArWBVCAtDF ;ArBCFYDuc ;AlB3CH]DJ F[A䧗BSC< QKABMC;C dAKBC(jE,N fA BC<> A䖞BC;,C;b ABlC3qh A䚸BC.C` AYB/C/D3 A=B7C4:C25 ABoC98C0 AB7C:IA<: A"BCC=VC CAB2C +A`B2C;?@< ±AmBCLSDN| ²AqBCEDJ ³AmBCLSDN| IAB/C??B( A䨪BC5 A(B5C<D= \A]B;C<>w ]A]B;C<>w  ABEC; ABC;# AƛB(C76>D AhBfC66 ABC8AH ABECJ oA]BC9 SAYBC;  A]B;C<>w !ABHC6Des ABC5X AhBfC66 ABC8AH A]B;C<>w &sABC; .-AhBfC66 ..AhBfC66 .8ABoC98C0 3uABEC9} 6ABoC98C0 6ABoC98C0 9ABC5X 9ABC5X y7AwB1C7eD5 AYBC; AhBfC66 ABC,_D)b AABpC= CABKC7Q DA}BCI^D5t EA;BC5бD>N aAYBC; ABC8AH .AB}C;(C! ABC3 +A䨋BC2qh =A]BC9 >A]BC9 ABC8AH 'A]B;C<>w IA䭐BC;C|~ wAYBC; xAYBC; yAYBC; ٌABC8AH ABJCADlSI AhB8C< AjBcCD AO BCI,DH! IATBwCXi ?!AdwBCEӶ ?gAEBvCPdCmZ ?iA)BuCwB%ޯ D{AUBCCMqD]g D|AUBCKPDVW D}ANTB;CHDt( AM BqCDq EAjBTCMC& A[BCG9CC?U MA)BuCyAGB( hAW8BCCL vDl  AVzBrCG]DVu AVLBq+CFB )A[BCHE pAOBCIdC! MAEBvCR6BCs fAXBBCU06D{+ gATEBCJpD AO BCHsnC9% APBzBD  AUByXCEDNݴ AjB9CJ AeBCCHh1 AjB9CJ AjB9CJ AB5C?~ A喸B=C< AoVB0CKBB AoVB0CKBB -*AB5C?~ 0qAeBCCHh1 gAoVB0CKBB AoVB0CKBB A喸B=C< AB5C?~ MA喸B=C< AoVB0CKBB AB5C?~ AjB9CJ AoVB0CKBB AB5C?~ AB5C?~ AeBCCHh1 AeBCCHh1 @vABC7 QABZC]? RABZC]? SABZC]? ZAn.B[CJ33 A䌁Bq~CHS |AhBd[C= WABTCSD@  XABUCOED`  A䎦BSCV~C@  ABN3CO|D A䌁Bq~CHS 7ABTCXDM 8A}BQCbX MA䌁Bq~CHS # AhBd[C= # AhBd[C= # AhBd[C= #ABZC]? %A䈜BTCUD  %AZBULCVkD  %ABR,CaSD$w 'kAhBd[C= 'lAhBd[C= HE7-u[BI BJ EKE7-udBB)EYmAJ7-ueBB)vEXzChU7-unB1xBVPEMD֤7-uwBZBcEN FB7-uxBBvEPEQl7-uyBBt#EPEzÈ7-uB8B-aER(Ea7-uBBVEMLFX7-uBJBn{EPF S7-uB;BMELlOE7-1BB(EX9yD27-2BHB(tEW]DH7-3BJB(sEWkDI7-BB(EWHDH7-BNB,ERָ7-BgjB2lEJrE5BB HA;dBB HA;dLB~ B AqDLBpUB x@CbL,BB HALqBrB |sA,BIL|BB B B81L}BB BhCLBB AȴA޷CrBq(B x;AEqBr)B {AeDgrBl)B tAQwDcBPB ])BC!BkB T6AߑDBB QtA]D/B$B UAdD1TBB BATWDCGB B BBACMBB R(Aٻd?o>B B _BVD:7 B B kQB|Dg#BB dBj{Dr#BB bBCeCp{j%BZB >A޷Cr%B{B =B oCM%BB >NAC8uBB cBBD=`uBrQB {AvBHB ydBW@zwB B G"@OtDwBB HA!S@Dͬw BB J=AD+w BB Fd>9D[xBB RADy.BGB LASpDy7BB AA-DTy8BDB @A Dbiy9BB BAfDѾy:BBB C?EDUy;B!B ?+ADy,wBB(BĜ,xBBB(viA,zBmB`B%ڞAW,{BB6BU]CE,BBLBsCGOBfBǾB-\)BBB,CBB*BDhBBɏDW B\BvBBtB1B$QB)a$BB@BCyBBpAZDJ&MB+BoACzаTBBB2)[C\hBBB5óBG jB B#AcCEkB B9B22C2oBB?D9DcpByBAUkCWiqBBBwAwBBtBgYCkgBBPB3/CByBDBQWCo`'BBB+w'BBB>CB'B5BW?D'BBB,Co1(BBB&nA4vm(BBcB6Af(BjB>ADP!!(BBȖ~oD%b(BnB+B!cC.J) BiB`B1Bڮ+BBաD<;,uB$BLB;CU,vBBȨB2aCZW,wB9BaBACHM,{B:BBC[,BkBrzDh,B|BA Co,BBB4zBJKBBɾ?dDṇ2BBcBPBBB B_BBCJBBB7$C n:BBEB;?)CLBBBgbNʶB.B#B1 :YBPB6B3BBҚBB ;BX\BB`BDT@BB]B?}RB=B_BCBBBB'BAB:$B4BBXABBBp#C肉BBB 9XgBˣBBcC=nBBRBCyoB]BUA|yBBBKDzBЉBB%C1OB̗BBcC<BBBmB3BA&nBŇB3BB~C,ޕoB1B|B B_BяB.BXCBϾBBD!1BтBEB rCqBBLBDJf#rBǗB C&DsBBBV@CF;BBA#8DxB9BBB6,B~BAD$0-BBBVBb'B}B[AΣ'B~BnoDY'BBIACĂ'BθBA)C%(BBB?RDX*B BBD~*BBBD*f*BŎBBKD[+/BŷBB AO2+BBBI,]B`B3AVdDFv,_BBIACĂ,BBB?V,B BB ;A,BBB e]A8-BB+A CI-BBB% ~C.BBA{/BĖBhAuDd0/BþBB BUvB˵BnBC5`|BBB1~BCB.BB;B BAB BB^A DYBBB1?MB B&B7[CTB BBTV=BB BBTV=BjBBB iy'B BB^A DY(BBB3ZDgגJIB B&B9CT4JKBB_@pDͻB B&B9CT4ͽB B&B9CT4 kBBB? oBBB1/@ (;BBBA3Af ,{BBB!RdB2r BBB'.C@o BB B0:CZ ?BBDBG7C @BBBF[CZ BʪBںB=&D= BBB8Di BBڧBKnQD >g BBB"u B}B B2tDRg BB)B(D ,BɂBBF 0BBw}B׾BݔAtCznBBBBB4BBv8CT2BEBBhsBGB\YDC'RBՏBߔB{KBs"BBBCx/#B*BBCE$BҹBB`B.B'BRB6mAm|BBKBihDkBԝB)@uDIBBe?ՑtDOBBBkCm9 B׎BBSB=BޒBBNBBaBDB=B@qDKA%BDBBC}zBBB{BBBhCSgDB[BpB+1D 6EBBA&D[FBBB4u'BBB4u'BեBAA\D'B)BBICW' ByB BI'CBB(Bm5cC'EB ByB}CK'FBշBBNoCBY'IBBGB,\D#'JB֤BߍA̹D B(BB[ HDh(BصBBJB_)}B+BBDS)~BBB7/BֶBeB'D=09B׵BRxDUJ9BEBBhsNBBB{BN)BיB^BaBێŇBBBG*Az'ňB3BrBmQDB 8B8)BaB #B,BB B";BrDѤB "B(B$-$B =&B7bB*B !B&fB C+B "B"_B s3B #CB6A^56B -B8AC֗6B 1VB9sAZD=76 B 4B9AstD&Ɨ6 B 6B9&BD;B ?B7BBDI;B KB6OBZ6C@ļJnB 7B8)B_IC JoB 8B8B;C[JrB 7B8 BtC_6JtB K\B6XB[f[B:oJB 7B8)BCZC/-nYUB 7aB8:A>ABYVB 8B8 BsC YWB 7B8BcBtYXB 8B8B~9!CYZB ;mB7B`C˜Y[B ?B7BBDIY]B 8B8 BjCHY_B L_B6"Bc1kB0YbB L_B6"Bc1kB0YdB KB6OBZ6C@ļYeB KB6OBZ6C@ļ˪B =YB7BAD*˫B KB6OBZ6C@ļ˭B K6B6cBXxBabB .B83B D՘cB 1B9nA'C<ұB 2B9BD+ZҲB -B8B(DDo\ҵB 6@B9B բC #ҶB /B8Y@iDoҷB :B8BaDҹB 6CB86B  BB D%7B @BiBmDP8B.BBD'B,BB5C_cϯBGBB DPB;B7B YC*>:B BBP}BB1B WDg~B BBC#)BBB;BIB 8B8BHCgAB B3{D|B #CB6A^5B mB5AzrC>UwB B.E)EB {B,BsE=uB B,aB_1E6&?B "cB."A54D;&@B "B(B$-'B !>B6*Aȴ*B #CB6A^5/>B ,HB8B D /?B +9B8~B@D3B 6yB81A8B 3B B12µBWD*3B "AB6pA2Cm6B 1B9QA\)6 B 7IB8QA*B16B B3{D|B 7 B84AӺ^B KCB6fBYB ƋB B5A蕁B B1ADhbB .B8#Bd2DyRcB )KB8sAPDφB B5AD EұB 6^B8>ACҲB 0B9A@CSҶB 5B8=AѥA԰.9BBB85?.OBB9B)/BB߫BB3 ,/BBcB%/B#BtBTDF /RBdBAV~C$9/VBBHB#A:/B8B>B#BN/BB>B'B*//BbBA8C@/BTBߢAD /'9BB/'^BBݝBf/)kBBBƧAK/)BBݝBf/)BBB#Au/*B BeB(/+BBBSC/,WBBߎ@_CCN/,XBB+;Dk/,YB`Bޗ@JD /.BBAz/5B9BBCS/5B2BBVECvjc/5ByBB?DCr/< BPBBoC/< BBBDBJ/ZBBBB'&/ZB^BAޗCA%l/ZBBEB(l7B]/BlBACs/BBBmB/JBB2B'xCL/LBBB.C('/ԄBB;BDCAF2(BBAzE5BBB ~0C?xEBTBB+EEBBB#DEBTBB+EEB^BSB&D8EDBBB0YE$SBcBB8ȴE(sB{BB~CcE(BB;B0`2Chc+E)BBB2E*BBIBC6E*BFB BwE/B,BB5C_cE/BBB5cCzcE6BBB(DmNE6BdBBD(okE6BTBB+EE6BBB"DrE6BBkBE6B݁B/B+ҶD4+EBBB.[C+EBdBBD(okEBBB2EϯBBB6hCdEӀBBB!UDRmEӁB%BB#C EӂBBB0YF\BBAD[/FBBB}C?FBBB CkFBBB@B gFB@BaBWCQFBBBbNFmBBA"DYFnBBB)DײFoBKBB@DLCFmBBAZ7C)FnBBAYD5FoBqBxVzDFqBBsB42DipF,BĻB>ͨDNWF-BïBACF.BBAXC7F'B6BB1+CF' BͺB$B?ĜF'BBAQ`PDBF'BABRB4bDgF'BiBVB= ;D1F(BgByB D]$F(BmB]DF(BBDF)BBi/]D=F*BfBCB C F*B$BAF,BqB~%D޻F,BčB^A֊D F,BBAZ]CF.mB%B*?#>DRF.nBBA'CdF.B*BA"CF/ BBAF/B BBbB{7F/BëBASF3BBA笳CyF5BBpAbF9"BBAB FORB̺BQBBsB)FB2BACFBBsA6BPF BiB>B)4C3;F|BBAC8F}BBABCD[F~BĖB@gDJFBBAxF̀BnBB ZBӁGB # B*QBpDPvB B uBfC֭P}B %BAWE?}GP~B rBXAQDPB SBA|DPB BB0QC1PB BAyD]~PB BA僑EP&?B "CB%A8DeP&@B B";BrDѤP)YB B +B)P,B B +B)P6B BB EfP6B B B$DP6B B 9A!EEP7^B dB`Bs>DP7_B ;B`AD7P7B BAdE E=P7B -B*A*DP8B BATPȮB eBBIC<|PȰB BAmD1PȸB !B%BBPȺB "B ByPB B[B_C\e:P*B dB`Bs>DP+B BBDPB BBAID$ PB BBlD0YPB B QB(DAP:B B`BuCWP;B cBB@DsP~B B{AD7dP%B BARNBB.ACcROBB+AD&7RPBB.ARB)BF,{B-BB+B AuBBhB5uBQBB0B (BwBBJiVB,+_BBǍB=+BPBOB+8A-%BBǞB!B{ -&BBtB/cC30BBB/qB]I0#BCBB6NOBBBsCOB4BB(wO BwBB C@,6lPBBŦBT^5@8QB:BcB*BQBBBBYB:iQ#BBBfC9&Q}BRB B/]S@>R1BBNB/C[R2BBB8RBB!A5,`DohRBBB4DaRBBBC5SBB.BA@BS"B B̤B#S#BVBǛB A&TaBBMEaBBȰB/kCoBc\BdB迈B*D XdBBB C`"dBkBbB;"YC$iBBAe'D8jBBB|E^|kBxBB;RmA1BzBB'!6@EBBB&C:O#BnBB4{@1B{BB+AzAB9BBXA?aIBQBB'BBVB.üḂB;BLHɘBB6B{IBBB5N@uBBB5ulBBLB^T~DeBBMBD߫E(B7BBFFCY(B]BÎB(h>D (BLB C?D+SBPBVB#S@D`+TBB'Bf+DԨ-%BBsB0A4-&BBB B'W0!B-B'BSC0"BlB|C'rDϑR0#BB D3sBB踀BS@73BBBbOBB B5DClByO/BRBBC|DS;O0BvBƖBb ACJO1BBlBJOBˑB軂B.CWOBaBǑBwPBHBLBFqPBdBYEvPBBKu EQB*BBkC9%QBTBB*pD#fQBtBB4hQB#B$B][DQBCB±BgDbRBIBU@XMDM5R1BBCäE3R2BBGD#R3BBCD9RBB,BM+DkSB5B9Bb:CISBBB#ED'SBCBŪBvD9USB3BƄBSW [BGBBoDq[B^B%Be)D[ B;B輵BtD ,]!BBYBD-D]"BBB^7D]#BBB[ BM6S]BhBEBSbDOy]BBBB]B?BHBTSHDD]B"BZBPCc\BNB迻BcMBfjdBB!BG=C!BVdBNBɈBﭞDցdBBƂBOKfB=B躰BR}nḂB)BOC]nBBACznB B!DEpBBBMPD]iBkB=BJ]CPBBB3gAUNB]BBDRuBeBeBgAB B蹌B] 7B3B~BT!@xBBnBBgBcBbBi@k~BBu/DBBB/DfrÀB;BBZûBfBB${%D'büB˫B轁A!DFkyoBJBJ@_DPdɘB|BzBQ˅B3B-B_?DAJ ˆBB4Bw7sDY]ˇBqB+BLDOD"ilB4B!BUTCNlBkB?B\DBqBBp6DB΃B/ACDEWB%BUB^eCBB迚BQC532BBBY9DxBNBHBLBG5B%BBD{z6BEBBU?C7BABB"DHIBBB2!BB@Bc3UDz,LPBBxB?BaQB}BBhZC=t+BTBBg,@BœBvB"?X/B0BBTtDB{3BBdAiDt7R'BBBsDMwSBgBrB*ڹD"+SBB AѼD`-BtBAC`.BkBABS`/BBB?DS*bB™B B 'AvfBŁBYB!!C RfBŝB[B%4CuqBB1@rD*BqBBjB.rCqBZBnB)CD&6BB۟BF D@1;̉BdB|B%-mBmByBC-3B—B BdRAnB9BkBaP?DBB*B(~EC'BQBBD$1Dj'BʌBB)(IBBJBC@D+]BB6BԐ8Dw+_B BȨB$Cִ|+BBB6B+BBBB*lDBhF3BBAeDk3BBByCNBBMB)kC4=NBBBD "NB|BB3YD>ZNBB B(NB&BB9COBBɷA~VD OB9BʮBDO BBaB@Ds6O/BSB͛B(O0B BƍBMOBʌBэB0CT+OBBǒB)EBhPBBlBPn@8PBBIBi5DPB?BB133PBBB5DGQBBBAQ#BBB3iBUQ_BBBD[Q`BB B;UCQaB$BղBi&DjVQsBkBBRBWBrBA[DG^SBsB.B;SBBNB A!`SBEBoBLB<SBnBEB7CS!BwB4B*BC[v{S"BB@öDS#BBʩRDCSFBCBB&TB$~SBOBB/jECSBBB\ D6SBBJBwLSBB4BTC8SBBeBCSBBBFSBjB ADA ],B\BRB!cT]BB۟BF D@1;]B'BtB1_BBBIj1DQ6_BʚBB8YHCKrB_BʔBOB*e!DM]`/B—B BdRAnbBnBBUCRcBgBκB&Cd`BBBKDVdaBQB)B ?D+dBxBBF^CdBBB-D}tdBNBFB DbdBBƜBN&rCfBBBhDXfBƓBB.fBÿBB-gD,( hBB BCVAKhBWBC'oLDڬhBvBCEjjBB׋BfWBjB[BIB^DkYBB=B0D(3kZBB;B*DUk[BB5BxD }kBBbB PpB[B˸BDCTHqBNB~BCdwqB8BBKrD?qBBNBtWBBǎBB'AjBBȼACkBBBLusBBԿBZ%BLBWB90B'BɑBٚpFDd}BʪB@AZD;BB"B5taBxBOBCBBiBO@@ 8ǻBˎBAmD7ǼBɸBB5xD-XǽBBBPCBʷB;B5CC֯ɘBBJBpDɪBBׁBV;FD|ɫBʧBhB87ɬBgBκB&C5BBBO33IBB!B4A/˷BΛBB&˹BB@LDrBaBBCB BB%DBLBܿCDǤlBxBBmBBAGDwnB˜BuB @bBĚB B,NDBYBB-vDyBÉB B0SDx ܓBBB7 DBB+B1D1BkBNB#)SCLB&BBm 2DBBB0 AYB $$B6ACPNamB "xB&A C1BBB)tfB:h1'0BFBqB.DK^1'1B'BB$w\Bv]1(=BFBoB*] CTa1(?BGBB *DvW1+BףBAD$C1/B_BNBIFCF 1OaBxBBB[AM1ObBEBBJfCj1QB̹BB?-D=C1QBBB+CHA1R;BףBVDX1RDO1SBEB-B-/1SBҸBB+:C71SBȓBB5C>1V_BBA[#EQ~1V`BEB-B-/1VaBBBD~1VBBjB9:^1VBշBuB+C1cBIBBH8Csf1hlBpB\BGȴ1kBBB2\D1kBBB*-1kBBBD]iA1kBCBCEZ1kBϏBpBMTDk1l&BUBޫBxE Y1l'B̉B@3GD+g1l(BcBbGD1l:B#BPBH1l;B/BB@u2DC1tBBBExD 1tB֭BB6D1tBB?B6,D?m1BbBBXB161%BB B's19BgBXDeS1:BֈBB?D1;BYB6[D]_1BΞBBN1ǼBΛBB&1BѶBB1BBnB@JDMDY1˷BճBBH%Dk&1BB hDu{1BBAtD\1B{BW׸DJ1ԥBB]B@D1ԦBpB\BGȴ1MBBB$B81OBBByC 1BBB#C1BIB!D2r1݇BB޷AD1݉B*BB0B1BaBpBnPPCM^B1BBmBHWEX1BBBPkD:H1BȋB BBKDǂ1BQBB'1B:BKBB"C1BBBPkD:H4BB:B"*EU4'1BBB%4+BB݌BD4+BB[#DaE4+BB[ACa4OBmBBHXC4OBdB4BfD@4OB BfB1D4QB#BBSC 4QB4B7A"4QB-BیBUA1W4QB_BuB *DR)4QB_BߙBGVD~4QB̠BBHDv&4R;BB BB4RCx7B /B8 D&7B &B7{A2pDr!,7B (B3vSlE)`7۲B "B BDL7B %B7MAuεC7B #B+A`7B $9B#1A7B B,BmE݅7B !B!BDٞ7B %B3¨AD7B /B8 D&N(B BB=+D08N(B BARTED N(B BB5VDoN/"B gBB[IC.N/#BBpB/;"C| NNB B BVNSBBMB(ZDQ$NTB B B,CەNhB B&\BENhB BB_pEIɶNhB BBDDNiB +BAԝESNiB B1B) E5?mNiB BBPENj B ! B1+@5DNl!B HBBBByClNl#B MBBfsC\NmB CB~B NmB BB*JHDNq,B "!B(B EQNszB B BVNB B B JD1NB BB-D ;NUB B&4BEHNWB BB NB "B'AkD>NTB B B,CەNgB WB2BWZNB "B(GADTMNIB 2B)BJD\oNKB xB'BL*DpNB B-B0DP)fBB#B6FP/#BBB{PSBuBBC PVBBB#CPVBBaB1DPVB;B\BE/DPaeBBBD*SPi[BBByPi\BB#B6FPlBBByPnB`BB19EPnBBBDPnBBB[D(PBB`BPC6P;BBB%PBrB#B#D&PBBB_DwxPBBB VXCeaPBBB#CPgBBpB/;"C| PBB`BPC6PBBBD ^wPBB`BPC6PBBBD ^wP)BBBƨP*BBxB/DwQ(B !BYB"6FQTB B BDQTB 8B\BtĜQTB {B BWD3Q`B BGBAnE QamB #mB*A'CQmB B"BD@QB CB~B QB BB2?D)QB B5A&QB !B+BoVE ` \ qaC NA xA{D`  q_C PAyGa XC By}DwWaI DC B/qDIaK XC By}DwWa JC B2 Cݕa JC B2 Cݕa 2C AXa XC By}DwWa  C oB7jC ra qaC NA xA{Da qaC NA xA{Da qaC NA xA{Da qaC NA xA{Da qaC NA xA{Da C KAoa JC B2 Cݕa JC B2 Cݕ = ,%C~D!E!FHz =/ CDQEx =/ CD kBvi =/ >C!DDS =/ CbDRTD =/ CdDnDk =/ ݃CD ƑEv7] =/ C2DrC\ =0 CD; =0 CyD!D˪! =0  CD/E2F =0 .CD 0EG =0 CD7&E? =0 IC"KDD" =0 VC$tDg,D< =0 V/C$D =1 EC*D D) =L cCD+B|a =L eCDVw =^M iC?DD`G =^N poCRD5E._ =^O poCRD5E._ =^ CDExt+ =^ CD )E =^ CC*E2 =_ QC#DHDP =e 6C!RDъD =e HC!DD2 =e CD9EI =e PQCPDC E =e CDq =e CD^?E9 =e hCAID }tCAJB, }xCAhCڎ }xCA9C2M yC;ArAi y]C.AK33 tCA yCB kgDl  uT&CC }HI_C BBD( }oHC)?Cx }pICBESO }tFjC/CD }uJBFD GC-qCoyC qCECXCÆ GC$'CE19 FsC2BE HICBSD9 HC%B2'E H|C(CDn H)C$}C EL -GC-CBв /IC#]Bو1 7L]C Bn#Do PC>B"kATh JCwAD  HCAD> HC&AD@ &~JwCYAbu &wCbAt[B: )[[CC F8 )rECBN )wECBT@ّ )J]CyAeD@  ) bFCmA^Bf )uGCA DFg )uG?CA䨂D:\ )uGCAiCس )uGECA/D| )uEC|B @Ag )uECyB0B, )v]MCAC$Y )v_N_COBñDtl )v5CHBD )v^C2AͳAJ: )vNtCbAocDSh )w00aCB- BL )w91C+B*rDCH )w:1C2BC6 )x=TCBo )x>dSC4AyE] )x?U CBCd )xuChB0UiCQ )xqCeDb )zPFCA?D]|- )zUEC BO )z[1CrBB#  )z1@C.BX )zECyB0B, )zECyB0B, )zECpA2B/ )zECyB0B, )zECpAB ){gLnCBE1m ){hJCA͍Dآ ){iJCA$DpZ ){lLCAAiD#; ){mLCADZ ){nJlCAC ){q5CFBD? ){r:C{B- ){LCpB9CK )|9C~B?vBU"U )| 9C~B?vBU"U )| 9C~B?vBU"U )|iC@ZEj< )}LCpE )}N"CB Dȃ )}OC7A˩ )}uChA:Bl^ )}b CQAfE? )} i CAE{Ej )}!iC`b(E2 )}GHCHB+XC=X )}HMKCB )}yC@AL@$0 )}0C5B CH )}1 C2B~%C>F )'0CB BBd )2tCAt 9Aa )PECB AH )iNCcA CD̿ )OC~ADDi )MCAˏDe )L]CA߂'C\9u )OC3Aj )KCB   )rC"MD )tCRA C! )9C~B?vBU"U )1CHBbCc+ )LACKB؜FD* )tCA7B )u5CAp )?0CXB~~Cu. )O,CAMCD )OCA˟B )7KC OBMcE% )GdCAAEfy )Z^C/ACA  )KC'Bw )tCAV )XFCA )eywC9AMA(I )FwCVA$B], -^C5A0!? -uLMC5CcERh -wmwCESCAAE -wPC>C~w -whGC=BDe] -x>[COA;D0  -zcXC@Io mE -}]fCAD -}UMC@B@D9 -}EC8C C -}SC?hB5zCe -}t CFBVE -j_C?2B+ -GtCA%A -wCEtCs[E -hC>BҏC -[CEBϣBF -7MB J@Ҋ -Z^C5B,{ -qiCKiBhtC]ACԬNHCUA JRLg+ALIE  gV`G>, BguA;F< DguA;F< hV/fu@j j"gA.pE` jogcA@9pA3mhWfSBjEymhXfBCEڏmhZfBRrEmjlg AEP85|?C,NP:5|?C,N amVA a 'sLA a swAw a ʺrB#EƧ a r;BBD03 a r?Bb a &s7xA_& a pYAkoE aeq(A;d aarxB$D aFwrA*aEc a]=uBdD$ a^@+uHB} a_2uB#hFG apr A aqrA^5 amABC amABC apA"JE3 a@pKA a."paxB|D8 ar,AQ a[rGA}E% aYMq+A[| aLq=AoE} a,qu:A ErK agqADu aPqiAaE{F aP0qB D  aPJt*^B\) aPKt6 B EM aPLty#ESH aPPsA aPur`B+ aQ vrmBuAA[I aQ'vOB/%D, aQvZB&XE aQvC,BiEFT aQX*q-Adu acuQtBJ#E6> acntB"WDP acnt|AE' af rA: h-AFr|}:5 xhoAu:@ gA:} gAݗ7@: gAݗ7@:" gAݗ7@:" xhoAu:" xhoAu:$C xhoAu;!? xhoAu 2ʯsAy = 3WrBx(E YݽrA`B Y'sAJ Zr1tRB#ED ZVsWAy mtB(En FsA  nrt B 5D rA/eC} ϴ:t ABFC Ϻv)tBMsD Rs9AoNDy qRtBgF?2v 's/AFs ! ,5uBFXj ! vM6BdUEF !3? uB,CKQ !3hnuB !3D3unBE !3}vlCDQ\ !3:uBE= !8Uu=EBF !/"L ?gA"niA" g6AP1" OLu.BG+" 4 g6AP1" pniA" g6AP1" 7k>/" gAH" (OLu.BG+" , ?gA" 0niA" 7k>/"hA"{hjqHN"$ ?gA"XhA" g6AP1" g6AP1"T ?gA"6€D"hAw" PiA4wD " Oi@UDp+" O,i@=CK" hiB.@D8" `&iB Dԋ$" k.iBJ"# q iwADig"$ oicA3D@", yi7A"C ai*BZ@T8"P kiAT"U jiBc0Cr"V jiBPC>"W jiBPC>"` jiBB"j qRiB "l n;ieA"s liBD~0"t niAŖID"u gi~ u'iAC|" L8i\?" \0iaB+rDa" SiA`)Cg" wiTA(TC@`" qeiB>A!":€D b" ugnDF" h*A-"  h${AE"  g{1EUu"  gAEDU"  ^gDjE" N rh]@LEmq" N Ch}AF`" N h꿛]CEs`" N h@E'" N ,hPjwE" O T]h3RE" O XehAPD#?" O RhTApE" O h@hE]B" O Th8B?DM" O h}!Fr*D" O /ThrFLg" O rhC5F٩" O XhZNGFi " O kh< _Fb" O Ih|ryFV" O ^h7#CnFi"" O! h<|CTF}." O" b@h^nF6\" O# gRo)F8" O$ h8AEf" O% ShHCF" O& h77E" O' Ch ^CJHkF#" O( IhPCJ1Fl" O) g&E# " O* gХFEf" O+ gOA;eF" O, gAヹE" O- gC7\UE6" O. kgmCLF=" O/ gC]F " O0 èggCEhE" O1 h GkVF: " O2 Ag ?oF)" O3 gkCSyF*e" O4 gDYFA" O5 kg4?E" O6 >gà 0F-I=" O7 Ĩgog`rF:H%" O9 z_ggE/" O> egݠCFD " O? gyCFF f" O@ ogyB,F " OA pgBEݞ" OB g|CkF " OC hB@F@z" OJ gùEd" OK gÁdF " OL xUhCFn" OM gVu2Eu" ON gCbF!" OO hPEr" OP g[C{pF +" OQ gCF!0" OR t2hC&F!" OV Cshz^CFo" OW NNhf E{" OX hCeeF_" O \1h3@U&DP" O zgD5Eѭ" O h F&3" O ]hCF3" bH h茼:\D" bK h\ES" bM h呿7El" bi h@)Dz" b Uh ʃE<" b \hrAy" b UhA#CƷ" b 8h¬S3F" b ahHHEDa" b RhSE" b h@yE7" b hoArEr" b hvAȴ" b 6)h|x6 FA"" b !h8o]F{" b h>KE" b R@hsRF<" b e`hBt9FV" b WhuSF!+" b oh,,%F3" b h*<$FW!" b l?h` CWF0" b {hB'F\" b {h6șEp" b RhKCeF" b ^g#ÿF" b hCHF(" b |h:yFz" b $gC8E݋" b gMpEg" b g"1FIL" b g•Bb\VE" b gCyF " b |*h%tDFن" b gBE" b ȴg,CN/Eѡ" b ڐh F%" b g:ųF%9" b ̊g"B%F$q" b cgDGF" b gC>RlEd" b 9gF." b g7BbFj" b ,gBNF@S" b $gٝ?[PES" b gD*o,FR" b gCf[F@o" b gID7FW-" b ͬgɊC7AF9" b էh )B?F;" b gB*tAEV" b gCDuE" b g%rE" b hKA3E " b gáF6{" b h C%F>" b gvFښEC" b 'gEBF" b vgTF x" b @hFP" b "hLCE" c/ gjAmE" c0 Ah=۫F" c1 gEg"m>FF݊" 4k;D "' Ejj@m!E0"' jA/ʮE"; 8jb>AIC="; jA}|?Cu"NRk B=*Ef"u1*m_A1E"u24 m AFFD["u3 m;TADdF"u:al[B:VD"u;|lµE"u<Il(*Afj"u=BlĉFJ.8"u> lA,F"u?4lDA1ܰFSG?"u@ksAətFO"uA*kAF@"uBl=pFSA"uCkAE"uD kpAzBFe"uEWkۑAվFY{6"uF k.E7"uHYk^A`tEߕf"uL bk@xD(e"uM S"uU fk5@;F$"uV qk)mA9]mF4S"uW ^ kL@F"uY `qk@AK~E."uZ kAE3"u^ nk&@߬3E"u_ | k:ABE|"u` ek8A/hEƃD"ua zkEZ"uc XkPAtEHJ"u jAoEO"u jAiD"u k A-"FD:"u dj@F i"u kA7xF) "u j̔AE["u j7A3OEA$"u jjǸAZEX"u !j&AFhE56"u ދjDA-ME"u jA}DJ"u %jA!E${"u 0jA#fE "u jĀA|wE_"u jm#"u j ADEp"u juB@JTE"u |j@E"u jvA ;d"v =j@E"v1tl¾dE}"v2hal Al1EYQN"v]{Ol»E"v^zliCEDFN"v_ ld@DFq"v`Ոk:A=F"vb7kַA9EO"vsPlA\)"v 8jA@,E:"ImAC0C"Q)lXA1*FL<"-m(Z@FۊY"l ŽtXE" m=5Ah(FG"k3@ F"kxFS"kl[AFF"zil1JIFt"l$AYF"}l\QB,F"kDAáE䱹" nlmDEBGqI"kAeEZ"kAlE"ekVAbEL" qkEK}d" km3Ep?" kAgFW" ,kr@L(CQ" A khA1F" NTka8@qND" k KlF1j#" j~k,@E" rk A"E5" GkW,F|*" YkJA8E B" 7YkrA% F(" tkU@g}Dr8" yqkoAZBE" SKkSA" ck=?xF<" SkS@UBA" jAtUEUL" j7A"F" tuk&v1(Df" qjA9E" jAyɯE2" gkAbF" jIApHE" jb@kS" FjAf/#EE" 5jrAEt" ?kA~F^}" FjA ?E4" j'A:DP" ޭjA6E" jͳ@ E`T" jA!E,"" j@FE#_"$ !jARn'E҄"% joE@RDf"& ~jA3NEK"' 'j@|"/ Ij`@.D &"f qjSAE"qlnTEV"klnCBXFu;"llqBtF~9("ηkMA_XEٺ"kEB$E1"nl!AaF" {Hk!@X"Rk?LD=T"q[kAǺuE+z" i^?Ep"y }i>A" iXA%E+" _i)A痍" isADr" |iZ4v}DL" ~TiyA9" iy@A#EG" ixEP" jiv%@O'D\" }oi{SAD2C" ~iz)@ضDi_" |iAÓu" $iNEBM<" kilBW\DXG" iimA%D6" |ij@?E e{" wihWA>E)" ijhB ECL" ii}A/"% ~YiuAƨ"( ij.A"1 Fi7ADZ{p"3 ~i7D"4 iBTDQ^"5 iAE7"6 ~i~@pE "8 }iACO"9 |Xi}@D"; |i@^D"< Yit*An'_DL"= iy2@ *D0X"> ~xi| A˅D7"\ _i)A痍"` ihdEW "c dil B*/D9u"d ii|@GE*O"e ig7E" qihAgD;x" ii}A/" ii}A/"N* i[#AsE"N- i[¿E"N. uiX>#iD0("N/ if~E"N0 i6?E"N1 ui:tODEZ"N2 i>?̥E"N3 iT\A*E7$"N5 0i] E="NM iG@aE)"NT _iUšhE"NV 8if@"NZ 0iKQE"N[ iWAEPIM"N\ iap¶ÜE."Nf iU@bN"N~ kiU3E"N i,@7rAE):"N i?CE:w"N Ãi.P@AE"N 1i)>|"N iC+@4E¢"N qiPME"N Јi?]E%/I"N i:@3'E| "N }hO@yF"N ڡh@WD"N Ӟi Q?E+k"N 0hRA Cy"N hA.Ch"N wh@E["N mhZG0~EN"O hDA 5nDH"O hL@͎C"OX hѿ"a iTlBE- "a i^»CEoL"a ii\AW8E/"a _i)@e4EV"a 1EIF"a 8if@"a ?iG>E{"a iTACv"a "iT'gũE["a iiMJ\bE{x"b vi@DEcO"b i:@>HwEC"b ia>VE3"b ϭiyKBFL"b .i&v@F?KE@"b i;V7EF"bB Ui@ME@F"bD iݬ2E"bH i?""Eʔ"bJ iV?_GF"bi h?%FJ"bk ہhMA%"b hAD"b hΕAGC"c iR&Ab]A"j jA8D7"k 7j@}]C[hK" -jA1wE" j @C?" #j?AR'jDT" jAi6D@" iAP>DE " jAB+-E %\" Rj A3.D" ii^EX" i A&E&=" 2iB;pD"/ j A:"0 " -igALEgt" iDAҒDwW" TiADѓ" iA=CU(" iAtDK:" _iݳAM33" iζAܴD;" i[A ͪDW?" i5B)f D" iŌB" !iPA̅Dn" ZiAbABbH" iBpE " i\@ȈEB" iaA'Dg)". GiA C"0 iA,C}"1 7iAItDN~"N ḯAVD "Q #iA.D M"R i]A"T i-BCDDfh"V iAD"\ iA$D"o hinA<CX"r iAD"v izA2IE42"w BiBsXD""z tiAvD" iXAGODJ~" i!@xCҏ" (iA dD" ci@G" i3AD."1 Kin? CG"3 I^i>^Dp_"G k&iB XCmd"O kiB X"P kiB$D ="Q kiB"X k.i&B"Z ki_A6zDk" ?+i@ˣDx" <~i @xD(B" 9iA o6DS_" >i@FhdD " 7Gi>A-ݦC" Gi&D " Ii;@Ep" >i(@|El "* iioB%1A:"+ hiAߔDIk", iiPB-GA"F iiB:CE+'"G jmiB?7Axr"H k i BDK5"I jiAHuD r"J ii}B%+"c jiTALC6"e j9imB*kJDE3" Gi?8CV{" Li?ļk" Kin? CG" H/iDv" ki&AC" k'iA aDRI" kiB"= >iL@ÚD݊"> 9irA*Dbi"? 0!i@Rn"U i(@|El " jAi" ?j5kD#" :CjA:fEe" Aj+D=" 4iU@:Cb" .oi@4ӛD%np" +iʰ@raDn" ,i@Q" &ui@zD~X" ,i1ACD" ,iBt*Di" )iA3\wD" ,i1ACD" 'i@=D- " 'iY@" &iGD44" 7Ei`A1_DH"% , iN@mCɡ"' 9cj+%C`"* ?j'Dʮ"? ,i,@H{D5nt"U 4 i@"W ,i@h8D " /@ih@Du_~" &ui@zD~X"u gjj?YLD a"u )8jBNTE{"u $jmA[E1]="u jPAZͦET"u oj$A{DGO "u cjuAlbE"u Pj(A/XEE,"u (j'ADݩ"u j*Azg~Dt"u j8AlE"u 2j:LAD;"u j6ADr"u 6j^@孚EM"u yjM.A,EH"u jbA 2 EdC"v j &ALZE"v j A C"v Cj4RAVE"v j\A UE"v 4jy>D% "v j AhE "v; j6AdD )"vE j$AC9"vF j2@EC"vG j&lADV"vH jAADp"vI 6j .A[UEu"vJ NjAbD"vk jnA"vu (Ni8FKEf"vv %OioAID"vx 'Xi^E="vy )iyA82D}"$ jA%1CE"R 'j/A,Da"S jAUDl"T #jCaE"[ jA5D52"\ kjAEC"] j$A, E ?"a tj+#A$E"b j*Azg~Dt"c jdA@VF!Z"d !j@E@"e jQTA&E"f j_@w,E䈵"g QjKA-EW"h jhAuTEI"i j`AHC-Y" &j A5D4" ">Ck" whj@fD" lj@RDN;s" Nj K&D|U " =j Dv"( xRjw@E "( jj@33"; gj@bN"; x!j@Da "; s jDR_D/"v :jCDy"v Pj \Dڪ"v ^j8UE$ּ"v Rj7UE4z"v gj@/D [1"v* ^bjr,Da5"v, ]sj&ww"x Mj 3hE h"x Tj D."R 3j?2JC"T =j Dv" @j>">Ck" bj.E`nv" Dj-6EG" ^bjr,Da5" G.j .ID "N2 WiUACmR"N5 iVFA|=Ae"Nf (iU@t9X"N iT?O"a iLD "a icAR;DT"a siSp@d(B "a iO:gD2"c iVAW"2* jiB4|CC"2, hiuA CI4"2B hAiB7C"2E hiAbDA%"2F hiB&?CG"2. gj@rE"2 hiNA)C"2 hiqAgC[n"2 hiA0SC\"||C;E4"|ǡCG?QF"J|C?*F"!"bA|COD ".{KCqF"dp|ICMEa."|CBV".}(C&*GES"X_{JaC|EZt"|CBV"N{iCFwV\"ivzBG*"x;B~Gk"xLB,"~2CwF}"y[BGZ"}dC[ Fȼ2"e}CVG "{ŸC G "wBG"f|CEZF'"*{GgCVG3z" 3yDÿG/" 9'~jCeGM"~YCFY"Z}CtfE]*/"5g{C" pzHCyD8"!Ew(BH"#Ч}|C;]G3 "4|zCI&F""Oh-zB>F|J"QyuB6GoN"A{1tC=G "xBBbF-"1}*CX5?"f|CM{Fv"l|CFFDW" 5v}!CG sFC" bzBYG6A" x¦BuFn" 4~ Cc\G " "'yZ&BD`Eݗ" 'YE}'CiHF[" )ywB:FK" 1d|׿CHO~FG" 9.4~NC~ " GxEaBF/*" Hxx{BF1Q" Ix'BFL" a|CC F~" b |C3F!" u|9CH3Fr" |7CF}" K}CRDR" EYxInBYvEU'"9;~p*Cs"Sϥ|cC8C̼<"y|afC9}E6"- $4GCgE"- &qCCվw". 7qCEn".}.MC_D"6€wD,GLw"6o~FCzF(;"6H~ C](DT˕"69~C|D"6 =~CE |"6 a(\C7D֗("6 bzpTCFŖ1"6 ~~hCLJF"6 "€CϾ"6 AfUCF4nc"6 zw€DD:"6 }u€D9Dl"6 ~s€JDDߟ"6 "C..F-"6 xHCZ"6 |CFd8"6 ~CdzF]"6 Q~CgG+!"6 rC@DV"6 4GCgE"6 aC_F'MI"6 d`CAF"6 ~CYFs"61 }CFp"6908~C|F2"6=xf€D+D"6@€Dw@}"6}gCW_C{"6s€JDDߟ"6UC EW"6sQ€CD"D{"6;uИCRD"6CCKCFT"6u: C9G)"'ErnAE"' rA("' r3Bm_E0Ad"'2r.B)D"N#/zreE""N)ms[AFH"N5|s0AEh"N8!rB*En"N:TcrB:E"N8rAT"NhrA=q"Nr8Ahs"N"rB ="NrAQ"urB&!"urAJE|"uprېBNCVDP"urAQ"uBrB E2q"B0rBDZ"Nws%@A=7L"UˈrAdZ"XIrA_D "hs9AkND9" rA(" r[BE"rڂAKXDU"RrLB*E"S5r8AeeE%N2"YGsA7EX"eus#AEث+"hqr'C'1Ewc"jerdILRFq"ú$rr(AE3|"ErZAVC@" rB_F "=rZBRzE5"_rθBDy"=xrAA!E>o"]rǃB?" rA#D"bTrA}4EI"uķs*A*+"x0rvAiIEnT"z~rjBE{"wrğA'ES"rB]E"i9"rBaE "rAQ"frCE˿" vB<"wÃB$FD"'6|qvAM%FrY"'@!v;BvF!"'q!AA;F5"'rCAo~Evr"N@}qA`X?Fm"NFXq AF|"NP:vQxBF0X"NRiro0B$E1C"NS vBe`"Nirl=AF."uN/uB֙DE`"uVyr[RAFJg"u`v2BFh"ucvwBҝ$Fb"uqB"`JyqA%A:gE?)"d]tBH|FX"fr5AA&Dw"p$v)B\F"rorwA AF"ZsqAeFF"×Np AaFv"øn@}Dj"jCkqPA E8\"|,nB+Du"qADT"exqwAӱD "nsŠ6;E!z"'sLABD0"'s9AkND9"'-qtBFV%"'.6u5B|6F\?"'=tKB7F`a"'FsnB "E"'KsA/F%9"'MPtMABxFT"'OCt&BF"N.gsO>A1E*"N>Kug~B&eFt"NH$sAߟFk?z"NMjtҙC<2AF1"NQ_tBDFDg"NVsA3tF;"N_t4]Fiݭ"uN07uaB̳F"uXssA*F!""u]^t-BfF"uaSuIBOFb"uc"v0B^"utKB +"Ns'A Ev"]qtIB&F"^<>uBF8"doAt(B.ff"hsdAȪDt"izWtAG"mtoBiFCY#"q^CtB0sF:"vs֊AJF\;"z>tTVB +"{s}&A3jDQ"}tMB*ЎF"5t$´eF"atB>rF9F"ft BbF[|"Ys9AkND9"^smA?E0"nMiuYB׌FK"xsA:F=ȉ"}rtWC>xFd"bsAD."~.uXBF"sB ݐFeqB"atcBS{F"tKB +"s5AT&D?"sPAΥ"'?wt BdS!DJ"o2t2B&Su@v"'8j>11G~!"' jlAdF7-"'hjB)}Dɻ"N:*j B"u2jE>TGR<"u3yjB\FI"uJEirB$CfDT"A[jM†ءG~o"B;*iWGo#E"Cwj1AF"D^kG]׼"Wj6A̰Et2g"Yk! n5Gn"\iA|=EK|"`cGj^@̯+GU"akzMuGbJ"biAL G"cZj~QGZz"dTkZCG.;"f2k!|xGIk"RIjB2E_"S% jsh@G"g ojm"ACC"jbkG0"qy/k#G;"sk!OGx7d"bD;jAG "ccj*jGZ"!lh7BݽE!@"!' h݊DF,"!'*iiU?C-F"!'+h bE"!'-i+mAZF+/"!'@׽hF "!'H՘heaEX;"!N:/iw@T#Gj"!N;+hC_.F"!NX"shDFB"!uJ@iWRfF "!uK_hzBFU"!uh hۄDF"!BfWiB&F24"!P!hDFu"!Wi\Gb"!Z'i18@F"![۞hBj}E"!][i8FC"!`YjbACD@"!gim(|@FⅣ"!nhD|FW"!p+Oh*DF>"!vi-G$ "!x٥hHeE;"!yܲhAdB"!g*iBx&Gm."!j8ivA߀FIn"!k h֒D3jF"!È!hDFU"!zi:d Fڅ"!{hCFvb{"!3hfDwF* "@fCAxX AZS"f@A_hB" f>AoC (_"tf?A;"fDA-CGr"fHBKE"fDBXDά"fBBV'A֎@C"fE,B'D&J"!fB+ACG"(&f?A-"/fJAE "0fObD<"@fTA6D"C(fhjAGFW"DfLgEEr"EfRE"F!f\AF"GAfQAMDߜ"KfO|AoDO"LBfcAnEG"N|fY~AHFZ"RmfiADrZ"S5fT AcE("Uf\yAEM"VfR\AclE"WĹfV5A" EJ"[f\NAF"\)fQ*AkD!"^fZ4A2E'"_ fZAE3"`tVfo~Ax/Fx"a]'fASuFJ"bfdNAQE_"gbfwSAvE"ifA?F,"j?JfgAkFg,"k6f?5A=[B"lufM:E5gr"m]fAsIFCM"of@q@FЁ"r/fP)A"D*8"sGfTAdgEN;"t&fPiDE=n"|cf=ABH"~f>AXC_!"fAEu",R~fMAiC]".fAI"3f&,G"4e5fvA۝NE`*"]f>4A9HB"Gfr@EE"C`>xfhAEm/"CmfAKCs"Co{fAXE4"Cg'=v|GrX"C"f G J"C.fI6JG"C!fATiF$|"C*!f0vGbr"C+:f2A F^"C,UfA7;F6"C-g7;€Gxx"C.9g7 G<"C/Qg%AF*"CBgjAEpF"CEDfAFJD"CF(fA|F"CGf9LG "MMxfADoE~"MafAD "Mg A&XQDF7"M0g(^A"M!g0ŒG#"M"2gSG"M#g7k(GI "M$&vgV@ѬF|"M%$g=gG-"M&jXgSF"McgYB_G0"M'g )F:"M'g )F:"MAg2 @b"MBgEs!GA1"MCQgHRGCv\"MDe-gR[W7Fmr" :k@kD" 6C>gYA@u" NYgFO4Gf"oMftAE4L"o=Qh8FdD"oagF+l"o2Mhê?F"o h8A=yE"o( h#pA?F,"o)gC\G"o*)h(F`"ogoA6cFc1"o}mg"/rGk"oQg:"o0 Džh@}AF$ "o1 thg1C5/G"o?:CgC4G;"o@qfRAZ9ETM"oA,%f$)\G&"oN0YgO@(ECܩ"o h=(60G]b"o h3A؛E:"o 0h4DA5Fw%"oO0 gA "oO@ Zg;AC1-"oOC ߚgB}Eb "ob \gAt*6B m"ob LgA?CMI"p h1 AFcx"p Ph<2AEYt"p F h4A"p2 h%€v.CzrCVf-6WVuoC|Di6WV€NCHCM6Wy*}€`DE,6WyKt€C+^DI*6WyL[€wBS&Es6WyM|€C6Wy_€QDdPEqi?6WzT€C E/86Wz*AA+C'D6;6Wz~.e€C"6W{}€`DE,6WD/e€~C6W|€D6W!€5D vfCキ6W3\€DCE_N6WZ€C6X+7€zNC`D6X+1$€C2@E+H06XR?€tCќD:6XR$4€{C߀DR6XR)U€g=C(?6XR@Jg€$CQ{D 6XRE€o]C0Dp6XR;€wCWDzF6XR>€ukC6Xy%8€yCDi6Xy4:/€CG@PÚ6Xy70€FCt>D6XyL)€"CRC K6Xy,R€C=D-6XyBP€rC6XyM€i=C+6Xy)€C6Xy7€yC+[Dc6Xz)€"CRC K6Xz0€DqDD6Xz~*€jC,D`56X5@x€sC+6XD*€jC,D`56XE@€s+CؐCL6XT15€}C6vDCd6XU9g€xC݉&D:6XAd€rCC 6X U€g@C'@6YR^€4%C;DCn6Yy)€äDR;CU6Yz€’D-6[x~cCLD4z6[Z%CtZ6[ECO F6[86mCA(s6[L~ރCF:B6[+NEhjCdE6[+r8 mC€C-D6`RU€DCME\z6`S~€1^Ey6`Sw8€OgCѲC6`SwG€P+C6`V€#CE86`Vu€bCF$؉6`Vl€ACEκ6`yt€;DE6`y's€RCD6`yw€Fz^ì9FW6,{W|CIKE:6,hì+F6S@{C (16Sg~zI @FeY6S/}zCU) F6Su|C5LED6Skze+ÊFvى6y+|#C%4Foc6z }NCi;F-6z|uC4p Em6zF}CS1F86zm$z3BPE<6z;{"6|@C;EF#p6)|C56hQzBE6ǽ|n_C&eFwߟ6s|C6E6d*zlBT\Ek6|C=NEl6 pz:CiB+26qz-C{B{6"| C/wD6ozB E6|"C<єF;6*pz'Bl6hyB16iyB`G6Č6vCKDĭ6QwB鵉F6vB!F6,By,EBiFLL6,x\B E)6,xwBfF6,xB.F}6,xbB'AE?6,1xqBEno6/~fyB 6S,yB׺F 6Sx4BWFDS;6S]wBOF 6SydBOFÿ'6SxhBaD6S%xBF 6z<yiBkF8{6zNxBtFX#6zlx=|BFI$6zyEB'FW6zxBږFB\6zxB.Ei6xJBBEEP6»xYB=EK6{}CozF6k~!Cr.Fp60(}-dC_~Fjt6m}Cs 0Fi6T}`CzH"D 6,1~ Cx(G ?6,X{}OClG6,b}CaFu+6R~LCBEmA6R/~=C&E*6S>~Cm6S:5~CxݟE6S}s Cz D6y$~WCCT6z5x~ 1C}Fl6!~(C{FJA6~MCm6 ، gA6 sBEՠ6  tSBM/F6 2֕rB42E6 2DtBMF6 2s/B.tEǰ6 39_sPBA5D6 3Dr˜BŶSF;`6 3t1soBʆEX6 3zxtACg7F;N6 3tB6 3Hs0)An?EZ/6 3лrB' DjQ6 Y.r^AmEjo6 Y,s&!@6 Y1sADj6 ZIsbB&E56 ZsB=EC6 Zth E>6 Z6sMAfF6 9twBq6 ztBҹFF6 .s1AE6 is"AJȴ6 +rtq&BD6 RFsBW!FA6 ds\B/6 o"teBloE6 mryAƃC6 ̅r9A6 sCgAEY6 2rBeEѓ6 VrA`C6 s"DAGuE@:6 teB[F/6 ˥sAE mH6 r[A0C`o6 .smApRDCr6 ϴs~B#[E#76 ϺtG%A#SFy6 s"DAGuE@:6 >^6 !Z=uB9Fs6 !ZHvjBQGEK6 !ZZvDB(F(6 !zDv6CH6 !3-u:BҖEF<6 !8Ju{BFԡ6 !Bn7>ӅG%^6 +Z{'nB[E6 +Z of[AdF6 +ZCpA*EPc6 +ZaKnY:A{F=u6 +Z=m#A4=q6 +ZTp4AsNFG6 +g*05m>@E6 +u'RmQ9@6 +p_B.F6 +FoBF&i(6 +mBEdF6 +nAnE wC6 +"mBE6 +N_nTAFN!6 +oaB-zF7m6 +qn~A}E;E6 +|ppBDg6 +mJBUEk6 ++pn+F#6 +[p* A"FmiA!FmA6 +pAFO6 0 r:A6 02pAKD6 02wr!Ar6 02FrB^6 02,qyBE6 02P>q8B @EN6 022-qjB:lEq#BAOE 6 0/qpBDis6 0"rACԍB6 0^Cr{BEف6 0/rAF%r6 0CqQB.F?6 0$qAlFrZ6 02޴qAnFu6 0oxpAME L6 0r1BRuEo6 0pA!Ev6 0B q~BBF6 0ϝpALEV6 0ϞUp-BZE-6 0r%A`B6 0r1BRuEo6 07 qJBdD-26 0"YrA&ExS6 0C pAODX(n6 0RӦq;AMAF&6 0Tr0A F6 0;p*B6E`6 0qBFw6 0GqB]uFR؜6 ?&rRA6 ?rJAئC666ffuAr661Zf+KD2B66Gf7њDO66 f,IED66fO0AC/665fA/66+fLAWEn66&f:ȼD+66*_f-BD;DO66fT=Ezm66f@ADZ66ffSrA_%EK W66fNGdDYU66Af9dB?D66 fABE.zF66fQA D 66f,‹] Ej̹66f;Bn,DUV66~f)#@(wC966,>f-x15D664fB`A@BE;661f*Eq66&vf/A-66rf:CqOE 66CfA 8lEf67  f&B DOZ67fIAlDh67@f0AB167RfqߵFd 67Tf9Al Dm672f,V@ؤSCc6]Qf4&*`D*6]+If.A\DE6]f=5hE;6],f,?Y]D@B6]f+@E zP6]6fEB4zE*6]@f9AE56]*f+A'|6]fFLBEF6]0f8fA6DM6]f7!Aѕ6]fA¨iEU6]i9fuA^C6]f0h?vD6]^fRACܼ6^%f4fBȤE]J?6^f(/ E6^ f2@TC6^ f9AEo_6^,f-B' D06^"fC D -66Nf=BHƎC566fDLEP66f5AfC66{f?A}O66f'A9:DM66f8'BLiD+66f)?&̝E0P67  f&wBD67f(~ E@{67@Tf-BD*M67Rzf?Au@67y9f4!A0DC6] ef'B|D(6]f2Am@l{6]!f-AC6]Lf?,AwIAw36]1f9ZB DDl 6]f:QBDr6]f7A^D6]f6 AvC;E6]ef)¾DӒ6]f86B6!D6]f'„юD_b6^# f AhB6^Pf*@İD(6^f3@A9D(d6^Qf1ABާ6|f.GÊoE96f>6?lC-6fCAt6f4VXDCy6f6A~A1O6f:!ME 6fNA;6f-=@,D6f,A~D.6f9AL)D6Lf? A`B6 f,f&0EFE63 f AhB6f4RBLD#yI6 f!A,CTt(6f6AC6sf6sDI6Ff6yBDL61f-f@"PD6qf4AAHD ^N6f0AC6f5A6f'ADD6&f2AHCDg"6-f*@~CJ62f"gABb6ӹf3A@7D *6f)h7DΟ"6 Pf4_A5?6Mf?LAA#6!}f7B;JD&H66Gf4AGEk66f)9@dC J66f@nsG66Jf/AjCP66ifPB EBM[767R"tfA D=U67fe^ABF"67fc\? \F,9`67ЗfPBiiy6]fYA4LE/6]FfCAMEa6])fAsҏF*u6]BmEr@6ՓfPB[\Db 6R4f|vA۸D 6mf\qA`Et6fOAC6f)@CѰN6ofQHBA196uf)@D>6wfoACF!6:Rf`OADE6\Oef}CA6cfSAD6.f(@1@y66fA5?66 fA@F&t66L 5h8@Ejn6@ h{A}EH6N~ hbA RE6N h@D66OJ h@A3pE*6OU hA ލD66g Hh$AD+6v h$@I7L6 h$ZC'F6$C yhoAQA&6' XhECQF~ 6' yhCF3^F xHh>CF 6> +h?%6> Gg+CoqEI 6> gBQD6> gB_EEV 6> h@TE6?I gPAD]Y6@) gC zE&6@ gãERn6@ gEra6@ hC!FjB6@ gCA$F6@ g9E6AH h 3D0NFQm16AI geA6AJ gܟCNVREY6At @goAV6BP gA8{6B gBsE?6B *gSME6N~ ph6@Ez;`6N ulh6CF6N %h$8±E +6N ŸgA 6N agyA6N Lg9@ D6N Tg.B\E6N :gBB*F/16N x>hlAD6O 5hSF 6O gBFs6O ӃgCF26OJ Zh@pZ@6O h kB{E6O (g5'E 6O g%C`ZE[56O ƨgApeEI6P~ h Ɠ~E]6P g QkEVJ6P _h7AAF;6_ g5A5QD6` hVSAUm AYDq6fkBWFX6fHl AHEy6f l/aA#!Fr6g*;mAAG~Fah6g7Yk]AE,~6g8k}AVA6gV (kA"\E',6i/ XMkQA!E6u-~m(AK &F6ulULAejnF7s6u[kG@YF,sb6v&l ]@m!DpÕ6vjlAF 6v =k)Ei)6wkB-[E6w kAvaFx6wmlAfZF/L6wԋkNA+Eq6wlW(AFDz6wr%lA[FN6wVkPAD 6ȍ/kkA=E5E/6ȍXl>A'E6Ȏ:,mKWADd6ȎGl[ AeLFWv6ȎHïkǥA$B6ȨElAu65P\lAƈEX06 +mX@ƨ6' juZAA4D6* @j9AEzl6> DjAO6? jARDz6? j0Ai:D\6N 6jm(A.ܩE,6Ot DjAO6Pz j,A8dsE&06Q djAkMeEt6f j[ALzDg6f ijxABA,E*m6u 3jAzE04q6u jARDz6u vjiAXIDsJ6x- jAE!6ɍ joAe.E[6ɍ jAەD`L6'I iADeo6'L siAAE!g6'R iaAvCW6( ivB6) iAXC|m6> inAIHCٷ6> OiAdD˵6@ i@Al6A iu@9D_6B iA6NV iA6N] iؘAYDl{6Nc 9i@6Ne %iAJuD6O# gi1AD:h6P /iuAܱD)6e %iADQt6e iA~DlY6g MiA2 DD6i irF;6um i+@Ĝ6w @iA6ˆf iIA{.D+ŏ6ˎ iA J6ː i@+BD}w6ː i?AnD~a-6\ ei*BeD)6' fi!A6', eiBrC6'T dcirBD@6'X giC&76> eiTBWD Zj6@g ei\B Cg6N< eiBrC6Nd diBmD¡6O4 miB OAu6O i,i B/s6P jFiATC)6e g,iBD6u5 fiBiCm6uL e8iB DD6ut giB5Ci6ux miB ?rm6u fiB!6vD iix?D|!6Ќ giIBWDnQ6Ў miBMY@36'( }i~BDb6( }iAt6(+ yZiAX6(B }Ki{+Au6) RiqBP&E(6 6> }iAt6> {iA=4C1%6@\ }Hi}ADժy6@] |i}AYhDĦ:6@n si"ADA6B iTA/6N$ }wi}A曦6N8 }i}ADB6NQ ~iAD~6N i}/A?HEm6O$ ~iB%6O; z+iAbjD{ֿ6P riADP6e }iAʾw6e uiABַ16gl }iuAC>o6g{ iTA/6g~ | i}ʅD#6i iTA/6uH }i|AղC6ua riADP6u ~iAD~6v4 iBy DS6x }i{}?E6 i2A7"EKv6'` ifARj6'k i9A"M6' i[-ADx"E\6(9 2i`ŽEO6(B iiPA0z6(C i9A"M6) ihAP J6* +iiA6? id8AKxDSa6N$ ihLA;6N i5AgE6N qiF A[~EW6O+ iiA;,Cm[6OI Qi[AEm6O{ iMQAw336_V id:AD6y6f (ie@@~Db!6gm icAWC"6u iitAl'B6ڍ ifT^=7Df6 q~joAn6'Z Lj AD6'\ 6?jAaCĹ56' ]jAAذDX!6(| Jj A.EU6> Ej AYJED{6@ QTj`A|De6OQ >jAG6h5 c3jA:DŮ6ue @KjbAUC6uz UgjGAGDCΘ6v Pj$AoEEX1C6܌ YjWAWEm%Q6܏E djAHhDm6 viA SEf6'k ui>-AP6'n h@=rEE6' Ki;CAj̈́D 6' ai@&E6' h @6' Lh@MC66(: Lh@q&6(B iaA =6(C &iPAoEsm6(D ;i%A4DB~6(E h@E?Z6) liA-/68F 'iA E6> %i VA%E6> Hi-*AHL[E6N~ ߗhXAG;6N i%A36N h@宖F3~Z6OS 9i)A&E~6OT h+@EtF6OU hA&fEw6_V 5i$AA;DB6e iBAeE" 6e i+AZEvQ6fa iTlA6u h@aE$R6ve ߗhXAG;6߆f ˷iAPHEu6ߍ -iAB]D7D6', diB {Cp6*L dzixB{6N diB ΟCy\6N doiB 6O! dViB&D*6v dpiBVCm6v1 d>iB.]C6\ iig8D$6'1 g-i AĝlD6'X g/i;BN6($ fiB*Cz6) gEiANCD6@i d6iB66NA igimAD6NG fiMBC6O j8iBs9C6P hiRB\D6e iiYB#6uQ giBTC$6v1 giB*)D8V6vD fi)BC66挸 jiTB,tBi*6 dsi_B|B,*6 gB*FbE 6U gAf%B6' g@nDc l6' gg̈>F1|6' g CEEW6' gUCxE 6' gSCRE6' g™Eu-;6' 4gw|DI6( sg.A_E ,6(q gaA\ EnzP6(t gA@Q6(u "gB]sERi6(v VgϵBH/E} 6(w gœ,Et6)m agՕC/l5E 6)n hsCEt6) g_NE6) 6gAչ DV&6*Q (gLB\MrD_6*W $gAD+6*^ MgA%BCk6*` gǟ2MD=E6*a +gBB}[C{6*h =g˷B,D%6*p kgBD?T68 y[hoA J6> 0gk{EK~6> GgׇE76> gyTE3h6> gCd#E%6?I gCnEw6@) g)C!oEt6@ gUJEK6@ gKû>fEO6@ gWA,Enr6@ gV[EGm6@ gC 1E$g6@ gC Eɀ6@ gAha@i6AH sh#DuFZ6AI gv¨zE6AJ xg\"E6At gcCE/6B gA6B g3AD16N gAНE}+6N _gdEh6N "gҸCFEH6N xgAvEf6N 9gA&F6O gjEU6O ig[1)YF,6OJ .ho@6O gÖ#E@6O tg~B$JEL6O IgƘE6O g>A݌E]O_6P AgȻB^/E}6P HuhiA6_ hzA>2Cb6e g8A%C{_6e (gÌbS!E6e YgCGEk6e gCUEDC6e gŠEw=6fY gBEw 6g9 gcACc<6g @gA@E6g gC+EQ6g g$CkHEKv6g gB1Be6g gBo"E026g !gCV^EJR6g ?g?E6g gJBE?W6g gKWD6hX pgC;F | 6hY ugnEU6hZ gBE:q6h ȉgAD66i vgB|EF6u h+P@, CI6u gEf6u g۸E\6u gBsVgF<6u sg}F }6u gcUE_K6v hC*E1,.6v, gB$GE;#6v hBo(D#6v g E&6v +giBU"EF6v gUC%\EQ6w gAA¼j6w 3gnCE6 gƨ.E1u6 igCQF6 ggӬEob6 gCmE׏6i 4gŽwE'6< gB&}E0B6I g¡E+m6鎬 g˱²E/;K6鎭 g`kTEL6鎱 g@D6鎲 gȶšt5EO46 g¯EL6 g=cEo6 gOD 6h /gD1Fi6i gzÌGE6j XgBZDL6鏔 4gՉB=E͛6p gCEE[H/6'f +&iBJ1Dy6' jAEq\6(| 6?jAaCĹ56) j/A6? ),iAqD6?% j/A6@ 1iwA\C6@Q jA?nDI6@ "jwAĶC?6Nv , 6iKxAsCL6@] ilB$9Dף6N8 iiA6N dib«WEDzV6O+ iaAaFEj6O: ihlAacDji6gm ijwBmME]e6u &ie#7E"n6} 5ijWA2UE]6'S ic@D6'^ ]jnA DgN6'_ j$AD_^6, Ii?WC=26> iAH6> iAD6> |i@}En\6? jAEH6@r j@AD6@ jAI6@ i B E{6Aj Xip@ӻDu6Nc io@D6Ne iCS6Nm jA+6Nn jCADn6O1 j@A@Dri6OM %iAx%DS6S i@?E6e i@'C1j6f PiAF-6fa iMQAw336f jBAgE66g iACڛ6g yi鬥D96us i@'Dذ6u j@A@Dri6u iGEU6v jB^6vA cj-ACE6{ 6i&AOʟC$)6 i@1E$p6 ^iAzEwtx6 j AvDy76 6iB DV6 jAsDj6? jPAo6(| Gj AglD}D6) j Ab6?L j/A6? fjPBdDI6P M_j Al6)~ j0+A"3D6? ojEAa+Ed6@Q j(A-EDr56N jAARD 6N j+DACt6f j5Ad!E"m6f jGoAlZD}@6ga ej,A`D6u jXA6u Fj9rAEa6 ojEAa+Ed6 j<3A&E~6 rjAEL6 yDjEAFDI6'] uFjBA6' [jOADn6) } j[A3DXy6? }3jA6D֦m6? q~joAn6Nm RjA[EOs6f jAlD6f jtDӨ6u EjB:DgZ}6w3 jB 0D:e6w }jDA/6 j(AĥDe}6 uFjADRl 6 jAI6' ii Bh/DL6' eiA\D, 6' lixAʹ_C)6' eiHADTi6'# rdi0ADj6' eri1A0B< 6(+ ggiAnUCÂ?6( eiAC 6) kViAѕ6) eiA B6) inDA{&C6* eiA(CN6* fi BB蚁6* f#iBKC{6* eiACis`6* eiAVB;6*# eiB D r6*K mi|AxBWb6> viAh(DLo6? giBuDS6N" fiUB 8Cןz6N$ JipBEE6N% h5iTB>oDp6N* ni_ACCFk6N. eiACo6N/ iiAD^6N iiA6e yeiAPC(6e vZiA6u2 giAC6u4 }miyACC/6u5 eiFA"|DbK6u8 oi]AD%[6u> fiA;6uC rWi.ADmG6u giA0C86u kiA؛5D)!6u eiA^6vE siADC6 gFAM6'G a1k>A|6' kAf2E6' jԘA"6' 6k AsE36' jAUEj6' .mkkAWtF6)j ‰j AC,6) jߗA?PE6? jAD6@ jAMD6@H مjAIAEF56@O mjXAE{6@U -jsA<6@ jAX6N @jAE6N jArDȮ6N ik2AREv6N qj@AD6O vkAE+6P| jHAF:1E56P vkAE+6P jABE(L\6f cjA9E9r6g UjVA#6gX jUA`EEOs6g_ ˶j7A5E6ge 3jAEX 6g zAkAE06h >k|ADrD6u j AE@'6u jADwz&6u r`k'A0E#6v bk {klA76% hC0Gi6% qh.WANګF[6% hI!?t$FY6% Qh1A@F?B6%' h@BEqa6%8 y5hoASBU)6%8 YhHAPFS6%8 xQhnAC6%@ gAKENiX6%AH gAF\BHa6%O h`A#6%P~ h&†QWE,6%_ hM@MeF/6%_ h"ŸbFH#=6%_ Ah5AEs6%` h;A֚G{6%` RhFw{6%e h@wCh6%g Eh;SAE6%hZ hAEY66%v, }gBøE.6% =hDHB.EB6% VhKACQ"6% 1gh8DAC5E6% BhC/¤@F:6% Ih:&>QE6% nh|A UB+6% }Uh^AN^E>D6% h1gAnE<6%! xhoAdZ6%& %hQeA%EO6%( zhAE6% h3ATD=F6%! h4UAE/6%6 SDhNܤ3F(^6%8 /hfFA6%F  hRAZEY6%H Gh%BmF&56/ 9rh7MAy6/ h1vAS\QFDv6/8 'h8lAVpEq6/_ h)&A+6/ Hh4QACu69]l^AdE46wglA6UlWA C`fso-frameworkd-0.10.1/etc/freesmartphone/ogsmd/la.db000066400000000000000000002271241174525413000223420ustar00rootroot00000000000000Hw€.D"=`Hm h2³GJOHKd{V3ð7H#UHS€vD^QGyH €c?tnGoH arHH1 zi~QąDRH2 mj>jF@}H8 9j!*F޻H9 ^Ci3CswE2H: hD6FLH; =hDFIH< _inAdDSH> >iAzEa̴HD l)L©G,eH€8D"CHBS/BC_C@B4BoEB?-@8CnE  BC1@Cb@#!EzB?oΤ#HAB!m5D 8dE+B!lDEB!8kiD װD mB!lDEsfB!znoD7vE B"Dm~DiG B"c}}"D\3B"~ sRD8Fi5B!mlID-E$,9B!)kD$DBGB!qD DB"twD4iFYB%F@ lBB%A@ :9B BqB/@->C~E_NtB3_@/@A!(CuB@+zeD)'#B&EnBD @1:BăgAIFΒ1FTB6@.zFVB3kS AClFS?LF%BSF)B>uABLFF*B=ACfFF-B9A_7CZGKEFAB=AYCB߄FDB=wA]IDF]!F'B=cAP'2KFSF'B9ACGF'B= A W ABgGFvB>AGDvGRFB;A7B"UGr#B3jAN7F#B3(ANAyC>B9 A"CHUB3IAO2AB)0AlCǰFB*GA@GDGCB2A0DNm#F *B3,AXB1+B33ABBѶE9B4sAdG. B5A%A7G!B55PACbYFnB3 AhB E1B.dADCqC*B/AC[Go)ߕB/,cA}CpF*B3AGB*EG= B4AqBRB7c*An IG@=GB6GABp>F bB1IAATGWcB1xA)yGJSB0A8BXPFA@B2'AشCE#@B8 A=B%EF?B,A(ĈGXB8(AH=HQgB6A\C{0mEblB6A\BFEB6A] QCD}DBd=B6A\CG2B7{fAXtAuG~uB6A\iB\Bٕ0 B6A\_BP B6A\vBDAי@ B6A\-Br7C{C B6A\BD3S B6 A\~B> B6A\,Ba@ H B6A\{BUR B6A\5B.Ǯ B6SA\C DP+ ?B6/A\8CD @B8SASeBI7 BB6A] QCD}DBd= B6A\B0eDW` B6A\BDu B6A\:BYD3^B6/A\8CD`B89BASB|AaB6A] QCD}DBd=dB89VAS{BCA~2B6 A\BD9B6A\hB?D?zB6A\!BlA%`B7l7AX9}GqKdB89VAS{BCA~B6A\BC B6 A\BC $B6DA\BmA/,!B8B1AS+B*~D63*B6A\ BfA7Δ_B7AVKsąG4`B6WA\CD% b?BA C\uB6A\CWB8B1AS+B*~D6B:A2|OPQVB'7AG_AG=D;jB6A\B=bսB7OAY9qC|G}=վB6A[VCMF{z׿B6}A9?B}E&2nB'BAG^%A2DB'AGvAB uB'=8AEAR N_B9AE!B;a@ҖDnFx^B=6d@FCDqZ(B=>A&5omG?B=:ACCJN@ģB;@xsDAPFPB=A/C}~E0Y9B=AC_NEB=A :CF= B;=@DɅF: xB;@?CF̘ qB=eAUdGi B=A 4-D$kEA4B=TA C۹F B;\@NC)D%B=&A CF<#a2B=iA"CaC18B=wAJC CɺB;vA8*SH:B=\!AQ4G~B=\ADtF\BB=A3CUGYCB=A=CФ{FB=wAV#D3Do%jB=wAV#D3Do%jLBB=SAGCWB=BAnCLB=A0C bB=A&CĘs B>@*dCNFB;@~CsEB=tAXD+C]R)B=nAjqCl@/VB=w4AUCҴ6B=hADGRDXB;6AyCک FGLB<-6ADV*GV%B=A?EwG*B>@WòG2B=9(A7` GG_6B<@(5C?G<[7oB=_A5@DOd7B=A@D$: B=AdDr=:B;A#D ExmFPB=@vCGkJ8B=AD>G(GhhB=]A^Cu0B=\bA;C3FmyB=AyDGY$Fl}B=A :DQGK²B=AeDJGB8A C:+Ah:B=-A D!G`qB>IA CGKB=AwA:CDM/j-B=ACƼ)MB<-A C݁ B>WAaC{rB:h@պ}CF!>sB: .@ԩC;FD_tB:@vD GGvB;W@嬋DTxB9@rDEsnuZB=A D ͅF-\B=SAqBA"G"p_B=A CD *Fd`B=(AC(F#\bB=A .!DzEFZhB=A3D!OF <iB=miAUJ)tGjBA|j-C#0HaBFOAhCȴtBFA#C<]DoEBG}ACR=?Z tBFA^pCZ |BFA^CBGoA;CBGn}ACBC  0BFAU:bFpF :BGpA[M;5G NBF2AO!DE G9 XBFYAQGkC lBFAT\"mGU1)%NBF#AqtCvGJ5 BG-Aww%PG54BG,AFG=6X5>BGA;a0Gb5pBFA2DF5BHAzSClGI68BH5A~9)C|(8HBHMAlJCD: @BHKAdDDF6 @2BH_)AeCV@G>BGaArcD] GaGpBF]A6C^CĻPBHbAkáF߰P(BHMApDyUFxNU BHTAfCcRC:UBH;AfCC4EωzUBH3Af CMJE4UVG|msZBFFAHijXGBY|BFA}H BF5AyŜ~H*BDAo_Cs#@D(BGA=ŰoHVCBF·A/CCBFDACpE4 xBD´AC[ BFA6CvkE.BB{ACcTBDɵAدCgyBDACPBGvACs'hBD­AC[d?"c]BDdALCBH^AfsCc C ]BFAՖCBDCAUPCcj=BEAݒCCWBD͘A&CXBHXAf6CBFACs2C'z$BDڔA_-CcD4BD2AaCtqJBFAsC_;dBDACBDACBF71ACkd-BFdA8C,C6LBFH A-nCCMBHjAgkCR{F6NBHXAjW CFǯ]BHTAiCd]kBH[AfDsC!_E3VpBHZAfeACoDqBHjAf4C{E%}rBHlAf[B'EzBH#nAfMCBHqAeՠCSFBH_AgQCzFj6BHxAeBEF`BHyAey~C F>gBFA6CE.taBH~%Ae?#CESBHzAfCZElBHAerCp{F uBHjAohHCC{E]BHNAnDWG|BH)Au!QC$رFBFPoACBFOMACD BE AfCvF}BG?AC @MSTBDCAUPCcj=BDͷAOC(D;BHWUAfMrCyBDaWA>C9 DQBE AC8>BHiAfS!C\EKBHdVAoڟC7LBGAdG>fNBG}AAD+GOBGRA}P/?G;PBGdA~)XGCbQBGANDp5G|BGAnw F BFA B[|G0XBFAnĂPG,BF1A@\G%4BFkA DuGkBFXADgMGqBFAtĨGOGBG1A? BFA]IC\ 'BEOAcC ;BH]PAe6C? BD´AC[  BHM!Aqu|C( 9BH^AfsCc C ] BD­AC[d?"c] BD´AC[ RBH VAtbC;hBFAA0C/*B?RXlBFATA3Cm BDA0CdޮCr{hBGtAC 8BB"BGtAC 8BB"@BEAzCW3uDBEAzCW3uBFEsAjCrBDA#YCP?BFBACf%A9BFACtBFEAlCC'xBFɒA2 C@OyBH Af7CBDA_"Cu6C{BH Af7CBF=ACE BGY!A;6CV1BE IAثCܒEĈ[BDۃA͋Cx,zC\BDA CdNBDAAACXCC"BGY!A;6CV#yBHqAeCtD(BGfA&CVvC')BDhACD*BDAAACXCC-6BDhACD/BHTAiCd]/BFQA C 1dBHTAiCd]1BBAC4tE!2BFCAC3 BF>AjCvD@8BBfAߦCLDD$59 BFk9ACyDH:BH]PAe6C?; BERACXC)7<BH^Af9BBEA*BH\ AgDCWqE 6BHLABQCN@~I BE;A`CιtF HBH}[AnD7Gv BEYAbC|C(XBHdAfC[7FBI[Ay(D?GyBH#A\E\FBHA-6 G-BEOAE#*G@BEAE8NGBF]AxB FބBHjUA|)JhG:BHA}_GBEsA C>FO3BIArgDXBGwzBFzABDr FBFAdBFBBFpAqD%FϢBFdA"4GS BI"UAqü&GZ. BHdbAf6)C*fF?![BHAk}CnIFBHqAjBF^BHT'AgYCƳELBH#rBDA>xCwE9{BF2ANC#BApAF|HBBV,ACļ3B@.AVE4B@AD6G.d85B@}ABMF0>6B@gA:Fn`7B@ƵABCbpFKE8B@8ACsFܗB@åAIBu?B@&AuuDGi@BAtA5DƑGvxBBAABȠGCBAAB]FNDB@A;CPFFBBA&DqZG7GBA\AGk:JBAACL'GH7B@AUCJBB@AC B4BBZACeAp 5B@A1C#_Ba BB@kABCx1BBZACpCBBZACp7B@AhC"$BXʃB@A6D}G,BABAĢGqXBAHAA7D{6*GXBA6IAuDqAF( BEsAgD FST BEAăGBA.A4 C[nGR BBNLACHתB@AZD"G(BAACkQG}WBDA7 D#E?-BBgiA_CvDۿbB@ACF4eB@AgI=ήFsgBA\AaID%FoBE}A.UCѢEǗtBBA:oD GjGCzBA@8ACĎFSB@ApuBzFBAA@ HBBPACoEBA=AQDUGBB*A?C3F$hBD]A(D =DgwBA|AmCHD]BAAhiC5qA*geBBiGACAUBiBBiKAC-'ABAAJCQ# BANAPCACE%$BAACHDBA+AQCRG>wˆBA0AE GMBBA']G4BB"AXpİ}HFyBAAB [HKB@ACQB@AgCwA;B=qATDMNYB=%A|CөDGMB@AC:D/PB@'AڠCtEDzQB@ZAuD/E ;B@FACE  B>{AR+C B>{AR+C B@VACyDFv !B@ACHDP "B@AC8A6B@A(bCUE' B@XA4DO4F)oB@AgCwA;B@ĺAgC.B>oA>DA CQ0B>tA>DkCύ.B<8AwC=ENB.B< AD}G+(.B>ACF).B=ܻAK9NF{.BD FgB0B?=A'C`F 0B?_A3BrGp.0B@Y0AD@F(R0B?AxꈬG CQ0B>ACNTG5O0B?ATC\Fi5\B=jA^DL~xFۓ?5aB; A{iDOF;B@KACBGFEuBiB@zAŤD%E wBkB@A\J&EGB>{AR+CHGB?MAM qDoLB<6;AvCeNEO$LBAFDNGO%B?@0AP`CiEnSO&B?2ANBFO'B=JAVxZDzGk8/O(B>uA>DxBO)B>3ASwD3GzO*B?"GAP?JCˇF6|O+B?+AI[D^ G ycB>{AR+C~B>{AR+CB=qATDMN$B=qATDMNDB<AD EB<AD FB<AD 9B<AD :B<AD P B>A>DwP@B>A>DwP`B>ϟABgDĎGB=A6.&F B=RABOF͡x B=pA59FG6u0B=ADG:S@B8AI&G0B>2A?DLrGR@B=ARDQ3Fd8B@AC,E4q;gBA&AC`bEo"hB@ANCmD2ywiB@DAV#B@7FAlB@vA` GFzB@ABޒEp1BAzAЯBlF B@/AjCFEaB@AB*?EJB@$AC؞EIB@!A>iEƠHB@A DFBAzA)DF=B@xA(C?G\ B@ACE09:B@%AVUC-FbB@5ACFZGB@A)CF-B@EAC`D B9DCTGD<fBG*/ BaA =BC5B]jA'YAĜ8B[:A2AB!RBaA C F<n vB`-A!BF!Z  2Ba+Ai P%G-JBaA;áFBmsAzBGDMOBmr5AڅAC!Bmx ABB}S~AI)CG[/BoNA,@BP#G>BpyA2C#UD[X Bph1A2HC!m19B}A+^æhGI2eB}|LA$F!3.BqR A3BY{G[4YBrA5.X¢d0Gec5BoA&fCG F6"BplA2QC7DZZG6BoSA(sB$e0FŻ07Bp#A0xC$*FN 77yBubA0BGH)8ABoA."CF?9mB|OA:G:9B|5UA +%̊Gb!:dBoA)@ =:B|A bGذ<Bo`"A'AσB}r-A%>C7L?B}r-A%>C7L?IBoA&&C;EI?}B}r-A%>C7L@B}r-A%>C7L@uBzA&,HH?/A=B|&gABC$G;UABoZ>A)ƴC4FevBpA3CI)D,qAx/Bo@A' @wx2BoOA'#-BpJAAjE#.BpAwGB+EQ:S#/Bp'AgJB<Dڜ#BpA{BgD P?BzvAĿY*E8BZtAeCBF6BZ}AQ1BJqA BZ:AXGFDA BZaAjG,BZ}AQ1BJqA 4BZ3A CwCuBZ}AQ1BJqABk;B#ǫGId BfB oI"BjBFCL B^>BcBZ"Dq%B^mGBÄ14Eۅ5B^sBAc'E HB^6BmC.8BnAuAcFHBo\A]IEVBo A1Ba3C=)^Bo$ApB]F]svB^XlB/BBp!AZC6DLB^wB3C yE-B^UhBCeD&BpAxBB^6BC.RA䎲1B^gBJkC D9{:B^{ B޺‘*F( ;B^YBuwD "FP^=B^j BQC튅EW>B^BvB׎E2 CB^gBb BDB^6BnC/k@SNB^BC FdBjZ3BF]C9tBo*ABE^YyBo AB]TBBn?ASB+FBo AB]TBBjBFսB)yxBnABFBo%AزLa$BoegABAwAƏ(BoeAA@4A)BoeeAOo@b0BnA6BB Cn@Bo,AV)WG wDBoA@mPXChHBoɗA@O(B!LBM8HBCD(`BoĤA@;VAdBoğA@}B_iBozAA7lBozAA7pBoeAA'tBoeA\A#BpBM'BgCBkCB"BIE/*BnABMPBM6BqxCEYBnAB_D9BoAiBdC+Bk@5B*$BfDBnA|BF-G BoAu@!B3$BoƮAF@˜Db((Bo8A{A;hdAҪLBoƢA?A@KBo-AA+BMB{C8jE)vBoaAB3BncA_A!BoN'AAp}D9)BoGAV@D BoAnAEBoBA4@Cc Bi;BUBJRBoKA!@ CVBoMA@DCU\BoA G1`BoOA@wdBoMA@&qBoBA4@CctBMB,UCE%xBMB,UCE%BoaAMBtB'Bo*AAF$BnRAuBByBoȹAS@ߕBo=A@hsBiYBEBCBo)A+%Al&BB{Bn^bAB,B  Bn^iAB7HBoŤA!1AA0QBM:BC!`BoaAMBtB'hBl{oAw+BIC "pBmAW }Gq7|Bo€AB{CiBo)xAA;dBlBhġiH|QBoqA BnB9A 5BA5 BnCfABC BnDAB.DL (BiBATPDq0| ,BioBID 4BnVmA$B[DZ 8BnS\A9BwL \Bo)xAA;d xBoeA AB" BoWA@.CxJ BokAARu Bo AADC5 BnAB>Cl BoeYAA BnA2 B BnYA=Bѩ^Dqw BnAA9BяCY Bn]ABH1 BoewA6B" BljBę$H|[ BiBB+ BnO.A BE2.5 Bn.AeBO2Gk &BMBD+F ,Bo59AMA] 8BoBA@Q xBoA,AMAX BLʃB5C CU BLB9C[# BoA=ÃCAW BoA@A&%B# BnFABXiC BnEMABC4 BpA}ABB'» Bo59AMA] Bog@AAeBf BoaAB3 @BoAMAWC DBoAA;F=W HBo`DAAsl PBodAAMo \BoMA A dXA'C BoADBnHArBȞ!B@XBnQAB"D\BnABD_zlBo4ABojAB]FBoABZB'BBoABZB'BBo4ABoelAAA?^U@ߢBoepAFA ^AX*9BoepAFA ^AX*9Bo]A*A^QD@Bn}A&BcYCBnABԥ`BnAB\CD +BoetAKB?@yBoexAaB >A(BojAB]F,Bn;AUBEpDBo7~AA BLBo\?ALA GPBo] A2@C-iTBoSA@XBoeABB @|Bo*AvApFΨ^BnA$B_C>BnRAϜBCA *BnAB2vCBoRAA/BoAYA5?Boe^AAk AiwBoebA`1>ABoe^AAk AiwBoebA`1>ABodAAcBzBnAnB&C],Bo`AA3DqVBoAA1B/8GFNBoeWA4AA`Boe:AAs?+BnABBnAl"B;Fw~Bo59AMA] BnNfAgB]DK$BnSA`Bq'PBoaAMBtB'`BoAABo`A1AsmBoexAnBAT@BnAƥBEDBnAÃB D1׶BnCAȭB>VBoAA9BJ5C PBoAkBQBoe^AAk AiwBnmAAC2UBo9FA$HscBn~A !4GdBoA@rABfBmAĻАGƚhBoA@;AflBMwBoVA aA}FTBoe^A@%C&BiBABD}BnEABkC#]BnCAxB B\-BoewA6B"BoAYA5?BnAdUBC@BnAxBBnA{,Bu?BnAkB4BoH7ApA@Bo4ADBiBB fCz!HBn&ABC65BTWA!IEvH!BTdA Ġ6H}1BUkA*C DBV ApC% BWA\YCzDz. BWAܮCMUA BWkA ;DFW BWACRXE|YBW`A* EeBWA CvADBYZrA-D wGBUA B8GBPxtAB FBTAA@BHGBQHABF?BPG%ACFBRAɪGBPrA@B^G+BPA`O7G+f+BPAi`EG+BM3AбD0tFf+BPADCܪAFX+BKaA1C:+BOA;4G+BQL AJC GFNyVBP ABGl4vyXBPABy\BQ<AuZYG/:?ydBQ AC{GTOysBQRoACVFZytBQZA^B\yBQ%AVCGj|BQAhC%E|:|BQADG!|BQuiAChNGRͽ|BQacAQBhQGf|BQfAhCQ(Fϗ|BPABsF}4BL!Aa9B5FA"}?BLI;ACF&}GBL]rA BڡYG1}BLw{Ai&FY}BLAI EA/BP #AxyG:BOA6\AvGJCBOAgo GuWBPA>IgGTcBP A@BٳC"7BQCQApB?ƌBP?APCFhBPwA GBP5AA+GCBPZAUs'?G9E}BKA)B)MFٶBKG&AD yFi*BKA&+CG,BKtACTE4Fs̫mBKAsC"0!BW XAbBD PBW XAbBD PBKAy čɾH|BI]AQC{aHBIACBI A޷CBOBWAr}BtBITA>C)DJ xBIpACׂD HBI]AQC{aHlBIOAtCCpBIQAvC.CBP#AGBY@E-+Q BI ACD BIACaH (BHAyC 7 BI uACC4BHOACrqdBIAʹCqC{zhBIAClgBI A_C*#C$BNPiAB BNPbAYB.?W}BOA5DC3DlBHAŠCq:LDdPBI+AgBP-FK SBHFA CMEHVBHŢACğEma( BPۤAC E(A:DHGhVBJԭA$C7E<BKAYCmCBJADy'dBPA#DkF= eBPyAPB F?fBP_AwBE- vBPAC8F!-BP5ABBPBA;)[EBPrABBP$ABW3EN BP#AB(C_BKBJ@A CBJA:DAC{^(BKA]B Fl*`(BLYAABB{ZjqBLAZ»FWOqBL;AQDeάF4BLqUA6B E}_bBLlA%^}FŇhBKAhE[ĞwBL*A ,G]SBK]AjCȣ3ˆBK[A*G$MoBJ'A CǗ$FBBUA)CݽBFnkA|QHdBK@BdBF|A3cCBJAƒ6I+BG @A?CCBNA.CݑBKA0uCBoBKA0uCBoBNA.CݑBLvA|IqH?BJaBABG $BBAʗD*щxBS՜A 7*AzBKA0uCBo BL}@CwC BL1@QCD+ BN@'7F BNߌ@WQB8C#; BP.ABV AuCd9BALA/:C3BRAP0A`BCdA C BGA5}lCQBL/A* ŸͥIBKr6A"ǰjI6B>SA7D8B@kA9xDSKB@kA9xDSZBALA/:C3 BJ@oC9Ctb BJ@ C BJc@CCa BK@(B]lHDn!BM@Aq/EѠ!BMu@C3BGFL!BM~@BڑF"BKK@Bp91Fp_"BK@#BNA@6D)G#BNR@Ü!cFz#BO@+BF5br#BN ,@(mkF#BN(@KC>FL#BN@ C]4Fr#BMq@CԒFP#BN@@YBb~F&$BMf8@tB)y$BM{@BFVR$BM@ B"F$BMӒ@Z.Fh$BM @OC[dMCw؁%BL@ٜB^iE<% BMM@BaF:#%BM@پAWF &BMz@CFF!F&BL!@qCXEVw&BM@TC>"|F&BMgX@!aC"F w'BOFAG1Xw1BQA[KD+TBG,1BRpAYdzEc1 BNAY|:BF]1 BQm Ab{E(#Gq1BS/AP[бG%2 BLjA]CF12 BK6cARBQC@N2BKtA^C A:M.VG4BPIA9\BvGN4BPA$ND46BR^WAX1RC⡬F?6BQAZ "F؆J6BQATPD NGW6BR@AXgD,F 6BSs\ALVB E7j6 BRIAMBBGV>BLH`A.C%@BV-A!CDA F@BV0A B]RDم@BVA U>w1F XiA BWlAFBBV7A)ƁAXBBV>A! GDyBBVC\A$+tFxWCBVA " RGECBVLA B8ڂF;C BU;A +DFL-C BU!/A EBTYA 3D;pFcEBTK9A @NGD@EBSĬABؓG(lEBTAXlGMEBSAz5GCݼEBRYA CRaGGEBRAyG-{EBUvA 1ώ1F؏E BTnAf:TFʁEBTA G@K F|FBUA% C.GF-OFBU3AjCV BQA6DGV BQ@#CG եVBQAwDG׸X BQRAABFEXBP1A"DDGXBPAqBԁ;E`XBOA(YąѥGXBQ$AmQ2GoXBPo!A&%=ątGSXBPC5A*=?c G XBQJ AC EAtYBOA {B& F*`BG&A CF` BFA 6C?NF aBH)AckF mbBIsALG ѼbBI A CE]*bBI A6CFEb BIA TCG@ub BIA 7CWGb BJxiAO8CC߱G9cBEABaG S)cBEA0CIFucBCA eClDzcBE%A/-FF >cBF AC yFcBEAkCFܭc BEANCOD%JcBFAuIF dBG@#0C\DdBG@(C^D؉teBHeA gCECneBHcA p7B˜Ate BHlA BƀC6Ze BHA yB.UD5 eB@kA9xDSgBH@9BPbF(_gBGA^B56?F=gBF,A~JFThBDmABŀVG7ބhBE A~G@hBF0A"oCD8Fubh BDDA _D2!G=hBEMAD"˻FhBEAB_]EbGhBEmA BϡFMhBEYA wUCCVFhBDA HD܏F͊hBDU?A BTGJiBHPAsCAF3"i BHAC F2iBHACgFiBGv5ABENpBD@A$GpBDA /ĕG^7pBC׷AÐK%FpBCXAC}FJpBCqAD5 (GpBCpA CбOFpBCAAD9 Fbp BBJAv OZGp BCdA %C7IFp BC~,AAD;GJp BDÚAB*dGp BD~ANȡcG';pBB7AnC&IG(%pBBFA!0.h:GQpBC!BA Cs5F_pBC*,Ae3D[FpBC=AmD ˋF3pBC/ADE-FpBBADyvGqBBNAȌDK^GD3qBBxAD1FmqBBABOFBqBCAdxDV7!FqBB 6A CKEqBBAGVq BBADU0F.qBBAD,N,G ӨqBB\AD<\GqBBYkACF#qBB$yA$OPqBBaA8DGqBB AV/KG fqBBȪA}AGurBAz#@InCtzF*CrB@~@,oFrB@W@AFarB?@D&KG~rB?:]@BfGmr B?Ci@\0B,F+r B>6@CEssBDABFe3s BDA?BƛFs BCxA|4<7FPsBCA|D5B2FI[sBDceA2@DG3lsBD,A }>"G- tBB (ĂGt B@AwDG t B@AlG9t B?{ AD}mG.tB@[A DC5F,atB?A ,CEKtB>!A›D D|tBAtA d DFItB?vA .D?uB?&ADD Gk;uB@kANzG* u B@ADSGn=uB@RAnDG5uBAAD^ GuBAA!DžG)xuB>t/ADZ;G)(vWB>A$ND4xBC7AKBSFxBCA^B^F٢xBBAe?`FlxBCRA ^WiFTxBCA%D2OF]xBB;!@¨l9G HxBB-G@/CKFږx BAƶ@BD FxSB@kA9xDSyBAPA:jQCݙBAcA>yD .HH B?AEDDVBC A79C[G r.BAsA.čGZZBAGA+NSoGB?+A:m4DG|B? lAH bDpGB?^XA?w6D8G B>tA1>OP# B?BAHDsaGBAmA7D8,GVB@A;;Bp$G B@A9D>AĩRqGB>A)pD[G@B@PA)EGKB@wA:C F03 B@OA?B8G`B@/A9ī\G%B@5A9DMZZFB@A9ICךjFB@tA8C5ENB@ A9'OPBHWA;:|DC{#GfwBGNA8/͎GBIU2ACzG OBLA@;QCFG GBKcA=DFzBJPAEG3BKA;[ĶGM:BMf,ABF|CpF6-BLFA@m/C<~G BM~ABx%C;Fj3BMA2ABBE%BFq$A0CaÚFF}BFAA0CFoBEA1NCKDԞBEA1C:FFt+BETA1CE) BEoA1svCEV' BF-A2BՅEGe BEA2VQ?|FI BFA,9C)GBDA#gCtG0 BE}A3G"ZBFhA1#$G BE:A3ÛDF BD!A5CHG/BC1/A7qICO8GBDVA4uC9 G gBCA7J3C*EbBGA-VFzG BHZA4 EG:GJBHA/FC GBFA"f1 SG BFA#DG$BIBuAA$ND4B>A$ND4nBLH`A.C%BALA/:C3ɡBP.ABVDBAPA:jQCݙFBAPA:jQCݙBAPA:jQCݙ:BAPA:jQCݙBP.ABV(BP.ABVBKA0uCBoBJBA,EfI/sB=}"A#JiDBB3JA,HDB!7B?-A.CQYH N=BGzA*@SIBDcA7`-KHBR:A9F+tHXBPA)B=Bb!BU A!ďG5 BS~A]ęt Hr$BPAB+BJφ@䞡B^}E6-BMd@彰CfF/BM@BQWE0BHZ@87H>x9BEA3CY%F CBGA/1G XBFoAANB @{F vBV.AlHB.~BA:A;C]DRJABFsAaFBQA.BPD6BBM"@CTbĀq"BKk@EEL-BSXAJD`cH1BRAS7SGq4BQATCgBRJ AVCpF"ABQAU@C4eFuCBRwARH2G 3QBI A-PD)GyTBM`^AEYEKG_YBMACf;GH]BNKVA7A H#dBKK-APA}GD?eBLIAPDӌGlfBLM}ASD`G66hBKlA2wEJ08GّeiBJc;A'}4 sG^lBKJRA'"ī`GnBFA4cĮ&H.oBLxA?ҷCT9FpBK8sA?6DQGcqBJA=2CcH sBJSAJ:rD3zGjtBKAGN ĀrG]uBJzA@C]GӝvBJAG*DWGQ{BKoALT\sG|BP0HA5>WĽG}BPٹADK9QHՈBVlA0FAgFBU,A2EcGBNA'(Š4HSBUmA>{k7GYBV=#ArƒxG$BV%AT@VE{BUA)iDzxGBVA3AJbFBVNAC"MF)BUuA%(CHGBSA[BFF{_BYA :@fE BYA$GBWAZ-GBV.AlHB.BElAgKBJD;?BQpAٌØG.JBIA*5{uvHvBQAw$(G6BP)|A)DqxG!BQA@Ó]G BSR'AvČG_BPkA$'DDniG BPABǨCOBDWA5CkG.!BN,}AC%5D4 3BN)AyBGpM6BKWAuyEDyGմ7BLACqrFaJ;BBy3A8|CbbBGBA-aOPTcBHA CLhFZWgBG@y0Gō~nBE7@9)6GLpBFRACIGmBJX`AcGBF8ACVF&B@ٛA9CBJsA zVC,jBGCA.OP`BJAHAadGuBHcADGVBGz@ﲦO3PBF @*OPBBHA DF6jBGAD`'GhCBJ+A0G#BC7A^D)FnlBBA.CzKG9BBA#GBCAwsCEF=GBBw|A(D GXBKzA-~V?G6BBA6D G#B@oA8;TD?ABDA =%GBA6A"GBCZA%DR,G VBE3AE%sG_B>A9DstG{BCUACCGH\ BGW>A@CFBF/A#0GxT BCACF\BDПA<:G B?A D:.FNB@]A G}B@QA9D^BLAHnBfDGBNRAIO MAGCB?ϦA;XDpG/GB?jADC2BO*AB!Gz7BO(AMtnyGO&BL>AUD FFw'BLjAREH~GYYb*BF*RA2AFɿ5B@A8DdG,7B@A8DE؁n?B?A7^IEGդ@B@]A;DUdGY#HB?IA*ED&HIBBmA& GJBCX?A7-CLB?3A$kD0HRBEA+*D :G|~TBH8A/'=H 'yUBDvA/:~Y,H#L\BG@A8ry„1H$ۃ]BF3A6ցĪFHfB@ZA9|DFkrBBjA9;D.UG{zB@qA;fDe-G`m9}B@wA8DKYF}0~BAA:DgjG%BAA9CCsBEA1CFʀBK@pB5QC'ŭBK@ ByC*IBAA<CBA}1BFA0CTGBF ]A0zCBUOBA$MD*ILBKnANC}C? BFBA/ C BFBA/ C B?,A I CY0D Q"BL(A0YC/#BFA0ؾCEQ#BFA0UCx8G'5#BFBA/ C% BErA3:C&BFBA/ C-BKӛA.CF ͯ.BN2/AC)L0BN2/AC)L1OBFBA/ C1BPABr#Cj2BPABr#Cj4BQ]A$$BDZ9BBy3A8|Cb:YB@A9?C9y:aB@3A9CCt>BC :ASCjO>BBACj?MBBy3A8|CbRdBErA3:CVBFA{BDXBBACj?XBBACj?XBBACj?[BF ]A0zCoABBACj?tBAA9CCsvBAP>A9C]2DzBB A7JC'C{zBBA7qCҐqFҲB>ACqhBQAaB4B&tBBA8C>)G'9BABA;CA?f(BAWA;wmCYEBAoA:DdF;BS|AL0TBD  BBy3A8|CbBErA3:CBErA3:C5BQAaBB BQAhBXBQ_#AVBBKFAQCk3CqBQzA.UBaE<SBQA.BPD6BBKmZAZƆ}I]4EBK$AjYIVwQBTAcHBCA(CF BV'ABPF z BQrA BFi BK@\C͑BMA@DG2|BHdA B_D+BC*A tZG -BVl+A :CFBQ2AZÂYG.kBPAWH+@<EBOACD>G`XBL/@BCXUF&BD(A_B:FN 3BUA 0GU8 BPgA*EGi!BQARB݁E^"BM0A=8CG'$BL'@7D*.G>*BPA$2À-jGΎ.BN%@bFFC0BF%4AC{GM4BQHwA'۝B^6BKIAP`VDE8BMj@ӽ,B|E49BE A3ͩC?]FH>BTFAD@GfABIA C5 G CBG8A9HC6F2 DBDAÝGt HBSA m==G5_NBEA jCCLLLG-QBVߺAB^FRBQyAB)LGJ5UBI_@KCCFNXBFs{A %mC5GC \BO,AkMOGf&^BLA@_C2@G4; _BMFA\B FbBBAzBEʃ}BL*A YG+/9BOA ļHGBIA $YCD+ FH3BFtA!CB&G@-JB@@m B LGKBNvvACP9Gy4BTA m(bFKBJ(A\BPGBN@eCvGrBSXAGYBJ5A'jÏoYH_BG]A!/CnGNgBFAjC.G\%BVHA rFمBTA.ӌ)G< BJ͓AtD*GSxB>AC/BZ@A?) uGBHfP@PD)DEBM@AKC!vGBMq@CBQTA!D:xGQ>}BMAMėH pBHA @CrE9BJN@<3G1BFAQ3B`BBMB@~A7B>ACyEBQxmA ~A 14E~ZBNY@CPnF3bBBA C螞H1BO |A ~wG^.BM@@µ-G8]B>CA6*CG{B@}A89D &E BQOE B?O@ BSGCˑBD'BO5A!kC!YFe#(BEATb3bH֞7*BFKA CCF^.BTAK5\0BJAQÍDjEi8BRlADGRF9BQ}AHCD=G:BPA9.8FcBאF<BK@tiuGFsVBOAxDGBOAU BG3BL;A#AG*oBL,@ה"@FfIBP-{A$$GBI8@@cG>>BQ)AlBlhE>΄BOA(B$DgBG|P@_CmFDBMj@`DrG96BDA#\DG pBR bAx@]GK_}BJAQ2 D5F>BG^A !FBKg@ CdG DBDA'oC2GCBG;@iB8G|wBHeA ɴBжBK,@)BsCGBF8A!COGv3BF@C3GBMB@~A7BDcA9C@{G~JbBT{AA£ZG]BH1A Br Eh~BL _@J@d\GI=IBEA OCU G ژBL@>•00FBDTA @2 G:BMAr@%I`Gێ BN A CjF[IBDA-9DE'HBEI8@DVG0HBN{@ AF BFw[A6RDqCBG _@ߣD!BJ;A0CDy#BGTA zC}G4},BK @ÓC-F,.BF-ABGC6BJc@YCc!F8BBCA=CͻsCijABBgA*zDZGLB?n@/D>GTBN<@EBGV_BF~A7D:EohBK @GqBL{@5<0{FBV:A lA EBLOvAܬG0BM@sBKF}(BG%@DVl|GXBK.@YC?Fm_BI@B~-GԫBNL@עB?FیBO2@~ĩBG?BJAY H=G}^BEwA?*;/GlɍBKR-@)#GBKAa)CkE1BJA>CBDU@B%FHFBC(ACm%BT9XANCpFWpBK5AP#CBF2A lcFhBAAGDGÄBM@׹N6fFj0BANAICGjBBK@l‹F1BCTAeC~%EBK @kB1[~FD`B>@E\C:ED BGJ>A 1B^G oBIA IKC2AGb# B?=@ KGkBGtA@CjtE %BGAĘZG.ABK u@?CDiCBK@YCAREBEA#GC7F̃ZBB.j@ GKݽuBO@BJBCMlA6D-jFSBFA&DGB@#A$DcGB^BQ}ANB!(BQvGA FB,%DBD*A#UÐ'Fk!BAiA%ZĜwH?XBQYA%BID BBAC[> F~ BB#Ak6G sBBPA&5D1G }BBbA@/@G~ B?J6A4SD BJ@B BC$AN¾_FhR BBeACjGl BB؃A|G(V BAEA9nCڸFC3 ]BQWYA$BwDd yBQx7A!-BRT BC]dA!CFZG P /BBAƒC+G"y jBQFA#:BAI>6 BGAo.QF!)% B?TfAKfCE  BBYArNDO,F BBAdBG B@V@eC?ERXpxBKA38EGABGA9>C֎G5KBIiA<'DFGl7BAKBA!mD^)CʑB>pFAtCPGBuBONA A(BҎVB@^ @xC:yXBCA&C 'FQgBCSlAD+%FSBB4A>CFBJ@VCFbdBLE@BKsFwB@KRA"NDB\fGp'B>tAF=CG1B>w^A0CG/rBKACDTC'xBJ@B"BLgA=CXqG"B?A D6Gigl"sBCA!D.G*"BCAC˳F"BM.@{BpC-#;BCS}ACF{#BFA,CG/#BFێA7Dc@F$BD1XA+4ECFEz{$BKg@Bq&#BQ8AعE&%B@-@CJu'BRBA@@F'/BSAN 0ÿG4R'rBC@AqBpF(BREA>BFPP*+BIA C`+sB?A6aD gFCM,tBQ7TA!Bz,B?@@-CsCڦ-@BQ7A!}aBuhC'.BR}AOAiiD/(B?\@(%C]DXc/*B?#@(C[Dd/vBKu@՛Cl^E0\BLo@eC}2&BA\A!bDHC,G2cB@ PA6|D/2BKA>?C2BK@'CD_2BK o@sCd D\&2BK Y@C_2BRA}B D!2BSAA_uB%EQ2BSAA_uB%EQ3-BQ7A!}aBuhC'3B@JA9D-4BAIA9oCDԧ#4BRAYAL}E 4B@)@C>Dq4-BA6A-֛C؃Dt4rB@)@C>Dq4B@A9DD4BAA-pC57BDAqPCW]@5BP5@BtDKD6BTA '?޻E397B@EA9DU8/BA^A!`JD$DGC8FBLo@eC}9B?@C9 B?@?CnDo9B?@ C~fA9B?@@-CsCڦ:1BAґA9pC`DJ??B@ @GCRD4?fBH,@o&C@BKg@BqBBA6A-֛C؃DtCBONA A(BҎVFbBA޸A-C#YENHB@@yCettDeIBJlA=CGPgNBSA+M$oG`NBTAj1VGThPB@"@(CE EPB@>@CvPBKg@BqRjBFAQ3B`BVBC)A%CVBC)A%CWBC)A%C`BL@TB"AA f%BC(ACm%gBSHAgB0*DH+gB?xA5D(D.gBOA40CyhB@A7ZD\DXճhB@A7ZD\DXճi BX\ AB8EAƨjPBKg@BqlBQ2A&BhlBQ2A&BhnBB&A‰CY7oB?JPAZ{DCovhB>AC/voB@%A8DiF,UvyB@vA9_-CFBvB?A66DGNOvB@A6zD@:FvBC(ACm%yB?bA*!D,GjyB@A/DafG1e8yB?ܿA-4E6G\zB?-A:EDbEGTɂzBB~A4PXCGBzBC A5`G8{B>0A?DA{B>AC/{BOKA3BGD |KB?A/d$G%|UB>A0 oCDG(A}B@A8|D ZDB@?A5$DwGfB@ZA8A2pC*G )]B>A3,EyG{gB? A3DkWGh\xBLo@eC}BLo@eC}B?7A3o8D)/6FܢqB? A3DG B@jmA8D/Fs B@A6~)GzpBAP9A!jDGL=B? 2A5xDh%C"-B@A6D!7BAJA0DrXGBA~A-D0G&BB&A*!DG/kB?fA1KD FuB?fA1KD FLBLo@eC}B@BA3`DGx\B??tA3SC(GBNA:BB?HA3ÓGVaBB&A‰CY7uBAA9vC&EBB&A‰CY72BO@BDyOB>3@`2ChB <RB?,A3MD" YC?WB?,A3MD" YC?BO@ B)BD=GA+YCCkz|BJ@BB?~@CC2B>lA2QD!nB?FA5DK1B?fA1KD F[B?fA1KD FBKg@BqBC(ACm%xBKA6CoCΉLB@FA7oD2D՟áB?)AtD0C7BJ@BBP@BgC*:BQPABI`D⢛̘B@A9DD[BQ2A&BhڸBSMAAEB@A6D!7BP^@B<2oDn ܰBA@SC1oB@A6D!7fB>@kCBC)A%COBC)A%CBA\A!bDHC,GBB&A‰CY7BB&A‰CY7BB&A‰CY7B@e4@ C B sB@e5@~C B:[BJ@BBLAzUׅIWg BJ}AOIFkBHf;A:΅IBKKA*%&I^(BDlhA OH BFMyA#BLBEhA!nIuBAOA!Y9DWmB@FA.D HBlBL`A C\C?pBELA BHqBPA ߫C3$BWcjA)@ߍPBAA-ΕC3BA}A-CBO$A%0 DA!D7B>A!D7BJ@WCbDBK@+cBwLtBKy@PB|BK@@BjBK@@4B\BK@=BiyBKf@ BBIv@4CXBBKA?C-FXPBBgAPOCEMBOA(ZD6fBAOA!Y9DWmBOUA(tD7\JBNA B@BBNA BlA.YBK@M~B\gmBJ3@k ChB$zTBQAŌ}HhBSAAvB=KACŒ sBB6A5nD |BJ@CB }BBJADEa BAqA,+DM BJm;@(D5Dk BNA^tKBE BN2A^B}q ,BJ@C= `BRtzA B  BNA^tKBE BNA^tKBE /BB{A.CBEg BVAR/Ay BK@`>BDD? ,BOFgA^^&BA` dBO&A^ BxD_ B>ƋAD  BDiAlCBOJA^q6BdVBOsA^m6B0!BOeA^B]V BYǐA*A#\)BSBAM}A(BSoALBĜBVAR/AyBO,XA%Ds{CleBRTVAjA@adPBO*A2B>C}8BL@< B+3BI#@9CBSoALBĜBLAXyBBQmA\GABK/0A:܍IFʼBFIA%BS0HޡBFD9ABv^D'BH%A=SŋBIBBf}AGD9IDM?BL@< B+3tBK%@KzCBBBUF@]k@ZF)?BU P@ CVG)ABSlAxĂ;G}.OBK3@ڶyBJ0!.}BK@[B0BJX@ůCT1BK3@ڶyBJ0!4BOA/.C>w4 BNA,/;C 49B>A!>D(WEA>7BOMA'B$Z<BL¦@B9AMDBBQA+ B+D>?JBB ACBoMBYqA #AQNBRAV^¨kLFSQNBR8{AUB/FjNBR-EAU*:FNBQSAWVDbFj+NBROAQD G;GNBQzAOĿHGfQWNBQ7AVrC1qGhNBO0AUiĈoGNBQAU1BNFClNBQ?AYđG ]NBRAWDzCG8fNBRAWü9F$ZOBRFAW>CͥFOBQDAUv:ΞF9PJOBRAY݂DS#F`OBRQAUsFtOBRAUUCFHOBSARBNGQOBQ>AA CaG(5OBP<*A5ƚC#GPOBP4A@CiGrOBQAICOUBN>A?;EOVBN`LA:4G OWBL3A?!pGWOYBMA?QDBGO`BLATŽDG*G¦OlBL…AYnCG*OmBLA^`WCaFOnBLhAYcB" F>D+OqBLR A[ÏiF"O{BMA[2G>OOBKAN\×GBsOBKutAM̬CFOBL]ATFC~;G>OBKQ(AICJxEAOBKHAJNDM FY1OBJfAC&D aG"iOBJABq/E=`GEOBKpA<?ĕ G"OBNEcA?wCCOBOA(ZD;qyOBNA**Dq2GPOBK|Az@CMCOBJeA= 'C27GVvRBTaAZbBE9RBWA-Z}@nJEQVyBGX@+CeBK%@KzCuBAA.D NEuBACADb%Fw3BNlc@aîGK'w5BN@ԈBC,Ej>nw7BM_@ B)~D5w8BN}@D Gw9BMy @hC[rzFsw:BM&@.ÑpFw;BKQ@DBN Gaw+yBN,@CFyBN67@cF6}3BK%@KzCBGNA ѸDBOF,BGA#TCG \BFlA%([DG3BJG^AD=_"GfJBHBFA  BvEBIAY҂;G}BE-A[BgG/m BGA /C|F@ BHR_A C^DBHaeA wC&E)BH=@A CVEi ?BHDA BETmBI[@HBG)'nBI#@Ç> GJoBHw?@BڵGqBI:y@# D)H SrBG֨A_3GxsBGA)SC(.GBGC@c rgGBIA VCFBJ|A.CaGsBGf@ 6EG@BJQA2CL^GLBH̐@bCؗvDXBJiA̾CFJBFq@ޠCD6BFګ@CFMJBF@hD7yG BGt+A SSB QFZBEuA`{CGBG!eACHFnVBFwADg[VG-GBECADG$w BEA hC G" BE>A sZfG BE0A YfF;@BK%@KzCIBK%@KzCBWsA G|BB ACBoBJF@SD _CmƉBDAC|ƎBFA1NwH0;ƏBEAA2OC/FёƑBEyA3&D2eG9^ƒBBvA7WEXpG}ƓBI2A< D5ޅGNƔBG:A9CݩzGFUƕBDjaA$D/H9ƖBG.|A2SCLGmdƗBFj6A@E`-GCƘBE} A6G-GlƙBEA=ܕjGSƚBE;A5˨AG.ƜBF$bACCڰGo B@iA0ēGBE72A CFBB'A%B*G0BDնAc+A=F9BDFADG=BC̪ACG,BC+hA CǝFEBBAC:EBBA԰C*G'B=WAC\2F BCA#*DX*F BCApsD FsBC AC/BBfAÑDEDBBA:aDsbFJBBAIG<B> A-EGZzB>*ACFBBA d9GuB@ʔA!b CHG;B@xA D iFN^B@hA9}DW,Fj_BA$A9@6DF ^`B@yA5޺GnaBAzA*BCĢGjbB@0A5͵Ĩ/G%lcB?(AAjD&G? dB@>A;5C}GreBAA$9\DG΍fB@ GA?BDMPgB@rAA(DEG[LkB>VA"<EG8lB@ A?C*MF<mB?=A?DLFnBAeA*cG!WoB?A!D GrpB? A2JÙGqB@FA6oDp`G: iBYIA!Aˇ+9BF7(ApBqCQBRAP*BffBFfAB.BB ACBo[BWGA'A]DC B" ԔH2 BZunBF  B+_BrD/ BڢBID BmBJ B_BlxFCgإ iBBB8 BڢBID %BBB8 2B0BCۤI  BڢBID 'uBx]SIG:! 'vBtB`UFJ 'wBXPA¹&F t 'xB\(CXD 'B[ IA Fł +aBF'BE /EBֵ 9BNEKR :BY[ #Cݦ|Guv :BӾ.UKG  :B%/dG: ;B|Bw oB)GB zFWUV UBfr.D,CGy VB?CG WBjޕA1 üB%BhD/޵ Bf#C DhBFt.@[tLFjBG@ʆC`GGKMBG@CJ E?MBG@JC_D:P+MBF@[C}F OKBU]1BF IBU4A@eD BU6B)WFPBUoë{GtBU| VUGBTUUcGrBU3cbJLH}BUGRCGBUdǩnC"E BUGłBZFrfL"BU4T@_ G6#/BU%MC4BUNDG9`BUAɹ6C+oBFbBUA]jC]FBU,B[BcD N!BU6)J:-E2N"BUb^@ADN#BU-RA BlN&BU37+AS*EN5BUG^uAkF(N6BUAn@EN7BUH6A F!N8BU,*3tB4c0lB̞AABAs@F/B\A(iA`vBA$AoEB*AwDxB*AHDFڈ^@B*AD3DB(A(Cs7GB*?AZC/G%)B9AqCF%) B8n}AgDF&%)B8wAfqqCE!EH%)B82/Ag-tCYX%)+B6A]dCuE]%);B8>Ai=C~Ei&dB&A8B/TFW1&B&hACuĸF&B'#.'tBKD}tE.BhB/ZžC#.BB/ž CD.CB/žCFZE%D.D\B.nžC F6BhšC V6B7B8lCu6B3scDߛQ6B1hA N@s6BC_[F`N6B qE>6B6tbAG 6BR‘Fa6HB5=16 tBš2ZBVB6rwA @aCU6rzA VՃFX6r{Agc /{E }6rA7 @FYXi6mB>QA'vD 4~6IB)š;TC@bF76KBYš#ã4nF;x6LBšeC'GvA6MBš3BF6OBWjš&+?$F;6PBšGrFN$6RBiiš'OF\6UBš7 oF6WBVšZBJE66A CFw69Al UH;E6B ׭Ÿ7BA6B ŸhB#6B (ŸhTB=q6B ŸXBu6B ŸF=BZBFv673B zFž.CGFE67QB wŸTD~G%6D B XŸfr4F^6BNš_SH'6BšiBKŁFE6B›MšH_6 BVšXB{6eBšBQEGl6fBššpeBicD$j6BAšPGBZ6Bȵ–oAo6Bȵ–oAo6=B" /CE6FB%! JCG8o6GB(@  VCJG)6[B(\4Ÿ0CQGIP6 B":ŸCq6B"yŸ{CuF2`F6B":ŸCFoz6B]CZ^A2t6YB$šC3w6\BšrC~8Fi6^B1šnBRuFh}6gBšMC%EC6hBVšfBRnF6yBcšaBö F$6'B›C]0F6'BšdAcY^FQ.6'B9šn@9HE6'BšgBdnF6'BšcB?$E6'!BRšaAj,ZD6'"BšWC ĬD6YBdk›@Gd6iA ^R;E~6iAd ED6iA ~EIJ6y!BCB.u6(B"  CF:Il6)B# =Cv=Gl62B#K 5CFӀ6mB›C2D3C-6 B]CZ^A2t66B".¨CsAPNBCANBDBvNBD BAB[D7 ABD+E(A=B|D1G+nA=B|D1G+ jA~BEC;F\ lA{0B[CUEc oA[B>$Cb)A pA^BwCTE~ qAeBhACO?QE  rAUdBpBESF sA>BtPC9F A BIC;DB AB`CTE AeBCrE AB?FX AB1C]EA< AR$B1F*T( A{B9C,EO ABC7 AB]cBF , AhBl=C;E# 6AˀBBqEu]7-B++BOEWLFxBB HA;dLBB 2FMKBB g2-?F2 ,$BB ADxHBlB#BjNLEqBBǗB;DXBBB"EZBzBBZET,BBB[AEB $ B.QAE=1BԽB(SE4B9BXE87B (/B+BE(NB B-AsF0PBBFBLE@QB B$CF` qaC NA xA{Da %C DԠF8 = #C]D54Ff @ CDFLbd FCD\?CfE{tE!C1A7XDECmAqBGD|C@B 7C;ixFCdAB** lfC4EC x`C=yNrCRF u0C2AMAφ ECB Z0C!CLF &wC`AV!B  )VCYF- -ktC GThtC]ACԬNHCUA JLg+ALIE  g[s,G>mgdB[G;55|?C,N a,r?ČHpc: h-AFr|}; xhoAu s8CnGM !@uBG] &\rBy2F4 +prAUF 0q>GQ gA"xsީErRIBY"€D"i2֤H":€D b" ^hHD}G3 "k\~G͢" i2{KF[#" jBF)v" ?iCGF" LiCFC" Wj"GCF." bnjEAJF(Y" iVEA yEY1 "2 hgi¾+F*h"QezHHP"-4GCgE".7qCEn"6x€dCL0DH"7rNG"t @YH,"X)nD'H)"ukt2G"stBGE"BjS H.Y"!piVD7A$%F"w€\DD6 g6F#ԏ6L€D H3F6VQ,€C:G66W€EsFGE9s6XB€z CEP6Yz€{CgFs6[b,7C@rGX6`%€%C F6o+€D'4mF6A{NG`{6"xY-wG6I}C(VGp6 gA6 s|B<Gck6 !2v)qEmGW66 &lrrGUҺ6 +nÀG)G6 0P9qW3G6 ?rA5Em.6fgkÚG %6f=EY6 &i^€Eץ6 i]C&F6 jPAo6 =jB1FK6 j1{A9E6 juE}6 xilBuE t6 gFAM6 jk"- G@6% Wh-DPGO6/ h0A>F96^\lAEfso-frameworkd-0.10.1/etc/freesmartphone/ogsmd/networks.tab000066400000000000000000003066551174525413000240120ustar00rootroot00000000000000% encoded in UTF-8 % taken from http://en.wikipedia.org/wiki/Mobile_Network_Code # ISO Country AF Afghanistan # MCC MNC Brand Operator Status Bands Notes 412 01 AWCC Afghan Wireless Communication Company Operational GSM 900 / GSM 1800 [1] 412 20 Roshan Telecom Development Company Afghanistan Ltd. Operational GSM 900 412 40 Areeba MTN Afghanistan Operational GSM 900 / GSM 1800 412 50 Etisalat Etisalat Afghanistan Operational GSM 900 / GSM 1800 # ISO Country AL Albania # MCC MNC Brand Operator Status Bands Notes 276 01 AMC Albanian Mobile Communications Operational GSM 900 / GSM 1800 [2] 276 02 Vodafone Vodafone Albania Operational GSM 900 / GSM 1800 276 03 Eagle Mobile Eagle Mobile Operational GSM 900 / GSM 1800 # ISO Country DZ Algeria # MCC MNC Brand Operator Status Bands Notes 603 01 Mobilis ATM Mobilis Operational GSM 900 [3] 603 02 Djezzy Orascom Telecom Algerie Spa Operational GSM 900 / GSM 1800 603 03 Nedjma Wataniya Telecom Algerie Operational GSM 900 / GSM 1800 # ISO Country AD Andorra # MCC MNC Brand Operator Status Bands Notes 213 03 Mobiland Servei De Tele. DAndorra Operational GSM 900 [4] # ISO Country AO Angola # MCC MNC Brand Operator Status Bands Notes 631 02 UNITEL UNITEL S.a.r.l. Operational GSM 900 / GSM 1800 [5] # ISO Country AI Anguilla (United Kingdom) # MCC MNC Brand Operator Status Bands Notes 365 010 Weblinks Limited Operational Unknown # ISO Country AG Antigua and Barbuda # MCC MNC Brand Operator Status Bands Notes 344 030 APUA Antigua Public Utilities Authority Operational GSM 1900 [6] 344 920 bmobile Cable & Wireless Caribbean Cellular (Antigua) Limited Operational GSM 850 344 930 Digicel Antigua Wireless Ventures Limited Operational GSM 900 # ISO Country AR Argentina # MCC MNC Brand Operator Status Bands Notes 722 010 Movistar Telefonica Móviles Argentina SA Operational GSM 850 / GSM 1900 722 020 Nextel NII Holdings Operational iDEN 800 722 070 Movistar Telefonica Móviles Argentina SA Operational GSM 1900 [7] 722 310 Claro AMX Argentina S.A Operational GSM 1900 722 320 Claro AMX Argentina S.A Operational GSM 850 / GSM 1900 722 330 Claro AMX Argentina S.A Operational GSM 850 / GSM 1900 722 340 Personal Telecom Personal SA Operational GSM 850 / GSM 1900 722 350 Hutchinson (PORT HABLE) Unknown GSM 900 # ISO Country AM Armenia # MCC MNC Brand Operator Status Bands Notes 283 01 Beeline ArmenTel Operational GSM 900 / GSM 1800 UMTS 2100 is planned [8] 283 05 VivaCell-MTS K Telecom CJSC Operational GSM 900 / GSM 1800 # ISO Country AW Aruba (Kingdom of the Netherlands) # MCC MNC Brand Operator Status Bands Notes 363 01 SETAR SETAR (Servicio di Telecomunicacion di Aruba) Operational GSM 900 / GSM 1900 / TDMA 800 363 20 Digicell Operational GSM 900 / GSM 1800 # ISO Country AU Australia # MCC MNC Brand Operator Status Bands Notes 505 01 Telstra Telstra Corp. Ltd. Operational GSM 900 / GSM 1800 / UMTS 850 / UMTS 2100 505 02 YES OPTUS Singtel Optus Ltd Operational GSM 900 / GSM 1800 / UMTS 900 / UMTS 2100 505 03 Vodafone Vodafone Australia Operational GSM 900 / GSM 1800 / UMTS 900 / UMTS 2100 505 04 Department of Defence Operational Unknown 505 05 Ozitel Not operational Unknown 505 06 3 Hutchison 3G Operational UMTS 2100 505 07 Vodafone Vodafone Australia Not operational Unknown 505 08 One. Tel Not operational Unknown 505 09 Airnet Not operational Unknown 505 11 Telstra Telstra Corp. Ltd. Not operational Unknown 505 12 3 Hutchison 3G Not operational Unknown 505 14 AAPT Not operational Unknown 505 15 3GIS Not operational Unknown 505 24 Advanced Communications Technologies Not operational Unknown 505 38 Crazy John's MVNO GSM 900 / GSM 1800 / UMTS 2100 505 71 Telstra Telstra Corp. Ltd. Not operational Unknown 505 72 Telstra Telstra Corp. Ltd. Not operational Unknown 505 88 Localstar Not operational Globalstar Satellite 505 90 YES OPTUS Singtel Optus Ltd Operational Unknown 505 99 One. Tel Not operational GSM 1800 # ISO Country AT Austria # MCC MNC Brand Operator Status Bands Notes 232 01 A1 Mobilkom Austria Operational GSM 900 / GSM 1800 / UMTS 2100 232 03 T-Mobile T-Mobile Austria Operational GSM 900 / GSM 1800 / UMTS 2100 232 05 Orange Orange Austria Operational GSM 900 / GSM 1800 / UMTS 2100 former One 232 07 T-Mobile T-Mobile Austria Operational GSM 1800 232 10 3 Hutchison 3G Operational UMTS 2100 # ISO Country AZ Azerbaijan # MCC MNC Brand Operator Status Bands Notes 400 01 Azercell Operational GSM 900 / GSM 1800 [10] 400 02 Bakcell Operational GSM 900 / GSM 1800 400 03 FONEX CATEL LLC Operational CDMA 400 04 Nar Mobile Azerfon Operational GSM 900 / GSM 1800 # ISO Country BS Bahamas # MCC MNC Brand Operator Status Bands Notes 364 390 BaTelCo The Bahamas Telecommunications Company Ltd Operational [11] # ISO Country BH Bahrain # MCC MNC Brand Operator Status Bands Notes 426 01 Batelco Operational Unknown 426 02 MTC-VFBH Operational Unknown # ISO Country BD Bangladesh # MCC MNC Brand Operator Status Bands Notes 470 01 Grameenphone GrameenPhone Ltd Operational GSM 900 470 02 Aktel Operational GSM 900 / GSM 1800 470 03 Banglalink Orascom Telecom Bangladesh Limited Operational GSM 900 470 04 TeleTalk Operational GSM 900 470 06 Citycell Operational CDMA 470 07 Warid Warid Telecom Operational GSM 1800 # ISO Country BB Barbados # MCC MNC Brand Operator Status Bands Notes 342 600 bmobile Cable & Wireless Barbados Ltd. Operational GSM 900 [12] 342 750 Digicel Digicel (Jamaica) Limited Operational GSM 900 / GSM 1800 342 820 Sunbeach Communications Reserved Unknown # ISO Country BY Belarus # MCC MNC Brand Operator Status Bands Notes 257 01 Velcom Operational GSM 900 / GSM 1800 257 02 MTS JLLC Mobile TeleSystems Operational GSM 900 / GSM 1800 257 04 life:) Belarussian Telecommunications Network Operational GSM 900 / GSM 1800 % 257 ? BelCel Operational CDMA 450 # ISO Country BE Belgium # MCC MNC Brand Operator Status Bands Notes 206 01 Proximus Belgacom Mobile Operational GSM 900 / GSM 1800 / UMTS 2100 206 10 Mobistar France Telecom Operational GSM 900 / GSM 1800 / UMTS 2100 206 20 BASE KPN Operational GSM 900 / GSM 1800 / UMTS 2100 # ISO Country BZ Belize # MCC MNC Brand Operator Status Bands Notes 702 67 Belize Telemedia Operational GSM 1900 702 68 International Telecommunications Ltd. Operational Unknown % 702 ?? Smart Operational CDMA # ISO Country BM Bermuda # MCC MNC Brand Operator Status Bands Notes 350 01 Digicel Bermuda Telecommunications (Bermuda & West Indies) Ltd Operational GSM 1900 350 02 Mobility M3 Wireless Operational GSM 1900 310 38 Digicel Operational GSM 1900 % 310 ?? Cellular One Operational CDMA # ISO Country BJ Benin # MCC MNC Brand Operator Status Bands Notes 616 00 BBCOM Bell Benin Communications Operational GSM 900 [13] 616 01 Libercom Operational Unknown 616 02 Telecel Telecel Benin Ltd Operational GSM 900 616 03 Areeba Spacetel Benin Operational GSM 900 Former BeninCell # ISO Country BT Bhutan # MCC MNC Brand Operator Status Bands Notes 402 11 B-Mobile B-Mobile Operational GSM 900 402 77 TashiCell Tashi InfoComm Limited Operational GSM 900 / GSM 1800 # ISO Country BO Bolivia # MCC MNC Brand Operator Status Bands Notes 736 01 Nuevatel Nuevatel PCS De Bolivia SA Operational GSM 1900 [14] 736 02 Entel Entel SA Operational GSM 1900 736 03 Tigo Telefonica Celular De Bolivia S.A Operational GSM 850 Aka. Telecel Bolivia # ISO Country BA Bosnia and Herzegovina # MCC MNC Brand Operator Status Bands Notes 218 03 ERONET Public Enterprise Croatian Telecom Ltd. Operational GSM 900 [15] 218 05 m:tel RS Telecommunications JSC Banja Luka Operational GSM 900 / GSM 1800 218 90 BH Mobile BH Telecom Operational GSM 900 / GSM 1800 # ISO Country BW Botswana # MCC MNC Brand Operator Status Bands Notes 652 01 Mascom Mascom Wireless (Pty) Limited Operational GSM 900 [16] 652 02 Orange Orange (Botswana) Pty Limited Operational GSM 900 652 04 BTC Mobile Botswana Telecommunications Corporation Operational GSM 900 / GSM 1800 [17] # ISO Country BR Brazil # MCC MNC Brand Operator Status Bands Notes 724 02 TIM Telecom Italia Mobile Operational GSM 900 / GSM 1800 / UMTS 850 / UMTS 2100 [18] 724 03 TIM Telecom Italia Mobile Operational GSM 900 / GSM 1800 / UMTS 850 / UMTS 2100 724 04 TIM Telecom Italia Mobile Operational GSM 900 / GSM 1800 / UMTS 850 / UMTS 2100 724 05 Claro Claro (América Móvil) Operational GSM 900 / GSM 1800 / UMTS 850 / UMTS 2100 724 06 Vivo Operational GSM 850 / UMTS 850 / UMTS 2100 724 07 CTBC Celular CTBC Telecom Operational GSM 900 / GSM 1800 / UMTS 850 / UMTS 2100 724 08 TIM Telecom Italia Mobile Operational GSM 900 / GSM 1800 / UMTS 850 / UMTS 2100 724 10 Vivo Operational GSM 850 / UMTS 850 / UMTS 2100 724 11 Vivo Operational GSM 850 / UMTS 850 / UMTS 2100 724 15 Sercomtel Sercomtel Celular Operational GSM 900 / GSM 1800 724 16 Oi / Brasil Telecom Brasil Telecom Celular SA Operational GSM 1800 / UMTS 2100 724 23 Vivo Operational GSM 900 / GSM 1800 / UMTS 850 / UMTS 2100 724 24 Oi / Amazonia Celular Amazônia Celular S.A. / GSM 1800 [19] 724 31 Oi TNL PCS Operational GSM 1800 / UMTS 2100 Oi 724 32 CTBC Celular CTBC Celular S.A. Operational 724 33 CTBC Celular CTBC Celular S.A. Operational 724 34 CTBC Celular CTBC Celular S.A. Operational 724 37 aeiou Unicel do Brasil Operational GSM 1800 Former: Unicel Telecomunicações # ISO Country VG British Virgin Islands (United Kingdom) # MCC MNC Brand Operator Status Bands Notes 348 170 Cable & Wireless Cable & Wireless (West Indies) Operational GSM 850 348 570 Caribbean Cellular Telephone Operational GSM 900 / GSM 1900 # ISO Country BN Brunei # MCC MNC Brand Operator Status Bands Notes 528 01 Jabatan Telekom Unknown Unknown 528 02 B-Mobile B-Mobile Communications Sdn Bhd Operational UMTS 2100 [20] 528 11 DTSCom DataStream Technology Operational GSM 900 # ISO Country BG Bulgaria # MCC MNC Brand Operator Status Bands Notes 284 01 M-Tel Mobiltel Operational GSM 900 / GSM 1800 / UMTS 2100 284 03 Vivatel BTC Operational GSM 900 / GSM 1800 / UMTS 2100 284 05 GLOBUL Cosmo Bulgaria Mobile Operational GSM 900 / GSM 1800 / UMTS 2100 # ISO Country BF Burkina Faso # MCC MNC Brand Operator Status Bands Notes 613 01 Onatel Operational GSM 900 [21] 613 02 Zain Celtel Burkina Faso Operational GSM 900 [22] 613 03 Telecel Faso Telecel Faso SA Operational GSM 900 # ISO Country BI Burundi # MCC MNC Brand Operator Status Bands Notes 642 01 Spacetel Econet Wireless Burundi PLC Operational GSM 900 [23] 642 02 Africell Africell PLC Operational GSM 900 Was Safaris 642 03 Telecel Telecel Burundi Company Operational GSM 900 % 642 ? Onatel Planned GSM 900 % 642 ? LACELL SU Planned GSM 1800 # ISO Country HK Cambodia # MCC MNC Brand Operator Status Bands Notes 456 01 Mobitel CamGSM Operational GSM 900 / UMTS 2100 456 02 hello Telekom Malaysia International (Cambodia) Co. Ltd Operational GSM 900 456 03 S Telecom Reserved CDMA 456 04 qb Cambodia Advance Communications Co. Ltd Operational GSM 1800 / UMTS 2100 456 05 Star-Cell APPLIFONE CO. LTD. Operational GSM 1800 456 18 Camshin / Shinawatra Operational Unknown % 456 ? Excell Operational CDMA % 456 ? Latelz Co., Ltd Reserved GSM 1800 % 456 ? Beeline Sotelco Ltd. (Beeline-KH) Reserved GSM 900 / GSM 1800 # ISO Country CM Cameroon # MCC MNC Brand Operator Status Bands Notes 624 01 MTN Cameroon Mobile Telephone Network Cameroon Ltd Operational GSM 900 [24] 624 02 Orange Orange Cameroun S.A. Operational GSM 900 # ISO Country CA Canada # MCC MNC Brand Operator Status Bands Notes 302 220 Telus Unknown Unknown 302 350 FIRST FIRST Networks Operations Inc Operational GSM 850 302 360 MiKe Telus Mobility Operational CDMA 302 361 Telus Mobility Operational CDMA 302 370 Fido Microcell Telecommunications Inc (Fido) Operational GSM 1900 302 380 DTMS Dryden Mobility Planned GSM 850 302 620 ICE Wireless Operational GSM 1900 302 651 Bell Mobility Operational CDMA 302 652 BC Tel Mobility Operational Unknown 302 653 Telus Mobility Operational CDMA 302 654 Sask Tel Mobility Operational Unknown 302 655 MTS Mobility Operational CDMA 302 656 Tbay Mobility Operational Unknown 302 657 Telus (Quebec) Mobility Operational CDMA 302 701 MB Tel Mobility Operational Unknown 302 702 MT&T Mobility Operational Unknown 302 703 New Tel Mobility Operational Unknown 302 710 Globalstar Operational Unknown 302 720 Rogers Wireless Operational GSM 850 / GSM 1900 / UMTS 850 / UMTS 1900 # ISO Country CV Cape Verde # MCC MNC Brand Operator Status Bands Notes 625 01 CVMOVEL CVMovel, S.A. Operational GSM 900 / GSM 1800 # ISO Country KY Cayman Islands (United Kingdom) # MCC MNC Brand Operator Status Bands Notes 346 140 Cable & Wireless Cable & Wireless (Cayman Islands) Limited Operational GSM 850 / GSM 1900 [26] 338 050 Digicel Digicel Cayman Ltd. Operational GSM 900 / GSM 1800 # ISO Country CF Central African Republic # MCC MNC Brand Operator Status Bands Notes 623 01 CTP Centrafrique Telecom Plus Operational GSM 900 [27] 623 02 TC Telecel Centrafrique Operational GSM 900 623 03 Orange Orange RCA Planned GSM 1800 623 04 Nationlink Nationlink Telecom RCA Operational GSM 900 # ISO Country TD Chad # MCC MNC Brand Operator Status Bands Notes 622 01 Zain CelTel Tchad SA Operational GSM 900 [28] 622 02 Tchad Mobile Operational Unknown 622 03 TIGO - Millicom Operational GSM 900 622 02 TAWALI Operational CDMA # ISO Country CL Chile # MCC MNC Brand Operator Status Bands Notes 730 01 Entel Entel Pcs Operational GSM 1900 / HSDPA 1900 [29] 730 02 movistar Movistar Chile Operational GSM 850 / GSM 1900 / CDMA 800 / TDMA 800 / HSDPA 850 730 03 Claro Claro Chile Operational GSM 1900 / CDMA 1900 / UMTS 1900 730 10 Entel Entel Telefonica Movil Operational GSM 1900 / HSDPA 1900 # ISO Country CN China # MCC MNC Brand Operator Status Bands Notes 460 00 China Mobile Operational GSM 900 / GSM 1800 460 01 China Unicom Operational GSM 900 / UMTS 2100 CDMA network sold to China Telecom, UMTS is not officially launched but a few towers are up and functioning 460 02 China Mobile Operational TD-SCDMA 2010 460 03 China Telecom Operational CDMA 800 460 05 China Telecom Not Operational CDMA 2000 460 06 China Unicom Not Operational UMTS 2100 (new entrant?) So far only a few towers in and around Shanghai # ISO Country CO Colombia # MCC MNC Brand Operator Status Bands Notes 732 001 Colombia Telecomunicaciones S.A. - Telecom Operational Unknown 732 002 Edatel Edatel S.A. Operational Unknown 732 101 Comcel Comcel Colombia Operational GSM 850 / GSM 1900 732 102 movistar Bellsouth Colombia Operational GSM 850 / GSM 1900 / CDMA 850 732 103 Tigo Colombia Móvil Operational GSM 1900 732 111 Tigo Colombia Móvil Operational GSM 1900 732 123 movistar Telefónica Móviles Colombia Operational GSM 850 / GSM 1900 / CDMA 850 # ISO Country KM Comoros # MCC MNC Brand Operator Status Bands Notes 654 01 HURI - SNPT Operational Unknown # ISO Country CG Republic of the Congo # MCC MNC Brand Operator Status Bands Notes 629 01 Zain Celtel Congo Operational Unknown Former Celtel brand [30] 629 10 Libertis Telecom MTN CONGO S.A Operational GSM 900 % 629 ? African Telecoms Operational Unknown % 629 ? Congolaise Wireless Operational Unknown # ISO Country CK Cook Islands (New Zealand) # MCC MNC Brand Operator Status Bands Notes 548 01 Telecom Cook Operational GSM 900 # ISO Country CR Costa Rica # MCC MNC Brand Operator Status Bands Notes 712 01 ICE Instituto Costarricense de Electricidad Operational GSM 1800 712 02 ICE Instituto Costarricense de Electricidad Operational GSM 1800 712 03 ICE Grupo ICE Planned UMTS 850 # ISO Country HR Croatia # MCC MNC Brand Operator Status Bands Notes 219 01 T-Mobile T-Mobile Croatia Operational GSM 900 / UMTS 2100 219 02 Tele2 Operational GSM 1800 219 10 VIPnet Operational GSM 900 / UMTS 2100 # ISO Country CU Cuba # MCC MNC Brand Operator Status Bands Notes 368 01 ETECSA Empresa de Telecomunicaciones de Cuba, SA Operational GSM 900 [31] # ISO Country CY Cyprus # MCC MNC Brand Operator Status Bands Notes 280 01 Cytamobile-Vodafone Cyprus Telecommunications Auth Operational GSM 900 / GSM 1800 280 10 MTN Areeba Ltd Operational GSM 900 / GSM 1800 # ISO Country CZ Czech Republic # MCC MNC Brand Operator Status Bands Notes 230 01 T-Mobile T-Mobile Czech Republic Operational GSM 900 / GSM 1800 / UMTS 2100 230 02 EUROTEL PRAHA Telefónica O2 Czech Republic Operational CDMA 450 / GSM 900 / GSM 1800 / UMTS 2100 230 03 OSKAR Vodafone Czech Republic Operational GSM 900 / GSM 1800 230 04 U:fon MobilKom, a. s. Operational CDMA 410 - 430 230 98 S\u017dDC s.o. Operational GSM-R 900 230 99 Vodafone Vodafone Czech Republic a.s., R&D Centre at FEE, CTU Operational Unknown # ISO Country CD Democratic Republic of the Congo # MCC MNC Brand Operator Status Bands Notes 630 01 Vodacom Vodacom Congo RDC sprl Operational GSM 900 / GSM 1800 [32] 630 02 Zain Celtel Congo Operational GSM 900 [33] 630 04 Cellco Unknown Unknown 630 05 Supercell Supercell SPRL Operational GSM 900 / GSM 1800 630 86 CCT Congo-Chine Telecom s.a.r.l. Operational GSM 900 / GSM 1800 630 89 SAIT Telecom OASIS SPRL Operational GSM 1800 % 630 ? Africell Africell RDC sprl Reserved GSM 900 # ISO Country DK Denmark # MCC MNC Brand Operator Status Bands Notes 238 01 TDC TDC A/S Operational GSM 900 / GSM 1800 / UMTS 2100 [34] 238 02 Tele2 Telenor Operational GSM 900 / GSM 1800 / UMTS 2100 238 03 MIGway A/S Reserved Unknown 238 06 3 Hi3G Denmark ApS Operational UMTS 2100 238 07 Barablu Mobile Ltd. Reserved Unknown 238 10 TDC TDC A/S Operational Unknown 238 20 Telia Operational Unknown 238 30 Telia Telia Nãttjänster Norden AB Operational GSM 900 / GSM 1800 238 77 Sonofon Telenor Operational GSM 900 / GSM 1800 # ISO Country DJ Djibouti # MCC MNC Brand Operator Status Bands Notes 638 01 Evatis Djibouti Telecom SA Operational GSM 900 [35] # ISO Country DM Dominica # MCC MNC Brand Operator Status Bands Notes 366 020 Digicel Unknown GSM 900 / GSM 1800 /GSM 1900 366 110 Cable & Wireless Unknown GSM 850 # ISO Country DO Dominican Republic # MCC MNC Brand Operator Status Bands Notes 370 01 Orange Orange Dominicana Operational GSM 1800 / GSM 1900 / GSM 900 370 02 Claro Compañía Dominicana de Teléfonos, C por Operational CDMA2000 1900 / GSM 850 / GSM 1900 / UMTS 850 370 03 Claro 3G Compañía Dominicana de Teléfonos, C por Operational 3G 850 370 04 Viva Trilogy Dominicana, S.A. Operational CDMA2000 1xEV-DO 1900 / GSM 1900 Former Centennial Dominicana # ISO Country TL East Timor # MCC MNC Brand Operator Status Bands Notes 514 02 Timor Telecom Operational GSM 900 [36] [37] # ISO Country EC Ecuador # MCC MNC Brand Operator Status Bands Notes 740 00 Movistar Otecel S.A. Operational CDMA 850 / GSM 850 Former BellSouth 740 01 Porta América Móvil Operational GSM 850 740 02 Alegro Telecsa S.A. Operational CDMA 1900 # ISO Country EG Egypt # MCC MNC Brand Operator Status Bands Notes 602 01 Mobinil ECMS-Mobinil Operational GSM 900 / GSM 1800 / UMTS 2100 602 02 Vodafone Vodafone Egypt Operational GSM 900/ GSM 1800 / UMTS 2100 602 03 Etisalat Etisalat Egypt Operational GSM 900 / GSM 1800 / UMTS 2100 # ISO Country SV El Salvador # MCC MNC Brand Operator Status Bands Notes 706 01 CTE Telecom Personal CTE Telecom Personal SA de CV Operational GSM 1900 706 02 digicel Digicel Group Operational GSM 900 706 03 Telemovil EL Salvador Telemovil EL Salvador S.A Operational GSM 850 Tigo / Millicom 706 04 movistar Telefónica Móviles El Salvador Operational CDMA 850 / GSM 850 706 10 Claro América Móvil Operational GSM 1900 / UMTS 1900 # ISO Country GQ Equatorial Guinea # MCC MNC Brand Operator Status Bands Notes 627 01 Orange GQ GETESA Operational GSM 900 # ISO Country ER Eritrea # MCC MNC Brand Operator Status Bands Notes 657 01 Eritel Eritrea Telecommunications Services Corporation Operational GSM 900 [38] # ISO Country EE Estonia # MCC MNC Brand Operator Status Bands Notes 248 01 EMT Estonian Mobile Telecom Operational GSM 900 / GSM 1800 / UMTS 2100 248 02 Elisa Elisa Eesti Operational GSM 900 / GSM 1800 / UMTS 900 / UMTS 2100 248 03 Tele 2 Tele 2 Eesti Operational GSM 900 / GSM 1800 248 04 OY Top Connect Unknown Unknown 248 05 AS Bravocom Mobiil Unknown Unknown 248 06 OY ViaTel Unknown UMTS 2100 # ISO Country ET Ethiopia # MCC MNC Brand Operator Status Bands Notes 636 01 ETMTN Ethiopian Telecommunications Corporation Operational GSM 900 # ISO Country FO Faroe Islands (Denmark) # MCC MNC Brand Operator Status Bands Notes 288 01 Faroese Telecom Faroese Telecom Operational GSM 900 [39] 288 02 Vodafone Vodafone Faroe Islands Operational GSM 900 Former Kall # ISO Country FJ Fiji # MCC MNC Brand Operator Status Bands Notes 542 01 Vodafone Vodafone Fiji Operational GSM 900 / UMTS 2100 # ISO Country FI Finland # MCC MNC Brand Operator Status Bands Notes 244 03 DNA DNA Oy Operational GSM 1800 Former Telia 244 05 Elisa Elisa Oyj Operational GSM 900 / GSM 1800 / UMTS 900 / UMTS 2100 Former Radiolinja 244 10 TDC Oy Unknown Unknown 244 12 DNA DNA Oy Operational GSM 900 / GSM 1800 / UMTS 2100 [40] 244 14 AMT Ålands Mobiltelefon Operational GSM 900 coverage only in Åland Islands 244 21 Saunalahti Elisa Oyj Operational MVNO Internal MVNO of Elisa Oyj, former Saunalahti Group Oyj 244 29 Scnl Truphone Unknown Unknown 244 91 Sonera TeliaSonera Finland Oyj Operational GSM 900 / GSM 1800 / UMTS 2100 # ISO Country FR France # MCC MNC Brand Operator Status Bands Notes 208 00 Orange Operational GSM 900 / GSM 1800 / UMTS 2100 [41] 208 01 France Telecom Mobile France Orange Operational GSM 900 / GSM 1800 / UMTS 2100 208 02 Orange Operational GSM 900 / GSM 1800 / UMTS 2100 208 05 Globalstar Europe Operational Satellite 208 06 Globalstar Europe Operational Satellite 208 07 Globalstar Europe Operational Satellite 208 10 SFR Operational GSM 900 / GSM 1800 / UMTS 2100 208 11 SFR Operational UMTS 2100 UMTS 208 13 SFR Operational Unknown Zones Blanches 208 20 Bouygues Bouygues Telecom Operational GSM 900 / GSM 1800 208 21 Bouygues Bouygues Telecom Operational GSM 900 / GSM 1800 208 88 Bouygues Bouygues Telecom Operational Unknown Zones Blanches # ISO Country PF French Polynesia (France) # MCC MNC Brand Operator Status Bands Notes 547 20 VINI Tikiphone SA Operational GSM 900 # ISO Country GQ Gabon # MCC MNC Brand Operator Status Bands Notes 628 01 Libertis Libertis S.A. Operational GSM 900 628 02 Moov (Telecel) Gabon S.A. Operational GSM 900 628 03 Zain Celtel Gabon S.A. Operational GSM 900 [42] # ISO Country GM Gambia # MCC MNC Brand Operator Status Bands Notes 607 01 Gamcel Gamcel Operational 900/1800 607 02 Africel Africel Operational 900/1800 # ISO Country GE Georgia # MCC MNC Brand Operator Status Bands Notes 282 01 Geocell Geocell Limited Operational GSM 900 / GSM 1800 / UMTS 2100 [43] 282 02 Magti Magticom GSM Operational GSM 900 / GSM 1800 / UMTS 2100 282 03 Iberiatel Iberiatel Ltd. Operational CDMA 450 282 04 Beeline Mobitel LLC Operational GSM 1800 289 67 Aquafon Operational GSM Abkhazia region only 289 88 A-Mobile Operational GSM Abkhazia region only # ISO Country DE Germany # MCC MNC Brand Operator Status Bands Notes 262 01 T-Mobile T-Mobile Deutschland GmbH Operational GSM 900 / GSM 1800 / UMTS 2100 D1 - DeTe Mobil 262 02 Vodafone Vodafone D2 GmbH Operational GSM 900 / GSM 1800 / UMTS 2100 262 03 E-Plus E-Plus Mobilfunk Operational GSM 900 / GSM 1800 / UMTS 2100 262 04 Vodafone Operational GSM 900 / GSM 1800 / UMTS 2100 262 05 E-Plus E-Plus Mobilfunk Operational GSM 900 / GSM 1800 / UMTS 2100 262 06 T-Mobile Operational GSM 900 / GSM 1800 / UMTS 2100 262 07 O2 O2 (Germany) GmbH & Co. OHG Operational GSM 900 / GSM 1800 / UMTS 2100 262 08 O2 Operational GSM 900 / GSM 1800 / UMTS 2100 262 09 Vodafone Operational GSM 900 / GSM 1800 / UMTS 2100 262 10 Arcor AG & Co Operational GSM 900 / GSM 1800 / UMTS 2100 262 11 O2 Operational GSM 900 / GSM 1800 / UMTS 2100 262 12 Dolphin Telecom Not operational GSM 900 / GSM 1800 / UMTS 2100 262 13 Mobilcom Multimedia Not operational GSM 900 / GSM 1800 / UMTS 2100 262 14 Group 3G UMTS Not operational GSM 900 / GSM 1800 / UMTS 2100 262 15 Airdata Operational GSM 900 / GSM 1800 / UMTS 2100 262 16 vistream Operational Unknown MVNE 262 60 DB Telematik Operational GSM-R 900 262 76 Siemens AG Operational GSM 900 262 77 E-Plus Operational GSM 900 262 901 Debitel Operational Unknown # ISO Country GH Ghana # MCC MNC Brand Operator Status Bands Notes 620 01 MTN ScanCom Ltd Operational GSM 900 / GSM 1800 [44] 620 02 Ghana Telecom Mobile Ghana Telecommunications Company Ltd Operational GSM 900 / GSM 1800 Onetouch 620 03 tiGO Millicom Ghana Limited Operational GSM 900 / GSM 1800 620 04 Kasapa / Hutchison Telecom Operational CDMA # ISO Country GI Gibraltar (United Kingdom) # MCC MNC Brand Operator Status Bands Notes 266 01 GibTel Gibraltar Telecoms Operational GSM 900 # ISO Country GR Greece # MCC MNC Brand Operator Status Bands Notes 202 01 Cosmote COSMOTE - Mobile Telecommunications S.A. Operational GSM 900 / GSM 1800 / UMTS 2100 [45] 202 05 Vodafone Vodafone Greece Operational GSM 900 / GSM 1800 / UMTS 2100 202 09 Wind Wind Hellas Telecommunications S.A. Operational GSM 1800 Q-telecom Discontinued, merged in 202 10 202 10 Wind Wind Hellas Telecommunications S.A. Operational GSM 900 / GSM 1800 / UMTS 2100 # ISO Country GL Greenland (Denmark) # MCC MNC Brand Operator Status Bands Notes 290 01 TELE Greenland A/S Operational GSM 900 [46] # ISO Country GD Grenada # MCC MNC Brand Operator Status Bands Notes 352 030 Digicel Digicel Grenada Ltd. Operational GSM 900 / GSM 1800 [47] 352 110 Cable & Wireless Cable & Wireless Grenada Ltd. Operational GSM 850 # ISO Country GP Guadeloupe (France) # MCC MNC Brand Operator Status Bands Notes 340 01 Orange Orange Caraïbe Mobiles Operational GSM 900 [48] 340 02 Outremer Outremer Telecom Operational GSM 900 / GSM 1800 340 03 Telcell Saint Martin et Saint Barthelemy Telcell Sarl Operational GSM 900 / GSM 1800 340 08 MIO GSM Dauphin Telecom Operational GSM 900 / GSM 1800 340 20 Digicel DIGICEL Antilles Française Guyane Operational GSM 900 Former Bouygues Telecom Caraïbes # ISO Country GU Guam (United States of America) # MCC MNC Brand Operator Status Bands Notes 310 032 IT&E Wireless IT&E Overseas, Inc Operational CDMA 1900 GSM 1900 planned [49] 310 033 Guam Telephone Authority Unknown Unknown 310 140 mPulse GTA Wireless Operational GSM 850 / GSM 1900 310 370 Guamcell Guam Cellular & Paging Inc Operational CDMA 850 311 250 i CAN_GSM Wave Runner LLC Not operational GSM 1900 Planned 310 470 Guamcell Guam Cellular & Paging Inc Operational GSM 1900 / UMTS 850 Formerly HafaTEL. The UMTS 850 service is planned. # ISO Country GT Guatemala # MCC MNC Brand Operator Status Bands Notes 704 01 Claro Servicios de Comunicaciones Personales Inalambricas (SERCOM) Operational CDMA 1900 / GSM 900 / GSM 1900 / UMTS 1900 704 02 Comcel / Tigo Millicom / Local partners Operational GSM 850 / TDMA 800 / UMTS 850 704 03 movistar Telefonica Móviles Guatemala (Telefónica) Operational CDMA 1900 / GSM 1900 % 704 ? digicel Digicel Group Planned GSM 900 # ISO Country GN Guinea # MCC MNC Brand Operator Status Bands Notes 611 01 Spacetel Operational Unknown 611 02 Lagui Sotelgui Lagui Operational GSM 900 611 03 Telecel Guinee INTERCEL Guinée Operational GSM 900 611 04 MTN Areeba Guinea Operational GSM 900 / GSM 1800 # ISO Country GW Guinea-Bissau # MCC MNC Brand Operator Status Bands Notes 632 02 Areeba Spacetel Guiné-Bissau S.A. Operational GSM 900 [50] # ISO Country GY Guyana # MCC MNC Brand Operator Status Bands Notes 738 01 Digicel U-Mobile (Cellular) Inc. Operational GSM 900 # ISO Country HT Haiti # MCC MNC Brand Operator Status Bands Notes 372 010 Comcel / Voila Operational GSM 850 338 050 Digicel Operational GSM 1800 # ISO Country HN Honduras # MCC MNC Brand Operator Status Bands Notes 708 01 Claro Servicios de Comunicaciones de Honduras S.A. de C.V. Operational GSM 1900 708 02 Celtel / Tigo Operational CDMA2000 850 / GSM 850 708 04 DIGICEL Digicel de Honduras Operational GSM 1900 708 30 Hondutel Empresa Hondureña de Telecomunicaciones Operational GSM 1900 # ISO Country HK Hong Kong (People's Republic of China) # MCC MNC Brand Operator Status Bands Notes 454 00 CSL Hong Kong CSL Limited Operational GSM 900 / GSM 1800 454 01 CITIC Telecom 1616 Operational GSM 900 / GSM 1800 MVNO 454 02 CSL 3G Hong Kong CSL Limited Operational UMTS 2100 454 03 3 (3G) Hutchison Telecom Operational UMTS 2100 454 04 3 DualBand (2G) Hutchison Telecom Operational GSM 900 / GSM 1800 454 05 3 CDMA Hutchison Telecom Not Operational CDMA 800 Decommissioned on 19 November 2008 11:59pm 454 06 Smartone-Vodafone SmarTone Mobile Comms Operational GSM 900 / GSM 1800 / UMTS 2100 454 07 China Unicom Operational GSM 900 / GSM 1800 MVNO 454 08 Trident Operational GSM 900 / GSM 1800 MVNO 454 09 China Motion Telecom Operational GSM 900 / GSM 1800 MVNO 454 10 New World Hong Kong CSL Limited Operational GSM 1800 Joint Venture of CSL and Telecom Digital 454 11 China-Hongkong Telecom Operational GSM 900 / GSM 1800 MVNO 454 12 CMCC Peoples China Mobile Hong Kong Company Limited Operational GSM 1800 454 14 Hutchison Telecom Operational GSM 1800 Operational in Airport only 454 15 SmarTone Mobile Comms Operational GSM 1800 Operational in Airport only 454 16 PCCW PCCW Mobile (PCCW Ltd) Operational GSM 1800 454 17 SmarTone Mobile Comms Operational GSM 1800 Operational in Airport only 454 18 Hong Kong CSL Limited Operational GSM 1800 Operational in Airport only 454 19 PCCW PCCW Mobile (PCCW Ltd) Operational UMTS 2100 454 29 PCCW PCCW Mobile (PCCW Ltd) Operational CDMA2000 800 1X EVDO Rev A Roamer's network, coverage limited to Golden Triangle Area around Victoria Harbour # ISO Country HU Hungary # MCC MNC Brand Operator Status Bands Notes 216 01 Pannon Pannon GSM Távközlési Zrt. Operational GSM 900 / GSM 1800 / UMTS 2100 216 30 T-Mobile Magyar Telekom Plc Operational GSM 900 / GSM 1800 / UMTS 2100 216 70 Vodafone Vodafone Magyarország Zrt. Operational GSM 900 / GSM 1800 / UMTS 2100 # ISO Country IS Iceland # MCC MNC Brand Operator Status Bands Notes 274 01 Siminn Iceland Telecom Operational GSM 900 / GSM 1800 / UMTS 2100 Former Landssimi hf 274 02 Vodafone Og fjarskipti hf Operational GSM 900 / GSM 1800 Former Islandssimi ehf 274 03 Vodafone Vodafone Iceland Operational Unknown Former Islandssimi ehf 274 04 Viking IMC Island ehf Operational GSM 1800 274 06 Núll níu ehf Reserved Unknown 274 07 IceCell IceCell ehf Operational GSM 1800 274 08 On-waves Iceland Telecom Operational Unknown On ferries and cruise ships 274 11 Nova Nova ehf Operational UMTS 2100 # ISO Country IN India # MCC MNC Brand Operator Status Bands Notes 404 01 Vodafone - Haryana Vodafone Essar Digilink Limited Operational GSM 900 404 02 Airtel - Punjab Bharti Airtel Operational GSM 900 404 03 Airtel Bharti Airtel Operational GSM 900 / GSM 1800 404 04 Idea - Delhi Idea cellular Operational GSM 1800 404 05 Vodafone - Gujarat Vodafone Essar Gujarat Limited Operational GSM 900 Hutch / Fascel 404 06 Airtel Bharti Airtel Operational Unknown 404 07 TATA Cellular / Idea Cellular Operational GSM 900 404 09 Reliance GSM Operational Unknown 404 10 Airtel - Delhi Bharti Airtel Operational Unknown 404 11 Vodafone - Delhi Vodafone Essar Operational Unknown 404 12 Idea (Escotel) Haryana IDEA Cellular Limited Operational GSM 900 404 13 Vodafone Vodafone Essar South Operational Unknown 404 14 Spice Telecom - Punjab Spice Communications Operational Unknown 404 15 Aircell Digilink Essar Cellph. Operational Unknown 404 20 Vodafone Vodafone Mumbai Operational GSM 900 Formerly Hutchison Maxtouch / Orange / Hutch Mumbai 404 21 BPL Mobile Mumbai Operational GSM 900 404 22 IDEA Cellular - Maharashtra Idea cellular Operational GSM 900 404 24 IDEA Cellular - Gujarat Idea cellular Operational GSM 900 404 27 Vodafone - Maharashtra Vodafone Essar Operational GSM 900 404 28 Aircel - Orissa Dishnet Wireless/Aircel Operational GSM 900 404 30 Vodafone - Kolkata Vodafone Essar East Limited Operational GSM 900 404 31 Airtel - Kolkata Bharti Airtel Operational GSM 900 404 34 BSNL Bharat Sanchar Nigam Limited Operational Unknown 404 36 Reliance Reliance GSM - Bihar & Jharkhand Operational Unknown 404 38 BSNL Bharat Sanchar Nigam Limited Operational Unknown 404 40 Airtel Bharti Airtel Operational Unknown 404 41 Aircel - Chennai Metro Aircel Operational Unknown 404 42 Aircel - Tamil Nadu Aircel Operational Unknown 404 43 Vodafone Vodafone Essar Operational Unknown 404 44 Spice Telecom - Karnataka Spice Communications Limited Operational GSM 900 404 45 Airtel Bharti Airtel Operational Unknown 404 46 Vodafone Operational Unknown Was BPL Cellular 404 49 Airtel Bharti Airtel Operational Unknown 404 50 Reliance Reliance GSM Operational GSM 900 404 51 BSNL Bharat Sanchar Nigam Limited Operational Unknown 404 52 Reliance - Orissa Reliance Telecom Private Operational GSM 900 404 53 BSNL Bharat Sanchar Nigam Limited Operational Unknown 404 54 BSNL Bharat Sanchar Nigam Limited Operational Unknown 404 55 BSNL Bharat Sanchar Nigam Limited Operational Unknown 404 56 Idea - UP West IDEA Cellular Limited Operational GSM 900 404 57 BSNL Bharat Sanchar Nigam Limited Operational Unknown 404 58 BSNL Bharat Sanchar Nigam Limited Operational Unknown 404 59 BSNL Bharat Sanchar Nigam Limited Operational Unknown 404 60 Aircell Digilink Operational Unknown 404 62 BSNL J&K Bharat Sanchar Nigam Limited Operational Unknown 404 64 BSNL Bharat Sanchar Nigam Limited Operational Unknown 404 66 BSNL - Maharashtra BSNL Maharashtra & Goa Operational GSM 900 404 67 Vodafone - West Bengal Vodafone Essar East Operational GSM 900 404 68 MTNL - Delhi Mahanagar Telephone Nigam Ltd Operational GSM 900 404 69 MTNL - Mumbai Mahanagar Telephone Nigam Ltd Operational GSM 900 404 70 Airtel Bharti Airtel Operational Unknown 404 71 BSNL Bharat Sanchar Nigam Limited Operational Unknown 404 72 BSNL Kerala Bharat Sanchar Nigam Limited Operational GSM 900 404 73 BSNL Bharat Sanchar Nigam Limited Operational Unknown 404 74 BSNL - West Bengal Bharat Sanchar Nigam Limited Operational GSM 900 404 75 BSNL Bharat Sanchar Nigam Limited / CellOne Operational Unknown 404 76 BSNL Bharat Sanchar Nigam Limited Operational Unknown 404 77 BSNL Bharat Sanchar Nigam Limited Operational Unknown 404 78 BTA Cellcom Operational Unknown 404 80 BSNL Bharat Sanchar Nigam Limited Operational Unknown 404 81 BSNL - Kolkata Bharat Sanchar Nigam Limited Operational GSM 900 404 82 Idea Idea Operational Unknown 404 83 Reliance Reliance GSM - Kolkata Operational Unknown 404 84 Vodafone - Chennai Metro Vodafone Essar South Operational Unknown 404 85 Reliance Reliance GSM - West Bengal Operational GSM 1800 404 86 Vodafone Vodafone Essar South Operational Unknown 404 87 Escorts Telecom Operational Unknown 404 88 Escorts Telecom Operational Unknown 404 89 Escorts Telecom Operational Unknown 404 90 Airtel - Maharashtra Airtel Maharashtra & Goa Operational GSM 1800 404 92 Airtel Mumbai Operational GSM 1800 404 93 Airtel Gujrat Operational Unknown 404 94 Airtel Bharti Airtel Operational Unknown 404 95 Airtel Bharti Airtel Operational GSM 1800 404 96 Airtel - Haryana Bharti Airtel Operational GSM 1800 404 97 Airtel Bharti Airtel Operational Unknown 404 98 Airtel Bharti Airtel Operational Unknown 405 10 Reliance - Karnataka Reliance GSM Operational GSM 1800 405 13 Reliance - Maharashtra Reliance GSM Operational GSM 1800 405 51 Airtel - West Bengal Bharti Airtel Operational GSM 900 405 52 Airtel-Bihar Bharti Airtel Operational GSM 900 405 753 Vodafone-Orissa Vodafone Essar Operational GSM 1800 405 752 Vodafone-Bihar Vodafone Essar Operational GSM 1800 # ISO Country ID Indonesia # MCC MNC Brand Operator Status Bands Notes 510 00 PSN PT Pasifik Satelit Nusantara (ACeS) Operational Satellite 510 01 INDOSAT PT Indonesian Satellite Corporation Tbk (INDOSAT) Operational GSM 900 / GSM 1800 / UMTS 2100 Formerly PT Satelindo 510 03 StarOne PT Indosat Operational CDMA 800 510 07 TelkomFlexi PT Telkom Operational CDMA 800 510 08 AXIS PT Natrindo Telepon Seluler Operational GSM 1800 / UMTS 2100 510 09 SMART PT Smart Telecom Operational CDMA 1900 510 10 Telkomsel PT Telekomunikasi Selular Operational GSM 900 / GSM 1800 / UMTS 2100 510 11 XL PT Excelcomindo Pratama Operational GSM 900 / GSM 1800 / UMTS 2100 510 20 TELKOMMobile PT Telkom Unknown GSM 1800 Merged with Telkomsel 510 21 IM3 PT Indonesian Satellite Corporation Tbk (INDOSAT) Not operational GSM 1800 Merged with Indosat (MNC 01) MNC 21 not used anymore 510 27 Ceria PT Sampoerna Telekomunikasi Indonesia Operational CDMA 450 510 28 Fren/Hepi PT Mobile-8 Telecom Operational CDMA 800 510 89 3 PT Hutchison CP Telecommunications Operational GSM 1800 / UMTS 2100 510 99 Esia PT Bakrie Telecom Operational CDMA 800 # ISO Country IR Iran # MCC MNC Brand Operator Status Bands Notes 432 11 MCI Mobile Communications Company of Iran Operational GSM 900 / GSM 1800 [52] 432 14 TKC KFZO Operational GSM 900 432 19 MTCE Mobile Telecommunications Company of Esfahan Operational GSM 900 432 32 Taliya Operational GSM 900 432 35 Irancell Irancell Telecommunications Services Company Operational GSM 900 / GSM 1800 # ISO Country IQ Iraq # MCC MNC Brand Operator Status Bands Notes 418 20 Zain IQ Zain Iraq Operational GSM 900 418 30 Zain IQ Zain Iraq Operational GSM 900 [53] 418 50 Asia Cell Asia Cell Telecommunications Company Operational GSM 900 418 40 Korek Korek Telecom Ltd. Operational GSM 900 % 418 ? SanaTel Operational GSM 900 % 418 ? IRAQNA Orascom Telecom Iraq Corporation Operational GSM 900 # ISO Country IE Ireland # MCC MNC Brand Operator Status Bands Notes 272 01 Vodafone Vodafone Ireland Operational GSM 900 / GSM 1800 / UMTS 2100 [54] 272 02 O2 O2 Ireland Operational GSM 900 / GSM 1800 / UMTS 2100 272 03 Meteor Operational GSM 900 / GSM 1800 272 04 Access Telecom Unknown Unknown 272 05 3 Hutchison 3G Ireland limited Operational UMTS 2100 272 07 Eircom Unknown Unknown 272 09 Clever Communications Unknown Unknown # ISO Country IL Israel # MCC MNC Brand Operator Status Bands Notes 425 01 Orange Partner Communications Company Ltd Operational GSM 1800 / UMTS 2100 [55] 425 02 Cellcom Operational TDMA 850 / GSM 1800 / UMTS 2100 425 03 Pelephone Operational CDMA2000 800 / UMTS 850 / 2100 425 77 Mirs Operational iDEN # ISO Country IT Italy # MCC MNC Brand Operator Status Bands Notes 222 01 TIM Telecom Italia SpA Operational GSM 900 / GSM 1800 / UMTS 2100 [56] 222 02 Elsacom Operational Satellite (Globalstar) 222 10 Vodafone Vodafone Omnitel N.V. Operational GSM 900 / GSM 1800 / UMTS 2100 222 30 RFI Rete Ferroviaria Italiana Operational GSM-R 900 222 77 IPSE 2000 Not operational UMTS 2100 Retired 222 88 Wind Wind Telecomunicazioni SpA Operational GSM 900 / GSM 1800 / UMTS 2100 222 98 Blu Not operational GSM 1800 Retired 222 99 3 Italia Hutchison 3G Operational UMTS 2100 # ISO Country CI Ivory Coast # MCC MNC Brand Operator Status Bands Notes 612 01 Cora de Comstar Not operational Unknown 612 02 Moov Operational GSM 900 / GSM 1800 612 03 Orange Operational GSM 900 612 04 KoZ Comium Ivory Coast Inc Operational GSM 900 / GSM 1800 612 05 MTN Operational GSM 900 612 06 ORICEL ORICEL Operational GSM 1800 # ISO Country JM Jamaica # MCC MNC Brand Operator Status Bands Notes 338 020 Cable & Wireless Cable & Wireless Operational GSM 1900 338 050 Digicel Digicel (Jamaica) Limited Operational GSM 900 / GSM 1800 / CDMA2000 1900 338 070 Claro Oceanic Digital Jamaica Limited Operational GSM 850 / GSM 1900 / CDMA 850 338 180 Cable & Wireless Cable & Wireless Operational Unknown # ISO Country JP Japan # MCC MNC Brand Operator Status Bands Notes 440 00 eMobile eMobile, Ltd. Operational UMTS 1700 440 01 DoCoMo NTT DoCoMo Operational UMTS 2100 440 02 DoCoMo NTT DoCoMo Kansai Operational UMTS 2100 440 03 DoCoMo NTT DoCoMo Hokuriku Operational UMTS 2100 440 04 SoftBank SoftBank Mobile Corp Operational UMTS 2100 Formerly known as Vodafone K.K. 440 06 SoftBank SoftBank Mobile Corp Operational UMTS 2100 Formerly known as Vodafone K.K. 440 07 KDDI KDDI Corporation Operational CDMA2000 1X EV-DO Rev.A 440 08 KDDI KDDI Corporation Operational Unknown 440 09 DoCoMo NTT DoCoMo Kansai Operational Unknown 440 10 DoCoMo NTT DoCoMo Kansai Operational UMTS 800 / UMTS 1700 / UMTS 2100 440 11 DoCoMo NTT DoCoMo Tokai Operational Unknown 440 12 DoCoMo NTT DoCoMo Operational Unknown 440 13 DoCoMo NTT DoCoMo Operational Unknown 440 14 DoCoMo NTT DoCoMo Tohoku Operational Unknown 440 15 DoCoMo NTT DoCoMo Operational Unknown 440 16 DoCoMo NTT DoCoMo Operational Unknown 440 17 DoCoMo NTT DoCoMo Operational Unknown 440 18 DoCoMo NTT DoCoMo Tokai Operational Unknown 440 19 DoCoMo NTT DoCoMo Hokkaido Operational Unknown 440 20 SoftBank SoftBank Mobile Corp Operational UMTS 2100 440 21 DoCoMo NTT DoCoMo Operational Unknown 440 22 DoCoMo NTT DoCoMo Kansai Operational Unknown 440 23 DoCoMo NTT DoCoMo Tokai Operational Unknown 440 24 DoCoMo NTT DoCoMo Chugoku Operational Unknown 440 25 DoCoMo NTT DoCoMo Hokkaido Operational Unknown 440 26 DoCoMo NTT DoCoMo Kyushu Operational Unknown 440 27 DoCoMo NTT DoCoMoTohoku Operational Unknown 440 28 DoCoMo NTT DoCoMo Shikoku Operational Unknown 440 29 DoCoMo NTT DoCoMo Operational Unknown 440 30 DoCoMo NTT DoCoMo Operational Unknown 440 31 DoCoMo NTT DoCoMo Kansai Operational Unknown 440 32 DoCoMo NTT DoCoMo Operational Unknown 440 33 DoCoMo NTT DoCoMo Tokai Operational Unknown 440 34 DoCoMo NTT DoCoMo Kyushu Operational Unknown 440 35 DoCoMo NTT DoCoMo Kansai Operational Unknown 440 36 DoCoMo NTT DoCoMo Operational Unknown 440 37 DoCoMo NTT DoCoMo Operational Unknown 440 38 DoCoMo NTT DoCoMo Operational Unknown 440 39 DoCoMo NTT DoCoMo Operational Unknown 440 40 SoftBank SoftBank Mobile Corp Operational Unknown 440 41 SoftBank SoftBank Mobile Corp Operational Unknown 440 42 SoftBank SoftBank Mobile Corp Operational Unknown 440 43 SoftBank SoftBank Mobile Corp Operational Unknown 440 44 SoftBank SoftBank Mobile Corp Operational Unknown 440 45 SoftBank SoftBank Mobile Corp Operational Unknown 440 46 SoftBank SoftBank Mobile Corp Operational Unknown 440 47 SoftBank SoftBank Mobile Corp Operational Unknown 440 48 SoftBank SoftBank Mobile Corp Operational Unknown 440 49 DoCoMo NTT DoCoMo Operational Unknown 440 50 KDDI KDDI Corporation Operational Unknown 440 51 KDDI KDDI Corporation Operational Unknown 440 52 KDDI KDDI Corporation Operational Unknown 440 53 KDDI KDDI Corporation Operational Unknown 440 54 KDDI KDDI Corporation Operational Unknown 440 55 KDDI KDDI Corporation Operational Unknown 440 56 KDDI KDDI Corporation Operational Unknown 440 58 DoCoMo NTT DoCoMo Kansai Operational Unknown 440 60 DoCoMo NTT DoCoMo Kansai Operational Unknown 440 61 DoCoMo NTT DoCoMo Chugoku Operational Unknown 440 62 DoCoMo NTT DoCoMo Kyushu Operational Unknown 440 63 DoCoMo NTT DoCoMo Operational Unknown 440 64 DoCoMo NTT DoCoMo Operational Unknown 440 65 DoCoMo NTT DoCoMo Shikoku Operational Unknown 440 66 DoCoMo NTT DoCoMo Operational Unknown 440 67 DoCoMo NTT DoCoMo Tohoku Operational Unknown 440 68 DoCoMo NTT DoCoMo Kyushu Operational Unknown 440 69 DoCoMo NTT DoCoMo Operational Unknown 440 70 KDDI KDDI Corporation Operational Unknown 440 71 KDDI KDDI Corporation Operational Unknown 440 72 KDDI KDDI Corporation Operational Unknown 440 73 KDDI KDDI Corporation Operational Unknown 440 74 KDDI KDDI Corporation Operational Unknown 440 75 KDDI KDDI Corporation Operational Unknown 440 76 KDDI KDDI Corporation Operational Unknown 440 77 KDDI KDDI Corporation Operational Unknown 440 78 Okinawa Cellular Telephone Operational Unknown 440 79 KDDI KDDI Corporation Operational Unknown 440 80 TU-KA TU-KA Cellular Tokyo Not operational Unknown Closed in March 31, 2008. 440 81 TU-KA TU-KA Cellular Tokyo Not operational Unknown Closed in March 31, 2008. 440 82 TU-KA TU-KA Phone Kansai Not operational Unknown Closed in March 31, 2008. 440 83 TU-KA TU-KA Cellular Tokai Not operational Unknown Closed in March 31, 2008. 440 84 TU-KA TU-KA Phone Kansai Not operational Unknown Closed in March 31, 2008. 440 85 TU-KA TU-KA Cellular Tokai Not operational Unknown Closed in March 31, 2008. 440 86 TU-KA TU-KA Cellular Tokyo Not operational Unknown Closed in March 31, 2008. 440 87 DoCoMo NTT DoCoMo Chugoku Operational Unknown 440 88 KDDI KDDI Corporation Operational Unknown 440 89 KDDI KDDI Corporation Operational Unknown 440 90 SoftBank SoftBank Mobile Corp Operational Unknown 440 92 SoftBank SoftBank Mobile Corp Operational Unknown 440 93 SoftBank SoftBank Mobile Corp Operational Unknown 440 94 SoftBank SoftBank Mobile Corp Operational Unknown 440 95 SoftBank SoftBank Mobile Corp Operational Unknown 440 96 SoftBank SoftBank Mobile Corp Operational Unknown 440 97 SoftBank SoftBank Mobile Corp Operational Unknown 440 98 SoftBank SoftBank Mobile Corp Operational Unknown 440 99 DoCoMo NTT DoCoMo Operational Unknown # ISO Country JO Jordan # MCC MNC Brand Operator Status Bands Notes 416 01 Zain Jordan Mobile Telephone Services Operational GSM 900 [57] 416 02 XPress Telecom Operational iDEN 800 416 03 Umniah Operational GSM 1800 416 77 Orange Petra Jordanian Mobile Telecommunications Company (MobileCom) Operational GSM 900 # ISO Country KZ Kazakhstan # MCC MNC Brand Operator Status Bands Notes 401 01 Beeline KaR-Tel LLP Operational GSM 900 401 02 K'Cell GSM Kazakhstan Ltd Operational GSM 900 401 07 Dalacom Operational CDMA2000 800 401 77 Mobile Telecom Service Mobile Telecom Service LLP Operational GSM 900 # ISO Country KE Kenya # MCC MNC Brand Operator Status Bands Notes 639 02 Safaricom Safaricom Limited Operational GSM 900 / GSM 1800 [58] 639 03 Zain Celtel Kenya Limited Operational GSM 900 [59] 639 07 Orange Kenya Telkom Kenya Operational CDMA2000 / GSM 900 / GSM 1800 # ISO Country KI Kiribati # MCC MNC Brand Operator Status Bands Notes 545 09 Kiribati Frigate Telecom Services Kiribati Ltd Operational GSM 900 # ISO Country KP North Korea # MCC MNC Brand Operator Status Bands Notes 467 193 SUN NET Korea Posts and Telecommunications Corporation Operational GSM 900 [60] # ISO Country KR South Korea # MCC MNC Brand Operator Status Bands Notes 450 02 KTF KTF CDMA Operational CDMA2000 1700 450 03 Digital 017 Shinsegi Telecom, Inc. Not Operational CDMA2000 850 Merged with SK Telecom in 2002 450 04 KTF KTF Operational CDMA2000 1700 450 05 SKT SK Telecom Operational CDMA2000 850 / UMTS 2100 450 06 LGT LG Telecom OperationalCDMA2000 450 08 KTF SHOW KTF] Operational UMTS 2100 # ISO Country -- Kosovo # MCC MNC Brand Operator Status Bands Notes 212 01 Vala PTK - Directory of Post of Kosovo Operational GSM 900 Monaco # MCC. Monaco Telecom is responsible for the delegated management of the GSM network in Kosovo. [62] 293 41 iPKO/Mobitel Mobitel Slovenia Operational GSM 900 / GSM 1800 # ISO Country KW Kuwait # MCC MNC Brand Operator Status Bands Notes 419 02 Zain Mobile Telecommunications Co. Operational GSM 900 / UMTS 2100 419 03 Wataniya National Mobile Telecommunications Operational GSM 900 / GSM 1800 / UMTS 2100 419 04 Viva Kuwait Telecommunication Company Operational GSM 900 / GSM 1800 / UMTS 2100 # ISO Country KG Kyrgyzstan # MCC MNC Brand Operator Status Bands Notes 437 01 Bitel Sky Mobile LLC Operational GSM 900 / GSM 1800 437 05 MegaCom BiMoCom Ltd Operational GSM 900 / GSM 1800 437 09 O! NurTelecom LLC Operational GSM 900 / GSM 1800 # ISO Country LA Laos # MCC MNC Brand Operator Status Bands Notes 457 01 LaoTel Lao Shinawatra Telecom Operational GSM 900 [63] 457 02 ETL Enterprise of Telecommunications Lao Planned GSM 900 457 03 LAT Lao Asia Telecommunication State Enterprise (LAT) Operational GSM 900 / GSM 1800 457 08 Tigo Millicom Lao Co Ltd Operational GSM 900 / GSM 1800 # ISO Country LV Latvia # MCC MNC Brand Operator Status Bands Notes 247 01 LMT Latvian Mobile Telephone Operational GSM 900 / GSM 1800 / UMTS 2100 247 02 Tele2 Tele2 Operational GSM 900 / GSM 1800 / UMTS 2100 247 03 TRIATEL Telekom Baltija Operational CDMA 450 247 05 Bite Bite Latvija Operational GSM 900 / GSM 1800 / UMTS 2100 Bite's postpaid customers are still being assigned SIM cards with 246 02 MNC 247 06 Rigatta Reserved Unknown No own network. Not in commercial use [64] 247 07 MTS Master Telecom Operational MVNO, uses Bite network [65] 247 08 IZZI IZZI Operational MVNO, uses Bite network [66] # ISO Country LB Lebanon # MCC MNC Brand Operator Status Bands Notes 415 01 Alfa Operational GSM 900 415 03 MTC-Touch MIC 2 Operational GSM 900 # ISO Country LS Lesotho # MCC MNC Brand Operator Status Bands Notes 651 01 Vodacom Vodacom Lesotho (Pty) Ltd Operational GSM 900 651 02 Econet Ezin-cel Operational Unknown # ISO Country LR Liberia # MCC MNC Brand Operator Status Bands Notes 618 01 Lonestar Cell Lonestar Communications Corporation Operational GSM 900 618 04 Comium Liberi Operational Unknown 618 20 LIBTELCO Liberia Telecommunications Corporation Operational CDMA 2000 # ISO Country LY Libya # MCC MNC Brand Operator Status Bands Notes 606 00 Libyana Operational GSM 900 606 01 Madar Al Madar Operational GSM 900 # ISO Country LI Liechtenstein # MCC MNC Brand Operator Status Bands Notes 295 01 Swisscom Swisscom Schweiz AG Operational GSM 900 / GSM 1800 [67] 295 02 Orange Orange Liechtenstein AG Operational GSM 1800 / UMTS 2100 295 05 FL1 Mobilkom Liechtenstein AG Operational GSM 900 / GSM 1800 / UMTS 2100 295 77 Tele 2 Belgacom Operational GSM 900 Aka. Tango # ISO Country LT Lithuania # MCC MNC Brand Operator Status Bands Notes 246 01 Omnitel Operational GSM 900 / GSM 1800 / UMTS 2100 246 02 BITE UAB Bité Lietuva Operational GSM 900 / GSM 1800 / UMTS 2100 246 03 Tele 2 Operational GSM 900 / GSM 1800 / UMTS 2100 # ISO Country LU Luxembourg # MCC MNC Brand Operator Status Bands Notes 270 01 LuxGSM P&T Luxembourg Operational GSM 900 / GSM 1800 / UMTS 2100 270 77 Tango Tango SA Operational GSM 900 / GSM 1800 / UMTS 2100 270 99 Voxmobile VOXmobile S.A. Operational GSM 900 / GSM 1800 / UMTS 2100 # ISO Country MO Macau (People's Republic of China) # MCC MNC Brand Operator Status Bands Notes 455 00 SmarTone SmarTone Macao Operational GSM 900 / GSM 1800 [68] 455 01 CTM C.T.M. Telemovel+ Operational GSM 900 / GSM 1800 / UMTS 2100 455 02 China Telecom China Telecom Operational CDMA 800 455 03 3 Hutchison Telecom Operational GSM 900 / GSM 1800 455 04 CTM C.T.M. Telemovel+ Operational UMTS 2100 455 05 3 Hutchison Telecom Operational UMTS 2100 # ISO Country MK Republic of Macedonia # MCC MNC Brand Operator Status Bands Notes 294 01 T-Mobile T-Mobile Makedonija Operational GSM 900 294 02 Cosmofon Operational GSM 900 / UMTS 2100 294 03 Vip VIP Operator Operational GSM 900 / GSM 1800 # ISO Country MG Madagascar # MCC MNC Brand Operator Status Bands Notes 646 01 Zain Celtel Operational GSM 900 / GSM 1800 Former Madacom [69] 646 02 Orange Orange Madagascar S.A. Operational GSM 900 646 04 Telma Telma Mobile S.A. Operational GSM 900 # ISO Country MW Malawi # MCC MNC Brand Operator Status Bands Notes 650 01 TNM Telecom Network Malawi Operational GSM 900 / GSM 1800 [70] 650 10 Zain Celtel Limited Operational GSM 900 [71] # ISO Country MY Malaysia # MCC MNC Brand Operator Status Bands Notes 502 12 Maxis Maxis Communications Berhad Operational GSM 900 / GSM 1800 / UMTS 2100 [72] 502 13 Celcom Celcom Malaysia Sdn Bhd Operational UMTS 2100 [73] 502 16 DiGi DiGi Telecommunications Operational GSM 1800 502 17 Maxis Maxis Communications Berhad Operational Unknown Former Timecel 502 18 U Mobile U Mobile Sdn Bhd Operational UMTS 2100 502 19 Celcom Celcom Malaysia Sdn Bhd Operational GSM 900 / GSM 1800 # ISO Country MV Maldives # MCC MNC Brand Operator Status Bands Notes 472 01 Dhiraagu Dhivehi Raajjeyge Gulhun Operational GSM 900 [74] 472 02 Wataniya Wataniya Telecom Maldives Operational GSM 900 / UMTS 2100 # ISO Country ML Mali # MCC MNC Brand Operator Status Bands Notes 610 01 Malitel Malitel SA Operational GSM 900 [75] 610 02 Orange Orange Mali SA Operational GSM 900 # ISO Country MT Malta # MCC MNC Brand Operator Status Bands Notes 278 01 Vodafone Vodafone Malta Operational GSM 900 / UMTS 2100 278 21 GO Mobisle Communications Limited Operational GSM 1800 / UMTS 2100 # ISO Country MH Marshall Islands # MCC MNC Brand Operator Status Bands Notes % ? ? ? Unknown Unknown # ISO Country MR Mauritania # MCC MNC Brand Operator Status Bands Notes 609 01 Mattel Mattel Operational GSM 900 [76] 609 10 Mauritel Mauritel Mobiles Operational GSM 900 # ISO Country MU Mauritius # MCC MNC Brand Operator Status Bands Notes 617 01 Orange Cellplus Mobile Communications Ltd. Operational GSM 900 617 02 MTNL Mahanagar Telephone (Mauritius) Ltd. Operational Unknown 617 10 Emtel Emtel Ltd Operational GSM 900 # ISO Country MX Mexico # MCC MNC Brand Operator Status Bands Notes 334 01 Nextel Nextel México Operational iDEN 800 334 02 Telcel América Móvil Operational TDMA 850 / GSM 1900 / UMTS 850 334 03 movistar Pegaso Comunicaciones y Sistemas Operational CDMA 1900 / CDMA 850 / GSM 1900 # ISO Country FM Micronesia # MCC MNC Brand Operator Status Bands Notes 550 01 FSM Telecom Operational GSM 900 / GSM 1800 # ISO Country MD Moldova # MCC MNC Brand Operator Status Bands Notes 259 01 Orange Orange Moldova Operational GSM 900 / GSM 1800 / UMTS 2100 Former Voxtel. 259 02 Moldcell Operational GSM 900 / GSM 1800 / UMTS 2100 259 03 Unité Moldtelecom Operational CDMA 450 259 03 IDC Interdnestrcom Operational CDMA 450 / CDMA 800 Unoficially sharing the same MNC with Unité 259 04 Eventis Operational GSM 900 / GSM 1800 UMTS 2100 planning # ISO Country MC Monaco # MCC MNC Brand Operator Status Bands Notes 212 01 Office des Telephones Monaco Telecom Unknown Unknown Used for the Vala network in Kosovo. The GSM Association lists the PTK (P&T Kosovo) website for this network. [77] # ISO Country MN Mongolia # MCC MNC Brand Operator Status Bands Notes 428 99 MobiCom Mobicom Corporation Operational GSM 900 [78] 428 88 Unitel Unitel LLC Operational GSM 900 # ISO Country ME Montenegro # MCC MNC Brand Operator Status Bands Notes 297 01 ProMonte ProMonte GSM Operational GSM 900 / GSM 1800 / UMTS 2100 [79] 297 02 T-Mobile T-Mobile Montenegro LLC Operational GSM 900 297 03 m:tel CG MTEL CG Operational GSM 900 / GSM 1800 / UMTS 2100 # ISO Country MA Morocco # MCC MNC Brand Operator Status Bands Notes 604 00 Méditel Medi Telecom Operational GSM 900 / GSM 1800 [80] 604 01 IAM Ittissalat Al Maghrib (Maroc Telecom) Operational GSM 900 / GSM 1800 / UMTS 2100 # ISO Country MZ Mozambique # MCC MNC Brand Operator Status Bands Notes 643 01 mCel Mocambique Celular S.A.R.L Operational GSM 900 / GSM 1800 [81] 643 04 Vodacom Vodacom Mozambique, S.A.R.L. Unknown GSM 900 / GSM 1800 # ISO Country MM Myanmar # MCC MNC Brand Operator Status Bands Notes 414 01 MPT Myanmar Post and Telecommunication Operational GSM 900 [82] [83] # ISO Country NA Namibia # MCC MNC Brand Operator Status Bands Notes 649 01 MTC MTC Namibia Operational GSM 900 / GSM 1800 649 02 switch Telecom Namibia Operational CDMA 800 649 03 Cell One Telecel Globe (Orascom) Operational GSM 900 / GSM 1800 # ISO Country NR Nauru # MCC MNC Brand Operator Status Bands Notes % 536 Nauru has no mobile network # ISO Country NP Nepal # MCC MNC Brand Operator Status Bands Notes 429 01 Nepal Telecom Operational GSM 900 / GSM 1800 / CDMA 429 02 Mero Mobile Spice Nepal Private Ltd Operational GSM 900 / GSM 1800 429 03 United Telecom Limited Operational CDMA 1900 # ISO Country NL Netherlands (Kingdom of the Netherlands) # MCC MNC Brand Operator Status Bands Notes 204 02 Tele2 Netherlands Not operational GSM 900 / GSM 1800 204 21 NS Railinfrabeheer B.V. Operational GSM-R 204 04 Vodafone Vodafone Netherlands Operational GSM 900 / GSM 1800 / UMTS 2100 204 08 KPN KPN Operational GSM 900 / GSM 1800 / UMTS 2100 204 12 Telfort KPN Operational GSM 900 / GSM 1800 204 16 T-Mobile / Ben T-Mobile Netherlands B.V Operational GSM 900 / GSM 1800 204 20 Orange Nederland T-Mobile Netherlands B.V Operational GSM 1800 / UMTS 2100 # ISO Country AN Netherlands Antilles (Kingdom of the Netherlands) # MCC MNC Brand Operator Status Bands Notes 362 51 Telcell Telcell N.V. Operational GSM 900 St.Maarten [84] 362 69 Digicel Curaçao Telecom N.V. Operational GSM 900 / GSM 1800 Curacao 362 91 UTS Setel N.V. Operational GSM 900 Chippie Land 362 94 Bayòs Bòbò Frus N.V. Operational TDMA PCS Mobile Solutions 362 95 MIO E.O.C.G. Wireless Operational CDMA 850 http://curacao.mio.an % 362 ?? East Caribbean Cellular Operational GSM 900 / GSM 1800 % 362 ?? Antiliano Por N.V. Operational GSM 900 Live from July 2007 # ISO Country NC New Caledonia (France) # MCC MNC Brand Operator Status Bands Notes 546 01 Mobilis OPT New Caledonia Operational GSM 900 [85] # ISO Country NZ New Zealand # MCC MNC Brand Operator Status Bands Notes 530 00 Telecom Telecom New Zealand Not operational AMPS 800 / TDMA 800 AMPS MIN based IMSI's 530 01 Vodafone Vodafone New Zealand Operational GSM 900 / GSM 1800 / UMTS 900 / UMTS 2100 [86] 530 02 Telecom Telecom New Zealand Operational CDMA 800 CDMA network 530 03 Woosh Woosh Wireless New Zealand Operational UMTS 2000 530 04 TelstraClear TelstraClear New Zealand Not operational UMTS 2100 530 05 Telecom Telecom New Zealand Operational UMTS 850 / UMTS 2100 UMTS network 530 24 NZ Comms NZ Communications New Zealand Operational GSM 900 / GSM 1800 / UMTS 2100 # ISO Country NI Nicaragua # MCC MNC Brand Operator Status Bands Notes 710 21 Claro Empresa Nicaragüense de Telecomunicaciones, S.A. Operational UMTS 850 / GSM 1900 [87] 710 30 movistar Telefónica Móviles de Nicaragua S.A. Operational UMTS 850 / GSM 850 / 1900 710 73 SERCOM Servicios de Comunicaciones S.A. Operational UMTS 850 / GSM 1900 Not listed by the GSM Association (Merge with Claro) # ISO Country NE Niger # MCC MNC Brand Operator Status Bands Notes 614 01 SahelCom Operational GSM 900 [88] 614 02 Zain Celtel Niger Operational GSM 900 [89] 614 03 Telecel Telecel Niger SA Operational GSM 900 614 04 Orange Orange Niger Operational GSM 900 / GSM 1800 # ISO Country NG Nigeria # MCC MNC Brand Operator Status Bands Notes 621 20 Zain Celtel Nigeria Ltd Ltd. Operational GSM 900 / GSM 1800 Former V-Mobile [90] 621 30 MTN MTN Nigeria Communications Limited Operational GSM 900 / GSM 1800 621 40 M-Tel Nigerian Mobile Telecommunications Limited Operational GSM 900 / GSM 1800 621 50 Glo Globacom Ltd Operational GSM 900 / GSM 1800 # ISO Country NO Norway # MCC MNC Brand Operator Status Bands Notes 242 01 Telenor Operational GSM 900 / GSM 1800 / UMTS 2100 [91] 242 02 NetCom NetCom GSM Operational GSM 900 / GSM 1800 / UMTS 2100 242 03 MTU Not operational GSM 1800 Closed December 1 2008. Formerly Teletopia (However cells are still powered up as of January 12 242 04 Tele2 MVNO 242 05 Network Norway Operational GSM 900 242 06 Ice Nordisk Mobiltelefon Operational CDMA2000 450 Data services only 242 07 Ventelo Reserved Unknown 242 08 TDC Mobil AS Reserved Unknown 242 09 Barablu Mobile Norway Ltd MVNO 242 20 Jernbaneverket AS Operational GSM-R 900 # ISO Country OM Oman # MCC MNC Brand Operator Status Bands Notes 422 02 Oman Mobile Oman Telecommunications Company Operational GSM 900 / GSM 1800 [92] 422 03 Nawras Omani Qatari Telecommunications Company SAOC Operational GSM 900 / GSM 1800 # ISO Country PK Pakistan # MCC MNC Brand Operator Status Bands Notes 410 01 Mobilink Mobilink-PMCL Operational GSM 900 / GSM 1800 410 03 Ufone Pakistan Telecommunciation Mobile Ltd Operational GSM 900 / GSM 1800 410 04 Zong China Mobile Operational GSM 900 / GSM 1800 Formerly Paktel 410 06 Telenor Telenor Pakistan Operational GSM 900 / GSM 1800 410 07 Warid WaridTel Operational GSM 900 / GSM 1800 410 08 Instaphone Instaphone Operational AMPS # ISO Country PW Palau # MCC MNC Brand Operator Status Bands Notes 552 01 PNCC Palau National Communications Corp. Operational GSM 900 552 80 Palau Mobile Palau Mobile Corporation Operational GSM 1800 # ISO Country PS Palestine # MCC MNC Brand Operator Status Bands Notes 425 05 JAWWAL Palestine Cellular Communications, Ltd. Operational GSM 900 [93] [94] 425 06 Wataniya Wataniya Palestine Mobile Telecommunications Company Reserved GSM 900 / GSM 1800 Planned http://www.wataniya.ps/ # ISO Country PA Panama # MCC MNC Brand Operator Status Bands Notes 714 01 Cable & Wireless Cable & Wireless Panama S.A. Operational GSM 850 [95] 714 02 movistar Telefonica Moviles Panama S.A Operational CDMA2000 850 / GSM 850 714 04 Digicel Digicel (Panama) S.A. Operational GSM 1800/1900 # ISO Country PG Papua New Guinea # MCC MNC Brand Operator Status Bands Notes 537 01 B-Mobile Pacific Mobile Communications Operational GSM 900 % 537 ? Digicel Digicel PNG Operational GSM 900 # ISO Country PY Paraguay # MCC MNC Brand Operator Status Bands Notes 744 01 VOX Hola Paraguay S.A Operational GSM 1900 744 02 Claro AMX Paraguay S.A. Operational GSM 1900 / UMTS 1900 744 04 Tigo Telefonica Celular Del Paraguay S.A. (Telecel) Operational GSM 850 / UMTS 850 744 05 Personal Núcleo S.A Operational GSM 850 / GSM 1900 / UMTS 850 / UMTS 1900 # ISO Country PE Peru # MCC MNC Brand Operator Status Bands Notes 716 06 movistar Telefónica Móviles Perú Operational CDMA 850 / GSM 850 [96] 716 10 Claro América Móvil Perú Operational GSM 1900 716 07 NEXTEL NII Holdings Operational iDEN # ISO Country PH Philippines # MCC MNC Brand Operator Status Bands Notes 515 01 Islacom Innove Communications Inc Operational GSM 900 [97] 515 02 Globe Globe Telecom Operational GSM 900 / GSM 1800 / UMTS 2100 515 03 Smart Gold Smart Communications Inc Operational GSM 900 / GSM 1800 / UMTS 2100 515 05 Digitel Digital Telecommunications Philippines Operational GSM 1800 Aka. Sun Cellular 515 11 ACeS Philippines Unknown Unknown 515 18 Red Mobile Connectivity Unlimited Resource Enterprise Operational UMTS 2100 Formerly ümobile 515 88 Nextel Unknown Unknown # ISO Country PL Poland # MCC MNC Brand Operator Status Bands Notes 260 01 Plus Polkomtel Operational GSM 900 / GSM 1800 / UMTS 2100 260 02 T-Mobile Polska Telefonia Cyfrowa (PTC) Operational GSM 900 / GSM 1800 / UMTS 2100 Former Era 260 03 Orange PTK Centertel Operational GSM 900 / GSM 1800 / UMTS 2100 Former Idea 260 05 PTK Centertel Operational UMTS 2100 Not in use, using MNC 03 260 06 Play P4 Sp. z o.o Operational GSM 900 / UMTS 2100 Also roaming on Polkomtel 2G/3G network 260 07 Netia Netia S.A. Operational GSM 900 / UMTS 2100 MVNO roaming on Play (P4) network 260 08 E-Telko Not operational Unknown 260 09 Telekomunikacja Kolejowa Not operational GSM-R 900 railways communication 260 10 Sferia Sferia S.A. Operational UMTS 850 Formerly assigned to Telefony Opalenickie S.A. 260 11 Nordisk Polska Nordisk Polska Sp. z o.o. Operational CDMA 420 260 12 Cyfrowy Polsat Cyfrowy Polsat S.A. Operational GSM 900 / GSM 1800 / UMTS 2100 MVNO using national roaming with several operators 260 14 CenterNet CenterNet S.A. Operational UMTS 1800 LTE1800 with 20 MHz channels. Used wRodzinie brand cards until 2010, now data transmission only. 260 15 Mobyland Mobyland Sp. z o.o. Operational UMTS 1800 LTE1800 with 20 MHz channels 260 16 Aero2 Aero 2 Sp. z o.o. Operational UMTS 900 # ISO Country PT Portugal # MCC MNC Brand Operator Status Bands Notes 268 01 Vodafone Vodafone Portugal Operational GSM 900 / GSM 1800 / UMTS 2100 268 03 Optimus Sonaecom \u2013 Serviços de Comunicações, S.A. Operational GSM 900 / GSM 1800 / UMTS 2100 268 06 TMN Telecomunicações Móveis Nacionais Operational GSM 900 / GSM 1800 / UMTS 2100 % 268 ? Zapp Zapp Portugal Operational CDMA2000 450 # ISO Country PR Puerto Rico # MCC MNC Brand Operator Status Bands Notes 330 11 Claro Puerto Rico Telephone Company Operational GSM 850 / GSM 1900 / UMTS 850 # ISO Country QA Qatar # MCC MNC Brand Operator Status Bands Notes 427 01 Qatarnet Q-Tel Operational GSM 900 / GSM 1800 # ISO Country RE Réunion (France) # MCC MNC Brand Operator Status Bands Notes 647 00 Orange Orange La Réunion Operational GSM 900 / GSM 1800 [99] 647 02 Outremer Outremer Telecom Operational GSM 900 / GSM 1800 647 10 SFR Reunion Societe Reunionnaise de Radiotelephone Operational GSM 900 # ISO Country RO Romania # MCC MNC Brand Operator Status Bands Notes 226 01 Vodafone Vodafone România Operational GSM 900 / GSM 1800 / UMTS 2100 Former Conex 226 03 Cosmote Cosmote România Operational GSM 900 / GSM 1800 226 04 Zapp Telemobil Operational CDMA 450 226 05 DIGI.mobil RCS&RDS Operational UMTS 2100 226 06 Zapp Telemobil Reserved UMTS 2100 UMTS 2100 is under testing 226 10 Orange Orange România Operational GSM 900 / GSM 1800 / UMTS 2100 Former Dialog # ISO Country RU Russian Federation # MCC MNC Brand Operator Status Bands Notes 250 01 MTS Mobile TeleSystems Operational GSM 900 / GSM 1800 / UMTS 2100 250 02 MegaFon MegaFon OJSC Operational GSM 900 / GSM 1800 / UMTS 2100 250 03 NCC Nizhegorodskaya Cellular Communications Operational GSM 900 / GSM 1800 250 04 Sibchallenge Sibchallenge Not operational GSM 900 250 05 ETK Yeniseytelecom Operational GSM 900 / GSM 1800 Mobile Communications Systems 250 07 SMARTS Zao SMARTS Operational GSM 900 / GSM 1800 250 09 Skylink Khabarovsky Cellular Phone Operational CDMA 450 250 10 DTC Dontelekom Not operational GSM 900 250 11 Orensot Not operational Unknown 250 12 Baykalwestcom Baykal Westcom / New Telephone Company / Far Eastern Cellular Operational GSM 900 / GSM 1800 250 13 KUGSM Kuban GSM Not operational GSM 900 / GSM 1800 250 14 SMARTS SMARTS Ufa Operational GSM 1800 250 16 NTC New Telephone Company Operational GSM 900 / GSM 1800 250 17 Utel JSC Uralsvyazinform Operational GSM 900 / GSM 1800 Former Ermak RMS 250 19 INDIGO INDIGO Operational GSM 1800 Former Volgograd Mobile 250 20 Tele2 Tele2 Operational GSM 900 / GSM 1800 250 23 Mobicom - Novosibirsk Mobicom - Novosibirsk Operational GSM 900 / GSM 1800 250 28 Beeline Beeline Not operational GSM 900 Former EXTEL 250 35 MOTIV MOTIV Not operational GSM 1800 250 39 Utel Uralsvyazinform Operational GSM 900 / GSM 1800 250 44 Stavtelesot / North Caucasian GSM Not operational Unknown 250 92 Primtelefon Not operational Unknown 250 93 Telecom XXI Not operational Unknown 250 99 Beeline OJSC VimpelCom Operational GSM 900 / GSM 1800 / UMTS 2100 % 250 ? SkyLink/MTS/the Moscow Cellular communication Unknown CDMA 450 % 250 ? Akos Unknown GSM 1800 # ISO Country RW Rwanda # MCC MNC Brand Operator Status Bands Notes 635 10 MTN MTN Rwandacell SARL Operational GSM 900 [100] # ISO Country KN Saint Kitts and Nevis # MCC MNC Brand Operator Status Bands Notes 356 050 Digicel Unknown GSM 900 / GSM 1800 / GSM 1900 356 110 Cable & Wireless Unknown GSM 850 / GSM 1900 # ISO Country LC Saint Lucia # MCC MNC Brand Operator Status Bands Notes 358 050 Digicel Unknown GSM 900 / GSM 1800 / GSM 1900 358 110 Cable & Wireless Unknown GSM 850 # ISO Country PM Saint Pierre and Miquelon (France) # MCC MNC Brand Operator Status Bands Notes 308 01 Ameris St. Pierre-et-Miquelon Télécom Operational GSM 900 [101] # ISO Country VC Saint Vincent and the Grenadines # MCC MNC Brand Operator Status Bands Notes 360 070 Digicel Unknown GSM 900 / GSM 1800 / GSM 1900 360 100 Cingular Wireless Unknown GSM 850 360 110 Cable & Wireless Cable & Wireless Unknown GSM 850 # ISO Country WS Samoa # MCC MNC Brand Operator Status Bands Notes 549 01 Digicel Digicel Pacific Ltd. Operational GSM 900 [102] 549 27 SamoaTel SamoaTel Ltd Operational GSM 900 # ISO Country SM San Marino # MCC MNC Brand Operator Status Bands Notes 292 01 PRIMA San Marino Telecom Operational GSM 900 / GSM 1800 / UMTS 2100 [103] [104] # ISO Country ST Sao Tome and Principe # MCC MNC Brand Operator Status Bands Notes 626 01 CSTmovel Companhia Santomese de Telecomunicaçôe Operational GSM 900 # ISO Country SA Saudi Arabia # MCC MNC Brand Operator Status Bands Notes 420 01 STC Saudi Telecom Company Operational GSM 900 / UMTS 2100 Al Jawwal [105] 420 03 Mobily Etihad Etisalat Company Operational GSM 900 / UMTS 2100 420 07 EAE Electronics App' Est. Unknown Unknown 420 04 Zain SA MTC Saudi Arabia Operational GSM 900 / GSM 1800 / UMTS 2100 Active September 2008 # ISO Country SN Senegal # MCC MNC Brand Operator Status Bands Notes 608 01 Sonatel ALIZE Operational Unknown 608 02 Sentel GSM Operational Unknown # ISO Country RS Serbia # MCC MNC Brand Operator Status Bands Notes 220 01 Telenor Telenor Serbia Operational GSM 900 / GSM 1800 / UMTS 2100 220 03 Telekom Srbija Telekom Srbija Operational GSM 900 / GSM 1800 / UMTS 2100 220 05 VIP Mobile Operational GSM 900 / GSM 1800 / UMTS 2100 # ISO Country SC Seychelles # MCC MNC Brand Operator Status Bands Notes 633 01 Cable & Wireless Cable & Wireless (Seychelles) Ltd. Operational Unknown 633 02 Mediatech International Mediatech International Ltd. Operational GSM 1800 633 10 Telecom Airtel Operational Unknown # ISO Country SL Sierra Leone # MCC MNC Brand Operator Status Bands Notes 619 01 Zain Operational Unknown Former Celtel [106] 619 02 Millicom Operational Unknown 619 03 Datatel Operational Unknown 619 04 Comium Comium Sierra leone INC Operational Unknown 619 05 Africell Operational Unknown 619 25 Mobitel Operational Unknown % 619 ? LeoneCel Sierratel Reserved Unknown # ISO Country SG Singapore # MCC MNC Brand Operator Status Bands Notes 525 01 SingTel Singapore Telecom Operational GSM 900 / GSM 1800 / UMTS 2100 [107] 525 02 SingTel-G18 Singapore Telecom Operational GSM 1800 525 03 M1 MobileOne Asia Operational GSM 900 / GSM 1800 / UMTS 2100 525 05 StarHub StarHub Mobile Operational GSM 1800 / UMTS 2100 525 12 Digital Trunked Radio Network Operational iDEN 800 # ISO Country SK Slovakia # MCC MNC Brand Operator Status Bands Notes 231 01 Orange Orange Slovensko Operational GSM 900 / GSM 1800 / UMTS 2100 [108] 231 02 T-Mobile T-Mobile Slovensko Operational GSM 900 / GSM 1800 Former Eurotel 231 03 Unient Communications Unknown Unknown 231 04 T-Mobile T-Mobile Slovensko Operational UMTS 2100 231 05 Mobile Entertainment Company Unknown UMTS 2100 231 06 O2 Telefónica O2 Slovakia Operational GSM 900 / GSM 1800 / UMTS 2100 231 99 \u017dSR Unknown Unknown # ISO Country SI Slovenia # MCC MNC Brand Operator Status Bands Notes 293 40 Si.mobil SI.MOBIL d.d. Operational GSM 900 / GSM 1800 / UMTS 2100 [109] 293 41 Mobitel Mobitel D.D. Operational GSM 900 / GSM 1800 / UMTS 2100 293 64 T-2 T-2 d.o.o. Operational UMTS 2100 293 70 Tušmobil Tušmobil d.o.o. Operational GSM 900 / GSM 1800 / UMTS 2100 # ISO Country SB Solomon Islands # MCC MNC Brand Operator Status Bands Notes 540 1 BREEZE Solomon Telekom Co Ltd Operational GSM 900 [110] # ISO Country SO Somalia # MCC MNC Brand Operator Status Bands Notes 637 04 Somafone Somafone FZLLC Operational GSM 900 / GSM 1800 637 10 Nationlink Operational GSM 900 [111] 637 19 Hormuud Hormuud Telecom Somalia Inc Operational GSM 900 Uncertain MNC number 637 30 Golis Golis Telecommunications Company Operational GSM 900 637 62 Telcom Mobile Telecom Mobile Operational GSM 900 637 65 Telcom Mobile Telecom Mobile Operational GSM 900 637 82 Telcom Somalia Telcom Somalia Operational GSM 900 / GSM 1800 / CDMA # ISO Country ZA South Africa # MCC MNC Brand Operator Status Bands Notes 655 01 Vodacom Operational GSM 900 / UMTS 2100 655 02 Telkom Operational WCDMA 800 655 06 Sentech Operational Unknown 655 07 Cell C Operational GSM 900 / GSM 1800 655 10 MTN MTN Group Operational GSM 900 / UMTS 2100 655 11 SAPS Gauteng Operational TETRA 410 655 13 Neotel Operational CDMA 800 655 21 Cape Town Metropolitan Council Operational TETRA 410 655 30 Bokamoso Consortium Operational Unknown 655 31 Karabo Telecoms (Pty) Ltd. Operational Unknown 655 32 Ilizwi Telecommunications Operational Unknown 655 33 Thinta Thinta Telecommunications Operational Unknown # ISO Country ES Spain # MCC MNC Brand Operator Status Bands Notes 214 01 Vodafone Vodafone Spain Operational GSM 900 / GSM 1800 / UMTS 2100 214 03 Orange France Telecom España SA Operational GSM 900 / GSM 1800 / UMTS 2100 214 04 Yoigo Xfera Moviles SA Operational UMTS 2100 214 05 TME Telefónica Móviles España Operational GSM 900 / GSM 1800 / UMTS 2100 Used by resellers 214 06 Vodafone Vodafone Spain Operational GSM 900 / GSM 1800 / UMTS 2100 Used by resellers 214 07 movistar Telefónica Móviles España Operational GSM 900 / GSM 1800 / UMTS 2100 214 08 Euskaltel Operational GSM 900 / GSM 1800 / UMTS 2100 MVNO 214 09 Orange France Telecom España SA Operational GSM 900 / GSM 1800 / UMTS 2100 Used by resellers 214 15 BT BT Group Operational GSM 900 / GSM 1800 / UMTS 2100 MVNO 214 16 TeleCable Operational GSM 900 / GSM 1800 / UMTS 2100 MVNO 214 18 ONO Operational GSM 900 / GSM 1800 / UMTS 2100 MVNO 214 19 Simyo Operational ? MVNO 214 22 DigiMobil Best Spain Telecom Operational MVNO 214 23 Barablu BARABLU MÓVIL ESPAÑA MVNO # ISO Country LK Sri Lanka # MCC MNC Brand Operator Status Bands Notes 413 01 Mobitel Mobitel Lanka Ltd. Operational GSM 1800 / UMTS 2100 HSPA [112] 413 02 Dialog Dialog Telekom PLC. Operational GSM 900 / GSM 1800 / UMTS 2100 413 03 Tigo Celtel Lanka Ltd Operational GSM 900 413 08 Hutch Sri Lanka Operational GSM 900 Not listed by GSM Association 413 05 AirTel Sri Lanka Bharathi AirTel Reserved GSM 900 / UMTS 2100 % 413 RTEC Mobile Relaince India(Ambhani Group) Not operational Unkown Planned to launch in 2009 # ISO Country SD Sudan # MCC MNC Brand Operator Status Bands Notes 634 01 Mobitel / Mobile Telephone Company Operational GSM 900 634 02 MTN MTN Sudan Operational GSM 900 / GSM 1800 / UMTS 2100 % 634 ? Vivacell Wawat Securities Operational Unknown # ISO Country SR Suriname # MCC MNC Brand Operator Status Bands Notes 746 02 Telesur Operational Unknown # ISO Country SZ Swaziland # MCC MNC Brand Operator Status Bands Notes 653 10 Swazi MTN Operational Unknown # ISO Country SE Sweden # MCC MNC Brand Operator Status Bands Notes 240 01 Telia TeliaSonera Mobile Networks Operational GSM 900 / GSM 1800 240 02 3 Operational UMTS 2100 240 03 Ice.net Nordisk Mobiltelefon Operational CDMA 450 240 04 3G Infrastructure Services Operational UMTS 2100 240 05 Sweden 3G Operational UMTS 2100 Owned by Telia and Tele2 240 06 Telenor Operational UMTS 2100 240 07 Tele2 Tele2 AB Operational GSM 900 / GSM 1800 240 08 Telenor Operational GSM 900 / GSM 1800 240 09 Telenor Mobile Sverige Operational Unknown 240 10 SpringMobil Operational Unknown 240 11 Lindholmen Science Park Unknown Unknown 240 12 Barablu Mobile Scandinavia Unknown Unknown 240 13 Ventelo Sverige Unknown Unknown 240 14 TDC Mobil Unknown Unknown 240 15 Wireless Maingate Nordic Unknown Unknown 240 16 42IT perational Unknown 240 20 Wireless Maingate Message Services Operational Unknown 240 21 Banverket Operational GSM-R 900 # ISO Country CH Switzerland # MCC MNC Brand Operator Status Bands Notes 228 01 Swisscom Swisscom Ltd Operational GSM 900 / GSM 1800 / UMTS 2100 [113] 228 02 Sunrise Sunrise Communications AG Operational GSM 900 / GSM 1800 / UMTS 2100 228 03 Orange Orange Communications SA Operational GSM 1800 / UMTS 2100 228 05 Togewanet AG (Comfone) Reserved Unknown Planned 228 06 SBB AG Operational GSM-R 900 228 07 IN&Phone IN&Phone SA Operational GSM 1800 228 08 Tele2 Tele2 Telecommunications AG Operational GSM 1800 228 50 3G Mobile AG Reserved UMTS 2100 Planned 228 51 BebbiCell AG Operational MVNO also for Paging # ISO Country SY Syria # MCC MNC Brand Operator Status Bands Notes 417 01 SyriaTel Operational GSM 900 / GSM 1800 [114] 417 02 MTN Syria MTN Syria (JSC) Operational GSM 900 # ISO Country TW Taiwan # MCC MNC Brand Operator Status Bands Notes 466 01 FarEasTone Far EasTone Telecommunications Co Ltd Operational GSM 900 / GSM 1800 / UMTS 2100 [115] 466 02 APTG Asia Pacific Telecom Operational CDMA 800 466 06 Tuntex Tuntex Telecom Operational GSM 1800 466 11 Chunghwa LDM LDTA/Chungwa Telecom Operational Unknown 466 88 KG Telecom KG Telecom Operational GSM 1800 Roaming with FarEasTone 466 89 VIBO VIBO Telecom Operational UMTS 2100 466 92 Chungwa Chunghwa Operational GSM 900 / GSM 1800 / UMTS 2100 466 93 MobiTai Mobitai Communications Operational GSM 900 Roaming with Taiwan Mobile 466 97 Taiwan Mobile Taiwan Mobile Co. Ltd Operational GSM 1800 / UMTS 2100 466 99 TransAsia TransAsia Telecoms Operational GSM 900 Roaming with Taiwan Mobile # ISO Country TJ Tajikistan # MCC MNC Brand Operator Status Bands Notes 436 01 Somoncom JV Somoncom Operational GSM 900 [116] 436 02 Indigo Indigo Tajikistan Operational GSM 900 / GSM 1800 / UMTS 2100 436 03 MLT TT Mobile, Closed joint-stock company Operational GSM 900 / GSM 1800 436 04 Babilon-M CJSC Babilon-Mobile Operational GSM 900 / GSM 1800 / UMTS 2100 436 05 Beeline TJ Co Ltd. Tacom Operational GSM 900 / GSM 1800 / UMTS 2100 http://www.beeline.tj # ISO Country TZ Tanzania # MCC MNC Brand Operator Status Bands Notes 640 01 Tritel Not operational Unknown 640 02 Mobitel MIC Tanzania Limited Operational GSM 900 / GSM 1800 640 03 Zantel Zanzibar Telecom Ltd Operational GSM 900 / GSM 1800 640 04 Vodacom Vodacom Tanzania Limited Operational GSM 900 / GSM 1800 640 05 Zain Celtel Tanzania Limited Operational Unknown Former Celtel [117] # ISO Country TH Thailand # MCC MNC Brand Operator Status Bands Notes 520 00 CAT CDMA Operational CDMA 850 520 01 Advanced Info Service Operational GSM 900 520 10 WCS IQ Unknown Unknown 520 15 ACT Mobile Operational GSM 1900 520 18 DTAC Total Access Communication Operational GSM 1800 520 23 Advanced Info Service Operational GSM 1800 520 99 True Move Operational GSM 1800 # ISO Country TG Togo # MCC MNC Brand Operator Status Bands Notes 615 01 Togo Cell Togo Telecom Operational GSM 900 [118] 615 05 Telecel Telecel Togo Operational GSM 900 # ISO Country TO Tonga # MCC MNC Brand Operator Status Bands Notes 539 01 Tonga Communications Corporation Operational Unknown 539 43 Shoreline Communication Operational Unknown % 539 ? Digicel Operational GSM # ISO Country TT Trinidad and Tobago # MCC MNC Brand Operator Status Bands Notes 374 12 bmobile TSTT Operational TDMA 850 / GSM 1800 374 13 Digicel Operational GSM 850 / GSM 1900 # ISO Country TN Tunisia # MCC MNC Brand Operator Status Bands Notes 605 02 Tunicell Tunisie Telecom Operational GSM 900 / GSM 1800 605 03 Tunisiana Orascom Telecom Tunisie Operational GSM 900 / GSM 1800 # ISO Country TR Turkey # MCC MNC Brand Operator Status Bands Notes 286 01 Turkcell Turkcell Iletisim Hizmetleri A.S. Operational GSM 900 286 02 Vodafone Vodafone Turkey Operational GSM 900 Formerly known as Telsim 286 03 Avea Operational GSM 1800 Formerly Aria and Aycell 286 04 Aycell Not operational GSM 1800 Merged into Aria to form Avea # ISO Country TM Turkmenistan # MCC MNC Brand Operator Status Bands Notes 438 01 MTS Barash Communication Technologies Operational GSM 900 / GSM 1800 438 02 TM-Cell Operational GSM 900 / GSM 1800 # ISO Country TV Tuvalu # MCC MNC Brand Operator Status Bands Notes Tuvalu has no mobile networks # ISO Country UG Uganda # MCC MNC Brand Operator Status Bands Notes 641 01 Zain Celtel Cellular Operational Unknown [119] 641 10 MTN MTN Uganda Operational GSM 900 / GSM 1800 641 11 Uganda Telecom Ltd. Operational Unknown 641 22 Warid Telecom Warid Telecom Operational GSM 900 / GSM 1800 641 14 Orange Orange Uganda Operational GSM 900 / GSM 1800 Former HITS Telecom # ISO Country UA Ukraine # MCC MNC Brand Operator Status Bands Notes 255 01 MTS Ukrainian Mobile Communications Operational GSM 900 / GSM 1800 / CDMA 450 Former UMC 255 02 Beeline Ukrainian Radio Systems Operational GSM 900 / GSM 1800 Former WellCOM 255 03 Kyivstar Kyivstar GSM JSC Operational GSM 900 / GSM 1800 255 04 IT Intertelecom Operational CDMA 800 255 05 Golden Telecom Golden Telecom Operational GSM 1800 255 06 life:) Astelit Operational GSM 900 / GSM 1800 255 07 Utel Ukrtelecom Operational UMTS 2100 255 21 PEOPLEnet Telesystems of Ukraine Operational CDMA 800 255 23 CDMA Ukraine ITC Operational CDMA 800 # ISO Country AE United Arab Emirates # MCC MNC Brand Operator Status Bands Notes 424 02 Etisalat E mirates Telecom Corp Operational GSM 900 / UMTS 2100 [120] 424 03 du Emirates Integrated Telecommunications Company Operational GSM 900 / GSM 1800 / UMTS 2100 # ISO Country GB United Kingdom # MCC MNC Brand Operator Status Bands Notes 234 00 BT British Telecom Operational GSM 900 / GSM 1800 / UMTS 2100 [121] 234 01 UK01 Mapesbury Communications Ltd. Operational GSM 1800 234 02 O2 Operational GSM 900 / GSM 1800 / UMTS 2100 234 03 Jersey Telenet Operational GSM 900 234 04 FMS Solutions Ltd FMS Solutions Ltd Planned GSM 1800 234 08 OnePhone Ltd 234 10 O2 Telefónica O2 UK Limited Operational GSM 900 / GSM 1800 / UMTS 2100 234 11 O2 Telefónica Europe Operational GSM 900 / GSM 1800 / UMTS 2100 234 12 Railtrack Network Rail Infrastructure Ltd Operational GSM 900 / GSM 1800 / UMTS 2100 234 15 Vodafone Vodafone United Kingdom Operational GSM 900 / GSM 1800 / UMTS 2100 234 16 Opal Telecom Ltd Opal Telecom Ltd Operational GSM 900 / GSM 1800 / UMTS 2100 234 18 Cloud9 Wire9 Telecom plc Operational GSM 900 / GSM 1800 / UMTS 2100 234 19 Telaware Telaware plc Operational GSM 1800 234 20 3 Hutchison 3G UK Ltd Operational UMTS 2100 234 22 Routo Telecom Unknown Unknown 234 30 T-Mobile Operational GSM 1800 / UMTS 2100 234 31 Virgin Virgin Mobile Operational GSM 900 / GSM 1800 / UMTS 2100 234 32 Virgin Virgin Mobile Operational GSM 900 / GSM 1800 / UMTS 2100 234 33 Orange Orange UK Operational GSM 1800 / UMTS 2100 234 34 Orange Orange UK Operational GSM 1800 / UMTS 2100 234 50 JT-Wave Jersey Telecom Operational GSM 900 / GSM 1800 / UMTS 2100 234 55 Cable & Wireless Guernsey / Sure Mobile (Jersey) Operational GSM 900 (Guernsey) / GSM 1800 (Jersey) / UMTS 2100 234 58 Manx Telecom Operational GSM 900 / GSM 1800 / UMTS 2100 234 75 Inquam Inquam Telecom (Holdings) Ltd Operational GSM 900 / GSM 1800 234 77 Unknown Operational GSM 900 / GSM 1800 # ISO Country US United States of America # MCC MNC Brand Operator Status Bands Notes 310 000 Mid-Tex Cellular Operational Unknown 310 004 Verizon Verizon Wireless Operational Unknown 310 010 MCI Not operational Unknown 310 012 Verizon Verizon Wireless Operational CDMA 850 / CDMA 1900 310 013 MobileTel Unknown Unknown 310 014 Testing Operational Unknown 310 016 Cricket Communications Operational CDMA / EV-DO 1900 1700 310 017 North Sight Communications Inc. Operational Unknown 310 020 Union Telephone Company Operational GSM 850 / GSM 1900 Or APC Sprint Spectrum 310 026 T-Mobile Operational GSM 1900 / UMTS 1700 Formerly Cook Inlet Voicestream 310 030 Centennial Centennial Communications Operational GSM 850 310 034 Airpeak Operational Unknown Formerly Nevada Wireless 310 038 AT&T AT&T Mobility Operational GSM 850 / UMTS 850 / UMTS 1900 310 040 Concho Concho Cellular Telephone Co., Inc. Operational GSM 1900 310 046 SIMMETRY TMP Corp Operational GSM 1900 310 060 Consolidated Telcom Operational Unknown 310 070 Highland Cellular Operational Unknown Cellular One reseller 310 080 Corr Corr Wireless Communications LLC Operational GSM 1900 310 090 AT&T Operational GSM 1900 / UMTS 1900 Formerly Edge Wireless 310 100 Plateau Wireless New Mexico RSA 4 East Ltd. Partnership Operational GSM 850 310 110 PTI Pacifica PTI Pacifica Inc. Operational GSM 850 310 120 Sprint Unknown CDMA 310 150 AT&T AT&T Mobility Operational GSM 850 / UMTS 850 / UMTS 1900 Formerly Bell South Mobility DCS 310 160 T-Mobile Not operational GSM 1900 310 170 T-Mobile Not operational GSM 1900 Formerly Cingular CA/NV, also Pacific Bell Wireless 310 180 West Central West Central Wireless Operational GSM 850 / UMTS 850 / UMTS 1900 310 190 Dutch Harbor Alaska Wireless Communications, LLC Operational GSM 850 310 200 T-Mobile Not operational GSM 1900 Formerly Smith Bagley 310 210 T-Mobile Not operational GSM 1900 Iowa 310 220 T-Mobile Not operational GSM 1900 Kansas / Oklahoma 310 230 T-Mobile Not operational GSM 1900 Utah 310 240 T-Mobile Not operational GSM 1900 New Mexico / Texas / Arizona 310 250 T-Mobile Not operational GSM 1900 Hawaii 310 260 T-Mobile Operational GSM 1900 Universal USA code 310 270 T-Mobile Not operational GSM 1900 Formerly Powertel 310 280 T-Mobile Not operational GSM 1900 310 290 T-Mobile Not operational GSM 1900 310 300 Get Mobile Inc Get Mobile Inc Operational GSM 1900 310 310 T-Mobile Not operational GSM 1900 Formerly Aerial Communications 310 311 Farmers Wireless Operational GSM 1900 310 320 Cellular One Smith Bagley, Inc. Unknown GSM 1900 310 330 T-Mobile Unknown GSM 1900 310 340 Westlink Westlink Communications Operational GSM 1900 Kansas 310 350 Carolina Phone Not operational GSM 1900 310 380 AT&T Mobility Not operational GSM 850 / UMTS 850 / UMTS 1900 Was Cingular Wireless 310 390 Cellular One of East Texas TX-11 Acquisition, LLC Operational Unknown 310 400 i CAN_GSM Wave Runner LLC (Guam) Operational GSM 1900 310 410 AT&T AT&T Mobility Operational GSM 850 / UMTS 850 / UMTS 1900 Was Cingular 310 420 Cincinnati Bell Cincinnati Bell Wireless Operational GSM 1900 310 430 Alaska Digitel Operational GSM 1900 310 440 Cellular One Unknown GSM 1900 310 450 Viaero Viaero Wireless Operational GSM 850 310 460 Simmetry TMP Corporation Operational GSM 1900 310 480 Choice Phone Operational Unknown 310 490 SunCom Operational Unknown 310 500 Alltel Operational CDMA / EV-DO 310 510 Airtel Airtel Wireless Operational Unknown Formerly PSC Wireless 310 520 VeriSign Unknown Unknown 310 530 West Virginia Wireless Operational Unknown 310 540 Oklahoma Western Oklahoma Western Telephone Company Operational GSM 1900 310 560 AT&T AT&T Mobility Operational GSM 850 Formerly Cellular One DCS, Dobson 310 570 Cellular One MTPCS, LLC Operational GSM 1900 Formerly Chinook Wireless 310 580 T-Mobile Not operational Unknown Formerly PCS One, discontinued 310 590 Alltel Alltel Communications Inc Operational GSM 850 / GSM 1900 GSM roamer network only 310 610 Epic Touch Elkhart Telephone Co. Operational GSM 1900 310 620 Coleman County Telecom Coleman County Telecommunications Operational GSM 1900 310 630 AmeriLink PCS Choice Wireless Operational Unknown 310 640 Airadigm Airadigm Communications Operational GSM 1900 310 650 Jasper Jasper Wireless, inc Operational GSM 850 310 660 T-Mobile Not operational GSM 1900 Formerly DigiPhone PCS / DigiPH 310 670 Northstar Operational Unknown 310 680 AT&T AT&T Mobility Operational GSM 850 / GSM 1900 formerly Cellular One DCS, NPI Wireless 310 690 Conestoga Conestoga Wireless Company Operational Unknown 310 730 SeaMobile Operational Unknown 310 740 Convey Convey Communications Inc. Operational Unknown 310 760 Panhandle Panhandle Telecommunications Systems Inc. Operational Unknown 310 770 i wireless Iowa Wireless Services Operational GSM 1900 310 780 Airlink PCS Not operational Unknown 310 790 PinPoint PinPoint Communications Operational GSM 1900 310 800 T-Mobile Not operational GSM 1900 Formerly SOL Communications 310 830 Caprock Caprock Cellular Operational GSM 850 310 850 Aeris Aeris Communications, Inc. Operational CDMA2000 850 / CDMA2000 1900 / GSM 850 / GSM 1900 310 870 PACE Kaplan Telephone Company Operational GSM 850 310 880 Advantage Advantage Cellular Systems Operational GSM 850 310 890 Unicel Rural Cellular Corporation Operational GSM 850 / GSM 1900 Former Unicel markets split between AT&T Mobility and Verizon Wireless 310 900 Taylor Taylor Telecommunications Operational GSM 850 310 910 First Cellular First Cellular of Southern Illinois Operational GSM 850 310 940 Digital Cellular Digital Cellular of Texas Operational Unknown 310 950 XIT Wireless Texas RSA 1 dba XIT Cellular Operational GSM 850 310 960 Plateau Wireless Operational Unknown 310 970 Globalstar Operational Satellite 310 980 AT&T Mobility Not operational GSM 850 / UMTS 850 / UMTS 1900 310 990 AT&T Mobility Not operational Unknown 311 000 Mid-Tex Cellular Operational CDMA2000 850 / CDMA2000 1900 311 010 Chariton Valley Chariton Valley Communications Operational GSM 1900 311 020 Missouri RSA 5 Partnership Operational GSM 850 311 030 Indigo Wireless Operational GSM 1900 311 040 Commnet Wireless Operational GSM 850 / GSM 1900 311 050 Wikes Cellular Operational GSM 850 / GSM 1900 311 060 Farmers Cellular Farmers Cellular Telephone Operational GSM 850 / GSM 1900 311 070 Easterbrooke Easterbrooke Cellular Corporation Operational GSM 850 311 080 Pine Cellular Pine Telephone Company Operational GSM 850 311 090 Long Lines Wireless Long Lines Wireless LLC Operational GSM 1900 311 100 High Plains Wireless Operational GSM 1900 311 110 High Plains Wireless Operational GSM 1900 311 120 Choice Phone Operational Unknown 311 130 Cell One Amarillo Operational GSM 850 311 140 Sprocket MBO Wireless Operational Unknown 311 150 Wilkes Cellular Operational GSM 850 311 160 Endless Mountains Wireless Operational Unknown 311 170 PetroCom Broadpoint Inc Operational GSM 850 311 180 Cingular Wireless Not operational GSM 850 / UMTS 850 / UMTS 1900 311 190 Cellular Properties Unknown Unknown 311 210 Farmers Cellular Farmers Cellular Telephone Operational GSM 850 / GSM 1900 316 010 Nextel Nextel Communications Operational iDEN 800 Merged with Sprint forming Sprint Nextel 316 011 Southern Communications Services Operational iDEN 800 # ISO Country UY Uruguay # MCC MNC Brand Operator Status Bands Notes 748 00 Ancel Operational TDMA 800 748 01 Ancel Operational GSM 1800 / UMTS 1900 / UMTS 2100 [122] 748 07 Movistar Telefónica Móviles Uruguay Operational CDMA2000 850 / GSM 850 / GSM 1900 / UMTS 850 748 10 Claro AM Wireless Uruguay S.A. Operational GSM 1900 # ISO Country UZ Uzbekistan # MCC MNC Brand Operator Status Bands Notes 434 01 Buztel Not operational GSM 900 / GSM 1800 434 02 Uzmacom Not operational Unknown 434 04 Beeline Unitel LLC Operational GSM 900 / GSM 1800 [123] 434 05 Ucell Coscom Operational GSM 900 [124] 434 07 MTS Mobile TeleSystems (FE 'Uzdunrobita' Ltd) Operational GSM 900 / GSM 1800 % 434 ? Perfectum Mobile Operational CDMA2000 800 # ISO Country VU Vanuatu # MCC MNC Brand Operator Status Bands Notes 541 01 SMILE Telecom Vanuatu Ltd Operational GSM 900 # ISO Country VA Vatican # MCC MNC Brand Operator Status Bands Notes 225 Not operational The Vatican is served by Italian networks TIM, Vodafone Italy, Wind and 3 # ISO Country VE Venezuela # MCC MNC Brand Operator Status Bands Notes 734 01 Digitel Corporacion Digitel C.A. Operational GSM 900 Formerly INFONET 734 02 Digitel Corporacion Digitel C.A. Operational GSM 900 734 03 Digitel Corporacion Digitel C.A. Operational GSM 900 Formerly DIGICEL 734 04 movistar Telefónica Móviles Venezuela Operational CDMA2000 850 / GSM 850/1900 734 06 Movilnet Telecomunicaciones Movilnet Operational CDMA2000 850 / GSM 850/1900 # ISO Country VN Vietnam # MCC MNC Brand Operator Status Bands Notes 452 01 MobiFone Vietnam Mobile Telecom Services Company (VMS) Operational GSM 900 / GSM 1800 [125] 452 02 Vinaphone Vietnam Telecom Services Company Operational GSM 900 / GSM 1800 452 03 S-Fone S-Telecom Operational CDMA 452 04 Viettel Mobile Viettel Telecom Operational GSM 900 / GSM 1800 452 05 Vietnamobile Hanoi Telecom Operational eGSM Preparing change to GSM 452 06 E-Mobile EVN Telecom Operational CDMA 450 452 07 Beeline VN GTEL Mobile JSC Lauching soon GSM 1800 # ISO Country YE Yemen # MCC MNC Brand Operator Status Bands Notes 421 01 SabaFon Operational GSM 900 421 02 MTN SpaceTel Operational GSM 900 # ISO Country ZM Zambia # MCC MNC Brand Operator Status Bands Notes 645 01 Zain Operational GSM 900 Former Celtel brand [126] 645 02 MTN Operational GSM 900 / GSM 1800 Former Telecel brand [127] 645 03 ZAMTEL Operational GSM 900 / GSM 1800 # ISO Country ZW Zimbabwe # MCC MNC Brand Operator Status Bands Notes 648 01 Net*One Net*One Cellular (Pvt) Ltd Operational GSM 900 [128] 648 03 Telecel Telecel Zimbabwe (PVT) Ltd Operational GSM 900 648 04 Econet Econet Wireless (Private) Limited Operational GSM 900 # ISO Country -- International - Air # MCC MNC Brand Operator Status Bands Notes 901 14 AeroMobile AS Unknown GSM 1800 901 15 OnAir Switzerland Sarl Unknown GSM 1800 # ISO Country -- International - Ground # MCC MNC Brand Operator Status Bands Notes 901 13 GSM.AQ Global Networks Switzerland Inc. Operational GSM 1800 Antarctica +88234 Network # ISO Country -- Interntional - Satellite # MCC MNC Brand Operator Status Bands Notes 901 01 ICO ICO Satellite Management Operational Satellite 901 02 Sense Communications International Operational Unknown 901 03 Iridium Operational Satellite 901 04 Globalstar Operational Satellite 901 05 Thuraya RMSS Network Operational Satellite 901 06 Thuraya Satellite Telecommunications Company Operational Satellite 901 07 Ellipso Operational Unknown 901 08 Operational Unknown Reserved for station identification where the mobile does not have a subscription IMSI 901 09 Tele1 Europe Operational Unknown 901 10 ACeS Operational Satellite 901 11 Inmarsat Operational Satellite 901 16 Jasper Systems Operational Unknown 901 17 Navitas Operational GSM 1800 901 18 Cingular Wireless Operational GSM 900 / GSM 1900 / CDMA 1900 901 19 Vodafone Malta Maritime Operational Unknown # ISO Country -- International - Sea # MCC MNC Brand Operator Status Bands Notes 901 12 MCP Maritime Communications Partner AS Operational GSM 1800 [129] [130] 901 21 Seanet Seanet Maritime Communications Operational GSM 1800 http://www.seanet.se/ http://www.gsmworld.com/roaming/gsminfo/net_xwsn.shtml fso-frameworkd-0.10.1/etc/freesmartphone/opim/000077500000000000000000000000001174525413000212625ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/freesmartphone/opim/csv-contacts.txt000066400000000000000000000002561174525413000244350ustar00rootroot00000000000000name=Dr. Michael Lauer,tel=55503287 name=Daniel Willman,tel=+55512345689 name=Stefan Schmidt,tel=+55502938172 name=Jan Lübbe,tel=+555123091923 name=unknown guy tel=12348273 fso-frameworkd-0.10.1/etc/freesmartphone/opreferences/000077500000000000000000000000001174525413000227765ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/freesmartphone/opreferences/conf/000077500000000000000000000000001174525413000237235ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/freesmartphone/opreferences/conf/phone/000077500000000000000000000000001174525413000250345ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/freesmartphone/opreferences/conf/phone/default.yaml000066400000000000000000000002671174525413000273510ustar00rootroot00000000000000message-loop: true message-tone: "female_message.wav" message-volume: 10 message-vibration: true ring-loop: true ring-tone: "female_ringtone.wav" ring-volume: 10 ring-vibration: true fso-frameworkd-0.10.1/etc/freesmartphone/opreferences/conf/phone/ring.yaml000066400000000000000000000002711174525413000266570ustar00rootroot00000000000000message-loop: true message-tone: "female_message.wav" message-volume: 10 message-vibration: false ring-loop: true ring-tone: "female_ringtone.wav" ring-volume: 10 ring-vibration: false fso-frameworkd-0.10.1/etc/freesmartphone/opreferences/conf/phone/silent.yaml000066400000000000000000000001201174525413000272070ustar00rootroot00000000000000message-volume: 0 message-vibration: false ring-volume: 0 ring-vibration: false fso-frameworkd-0.10.1/etc/freesmartphone/opreferences/conf/phone/vibrate.yaml000066400000000000000000000001161174525413000273520ustar00rootroot00000000000000message-volume: 0 message-vibration: true ring-volume: 0 ring-vibration: true fso-frameworkd-0.10.1/etc/freesmartphone/opreferences/conf/profiles/000077500000000000000000000000001174525413000255465ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/freesmartphone/opreferences/conf/profiles/default.yaml000066400000000000000000000000661174525413000300600ustar00rootroot00000000000000profiles: - default - ring - vibrate - silent fso-frameworkd-0.10.1/etc/freesmartphone/opreferences/conf/rules/000077500000000000000000000000001174525413000250555ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/freesmartphone/opreferences/conf/rules/default.yaml000066400000000000000000000000531174525413000273630ustar00rootroot00000000000000 enabled-rules: - "" # unamed rules fso-frameworkd-0.10.1/etc/freesmartphone/opreferences/conf/rules/ring.yaml000066400000000000000000000000731174525413000267000ustar00rootroot00000000000000 enabled-rules: - "" # unamed rules - test-rule fso-frameworkd-0.10.1/etc/freesmartphone/opreferences/conf/rules/silent.yaml000066400000000000000000000000731174525413000272370ustar00rootroot00000000000000 enabled-rules: - "" # unamed rules - test-rule fso-frameworkd-0.10.1/etc/freesmartphone/opreferences/conf/rules/vibrate.yaml000066400000000000000000000000731174525413000273750ustar00rootroot00000000000000 enabled-rules: - "" # unamed rules - test-rule fso-frameworkd-0.10.1/etc/freesmartphone/opreferences/schema/000077500000000000000000000000001174525413000242365ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/freesmartphone/opreferences/schema/phone.yaml000066400000000000000000000013771174525413000262430ustar00rootroot00000000000000ring-tone: type: str profilable: yes ring-volume: type: int profilable: yes default: 10 ring-loop: type: bool profilable: yes default: 0 ring-length: type: int profilable: yes default: 0 ring-vibration: type: bool profilable: yes default: 1 message-tone: type: str profilable: yes message-volume: type: int profilable: yes default: 10 message-loop: type: bool profilable: yes default: 0 message-length: type: int profilable: yes default: 0 message-vibration: type: bool profilable: yes default: 0 bt-headset-enabled: type: bool profilable: no default: False bt-headset-address: type: str profilable: no default: "" fso-frameworkd-0.10.1/etc/freesmartphone/opreferences/schema/profiles.yaml000066400000000000000000000004161174525413000267460ustar00rootroot00000000000000 # This schema file defines the preferences parameters of the profiles service # For the moment all we have is a dict of (str -> int) # The strings are the profiles name. # The int have no meaning, it will be replaced by a real structure later profiles: type: list fso-frameworkd-0.10.1/etc/freesmartphone/opreferences/schema/rules.yaml000066400000000000000000000001051174525413000262500ustar00rootroot00000000000000 enabled-rules: profilable: yes type: list default: [""] fso-frameworkd-0.10.1/etc/freesmartphone/persist/000077500000000000000000000000001174525413000220075ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/freesmartphone/persist/README000066400000000000000000000001051174525413000226630ustar00rootroot00000000000000This directory is used to store persistent data for each subsystem. fso-frameworkd-0.10.1/etc/htcdream/000077500000000000000000000000001174525413000170635ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/htcdream/frameworkd.conf000066400000000000000000000033771174525413000221050ustar00rootroot00000000000000[frameworkd] # indicates this configuration version, do not change version = 1 # the default log_level, if not specified per subsystem or module # available log levels are: DEBUG, INFO, WARNING, ERROR, CRITICAL log_level = INFO # the global log_destination. Uncomment to enable # available destinations are: stderr, file, syslog log_to = file # if logging to a file, specify the destination log_destination = /var/log/frameworkd.log # persistence format, one of "pickle", "yaml" persist_format = pickle rootdir = ../etc/freesmartphone:/etc/freesmartphone:/usr/etc/freesmartphone # specify how subsystems scan for their plugins, # either "auto" (via filesystem scan) or "config" (via config section check) # the default is "auto" (slow). scantype = auto [odeviced] disable = 1 [ogsmd] disable = 1 [ogpsd] device = MSMDevice channel = SerialChannel path = /dev/smd27 [opreferencesd] rootdir = ../etc/freesmartphone/opreferences:/etc/freesmartphone/opreferences:/usr/etc/freesmartphone/opreferences [opreferencesd.opreferenced] [oeventsd] rules_file = ../etc/freesmartphone/oevents/rules.yaml:/etc/freesmartphone/oevents/rules.yaml:/usr/etc/freesmartphone/oevents/rules.yaml [oeventsd.oeventsd] [opimd] contacts_default_backend = SQLite-Contacts messages_default_backend = SQLite-Messages calls_default_backend = SQLite-Calls dates_default_backend = SQLite-Dates notes_default_backend = SQLite-Notes tasks_default_backend = SQLite-Tasks messages_default_folder = Unfiled messages_trash_folder = Trash sim_messages_default_folder = SMS contacts_merging_enabled = 1 rootdir = ../etc/freesmartphone/opim:/etc/freesmartphone/opim:/usr/etc/freesmartphone/opim [opimd.opimd] [ousaged] disable = 1 [ophoned] disable = 1 [otimed] disable = 1 [onetworkd] disable = 1 [testing] disable = 1 fso-frameworkd-0.10.1/etc/htcleo/000077500000000000000000000000001174525413000165525ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/htcleo/frameworkd.conf000066400000000000000000000033771174525413000215740ustar00rootroot00000000000000[frameworkd] # indicates this configuration version, do not change version = 1 # the default log_level, if not specified per subsystem or module # available log levels are: DEBUG, INFO, WARNING, ERROR, CRITICAL log_level = INFO # the global log_destination. Uncomment to enable # available destinations are: stderr, file, syslog log_to = file # if logging to a file, specify the destination log_destination = /var/log/frameworkd.log # persistence format, one of "pickle", "yaml" persist_format = pickle rootdir = ../etc/freesmartphone:/etc/freesmartphone:/usr/etc/freesmartphone # specify how subsystems scan for their plugins, # either "auto" (via filesystem scan) or "config" (via config section check) # the default is "auto" (slow). scantype = auto [odeviced] disable = 1 [ogsmd] disable = 1 [ogpsd] device = MSMDevice channel = SerialChannel path = /dev/smd27 [opreferencesd] rootdir = ../etc/freesmartphone/opreferences:/etc/freesmartphone/opreferences:/usr/etc/freesmartphone/opreferences [opreferencesd.opreferenced] [oeventsd] rules_file = ../etc/freesmartphone/oevents/rules.yaml:/etc/freesmartphone/oevents/rules.yaml:/usr/etc/freesmartphone/oevents/rules.yaml [oeventsd.oeventsd] [opimd] contacts_default_backend = SQLite-Contacts messages_default_backend = SQLite-Messages calls_default_backend = SQLite-Calls dates_default_backend = SQLite-Dates notes_default_backend = SQLite-Notes tasks_default_backend = SQLite-Tasks messages_default_folder = Unfiled messages_trash_folder = Trash sim_messages_default_folder = SMS contacts_merging_enabled = 1 rootdir = ../etc/freesmartphone/opim:/etc/freesmartphone/opim:/usr/etc/freesmartphone/opim [opimd.opimd] [ousaged] disable = 1 [ophoned] disable = 1 [otimed] disable = 1 [onetworkd] disable = 1 [testing] disable = 1 fso-frameworkd-0.10.1/etc/init.d/000077500000000000000000000000001174525413000164615ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/init.d/frameworkd000066400000000000000000000020351174525413000205450ustar00rootroot00000000000000#! /bin/sh # # frameworkd -- This shell script starts and stops the freemsmartphone.org framework daemon. # # chkconfig: 345 90 20 # description: frameworkd is the freesmartphone.org framework daemon # processname: python PATH=/bin:/usr/bin:/sbin:/usr/sbin NAME=frameworkd [ -f /etc/default/rcS ] && . /etc/default/rcS case "$1" in start) echo -n "Starting freesmartphone.org framework daemon: " start-stop-daemon --start --pidfile /var/run/${NAME}.pid --make-pidfile --background -x /usr/bin/frameworkd if [ $? = 0 ]; then echo "(ok)" else echo "(failed)" fi ;; stop) echo -n "Stopping freesmartphone.org framework daemon: " start-stop-daemon --stop --pidfile /var/run/${NAME}.pid --oknodo rm -f /var/run/${NAME}.pid echo "(done)" ;; restart|force-reload) $0 stop $0 start ;; *) echo "Usage: /etc/init.d/frameworkd {start|stop|restart|force-reload}" exit 1 ;; esac exit 0 fso-frameworkd-0.10.1/etc/magician/000077500000000000000000000000001174525413000170445ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/magician/frameworkd.conf000066400000000000000000000023711174525413000220570ustar00rootroot00000000000000[frameworkd] version = 1 log_level = DEBUG log_to = file log_destination = /var/log/frameworkd.log [odeviced.input] # magician doesn't have AUX, use the CAMERA key for now # (it's in about the same place as AUX on gta01/02) report1 = AUX,key,212,1 report2 = POWER,key,116,1 # magician doesn't use KEY_POWER2 for the charger, need to fix report3 = USB,key,356,0 report4 = HEADSET,switch,2,0 [ogsmd] # Magician has TI Calypso modemtype = ti_calypso ti_calypso_muxer_type = gsm0710muxd [opreferencesd] rootdir = ../etc/freesmartphone/opreferences:/etc/freesmartphone/opreferences:/usr/etc/freesmartphone/opreferences [oeventsd] rules_file = ../etc/freesmartphone/oevents/rules.yaml:/etc/freesmartphone/oevents/rules.yaml:/usr/etc/freesmartphone/oevents/rules.yaml [opimd] contacts_default_backend = SQLite-Contacts messages_default_backend = SQLite-Messages calls_default_backend = SQLite-Calls dates_default_backend = SQLite-Dates notes_default_backend = SQLite-Notes tasks_default_backend = SQLite-Tasks messages_default_folder = Unfiled messages_trash_folder = Trash sim_messages_default_folder = SMS contacts_merging_enabled = 1 rootdir = ../etc/freesmartphone/opim:/etc/freesmartphone/opim:/usr/etc/freesmartphone/opim [ousaged] disable = 1 [testing] disable = 1 fso-frameworkd-0.10.1/etc/nexusone/000077500000000000000000000000001174525413000171405ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/nexusone/frameworkd.conf000066400000000000000000000033161174525413000221530ustar00rootroot00000000000000[frameworkd] # indicates this configuration version, do not change version = 1 # the default log_level, if not specified per subsystem or module # available log levels are: DEBUG, INFO, WARNING, ERROR, CRITICAL log_level = INFO # the global log_destination. Uncomment to enable # available destinations are: stderr, file, syslog log_to = file # if logging to a file, specify the destination log_destination = /var/log/frameworkd.log # persistence format, one of "pickle", "yaml" persist_format = pickle rootdir = ../etc/freesmartphone:/etc/freesmartphone:/usr/etc/freesmartphone # specify how subsystems scan for their plugins, # either "auto" (via filesystem scan) or "config" (via config section check) # the default is "auto" (slow). scantype = auto [odeviced] disable = 1 [ogsmd] disable = 1 [ogpsd] disable = 1 [opreferencesd] rootdir = ../etc/freesmartphone/opreferences:/etc/freesmartphone/opreferences:/usr/etc/freesmartphone/opreferences [opreferencesd.opreferenced] [oeventsd] rules_file = ../etc/freesmartphone/oevents/rules.yaml:/etc/freesmartphone/oevents/rules.yaml:/usr/etc/freesmartphone/oevents/rules.yaml [oeventsd.oeventsd] [opimd] contacts_default_backend = SQLite-Contacts messages_default_backend = SQLite-Messages calls_default_backend = SQLite-Calls dates_default_backend = SQLite-Dates notes_default_backend = SQLite-Notes tasks_default_backend = SQLite-Tasks messages_default_folder = Unfiled messages_trash_folder = Trash sim_messages_default_folder = SMS contacts_merging_enabled = 1 rootdir = ../etc/freesmartphone/opim:/etc/freesmartphone/opim:/usr/etc/freesmartphone/opim [opimd.opimd] [ousaged] disable = 1 [ophoned] disable = 1 [otimed] disable = 1 [onetworkd] disable = 1 [testing] disable = 1 fso-frameworkd-0.10.1/etc/nokia900/000077500000000000000000000000001174525413000166265ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/nokia900/frameworkd.conf000066400000000000000000000034571174525413000216470ustar00rootroot00000000000000# Do note that the order of plugin sections is important. Plugins that depend # on others need to come later, # eg. fsousage.dbus_service AFTER fsousage.lowlevel_kernel26 [frameworkd] # indicates this configuration version, do not change version = 1 # the default log_level, if not specified per subsystem or module # available log levels are: DEBUG, INFO, WARNING, ERROR, CRITICAL log_level = INFO # the global log_destination. Uncomment to enable # available destinations are: stderr, file, syslog log_to = file # if logging to a file, specify the destination log_destination = /var/log/frameworkd.log # persistence format, one of "pickle", "yaml" persist_format = pickle rootdir = ../etc/freesmartphone:/etc/freesmartphone:/usr/etc/freesmartphone # specify how subsystems scan for their plugins, # either "auto" (via filesystem scan) or "config" (via config section check) # the default is "auto" (slow). scantype = auto [odeviced] disable = 1 [ogsmd] disable = 1 [ogpsd] disable = 1 # possible options are NMEADevice, UBXDevice, GTA02Device, EtenDevice device = GTA02Device # possible options are SerialChannel, GllinChannel, UDPChannel, FileChannel channel = SerialChannel # For UDPChannel the path defines the port to listen to path = /dev/ttySAC1 [ogpsd.factory] [opreferencesd] rootdir = ../etc/freesmartphone/opreferences:/etc/freesmartphone/opreferences:/usr/etc/freesmartphone/opreferences [opreferencesd.opreferenced] [oeventsd] rules_file = ../etc/freesmartphone/oevents/rules.yaml:/etc/freesmartphone/oevents/rules.yaml:/usr/etc/freesmartphone/oevents/rules.yaml [oeventsd.oeventsd] [opimd] rootdir = ../etc/freesmartphone/opim:/etc/freesmartphone/opim:/usr/etc/freesmartphone/opim [opimd.opimd] [ousaged] disable = 1 [ophoned] disable = 1 [otimed] disable = 1 [onetworkd] disable = 1 [testing] disable = 1 fso-frameworkd-0.10.1/etc/om-gta01/000077500000000000000000000000001174525413000166215ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/om-gta01/frameworkd.conf000066400000000000000000000034501174525413000216330ustar00rootroot00000000000000# Do note that the order of plugin sections is important. Plugins that depend # on others need to come later, # eg. fsousage.dbus_service AFTER fsousage.lowlevel_kernel26 [frameworkd] # indicates this configuration version, do not change version = 1 # the default log_level, if not specified per subsystem or module # available log levels are: DEBUG, INFO, WARNING, ERROR, CRITICAL log_level = INFO # the global log_destination. Uncomment to enable # available destinations are: stderr, file, syslog log_to = file # if logging to a file, specify the destination log_destination = /var/log/frameworkd.log # persistence format, one of "pickle", "yaml" persist_format = pickle rootdir = ../etc/freesmartphone:/etc/freesmartphone:/usr/etc/freesmartphone # specify how subsystems scan for their plugins, # either "auto" (via filesystem scan) or "config" (via config section check) # the default is "auto" (slow). scantype = auto [odeviced] disable = 1 [ogsmd] disable = 1 [ogpsd] # possible options are NMEADevice, UBXDevice, GTA02Device, EtenDevice device = NMEADevice # possible options are SerialChannel, GllinChannel, UDPChannel, FileChannel channel = GllinChannel # For UDPChannel the path defines the port to listen to path = /etc/init.d/gllin [ogpsd.factory] [opreferencesd] rootdir = ../etc/freesmartphone/opreferences:/etc/freesmartphone/opreferences:/usr/etc/freesmartphone/opreferences [opreferencesd.opreferenced] [oeventsd] rules_file = ../etc/freesmartphone/oevents/rules.yaml:/etc/freesmartphone/oevents/rules.yaml:/usr/etc/freesmartphone/oevents/rules.yaml [oeventsd.oeventsd] [opimd] rootdir = ../etc/freesmartphone/opim:/etc/freesmartphone/opim:/usr/etc/freesmartphone/opim [opimd.opimd] [ousaged] disable = 1 [ophoned] disable = 1 [otimed] disable = 1 [onetworkd] disable = 1 [testing] disable = 1 fso-frameworkd-0.10.1/etc/om-gta02/000077500000000000000000000000001174525413000166225ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/om-gta02/frameworkd.conf000066400000000000000000000034451174525413000216400ustar00rootroot00000000000000# Do note that the order of plugin sections is important. Plugins that depend # on others need to come later, # eg. fsousage.dbus_service AFTER fsousage.lowlevel_kernel26 [frameworkd] # indicates this configuration version, do not change version = 1 # the default log_level, if not specified per subsystem or module # available log levels are: DEBUG, INFO, WARNING, ERROR, CRITICAL log_level = INFO # the global log_destination. Uncomment to enable # available destinations are: stderr, file, syslog log_to = file # if logging to a file, specify the destination log_destination = /var/log/frameworkd.log # persistence format, one of "pickle", "yaml" persist_format = pickle rootdir = ../etc/freesmartphone:/etc/freesmartphone:/usr/etc/freesmartphone # specify how subsystems scan for their plugins, # either "auto" (via filesystem scan) or "config" (via config section check) # the default is "auto" (slow). scantype = auto [odeviced] disable = 1 [ogsmd] disable = 1 [ogpsd] # possible options are NMEADevice, UBXDevice, GTA02Device, EtenDevice device = GTA02Device # possible options are SerialChannel, GllinChannel, UDPChannel, FileChannel channel = SerialChannel # For UDPChannel the path defines the port to listen to path = /dev/ttySAC1 [ogpsd.factory] [opreferencesd] rootdir = ../etc/freesmartphone/opreferences:/etc/freesmartphone/opreferences:/usr/etc/freesmartphone/opreferences [opreferencesd.opreferenced] [oeventsd] rules_file = ../etc/freesmartphone/oevents/rules.yaml:/etc/freesmartphone/oevents/rules.yaml:/usr/etc/freesmartphone/oevents/rules.yaml [oeventsd.oeventsd] [opimd] rootdir = ../etc/freesmartphone/opim:/etc/freesmartphone/opim:/usr/etc/freesmartphone/opim [opimd.opimd] [ousaged] disable = 1 [ophoned] disable = 1 [otimed] disable = 1 [onetworkd] disable = 1 [testing] disable = 1 fso-frameworkd-0.10.1/etc/om-gta04/000077500000000000000000000000001174525413000166245ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/om-gta04/frameworkd.conf000066400000000000000000000030251174525413000216340ustar00rootroot00000000000000# Do note that the order of plugin sections is important. Plugins that depend # on others need to come later, # eg. fsousage.dbus_service AFTER fsousage.lowlevel_kernel26 [frameworkd] # indicates this configuration version, do not change version = 1 # the default log_level, if not specified per subsystem or module # available log levels are: DEBUG, INFO, WARNING, ERROR, CRITICAL log_level = INFO # the global log_destination. Uncomment to enable # available destinations are: stderr, file, syslog log_to = file # if logging to a file, specify the destination log_destination = /var/log/frameworkd.log # persistence format, one of "pickle", "yaml" persist_format = pickle rootdir = ../etc/freesmartphone:/etc/freesmartphone:/usr/etc/freesmartphone # specify how subsystems scan for their plugins, # either "auto" (via filesystem scan) or "config" (via config section check) # the default is "auto" (slow). scantype = auto [opreferencesd] rootdir = ../etc/freesmartphone/opreferences:/etc/freesmartphone/opreferences:/usr/etc/freesmartphone/opreferences [opreferencesd.opreferenced] [oeventsd] rules_file = ../etc/freesmartphone/oevents/rules.yaml:/etc/freesmartphone/oevents/rules.yaml:/usr/etc/freesmartphone/oevents/rules.yaml [oeventsd.oeventsd] [opimd] rootdir = ../etc/freesmartphone/opim:/etc/freesmartphone/opim:/usr/etc/freesmartphone/opim [opimd.opimd] [ousaged] disable = 1 [odeviced] disable = 1 [ogsmd] disable = 1 [ogpsd] disable = 1 [ophoned] disable = 1 [otimed] disable = 1 [onetworkd] disable = 1 [testing] disable = 1 fso-frameworkd-0.10.1/etc/palmpre/000077500000000000000000000000001174525413000167345ustar00rootroot00000000000000fso-frameworkd-0.10.1/etc/palmpre/frameworkd.conf000066400000000000000000000037361174525413000217550ustar00rootroot00000000000000[frameworkd] # indicates this configuration version, do not change version = 1 # the default log_level, if not specified per subsystem or module # available log levels are: DEBUG, INFO, WARNING, ERROR, CRITICAL log_level = INFO # the global log_destination. Uncomment to enable # available destinations are: stderr, file, syslog log_to = file # if logging to a file, specify the destination log_destination = /var/log/frameworkd.log # persistence format, one of "pickle", "yaml" persist_format = pickle rootdir = ../etc/freesmartphone:/etc/freesmartphone:/usr/etc/freesmartphone # specify how subsystems scan for their plugins, # either "auto" (via filesystem scan) or "config" (via config section check) # the default is "auto" (slow). scantype = auto # # Subsystem configuration for oeventsd # [odeviced] # set 1 to disable a subsystem or a module disable = 1 # # Subsystem configuration for oeventsd # [oeventsd] log_level = DEBUG disable = 0 [oeventsd.oevents] # # Subsystem configuration for ogspd # [ogpsd] disable = 1 # # Subsystem configuration for ogsmd # [ogsmd] disable = 1 # # Subsystem configuration for onetworkd # [onetworkd] disable = 1 # # Subsystem configuration for ophoned # [ophoned] disable = 1 # # Subsystem configuration for opimd # [opimd] contacts_default_backend = CSV-Contacts messages_default_backend = SIM-Messages-FSO calls_default_backend = SQLite-Calls dates_default_backend = SQLite-Dates notes_default_backend = SQLite-Notes tasks_default_backend = SQLite-Tasks contacts_merging_enabled = 1 messages_default_folder = Unfiled messages_trash_folder = Trash sim_messages_default_folder = SMS rootdir = ../etc/freesmartphone/opim:/etc/freesmartphone/opim:/usr/etc/freesmartphone/opim [opimd.opimd] # # Subsystem configuration for opreferencesd # [opreferencesd] log_level = DEBUG disable = 0 [opreferencesd.opreferences] # # Subsystem configuration for otimed # [otimed] disable = 1 # # Subsystem configuration for ousaged # [ousaged] disable = 1 [testing] disable = 1 fso-frameworkd-0.10.1/examples/000077500000000000000000000000001174525413000163375ustar00rootroot00000000000000fso-frameworkd-0.10.1/examples/ogpsd_methods.py000077500000000000000000000045401174525413000215560ustar00rootroot00000000000000#!/usr/bin/env python import dbus from time import gmtime, strftime obj = dbus.SystemBus().get_object('org.freesmartphone.ogpsd', '/org/freedesktop/Gypsy') constatus = obj.GetConnectionStatus(dbus_interface='org.freedesktop.Gypsy.Device') print 'ConnectionStatus: constatus = %u' % constatus, print '%s' % constatus and '(TRUE)' or '(FALSE)' fixstatus = obj.GetFixStatus(dbus_interface='org.freedesktop.Gypsy.Device') print 'FixStatus: fixstatus = %d' % fixstatus, if fixstatus == 1: print '(NONE)' elif fixstatus == 2: print '(2D)' elif fixstatus == 3: print '(3D)' else: print '(INVALID)' (fields, tstamp, lat, lon, alt) = obj.GetPosition(dbus_interface='org.freedesktop.Gypsy.Position') print 'Position:', print 'fields = %d,' % fields, print 'tstamp = %d (%s),' % (tstamp, strftime('%F %T', gmtime(tstamp))), print 'lat = %f (%s),' % (lat, fields & (1 << 0) and 'OK' or 'INVALID'), print 'lon = %f (%s),' % (lon, fields & (1 << 1) and 'OK' or 'INVALID'), print 'alt = %f (%s)' % (alt, fields & (1 << 2) and 'OK' or 'INVALID') (fields, pdop, hdop, vdop) = obj.GetAccuracy(dbus_interface='org.freedesktop.Gypsy.Accuracy') print 'Accuracy:', print 'fields = %d,' % fields, print 'pdop = %f (%s),' % (pdop, fields & (1 << 0) and 'OK' or 'INVALID'), print 'hdop = %f (%s),' % (hdop, fields & (1 << 1) and 'OK' or 'INVALID'), print 'vdop = %f (%s)' % (vdop, fields & (1 << 2) and 'OK' or 'INVALID') (fields, tstamp, speed, heading, climb) = obj.GetCourse(dbus_interface='org.freedesktop.Gypsy.Course') print 'Course:', print 'fields = %d,' % fields, print 'tstamp = %d (%s),' % (tstamp, strftime('%F %T', gmtime(tstamp))), print 'speed = %f (%s),' % (speed, fields & (1 << 0) and 'OK' or 'INVALID'), print 'heading = %f (%s),' % (heading, fields & (1 << 1) and 'OK' or 'INVALID'), print 'climb = %f (%s)' % (climb, fields & (1 << 2) and 'OK' or 'INVALID') time = obj.GetTime(dbus_interface='org.freedesktop.Gypsy.Time') print 'Time: time = %d (%s)' % (time, strftime('%F %T', gmtime(time))) satellites = obj.GetSatellites(dbus_interface='org.freedesktop.Gypsy.Satellite') print 'Satellites:' for (satIndex, sat) in enumerate(satellites): print '\tindex = %d,' % satIndex, print 'prn = %u,' % sat[0], print 'used = %u (%s),' % (sat[1], sat[1] and 'TRUE' or 'FALSE'), print 'elevation = %u,' % sat[2], print 'azimuth = %u,' % sat[3], print 'snr = %u' % sat[4] fso-frameworkd-0.10.1/examples/ogpsd_resource.py000077500000000000000000000003701174525413000217370ustar00rootroot00000000000000#!/usr/bin/env python import dbus from time import sleep obj = dbus.SystemBus().get_object('org.freesmartphone.ousaged', '/org/freesmartphone/Usage') dbus.Interface(obj, 'org.freesmartphone.Usage').RequestResource('GPS') sleep(3600 * 24 * 365) fso-frameworkd-0.10.1/examples/ogpsd_signals.py000077500000000000000000000060071174525413000215530ustar00rootroot00000000000000#!/usr/bin/env python import gobject import dbus from dbus.mainloop.glib import DBusGMainLoop from time import gmtime, strftime name = 'org.freesmartphone.ogpsd' path = '/org/freedesktop/Gypsy' def onConnectionStatusChanged(constatus): print 'ConnectionStatusChanged: constatus = %u' % constatus, print '%s' % constatus and '(TRUE)' or '(FALSE)' def onFixStatusChanged(fixstatus): print 'FixStatusChanged: fixstatus = %d' % fixstatus, if fixstatus == 1: print '(NONE)' elif fixstatus == 2: print '(2D)' elif fixstatus == 3: print '(3D)' else: print '(INVALID)' def onPositionChanged(fields, tstamp, lat, lon, alt): print 'PositionChanged:', print 'fields = %d,' % fields, print 'tstamp = %d (%s),' % (tstamp, strftime('%F %T', gmtime(tstamp))), print 'lat = %f (%s),' % (lat, fields & (1 << 0) and 'OK' or 'INVALID'), print 'lon = %f (%s),' % (lon, fields & (1 << 1) and 'OK' or 'INVALID'), print 'alt = %f (%s)' % (alt, fields & (1 << 2) and 'OK' or 'INVALID') def onAccuracyChanged(fields, pdop, hdop, vdop): print 'AccuracyChanged:', print 'fields = %d,' % fields, print 'pdop = %f (%s),' % (pdop, fields & (1 << 0) and 'OK' or 'INVALID'), print 'hdop = %f (%s),' % (hdop, fields & (1 << 1) and 'OK' or 'INVALID'), print 'vdop = %f (%s)' % (vdop, fields & (1 << 2) and 'OK' or 'INVALID') def onCourseChanged(fields, tstamp, speed, heading, climb): print 'CourseChanged:', print 'fields = %d,' % fields, print 'tstamp = %d (%s),' % (tstamp, strftime('%F %T', gmtime(tstamp))), print 'speed = %f (%s),' % (speed, fields & (1 << 0) and 'OK' or 'INVALID'), print 'heading = %f (%s),' % (heading, fields & (1 << 1) and 'OK' or 'INVALID'), print 'climb = %f (%s)' % (climb, fields & (1 << 2) and 'OK' or 'INVALID') def onTimeChanged(time): print 'TimeChanged: time = %d (%s)' % (time, strftime('%F %T', gmtime(time))) def onSatellitesChanged(satellites): print 'SatellitesChanged:' for (satIndex, sat) in enumerate(satellites): print '\tindex = %d,' % satIndex, print 'prn = %u,' % sat[0], print 'used = %u (%s),' % (sat[1], sat[1] and 'TRUE' or 'FALSE'), print 'elevation = %u,' % sat[2], print 'azimuth = %u,' % sat[3], print 'snr = %u' % sat[4] DBusGMainLoop(set_as_default=True) mainloop = gobject.MainLoop() bus = dbus.SystemBus() bus.add_signal_receiver(onConnectionStatusChanged, 'ConnectionStatusChanged', 'org.freedesktop.Gypsy.Device', name, path) bus.add_signal_receiver(onFixStatusChanged, 'FixStatusChanged', 'org.freedesktop.Gypsy.Device', name, path) bus.add_signal_receiver(onPositionChanged, 'PositionChanged', 'org.freedesktop.Gypsy.Position', name, path) bus.add_signal_receiver(onAccuracyChanged, 'AccuracyChanged', 'org.freedesktop.Gypsy.Accuracy', name, path) bus.add_signal_receiver(onCourseChanged, 'CourseChanged', 'org.freedesktop.Gypsy.Course', name, path) bus.add_signal_receiver(onTimeChanged, 'TimeChanged', 'org.freedesktop.Gypsy.Time', name, path) bus.add_signal_receiver(onSatellitesChanged, 'SatellitesChanged', 'org.freedesktop.Gypsy.Satellite', name, path) mainloop.run() fso-frameworkd-0.10.1/examples/ogsmd-execute-script-on-incoming-call.py000077500000000000000000000153331174525413000261200ustar00rootroot00000000000000import dbus import dbus.mainloop import dbus.mainloop.glib import gobject import subprocess actions = { \ "+491002":"/usr/bin/foo1", "+491002":"/usr/bin/foo2", "+491003":"/usr/bin/foo3" } def onCallStatus( index, status, properties ): if status == "incoming": try: action = actions[properties["peer"]] except KeyError: pass else: obj = bus.get_object( "org.freesmartphone.ogsmd", "/org/freesmartphone/GSM/Device" ) callInterface = dbus.Interface( obj, "org.freesmartphone.GSM.Call" ) callInterface.Release( index ) subprocess.Popen( action, shell=True ) dbus.mainloop.glib.DBusGMainLoop( set_as_default=True ) mainloop = gobject.MainLoop() bus = dbus.SystemBus() bus.add_signal_receiver( onCallStatus, "CallStatus", "org.freesmartphone.GSM.Call", "org.freesmartphone.ogsmd", "/org/freesmartphone/GSM/Device" ) mainloop.run() fso-frameworkd-0.10.1/examples/ogsmd-log-data.py000077500000000000000000000107641174525413000215230ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: UTF-8 -*- """ Log GSM Data from ogsmd (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later """ # constants you may want to change PIN = "9797" LOG = "/tmp/gsm-data.log" FREQUENCE = 2000 # ms EM_COMMAND = "%EM=2,1" AUTOANSWER = True # you may not want to change a lot below here import sys import gobject import dbus, dbus.mainloop.glib logfile = file( LOG, "w" ) gsm = None #----------------------------------------------------------------------------# def error( message ): #----------------------------------------------------------------------------# sys.stderr.write( "%s\n" % message ) sys.exit( -1 ) #----------------------------------------------------------------------------# def log( message ): #----------------------------------------------------------------------------# logfile.write( "%s\n" % message ) logfile.flush() print message #----------------------------------------------------------------------------# def timeout_handler(): #----------------------------------------------------------------------------# if gsm is not None: strength = gsm.GetSignalStrength() log( "SIGNAL NOW %d" % strength ) # here you can add more of your special AT commands result = gsm.DebugCommand( "AT%s\r\n" % EM_COMMAND ) log( "EM RESULT %s" % result[0] ) return True # call me again #----------------------------------------------------------------------------# def dbus_signal_handler( data, *args, **kwargs ): #----------------------------------------------------------------------------# signal = "%s.%s" % ( kwargs["interface"], kwargs["member"] ) if signal == "org.freesmartphone.GSM.Network.Status": if "lac" and "cid" in data: log( "LAC/CID NOW %s, %s" % ( data["lac"], data["cid"] ) ) elif signal == "org.freesmartphone.GSM.Network.SignalStrength": log( "SIGNAL NOW %d" % data ) elif signal == "org.freesmartphone.GSM.Call.CallStatus": status, properties = args if "peer" in properties: log( "CALL %s [%s]" % ( status, properties["peer"] ) ) else: log( "CALL %s [unknown]" % status ) if status == "incoming" and AUTOANSWER: log( "AUTOANSWERING CALL" ) gsm.Activate( data ) #----------------------------------------------------------------------------# def init_dbus(): #----------------------------------------------------------------------------# """initialize dbus""" print "trying to get bus...", try: bus = dbus.SystemBus() except Exception, e: error( "Can't connect to dbus: %s" % e ) print "ok" return bus #----------------------------------------------------------------------------# def init_ogsmd( bus ): #----------------------------------------------------------------------------# """initialize ogsmd""" print "trying to get object...", try: global gsm gsm = bus.get_object( "org.freesmartphone.ogsmd", "/org/freesmartphone/GSM/Device" ) except Exception, e: error( "can't connect to org.freesmartphone.ogsmd: %s" % e ) bus.add_signal_receiver( dbus_signal_handler, None, None, "org.freesmartphone.ogsmd", None, sender_keyword = "sender", destination_keyword = "destination", interface_keyword = "interface", member_keyword = "member", path_keyword = "path" ) print "ok" print "initializing gsm..." # init sequence try: print "-> setting antenna power..." gsm.SetAntennaPower( True ) # this will fail, if your SIM is PIN-protected except dbus.DBusException, m: authstatus = gsm.GetAuthStatus() if authstatus != "READY": print "-> card PIN protected, sending PIN..." gsm.SendAuthCode( PIN ) # send PIN gsm.SetAntennaPower( True ) # this should work now print "-> registering to network" gsm.Register() # autoregister print "gsm init ok, entering mainloop" return False # don't call me again #----------------------------------------------------------------------------# # program starts here #----------------------------------------------------------------------------# dbus.mainloop.glib.DBusGMainLoop( set_as_default=True ) mainloop = gobject.MainLoop() bus = init_dbus() gobject.idle_add( init_ogsmd, bus ) gobject.timeout_add( FREQUENCE, timeout_handler ) try: mainloop.run() except KeyboardInterrupt: mainloop.quit() else: print "normal exit." sys.exit( 0 ) fso-frameworkd-0.10.1/framework/000077500000000000000000000000001174525413000165165ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/__init__.py000066400000000000000000000001421174525413000206240ustar00rootroot00000000000000# cxnet / cxutil import sys, os sys.path.append( os.path.abspath( os.path.dirname( __file__ ) ) ) fso-frameworkd-0.10.1/framework/config.py000066400000000000000000000111561174525413000203410ustar00rootroot00000000000000#!/usr/bin/env python """ freesmartphone.org Framework Daemon (C) 2008-2010 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Module: config """ DBUS_BUS_NAME_PREFIX = "org.freesmartphone" DBUS_INTERFACE_PREFIX = "org.freesmartphone" DBUS_PATH_PREFIX = "/org/freesmartphone" NEEDS_VERSION = 1 __version__ = "1.1.1" __all__ = ( \ "DBUS_BUS_NAME_PREFIX", "DBUS_INTERFACE_PREFIX", "DBUS_PATH_PREFIX", "debug", "debugto", "debugdest", "installprefix", "rootdir" ) from configparse import SmartConfigParser import os import logging, logging.handlers loggingmap = { \ "DEBUG": logging.DEBUG, "INFO": logging.INFO, "WARNING": logging.WARNING, "ERROR": logging.ERROR, "CRITICAL": logging.CRITICAL, } # # busmap for subsystems # busmap = {} # # init configuration # config = None searchpath = [ os.path.expanduser( "~/.frameworkd.conf" ), "/etc/frameworkd.conf", os.path.join( os.path.dirname( __file__ ), "../conf/frameworkd.conf" ), os.path.expanduser("~/.frameworkd.conf") ] for p in searchpath: if os.path.exists( p ): logging.info( "Using configuration file %s" % p ) config = SmartConfigParser( p ) break if config is None: logging.error( "Can't find a configuration file. Looked in %s" % searchpath ) raise IOError, "can't find configuration file" version = config.getInt( "frameworkd", "version", 0 ) if version != NEEDS_VERSION: logging.warning( "configuration format too old. Please update and add the following lines to your configuration file:" ) logging.warning( "[frameworkd]" ) logging.warning( "version = %d" % NEEDS_VERSION ) debug = config.getValue( "frameworkd", "log_level", "INFO" ) debugto = config.getValue( "frameworkd", "log_to", "stderr" ) debugdest = config.getValue( "frameworkd", "log_destination", "/tmp/frameworkd.log" ) # get root logger and yank all existing handlers rootlogger = logging.getLogger( "" ) rootlogger.setLevel( loggingmap.get( debug, logging.INFO ) ) for handler in rootlogger.handlers: rootlogger.removeHandler( handler ) # now that we are clean, setup our actual handler and configure formatter if debugto == "stderr": handler = logging.StreamHandler() # default=stderr handler.setFormatter( logging.Formatter( "%(asctime)s.%(msecs)03d %(name)-20s %(levelname)-8s %(message)s", datefmt="%Y.%m.%d %H:%M:%S" ) ) elif debugto == "file": handler = logging.FileHandler( debugdest ) handler.setFormatter( logging.Formatter( "%(asctime)s.%(msecs)03d %(name)-20s %(levelname)-8s %(message)s", datefmt="%Y.%m.%d %H:%M:%S" ) ) elif debugto == "syslog": handler = logging.handlers.SysLogHandler( address = "/dev/log" ) # timestamps are not needed with syslog handler.setFormatter( logging.Formatter( "%(name)-8s %(levelname)-8s %(message)s" ) ) # set the handler rootlogger.addHandler( handler ) # # compute install prefix # installprefix = "/" # unknown first searchpath = "/usr/local /usr /local/pkg/fso /opt".split() thisdirname = os.path.dirname( __file__ ) for p in searchpath: if thisdirname.startswith( p ): installprefix = p break if installprefix == "/": # Installation not found yet, check for symlinked install. # e.g. Check if a /usr/... install is a symbolic link to /media/card/... separatedFilePath = __file__.split( os.sep ) for i in range( 2, len( separatedFilePath ) ): sepShortFilePath = [ "" ] + separatedFilePath[i:] shortFilePath = os.sep.join( sepShortFilePath ) #logging.debug( "Installprefix check comparing %s, %s" % (shortFilePath, __file__) ) # Symbolic link check if os.path.islink( shortFilePath ) and os.path.samefile( shortFilePath, __file__ ): for p in searchpath: # verify it's an install in the searchpath if shortFilePath.startswith(p): installprefix = p break # Stop checking if we've found and set the installprefix. if installprefix != "/": break logging.info( "Installprefix is %s" % installprefix ) # # compute root dir # # FIXME should rather be named confdir or rulesdir or something like that possible_rootdirs = os.path.abspath( config.getValue( "frameworkd", "rootdir", "../etc/freesmartphone:/etc/freesmartphone:/usr/etc/freesmartphone" ) ).split( ':' ) for path in possible_rootdirs: if os.path.exists( path ): rootdir = path break else: logging.warning( "can't find the etc directory; defaulting to /etc/freesmartphone" ) rootdir = "/etc/freesmartphone" logging.info( "Etc directory is %s" % rootdir ) fso-frameworkd-0.10.1/framework/configparse.py000066400000000000000000000107541174525413000213770ustar00rootroot00000000000000#!/usr/bin/env python """ freesmartphone.org Framework Daemon (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Module: configparse """ __version__ = "1.0.0" import ConfigParser import types #----------------------------------------------------------------------------# class SmartConfigParser( ConfigParser.SafeConfigParser ): #----------------------------------------------------------------------------# """ A smart config parser """ def __init__( self, filename = None ): ConfigParser.SafeConfigParser.__init__( self ) self.filename = filename if filename is not None: self.read( filename ) def sync( self ): # FIXME if we have a mainloop, collect sync calls and write deferred assert self.filename is not None, "no filename given yet" ConfigParser.SafeConfigParser.write( self, open(self.filename, "w" ) ) def getOptions( self, section ): try: options = self.options( section ) except ConfigParser.NoSectionError: return [] else: return options def getValue( self, section, key, default=None, set=False, getmethod=ConfigParser.SafeConfigParser.get ): try: value = getmethod( self, section, key ) except ConfigParser.NoSectionError: if set: self.add_section( section ) self.set( section, key, str(default) ) self.sync() return default except ConfigParser.NoOptionError: if set: self.set( section, key, str(default) ) self.sync() return default else: return value def setValue( self, section, key, value ): try: self.set( section, key, str(value) ) except ConfigParser.NoSectionError: self.add_section( section ) self.set( section, key, str(value) ) self.sync() def getBool( self, section, key, default=None, set=False ): return self.getValue( section, key, default, getmethod = ConfigParser.SafeConfigParser.getboolean ) def getFloat( self, section, key, default=None, set=False ): return self.getValue( section, key, default, getmethod = ConfigParser.SafeConfigParser.getfloat ) def getInt( self, section, key, default=None, set=False ): return self.getValue( section, key, default, getmethod = ConfigParser.SafeConfigParser.getint ) def getSetDefault( self, section, key, default ): """ The type to return is gathered from the type of the default value. If the section does not exist, it is created. If the key does not exist, it is created with the default value. """ value = self.getValue( section, key, default, True ) return self._typedValue( value, default ) def getDefault( self, section, key, default ): """ The type to return is gathered from the type of the default value. """ value = self.getValue( section, key, default, False ) #print "value =", value, "returning", self._typedValue( value, default ) return self._typedValue( value, default ) def _typedValue( self, value, default ): valuetype = type( default ) if valuetype == types.StringType: return str( value ) elif valuetype == types.IntType: return int( value ) elif valuetype == types.BooleanType: return bool( value ) elif valuetype == types.FloatType: return float( value ) else: # can't guess type, return it unchanged return value #----------------------------------------------------------------------------# if __name__ == "__main__": #----------------------------------------------------------------------------# FILENAME = "/tmp/test.ini" import os try: os.unlink( FILENAME ) except OSError: # might not exist pass c = SmartConfigParser( FILENAME ) print "TESTING...", # reading a non existent key assert c.getDefault( "foo.bar", "key", None ) == None assert c.getDefault( "foo.bar", "key", False ) == False assert c.getDefault( "foo.bar", "key", 10 ) == 10 assert c.getDefault( "foo.bar", "key", 10.0 ) == 10.0 assert c.getDefault( "foo.bar", "key", "10" ) == "10" # getset a key assert c.getSetDefault( "foo.bar", "key", 500 ) == 500 assert c.getDefault( "foo.bar", "key", 100 ) == 500 print "OK." fso-frameworkd-0.10.1/framework/controller.py000066400000000000000000000150261174525413000212570ustar00rootroot00000000000000#!/usr/bin/env python """ freesmartphone.org Framework Daemon (C) 2008-2010 Michael 'Mickey' Lauer (C) 2008-2009 Openmoko, Inc. GPLv2 or later Package: framework Module: controller """ MODULE_NAME = "frameworkd.controller" __version__ = "0.9.9" from framework.config import DBUS_BUS_NAME_PREFIX, debug, config, loggingmap from framework.patterns import daemon import subsystem import dbus, dbus.service, dbus.mainloop.glib import gobject import os, sys, types, time import logging logger = logging.getLogger( MODULE_NAME ) try: # not present in older glib versions from gobject import timeout_add_seconds except ImportError: logger.error( "python-gobject >= 2.14.0 required" ) sys.exit( -1 ) #----------------------------------------------------------------------------# class Controller( daemon.Daemon ): #----------------------------------------------------------------------------# """ Loading and registering plugins. """ # We store all DBus objects in a class attribute objects = {} @classmethod def object( cls, name ): """ Return a DBus object -not proxy- from the list of registered objects. """ return cls.objects[name] def __init__( self, path, options ): sys.path.append( path ) # to enable 'from import ...' and 'import ' self.launchTime = time.time() self.options = options daemon.Daemon.__init__( self, "/tmp/frameworkd.pid" ) # dbus & glib mainloop dbus.mainloop.glib.DBusGMainLoop( set_as_default=True ) self.mainloop = gobject.MainLoop() self.bus = dbus.SystemBus() # check if there's already something owning our bus name org.freesmartphone.frameworkd if ( not self.options.values.noframework ) and ( "%s.frameworkd" % DBUS_BUS_NAME_PREFIX in self.bus.list_names() ): logger.error( "dbus bus name org.freesmartphone.frameworkd already claimed. Exiting." ) sys.exit( -1 ) self._subsystems = {} self.busnames = [] # FIXME remove hardcoded controller knowledge from objects self.config = config # gather subsystem plugins scantype scantype = config.getValue( "frameworkd", "scantype", "auto" ) # call me when idle and in mainloop gobject.idle_add( self.idle ) self._configureLoggers() self._handleOverrides() # launch special framework subsystem if ( not self.options.values.noframework ): self._subsystems["frameworkd"] = subsystem.Framework( self.bus, path, scantype, self ) Controller.objects.update( self._subsystems["frameworkd"].objects() ) systemstolaunch = self.options.values.subsystems.split( ',' ) # add internal subsystems subsystems = [ entry for entry in os.listdir( path ) if os.path.isdir( "%s/%s" % ( path, entry ) ) ] # add external subsystems for section in config.sections(): external = config.getValue( section, "external", "" ) if external and ( external not in subsystems ): subsystems.append( section ) # walk and launch subsystems for s in subsystems: disable = config.getBool( s, "disable", False ) external = config.getValue( s, "external", "" ) if disable: logger.info( "skipping subsystem %s as requested via config file." % s ) continue if systemstolaunch != [""]: if s not in systemstolaunch: logger.info( "skipping subsystem %s as requested via command line" % s ) continue if external: logger.info( "launching external subsystem %s" % s ) self._subsystems[s] = subsystem.External( s, external, scantype, self ) else: logger.info( "launching internal subsystem %s" % s ) self._subsystems[s] = subsystem.Subsystem( s, self.bus, path, scantype, self ) Controller.objects.update( self._subsystems[s].objects() ) # do we have any subsystems left? if len( self._subsystems ) <= 1: # no additional subsystems could be loaded if len( self._subsystems ) == 0 or 'frameworkd' in self._subsystems: logger.error( "can't launch without at least one subsystem. Exiting." ) sys.exit( -1 ) def launch( self ): if self.options.values.daemonize: self.start() # daemonize, then run self.run() else: self.run() def subsystems( self ): return self._subsystems def idle( self ): logger.info( "================== mainloop entered ===================" ) logger.info( "startup time was %.2f seconds" % ( time.time() - self.launchTime ) ) gobject.timeout_add_seconds( 1*60, self.timeout ) return False # mainloop: don't call me again def timeout( self ): """ Regular timeout. """ # FIXME add self-monitoring and self-healing ;) logger.debug( "alive and kicking" ) return True # mainloop: call me again def run( self ): """ Run the mainloop. Called after daemonizing, or (in debug mode), from Controller.launch() """ self.mainloop.run() def shutdown( self ): """ Quit the mainloop. """ logger.info( "shutting down..." ) for subsystem in self._subsystems.values(): subsystem.shutdown() self.mainloop.quit() # # private API # def _configureLoggers( self ): # configure all loggers, default being INFO for section in config.sections(): loglevel = loggingmap.get( config.getValue( section, "log_level", debug ) ) logger.debug( "setting logger for %s to %s" % ( section, loglevel ) ) logging.getLogger( section ).setLevel( loglevel ) def _handleOverrides( self ): for override in self.options.values.overrides: try: left, value = override.split( '=', 1 ) section, key = left.split( '.', 1 ) except ValueError: self.options.error( "Wrong format for override values" ) if not config.has_section( section ): config.add_section( section ) config.set( section, key, value ) #----------------------------------------------------------------------------# if __name__ == "__main__": import dbus bus = dbus.SystemBus() fso-frameworkd-0.10.1/framework/cxnet/000077500000000000000000000000001174525413000176375ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/cxnet/__init__.py000066400000000000000000000016001174525413000217450ustar00rootroot00000000000000# Copyright (c) 2008 Peter V. Saveliev # # This file is part of Connexion project. # # Connexion 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 3 of the License, or # (at your option) any later version. # # Connexion 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 Connexion; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA __all__ = [ "arp", "common", "ether", "generic", "ip4", "libpcap", "netlink", "tcp", "utils", ] fso-frameworkd-0.10.1/framework/cxnet/arp.py000066400000000000000000000100541174525413000207730ustar00rootroot00000000000000""" ARP protocol primitives """ # Copyright (c) 2008 Peter V. Saveliev # # This file is part of Connexion project. # # Connexion 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 3 of the License, or # (at your option) any later version. # # Connexion 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 Connexion; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA from ctypes import * ## ARP protocol HARDWARE identifiers. ARPHRD_NETROM = 0 # from KA9Q: NET/ROM pseudo ARPHRD_ETHER = 1 # Ethernet 10Mbps ARPHRD_EETHER = 2 # Experimental Ethernet ARPHRD_AX25 = 3 # AX.25 Level 2 ARPHRD_PRONET = 4 # PROnet token ring ARPHRD_CHAOS = 5 # Chaosnet ARPHRD_IEEE802 = 6 # IEEE 802.2 Ethernet/TR/TB ARPHRD_ARCNET = 7 # ARCnet ARPHRD_APPLETLK = 8 # APPLEtalk ARPHRD_DLCI = 15 # Frame Relay DLCI ARPHRD_ATM = 19 # ATM ARPHRD_METRICOM = 23 # Metricom STRIP (new IANA id) ARPHRD_IEEE1394 = 24 # IEEE 1394 IPv4 - RFC 2734 ARPHRD_EUI64 = 27 # EUI-64 ARPHRD_INFINIBAND = 32 # InfiniBand ## Dummy types for non ARP hardware ARPHRD_SLIP = 256 ARPHRD_CSLIP = 257 ARPHRD_SLIP6 = 258 ARPHRD_CSLIP6 = 259 ARPHRD_RSRVD = 260 # Notional KISS type ARPHRD_ADAPT = 264 ARPHRD_ROSE = 270 ARPHRD_X25 = 271 # CCITT X.25 ARPHRD_HWX25 = 272 # Boards with X.25 in firmware ARPHRD_PPP = 512 ARPHRD_CISCO = 513 # Cisco HDLC ARPHRD_HDLC = ARPHRD_CISCO ARPHRD_LAPB = 516 # LAPB ARPHRD_DDCMP = 517 # Digital's DDCMP protocol ARPHRD_RAWHDLC = 518 # Raw HDLC ARPHRD_TUNNEL = 768 # IPIP tunnel ARPHRD_TUNNEL6 = 769 # IP6IP6 tunnel ARPHRD_FRAD = 770 # Frame Relay Access Device ARPHRD_SKIP = 771 # SKIP vif ARPHRD_LOOPBACK = 772 # Loopback device ARPHRD_LOCALTLK = 773 # Localtalk device ARPHRD_FDDI = 774 # Fiber Distributed Data Interface ARPHRD_BIF = 775 # AP1000 BIF ARPHRD_SIT = 776 # sit0 device - IPv6-in-IPv4 ARPHRD_IPDDP = 777 # IP over DDP tunneller ARPHRD_IPGRE = 778 # GRE over IP ARPHRD_PIMREG = 779 # PIMSM register interface ARPHRD_HIPPI = 780 # High Performance Parallel Interface ARPHRD_ASH = 781 # Nexus 64Mbps Ash ARPHRD_ECONET = 782 # Acorn Econet ARPHRD_IRDA = 783 # Linux-IrDA ## ARP works differently on different FC media .. so ARPHRD_FCPP = 784 # Point to point fibrechannel ARPHRD_FCAL = 785 # Fibrechannel arbitrated loop ARPHRD_FCPL = 786 # Fibrechannel public loop ARPHRD_FCFABRIC = 787 # Fibrechannel fabric ## 787->799 reserved for fibrechannel media types ARPHRD_IEEE802_TR = 800 # Magic type ident for TR ARPHRD_IEEE80211 = 801 # IEEE 802.11 ARPHRD_IEEE80211_PRISM = 802 # IEEE 802.11 + Prism2 header ARPHRD_IEEE80211_RADIOTAP = 803 # IEEE 802.11 + radiotap header ARPHRD_MPLS_TUNNEL = 899 # MPLS Tunnel Interface ARPHRD_VOID = 0xFFFF # Void type, nothing is known ARPHRD_NONE = 0xFFFE # zero header length ## ARP protocol opcodes. ARPOP_REQUEST = 1 # ARP request ARPOP_REPLY = 2 # ARP reply ARPOP_RREQUEST = 3 # RARP request ARPOP_RREPLY = 4 # RARP reply ARPOP_InREQUEST = 8 # InARP request ARPOP_InREPLY = 9 # InARP reply ARPOP_NAK = 10 # (ATM)ARP NAK ## ARP Flag values. ATF_COM = 0x02 # completed entry (ha valid) ATF_PERM = 0x04 # permanent entry ATF_PUBL = 0x08 # publish entry ATF_USETRAILERS = 0x10 # has requested trailers ATF_NETMASK = 0x20 # want to use a netmask (only for proxy entries) ATF_DONTPUB = 0x40 # don't answer this addresses ## # This structure defines an ethernet arp header. ## class arphdr (BigEndianStructure): _fields_ = [ ("hrd", c_uint16), # format of hardware address ("pro", c_uint16), # format of protocol address ("hln", c_uint8), # length of hardware address ("pln", c_uint8), # length of protocol address ("op", c_uint16), # ARP opcode (command) ] fso-frameworkd-0.10.1/framework/cxnet/common.py000066400000000000000000000050271174525413000215050ustar00rootroot00000000000000""" libc, csum etc. """ # Copyright (c) 2008 Peter V. Saveliev # # This file is part of Connexion project. # # Connexion 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 3 of the License, or # (at your option) any later version. # # Connexion 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 Connexion; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA __all__ = [ "cx_int", "libc", "hdump", "hprint", "csum", "csum_words", "csum_complement", ] from ctypes import * from sys import maxint if maxint == 2147483647: cx_int = c_uint32 else: cx_int = c_uint64 libc = CDLL("libc.so.6") def hdump(name,msg,size=0): """ Dump a packet into a file """ if not size: size = sizeof(msg) fd = libc.open(name,577,384) libc.write(fd,byref(msg),size) libc.close(fd) def hline(msg,size=0): """ Format packet into a string """ if not size: size = sizeof(msg) length = size offset = 0 ptr = addressof(msg) line = "" result = "" while offset < length: a = c_ubyte.from_address(ptr).value result += "%02x " % (a) if 31 < a and a < 127: line += chr(a) else: line += '.' if offset: if not (offset + 1) % 8: result += ": " if not (offset + 1) % 16: result += "\t %s\n" % (str(line)) line = "" offset += 1 ptr += 1 if line: result += "\t\t\t\t %s\n" % (str(line)) return result def hprint(msg,size=0): """ Dump a packet onto stdout """ print hline(msg,size) class be16 (BigEndianStructure): _fields_ = [ ("c", c_uint16), ] def csum_words(msg,l): odd = False if (l%2): l -= 1 odd = True # l is in bytes. We need 16-bit words a = addressof(msg) x = 0 for i in xrange(0,l,2): c = be16.from_address(a + i) x += c.c if odd: last = c_uint16(c_uint8.from_address(a + l).value << 8) x += last.value return x def csum_complement(x): x = c_uint32(x) x1 = c_uint16.from_address(addressof(x)) x2 = c_uint16.from_address(addressof(x) + 2) return ~c_uint16(x1.value + x2.value).value def csum(msg,l): ## # details: rfc 1071 # a simple description: http://www.netfor2.com/checksum.html ## return csum_complement(csum_words(msg,l)) fso-frameworkd-0.10.1/framework/cxnet/ether.py000066400000000000000000000075661174525413000213360ustar00rootroot00000000000000""" Ethernet definitions from if_ether.h """ # Copyright (c) 2008 Peter V. Saveliev # # This file is part of Connexion project. # # Connexion 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 3 of the License, or # (at your option) any later version. # # Connexion 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 Connexion; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA from ctypes import * ## # IEEE 802.3 Ethernet magic constants. The frame sizes omit the preamble # and FCS/CRC (frame check sequence). ## ETH_ALEN = 6 # Octets in one ethernet addr ETH_HLEN = 14 # Total octets in header ETH_ZLEN = 60 # Min. octets in frame sans FCS ETH_DATA_LEN = 1500 # Max. octets in payload ETH_FRAME_LEN = 1514 # Max. octets in frame sans FCS ## # These are the defined Ethernet Protocol ID's. ## ETH_P_LOOP = 0x0060 # Ethernet Loopback packet ETH_P_PUP = 0x0200 # Xerox PUP packet ETH_P_PUPAT = 0x0201 # Xerox PUP Addr Trans packet ETH_P_IP = 0x0800 # Internet Protocol packet ETH_P_X25 = 0x0805 # CCITT X.25 ETH_P_ARP = 0x0806 # Address Resolution packet ETH_P_BPQ = 0x08FF # G8BPQ AX.25 Ethernet Packet [ NOT AN OFFICIALLY REGISTERED ID ] ETH_P_IEEEPUP = 0x0a00 # Xerox IEEE802.3 PUP packet ETH_P_IEEEPUPAT = 0x0a01 # Xerox IEEE802.3 PUP Addr Trans packet ETH_P_DEC = 0x6000 # DEC Assigned proto ETH_P_DNA_DL = 0x6001 # DEC DNA Dump/Load ETH_P_DNA_RC = 0x6002 # DEC DNA Remote Console ETH_P_DNA_RT = 0x6003 # DEC DNA Routing ETH_P_LAT = 0x6004 # DEC LAT ETH_P_DIAG = 0x6005 # DEC Diagnostics ETH_P_CUST = 0x6006 # DEC Customer use ETH_P_SCA = 0x6007 # DEC Systems Comms Arch ETH_P_RARP = 0x8035 # Reverse Addr Res packet ETH_P_ATALK = 0x809B # Appletalk DDP ETH_P_AARP = 0x80F3 # Appletalk AARP ETH_P_8021Q = 0x8100 # 802.1Q VLAN Extended Header ETH_P_IPX = 0x8137 # IPX over DIX ETH_P_IPV6 = 0x86DD # IPv6 over bluebook ETH_P_SLOW = 0x8809 # Slow Protocol. See 802.3ad 43B ETH_P_WCCP = 0x883E # Web-cache coordination protocol defined in draft-wilson-wrec-wccp-v2-00.txt ETH_P_PPP_DISC = 0x8863 # PPPoE discovery messages ETH_P_PPP_SES = 0x8864 # PPPoE session messages ETH_P_MPLS_UC = 0x8847 # MPLS Unicast traffic ETH_P_MPLS_MC = 0x8848 # MPLS Multicast traffic ETH_P_ATMMPOA = 0x884c # MultiProtocol Over ATM ETH_P_ATMFATE = 0x8884 # Frame-based ATM Transport over Ethernet ETH_P_AOE = 0x88A2 # ATA over Ethernet ETH_P_TIPC = 0x88CA # TIPC ## # Non DIX types. Won't clash for 1500 types. ## ETH_P_802_3 = 0x0001 # Dummy type for 802.3 frames ETH_P_AX25 = 0x0002 # Dummy protocol id for AX.25 ETH_P_ALL = 0x0003 # Every packet (be careful!!!) ETH_P_802_2 = 0x0004 # 802.2 frames ETH_P_SNAP = 0x0005 # Internal only ETH_P_DDCMP = 0x0006 # DEC DDCMP: Internal only ETH_P_WAN_PPP = 0x0007 # Dummy type for WAN PPP frames ETH_P_PPP_MP = 0x0008 # Dummy type for PPP MP frames ETH_P_LOCALTALK = 0x0009 # Localtalk pseudo type ETH_P_PPPTALK = 0x0010 # Dummy type for Atalk over PPP ETH_P_TR_802_2 = 0x0011 # 802.2 frames ETH_P_MOBITEX = 0x0015 # Mobitex (kaz@cafe.net) ETH_P_CONTROL = 0x0016 # Card specific control frames ETH_P_IRDA = 0x0017 # Linux-IrDA ETH_P_ECONET = 0x0018 # Acorn Econet ETH_P_HDLC = 0x0019 # HDLC frames ETH_P_ARCNET = 0x001A # 1A for ArcNet :-) ## # This is an Ethernet frame header. ## class ethhdr (BigEndianStructure): _pack_ = 1 _fields_ = [ ("dest", c_uint8 * ETH_ALEN), # destination eth addr ("source", c_uint8 * ETH_ALEN), # source ether addr ("proto", c_uint16), # packet type ID field ] fso-frameworkd-0.10.1/framework/cxnet/generic.py000066400000000000000000000032201174525413000216220ustar00rootroot00000000000000""" Generic IP protocol primitives """ # Copyright (c) 2008 Peter V. Saveliev # # This file is part of Connexion project. # # Connexion 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 3 of the License, or # (at your option) any later version. # # Connexion 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 Connexion; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA from ctypes import * class GenericHeader (BigEndianStructure): _fields_ = [ ("len", c_uint16), ("saddr", c_uint64), ("daddr", c_uint64), ] class GenericProtocol (object): """ Generic network protocol """ hdr = None res = None def __init__(self, hdr = GenericHeader): self.hdr = hdr self.init() def inc(self,msg): return self.post(self._inc(msg)) def _inc(self,msg): """ Incapsulate a message as a payload """ if msg is None: class packet (Structure): _pack_ = 1 _fields_ = [ ("hdr", type(self.hdr)), ] else: class packet (Structure): _pack_ = 1 _fields_ = [ ("hdr", type(self.hdr)), ("payload", type(msg)), ] x = packet() x.hdr = self.hdr if not msg is None: x.payload = msg return x def post(self,msg): return msg def init(self): pass fso-frameworkd-0.10.1/framework/cxnet/ip4.py000066400000000000000000000100251174525413000207030ustar00rootroot00000000000000""" IP definitions from linux/ip.h """ # Copyright (c) 2008 Peter V. Saveliev # # This file is part of Connexion project. # # Connexion 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 3 of the License, or # (at your option) any later version. # # Connexion 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 Connexion; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA from ctypes import * from cxnet.generic import * from cxnet.common import csum IPTOS_TOS_MASK = 0x1E IPTOS_LOWDELAY = 0x10 IPTOS_THROUGHPUT = 0x08 IPTOS_RELIABILITY = 0x04 IPTOS_MINCOST = 0x02 IPTOS_PREC_MASK = 0xE0 IPTOS_PREC_NETCONTROL = 0xe0 IPTOS_PREC_INTERNETCONTROL = 0xc0 IPTOS_PREC_CRITIC_ECP = 0xa0 IPTOS_PREC_FLASHOVERRIDE = 0x80 IPTOS_PREC_FLASH = 0x60 IPTOS_PREC_IMMEDIATE = 0x40 IPTOS_PREC_PRIORITY = 0x20 IPTOS_PREC_ROUTINE = 0x00 ## IP options IPOPT_COPY = 0x80 IPOPT_CLASS_MASK = 0x60 IPOPT_NUMBER_MASK = 0x1f IPOPT_CONTROL = 0x00 IPOPT_RESERVED1 = 0x20 IPOPT_MEASUREMENT = 0x40 IPOPT_RESERVED2 = 0x60 IPOPT_END = (0 |IPOPT_CONTROL) IPOPT_NOOP = (1 |IPOPT_CONTROL) IPOPT_SEC = (2 |IPOPT_CONTROL|IPOPT_COPY) IPOPT_LSRR = (3 |IPOPT_CONTROL|IPOPT_COPY) IPOPT_TIMESTAMP = (4 |IPOPT_MEASUREMENT) IPOPT_CIPSO = (6 |IPOPT_CONTROL|IPOPT_COPY) IPOPT_RR = (7 |IPOPT_CONTROL) IPOPT_SID = (8 |IPOPT_CONTROL|IPOPT_COPY) IPOPT_SSRR = (9 |IPOPT_CONTROL|IPOPT_COPY) IPOPT_RA = (20|IPOPT_CONTROL|IPOPT_COPY) IPVERSION = 4 MAXTTL = 255 IPDEFTTL = 64 IPOPT_OPTVAL = 0 IPOPT_OLEN = 1 IPOPT_OFFSET = 2 IPOPT_MINOFF = 4 MAX_IPOPTLEN = 40 IPOPT_NOP = IPOPT_NOOP IPOPT_EOL = IPOPT_END IPOPT_TS = IPOPT_TIMESTAMP IPOPT_TS_TSONLY = 0 # timestamps only IPOPT_TS_TSANDADDR = 1 # timestamps and addresses IPOPT_TS_PRESPEC = 3 # specified modules only IPV4_BEET_PHMAXLEN = 8 def iptos_tos(tos): return tos & IPTOS_TOS_MASK def iptos_prec(tos): return tos & IPTOS_PREC_MASK def ipopt_copied(o): return o & IPOPT_COPY def ipopt_class(o): return o & IPOPT_CLASS_MASK def ipopt_number(o): return o & IPOPT_NUMBER_MASK class iphdr (BigEndianStructure): _fields_ = [ ("version", c_uint8, 4), # first 4 bits ("ihl", c_uint8, 4), # ... ("tos", c_uint8), ("tot_len", c_uint16), ("id", c_uint16), ("f_res", c_uint16, 1), ("f_DF", c_uint16, 1), ("f_MF", c_uint16, 1), ("frag_off", c_uint16, 13), ("ttl", c_uint8), ("protocol", c_uint8), ("check", c_uint16), ("saddr", c_uint32), ("daddr", c_uint32), ] def __init__(self): BigEndianStructure.__init__(self) self.version = 4 self.ttl = 64 self.id = 0 class ip_auth_hdr (BigEndianStructure): _fields_ = [ ("nexthdr", c_uint8), ("hdrlen", c_uint8), # This one is measured in 32 bit units! ("reserved", c_uint16), ("spi", c_uint32), ("seq_no", c_uint32), # Sequence number ("auth_data", c_uint8 * 4), # Variable len but >=4. Mind the 64 bit alignment! ] class ip_esp_hdr (BigEndianStructure): _fields_ = [ ("spi", c_uint32), ("seq_no", c_uint32), # Sequence number ("enc_data", c_uint8 * 8), # Variable len but >=8. Mind the 64 bit alignment! ] class ip_comp_hdr (BigEndianStructure): _fields_ = [ ("nexthdr", c_uint8), ("flags", c_uint8), ("cpi", c_uint16), ] class ip_beet_phdr (BigEndianStructure): _fields_ = [ ("nexthdr", c_uint8), ("hdrlen", c_uint8), ("padlen", c_uint8), ("reserved", c_uint8), ] class IPv4Protocol(GenericProtocol): def post(self,msg): msg.hdr.ihl = sizeof(msg.hdr) / 4 msg.hdr.tot_len = sizeof(msg.payload) + sizeof(msg.hdr) msg.hdr.check = 0 msg.hdr.check = csum(msg.hdr,sizeof(msg.hdr)) return msg fso-frameworkd-0.10.1/framework/cxnet/libpcap.py000066400000000000000000000025021174525413000216220ustar00rootroot00000000000000""" A simple libpcap injector """ # Copyright (c) 2008 Peter V. Saveliev # # This file is part of Connexion project. # # Connexion 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 3 of the License, or # (at your option) any later version. # # Connexion 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 Connexion; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA __all__ = ["pcap_interface"] from ctypes import * PCAP_ERRBUF_SIZE = 256 libp = CDLL("libpcap.so.0.8") class pcap_interface(object): error = None psock = None def __init__(self,name): self.error = create_string_buffer(PCAP_ERRBUF_SIZE) self.psock = libp.pcap_open_live(name,65535,0,1,byref(self.error)) def inject(self,packet): return libp.pcap_inject(self.psock,byref(packet),sizeof(packet)) def perror(self): print string_at(addressof(self.error)) def close(self): libp.pcap_close(self.psock) fso-frameworkd-0.10.1/framework/cxnet/netlink/000077500000000000000000000000001174525413000213035ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/cxnet/netlink/__init__.py000066400000000000000000000015021174525413000234120ustar00rootroot00000000000000# Copyright (c) 2008 Peter V. Saveliev # # This file is part of Connexion project. # # Connexion 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 3 of the License, or # (at your option) any later version. # # Connexion 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 Connexion; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA __all__ = [ "generic", "ipq", "rtnl", ] fso-frameworkd-0.10.1/framework/cxnet/netlink/generic.py000066400000000000000000000120221174525413000232660ustar00rootroot00000000000000""" Generic Netlink protocol implementation """ # Copyright (c) 2007-2008 ALT Linux, Peter V. Saveliev # # This file is part of Connexion project. # # Connexion 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 3 of the License, or # (at your option) any later version. # # Connexion 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 Connexion; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA from ctypes import * from cxnet.common import * from socket import AF_NETLINK, SOCK_RAW from os import getpid ## Netlink family # NETLINK_ROUTE = 0 # Routing/device hook NETLINK_UNUSED = 1 # Unused number NETLINK_USERSOCK = 2 # Reserved for user mode socket protocols NETLINK_FIREWALL = 3 # Firewalling hook NETLINK_INET_DIAG = 4 # INET socket monitoring NETLINK_NFLOG = 5 # netfilter/iptables ULOG NETLINK_XFRM = 6 # ipsec NETLINK_SELINUX = 7 # SELinux event notifications NETLINK_ISCSI = 8 # Open-iSCSI NETLINK_AUDIT = 9 # auditing NETLINK_FIB_LOOKUP = 10 NETLINK_CONNECTOR = 11 NETLINK_NETFILTER = 12 # netfilter subsystem NETLINK_IP6_FW = 13 NETLINK_DNRTMSG = 14 # DECnet routing messages NETLINK_KOBJECT_UEVENT = 15 # Kernel messages to userspace NETLINK_GENERIC = 16 # leave room for NETLINK_DM (DM Events) NETLINK_SCSITRANSPORT = 18 # SCSI Transports ## Netlink message flags values (nlmsghdr.flags) # NLM_F_REQUEST = 1 # It is request message. NLM_F_MULTI = 2 # Multipart message, terminated by NLMSG_DONE NLM_F_ACK = 4 # Reply with ack, with zero or error code NLM_F_ECHO = 8 # Echo this request # Modifiers to GET request NLM_F_ROOT = 0x100 # specify tree root NLM_F_MATCH = 0x200 # return all matching NLM_F_ATOMIC = 0x400 # atomic GET NLM_F_DUMP = (NLM_F_ROOT|NLM_F_MATCH) # Modifiers to NEW request NLM_F_REPLACE = 0x100 # Override existing NLM_F_EXCL = 0x200 # Do not touch, if it exists NLM_F_CREATE = 0x400 # Create, if it does not exist NLM_F_APPEND = 0x800 # Add to end of list NLMSG_NOOP = 0x1 # Nothing NLMSG_ERROR = 0x2 # Error NLMSG_DONE = 0x3 # End of a dump NLMSG_OVERRUN = 0x4 # Data lost NLMSG_MIN_TYPE = 0x10 # < 0x10: reserved control messages NLMSG_MAX_LEN = 0x1000# Max message length # 8<-------------------------------------------------------- # # structures for recvmsg(2) support # class iov(Structure): _fields_ = [ ("buf",cx_int), ("size",cx_int), ] class rmsg(Structure): _fields_ = [ ("sa_addr",cx_int), ("sa_size",cx_int), ("iov_addr",cx_int), ("x1",cx_int), ("x2",cx_int), ("x3",cx_int), ("x4",cx_int), ] # 8<-------------------------------------------------------- class nlmsghdr(Structure): """ Generic Netlink message header """ _fields_ = [ ("length", c_uint32), ("type", c_uint16), ("flags", c_uint16), ("sequence_number", c_uint32), ("pid", c_uint32), ] class nlmsg(Structure): """ Generic Netlink message structure """ _fields_ = [ ("hdr", nlmsghdr), ("data", c_byte * NLMSG_MAX_LEN), ] class sockaddr(Structure): """ Sockaddr structure, see bind(2) """ _fields_ = [ ("family", c_ushort), ("pad", c_ushort), ("pid", c_uint32), ("groups", c_uint32), ] class nl_socket(object): """ Generic Netlink socket """ fd = None # socket file descriptor msg = nlmsg # message pattern def __init__(self, family=NETLINK_GENERIC, groups=0): """ Create and bind socket structure """ self.fd = libc.socket(AF_NETLINK,SOCK_RAW,family) sa = sockaddr() sa.family = AF_NETLINK sa.pid = getpid() sa.groups = groups l = libc.bind(self.fd, byref(sa), sizeof(sa)) if l != 0: self.close() raise Exception("libc.bind(): errcode %i" % (l)) def close(self): """ Close the socket """ libc.close(self.fd) def recv(self): """ Receive a packet from Netlink socket (using recvfrom(2)) """ msg = self.msg() l = libc.recvfrom(self.fd, byref(msg), sizeof(msg), 0, 0, 0) if l == -1: msg = None return (l,msg) def recv2(self): """ Receive a packet from Netlink socket (using recvmsg(2)) """ buf = self.msg() i = iov(addressof(buf),sizeof(buf)) sa = sockaddr() msg = rmsg(addressof(sa),sizeof(sa),addressof(i),1,0,0,0) l = libc.recvmsg(self.fd, byref(msg), 0) if l == -1: msg = None return (l,buf) def send(self, msg, size=0): """ Send a packet through Netlink socket """ if not size: size = sizeof(msg) sa = sockaddr() sa.family = AF_NETLINK sa.pid = 0 self.prepare(msg, size) l = libc.sendto(self.fd, byref(msg), size, 0, byref(sa), sizeof(sa)) return l def prepare(self, msg, size=0): """ Adjust message header fields before sending """ if not size: size = sizeof(msg) msg.hdr.length = size msg.hdr.pid = getpid() fso-frameworkd-0.10.1/framework/cxnet/netlink/ipq.py000066400000000000000000000056071174525413000224560ustar00rootroot00000000000000""" Netlink IP Queue """ # Copyright (c) 2008 Peter V. Saveliev # # This file is part of Connexion project. # # Connexion 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 3 of the License, or # (at your option) any later version. # # Connexion 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 Connexion; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA from generic import * from cxnet.common import * # Types of IPQ messages IPQM_BASE = 0x10 # standard netlink messages below this IPQM_MODE = IPQM_BASE + 1 # Mode request from peer IPQM_VERDICT = IPQM_BASE + 2 # Verdict from peer IPQM_PACKET = IPQM_BASE + 3 # Packet from kernel IPQM_MAX = IPQM_BASE + 4 IPQ_COPY_NONE = 0 # Initial mode, packets are dropped IPQ_COPY_META = 1 # Copy metadata IPQ_COPY_PACKET = 2 # Copy metadata + packet (range) IPQ_MAX_PAYLOAD = 0x800 # Responses from hook functions NF_DROP = 0 NF_ACCEPT = 1 NF_STOLEN = 2 NF_QUEUE = 3 NF_REPEAT = 4 NF_STOP = 5 class _ipq_mode_msg(Structure): _fields_ = [ ("value", c_ubyte), ("range", cx_int), ] class _ipq_packet_msg(Structure): _fields_ = [ ("packet_id", c_ulong), ("mark", c_ulong), ("timestamp_sec", c_long), ("timestamp_usec", c_long), ("hook", c_uint), ("indev_name", c_char * 16), ("outdev_name", c_char * 16), ("hw_protocol", c_ushort), ("hw_type", c_ushort), ("hw_addrlen", c_ubyte), ("hw_addr", c_ubyte * 8), ("data_len", cx_int), ("payload", c_byte * IPQ_MAX_PAYLOAD), ] class _ipq_verdict_msg(Structure): _fields_ = [ ("value", c_uint), ("id", c_ulong), ("data_len", cx_int), ("payload", c_ubyte), ] class _ipq_peer_msg(Union): _fields_ = [ ("mode", _ipq_mode_msg), ("verdict", _ipq_verdict_msg), ] class ipq_peer_msg(Structure): _fields_ = [ ("hdr", nlmsghdr), ("data", _ipq_peer_msg), ] class ipq_packet_msg(Structure): _fields_ = [ ("hdr", nlmsghdr), ("data", _ipq_packet_msg), ] class ipq_socket(nl_socket): """ IPQ socket """ msg = ipq_packet_msg def __init__(self, mode=IPQ_COPY_PACKET): nl_socket.__init__(self, family=NETLINK_FIREWALL) msg = ipq_peer_msg() msg.hdr.type = IPQM_MODE msg.hdr.flags = NLM_F_REQUEST msg.data.mode.value = mode msg.data.mode.range = IPQ_MAX_PAYLOAD self.send(msg) def verdict(self, seq, v): msg = ipq_peer_msg() msg.hdr.type = IPQM_VERDICT msg.hdr.flags = NLM_F_REQUEST msg.data.verdict.value = v msg.data.verdict.id = seq msg.data.verdict.data_len = 0 self.send(msg) fso-frameworkd-0.10.1/framework/cxnet/netlink/rtnl.py000066400000000000000000000326571174525413000226510ustar00rootroot00000000000000""" RT Netlink protocol """ # Copyright (c) 2007-2008 ALT Linux, Peter V. Saveliev # # This file is part of Connexion project. # # Connexion 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 3 of the License, or # (at your option) any later version. # # Connexion 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 Connexion; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA from generic import * from cxnet.common import * import types from os import listdir ## RTnetlink multicast groups RTNLGRP_NONE = 0x0 RTNLGRP_LINK = 0x1 RTNLGRP_NOTIFY = 0x2 RTNLGRP_NEIGH = 0x4 RTNLGRP_TC = 0x8 RTNLGRP_IPV4_IFADDR = 0x10 RTNLGRP_IPV4_MROUTE = 0x20 RTNLGRP_IPV4_ROUTE = 0x40 RTNLGRP_IPV4_RULE = 0x80 RTNLGRP_IPV6_IFADDR = 0x100 RTNLGRP_IPV6_MROUTE = 0x200 RTNLGRP_IPV6_ROUTE = 0x400 RTNLGRP_IPV6_IFINFO = 0x800 RTNLGRP_DECnet_IFADDR = 0x1000 RTNLGRP_NOP2 = 0x2000 RTNLGRP_DECnet_ROUTE = 0x4000 RTNLGRP_DECnet_RULE = 0x8000 RTNLGRP_NOP4 = 0x10000 RTNLGRP_IPV6_PREFIX = 0x20000 RTNLGRP_IPV6_RULE = 0x40000 ## Types of messages RTM_BASE = 16 RTM_NEWLINK = 16 RTM_DELLINK = 17 RTM_GETLINK = 18 RTM_SETLINK = 19 RTM_NEWADDR = 20 RTM_DELADDR = 21 RTM_GETADDR = 22 RTM_NEWROUTE = 24 RTM_DELROUTE = 25 RTM_GETROUTE = 26 RTM_NEWNEIGH = 28 RTM_DELNEIGH = 29 RTM_GETNEIGH = 30 RTM_NEWRULE = 32 RTM_DELRULE = 33 RTM_GETRULE = 34 RTM_NEWQDISC = 36 RTM_DELQDISC = 37 RTM_GETQDISC = 38 RTM_NEWTCLASS = 40 RTM_DELTCLASS = 41 RTM_GETTCLASS = 42 RTM_NEWTFILTER = 44 RTM_DELTFILTER = 45 RTM_GETTFILTER = 46 RTM_NEWACTION = 48 RTM_DELACTION = 49 RTM_GETACTION = 50 RTM_NEWPREFIX = 52 RTM_GETMULTICAST = 58 RTM_GETANYCAST = 62 RTM_NEWNEIGHTBL = 64 RTM_GETNEIGHTBL = 66 RTM_SETNEIGHTBL = 67 class rtnl_hdr(Structure): _fields_ = [ ("length", c_ushort), ("type", c_ushort), ] # 8<------------------------------------------------------------------------ class ifaddrmsg(Structure): _fields_ = [ ("family", c_ubyte), # Address family ("prefixlen", c_ubyte), # Address' prefix length ("flags", c_ubyte), # Address flags ("scope", c_ubyte), # Adress scope ("index", c_int), # Interface index ] class ifinfmsg(Structure): _fields_ = [ ("family", c_ubyte), # AF_UNSPEC (?) ("type", c_uint16), # Interface type ("index", c_int), # Interface index ("flags", c_int), # Interface flags (netdevice(7)) ("change", c_int), # Change mask (reserved, always 0xFFFFFFFF) ] class ndmsg(Structure): _fields_ = [ ("family", c_ubyte), # ("index", c_int), # Interface index ("state", c_uint16), # Neighbor entry state ("flags", c_uint8), # Neighbor entry flags ("type", c_uint8), # ] class rtmsg(Structure): # kernel://ipv4/route.c:2565 static int rt_fill_info(...) _fields_ = [ ("family", c_ubyte), # Route address family ("dst_len", c_ubyte), # Destination address mask ("src_len", c_ubyte), # Source address mask ("tos", c_ubyte), # TOS filter ("table", c_ubyte), # Routing table id ("proto", c_ubyte), # Routing protocol ("scope", c_ubyte), ("type", c_ubyte), ("flags", c_int32), ] # 8<------------------------------------------------------------------------ class rtnl_payload(Union): _fields_ = [ ("link", ifinfmsg), ("address", ifaddrmsg), ("route", rtmsg), ("neigh", ndmsg), ("raw", (c_byte * NLMSG_MAX_LEN)), ] class rtnl_msg(Structure): _fields_ = [ ("hdr", nlmsghdr), ("data", rtnl_payload), ] class rtnl_socket(nl_socket): """ Netlink RT socket implementation """ msg = rtnl_msg def __init__(self,groups = 0): nl_socket.__init__(self, family=NETLINK_ROUTE, groups=groups) ### # attribute types ### from cxutil.ip import * class t_attr: def set(self,address,typ,obj): class attr(Structure): pass # align block x = 4 k = sizeof(rtnl_hdr) + sizeof(type(obj)) align = (k + x - 1) & ~ (x - 1) pad = align - (sizeof(rtnl_hdr) + sizeof(type(obj))) if pad: attr._fields_ = [("hdr",rtnl_hdr), ("data",type(obj)), ("pad",(c_ubyte * pad))] else: attr._fields_ = [("hdr",rtnl_hdr), ("data",type(obj))] a = attr() # prepare header h = rtnl_hdr() h.type = typ h.length = sizeof(attr) # attach header and data a.hdr = h a.data = obj memmove(address,addressof(a),sizeof(a)) return address + sizeof(a) class t_ip4ad_raw: def get(self,address): return (c_uint8 * 4).from_address(address + sizeof(rtnl_hdr)) class t_ip4ad: def get(self,address): r = t_ip4ad_raw().get(address) return "%u.%u.%u.%u" % (r[0], r[1], r[2], r[3]) class t_l2ad_raw: def get(self,address): return (c_uint8 * 6).from_address(address + sizeof(rtnl_hdr)) class t_l2ad: def get(self,address): r = t_l2ad_raw().get(address) return "%x:%x:%x:%x:%x:%x" % (r[0], r[1], r[2], r[3], r[4], r[5]) class t_uint: def get(self,address): return c_uint.from_address(address + sizeof(rtnl_hdr)).value class t_asciiz: def get(self,address): return string_at(address + sizeof(rtnl_hdr)) class t_none: def get(self,address): return None class rtnl_attr(object): """ RT Netlink message attribute """ pass ## address attributes # # Important comment: # IFA_ADDRESS is prefix address, rather than local interface address. # It makes no difference for normally configured broadcast interfaces, # but for point-to-point IFA_ADDRESS is DESTINATION address, # local address is supplied in IFA_LOCAL attribute. # IFA_UNSPEC = 0 IFA_ADDRESS = 1 IFA_LOCAL = 2 IFA_LABEL = 3 IFA_BROADCAST = 4 IFA_ANYCAST = 5 IFA_CACHEINFO = 6 IFA_MULTICAST = 7 class ifa_attr (rtnl_attr): def __init__(self): self._map_ = { IFA_UNSPEC: (t_none, "none"), IFA_ADDRESS: (t_ip4ad, "address"), IFA_LOCAL: (t_ip4ad, "local"), IFA_LABEL: (t_asciiz, "dev"), IFA_BROADCAST: (t_ip4ad, "broadcast"), IFA_ANYCAST: (t_ip4ad, "anycast"), IFA_CACHEINFO: (t_none, "cacheinfo"), IFA_MULTICAST: (t_ip4ad, "multycast"), } ## neighbor attributes NDA_UNSPEC = 0 NDA_DST = 1 NDA_LLADDR = 2 NDA_CACHEINFO = 3 NDA_PROBES = 4 class nda_attr (rtnl_attr): def __init__(self): self._map_ = { NDA_UNSPEC: (t_none, "none"), NDA_DST: ( (t_ip4ad, "dest"), (t_ip4ad_raw, "raw_dest"), ), NDA_LLADDR: ( (t_l2ad, "lladdr"), (t_l2ad_raw, "raw_lladdr"), ), NDA_CACHEINFO: (t_none, "cacheinfo"), NDA_PROBES: (t_none, "probes"), } ## route attributes RTA_UNSPEC = 0 RTA_DST = 1 RTA_SRC = 2 RTA_IIF = 3 RTA_OIF = 4 RTA_GATEWAY = 5 RTA_PRIORITY = 6 RTA_PREFSRC = 7 RTA_METRICS = 8 RTA_MULTIPATH = 9 RTA_PROTOINFO = 10 RTA_FLOW = 11 RTA_CACHEINFO = 12 # FIXME: kernel://include/linux/rtnetlink.h:320, struct rta_cacheinfo RTA_SESSION = 13 RTA_MP_ALGO = 14 # no longer used RTA_TABLE = 15 ## rtmsg.type RTN_UNSPEC = 0 RTN_UNICAST = 1 # Gateway or direct route RTN_LOCAL = 2 # Accept locally RTN_BROADCAST = 3 # Accept locally as broadcast, send as broadcast RTN_ANYCAST = 4 # Accept locally as broadcast, but send as unicast RTN_MULTICAST = 5 # Multicast route RTN_BLACKHOLE = 6 # Drop RTN_UNREACHABLE = 7 # Destination is unreachable RTN_PROHIBIT = 8 # Administratively prohibited RTN_THROW = 9 # Not in this table RTN_NAT = 10 # Translate this address RTN_XRESOLVE = 11 # Use external resolver ## rtmsg.proto RTPROT_UNSPEC = 0 RTPROT_REDIRECT = 1 # Route installed by ICMP redirects; not used by current IPv4 RTPROT_KERNEL = 2 # Route installed by kernel RTPROT_BOOT = 3 # Route installed during boot RTPROT_STATIC = 4 # Route installed by administrator # Values of protocol >= RTPROT_STATIC are not interpreted by kernel; # they are just passed from user and back as is. # It will be used by hypothetical multiple routing daemons. # Note that protocol values should be standardized in order to # avoid conflicts. RTPROT_GATED = 8 # Apparently, GateD RTPROT_RA = 9 # RDISC/ND router advertisements RTPROT_MRT = 10 # Merit MRT RTPROT_ZEBRA = 11 # Zebra RTPROT_BIRD = 12 # BIRD RTPROT_DNROUTED = 13 # DECnet routing daemon RTPROT_XORP = 14 # XORP RTPROT_NTK = 15 # Netsukuku ## rtmsg.scope RT_SCOPE_UNIVERSE = 0 # User defined values RT_SCOPE_SITE = 200 RT_SCOPE_LINK = 253 RT_SCOPE_HOST = 254 RT_SCOPE_NOWHERE = 255 ## rtmsg.flags RTM_F_NOTIFY = 0x100 # Notify user of route change RTM_F_CLONED = 0x200 # This route is cloned RTM_F_EQUALIZE = 0x400 # Multipath equalizer: NI RTM_F_PREFIX = 0x800 # Prefix addresses class rta_attr (rtnl_attr): def __init__(self): self._map_ = { RTA_UNSPEC: (t_none, "none"), RTA_DST: (t_ip4ad, "dst_prefix"), RTA_SRC: (t_ip4ad, "src_prefix"), RTA_IIF: (t_uint, "input_link"), RTA_OIF: (t_uint, "output_link"), RTA_GATEWAY: (t_ip4ad, "gateway"), RTA_PRIORITY: (t_uint, "priority"), RTA_PREFSRC: (t_ip4ad, "prefsrc"), RTA_METRICS: (t_uint, "metric"), RTA_MULTIPATH: (t_none, "mp"), RTA_PROTOINFO: (t_none, "protoinfo"), RTA_FLOW: (t_none, "flow"), RTA_CACHEINFO: (t_none, "cacheinfo"), RTA_SESSION: (t_none, "session"), RTA_MP_ALGO: (t_none, "mp_algo"), # no longer used RTA_TABLE: (t_uint, "table"), } ## link attributes IFLA_UNSPEC = 0 IFLA_ADDRESS = 1 IFLA_BROADCAST = 2 IFLA_IFNAME = 3 IFLA_MTU = 4 IFLA_LINK = 5 IFLA_QDISC = 6 IFLA_STATS = 7 IFLA_COST = 8 IFLA_PRIORITY = 9 IFLA_MASTER = 10 IFLA_WIRELESS = 11 # Wireless Extension event - see iproute2:wireless.h IFLA_PROTINFO = 12 # Protocol specific information for a link IFLA_TXQLEN = 13 IFLA_MAP = 14 IFLA_WEIGHT = 15 IFLA_OPERSTATE = 16 IFLA_LINKMODE = 17 class ifla_attr (rtnl_attr): def __init__(self): self._map_ = { IFLA_UNSPEC: (t_none, "none"), IFLA_ADDRESS: ( (t_l2ad, "hwaddr"), (t_l2ad_raw, "raw_hwaddr"), ), IFLA_BROADCAST: ( (t_l2ad, "broadcast"), (t_l2ad_raw, "raw_broadcast"), ), IFLA_IFNAME: (t_asciiz, "dev"), IFLA_MTU: (t_uint, "mtu"), IFLA_LINK: (t_uint, "link"), IFLA_QDISC: (t_asciiz, "qdisc"), IFLA_STATS: (t_none, "stats"), } ## netdevice flags iff = {} iff["UP"] = 0x1 # interface is up iff["BROADCAST"] = 0x2 # broadcast address valid iff["DEBUG"] = 0x4 # turn on debugging iff["LOOPBACK"] = 0x8 # is a loopback net iff["POINTOPOINT"] = 0x10 # interface is has p-p link iff["NOTRAILERS"] = 0x20 # avoid use of trailers iff["RUNNING"] = 0x40 # resources allocated iff["NOARP"] = 0x80 # no ARP protocol iff["PROMISC"] = 0x100 # receive all packets iff["ALLMULTI"] = 0x200 # receive all multicast packets iff["MASTER"] = 0x400 # master of a load balancer iff["SLAVE"] = 0x800 # slave of a load balancer iff["MULTICAST"] = 0x1000# supports multicast iff["PORTSEL"] = 0x2000# can set media type iff["AUTOMEDIA"] = 0x4000# auto media select active iff["DYNAMIC"] = 0x8000# dialup device with changing addresses class rtnl_msg_parser(object): """ Generic RT Netlink attribute parser """ def get_attr(self,t,ptr,seen): hdr = rtnl_hdr.from_address(ptr) key = None result = None ### # FIXME # x -- Alignment. Must NOT be hardcoded! But ctypes gives # wrong alignment here. ### x = 4 k = hdr.length ### # possible variants: # * python-specific: k - k%s + (k%x and x) # cause in python ((y > 0) and x) == x # * from lau: ((k + x - 1) / x) * x # * from kernel sources: (k + x - 1) & ~ (x - 1) align = (k + x - 1) & ~ (x - 1) if t._map_.has_key(hdr.type): if type(t._map_[hdr.type][0]) == types.TupleType: result = t._map_[hdr.type][seen][0]().get(ptr) key = t._map_[hdr.type][seen][1] seen += 1 if seen == len(t._map_[hdr.type]): seen = 0 else: align = 0 else: result = t._map_[hdr.type][0]().get(ptr) key = t._map_[hdr.type][1] seen = 0 return (hdr.length,align,hdr.type,key,result,seen) def parse(self,msg): r = {} t = msg.hdr.type aa = [RTM_NEWADDR,RTM_NEWLINK,RTM_NEWROUTE,RTM_NEWNEIGH] ad = [RTM_DELADDR,RTM_DELLINK,RTM_DELROUTE,RTM_DELNEIGH] ## message type if \ t <= RTM_DELLINK: r["type"] = "link" r["index"] = msg.data.link.index r["flags"] = [] for (i,k) in iff.items(): if k & msg.data.link.flags: r["flags"].append(i) bias = ifinfmsg at = ifla_attr() elif \ t <= RTM_DELADDR: r["type"] = "address" r["mask"] = msg.data.address.prefixlen bias = ifaddrmsg at = ifa_attr() elif \ t <= RTM_DELROUTE: r["type"] = "route" r["dst_len"] = msg.data.route.dst_len r["src_len"] = msg.data.route.src_len r["t"] = msg.data.route.table bias = rtmsg at = rta_attr() elif \ t <= RTM_GETNEIGH: r["type"] = "neigh" r["index"] = msg.data.neigh.index bias = ndmsg at = nda_attr() else: r["type"] = "fake" r["action"] = "fake" return r ## message action if t in aa: r["action"] = "add" elif t in ad: r["action"] = "del" ptr = addressof(msg) + sizeof(nlmsghdr) + sizeof(bias) seen = 0 while True: (k,l,t,key,data,seen) = self.get_attr(at,ptr,seen) if k == 0: break if key: r[key] = data ptr += l if r.has_key("dev"): ### # find a PPP session for this device ### if r["dev"][:3] == "ppp": # list all PPP session files in /var/run for i in listdir("/var/run"): if (i[:3] == "ppp") and (i[-3:] == "pid"): try: fd = open("/var/run/%s" % (i),"r") for m in fd.readlines(): m = m.strip() if m == r["dev"]: r["session"] = i[4:-4] fd.close() except: pass return r fso-frameworkd-0.10.1/framework/cxnet/netlink/taskstats.py000066400000000000000000000136511174525413000237040ustar00rootroot00000000000000""" Netlink Taskstats protocol implementation """ # Copyright (c) 2008 ALT Linux, Peter V. Saveliev # # This file is part of Connexion project. # # Connexion 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 3 of the License, or # (at your option) any later version. # # Connexion 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 Connexion; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA from generic import * from cxnet.common import * TASKSTATS_VERSION = 6 TS_COMM_LEN = 32 class taskstatsmsg(Structure): _pack_ = 8 _fields_ = [ # The version number of this struct. This field is always set to # TAKSTATS_VERSION, which is defined in . # Each time the struct is changed, the value should be incremented. ("version", c_uint16), ("ac_exitcode", c_uint32), # Exit status # The accounting flags of a task as defined in # Defined values are AFORK, ASU, ACOMPAT, ACORE, and AXSIG. ("ac_flag", c_uint8), # Record flags ("ac_nice", c_uint8), # task_nice # Delay accounting fields start # # All values, until comment "Delay accounting fields end" are # available only if delay accounting is enabled, even though the last # few fields are not delays # # xxx_count is the number of delay values recorded # xxx_delay_total is the corresponding cumulative delay in nanoseconds # # xxx_delay_total wraps around to zero on overflow # xxx_count incremented regardless of overflow # Delay waiting for cpu, while runnable # count, delay_total NOT updated atomically ("cpu_count", c_uint64), #__u64 cpu_count __attribute__((aligned(8))); ("cpu_delay_total", c_uint64), # Following four fields atomically updated using task->delays->lock # Delay waiting for synchronous block I/O to complete # does not account for delays in I/O submission ("blkio_count", c_uint64), ("blkio_delay_total", c_uint64), # Delay waiting for page fault I/O (swap in only) ("swapin_count", c_uint64), ("swapin_delay_total", c_uint64), # cpu "wall-clock" running time # On some architectures, value will adjust for cpu time stolen # from the kernel in involuntary waits due to virtualization. # Value is cumulative, in nanoseconds, without a corresponding count # and wraps around to zero silently on overflow ("cpu_run_real_total", c_uint64), # cpu "virtual" running time # Uses time intervals seen by the kernel i.e. no adjustment # for kernel's involuntary waits due to virtualization. # Value is cumulative, in nanoseconds, without a corresponding count # and wraps around to zero silently on overflow ("cpu_run_virtual_total", c_uint64), # Delay accounting fields end # version 1 ends here # Basic Accounting Fields start ("ac_comm", (c_char * TS_COMM_LEN)), # Command name ("ac_sched", c_ubyte), # Scheduling discipline ("ac_pad", (c_char * 3)), ("ac_uid", c_uint32), # User ID ("ac_gid", c_uint32), # Group ID ("ac_pid", c_uint32), # Process ID ("ac_ppid", c_uint32), # Parent process ID ("ac_btime", c_uint32), # Begin time [sec since 1970] ("ac_etime", c_uint64), # Elapsed time [usec] ("ac_utime", c_uint64), # User CPU time [usec] ("ac_stime", c_uint64), # System CPU time [usec] ("ac_minflt", c_uint64), # Minor Page Fault Count ("ac_majflt", c_uint64), # Major Page Fault Count # Basic Accounting Fields end # Extended accounting fields start # Accumulated RSS usage in duration of a task, in MBytes-usecs. # The current rss usage is added to this counter every time # a tick is charged to a task's system time. So, at the end we # will have memory usage multiplied by system time. Thus an # average usage per system time unit can be calculated. ("coremem", c_uint64), # accumulated RSS usage in MB-usec # Accumulated virtual memory usage in duration of a task. # Same as acct_rss_mem1 above except that we keep track of VM usage. ("virtmem", c_uint64), # accumulated VM usage in MB-usec # High watermark of RSS and virtual memory usage in duration of # a task, in KBytes. ("hiwater_rss", c_uint64), # High-watermark of RSS usage, in KB ("hiwater_vm", c_uint64), # High-water VM usage, in KB # The following four fields are I/O statistics of a task. ("read_char", c_uint64), # bytes read ("write_char", c_uint64), # bytes written ("read_syscalls", c_uint64), # read syscalls ("write_syscalls", c_uint64), # write syscalls # Extended accounting fields end # Per-task storage I/O accounting starts ("read_bytes", c_uint64), # bytes of read I/O ("write_bytes", c_uint64), # bytes of write I/O ("cancelled_write_bytes", c_uint64), # bytes of cancelled write I/O ("nvcsw", c_uint64), # voluntary_ctxt_switches ("nivcsw", c_uint64), # nonvoluntary_ctxt_switches # time accounting for SMT machines ("ac_utimescaled", c_uint64), # utime scaled on frequency etc ("ac_stimescaled", c_uint64), # stime scaled on frequency etc ("cpu_scaled_run_real_total", c_uint64), # scaled cpu_run_real_total ] # # Commands sent from userspace # Not versioned. TASKSTATS_CMD_UNSPEC = 0 # Reserved TASKSTATS_CMD_GET = 1 # user->kernel request/get-response TASKSTATS_CMD_NEW = 2 # kernel->user event TASKSTATS_TYPE_UNSPEC = 0 # Reserved TASKSTATS_TYPE_PID = 1 # Process id TASKSTATS_TYPE_TGID = 2 # Thread group id TASKSTATS_TYPE_STATS = 3 # taskstats structure TASKSTATS_TYPE_AGGR_PID = 4 # contains pid + stats TASKSTATS_TYPE_AGGR_TGID = 5 # contains tgid + stats TASKSTATS_CMD_ATTR_UNSPEC = 0 TASKSTATS_CMD_ATTR_PID = 1 TASKSTATS_CMD_ATTR_TGID = 2 TASKSTATS_CMD_ATTR_REGISTER_CPUMASK = 3 TASKSTATS_CMD_ATTR_DEREGISTER_CPUMASK = 4 fso-frameworkd-0.10.1/framework/cxnet/netlink/util.py000066400000000000000000000041101174525413000226260ustar00rootroot00000000000000""" Netlink utility functions """ # Copyright (c) 2008 ALT Linux, Peter V. Saveliev # # This file is part of Connexion project. # # Connexion 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 3 of the License, or # (at your option) any later version. # # Connexion 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 Connexion; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA from cxnet.netlink.rtnl import * from cxutil.ip import * from ctypes import * from socket import htonl from cxnet.common import hprint def get_route(addr=None,mask=0,table=254,debug=False): """ Get route by host address """ socket = rtnl_socket() parser = rtnl_msg_parser() result = [] end = False if addr and not mask: mask = 32 h = nlmsghdr() h.type = RTM_GETROUTE h.flags = NLM_F_REQUEST _p = rtmsg() _p.family = 2 _p.table = table _p.dst_len = mask _p.type = RTN_UNICAST p = rtnl_payload() p.route = _p msgx = rtnl_msg() msgx.hdr = h msgx.data = p if addr: ptr = addressof(msgx) + sizeof(nlmsghdr) + sizeof(rtmsg) a = t_attr() ptr = a.set(ptr,RTA_DST,c_uint32(htonl(dqn_to_int(addr)))) ptr = a.set(ptr,RTA_TABLE,c_uint32(table)) if debug: print("send:") hprint(msgx,ptr - addressof(msgx)) socket.send(msgx,ptr - addressof(msgx)) while not end: bias = 0 (l,msgx) = socket.recv() while l >= 0: x = rtnl_msg.from_address(addressof(msgx) + bias) if debug and (x.hdr.length > 0): print("receive:") hprint(x, x.hdr.length) bias += x.hdr.length l -= bias if (x.hdr.length == 0) or (x.hdr.type <= NLMSG_DONE): end = True break result.append(parser.parse(x)) socket.close() return result fso-frameworkd-0.10.1/framework/cxnet/tcp.py000066400000000000000000000044251174525413000210040ustar00rootroot00000000000000""" TCP protocol primitives """ # Copyright (c) 2008 Peter V. Saveliev # # This file is part of Connexion project. # # Connexion 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 3 of the License, or # (at your option) any later version. # # Connexion 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 Connexion; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA from ctypes import * from cxnet.generic import * from cxnet.common import csum, csum_words, csum_complement class tcphdr(BigEndianStructure): _fields_ = [ ("sport", c_uint16), ("dport", c_uint16), ("seq_num", c_uint32), # Sequence number ("ack_num", c_uint32), # Acknoledge number ("hdrlen", c_uint16, 4), ("reserved", c_uint16, 6), ("f_urg", c_uint16, 1), # TCP flags ("f_ack", c_uint16, 1), # ("f_psh", c_uint16, 1), # ("f_rst", c_uint16, 1), # ("f_syn", c_uint16, 1), # ("f_fin", c_uint16, 1), # ("window", c_uint16), ("chksum", c_uint16), ("urgptr", c_uint16), ] class tcp_f_hdr(BigEndianStructure): _fields_ = [ ("daddr", c_uint32), ("saddr", c_uint32), ("reserved", c_uint8), ("protocol", c_uint8), ("tot_len", c_uint16), ] def __init__(self): BigEndianStructure.__init__(self) self.protocol = 6 class tcp_f_comp(Structure): _fields_ = [ ("pseudo_hdr", tcp_f_hdr), ("real_hdr", tcphdr), ] class TCPProtocol(GenericProtocol): seq = None p_hdr = None inack = None def __init__(self,p_hdr,hdr): GenericProtocol.__init__(self,hdr) self.p_hdr = p_hdr self.inack = True def seq(self): self.hdr.seq_num += 1 def post(self,msg): self.p_hdr.tot_len = sizeof(msg) msg.hdr.hdrlen = sizeof(msg.hdr) / 4 msg.hdr.chksum = 0 #msg.hdr.chksum = csum(self.p_hdr,sizeof(self.p_hdr)) + csum(msg,sizeof(msg)) msg.hdr.chksum = csum_complement(csum_words(self.p_hdr,sizeof(self.p_hdr)) + csum_words(msg,sizeof(msg))) return msg fso-frameworkd-0.10.1/framework/cxnet/utils.py000066400000000000000000000047351174525413000213620ustar00rootroot00000000000000""" Misc utils for IPv4 management """ # Copyright (c) 2008 Peter V. Saveliev # # This file is part of Connexion project. # # Connexion 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 3 of the License, or # (at your option) any later version. # # Connexion 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 Connexion; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA __all__ = [ "dqn_to_bit", "bit_to_dqn", "dqn_to_int", "int_to_dqn", "mask_unknown", "get_mask", "ip_range", ] msk = [] for i in xrange(33): a = 0 for k in xrange(i): a = a >> 1 a |= 0x80000000 msk.append(a) def dqn_to_bit(st): """ Convert dotted quad notation to /xx mask """ return msk.index(int(dqn_to_int(st))) def bit_to_dqn(st): """ Convert /xx mask to dotted quad notation """ return int_to_dqn(msk[int(st)]) def dqn_to_int(st): """ Convert dotted quad notation to integer """ st = st.split(".") ### # That is not so elegant as 'for' cycle and # not extensible at all, but that works faster ### return int("%02x%02x%02x%02x" % (int(st[0]),int(st[1]),int(st[2]),int(st[3])),16) def int_to_dqn(st): """ Convert integer to dotted quad notation """ st = "%08x" % (st) ### # The same issue as for `dqn_to_int()` ### return "%i.%i.%i.%i" % (int(st[0:2],16),int(st[2:4],16),int(st[4:6],16),int(st[6:8],16)) def mask_unknown(st): """ Detect mask by zero bytes """ st = st.split(".") st.reverse() mask = 32 c = [32] for i in st: mask -= 8 if i == "0": c.append(mask) return c[-1] def get_mask(st): """ Return int mask for IP """ st = st.split("/") if len(st) > 1: mask = st[1] if mask.find(".") > 0: mask = dqn_to_int(mask) else: mask = msk[int(mask)] else: mask = msk[mask_unknown(st[0])] return mask def ip_range(st): """ Return IP list for a network """ mask = get_mask(st) st = st.split("/") ip = dqn_to_int(st[0]) ### # ### net = ip & mask start = 0 stop = msk[32] & ~mask result = [] for i in xrange(start,stop + 1): result.append(( hex(i), int_to_dqn(net | i), )) return result fso-frameworkd-0.10.1/framework/cxnet/zeroconf.py000066400000000000000000001410141174525413000220370ustar00rootroot00000000000000""" Multicast DNS Service Discovery for Python, v0.12 Copyright (C) 2003, Paul Scott-Murphy Copyright (c) 2008, Peter V. Saveliev This module provides a framework for the use of DNS Service Discovery using IP multicast. It has been tested against the JRendezvous implementation from StrangeBerry, and against the mDNSResponder from Mac OS X 10.3.8. # This file is part of Connexion project. # # Connexion 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 3 of the License, or # (at your option) any later version. # # Connexion 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 Connexion; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """ """0.12 update - allow selection of binding interface typo fix - Thanks A. M. Kuchlingi removed all use of word 'Rendezvous' - this is an API change""" """0.11 update - correction to comments for addListener method support for new record types seen from OS X - IPv6 address - hostinfo ignore unknown DNS record types fixes to name decoding works alongside other processes using port 5353 (e.g. on Mac OS X) tested against Mac OS X 10.3.2's mDNSResponder corrections to removal of list entries for service browser""" """0.10 update - Jonathon Paisley contributed these corrections: always multicast replies, even when query is unicast correct a pointer encoding problem can now write records in any order traceback shown on failure better TXT record parsing server is now separate from name can cancel a service browser modified some unit tests to accommodate these changes""" """0.09 update - remove all records on service unregistration fix DOS security problem with readName""" """0.08 update - changed licensing to LGPL""" """0.07 update - faster shutdown on engine pointer encoding of outgoing names ServiceBrowser now works new unit tests""" """0.06 update - small improvements with unit tests added defined exception types new style objects fixed hostname/interface problem fixed socket timeout problem fixed addServiceListener() typo bug using select() for socket reads tested on Debian unstable with Python 2.2.2""" """0.05 update - ensure case insensitivty on domain names support for unicast DNS queries""" """0.04 update - added some unit tests added __ne__ adjuncts where required ensure names end in '.local.' timeout on receiving socket for clean shutdown""" __author__ = "Paul Scott-Murphy" __email__ = "paul at scott dash murphy dot com" __version__ = "0.12" import string import time import struct import socket import threading import select import traceback import types __all__ = ["Zeroconf", "ServiceInfo", "ServiceBrowser"] # hook for threads globals()['_GLOBAL_DONE'] = 0 # Some timing constants _UNREGISTER_TIME = 125 _CHECK_TIME = 175 _REGISTER_TIME = 225 _LISTENER_TIME = 200 _BROWSER_TIME = 500 # Some DNS constants _MDNS_ADDR = '224.0.0.251' _MDNS_PORT = 5353; _DNS_PORT = 53; _DNS_TTL = 60 * 60; # one hour default TTL _MAX_MSG_TYPICAL = 1460 # unused _MAX_MSG_ABSOLUTE = 8972 _FLAGS_QR_MASK = 0x8000 # query response mask _FLAGS_QR_QUERY = 0x0000 # query _FLAGS_QR_RESPONSE = 0x8000 # response _FLAGS_AA = 0x0400 # Authorative answer _FLAGS_TC = 0x0200 # Truncated _FLAGS_RD = 0x0100 # Recursion desired _FLAGS_RA = 0x8000 # Recursion available _FLAGS_Z = 0x0040 # Zero _FLAGS_AD = 0x0020 # Authentic data _FLAGS_CD = 0x0010 # Checking disabled _CLASS_IN = 1 _CLASS_CS = 2 _CLASS_CH = 3 _CLASS_HS = 4 _CLASS_NONE = 254 _CLASS_ANY = 255 _CLASS_MASK = 0x7FFF _CLASS_UNIQUE = 0x8000 _TYPE_A = 1 _TYPE_NS = 2 _TYPE_MD = 3 _TYPE_MF = 4 _TYPE_CNAME = 5 _TYPE_SOA = 6 _TYPE_MB = 7 _TYPE_MG = 8 _TYPE_MR = 9 _TYPE_NULL = 10 _TYPE_WKS = 11 _TYPE_PTR = 12 _TYPE_HINFO = 13 _TYPE_MINFO = 14 _TYPE_MX = 15 _TYPE_TXT = 16 _TYPE_AAAA = 28 _TYPE_SRV = 33 _TYPE_ANY = 255 # Mapping constants to names _CLASSES = { _CLASS_IN : "in", _CLASS_CS : "cs", _CLASS_CH : "ch", _CLASS_HS : "hs", _CLASS_NONE : "none", _CLASS_ANY : "any" } _TYPES = { _TYPE_A : "a", _TYPE_NS : "ns", _TYPE_MD : "md", _TYPE_MF : "mf", _TYPE_CNAME : "cname", _TYPE_SOA : "soa", _TYPE_MB : "mb", _TYPE_MG : "mg", _TYPE_MR : "mr", _TYPE_NULL : "null", _TYPE_WKS : "wks", _TYPE_PTR : "ptr", _TYPE_HINFO : "hinfo", _TYPE_MINFO : "minfo", _TYPE_MX : "mx", _TYPE_TXT : "txt", _TYPE_AAAA : "quada", _TYPE_SRV : "srv", _TYPE_ANY : "any" } # utility functions def currentTimeMillis(): """Current system time in milliseconds""" return time.time() * 1000 # Exceptions class NonLocalNameException(Exception): pass class NonUniqueNameException(Exception): pass class NamePartTooLongException(Exception): pass class AbstractMethodException(Exception): pass class BadTypeInNameException(Exception): pass # implementation classes class DNSEntry(object): """A DNS entry""" def __init__(self, name, type, clazz): self.key = string.lower(name) self.name = name self.type = type self.clazz = clazz & _CLASS_MASK self.unique = (clazz & _CLASS_UNIQUE) != 0 def __eq__(self, other): """Equality test on name, type, and class""" if isinstance(other, DNSEntry): return self.name == other.name and self.type == other.type and self.clazz == other.clazz return 0 def __ne__(self, other): """Non-equality test""" return not self.__eq__(other) def getClazz(self, clazz): """Class accessor""" try: return _CLASSES[clazz] except: return "?(%s)" % (clazz) def getType(self, type): """Type accessor""" try: return _TYPES[type] except: return "?(%s)" % (type) def toString(self, hdr, other): """String representation with additional information""" result = "%s[%s,%s" % (hdr, self.getType(self.type), self.getClazz(self.clazz)) if self.unique: result += "-unique," else: result += "," result += self.name if other is not None: result += ",%s]" % (other) else: result += "]" return result class DNSQuestion(DNSEntry): """A DNS question entry""" def __init__(self, name, type, clazz): # FIXME: why? # if not name.endswith(".local."): # raise NonLocalNameException DNSEntry.__init__(self, name, type, clazz) def answeredBy(self, rec): """Returns true if the question is answered by the record""" return self.clazz == rec.clazz and (self.type == rec.type or self.type == _TYPE_ANY) and self.name == rec.name def __repr__(self): """String representation""" return DNSEntry.toString(self, "question", None) class DNSRecord(DNSEntry): """A DNS record - like a DNS entry, but has a TTL""" def __init__(self, name, type, clazz, ttl): DNSEntry.__init__(self, name, type, clazz) self.ttl = ttl self.created = currentTimeMillis() def __eq__(self, other): """Tests equality as per DNSRecord""" if isinstance(other, DNSRecord): return DNSEntry.__eq__(self, other) return 0 def suppressedBy(self, msg): """Returns true if any answer in a message can suffice for the information held in this record.""" for record in msg.answers: if self.suppressedByAnswer(record): return 1 return 0 def suppressedByAnswer(self, other): """Returns true if another record has same name, type and class, and if its TTL is at least half of this record's.""" if self == other and other.ttl > (self.ttl / 2): return 1 return 0 def getExpirationTime(self, percent): """Returns the time at which this record will have expired by a certain percentage.""" return self.created + (percent * self.ttl * 10) def getRemainingTTL(self, now): """Returns the remaining TTL in seconds.""" return max(0, (self.getExpirationTime(100) - now) / 1000) def isExpired(self, now): """Returns true if this record has expired.""" return self.getExpirationTime(100) <= now def isStale(self, now): """Returns true if this record is at least half way expired.""" return self.getExpirationTime(50) <= now def resetTTL(self, other): """Sets this record's TTL and created time to that of another record.""" self.created = other.created self.ttl = other.ttl def write(self, out): """Abstract method""" raise AbstractMethodException def toString(self, other): """String representation with addtional information""" arg = "%s/%s,%s" % (self.ttl, self.getRemainingTTL(currentTimeMillis()), other) return DNSEntry.toString(self, "record", arg) class DNSAddress(DNSRecord): """A DNS address record""" def __init__(self, name, type, clazz, ttl, address): DNSRecord.__init__(self, name, type, clazz, ttl) self.address = address def write(self, out): """Used in constructing an outgoing packet""" out.writeString(self.address, len(self.address)) def __eq__(self, other): """Tests equality on address""" if isinstance(other, DNSAddress): return self.address == other.address and self.name == other.name return 0 def __repr__(self): """String representation""" try: return socket.inet_ntoa(self.address) except: return self.address class DNSHinfo(DNSRecord): """A DNS host information record""" def __init__(self, name, type, clazz, ttl, cpu, os): DNSRecord.__init__(self, name, type, clazz, ttl) self.cpu = cpu self.os = os def write(self, out): """Used in constructing an outgoing packet""" out.writeString(self.cpu, len(self.cpu)) out.writeString(self.os, len(self.os)) def __eq__(self, other): """Tests equality on cpu and os""" if isinstance(other, DNSHinfo): return self.cpu == other.cpu and self.os == other.os return 0 def __repr__(self): """String representation""" return self.cpu + " " + self.os class DNSPointer(DNSRecord): """A DNS pointer record""" def __init__(self, name, type, clazz, ttl, alias): DNSRecord.__init__(self, name, type, clazz, ttl) self.alias = alias def write(self, out): """Used in constructing an outgoing packet""" out.writeName(self.alias) def __eq__(self, other): """Tests equality on alias""" if isinstance(other, DNSPointer): return self.alias == other.alias return 0 def __repr__(self): """String representation""" return self.toString(self.alias) class DNSText(DNSRecord): """A DNS text record""" def __init__(self, name, type, clazz, ttl, text): DNSRecord.__init__(self, name, type, clazz, ttl) self.text = text def write(self, out): """Used in constructing an outgoing packet""" out.writeString(self.text, len(self.text)) def __eq__(self, other): """Tests equality on text""" if isinstance(other, DNSText): return self.text == other.text return 0 def __repr__(self): """String representation""" if len(self.text) > 30: return self.toString(repr(self.text[:27] + "...")) else: return self.toString(repr(self.text)) class DNSService(DNSRecord): """A DNS service record""" def __init__(self, name, type, clazz, ttl, priority, weight, port, server): DNSRecord.__init__(self, name, type, clazz, ttl) self.priority = priority self.weight = weight self.port = port self.server = server def write(self, out): """Used in constructing an outgoing packet""" out.writeShort(self.priority) out.writeShort(self.weight) out.writeShort(self.port) out.writeName(self.server) def __eq__(self, other): """Tests equality on priority, weight, port and server""" if isinstance(other, DNSService): return self.priority == other.priority and self.weight == other.weight and self.port == other.port and self.server == other.server return 0 def __repr__(self): """String representation""" return self.toString("%s:%s" % (self.server, self.port)) class DNSIncoming(object): """Object representation of an incoming DNS packet""" def __init__(self, data): """Constructor from string holding bytes of packet""" self.offset = 0 self.data = data self.questions = [] self.answers = [] self.numQuestions = 0 self.numAnswers = 0 self.numAuthorities = 0 self.numAdditionals = 0 self.readHeader() self.readQuestions() self.readOthers() def readHeader(self): """Reads header portion of packet""" format = '!HHHHHH' length = struct.calcsize(format) info = struct.unpack(format, self.data[self.offset:self.offset+length]) self.offset += length self.id = info[0] self.flags = info[1] self.numQuestions = info[2] self.numAnswers = info[3] self.numAuthorities = info[4] self.numAdditionals = info[5] def readQuestions(self): """Reads questions section of packet""" format = '!HH' length = struct.calcsize(format) for i in range(0, self.numQuestions): name = self.readName() info = struct.unpack(format, self.data[self.offset:self.offset+length]) self.offset += length question = DNSQuestion(name, info[0], info[1]) self.questions.append(question) def readInt(self): """Reads an integer from the packet""" format = '!I' length = struct.calcsize(format) info = struct.unpack(format, self.data[self.offset:self.offset+length]) self.offset += length return info[0] def readCharacterString(self): """Reads a character string from the packet""" length = ord(self.data[self.offset]) self.offset += 1 return self.readString(length) def readString(self, len): """Reads a string of a given length from the packet""" format = '!' + str(len) + 's' length = struct.calcsize(format) info = struct.unpack(format, self.data[self.offset:self.offset+length]) self.offset += length return info[0] def readUnsignedShort(self): """Reads an unsigned short from the packet""" format = '!H' length = struct.calcsize(format) info = struct.unpack(format, self.data[self.offset:self.offset+length]) self.offset += length return info[0] def readOthers(self): """Reads the answers, authorities and additionals section of the packet""" format = '!HHiH' length = struct.calcsize(format) n = self.numAnswers + self.numAuthorities + self.numAdditionals for i in range(0, n): domain = self.readName() info = struct.unpack(format, self.data[self.offset:self.offset+length]) self.offset += length rec = None if info[0] == _TYPE_A: rec = DNSAddress(domain, info[0], info[1], info[2], self.readString(4)) elif info[0] == _TYPE_CNAME or info[0] == _TYPE_PTR: rec = DNSPointer(domain, info[0], info[1], info[2], self.readName()) elif info[0] == _TYPE_TXT: rec = DNSText(domain, info[0], info[1], info[2], self.readString(info[3])) elif info[0] == _TYPE_SRV: rec = DNSService(domain, info[0], info[1], info[2], self.readUnsignedShort(), self.readUnsignedShort(), self.readUnsignedShort(), self.readName()) elif info[0] == _TYPE_HINFO: rec = DNSHinfo(domain, info[0], info[1], info[2], self.readCharacterString(), self.readCharacterString()) elif info[0] == _TYPE_AAAA: rec = DNSAddress(domain, info[0], info[1], info[2], self.readString(16)) else: # Try to ignore types we don't know about # this may mean the rest of the name is # unable to be parsed, and may show errors # so this is left for debugging. New types # encountered need to be parsed properly. # #print "UNKNOWN TYPE = " + str(info[0]) #raise BadTypeInNameException pass if rec is not None: self.answers.append(rec) def isQuery(self): """Returns true if this is a query""" return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_QUERY def isResponse(self): """Returns true if this is a response""" return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_RESPONSE def readUTF(self, offset, len): """Reads a UTF-8 string of a given length from the packet""" result = self.data[offset:offset+len].decode('utf-8') return result def readName(self): """Reads a domain name from the packet""" result = '' off = self.offset next = -1 first = off while 1: len = ord(self.data[off]) off += 1 if len == 0: break t = len & 0xC0 if t == 0x00: result = ''.join((result, self.readUTF(off, len) + '.')) off += len elif t == 0xC0: if next < 0: next = off + 1 off = ((len & 0x3F) << 8) | ord(self.data[off]) if off >= first: raise Exception("Bad domain name (circular) at " + str(off)) first = off else: raise Exception("Bad domain name at " + str(off)) if next >= 0: self.offset = next else: self.offset = off return result class DNSOutgoing(object): """Object representation of an outgoing packet""" def __init__(self, flags, multicast = 1): self.finished = 0 self.id = 0 self.multicast = multicast self.flags = flags self.names = {} self.data = [] self.size = 12 self.questions = [] self.answers = [] self.authorities = [] self.additionals = [] def addQuestion(self, record): """Adds a question""" self.questions.append(record) def addAnswer(self, inp, record): """Adds an answer""" if not record.suppressedBy(inp): self.addAnswerAtTime(record, 0) def addAnswerAtTime(self, record, now): """Adds an answer if if does not expire by a certain time""" if record is not None: if now == 0 or not record.isExpired(now): self.answers.append((record, now)) def addAuthorativeAnswer(self, record): """Adds an authoritative answer""" self.authorities.append(record) def addAdditionalAnswer(self, record): """Adds an additional answer""" self.additionals.append(record) def writeByte(self, value): """Writes a single byte to the packet""" format = '!c' self.data.append(struct.pack(format, chr(value))) self.size += 1 def insertShort(self, index, value): """Inserts an unsigned short in a certain position in the packet""" format = '!H' self.data.insert(index, struct.pack(format, value)) self.size += 2 def writeShort(self, value): """Writes an unsigned short to the packet""" format = '!H' self.data.append(struct.pack(format, value)) self.size += 2 def writeInt(self, value): """Writes an unsigned integer to the packet""" format = '!I' self.data.append(struct.pack(format, int(value))) self.size += 4 def writeString(self, value, length): """Writes a string to the packet""" format = '!' + str(length) + 's' self.data.append(struct.pack(format, value)) self.size += length def writeUTF(self, s): """Writes a UTF-8 string of a given length to the packet""" utfstr = s.encode('utf-8') length = len(utfstr) if length > 64: raise NamePartTooLongException self.writeByte(length) self.writeString(utfstr, length) def writeName(self, name): """Writes a domain name to the packet""" try: # Find existing instance of this name in packet # index = self.names[name] except KeyError: # No record of this name already, so write it # out as normal, recording the location of the name # for future pointers to it. # self.names[name] = self.size parts = name.split('.') if parts[-1] == '': parts = parts[:-1] for part in parts: self.writeUTF(part) self.writeByte(0) return # An index was found, so write a pointer to it # self.writeByte((index >> 8) | 0xC0) self.writeByte(index) def writeQuestion(self, question): """Writes a question to the packet""" self.writeName(question.name) self.writeShort(question.type) self.writeShort(question.clazz) def writeRecord(self, record, now): """Writes a record (answer, authoritative answer, additional) to the packet""" self.writeName(record.name) self.writeShort(record.type) if record.unique and self.multicast: self.writeShort(record.clazz | _CLASS_UNIQUE) else: self.writeShort(record.clazz) if now == 0: self.writeInt(record.ttl) else: self.writeInt(record.getRemainingTTL(now)) index = len(self.data) # Adjust size for the short we will write before this record # self.size += 2 record.write(self) self.size -= 2 length = len(''.join(self.data[index:])) self.insertShort(index, length) # Here is the short we adjusted for def packet(self): """Returns a string containing the packet's bytes No further parts should be added to the packet once this is done.""" if not self.finished: self.finished = 1 for question in self.questions: self.writeQuestion(question) for answer, time in self.answers: self.writeRecord(answer, time) for authority in self.authorities: self.writeRecord(authority, 0) for additional in self.additionals: self.writeRecord(additional, 0) self.insertShort(0, len(self.additionals)) self.insertShort(0, len(self.authorities)) self.insertShort(0, len(self.answers)) self.insertShort(0, len(self.questions)) self.insertShort(0, self.flags) if self.multicast: self.insertShort(0, 0) else: self.insertShort(0, self.id) return ''.join(self.data) class DNSCache(object): """A cache of DNS entries""" def __init__(self): self.cache = {} def add(self, entry): """Adds an entry""" try: list = self.cache[entry.key] except: list = self.cache[entry.key] = [] list.append(entry) def remove(self, entry): """Removes an entry""" try: list = self.cache[entry.key] list.remove(entry) except: pass def get(self, entry): """Gets an entry by key. Will return None if there is no matching entry.""" try: list = self.cache[entry.key] return list[list.index(entry)] except: return None def getByDetails(self, name, type, clazz): """Gets an entry by details. Will return None if there is no matching entry.""" entry = DNSEntry(name, type, clazz) return self.get(entry) def entriesWithName(self, name): """Returns a list of entries whose key matches the name.""" try: return self.cache[name] except: return [] def entries(self): """Returns a list of all entries""" def add(x, y): return x+y try: return reduce(add, self.cache.values()) except: return [] class Engine(threading.Thread): """An engine wraps read access to sockets, allowing objects that need to receive data from sockets to be called back when the sockets are ready. A reader needs a handle_read() method, which is called when the socket it is interested in is ready for reading. Writers are not implemented here, because we only send short packets. """ def __init__(self, zeroconf): threading.Thread.__init__(self) self.zeroconf = zeroconf self.readers = {} # maps socket to reader self.timeout = 5 self.condition = threading.Condition() self.setName("zeroconf.Engine") self.setDaemon(True) self.start() def run(self): while not globals()['_GLOBAL_DONE']: rs = self.getReaders() if len(rs) == 0: # No sockets to manage, but we wait for the timeout # or addition of a socket # self.condition.acquire() self.condition.wait(self.timeout) self.condition.release() else: try: rr, wr, er = select.select(rs, [], [], self.timeout) for socket in rr: try: self.readers[socket].handle_read() except: traceback.print_exc() except: pass def getReaders(self): result = [] self.condition.acquire() result = self.readers.keys() self.condition.release() return result def addReader(self, reader, socket): self.condition.acquire() self.readers[socket] = reader self.condition.notify() self.condition.release() def delReader(self, socket): self.condition.acquire() del(self.readers[socket]) self.condition.notify() self.condition.release() def notify(self): self.condition.acquire() self.condition.notify() self.condition.release() class Listener(object): """A Listener is used by this module to listen on the multicast group to which DNS messages are sent, allowing the implementation to cache information as it arrives. It requires registration with an Engine object in order to have the read() method called when a socket is availble for reading.""" def __init__(self, zeroconf): self.zeroconf = zeroconf self.zeroconf.engine.addReader(self, self.zeroconf.socket) def handle_read(self): data, (addr, port) = self.zeroconf.socket.recvfrom(_MAX_MSG_ABSOLUTE) self.data = data msg = DNSIncoming(data) if msg.isQuery(): # Always multicast responses # if port == _MDNS_PORT: self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT, addr) # If it's not a multicast query, reply via unicast # and multicast # elif port == _DNS_PORT: self.zeroconf.handleQuery(msg, addr, port, addr) self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT, addr) else: self.zeroconf.handleResponse(msg, addr) class Reaper(threading.Thread): """A Reaper is used by this module to remove cache entries that have expired.""" def __init__(self, zeroconf): threading.Thread.__init__(self) self.zeroconf = zeroconf self.setName("zeroconf.Reaper") self.setDaemon(True) self.start() def run(self): while 1: self.zeroconf.wait(10 * 1000) if globals()['_GLOBAL_DONE']: return now = currentTimeMillis() for record in self.zeroconf.cache.entries(): if record.isExpired(now): for i in self.zeroconf.hooks: try: i.expire(record) except: pass self.zeroconf.updateRecord(now, record) self.zeroconf.cache.remove(record) class ServiceBrowser(threading.Thread): """Used to browse for a service of a specific type. The listener object will have its addService() and removeService() methods called when this browser discovers changes in the services availability.""" def __init__(self, zeroconf, type, listener): """Creates a browser for a specific type""" threading.Thread.__init__(self) self.zeroconf = zeroconf self.type = type self.listener = listener self.services = {} self.nextTime = currentTimeMillis() self.delay = _BROWSER_TIME self.list = [] self.done = 0 self.zeroconf.addListener(self, DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN)) self.setName("zeroconf.ServiceBrowser") self.setDaemon(True) self.start() def updateRecord(self, zeroconf, now, record): """Callback invoked by Zeroconf when new information arrives. Updates information required by browser in the Zeroconf cache.""" if record.type == _TYPE_PTR and record.name == self.type: expired = record.isExpired(now) try: oldrecord = self.services[record.alias.lower()] if not expired: oldrecord.resetTTL(record) else: del(self.services[record.alias.lower()]) callback = lambda x: self.listener.removeService(x, self.type, record.alias) self.list.append(callback) return except: if not expired: self.services[record.alias.lower()] = record callback = lambda x: self.listener.addService(x, self.type, record.alias) self.list.append(callback) expires = record.getExpirationTime(75) if expires < self.nextTime: self.nextTime = expires def cancel(self): self.done = 1 self.zeroconf.notifyAll() def run(self): while 1: event = None now = currentTimeMillis() if len(self.list) == 0 and self.nextTime > now: self.zeroconf.wait(self.nextTime - now) if globals()['_GLOBAL_DONE'] or self.done: return now = currentTimeMillis() if self.nextTime <= now: out = DNSOutgoing(_FLAGS_QR_QUERY) out.addQuestion(DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN)) for record in self.services.values(): if not record.isExpired(now): out.addAnswerAtTime(record, now) self.zeroconf.send(out) self.nextTime = now + self.delay self.delay = min(20 * 1000, self.delay * 2) if len(self.list) > 0: event = self.list.pop(0) if event is not None: try: event(self.zeroconf) except: pass class ServiceInfo(object): """Service information""" def __init__(self, type, name, address=None, port=None, weight=0, priority=0, properties={}, server=None, records=[_TYPE_A, _TYPE_SRV, _TYPE_TXT], ttl=_DNS_TTL): """Create a service description. domain: fully qualified service type name name: fully qualified service name address: IP address as unsigned short, network byte order port: port that the service runs on weight: weight of the service priority: priority of the service properties: dictionary of properties (or a string holding the bytes for the text field) server: fully qualified name for service host (defaults to name)""" if not name.endswith(type): raise BadTypeInNameException self.type = type self.name = name self.address = address self.port = port self.weight = weight self.priority = priority self.records = records self.ttl = ttl self.announced = 0 if server: self.server = server else: self.server = self.name self.setProperties(properties) def timeToGo(self,now): d = ( now - self.announced ) / 1000 if d * 2 >= self.ttl: self.announced = now return True return False def setProperty(self,key,value): """ Update only one property in the dict """ self.properties[key] = value self.syncProperties() def syncProperties(self): """ Set text from dict """ list = [] result = '' for key in self.properties.keys(): value = self.properties[key] if value is None: suffix = ''.encode('utf-8') elif isinstance(value, str): suffix = value.encode('utf-8') elif isinstance(value, int): if value: suffix = 'true' else: suffix = 'false' else: suffix = ''.encode('utf-8') list.append('='.join((key, suffix))) for item in list: result = ''.join((result, struct.pack('!c', chr(len(item))), item)) self.text = result def setProperties(self, properties): """Sets properties and text of this info from a dictionary""" if isinstance(properties, dict): self.properties = properties self.syncProperties() else: self.text = properties def setText(self, text): """Sets properties and text given a text field""" self.text = text try: result = {} end = len(text) index = 0 strs = [] while index < end: length = ord(text[index]) index += 1 strs.append(text[index:index+length]) index += length for s in strs: eindex = s.find('=') if eindex == -1: # No equals sign at all key = s value = 0 else: key = s[:eindex] value = s[eindex+1:] if value == 'true': value = 1 elif value == 'false' or not value: value = 0 # Only update non-existent properties if key and result.get(key) == None: result[key] = value self.properties = result except: traceback.print_exc() self.properties = None def getType(self): """Type accessor""" return self.type def getName(self): """Name accessor""" if self.type is not None and self.name.endswith("." + self.type): return self.name[:len(self.name) - len(self.type) - 1] return self.name def getAddress(self): """Address accessor""" return self.address def getPort(self): """Port accessor""" return self.port def getPriority(self): """Pirority accessor""" return self.priority def getWeight(self): """Weight accessor""" return self.weight def getProperties(self): """Properties accessor""" return self.properties def getText(self): """Text accessor""" return self.text def getServer(self): """Server accessor""" return self.server def updateRecord(self, zeroconf, now, record): """Updates service information from a DNS record""" if record is not None and not record.isExpired(now): if record.type == _TYPE_A: if record.name == self.name: self.address = record.address elif record.type == _TYPE_SRV: if record.name == self.name: self.server = record.server self.port = record.port self.weight = record.weight self.priority = record.priority self.address = None self.updateRecord(zeroconf, now, zeroconf.cache.getByDetails(self.server, _TYPE_A, _CLASS_IN)) elif record.type == _TYPE_TXT: if record.name == self.name: self.setText(record.text) def request(self, zeroconf, timeout): """Returns true if the service could be discovered on the network, and updates this object with details discovered. """ now = currentTimeMillis() delay = _LISTENER_TIME next = now + delay last = now + timeout result = 0 try: zeroconf.addListener(self, DNSQuestion(self.name, _TYPE_ANY, _CLASS_IN)) while self.server is None or self.address is None or self.text is None: if last <= now: return 0 if next <= now: out = DNSOutgoing(_FLAGS_QR_QUERY) out.addQuestion(DNSQuestion(self.name, _TYPE_SRV, _CLASS_IN)) out.addAnswerAtTime(zeroconf.cache.getByDetails(self.name, _TYPE_SRV, _CLASS_IN), now) out.addQuestion(DNSQuestion(self.name, _TYPE_TXT, _CLASS_IN)) out.addAnswerAtTime(zeroconf.cache.getByDetails(self.name, _TYPE_TXT, _CLASS_IN), now) if self.server is not None: out.addQuestion(DNSQuestion(self.server, _TYPE_A, _CLASS_IN)) out.addAnswerAtTime(zeroconf.cache.getByDetails(self.server, _TYPE_A, _CLASS_IN), now) zeroconf.send(out) next = now + delay delay = delay * 2 zeroconf.wait(min(next, last) - now) now = currentTimeMillis() result = 1 finally: zeroconf.removeListener(self) return result def __eq__(self, other): """Tests equality of service name""" if isinstance(other, ServiceInfo): return other.name == self.name return 0 def __ne__(self, other): """Non-equality test""" return not self.__eq__(other) def __repr__(self): """String representation""" result = "service[%s,%s:%s," % (self.name, socket.inet_ntoa(self.getAddress()), self.port) if self.text is None: result += "None" else: if len(self.text) < 20: result += self.text else: result += self.text[:17] + "..." result += "]" return result class Heartbeat(threading.Thread): """ Optional heartbeat thread """ def __init__(self, zeroconf): threading.Thread.__init__(self) self.zeroconf = zeroconf self.condition = threading.Condition() self.setName("zeroconf.Heartbeat") self.setDaemon(True) self.start() def wait(self, timeout): """Calling thread waits for a given number of milliseconds or until notified.""" self.condition.acquire() self.condition.wait(timeout/1000) self.condition.release() def notifyAll(self): """Notifies all waiting threads""" self.condition.acquire() self.condition.notifyAll() self.condition.release() def run(self): while 1: self.wait(1000) if globals()['_GLOBAL_DONE']: return now = currentTimeMillis() for (i,k) in self.zeroconf.services.items(): if k.timeToGo(now): self.zeroconf.announceService(k.name,iterations=1) class Announcer(object): """ Template class for ZeroConf hooks """ def add(self,record): pass def remove(self,record): pass def expire(self,record): pass def update(self,record): pass class Zeroconf(object): """Implementation of Zeroconf Multicast DNS Service Discovery Supports registration, unregistration, queries and browsing. """ # interfaces to bind to intf = None def __init__(self, bindaddress='', joinaddress=None, adaptive=False, heartbeat=False): """ Creates an instance of the Zeroconf class, establishing multicast communications, listening and reaping threads. bindaddress - address to bind() to (additional security besides of joinaddress) joinaddress - none, string or a tuple/list: on which interfaces to join the group adaptive - DNS hack. When receives address 0.0.0.0, substitute it with sender's IP heartbeat - run mDNS in the heartbeat mode """ globals()['_GLOBAL_DONE'] = 0 self.intf = [] self.adaptive = adaptive if type(joinaddress) is types.NoneType: if bindaddress: self.intf.append(bindaddress) else: self.intf.append(socket.gethostbyname(socket.gethostname())) elif type(joinaddress) is types.StringType: self.intf.append(joinaddress) elif type(joinaddress) in (types.TupleType, types.ListType): self.intf = joinaddress else: raise Exception("choose correct interfaces to join on") self.group = (bindaddress, _MDNS_PORT) self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) except: # SO_REUSEADDR should be equivalent to SO_REUSEPORT for # multicast UDP sockets (p 731, "TCP/IP Illustrated, # Volume 2"), but some BSD-derived systems require # SO_REUSEPORT to be specified explicity. Also, not all # versions of Python have SO_REUSEPORT available. So # if you're on a BSD-based system, and haven't upgraded # to Python 2.3 yet, you may find this library doesn't # work as expected. # pass self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_TTL, 255) self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1) try: self.socket.bind(self.group) except: # Some versions of linux raise an exception even though # the SO_REUSE* options have been set, so ignore it # pass for i in self.intf: self.addIntf(i) self.hooks = [] self.listeners = [] self.browsers = [] self.services = {} self.cache = DNSCache() self.condition = threading.Condition() self.engine = Engine(self) self.listener = Listener(self) self.reaper = Reaper(self) self.heartbeat = None if heartbeat: self.heartbeat = Heartbeat(self) def addIntf(self,addr): ''' Subscribe to multicast group on an interface ''' print addr self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_IF, socket.inet_aton(addr) + socket.inet_aton('0.0.0.0')) self.socket.setsockopt(socket.SOL_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton(addr)) def isLoopback(self): for i in self.intf: if i.startswith("127.0.0.1"): return True return False def isLinklocal(self): for i in self.intf: if i.startswith("169.254."): return True return False def wait(self, timeout): """Calling thread waits for a given number of milliseconds or until notified.""" self.condition.acquire() self.condition.wait(timeout/1000) self.condition.release() def notifyAll(self): """Notifies all waiting threads""" self.condition.acquire() self.condition.notifyAll() self.condition.release() def getServiceInfo(self, type, name, timeout=3000): """Returns network's service information for a particular name and type, or None if no service matches by the timeout, which defaults to 3 seconds.""" info = ServiceInfo(type, name) if info.request(self, timeout): return info return None def addServiceListener(self, type, listener): """Adds a listener for a particular service type. This object will then have its updateRecord method called when information arrives for that type.""" self.removeServiceListener(listener) self.browsers.append(ServiceBrowser(self, type, listener)) def removeServiceListener(self, listener): """Removes a listener from the set that is currently listening.""" for browser in self.browsers: if browser.listener == listener: browser.cancel() del(browser) def registerService(self, info): """Registers service information to the network with a default TTL of 60 seconds. Zeroconf will then respond to requests for information for that service. The name of the service may be changed if needed to make it unique on the network.""" self.checkService(info) self.services[info.name.lower()] = info self.announceService(info.name) def announceService(self, name, iterations=3): info = self.services[name.lower()] now = currentTimeMillis() nextTime = now while iterations > 0: if now < nextTime: self.wait(nextTime - now) now = currentTimeMillis() continue out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, info.ttl, info.name), 0) if _TYPE_SRV in info.records: out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, info.ttl, info.priority, info.weight, info.port, info.server), 0) if _TYPE_TXT in info.records: out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, info.ttl, info.text), 0) if info.address and _TYPE_A in info.records: out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, info.ttl, info.address), 0) self.send(out) iterations -= 1 nextTime += _REGISTER_TIME def unregisterService(self, info): """Unregister a service.""" try: del(self.services[info.name.lower()]) except: pass now = currentTimeMillis() nextTime = now i = 0 while i < 3: if now < nextTime: self.wait(nextTime - now) now = currentTimeMillis() continue out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0) out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, 0, info.priority, info.weight, info.port, info.name), 0) out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0) if info.address: out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, 0, info.address), 0) self.send(out) i += 1 nextTime += _UNREGISTER_TIME def unregisterAllServices(self): """Unregister all registered services.""" if len(self.services) > 0: now = currentTimeMillis() nextTime = now i = 0 while i < 3: if now < nextTime: self.wait(nextTime - now) now = currentTimeMillis() continue out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) for info in self.services.values(): out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0) out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, 0, info.priority, info.weight, info.port, info.server), 0) out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0) if info.address: out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, 0, info.address), 0) self.send(out) i += 1 nextTime += _UNREGISTER_TIME def checkService(self, info): """Checks the network for a unique service name, modifying the ServiceInfo passed in if it is not unique.""" now = currentTimeMillis() nextTime = now i = 0 while i < 3: for record in self.cache.entriesWithName(info.type): if record.type == _TYPE_PTR and not record.isExpired(now) and record.alias == info.name: if (info.name.find('.') < 0): info.name = info.name + ".[" + info.address + ":" + info.port + "]." + info.type self.checkService(info) return raise NonUniqueNameException if now < nextTime: self.wait(nextTime - now) now = currentTimeMillis() continue out = DNSOutgoing(_FLAGS_QR_QUERY | _FLAGS_AA) self.debug = out out.addQuestion(DNSQuestion(info.type, _TYPE_PTR, _CLASS_IN)) out.addAuthorativeAnswer(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, info.ttl, info.name)) self.send(out) i += 1 nextTime += _CHECK_TIME def addCacheHook(self, hook): if not hook in self.hooks: self.hooks.append(hook) def addListener(self, listener, question): """Adds a listener for a given question. The listener will have its updateRecord method called when information is available to answer the question.""" now = currentTimeMillis() self.listeners.append(listener) if question is not None: for record in self.cache.entriesWithName(question.name): if question.answeredBy(record) and not record.isExpired(now): listener.updateRecord(self, now, record) self.notifyAll() def removeListener(self, listener): """Removes a listener.""" try: self.listeners.remove(listener) self.notifyAll() except: pass def updateRecord(self, now, rec): """Used to notify listeners of new information that has updated a record.""" for listener in self.listeners: listener.updateRecord(self, now, rec) self.notifyAll() def handleResponse(self, msg, address): """Deal with incoming response packets. All answers are held in the cache, and listeners are notified.""" now = currentTimeMillis() for record in msg.answers: if self.adaptive and record.type == _TYPE_A: if record.address == '\x00\x00\x00\x00': record.address = socket.inet_aton(address) expired = record.isExpired(now) if record in self.cache.entries(): if expired: for i in self.hooks: try: i.remove(record) except: pass self.cache.remove(record) else: entry = self.cache.get(record) if entry is not None: for i in self.hooks: try: i.update(record) except: pass entry.resetTTL(record) record = entry else: for i in self.hooks: try: i.add(record) except: pass self.cache.add(record) self.updateRecord(now, record) def handleQuery(self, msg, addr, port, orig): """ Deal with incoming query packets. Provides a response if possible. msg - message to process addr - dst addr port - dst port orig - originating address (for adaptive records) """ out = None # Support unicast client responses # if port != _MDNS_PORT: out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA, 0) for question in msg.questions: out.addQuestion(question) for question in msg.questions: if question.type == _TYPE_PTR: for service in self.services.values(): if question.name == service.type: if out is None: out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) out.addAnswer(msg, DNSPointer(service.type, _TYPE_PTR, _CLASS_IN, service.ttl, service.name)) else: try: if out is None: out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) service = self.services.get(question.name.lower(), None) try: rs = service.records except: rs = [] # Answer A record queries for any service addresses we know if (question.type == _TYPE_A or question.type == _TYPE_ANY) and (_TYPE_A in rs): for service in self.services.values(): if service.server == question.name.lower(): out.addAnswer(msg, DNSAddress(question.name, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, service.ttl, service.address)) if not service: continue if (question.type == _TYPE_SRV or question.type == _TYPE_ANY) and (_TYPE_SRV in rs): out.addAnswer(msg, DNSService(question.name, _TYPE_SRV, _CLASS_IN | _CLASS_UNIQUE, service.ttl, service.priority, service.weight, service.port, service.server)) if (question.type == _TYPE_TXT or question.type == _TYPE_ANY) and (_TYPE_TXT in rs): out.addAnswer(msg, DNSText(question.name, _TYPE_TXT, _CLASS_IN | _CLASS_UNIQUE, service.ttl, service.text)) if (question.type == _TYPE_SRV) and (_TYPE_SRV in rs): out.addAdditionalAnswer(DNSAddress(service.server, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, service.ttl, service.address)) except: traceback.print_exc() if out is not None and out.answers: out.id = msg.id self.send(out, addr, port) def send(self, out, addr = _MDNS_ADDR, port = _MDNS_PORT): """Sends an outgoing packet.""" # This is a quick test to see if we can parse the packets we generate #temp = DNSIncoming(out.packet()) try: bytes_sent = self.socket.sendto(out.packet(), 0, (addr, port)) except: # Ignore this, it may be a temporary loss of network connection pass def close(self): """Ends the background threads, and prevent this instance from servicing further queries.""" if globals()['_GLOBAL_DONE'] == 0: globals()['_GLOBAL_DONE'] = 1 self.notifyAll() self.engine.notify() self.unregisterAllServices() try: # there are cases, when we start mDNS without network self.socket.setsockopt(socket.SOL_IP, socket.IP_DROP_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0')) except: pass self.socket.close() # Test a few module features, including service registration, service # query (for Zoe), and service unregistration. if __name__ == '__main__': print "Multicast DNS Service Discovery for Python, version", __version__ r = Zeroconf("127.0.0.1") print "1. Testing registration of a service..." desc = {'version':'0.10','a':'test value', 'b':'another value'} info = ServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local.", socket.inet_aton("127.0.0.1"), 1234, 0, 0, desc) print " Registering service..." r.registerService(info) print " Registration done." print "2. Testing query of service information..." print " Getting ZOE service:", str(r.getServiceInfo("_http._tcp.local.", "ZOE._http._tcp.local.")) print " Query done." print "3. Testing query of own service..." print " Getting self:", str(r.getServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local.")) print " Query done." print "4. Testing unregister of service information..." r.unregisterService(info) print " Unregister done." r.close() fso-frameworkd-0.10.1/framework/cxutil/000077500000000000000000000000001174525413000200265ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/cxutil/UID.py000066400000000000000000000021621174525413000210220ustar00rootroot00000000000000 # Copyright (c) 2008 Peter V. Saveliev # # This file is part of Connexion project. # # Connexion 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 3 of the License, or # (at your option) any later version. # # Connexion 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 Connexion; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA try: from mx.UID import UID as _UID def UID(): return str(_UID()) except: try: from uuid import uuid4 def UID(): return str(uuid4()) except: try: from cxutil import Executor def UID(): return str(Executor("uuidgenx")) except: counter = 1l def UID(): global counter counter += 1 return str(counter) fso-frameworkd-0.10.1/framework/cxutil/__init__.py000066400000000000000000000000001174525413000221250ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/cxutil/cmask.py000066400000000000000000000021201174525413000214710ustar00rootroot00000000000000""" Commit call mask (see state/core.py) """ # Copyright (c) 2008 Peter V. Saveliev # # This file is part of Connexion project. # # Connexion 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 3 of the License, or # (at your option) any later version. # # Connexion 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 Connexion; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # ... empty mask Empty = 0 # ... force execution Force = 1 # ... pass execution Bypass = 2 # ... `in-call` commit Call = 4 # ... immediate call from state2.parse() Immediate = 8 # ... script up ScriptUp = 16 # ... script down ScriptDown = 32 # ... signal Signal = 64 fso-frameworkd-0.10.1/framework/cxutil/emask.py000066400000000000000000000015671174525413000215110ustar00rootroot00000000000000""" Service mask """ # Copyright (c) 2008 Peter V. Saveliev # # This file is part of Connexion project. # # Connexion 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 3 of the License, or # (at your option) any later version. # # Connexion 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 Connexion; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # ... empty mask Empty = 0 # ... background job, no output Background = 1 fso-frameworkd-0.10.1/framework/cxutil/exceptions.py000066400000000000000000000025351174525413000225660ustar00rootroot00000000000000# Copyright (c) 2008 Peter V. Saveliev # # This file is part of Connexion project. # # Connexion 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 3 of the License, or # (at your option) any later version. # # Connexion 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 Connexion; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA class Dump(Exception): ''' Emit a signal to dump environment ''' module = None def __init__(self,module=None): self.module = module def __str__(self): if self.module: return "debug dump for `%s`" % (self.module.fn) else: return "debug dump" class CommitRaise(Exception): pass class CallPass(CommitRaise): ''' Emit a signal to pass this commit call ''' pass class CallQueue(CommitRaise): ''' Emit a signal to queue this commit call ''' pass class BranchPass(CommitRaise): ''' Emit a signal to mark this branch as executed and pass commit ''' passfso-frameworkd-0.10.1/framework/cxutil/flags.py000066400000000000000000000030231174525413000214720ustar00rootroot00000000000000""" Module flags (see command.py) """ # Copyright (c) 2008 Peter V. Saveliev # # This file is part of Connexion project. # # Connexion 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 3 of the License, or # (at your option) any later version. # # Connexion 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 Connexion; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # ... can create non-leaf nodes in the tree Begin = 1 # ... takes addition system info Esoteric = 2 # ... runs immediately anyway Immediate = 4 # ... pass command execution Bypass = 8 # ... unique node Unique = 0x10 # ... hidden node Hidden = 0x20 # ... force subtree to be restarted Force = 0x40 # ... transparent node Transparent = 0x80 # ... internal node Internal = 0x100 # ... newborn flag Newborn = 0x200 # ... once ? Once = 0x400 # ... transparent node for locals upload LocalsTransparent = 0x800 # ... upoad variables Upload = 0x1000 # ... stop locals StopLocals = 0x2000 # ... satellite class, not for direct commands Satellite = 0x4000 # ... hold the node even if the parent want to run it immediately Hold = 0x8000fso-frameworkd-0.10.1/framework/cxutil/ip.py000066400000000000000000000045501174525413000210140ustar00rootroot00000000000000""" Misc utils for IPv4 management """ # Copyright (c) 2008 Peter V. Saveliev # # This file is part of Connexion project. # # Connexion 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 3 of the License, or # (at your option) any later version. # # Connexion 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 Connexion; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA msk = [] for i in xrange(33): a = 0 for k in xrange(i): a = a >> 1 a |= 0x80000000 msk.append(a) def dqn_to_bit(st): """ Convert dotted quad notation to /xx mask """ return msk.index(int(dqn_to_int(st))) def bit_to_dqn(st): """ Convert /xx mask to dotted quad notation """ return int_to_dqn(msk[int(st)]) def dqn_to_int(st): """ Convert dotted quad notation to integer """ st = st.split(".") ### # That is not so elegant as 'for' cycle and # not extensible at all, but that works faster ### return int("%02x%02x%02x%02x" % (int(st[0]),int(st[1]),int(st[2]),int(st[3])),16) def int_to_dqn(st): """ Convert integer to dotted quad notation """ st = "%08x" % (st) ### # The same issue as for `dqn_to_int()` ### return "%i.%i.%i.%i" % (int(st[0:2],16),int(st[2:4],16),int(st[4:6],16),int(st[6:8],16)) def mask_unknown(st): """ Detect mask by zero bytes """ st = st.split(".") st.reverse() mask = 32 c = [32] for i in st: mask -= 8 if i == "0": c.append(mask) return c[-1] def get_mask(st): """ Return int mask for IP """ st = st.split("/") if len(st) > 1: mask = st[1] if mask.find(".") > 0: mask = dqn_to_int(mask) else: mask = msk[int(mask)] else: mask = msk[mask_unknown(st[0])] return mask def ip_range(st): """ Return IP list for a network """ mask = get_mask(st) st = st.split("/") ip = dqn_to_int(st[0]) ### # ### net = ip & mask start = 0 stop = msk[32] & ~mask result = [] for i in xrange(start,stop + 1): result.append(( hex(i), int_to_dqn(net | i), )) return result fso-frameworkd-0.10.1/framework/cxutil/pmask.py000066400000000000000000000016671174525413000215250ustar00rootroot00000000000000""" Command parameters mask """ # Copyright (c) 2008 Peter V. Saveliev # # This file is part of Connexion project. # # Connexion 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 3 of the License, or # (at your option) any later version. # # Connexion 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 Connexion; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # ... empty mask Empty = 0 # ... optional Optional = 1 # ... function name Fname = 2 # ... make a list from variables List = 4 fso-frameworkd-0.10.1/framework/cxutil/utils.py000066400000000000000000000114271174525413000215450ustar00rootroot00000000000000# Copyright (c) 2008 Peter V. Saveliev # # This file is part of Connexion project. # # Connexion 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 3 of the License, or # (at your option) any later version. # # Connexion 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 Connexion; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA import types from os import WIFEXITED, WEXITSTATUS from popen2 import Popen3 import re def fetch(opts,key,default): ''' ''' if opts.has_key(key): return opts[key] else: return default def subtract(d1,d2): ''' Subtract d1 from d2 ''' for i in d1.keys(): if d2.has_key(i): if (type(d1[i]) == types.ListType) and (type(d2[i]) == types.ListType): for k in d1[i]: try: d2[i].remove(k) except: pass elif \ ((type(d1[i]) == types.DictType) or (type(d1[i]) == type(opts()))) and\ ((type(d2[i]) == types.DictType) or (type(d2[i]) == type(opts()))): subtract(d1[i],d2[i]) else: del d2[i] def merge(d1,d2): ''' Merge d1 into d2 ''' for i in d1.keys(): if not d2.has_key(i): d2[i] = d1[i] else: if (type(d1[i]) == types.ListType) and (type(d2[i]) == types.ListType): for k in d1[i]: if not k in d2[i]: d2[i].append(k) elif \ ((type(d1[i]) == types.DictType) or (type(d1[i]) == type(opts()))) and\ ((type(d2[i]) == types.DictType) or (type(d2[i]) == type(opts()))): merge(d1[i],d2[i]) def nsort(xI,yI): ''' Compare two string word by word, taking numbers in account ''' x = xI.split() y = yI.split() r = re.compile("^[0-9]+$") lx = len(x) ly = len(y) for i in xrange(lx): # type mask mask = 0 # check, if ly <= i if ly <= i: # yI > xI return 1 # check word types if r.match(x[i]): kx = int(x[i]) mask |= 2 else: kx = x[i] if r.match(y[i]): ky = int(y[i]) mask |= 1 else: ky = y[i] # string > int if mask == 1: # kx -- string # ky -- int # kx > ky return 1 if mask == 2: # kx -- int # ky -- string # kx < ky return -1 # both strings or ints if kx != ky: if kx > ky: return 1 if kx < ky: return -1 # ly > lx return -1 class opts(object): ''' Pseudo-dict object ''' __dct__ = None __hidden__ = [ "__init__", "__getitem__", "__setitem__", "__setattr__", "keys", "items", "has_key", "dump_recursive", "__str__", "__hidden__", "__dct__", ] def __init__(self,dct = {}): object.__setattr__(self,"__dct__",{}) merge(dct,self) def __getitem__(self,key): return self.__dct__[key] def __delitem__(self,key): if not key in self.__hidden__: del self.__dct__[key] object.__delattr__(self,key) def __delattr__(self,key): self.__delitem__(key) def __setitem__(self,key,value): self.__setattr__(key,value) def __setattr__(self,key,value): if type(value) == types.DictType: value = opts(value) if type(key) == types.StringType: if not key in self.__hidden__: object.__setattr__(self,key,value) self.__dct__[key] = value def keys(self): return self.__dct__.keys() def items(self): return self.__dct__.items() def has_key(self,key): return self.__dct__.has_key(key) def dump_recursive(self,prefix = ""): t = "" for (i,k) in self.items(): t += "%s%s: " % (prefix,i) if type(k) == type(self): t += "\n" t += k.dump_recursive(prefix + "\t") else: t += str(k) t += ";\n" return t def __str__(self): return "%s" % (self.__dct__) class Executor(object): ''' Shell/exec launcher Runs a command in the subshell or via fork'n'exec (see the class constructor). ''' data = None lines = None edata = None elines = None pid = None ret = None def __init__(self,command,fc=True): ''' Creates object _and_ runs a command command - a command to run fc - `fast call` - whether to run via fork'n'exec (True) or in the subshell (False) ''' if fc: command = command.split() inst = Popen3(command,True,-1) (o,i,e) = (inst.fromchild, inst.tochild, inst.childerr) self.pid = inst.pid self.elines = e.readlines() self.lines = o.readlines() ret = inst.wait() if WIFEXITED(ret): self.ret = WEXITSTATUS(ret) else: self.ret = 255 i.close() o.close() e.close() self.edata = "" self.data = "" for i in self.lines: self.data += i for i in self.elines: self.edata += i def __str__(self): return self.data.strip() fso-frameworkd-0.10.1/framework/frameworkd000077500000000000000000000066141174525413000206140ustar00rootroot00000000000000#!/usr/bin/env python """ The Open Device Daemon - Python Implementation (C) 2008-2010 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later """ __version__ = "1.3.1" import sys, os from optparse import OptionParser #----------------------------------------------------------------------------# class TheOptionParser( OptionParser ): #----------------------------------------------------------------------------# def __init__( self ): OptionParser.__init__( self ) self.set_defaults( overrides=[] ) self.add_option( "-o", "--override", dest = "overrides", help = "override configuration", metavar = "SECTION.KEY=VALUE", action = "append" ) self.add_option( "-s", "--subsystems", metavar = "system1,system2,system3,...", dest = "subsystems", default = "", help = "launch only the following subsystems (default=all)", action = "store", ) self.add_option( "-n", "--noframework", dest = "noframework", help = "do not launch the framework subsystem (use for multiple framework processes)", action = "store_true", ) self.add_option( "-d", "--daemonize", dest = "daemonize", help = "launch as daemon", action = "store_true", ) self.add_option( "-p", "--profile", metavar = "", dest = "profile", help = "launch in profile mode (needs python-profile)", action = "store", ) self.add_option( "-l", "--loophole", dest = "loophole", help = "create loophole listening on port 8822 (needs python-netserver)", action = "store_true", ) #----------------------------------------------------------------------------# if __name__ == "__main__": #----------------------------------------------------------------------------# options = TheOptionParser() options.parse_args( sys.argv ) if options.values.profile: try: import cProfile except ImportError: print "Can't import cProfile; python-profile not installed? Can't profile." sys.exit( -1 ) else: print "WARNING: profiling mode. profiling to %s" % options.values.profile try: from framework import controller except ImportError: sys.path.append( os.path.dirname( os.path.abspath( os.path.curdir ) ) ) from framework import controller c = controller.Controller( "%s/subsystems" % os.path.dirname( controller.__file__ ), options ) if options.values.loophole: try: from patterns import loophole except ImportError, e: print "Can't launch loophole: %s", e else: l = loophole.LoopHole( dict( controller=c ) ) print "LoopHole listening on port 8822" try: if options.values.profile: p = cProfile.Profile() p.run( "c.launch()" ) else: c.launch() except KeyboardInterrupt: print >>sys.stderr, "ctrl-c: exiting." c.shutdown() del c if options.values.profile: import lsprofcalltree k = lsprofcalltree.KCacheGrind(p) data = open( options.values.profile, "w" ) k.output( data ) data.close() fso-frameworkd-0.10.1/framework/helpers.py000066400000000000000000000046441174525413000205420ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ freesmartphone.org Framework Daemon (C) 2009 Jan 'Shoragan' Lübbe (C) 2009 Openmoko, Inc. GPLv2 or later Package: framework Module: helpers """ MODULE_NAME = "frameworkd.helpers" from framework.patterns import decorator import dbus, types import logging logger = logging.getLogger( MODULE_NAME ) def drop_dbus_result( *args ): if args: logger.warning( "unhandled dbus result: %s", args ) def log_dbus_error( desc ): def dbus_error( e, desc = desc ): if hasattr(e, "get_dbus_name") and hasattr(e, "get_dbus_message"): logger.error( "%s (%s %s: %s)" % ( desc, e.__class__.__name__, e.get_dbus_name(), e.get_dbus_message() ) ) else: logger.error( "%s (%s)" % ( desc, e.__class__.__name__ ) ) return dbus_error def dbus_to_python( v ): class ObjectPath( object ): def __init__( self, path ): self.path = str( path ) def __repr__( self ): return "op%s" % repr(self.path) if isinstance(v, dbus.Byte) \ or isinstance(v, dbus.Int64) \ or isinstance(v, dbus.UInt64) \ or isinstance(v, dbus.Int32) \ or isinstance(v, dbus.UInt32) \ or isinstance(v, dbus.Int16) \ or isinstance(v, dbus.UInt16) \ or type(v) == types.IntType: return int(v) elif isinstance(v, dbus.Double) or type(v) == types.FloatType: return float(v) elif isinstance(v, dbus.String) or type(v) == types.StringType: return str(v) elif isinstance(v, dbus.Dictionary) or type(v) == types.DictType: return dict( (dbus_to_python(k), dbus_to_python(v)) for k,v in v.iteritems() ) elif isinstance(v, dbus.Array) or type(v) == types.ListType: return [dbus_to_python(x) for x in v] elif isinstance(v, dbus.Struct) or type(v) == types.TupleType: return tuple(dbus_to_python(x) for x in v) elif isinstance(v, dbus.Boolean) or type(v) == types.BooleanType: return bool(v) elif isinstance(v, dbus.ObjectPath) or type(v) == ObjectPath: return ObjectPath(v) else: raise TypeError("can't convert type %s to python object" % type(v)) @decorator.decorator def exceptionlogger( f, *args, **kw ): """ This decorator is used to log exceptions thrown in event handlers """ try: return f( *args, **kw ) except: logger.exception( 'event handler failed:' ) raise fso-frameworkd-0.10.1/framework/introspection.py000066400000000000000000000151501174525413000217720ustar00rootroot00000000000000# Copyright (C) 2003, 2004, 2005, 2006 Red Hat Inc. # Copyright (C) 2003 David Zeuthen # Copyright (C) 2004 Rob Taylor # Copyright (C) 2005, 2006 Collabora Ltd. # Copyright (C) 2007 John (J5) Palmieri # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation # files (the "Software"), to deal in the Software without # restriction, including without limitation the rights to use, copy, # modify, merge, publish, distribute, sublicense, and/or sell copies # of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER from xml.parsers.expat import ExpatError, ParserCreate from dbus.exceptions import IntrospectionParserException class _Parser(object): __slots__ = ('map', 'in_iface', 'in_method', 'in_signal', 'in_property', 'property_access', 'in_sig', 'out_sig', 'node_level', 'in_signal') def __init__(self): self.map = {'child_nodes':[],'interfaces':{}} self.in_iface = '' self.in_method = '' self.in_signal = '' self.in_property = '' self.property_access = '' self.in_sig = [] self.out_sig = [] self.node_level = 0 def parse(self, data): parser = ParserCreate('UTF-8', ' ') parser.buffer_text = True parser.StartElementHandler = self.StartElementHandler parser.EndElementHandler = self.EndElementHandler parser.Parse(data) return self.map def StartElementHandler(self, name, attributes): if name == 'node': self.node_level += 1 if self.node_level == 2: self.map['child_nodes'].append(attributes['name']) elif not self.in_iface: if (not self.in_method and name == 'interface'): self.in_iface = attributes['name'] else: if (not self.in_method and name == 'method'): self.in_method = attributes['name'] elif (self.in_method and name == 'arg'): arg_type = attributes['type'] arg_name = attributes.get('name', None) if attributes.get('direction', 'in') == 'in': self.in_sig.append({'name': arg_name, 'type': arg_type}) if attributes.get('direction', 'out') == 'out': self.out_sig.append({'name': arg_name, 'type': arg_type}) elif (not self.in_signal and name == 'signal'): self.in_signal = attributes['name'] elif (self.in_signal and name == 'arg'): arg_type = attributes['type'] arg_name = attributes.get('name', None) if attributes.get('direction', 'in') == 'in': self.in_sig.append({'name': arg_name, 'type': arg_type}) elif (not self.in_property and name == 'property'): prop_type = attributes['type'] prop_name = attributes['name'] self.in_property = prop_name self.in_sig.append({'name': prop_name, 'type': prop_type}) self.property_access = attributes['access'] def EndElementHandler(self, name): if name == 'node': self.node_level -= 1 elif self.in_iface: if (not self.in_method and name == 'interface'): self.in_iface = '' elif (self.in_method and name == 'method'): if not self.map['interfaces'].has_key(self.in_iface): self.map['interfaces'][self.in_iface]={'methods':{}, 'signals':{}, 'properties':{}} if self.map['interfaces'][self.in_iface]['methods'].has_key(self.in_method): print "ERROR: Some clever service is trying to be cute and has the same method name in the same interface" else: self.map['interfaces'][self.in_iface]['methods'][self.in_method] = (self.in_sig, self.out_sig) self.in_method = '' self.in_sig = [] self.out_sig = [] elif (self.in_signal and name == 'signal'): if not self.map['interfaces'].has_key(self.in_iface): self.map['interfaces'][self.in_iface]={'methods':{}, 'signals':{}, 'properties':{}} if self.map['interfaces'][self.in_iface]['signals'].has_key(self.in_signal): print "ERROR: Some clever service is trying to be cute and has the same signal name in the same interface" else: self.map['interfaces'][self.in_iface]['signals'][self.in_signal] = (self.in_sig,) self.in_signal = '' self.in_sig = [] self.out_sig = [] elif (self.in_property and name == 'property'): if not self.map['interfaces'].has_key(self.in_iface): self.map['interfaces'][self.in_iface]={'methods':{}, 'signals':{}, 'properties':{}} if self.map['interfaces'][self.in_iface]['properties'].has_key(self.in_property): print "ERROR: Some clever service is trying to be cute and has the same property name in the same interface" else: self.map['interfaces'][self.in_iface]['properties'][self.in_property] = (self.in_sig, self.property_access) self.in_property = '' self.in_sig = [] self.out_sig = [] self.property_access = '' def process_introspection_data(data): """Return a structure mapping all of the elements from the introspect data to python types TODO: document this structure :Parameters: `data` : str The introspection XML. Must be an 8-bit string of UTF-8. """ try: return _Parser().parse(data) except Exception, e: raise IntrospectionParserException('%s: %s' % (e.__class__, e)) fso-frameworkd-0.10.1/framework/lsprofcalltree.py000066400000000000000000000067641174525413000221260ustar00rootroot00000000000000# lsprofcalltree.py: lsprof output which is readable by kcachegrind # David Allouche # Jp Calderone & Itamar Shtull-Trauring # Johan Dahlin import optparse import os import sys try: import cProfile except ImportError: raise SystemExit("This script requires cProfile from Python 2.5") def label(code): if isinstance(code, str): return ('~', 0, code) # built-in functions ('~' sorts at the end) else: return '%s %s:%d' % (code.co_name, code.co_filename, code.co_firstlineno) class KCacheGrind(object): def __init__(self, profiler): self.data = profiler.getstats() self.out_file = None def output(self, out_file): self.out_file = out_file print >> out_file, 'events: Ticks' self._print_summary() for entry in self.data: self._entry(entry) def _print_summary(self): max_cost = 0 for entry in self.data: totaltime = int(entry.totaltime * 1000) max_cost = max(max_cost, totaltime) print >> self.out_file, 'summary: %d' % (max_cost,) def _entry(self, entry): out_file = self.out_file code = entry.code #print >> out_file, 'ob=%s' % (code.co_filename,) if isinstance(code, str): print >> out_file, 'fi=~' else: print >> out_file, 'fi=%s' % (code.co_filename,) print >> out_file, 'fn=%s' % (label(code),) inlinetime = int(entry.inlinetime * 1000) if isinstance(code, str): print >> out_file, '0 ', inlinetime else: print >> out_file, '%d %d' % (code.co_firstlineno, inlinetime) # recursive calls are counted in entry.calls if entry.calls: calls = entry.calls else: calls = [] if isinstance(code, str): lineno = 0 else: lineno = code.co_firstlineno for subentry in calls: self._subentry(lineno, subentry) print >> out_file def _subentry(self, lineno, subentry): out_file = self.out_file code = subentry.code #print >> out_file, 'cob=%s' % (code.co_filename,) print >> out_file, 'cfn=%s' % (label(code),) if isinstance(code, str): print >> out_file, 'cfi=~' print >> out_file, 'calls=%d 0' % (subentry.callcount,) else: print >> out_file, 'cfi=%s' % (code.co_filename,) print >> out_file, 'calls=%d %d' % ( subentry.callcount, code.co_firstlineno) totaltime = int(subentry.totaltime * 1000) print >> out_file, '%d %d' % (lineno, totaltime) def main(args): usage = "%s [-o output_file_path] scriptfile [arg] ..." parser = optparse.OptionParser(usage=usage % sys.argv[0]) parser.allow_interspersed_args = False parser.add_option('-o', '--outfile', dest="outfile", help="Save stats to ", default=None) if not sys.argv[1:]: parser.print_usage() sys.exit(2) options, args = parser.parse_args() if not options.outfile: options.outfile = '%s.log' % os.path.basename(args[0]) sys.argv[:] = args prof = cProfile.Profile() try: try: prof = prof.run('execfile(%r)' % (sys.argv[0],)) except SystemExit: pass finally: kg = KCacheGrind(prof) kg.output(file(options.outfile, 'w')) if __name__ == '__main__': sys.exit(main(sys.argv)) fso-frameworkd-0.10.1/framework/objectquery.py000066400000000000000000000154341174525413000214330ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: UTF-8 -*- """ freesmartphone.org: Framework Support Object (C) 2008 Michael 'Mickey' Lauer (C) 2008 Jan 'Shoragan' Lübbe (C) 2008 Openmoko, Inc. GPLv2 or later """ __version__ = "0.6.1" try: from .__version__ import version except ImportError: version = "inline" from .introspection import process_introspection_data from .config import DBUS_INTERFACE_PREFIX from framework.patterns import tasklet, dbuscache import gobject import dbus, dbus.service import os, sys, logging, logging.handlers logger = logging # is this ok or do we need a formal logger for this module as well? loggingmap = { \ "DEBUG": logging.DEBUG, "INFO": logging.INFO, "WARNING": logging.WARNING, "ERROR": logging.ERROR, "CRITICAL": logging.CRITICAL, } formatter = logging.Formatter('%(asctime)s %(name)-12s: %(levelname)-8s %(message)s') #----------------------------------------------------------------------------# class InvalidLogger( dbus.DBusException ): #----------------------------------------------------------------------------# _dbus_error_name = "org.freesmartphone.Framework.InvalidLogger" #----------------------------------------------------------------------------# class InvalidLevel( dbus.DBusException ): #----------------------------------------------------------------------------# _dbus_error_name = "org.freesmartphone.Framework.InvalidLevel" #----------------------------------------------------------------------------# class InvalidTarget( dbus.DBusException ): #----------------------------------------------------------------------------# _dbus_error_name = "org.freesmartphone.Framework.InvalidTarget" #----------------------------------------------------------------------------# class Framework( dbus.service.Object ): #----------------------------------------------------------------------------# """A D-Bus Object implementing org.freesmartphone.Framework""" DBUS_INTERFACE_FRAMEWORK = DBUS_INTERFACE_PREFIX + ".Framework" InterfaceCache = {} def __init__( self, bus, controller ): self.interface = self.DBUS_INTERFACE_FRAMEWORK self.path = "/org/freesmartphone/Framework" self.bus = bus dbus.service.Object.__init__( self, bus, self.path ) self.controller = controller def _getInterfaceForObject( self, object, interface ): return dbuscache.dbusInterfaceForObjectWithInterface( "org.freesmartphone.frameworkd", object, interface ) def _shutdownFramework( self ): self.controller.shutdown() return False # # dbus methods # @dbus.service.method( DBUS_INTERFACE_FRAMEWORK, "", "as" ) def ListDebugLoggers( self ): """ List available debug loggers. """ return logging.root.manager.loggerDict.keys() @dbus.service.method( DBUS_INTERFACE_FRAMEWORK, "", "ss" ) def GetDebugDestination( self ): try: handler = logging.root.handlers[0] except IndexError: handler = logging.StreamHandler() if isinstance( handler, logging.StreamHandler ): return ( "stderr", "" ) elif isinstance( handler, logging.handlers.SysLogHandler ): return ( "syslog", "" ) elif isinstance( handler, logging.FileHandler ): return ( "file", handler.stream.name ) else: return ( "unknown", "" ) @dbus.service.method( DBUS_INTERFACE_FRAMEWORK, "ss", "" ) def SetDebugDestination( self, category, destination ): """ Set the debug destination of logger. """ if category == "stderr": handler = logging.StreamHandler() elif category == "syslog": handler = logging.handlers.SysLogHandler( address = "/dev/log" ) elif category == "file" and destination != "": handler = logging.FileHandler( destination ) else: raise InvalidHandler( "available handlers are: stderr, syslog, file" ) handler.setFormatter( formatter ) # yank existing handlers before adding new one for h in logging.root.handlers: logging.root.removeHandler( h ) logging.root.addHandler( handler ) @dbus.service.method( DBUS_INTERFACE_FRAMEWORK, "s", "s" ) def GetDebugLevel( self, logger ): """ Get the debug level of logger. """ try: logger = logging.root.manager.loggerDict[logger] except KeyError: raise InvalidLogger( "available loggers are: %s" % logging.root.manager.loggerDict.keys() ) else: return logging.getLevelName( logger.level ) @dbus.service.method( DBUS_INTERFACE_FRAMEWORK, "ss", "" ) def SetDebugLevel( self, logger, levelname ): """ Set the debug level of logger to levelname. """ try: level = loggingmap[levelname] except KeyError: raise InvalidLevel( "available levels are: %s" % loggingmap.keys() ) else: if logger != "*": try: logger = logging.root.manager.loggerDict[logger] except KeyError: raise InvalidLogger( "available loggers are: %s" % logging.root.manager.loggerDict.keys() ) else: logger.setLevel( level ) else: for logger in logging.root.manager.loggerDict.items(): logger.setLevel( level ) @dbus.service.method( DBUS_INTERFACE_FRAMEWORK, "", "as" ) def ListSubsystems( self ): return self.controller.subsystems().keys() @dbus.service.method( DBUS_INTERFACE_FRAMEWORK, "s", "as" ) def ListObjectsInSubsystem( self, subsystem ): raise dbus.DBusException( "not yet implemented" ) @dbus.service.method( DBUS_INTERFACE_FRAMEWORK, "", "" ) def Shutdown( self ): gobject.idle_add( self._shutdownFramework ) @dbus.service.method( DBUS_INTERFACE_FRAMEWORK, "", "s" ) def GetVersion( self ): return version #----------------------------------------------------------------------------# def factory( prefix, controller ): #----------------------------------------------------------------------------# return [ Framework( controller.bus, controller ) ] #----------------------------------------------------------------------------# if __name__ == "__main__": #----------------------------------------------------------------------------# import dbus bus = dbus.SystemBus() query = bus.get_object( "org.freesmartphone.frameworkd", "/org/freesmartphone/Framework" ) objects = query.ListObjectsByInterface( '*', dbus_interface="org.freesmartphone.Framework" ) phone = bus.get_object( "org.freesmartphone.ogsmd", "/org/freesmartphone/GSM/Device" ) fso-frameworkd-0.10.1/framework/patterns/000077500000000000000000000000001174525413000203565ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/patterns/__init__.py000066400000000000000000000000001174525413000224550ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/patterns/asyncworker.py000066400000000000000000000126301174525413000233010ustar00rootroot00000000000000#!/usr/bin/env python """ Asynchronous Worker This file is part of MPPL: Mickey's Python Pattern Library (C) 2008 Michael 'Mickey' Lauer GPLv2 or later """ __version__ = "1.0.0" __author__ = "Michael 'Mickey' Lauer " from Queue import Queue import gobject # FIXME use parent/child logger hierarchy for subsystems/modules if __debug__: class logger(): @staticmethod def debug( message ): print message else: import logging logger = logging.getLogger( "mppl.asyncworker" ) #============================================================================# class AsyncWorker( object ): #============================================================================# """ This class implements an asynchronous worker queue. You can insert an element into the queue. If there are any elements, glib idle processing will be started and the elements will be processed asynchronously. Note that you need a running mainloop. If the last element has been processed, the idle task will be removed until you add new elements. """ # # public API # def __init__( self ): """ Initialize """ self._queue = Queue() self._source = None logger.debug( "init" ) def __del__( self ): """ Cleanup """ if self._source is not None: gobject.source_remove( self._source ) def enqueue( self, *element ): """ Enqueue an element, start processing queue if necessary. """ restart = self._queue.empty() # should we wrap this in a mutex to play thread-safe? self._queue.put( element ) if restart: logger.debug( "no elements in queue: starting idle task." ) self._source = gobject.idle_add( self._processElement ) else: logger.debug( "queue already filled (%s). idle task should still be running (source=%d)..." % ( self._queue.queue, self._source ) ) def remove( self, *element ): """ Remove one element from the queue. """ self._queue.queue.remove( element ) if self._queue.empty() and ( self._source is not None ): gobject.source_remove( self._source ) def removeAll( self, *element ): while True: try: self.remove( *element ) except ValueError: break def onProcessElement( self, element ): """ Called, when there is an element ready to process. Override this to implement your element handling. The default implementation does nothing. """ pass # # private API # def _processElement( self ): """ Process an element. Start idle processing, if necessary. """ logger.debug( "_processElement()" ) if self._queue.empty(): logger.debug( "no more elements: stopping idle task." ) self._source = None return False # don't call me again next = self._queue.get() logger.debug( "got an element from the queue" ) try: self.onProcessElement( next ) except: logger.exception( 'exception while processing element %s:', next ) return True #============================================================================# class SynchronizedAsyncWorker( AsyncWorker ): #============================================================================# """ This class implements a synchronized asynchronous worker queue. """ # # public API # def trigger( self ): """ Call, when you are ready to process the next element. """ if not self._queue.empty(): self._source = gobject.idle_add( self._processElement ) # # private API # def _processElement( self ): """ Process an element. Stop idle processing. """ logger.debug( "_processElement()" ) if self._queue.empty(): logger.warning( "no more elements" ) self._source = None return False # don't call me again next = self._queue.get() logger.debug( "got an element from the queue" ) try: self.onProcessElement( next ) except: logger.exception( 'exception while processing element %s:', next ) self._source = None return False #============================================================================# if __name__ == "__main__": #============================================================================# class TestAsyncWorker( SynchronizedAsyncWorker ): def onProcessElement( self, element ): print ( "processing %s\n>>>" % repr(element) ) self.trigger() import logging logging.basicConfig( \ level=logging.DEBUG, format='%(asctime)s %(levelname)-8s %(message)s', datefmt='%d.%b.%Y %H:%M:%S', ) gobject.threads_init() import thread a = TestAsyncWorker() for i in xrange( 10 ): a.enqueue( i ) for i in xrange( 10 ): a.enqueue( "yo" ) a.removeAll( "yo" ) a.remove( 9 ) mainloop = gobject.MainLoop() thread.start_new_thread( mainloop.run, () ) import time time.sleep( 1 ) for i in xrange( 1000 ): a.enqueue( i ) del a import sys sys.exit( 0 ) fso-frameworkd-0.10.1/framework/patterns/daemon.py000066400000000000000000000103301174525413000221700ustar00rootroot00000000000000#!/usr/bin/env python # Python Daemonizing Code # based on a module published by Sander Marechal # http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/ import sys, os, time, atexit from signal import SIGTERM, signal class Daemon( object ): """ A generic daemon class. Usage: subclass the Daemon class and override the run() method """ def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): self.stdin = stdin self.stdout = stdout self.stderr = stderr self.pidfile = pidfile self.sigtermhandler = signal( SIGTERM, self.cbSIGTERM ) def daemonize(self): """ do the UNIX double-fork magic, see Stevens' "Advanced Programming in the UNIX Environment" for details (ISBN 0201563177) http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16 """ try: pid = os.fork() if pid > 0: # exit first parent sys.exit(0) except OSError, e: sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror)) sys.exit(1) # decouple from parent environment os.chdir("/") os.setsid() os.umask(0) # do second fork try: pid = os.fork() if pid > 0: # exit from second parent sys.exit(0) except OSError, e: sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror)) sys.exit(1) # redirect standard file descriptors sys.stdout.flush() sys.stderr.flush() si = file(self.stdin, 'r') so = file(self.stdout, 'a+') se = file(self.stderr, 'a+', 0) os.dup2(si.fileno(), sys.stdin.fileno()) os.dup2(so.fileno(), sys.stdout.fileno()) os.dup2(se.fileno(), sys.stderr.fileno()) # FIXME this does not check whether the PIDfile is writeable! # write pidfile atexit.register(self.delpid) pid = str(os.getpid()) file(self.pidfile,'w+').write("%s\n" % pid) def delpid(self): os.remove(self.pidfile) def start(self): """ Start the daemon """ # Check for a pidfile to see if the daemon already runs try: pf = file(self.pidfile,'r') pid = int(pf.read().strip()) pf.close() except IOError: pid = None if pid: message = "pidfile %s already exist. Daemon already running?\n" sys.stderr.write(message % self.pidfile) sys.exit(1) # Start the daemon self.daemonize() self.run() def stop(self): """ Stop the daemon """ # Get the pid from the pidfile try: pf = file(self.pidfile,'r') pid = int(pf.read().strip()) pf.close() except IOError: pid = None if not pid: message = "pidfile %s does not exist. Daemon not running?\n" sys.stderr.write(message % self.pidfile) return # not an error in a restart # Try killing the daemon process try: while 1: os.kill(pid, SIGTERM) time.sleep(0.1) except OSError, err: err = str(err) if err.find("No such process") > 0: if os.path.exists(self.pidfile): os.remove(self.pidfile) else: print str(err) sys.exit(1) def cbSIGTERM( self, *args, **kwargs ): """ Handle SIGTERM. """ # reinstantiate original SIGTERM handler signal( signal, self.sigtermhandler ) self.shutdown() self.stop() def restart(self): """ Restart the daemon """ self.stop() self.start() def shutdown( self ): """ Override this, when you subclass Daemon. It will be called before the process quits. """ def run(self): """ You should override this method when you subclass Daemon. It will be called after the process has been daemonized by start() or restart(). """ fso-frameworkd-0.10.1/framework/patterns/dbuscache.py000066400000000000000000000034711174525413000226560ustar00rootroot00000000000000#!/usr/bin/env python """ freesmartphone.org Framework Daemon (C) 2009 Michael 'Mickey' Lauer GPLv2 or later Package: framework Module: controller """ __version__ = "1.0.2" import dbus _bus = dbus.SystemBus() _objects = {} _ifaces = {} #----------------------------------------------------------------------------# def dbusInterfaceForObjectWithInterface( service, object, interface ): #----------------------------------------------------------------------------# """ Gather dbus.Interface proxy for given triple of service, object, interface Try to cache as much as possible. """ try: iface = _ifaces[ ( service, object, interface ) ] except KeyError: try: obj = _objects[ ( service, object ) ] except KeyError: # this call will always succeed, even if the questioned service is not online yet obj = _objects[ ( service, object ) ] = _bus.get_object( service, object, introspect=False, follow_name_owner_changes=True ) iface = _ifaces[ ( service, object, interface ) ] = dbus.Interface( obj, interface ) return iface dbus.InterfaceForObjectWithInterface = dbusInterfaceForObjectWithInterface #----------------------------------------------------------------------------# def dbusCallAsyncDontCare( method, *args ): #----------------------------------------------------------------------------# """ Call dbus method async., don't care about any errors or replies. """ method( *args, **dict( reply_handler=nop, error_handler=nop ) ) #----------------------------------------------------------------------------# def nop( *args, **kwargs ): #----------------------------------------------------------------------------# #print ( "dbusCallAsyncDontCare returned with ", args, kwargs ) pass fso-frameworkd-0.10.1/framework/patterns/decorator.py000066400000000000000000000147241174525413000227220ustar00rootroot00000000000000""" Decorator module, see http://www.phyast.pitt.edu/~micheles/python/documentation.html for the documentation and below for the licence. """ ## The basic trick is to generate the source code for the decorated function ## with the right signature and to evaluate it. ## Uncomment the statement 'print >> sys.stderr, func_src' in _decorator ## to understand what is going on. __all__ = ["decorator", "new_wrapper", "getinfo"] import inspect, sys try: set except NameError: from sets import Set as set def getinfo(func): """ Returns an info dictionary containing: - name (the name of the function : str) - argnames (the names of the arguments : list) - defaults (the values of the default arguments : tuple) - signature (the signature : str) - doc (the docstring : str) - module (the module name : str) - dict (the function __dict__ : str) >>> def f(self, x=1, y=2, *args, **kw): pass >>> info = getinfo(f) >>> info["name"] 'f' >>> info["argnames"] ['self', 'x', 'y', 'args', 'kw'] >>> info["defaults"] (1, 2) >>> info["signature"] 'self, x, y, *args, **kw' """ assert inspect.ismethod(func) or inspect.isfunction(func) regargs, varargs, varkwargs, defaults = inspect.getargspec(func) argnames = list(regargs) if varargs: argnames.append(varargs) if varkwargs: argnames.append(varkwargs) signature = inspect.formatargspec(regargs, varargs, varkwargs, defaults, formatvalue=lambda value: "")[1:-1] return dict(name=func.__name__, argnames=argnames, signature=signature, defaults = func.func_defaults, doc=func.__doc__, module=func.__module__, dict=func.__dict__, globals=func.func_globals, closure=func.func_closure) # akin to functools.update_wrapper def update_wrapper(wrapper, model, infodict=None): infodict = infodict or getinfo(model) try: wrapper.__name__ = infodict['name'] except: # Python version < 2.4 pass wrapper.__doc__ = infodict['doc'] wrapper.__module__ = infodict['module'] wrapper.__dict__.update(infodict['dict']) wrapper.func_defaults = infodict['defaults'] wrapper.undecorated = model return wrapper def new_wrapper(wrapper, model): """ An improvement over functools.update_wrapper. The wrapper is a generic callable object. It works by generating a copy of the wrapper with the right signature and by updating the copy, not the original. Moreovoer, 'model' can be a dictionary with keys 'name', 'doc', 'module', 'dict', 'defaults'. """ if isinstance(model, dict): infodict = model else: # assume model is a function infodict = getinfo(model) assert not '_wrapper_' in infodict["argnames"], ( '"_wrapper_" is a reserved argument name!') src = "lambda %(signature)s: _wrapper_(%(signature)s)" % infodict funcopy = eval(src, dict(_wrapper_=wrapper)) return update_wrapper(funcopy, model, infodict) # helper used in decorator_factory def __call__(self, func): return new_wrapper(lambda *a, **k : self.call(func, *a, **k), func) def decorator_factory(cls): """ Take a class with a ``.caller`` method and return a callable decorator object. It works by adding a suitable __call__ method to the class; it raises a TypeError if the class already has a nontrivial __call__ method. """ attrs = set(dir(cls)) if '__call__' in attrs: raise TypeError('You cannot decorate a class with a nontrivial ' '__call__ method') if 'call' not in attrs: raise TypeError('You cannot decorate a class without a ' '.call method') cls.__call__ = __call__ return cls def decorator(caller): """ General purpose decorator factory: takes a caller function as input and returns a decorator with the same attributes. A caller function is any function like this:: def caller(func, *args, **kw): # do something return func(*args, **kw) Here is an example of usage: >>> @decorator ... def chatty(f, *args, **kw): ... print "Calling %r" % f.__name__ ... return f(*args, **kw) >>> chatty.__name__ 'chatty' >>> @chatty ... def f(): pass ... >>> f() Calling 'f' decorator can also take in input a class with a .caller method; in this case it converts the class into a factory of callable decorator objects. See the documentation for an example. """ if inspect.isclass(caller): return decorator_factory(caller) def _decorator(func): # the real meat is here infodict = getinfo(func) argnames = infodict['argnames'] assert not ('_call_' in argnames or '_func_' in argnames), ( 'You cannot use _call_ or _func_ as argument names!') src = "lambda %(signature)s: _call_(_func_, %(signature)s)" % infodict # import sys; print >> sys.stderr, src # for debugging purposes dec_func = eval(src, dict(_func_=func, _call_=caller)) return update_wrapper(dec_func, func, infodict) return update_wrapper(_decorator, caller) if __name__ == "__main__": import doctest; doctest.testmod() ########################## LEGALESE ############################### ## Redistributions of source code must retain the above copyright ## notice, this list of conditions and the following disclaimer. ## Redistributions in bytecode form must reproduce the above copyright ## notice, this list of conditions and the following disclaimer in ## the documentation and/or other materials provided with the ## distribution. ## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ## HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, ## INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, ## BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS ## OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ## ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR ## TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE ## USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH ## DAMAGE. fso-frameworkd-0.10.1/framework/patterns/kobject.py000066400000000000000000000211761174525413000223600ustar00rootroot00000000000000#!/usr/bin/env python """ freesmartphone.org Framework Daemon (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: framework Module: services """ __version__ = "0.2.1" MODULE_NAME = "frameworkd.kobject" SYS_CLASS_NET = "/sys/class/net" BUFFER_SIZE = 2048 from cxnet.netlink.rtnl import rtnl_msg as RtNetlinkMessage from cxnet.netlink.rtnl import rtnl_msg_parser as RtNetlinkParser from cxnet.netlink.rtnl import RTNLGRP_LINK, RTNLGRP_IPV4_IFADDR, RTNLGRP_IPV4_ROUTE import gobject import os, time, sys, socket, ctypes try: socket.NETLINK_KOBJECT_UEVENT except AttributeError: socket.NETLINK_KOBJECT_UEVENT = 15 # not present in earlier versions import logging logger = logging.getLogger( MODULE_NAME ) #----------------------------------------------------------------------------# class KObjectDispatcher( object ): #----------------------------------------------------------------------------# """ An object dispatching kobject messages """ @classmethod def addMatch( klass, action, path, callback ): if klass._instance is None: klass._instance = KObjectDispatcher() klass._instance._addMatch( action, path, callback ) @classmethod def removeMatch( klass, action, path, callback ): if klass._instance is None: raise KeyError( "Unknown match" ) else: klass._instance._removeMatch( action, path, callback ) if not len( klass._matches ): self._instance = None _instance = None _matches = {} ACTIONS = "add change remove addaddress deladdress addlink dellink addroute delroute".split() def __init__( self ): self._socketU = None self._socketR = None self._watchU = None self._watchR = None # register with kobject system self._socketU = socket.socket( socket.AF_NETLINK, socket.SOCK_DGRAM, socket.NETLINK_KOBJECT_UEVENT ) self._socketR = socket.socket( socket.AF_NETLINK, socket.SOCK_DGRAM, socket.NETLINK_ROUTE ) # this only works as root if ( os.getgid() ): logger.error( "Can't bind to netlink as non-root" ) return try: self._socketU.bind( ( os.getpid(), 1 ) ) except socket.error, e: logger.error( "Could not bind to netlink, uevent notifications will not work." ) else: logger.info( "Successfully bound to netlink uevent." ) self._watchU = gobject.io_add_watch( self._socketU.fileno(), gobject.IO_IN, self._onActivityU ) try: self._socketR.bind( ( os.getpid(), RTNLGRP_LINK | RTNLGRP_IPV4_IFADDR | RTNLGRP_IPV4_ROUTE ) ) except socket.error, e: logger.error( "Could not bind to netlink, kobject notifications will not work." ) else: logger.info( "Successfully bound to netlink route." ) self._watchR = gobject.io_add_watch( self._socketR.fileno(), gobject.IO_IN, self._onActivityR ) # for rtnetlink assistance self._libc = ctypes.CDLL( "libc.so.6" ) self._parser = RtNetlinkParser() def __del__( self ): """ Deregister """ for w in ( self._watchU, self._watchR ): if w is not None: gobject.remove_source( w ) w = None for s in ( self._socketU, self._socketR ): if s is not None: s.shutdown( SHUT_RD ) s = None logger.info( "Unlinked from all netlink objects. No further notifications." ) def _addMatch( self, action, path, callback ): logger.debug( "_addMatch %s, %s, %s" % ( action, path, callback ) ) #print "action='%s', path='%s'" % ( action, path ) if action == '*': self._addMatch( "add", path, callback ) self._addMatch( "change", path, callback ) self._addMatch( "remove", path, callback ) elif action in self.__class__.ACTIONS: path = path.replace( '*', '' ) if path == '' or path.startswith( '/' ): match = "%s@%s" % ( action, path ) #print "adding match", match self._matches.setdefault( match, [] ).append( callback ) #print "all matches are", self._matches else: raise ValueError( "Path needs to start with / or be '*'" ) else: raise ValueError( "Action needs to be one of %s" % self.__class__.ACTIONS ) def _removeMatch( self, action, path, callback ): logger.debug( "_removeMatch %s, %s, %s" % ( action, path, callback ) ) if action == '*': self._removeMatch( "add", path, callback ) self._removeMatch( "remove", path, callback ) elif action in "add remove".split(): path = path.replace( '*', '' ) if path == '' or path.startswith( '/' ): match = "%s@%s" % ( action, path ) #print "removing match", match try: matches = self._matches[match] except KeyError: pass else: matches.remove( callback ) #print "all matches are", self._matches else: raise ValueError( "Path needs to start with / or be '*'" ) else: raise ValueError( "Action needs to be 'add' or 'remove'" ) def _onActivityU( self, source, condition ): """ Run through callbacks and call, if applicable """ data = os.read( source, BUFFER_SIZE ) logger.debug( "Received kobject notification: %s" % repr(data) ) parts = data.split( '\0' ) action, path = parts[0].split( '@' ) properties = {} if len( parts ) > 1: properties = dict( [ x.split('=') for x in parts if '=' in x ] ) #print "action='%s', path='%s', properties='%s'" % ( action, path, properties for match, rules in self._matches.iteritems(): #print "checking %s startswith %s" % ( parts[0], match ) if parts[0].startswith( match ): for rule in rules: rule( action, path, **properties ) return True def _onActivityR( self, source, condition ): """ Run through callbacks and call, if applicable """ msg = RtNetlinkMessage() l = self._libc.recvfrom( source, ctypes.byref( msg ), ctypes.sizeof( msg ), 0, 0, 0 ) result = self._parser.parse( msg ) logger.debug( "Received netlink notification: %s" % repr(result) ) try: action = "%s%s" % ( result["action"], result["type"] ) path = "" except KeyError: logger.warning( "Not enough information in netlink notification" ) else: properties = dict(result) del properties["action"] del properties["type"] for match, rules in self._matches.iteritems(): if match[:-1] == action: for rule in rules: rule( action, path, **properties ) #print result return True #----------------------------------------------------------------------------# if __name__ == "__main__": #----------------------------------------------------------------------------# logging.basicConfig() def change_class_callback( *args, **kwargs ): print "change @ class callback", args, kwargs def class_callback( *args, **kwargs ): print "class callback", args, kwargs def devices_callback( *args, **kwargs ): print "devices callback", args, kwargs def all_callback( *args, **kwargs ): print "* callback", args, kwargs def add_link_callback( *args, **kwargs ): print "add link callback", args, kwargs def del_link_callback( *args, **kwargs ): print "del link callback", args, kwargs def add_route_callback( *args, **kwargs ): print "add route callback", args, kwargs def del_route_callback( *args, **kwargs ): print "del route callback", args, kwargs KObjectDispatcher.addMatch( "change", "/class/", change_class_callback ) KObjectDispatcher.addMatch( "add", "/class/", class_callback ) KObjectDispatcher.addMatch( "add", "/devices/", devices_callback ) KObjectDispatcher.addMatch( "*", "*", all_callback ) KObjectDispatcher.addMatch( "addlink", "", add_link_callback ) KObjectDispatcher.addMatch( "dellink", "", del_link_callback ) KObjectDispatcher.addMatch( "addroute", "", add_route_callback ) KObjectDispatcher.addMatch( "delroute", "", del_route_callback ) mainloop = gobject.MainLoop() mainloop.run() fso-frameworkd-0.10.1/framework/patterns/loophole.py000066400000000000000000000071071174525413000225560ustar00rootroot00000000000000#!/usr/bin/env python """ freesmartphone.org Framework Daemon (C) 2008-2009 Michael 'Mickey' Lauer GPLv2 or later Package: framework.patterns Module: loophole """ __version__ = "0.2.0" import SocketServer import thread import code import sys import gobject gobject.threads_init() if __debug__: class logger(): @staticmethod def debug( message ): print message else: import logging logger = logging.getLogger( "mppl.loophole" ) #============================================================================# class Writer( object ): #============================================================================# def __init__( self, delegate ): self.delegate = delegate def write( self, data ): self.delegate( data ) #============================================================================# class NetworkInterpreterConsole( code.InteractiveConsole ): #============================================================================# def __init__( self, request, locals_, *args, **kwargs ): self.request = request self.exitflag = False code.InteractiveConsole.__init__( self, locals_, *args, **kwargs ) self.oldstdout = sys.stdout sys.stdout = Writer( self.write ) def raw_input( self, prompt ): self.request.send( prompt ) print >>self.oldstdout, "waiting for data..." data = self.request.recv( 1024 ) print >>self.oldstdout, "got data '%s'" % repr(data) if data == "\x04": raise EOFError # omit trailing line terminators if data.endswith( "\r\n" ): command = data[:-2] elif data.endswith( "\n" ): command = data[:-1] else: command = data return command def close( self ): self.exitflag = True sys.stdout = self.oldstdout del self.oldstdout def write( self, data ): if not self.exitflag: self.request.send( data ) #============================================================================# class InterpreterRequestHandler( SocketServer.BaseRequestHandler ): #============================================================================# """ Request Handler """ interpreters = {} def setup( self ): try: self.myinterpreter = self.interpreters[self.client_address] except KeyError: self.myinterpreter = self.interpreters[self.client_address] = NetworkInterpreterConsole( self.request, self.locals_ ) def handle( self ): self.myinterpreter.interact() def finish( self ): self.myinterpreter.close() self.request.send( "Bye %s\n" % str( self.client_address ) ) self.request.close() #============================================================================# class LoopHole( object ): #============================================================================# def __init__( self, locals_ = {} ): InterpreterRequestHandler.locals_ = locals_ self.server = SocketServer.ThreadingTCPServer( ( "", 8822 ), InterpreterRequestHandler ) thread.start_new_thread( self.run, () ) def run( self, *args, **kwargs ): self.server.serve_forever() #============================================================================# if __name__ == "__main__": #============================================================================# import time l = LoopHole() try: while True: time.sleep( 10 ) except KeyboardInterrupt: pass sys.exit( 0 )fso-frameworkd-0.10.1/framework/patterns/null.py000066400000000000000000000070511174525413000217050ustar00rootroot00000000000000#!/usr/bin/env python """null.py This is a sample implementation of the 'Null Object' design pattern. Roughly, the goal with Null objects is to provide an 'intelligent' replacement for the often used primitive data type None in Python or Null (or Null pointers) in other languages. These are used for many purposes including the important case where one member of some group of otherwise similar elements is special for whatever reason. Most often this results in conditional statements to distinguish between ordinary elements and the primitive Null value. Among the advantages of using Null objects are the following: - Superfluous conditional statements can be avoided by providing a first class object alternative for the primitive value None. - Code readability is improved. - Null objects can act as a placeholder for objects with behaviour that is not yet implemented. - Null objects can be replaced for any other class. - Null objects are very predictable at what they do. To cope with the disadvantage of creating large numbers of passive objects that do nothing but occupy memory space Null objects are often combined with the Singleton pattern. For more information use any internet search engine and look for combinations of these words: Null, object, design and pattern. Dinu C. Gherman, August 2001 """ class Null: """A class for implementing Null objects. This class ignores all parameters passed when constructing or calling instances and traps all attribute and method requests. Instances of it always (and reliably) do 'nothing'. The code might benefit from implementing some further special Python methods depending on the context in which its instances are used. Especially when comparing and coercing Null objects the respective methods' implementation will depend very much on the environment and, hence, these special methods are not provided here. """ # object constructing def __init__(self, *args, **kwargs): "Ignore parameters." return None # object calling def __call__(self, *args, **kwargs): "Ignore method calls." return self # attribute handling def __getattr__(self, mname): "Ignore attribute requests." return self def __setattr__(self, name, value): "Ignore attribute setting." return self def __delattr__(self, name): "Ignore deleting attributes." return self # misc. def __repr__(self): "Return a string representation." return "" def __str__(self): "Convert to a string and return it." return "Null" def __nonzero__(self): "Return whether the object is nonzero." return False def test(): "Perform some decent tests, or rather: demos." # constructing and calling n = Null() n = Null('value') n = Null('value', param='value') n() n('value') n('value', param='value') # attribute handling n.attr1 n.attr1.attr2 n.method1() n.method1().method2() n.method('value') n.method(param='value') n.method('value', param='value') n.attr1.method1() n.method1().attr1 n.attr1 = 'value' n.attr1.attr2 = 'value' del n.attr1 del n.attr1.attr2.attr3 # representation and conversion to a string assert repr(n) == '' assert str(n) == 'Null' assert(not n, "n should return False. i.e. I would expect same behavior as testing an empty list or string") if __name__ == '__main__': test() fso-frameworkd-0.10.1/framework/patterns/processguard.py000066400000000000000000000140531174525413000234340ustar00rootroot00000000000000#!/usr/bin/env python """ freesmartphone.org Framework Daemon (C) 2008-2010 Michael 'Mickey' Lauer (C) 2008-2009 Openmoko, Inc. GPLv2 or later Package: framework.patterns Module: processguard """ __version__ = "0.3.0" import gobject import os, signal, types MAX_READ = 4096 import logging logger = logging.getLogger( "mppl.processguard" ) #============================================================================# class ProcessGuard( object ): #============================================================================# # # private # def __init__( self, cmdline ): """ Init """ if type( cmdline ) == types.ListType: self._cmdline = cmdline else: self._cmdline = cmdline.split() self._childwatch = None self._stdoutwatch = None self._stderrwatch = None self.hadpid = None self._reset() logger.debug( "Created process guard for %s" % repr(self._cmdline) ) def _reset( self ): """ Reset """ self.pid = None self.stdin = None self.stdout = None self.stderr = None if self._childwatch is not None: gobject.source_remove( self._childwatch ) self._childwatch = None if self._stdoutwatch is not None: gobject.source_remove( self._stdoutwatch ) self._stdoutwatch = None if self._stderrwatch is not None: gobject.source_remove( self._stderrwatch ) self._stderrwatch = None def _execute( self, options ): """ Launch the monitored process """ if options is None: cmdline = self._cmdline else: cmdline = [self._cmdline[0]] + options.split() result = gobject.spawn_async( cmdline, envp="", working_directory=os.environ.get( "PWD", "/" ), flags=gobject.SPAWN_DO_NOT_REAP_CHILD, # needed for child watch user_data=None, standard_input=False, standard_output=True, standard_error=True ) if result[0] < 0: raise OSError( "foo" ) self.pid, self.stdin, self.stdout, self.stderr = result self._childwatch = gobject.child_watch_add( self.pid, self._exitFromChild, priority=100 ) self._stdoutwatch = gobject.io_add_watch( self.stdout, gobject.IO_IN, self._outputFromChild ) self._stderrwatch = gobject.io_add_watch( self.stderr, gobject.IO_IN, self._errorFromChild ) def _outputFromChild( self, source, condition ): """ Called on child output """ if condition != gobject.IO_IN: return False data = os.read( source, MAX_READ ) logger.debug( "%s got data from child: %s" % ( self, repr(data) ) ) if self._onOutput is not None: self._onOutput( data ) return True # mainloop: call me again def _errorFromChild( self, source, condition ): """ Called on child output (stderr) """ if condition != gobject.IO_IN: return False data = os.read( source, MAX_READ ) logger.debug( "%s got error from child: %s" % ( self, repr(data) ) ) if self._onError is not None: self._onError( data ) return True # mainloop: call me again def _exitFromChild( self, pid, condition, data=None ): """ Called after(!) child has exit """ exitcode = (condition >> 8) & 0xFF exitsignal = condition & 0xFF self.hadpid = pid self._reset() # self.pid now None if self._onExit is not None: self._onExit( pid, exitcode, exitsignal ) def __del__( self ): """ Cleanup """ self.shutdown() self._reset() # # API # def execute( self, options=None, onExit=None, onError=None, onOutput=None ): """ Launch the process Optionally override parameters and setup delegates. """ self._onExit = onExit self._onOutput = onOutput self._onError = onError self._execute( options ) def shutdown( self, sig=signal.SIGTERM ): """ Shutdown the process. """ if self.pid is not None: logger.info( "shutdown: killing process %d with signal %d", self.pid, sig ) try: os.kill( self.pid, sig ) except OSError: logger.info( "shutdown: process already vanished" ) return try: os.waitpid( self.pid, os.WNOHANG ) except OSError: logger.info( "shutdown: waitpid failed" ) return # is GLib.process_close_pid bound? else: logger.info( "shutdown: process already vanished" ) def isRunning( self ): """ Returns True, when the process is running. False, otherwise. """ return self.pid is not None #============================================================================# if __name__ == "__main__": #============================================================================# def firstExit( pid, exitcode, exitsignal ): print "first exit" p.execute( "/tmp", onExit=secondExit ) def secondExit( pid, exitcode, exitsignal ): print "second exit" def thirdExit( pid, exitcode, exitsignal ): print "third exit" loop.quit() def killit(): print "killing..." p2.shutdown() return False loop = gobject.MainLoop() p = ProcessGuard( "/bin/ls ." ) p.execute( onExit=firstExit ) p2 = ProcessGuard( "/bin/sleep 10" ) p2.execute( onExit=thirdExit ) gobject.timeout_add_seconds( 3, killit ) try: loop.run() except KeyboardInterrupt: loop.quit() else: print "OK" fso-frameworkd-0.10.1/framework/patterns/tasklet.py000066400000000000000000000605011174525413000224010ustar00rootroot00000000000000# Tichy # copyright 2008 Guillaume Chereau (charlie@openmoko.org) # # This file is part of Tichy. # # Tichy 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 3 of the License, or # (at your option) any later version. # # Tichy 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 Tichy. If not, see . """ The tasklet module is a very powerfull tool that allow us to write functions that look like thread (with blocking call), but are in fact using callback. """ __docformat__ = "restructuredtext en" import sys, traceback from types import GeneratorType import gobject # Only used for the Sleep tasklet import dbus # Only used for the WaitDBusName tasklet import logging logger = logging.getLogger( "tasklet" ) # TODO: # - better stack printing in case of error def tasklet(func): """ A decorator that turns a generator function into a tasklet instance. """ def ret(*args, **kargs): return Tasklet( generator=func(*args, **kargs) ) ret.__dict__ = func.__dict__ ret.__name__ = func.__name__ ret.__doc__ = func.__doc__ return ret class Tasklet(object): """ This class can be used to write easy callback style functions using the 'yield' python expression. It is usefull in some cases where callback functions are the right thing to do, but make the code too messy This class is largely inspired by python PEP 0342: http://www.python.org/dev/peps/pep-0342/ See the examples below to understand how to use it. There is a very simple comunication mechanisme between tasklets : A tasklet can wait for an incoming message using `yield WaitMessage()`, an other tasklet can then send a message to this tasklet using the send_message method. See the example 8 to see how to use this. """ def __init__(self, *args, **kargs): if 'generator' in kargs: self.generator = kargs['generator'] else: self.generator = self.do_run(*args, **kargs) assert isinstance(self.generator, GeneratorType), type(self.generator) self.stack = traceback.extract_stack()[:-2] # The tasklet we are waiting for... self.waiting = None self.closed = False # The two lists used for messages passing between tasklets self.waiting_to_send_message = [] self.waiting_for_message = [] def __del__(self): if not self.closed and self.generator: logger.error( "Tasklet deleted without being executed\nTraceback to instantiation (most recent call last):\n%s", ''.join(traceback.format_list(self.stack)).rstrip() ) def do_run(self, *args, **kargs): return self.run(*args, **kargs) def run(self): """The default task run by the tasklet""" yield def start(self, callback = None, err_callback = None, *args, **kargs): """Start the tasklet, connected to a callback and an error callback :Parameters: - `callback`: a function that will be called with the returned value as argument - `err_callback`: a function that is called if the tasklet raises an exception. The function take 3 arguments as parameters, that are the standard python exception arguments. - `*args`: any argument that will be passed to the callback function as well - `**kargs`: any kargs argument that will be passed to the callback function as well """ self.callback = callback or self.default_callback self.err_callback = err_callback or self.default_err_callback self.args = args # possible additional args that will be passed to the callback self.kargs = kargs # possible additional keywords args that will be passed to the callback self.send(None) # And now we can initiate the task def start_from(self, tasklet): """Start the tasklet from an other tasklet""" self.start(tasklet.send, tasklet.throw) def start_dbus(self, on_ok, on_err, *args, **kargs): """Like start, except that the callback methods comply to the dbus async signature We should use this method instead of start when we want to connect to the callbacks defined in the dbus async_callbacks keyword. """ # If the returned value is None, then we don't pass it to the callback. def callback(value): if value is None: on_ok() else: on_ok(value) # DBus error callback take only one argument. def err_callback(type, e, trace): on_err(e) self.start(callback=callback, err_callback=err_callback, *args, **kargs) def default_callback(self, value): """The default callback if None is specified""" pass def default_err_callback(self, type, value, traceback): """The default error call back if None is specified""" if type is GeneratorExit: return # If a task generates a exception without having an error callback we kill the app. # It is not very nice, but the only way to avoid blocking. import traceback as tb import sys tb.print_exception(*sys.exc_info()) sys.exit(-1) def close(self): if self.closed: return self.callback = None self.err_callback = None if self.waiting: self.waiting.close() self.generator.close() self.closed = True def exit(self): # TODO: is this really useful, or should we use close here ? e = GeneratorExit() self.err_callback(*sys.exc_info()) def send(self, value = None, *args): """Resume and send a value into the tasklet generator """ # This somehow complicated try switch is used to handle all possible return and exception # from the generator function ### if self.generator == None: logger.error( "generator has vanished!" ) return ### assert self.closed == False, "Trying to send to a closed tasklet" try: value = self.generator.send(value) except StopIteration: # We don't propagate StopIteration value = None except Exception: self.err_callback(*sys.exc_info()) self.close() # This is very important, cause we need to make sure we free the memory of the callback ! return self.handle_yielded_value(value) def throw(self, type, value = None, traceback = None): """Throw an exeption into the tasklet generator""" try: value = self.generator.throw(type, value, traceback) except StopIteration: # We don't propagate StopIteration value = None except Exception: self.err_callback(*sys.exc_info()) self.close() # This is very important, cause we need to make sure we free the memory of the callback ! return self.handle_yielded_value(value) def handle_yielded_value(self, value): """This method is called after the waiting tasklet yielded a value We have to take care of two cases: - If the value is a Tasklet : we start it and connect the call back to the 'parent' Tasklet send and throw hooks - Otherwise, we consider that the tasklet finished, and we can call our callback function """ if isinstance(value, GeneratorType): value = Tasklet(generator = value) if isinstance(value, Tasklet): self.waiting = value value.start_from(self) else: assert self.callback, "%s has no callback !" % self self.callback(value, *self.args, **self.kargs) self.close() @tasklet def send_message(self, value = None): """Block until the tasklet accepts the incoming message""" if self.waiting_for_message: listener = self.waiting_for_message.pop(0) listener.trigger(value) else: sender = WaitTrigger() self.waiting_to_send_message.append((sender, value)) yield sender @tasklet def wait_message(self): """Block until the tasklet receive an incoming message Since we usually don't have access to the tasklet `self` argument (when using generators based tasklets) it is easier to use the WaitMessage class for this. """ if self.waiting_to_send_message: sender, value = self.waiting_to_send_message.pop(0) sender.trigger(value) yield value else: waiter = WaitTrigger() self.waiting_for_message.append(waiter) ret = yield waiter yield ret class WaitTrigger(Tasklet): """Special tasklet that will block until its `trigger` method is called This is mostly used by the send_message and WaitMessage tasklet. """ def start(self, callback = None, err_callback = None, *args, **kargs): self.callback = callback def trigger(self, v = None): if self.callback: self.callback(v) self.close() def close(self): self.callback = None class WaitMessage(Tasklet): """Special tasklet that will block until the caller tasklet receive a message.""" def start_from(self, tasklet): tasklet.wait_message().start(tasklet.send) def close(self): pass class Wait(Tasklet): """ A special tasklet that wait for an event to be emitted If o is an Object that can emit a signal 'signal', then we can create a tasklet that waits for this event like this : Wait(o, 'signal') """ def __init__(self, obj, event): assert obj is not None super(Wait, self).__init__() self.obj = obj self.event = event self.connect_id = None def _callback(self, o, *args): """This is the callback that is triggered by the signal""" assert o is self.obj if not self.connect_id: return # We have been closed already # We need to remember to disconnect to the signal o.disconnect(self.connect_id) self.connect_id = None # We can finally call our real callback try: self.callback(*args) except: self.err_callback(*sys.exc_info()) # We give a hint to the garbage collector self.obj = self.callback = None return False def start(self, callback, err_callback, *args): assert hasattr(self.obj, 'connect'), self.obj self.callback = callback self.err_callback = err_callback self.connect_id = self.obj.connect(self.event, self._callback, *args) def close(self): # It is very important to disconnect the callback here ! if self.connect_id: self.obj.disconnect(self.connect_id) self.obj = self.callback = self.connect_id = None class WaitFirst(Tasklet): """ A special tasklet that waits for the first to return of a list of tasklets. """ def __init__(self, *tasklets): super(WaitFirst, self).__init__() self.done = None self.tasklets = tasklets def _callback(self, *args): i = args[-1] values = args[:-1] if self.done: return self.done = True self.callback((i,values)) for t in self.tasklets: t.close() self.callback = None self.tasklets = None def start(self, callback = None, err_callback = None): self.callback = callback self.err_callback = Tasklet.default_err_callback # We connect all the tasklets for (i,t) in enumerate(self.tasklets): t.start(self._callback, err_callback, i) class WaitDBus(Tasklet): """Special tasket that wait for a DBus call""" def __init__(self, method, *args): super(WaitDBus, self).__init__() self.method = method self.args = args def start(self, callback, err_callback): self.callback = callback self.err_callback = err_callback kargs = {'reply_handler':self._callback, 'error_handler':self._err_callback} self.method(*self.args, **kargs) def _callback(self, *args): self.callback(*args) def _err_callback(self, e): self.err_callback(type(e), e, sys.exc_info()[2]) class WaitDBusSignal(Tasklet): """A special tasklet that wait for a DBUs event to be emited""" def __init__(self, obj, event, time_out = None): super(WaitDBusSignal, self).__init__() self.obj = obj self.event = event self.time_out = time_out self.connection = None self.timeout_connection = None def _callback(self, *args): if not self.connection: return # We have been closed already self.connection.remove() # don't forget to remove the timeout callback if self.timeout_connection: gobject.source_remove(self.timeout_connection) self.timeout_connection = None if len(args) == 1: # What is going on here is that if we have a single value, we return it directly, args = args[0] # but if we have several value we pack them in a tuple for the callback # because the callback only accpet a single argument try: self.callback(args) except: import sys self.err_callback(*sys.exc_info()) self.obj = self.callback = None return False def _err_callback(self): # can only be called on timeout self.timout_connection = None e = Exception("TimeOut") self.err_callback(type(e), e, sys.exc_info()[2]) def start(self, callback, err_callback): self.callback = callback self.err_callback = err_callback self.connection = self.obj.connect_to_signal(self.event, self._callback) if self.time_out: self.timeout_connection = gobject.timeout_add_seconds(self.time_out, self._err_callback) def close(self): # Note : it is not working very well !!!! Why ? I don't know... if self.connection: self.connection.remove() if self.timeout_connection: gobject.source_remove(self.timeout_connection) self.obj = self.callback = self.connection = self.timeout_connection = None class WaitDBusName(Tasklet): """Special tasklet that blocks until a given DBus name is available on the system bus""" def run(self, name): bus_obj = dbus.SystemBus().get_object('org.freedesktop.DBus', '/org/freedesktop/DBus') bus_obj_iface = dbus.proxies.Interface(bus_obj, 'org.freedesktop.DBus') all_bus_names = bus_obj_iface.ListNames() if name in all_bus_names: yield None while True: var = yield WaitDBusSignal( bus_obj_iface, 'NameOwnerChanged' ) if var[0] == name: yield None class WaitFunc(Tasklet): """A special tasklet that will wait for a function to call a callback. This is useful to reuse old style callback function. The function should take 2 parameters that are the callback to call """ def __init__(self, func): """Create the tasklet using a given function `func` should have this signature : func(on_ok, on_err) where : on_ok is a callback to call on return. on_err is a callback to call in case of an error, that take one single error argument. """ super(WaitFun, self).__init__() self.func = func def __callback(self, ret = None): self._callback(ret) def __err_callback(self, e): self._err_callback(type(e), e, sys.exc_info()[2]) def start(self, callback, err_callback): self._callback = callback self._err_callback = err_callback self.func(self.__callback, self.__err_callback) def close(self): pass class Producer(Tasklet): """ A Producer is a modified Tasklet that is not automatically closed after returing a value. This is still expermimental... """ def send(self, value = None, *args): """Resume and send a value into the tasklet generator """ # This somehow complicated try switch is used to handle all possible return and exception # from the generator function try: value = self.generator.send(value) except Exception: self.err_callback(*sys.exc_info()) self.close() # This is very important, cause we need to make sure we free the memory of the callback ! return self.handle_yielded_value(value) def throw(self, type, value, traceback): """Throw an exeption into the tasklet generator""" try: value = self.generator.throw(type, value, traceback) except Exception: self.err_callback(*sys.exc_info()) self.close() # This is very important, cause we need to make sure we free the memory of the callback ! return self.handle_yielded_value(value) def handle_yielded_value(self, value): """This method is called after the waiting tasklet yielded a value We have to take care of two cases: - If the value is a Tasklet : we start it and connect the call back to the 'parent' Tasklet send and throw hooks - Otherwise, we consider that the tasklet finished, and we can call our callback function """ if isinstance(value, GeneratorType): value = Tasklet(generator = value) if isinstance(value, Tasklet): self.waiting = value value.start_from(self) else: assert self.callback, "%s has no callback !" % self self.callback(value, *self.args, **self.kargs) class Sleep(Tasklet): """ This is a 'primitive' tasklet that will trigger our call back after a short time """ def __init__(self, time): """This tasklet has one parameter""" super(Sleep, self).__init__() self.time = time def start(self, callback, err_callback, *args): self.event_id = gobject.timeout_add_seconds(self.time, callback, None, *args) def close(self): # We cancel the event gobject.source_remove(self.event_id) class WaitFileReady(Tasklet): """This special Tasklet will block until a file descriptor is ready for reading or sending""" def __init__(self, fd, cond): super(WaitFileReady, self).__init__() self.fd = fd self.cond = cond self.event_id = None def _callback(self, *args): self.event_id = None self.callback(*args) return False def start(self, callback, err_callback, *args): self.callback = callback self.event_id = gobject.io_add_watch(self.fd, self.cond, self._callback, *args) def close(self): if self.event_id: gobject.source_remove(self.event_id) self.event_id = None if __name__ == '__main__': # And here is a simple example application using our tasklet class import gobject def example1(): print "== Simple example that waits two times for an input event ==" loop = gobject.MainLoop() @tasklet def task1(x): """An example Tasklet generator function""" print "task1 started with value %s" % x yield Sleep(1) print "tick" yield Sleep(1) print "task1 stopped" loop.quit() task1(10).start() print 'I do other things' loop.run() def example2(): print "== We can call a tasklet form an other tasklet ==" @tasklet def task1(): print "task1 started" value = yield task2(10) print "rask2 returned value %s" % value print "task1 stopped" @tasklet def task2(x): print "task2 started" print "task2 returns" yield 2 * x # Return value task1().start() def example3(): print "== We can pass exception through tasklets ==" @tasklet def task1(): try: yield task2() except TypeError: print "task2 raised a TypeError" yield task4() @tasklet def task2(): try: yield task3() except TypeError: print "task3 raised a TypeError" raise @tasklet def task3(): raise TypeError yield 10 @tasklet def task4(): print 'task4' yield 10 task1().start() def example4(): print "== We can cancel execution of a task before it ends ==" loop = gobject.MainLoop() @tasklet def task(): print "task started" yield Sleep(10) print "task stopped" loop.quit() task = task() task.start() # At this point, we decide to cancel the task task.close() print "task canceled" def example5(): print "== A task can choose to perform specific action if it is canceld ==" loop = gobject.MainLoop() @tasklet def task(): print "task started" try: yield Sleep(1) except GeneratorExit: print "Executed before the task is canceled" raise print "task stopped" loop.quit() task = task() task.start() # At this point, we decide to cancel the task task.close() print "task canceled" def example6(): print "== Using WaitFirst, we can wait for several tasks at the same time ==" loop = gobject.MainLoop() @tasklet def task1(x): print "Wait for the first task to return" value = yield WaitFirst(Sleep(2), Sleep(1)) print value loop.quit() task1(10).start() loop.run() def example7(): print "== Using Producer, we can create pipes ==" class MyProducer(Producer): def run(self): for i in range(3): yield Sleep(1) print "producing %d" % i yield i class MyConsumer(Tasklet): def run(self, input): print "start" try: while True: value = yield input print "get value %s" % value except StopIteration: print "Stop" loop.quit() loop = gobject.MainLoop() MyConsumer(MyProducer()).start() print "We can do other things in the meanwhile" loop.run() def example8(): print "== Using messages to comunicate between tasklets ==" loop = gobject.MainLoop() @tasklet def task1(): while True: msg = yield WaitMessage() if msg == 'end': break print "got message %s" % msg print "end task1" loop.quit() @tasklet def task2(task): for i in range(4): print "sending message %d" % i yield task.send_message(i) yield Sleep(1) yield task.send_message('end') print "end task2" task1 = task1() task1.start() task2(task1).start() loop.run() def test(): print "== Checking memory usage ==" def task1(): yield None import gc gc.collect() n = len(gc.get_objects()) for i in range(1000): t = Tasklet(generator=task1()) t.start() del t gc.collect() print len(gc.get_objects()) - n # test() # example1() # example2() # example3() # example4() # example6() # example7() example8() fso-frameworkd-0.10.1/framework/patterns/utilities.py000066400000000000000000000037771174525413000227610ustar00rootroot00000000000000#!/usr/bin/env python """ freesmartphone.org Framework Daemon (C) 2009 Michael 'Mickey' Lauer GPLv2 or later Package: framework.patterns Module: utilities """ import os, signal import logging logger = logging.getLogger( "mppl.utilities" ) #=========================================================================# def processIterator(): #=========================================================================# for entry in os.listdir( "/proc" ): fileName = os.path.join( "/proc", entry, "cmdline" ) if os.access( fileName, os.R_OK ): cmdline = file( fileName ).read() executablePath = cmdline.split("\x00")[0] executableName = executablePath.split(os.path.sep)[-1] #entry = pid, cmdline = cmdline file contents yield (entry, cmdline, executablePath, executableName) #=========================================================================# def processFinder(nameToFind, matchType): #=========================================================================# for entry, cmdline, executablePath, executableName in processIterator(): if matchType == "posix": if executableName == nameToFind: yield int( entry ) elif matchType == "weak": if executablePath.find( nameToFind ) != -1: yield int( entry ) elif matchType == "reallyweak": if cmdline.find( nameToFind ) != -1: yield int( entry ) #=========================================================================# def killall( nameToKill, matchType="posix", killSignal=signal.SIGTERM ): #=========================================================================# killedPids = [] for pid in processFinder( nameToKill, matchType ): try: os.kill( pid, killSignal ) except OSError, IOError: # permission denied/bad signal/process vanished/etc... pass else: killedPids.append( pid ) return killedPids fso-frameworkd-0.10.1/framework/persist.py000066400000000000000000000054021174525413000205620ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: UTF-8 -*- """ freesmartphone.org Framework Daemon (C) 2008 Michael 'Mickey' Lauer (C) 2008 Jan 'Shoragan' Lübbe (C) 2008 Openmoko, Inc. GPLv2 or later Module: persist """ from __future__ import with_statement __version__ = "1.0.1" MODULE_NAME = "frameworkd.persist" import os, atexit import logging logger = logging.getLogger( MODULE_NAME ) from framework.config import config, rootdir rootdir = os.path.join( rootdir, 'persist' ) format = config.getValue( "frameworkd", "persist_format", "pickle" ) if format == "pickle": import cPickle as pickle elif format == "yaml": from yaml import load, dump try: from yaml import CLoader as Loader from yaml import CDumper as Dumper except ImportError: from yaml import Loader, Dumper class Persist( object ): def __init__( self, rootdir ): self.rootdir = rootdir self.cache = {} self.dirty = set() atexit.register( self._atexit ) def _atexit( self ): for subsystem in list(self.dirty): logger.error( "dirty persist data for subsystem %s" % subsystem ) self.sync( subsystem ) def _load( self, subsystem ): if not subsystem in self.cache: try: filename = os.path.join( self.rootdir, subsystem+"."+format ) with file( filename, "r" ) as f: data = f.read() except: logger.info( "no persist data for subsystem %s" % subsystem ) data = "" if data == "": # empty file data = {} elif format == "pickle": data = pickle.loads( data ) elif format == "yaml": data = load( data, Loader=Loader ) self.cache[subsystem] = data def get( self, subsystem, key ): self._load( subsystem ) return self.cache[subsystem].get( key, None ) def set( self, subsystem, key, value ): self._load( subsystem ) if value is None: self.cache[subsystem].pop( key, None ) else: self.cache[subsystem][key] = value self.dirty.add( subsystem ) def sync( self, subsystem ): if subsystem in self.dirty: if format == "pickle": data = pickle.dumps( self.cache[subsystem], protocol = 2 ) elif format == "yaml": data = dump( self.cache[subsystem], Dumper=Dumper ) filename = os.path.join( self.rootdir, subsystem+"."+format ) with file( filename+".tmp", "w" ) as f: f.write( data ) os.rename( filename+".tmp", filename ) self.dirty.discard( subsystem ) persist = Persist( rootdir ) fso-frameworkd-0.10.1/framework/resource.py000066400000000000000000000273151174525413000207270ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ freesmartphone.org Framework Daemon (C) 2008 Guillaume 'Charlie' Chereau (C) 2008-2009 Michael 'Mickey' Lauer (C) 2008 Jan 'Shoragan' Lübbe (C) 2008 Openmoko, Inc. GPLv2 or later Package: framework Module: resource """ MODULE_NAME = "frameworkd.resource" __version__ = "0.5.3" from framework.config import config from framework.patterns import decorator, asyncworker, dbuscache from framework.helpers import exceptionlogger import gobject import dbus.service from dbus import validate_interface_name, Signature, validate_member_name import Queue import logging logger = logging.getLogger( MODULE_NAME ) #----------------------------------------------------------------------------# @decorator.decorator def checkedmethod(f, *args, **kw): """ This decorator wraps an asynchronous dbus method to checks the resource status and returning org.freesmartphone.Resource.ResourceNotEnabled if the resource is not enabled. """ #print "calling %s with args %s, %s" % (f.func_name, args, kw) self = args[0] dbus_error = args[-1] if self._resourceStatus == "enabled": return f(*args, **kw) else: dbus_error( ResourceNotEnabled( "Resource %s is not enabled, current status is '%s'" % ( self.__class__.__name__, self._resourceStatus ) ) ) #----------------------------------------------------------------------------# @decorator.decorator def checkedsyncmethod(f, *args, **kw): """ This decorator wraps a synchronous dbus method to checks the resource status and returning org.freesmartphone.Resource.ResourceNotEnabled if the resource is not enabled. """ #print "calling %s with args %s, %s" % (f.func_name, args, kw) self = args[0] if self._resourceStatus == "enabled": return f(*args, **kw) else: raise ResourceNotEnabled( "Resource %s is not enabled, current status is '%s'" % ( self.__class__.__name__, self._resourceStatus ) ) #----------------------------------------------------------------------------# @decorator.decorator def queuedsignal(f, *args, **kw): """ This decorator wraps a dbus signal and sends it only if the resource is enabled. Otherwise, it enqueues the signal. """ #print "calling %s with args %s, %s" % (f.func_name, args, kw) self = args[0] if self._resourceStatus == "enabled": return f(*args, **kw) else: self._delayedSignalQueue.put( ( f, args ) ) # push for later #----------------------------------------------------------------------------# @decorator.decorator def checkedsignal(f, *args, **kw): """ This decorator wraps a dbus signal and sends it only if the resource is enabled. Otherwise, it drops the signal. """ #print "calling %s with args %s, %s" % (f.func_name, args, kw) self = args[0] if self._resourceStatus == "enabled": return f(*args, **kw) else: logger.info( "Dropping signal %s, since resource %s is not enabled. Current status is '%s'" % ( f.__name__, self.__class__.__name__, self._resourceStatus ) ) #----------------------------------------------------------------------------# class ResourceNotEnabled( dbus.DBusException ): #----------------------------------------------------------------------------# _dbus_error_name = "org.freesmartphone.Resource.NotEnabled" #----------------------------------------------------------------------------# class ResourceError( dbus.DBusException ): #----------------------------------------------------------------------------# _dbus_error_name = "org.freesmartphone.Resource.Error" #----------------------------------------------------------------------------# class Resource( dbus.service.Object, asyncworker.SynchronizedAsyncWorker ): #----------------------------------------------------------------------------# """ Base class for all the resources The Resource class is used for anything that need to know about who is using a given resource. The OUsaged subsystem manage all the resources and keep track of how many clients are using them. When a resource is no longer used, its Disable method will be called by ousaged. The resource object can then do whatever is needed. When a resource is disabled and a client need to use it, ousaged will call its Enable method. A resource also needs to be able to prepare for a system suspend, or resume OUsaged will call the Suspend and Resume methods of the resource before a system suspend and after a system wakeup. To define a new resource, a subsystem needs to subclass this class, and call the register method once after initialisation. """ DBUS_INTERFACE = 'org.freesmartphone.Resource' sync_resources_with_lifecycle = config.getValue( "ousaged", "sync_resources_with_lifecycle", "always" ) commandInProgress = { \ "enable": "enabling", "disable": "disabling", "suspend": "suspending", "resume": "resuming", } commandDone = { \ "enable": "enabled", "disable": "disabled", "suspend": "suspended", "resume": "enabled", } def __init__( self, bus, name ): """ Register the object as a new resource in ousaged bus: dbus session bus name: the name of the resource that will be used by the clients """ # HACK HACK HACK: We do _not_ initialize the dbus service object here, # (in order to prevent initializing it twice), but rather rely on someone # else doing this for us. if not isinstance( self, dbus.service.Object ): raise RuntimeError( "Resource only allowed as mixin w/ dbus.service.Object" ) self._resourceBus = bus self._resourceName = name self._resourceStatus = "unknown" self._delayedSignalQueue = Queue.Queue() asyncworker.SynchronizedAsyncWorker.__init__( self ) # We need to call the ousaged.Register method, but we can't do it # immediatly for the ousaged object may not be present yet. # We use gobject.idle_add method to make the call only at the next # mainloop iteration def on_idle( self=self ): logger.debug( "Trying to register resource %s", self._resourceName ) try: usaged = self._resourceBus.get_object( "org.freesmartphone.ousaged", "/org/freesmartphone/Usage" ) usageiface = dbus.Interface( usaged, "org.freesmartphone.Usage" ) except dbus.exceptions.DBusException: logger.warning( "Can't register resource %s since ousaged is not present. Enabling device", name ) gobject.idle_add( self.Enable, lambda: False, lambda dummy: False ) else: def on_reply( self=self ): logger.debug( "%s is now a registered resource", self._resourceName ) def on_error( err, self=self ): logger.error( "%s can't be registered (%s). Enabling", self._resourceName, err ) gobject.idle_add( self.Enable, lambda: False, lambda dummy: False ) #usageiface = dbuscache.dbusInterfaceForObjectWithInterface( #"org.freesmartphone.ousaged", #"/org/freesmartphone/Usage", #"org.freesmartphone.Usage" ) #usageiface.RegisterResource( self._resourceName, self, reply_handler=on_reply, error_handler=on_error ) usageiface.RegisterResource( self._resourceName, self, reply_handler=on_reply, error_handler=on_error ) return False # mainloop: don't call me again gobject.idle_add( on_idle ) @exceptionlogger def onProcessElement( self, element ): command, ok_callback, err_callback = element logger.debug( "processing command '%s' for resource '%s' (present status=%s)", command, self, self._resourceStatus ) stateNow = self._resourceStatus if stateNow == "disabled" and command != "enable": ok_callback() else: def ok( self=self, command=command, dbus_ok=ok_callback ): self._updateResourceStatus( self.commandDone[command] ) logger.debug( "(ok) done processing command '%s' for resource '%s' (new status=%s); triggering next command", command, self, self._resourceStatus ) self.trigger() dbus_ok() def err( error, self=self, command=command, dbus_err=err_callback ): logger.debug( "(error) done processing command '%s' for resource '%s' (new status=%s); triggering next command", command, self, self._resourceStatus ) self.trigger() dbus_err( error ) self._updateResourceStatus( self.commandInProgress[command] ) if command == "enable": self._enable( ok, err ) elif command == "disable": self._disable( ok, err ) elif command == "suspend": self._suspend( ok, err ) elif command == "resume": self._resume( ok, err ) else: logger.warning( "unknown resource command '%s' ignored.", command ) self.trigger() def shutdown( self ): """ Called by the subsystem during system shutdown. """ if self.sync_resources_with_lifecycle in ( "always", "shutdown" ) and \ self._resourceStatus in ( "enabled" , "enabling", "unknown" ): # no error handling, either it works or not #self._disable( lambda: None, lambda Foo: None ) self.Disable( lambda: None, lambda Foo: None ) def _updateResourceStatus( self, nextStatus ): logger.info( "setting resource status for %s from %s to %s" % ( self._resourceName, self._resourceStatus, nextStatus ) ) self._resourceStatus = nextStatus # send all queued signals, if any if self._resourceStatus == "enabled": logger.debug( "resource now enabled. checking signal queue" ) while not self._delayedSignalQueue.empty(): f, args = self._delayedSignalQueue.get() logger.debug( "sending delayed signal %s( %s )", f, args ) f(*args) # The DBus methods update the resource status and call the python implementation @dbus.service.method( DBUS_INTERFACE, "", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) def Enable( self, dbus_ok, dbus_error ): self.enqueue( "enable", dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE, "", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) def Disable( self, dbus_ok, dbus_error ): self.enqueue( "disable", dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE, "", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) def Suspend( self, dbus_ok, dbus_error ): self.enqueue( "suspend", dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE, "", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) def Resume( self, dbus_ok, dbus_error ): self.enqueue( "resume", dbus_ok, dbus_error ) # Subclass of Service should reimplement these methods def _enable( self, on_ok, on_error ): logger.warning( "FIXME: Override Resource._enable for resource %s", self._resourceName ) on_ok() def _disable( self, on_ok, on_error ): logger.warning( "FIXME: Override Resource._disable for resource %s", self._resourceName ) on_ok() def _suspend( self, on_ok, on_error ): logger.warning( "FIXME: Override Resource._suspend for resource %s", self._resourceName ) on_ok() def _resume( self, on_ok, on_error ): logger.warning( "FIXME: Override Resource._resume for resource %s", self._resourceName ) on_ok() fso-frameworkd-0.10.1/framework/subsystem.py000066400000000000000000000164151174525413000211350ustar00rootroot00000000000000#!/usr/bin/env python """ freesmartphone.org Framework Daemon (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Module: subsystem """ MODULE_NAME = "frameworkd.subsystem" __version__ = "1.2.0" from .config import config, busmap, DBUS_BUS_NAME_PREFIX from patterns.processguard import ProcessGuard import dbus import os, sys, time import logging logger = logging.getLogger( MODULE_NAME ) #----------------------------------------------------------------------------# class Subsystem( object ): #----------------------------------------------------------------------------# """ Encapsulates a frameworkd subsystem exported via dbus. Every subsystem has its dedicated dbus bus connection to prevent all objects showing up on all bus names. """ def __init__( self, name, bus, path, scantype, controller ): logger.debug( "subsystem %s created" % name ) self.launchTime = time.time() self.name = name self.bus = dbus.bus.BusConnection( dbus.bus.BUS_SYSTEM ) busmap[name] = self.bus self.path = path self.scantype = scantype self.controller = controller self._objects = {} self.busnames = [] self.launch() self.launchTime = time.time() - self.launchTime logger.info( "subsystem %s took %.2f seconds to startup" % ( self.name, self.launchTime ) ) def launch( self ): """ Launch the subsystem. """ self.busnames.append( self.tryClaimBusName() ) self.registerModulesInSubsystem() # Clean out any busnames that couldn't be assigned self.busnames = [ busname for busname in self.busnames if busname != None ] if self.busnames == []: logger.warning( "service %s doesn't have any busnames registered" % self.name ) else: logger.debug( "service %s now owning busnames %s" % (self.name, self.busnames) ) def shutdown( self ): """ Shutdown the subsystems, giving objects a chance to clean up behind them. """ for o in self._objects.values(): try: o.shutdown() except AttributeError: # objects do not have to support this method pass def objects( self ): return self._objects def findModulesInSubsystem( self ): """ Find modules belonging to this subsystem. Depening on the scantype this is either based on the available config settings or 'auto', in which case the whole subsystem's directory is scanned (slow!) """ if self.scantype == "auto": modules = os.listdir( "%s/%s" % ( self.path, self.name ) ) else: modules = [ section for section in config.sections() \ if '.' in section \ if section.split('.')[0] == self.name ] logger.info( "Scanned subsystem via method '%s', result is %s", self.scantype, modules ) return modules def registerModulesInSubsystem( self ): """ Register all the modules found for one subsystem. """ # walk the modules path and find plugins for filename in self.findModulesInSubsystem(): if filename.endswith( ".py" ): # FIXME: we should look for *.pyc, *.pyo, *.so as well try: modulename = filename[:-3] disable = config.getBool( "%s.%s" % ( self.name, modulename ), "disable", False ) if disable: logger.info( "skipping module %s.%s as requested via config file." % ( self.name, modulename ) ) continue module = __import__( name = "%s.%s" % ( self.name, modulename ), fromlist = ["factory"], level = 0 ) except Exception, e: logger.error( "could not import %s: %s" % ( filename, e ) ) # This is a little bit ugly, but we need to see the traceback ! import traceback import sys traceback.print_exception(*sys.exc_info()) else: self.registerObjectsFromModule( module ) def registerObjectsFromModule( self, module ): """ Register all the objects given back from the factory method in one plugin (module). """ logger.debug( "...in subsystem %s: found module %s" % ( self.name, module ) ) try: factory = getattr( module, "factory" ) except AttributeError: logger.debug( "no plugin: factory function not found in module %s" % module ) else: try: need_busnames = getattr( module, "NEEDS_BUSNAMES" ) for busname in need_busnames: self.busnames.append( self.tryClaimBusName( busname ) ) except AttributeError: logger.debug( "module %s doesn't need additional busnames" % module ) try: # we used to pass the controller to the individual objects, we no longer do but # pass ourself instead for obj in factory( "%s.%s" % ( DBUS_BUS_NAME_PREFIX, self.name ), self ): self._objects[obj.path] = obj except Exception, e: logger.exception( "factory method not successfully completed for module %s" % module ) def tryClaimBusName( self, name=None ): """ Claim a dbus bus name. """ if not name: name = "%s.%s" % ( DBUS_BUS_NAME_PREFIX, self.name ) try: busname = dbus.service.BusName( name, self.bus ) except dbus.DBusException: logger.warning( "Can't claim bus name '%s', check configuration in /etc/dbus-1/system.d/frameworkd.conf" % name ) busname = None return busname #----------------------------------------------------------------------------# class Framework( Subsystem ): #----------------------------------------------------------------------------# """ The master subsystem. """ def __init__( self, bus, path, scantype, controller ): Subsystem.__init__( self, "frameworkd", bus, path, scantype, controller ) if self.busnames is []: logger.critical( "can't claim master busname. Exiting" ) sys.exit( -1 ) def registerModulesInSubsystem( self ): import framework.objectquery self.registerObjectsFromModule( framework.objectquery ) #----------------------------------------------------------------------------# class External( Subsystem ): #----------------------------------------------------------------------------# """ A Wrapper for an external subsystem. An external subsystem is "just" a child process to us. """ def __init__( self, name, path, scantype, controller ): self._process = ProcessGuard( path ) Subsystem.__init__( self, name, None, None, scantype, controller ) def launch( self ): self._process.execute( onExit=self.processExit ) def processExit( self, pid, exitcode, exitsignal ): print "process has exit :/" def shutdown( self ): self._process.shutdown() fso-frameworkd-0.10.1/framework/subsystems/000077500000000000000000000000001174525413000207375ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/__init__.py000066400000000000000000000000001174525413000230360ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/odeviced/000077500000000000000000000000001174525413000225215ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/odeviced/__init__.py000066400000000000000000000000001174525413000246200ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/odeviced/accelerometer.py000066400000000000000000000146011174525413000257070ustar00rootroot00000000000000""" Accelerometer module for odeviced. (C) 2008 John Lee (C) 2008 Openmoko, Inc. GPLv2 or later """ from __future__ import with_statement MODULE_NAME = "odeviced.accelerometer" __version__ = "0.2.2" from framework.config import config from threading import RLock, Thread import os, struct import logging logger = logging.getLogger( MODULE_NAME ) #============================================================================# class Accelerometer(object): #============================================================================# def retrieve(self): raise NotImplementedError #============================================================================# class MockAccelerometer(Accelerometer): #============================================================================# def retrieve(self): return 0, 0, 0 #============================================================================# class InputDevAccelerometer(Accelerometer): #============================================================================# """Read values from kernel input device """ # Event types EV_SYN = 0x00 EV_KEY = 0x01 EV_REL = 0x02 EV_ABS = 0x03 EV_MSC = 0x04 EV_SW = 0x05 EV_LED = 0x11 EV_SND = 0x12 EV_REP = 0x14 EV_FF = 0x15 EV_PWR = 0x16 EV_FF = 0x17 EV_MAX = 0x1f EV_CNT = (EV_MAX+1) # Relative axes REL_X = 0x00 REL_Y = 0x01 REL_Z = 0x02 REL_RX = 0x03 REL_RY = 0x04 REL_RZ = 0x05 REL_HWHEEL = 0x06 REL_DIAL = 0x07 REL_WHEEL = 0x08 REL_MISC = 0x09 REL_MAX = 0x0f REL_CNT = REL_MAX + 1 input_event_struct = "@llHHi" input_event_size = struct.calcsize(input_event_struct) def __init__(self, device): super(InputDevAccelerometer, self).__init__() self.device_fd = os.open(device, os.O_RDONLY | os.O_SYNC) def _unpack(self): """struct input_event { struct timeval time; /* (long, long) */ __u16 type; __u16 code; __s32 value; }; return (tv_sec, tv_usec, type, code, value) """ i = 0 while True: try: data = os.read(self.device_fd, InputDevAccelerometer.input_event_size) except OSError, e: logger.exception( "could not read from accelerometer device node: %s" % e ) raise else: if len(data) == InputDevAccelerometer.input_event_size: break; return struct.unpack(InputDevAccelerometer.input_event_struct,data) def _unpack_xyz(self): """return a 3 tuple """ # wait for EV_SYN while self._unpack()[2] != InputDevAccelerometer.EV_SYN: pass # now return (x, y, z) return (self._unpack()[4], self._unpack()[4], self._unpack()[4]) #============================================================================# class Gta02Accelerometer(InputDevAccelerometer): #============================================================================# """Read values from gta02. for now we use just one. >>> g = Gta02Accelerometer() >>> g.sample_rate = 400 >>> g.sample_rate 400 >>> g.sample_rate = 100 >>> g.sample_rate 100 """ INPUT_DEV = "/dev/input/event3" SYS_SAMPLE_RATE = "/sys/bus/platform/devices/lis302dl.2/sample_rate" def __init__(self, device=None, sample_rate=None): if device is None: device = Gta02Accelerometer.INPUT_DEV super(Gta02Accelerometer, self).__init__(device) if sample_rate is not None: self.sample_rate = sample_rate def _get_sample_rate(self): f = open(Gta02Accelerometer.SYS_SAMPLE_RATE, 'r', 0) sample_rate = int(f.read()) f.close() return sample_rate def _set_sample_rate(self, sample_rate): """possible values: 100, 400 """ if sample_rate != 100 and sample_rate != 400: return f = open(Gta02Accelerometer.SYS_SAMPLE_RATE, 'w', 0) f.write('%d\n' % sample_rate) f.close() sample_rate = property(_get_sample_rate, _set_sample_rate) def retrieve(self): return self._unpack_xyz() # stuffs for fso import dbus.service from helpers import DBUS_INTERFACE_PREFIX, DBUS_PATH_PREFIX from gobject import idle_add #============================================================================# class FSOSubsystem(dbus.service.Object): #============================================================================# DBUS_INTERFACE = DBUS_INTERFACE_PREFIX + ".Accelerometer" DBUS_PATH = DBUS_PATH_PREFIX + "/Accelerometer" def __init__(self, accelerometer, bus): self.path = FSOSubsystem.DBUS_PATH self.interface = FSOSubsystem.DBUS_INTERFACE self.accelerometer = accelerometer dbus.service.Object.__init__(self, bus, self.path) logger.info( "%s %s initialized. Serving %s at %s", self.__class__.__name__, __version__, self.interface, self.path ) @dbus.service.method(DBUS_INTERFACE, '', 'iii') def Value(self): return self.accelerometer.retrieve() @dbus.service.method(DBUS_INTERFACE, '', 'i') def GetSampleRate(self): return self.accelerometer.sample_rate @dbus.service.method(DBUS_INTERFACE, 'i', '') def SetSampleRate(self, sample_rate): self.accelerometer.sample_rate = sample_rate #============================================================================# def factory(prefix, controller): #============================================================================# # FIXME I would let the FSOSubsystem object deal with chosing the device type device_map = {'gta02': Gta02Accelerometer, 'mock': MockAccelerometer} device = config.getValue( MODULE_NAME, "accelerometer_type", "mock" ) device_class = device_map[ device ] f = FSOSubsystem(device_class(), controller.bus) return [f, ] #============================================================================# def _doctest(): #============================================================================# try: import doctest except ImportError: return else: doctest.testmod() #============================================================================# if __name__ == '__main__': #============================================================================# _doctest() fso-frameworkd-0.10.1/framework/subsystems/odeviced/audio.py000066400000000000000000000577421174525413000242130ustar00rootroot00000000000000#!/usr/bin/env python """ Open Device Daemon - A plugin for audio device peripherals (C) 2008-2009 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: odeviced Module: audio """ MODULE_NAME = "odeviced.audio" __version__ = "0.5.9.11" from framework.config import config from framework.patterns import asyncworker, processguard from helpers import DBUS_INTERFACE_PREFIX, DBUS_PATH_PREFIX, readFromFile, writeToFile, cleanObjectName APLAY_COMMAND = "/usr/bin/aplay" import gobject import dbus.service import sys, os, time, types, subprocess import logging logger = logging.getLogger( "odeviced.audio" ) #----------------------------------------------------------------------------# class UnknownFormat( dbus.DBusException ): #----------------------------------------------------------------------------# _dbus_error_name = "org.freesmartphone.Device.Audio.UnknownFormat" #----------------------------------------------------------------------------# class UnsupportedFormat( dbus.DBusException ): #----------------------------------------------------------------------------# _dbus_error_name = "org.freesmartphone.Device.Audio.UnsupportedFormat" #----------------------------------------------------------------------------# class PlayerError( dbus.DBusException ): #----------------------------------------------------------------------------# _dbus_error_name = "org.freesmartphone.Device.Audio.PlayerError" #----------------------------------------------------------------------------# class NotPlaying( dbus.DBusException ): #----------------------------------------------------------------------------# _dbus_error_name = "org.freesmartphone.Device.Audio.NotPlaying" #----------------------------------------------------------------------------# class AlreadyPlaying( dbus.DBusException ): #----------------------------------------------------------------------------# _dbus_error_name = "org.freesmartphone.Device.Audio.AlreadyPlaying" #----------------------------------------------------------------------------# class ScenarioInvalid( dbus.DBusException ): #----------------------------------------------------------------------------# _dbus_error_name = "org.freesmartphone.Device.Audio.ScenarioInvalid" #----------------------------------------------------------------------------# class ScenarioStackUnderflow( dbus.DBusException ): #----------------------------------------------------------------------------# _dbus_error_name = "org.freesmartphone.Device.Audio.ScenarioStackUnderflow" #----------------------------------------------------------------------------# class DeviceFailed( dbus.DBusException ): #----------------------------------------------------------------------------# _dbus_error_name = "org.freesmartphone.Device.Audio.DeviceFailed" #----------------------------------------------------------------------------# class Player( asyncworker.AsyncWorker ): #----------------------------------------------------------------------------# """ Base class implementing common logic for all Players. """ def __init__( self, dbus_object ): asyncworker.AsyncWorker.__init__( self ) self._object = dbus_object def enqueueTask( self, ok_cb, error_cb, task, *args ): self.enqueue( ok_cb, error_cb, task, args ) def onProcessElement( self, element ): logger.debug( "getting task from queue..." ) ok_cb, error_cb, task, args = element logger.debug( "got task: %s %s" % ( task, args ) ) try: method = getattr( self, "task_%s" % task ) except AttributeError: logger.debug( "unhandled task: %s %s" % ( task, args ) ) else: method( ok_cb, error_cb, *args ) return True def task_play( self, ok_cb, error_cb, name, loop, length ): ok_cb() def task_stop( self, ok_cb, error_cb, name ): ok_cb() def task_panic( self, ok_cb, error_cb ): ok_cb() @classmethod def supportedFormats( cls ): return [] #----------------------------------------------------------------------------# class NullPlayer( Player ): #----------------------------------------------------------------------------# """ A dummy player, useful e.g. if no audio subsystem is available. """ def task_play( self, ok_cb, error_cb, name, loop, length ): logger.info( "NullPlayer [not] playing sound %s" % name ) ok_cb() def task_stop( self, ok_cb, error_cb, name ): logger.info( "NullPlayer [not] stopping sound %s" % name ) ok_cb() def task_panic( self, ok_cb, error_cb ): logger.info( "NullPlayer [not] stopping all sounds" ) ok_cb() #----------------------------------------------------------------------------# class GStreamerPlayer( Player ): #----------------------------------------------------------------------------# """ A Gstreamer based Player. """ decoderMap = {} @classmethod def supportedFormats( cls ): try: global gst import gst as gst except ImportError: logger.warning( "Could not setup gstreamer player (python-gst not installed?)" ) return [] # set up decoder map if cls.decoderMap == {}: cls._trySetupDecoder( "mod", "modplug" ) cls._trySetupDecoder( "mp3", "mad" ) cls._trySetupDecoder( "sid", "siddec" ) cls._trySetupDecoder( "wav", "wavparse" ) # ogg w/ integer vorbis decoder, found on embedded systems haveit = cls._trySetupDecoder( "ogg", "oggdemux ! ivorbisdec ! audioconvert" ) if not haveit: # ogg w/ floating point vorbis decoder, found on desktop systems cls._trySetupDecoder( "ogg", "oggdemux ! vorbisdec ! audioconvert" ) return cls.decoderMap.keys() @classmethod def _trySetupDecoder( cls, ext, dec ): # FIXME might even save the bin's already, not just the description try: gst.parse_bin_from_description( dec, 0 ) except gobject.GError, e: logger.warning( "GST can't parse %s; Not adding %s to decoderMap" % ( dec, ext ) ) return False else: cls.decoderMap[ext] = dec return True def __init__( self, *args, **kwargs ): Player.__init__( self, *args, **kwargs ) self.pipelines = {} def _onMessage( self, bus, message, name ): pipeline, status, loop, length, ok_cb, error_cb = self.pipelines[name] logger.debug( "GST message received while file status = %s" % status ) t = message.type if t == gst.MESSAGE_EOS: # shall we restart? if loop: logger.debug( "G: EOS -- restarting stream" ) pipeline.seek_simple( gst.FORMAT_TIME, gst.SEEK_FLAG_FLUSH, 0 ) else: logger.debug( "G: EOS" ) self._updateSoundStatus( name, "stopped" ) pipeline.set_state( gst.STATE_NULL ) del self.pipelines[name] elif t == gst.MESSAGE_ERROR: pipeline.set_state(gst.STATE_NULL) del self.pipelines[name] err, debug = message.parse_error() logger.debug( "G: ERROR: %s %s" % ( err, debug ) ) error_cb( PlayerError( err.message ) ) elif t == gst.MESSAGE_STATE_CHANGED: previous, current, pending = message.parse_state_changed() logger.debug( "G: STATE NOW: (%s) -> %s -> (%s)" % ( previous, current, pending ) ) if ( previous, current, pending ) == ( gst.STATE_READY, gst.STATE_PAUSED, gst.STATE_PLAYING ): self._updateSoundStatus( name, "playing" ) ok_cb() if length: logger.debug( "adding timeout for %s of %d seconds" % ( name, length ) ) gobject.timeout_add_seconds( length, self._playTimeoutReached, name ) elif ( previous, current, pending ) == ( gst.STATE_PLAYING, gst.STATE_PAUSED, gst.STATE_READY ): self._updateSoundStatus( name, "stopped" ) pipeline.set_state( gst.STATE_NULL ) del self.pipelines[name] # ok_cb() else: # uninteresting state change pass else: logger.debug( "G: UNHANDLED: %s" % t ) def _playTimeoutReached( self, name ): try: pipeline, status, loop, length, ok_cb, error_cb = self.pipelines[name] except KeyError: # might have vanished in the meantime? logger.warning( "audio pipeline for %s has vanished before timer could fire" % name ) return False previous, current, next = pipeline.get_state() logger.debug( "custom player timeout for %s reached, state is %s" % ( name, current ) ) if loop: pipeline.set_state( gst.STATE_NULL ) del self.pipelines[name] self.task_play( lambda: None, lambda foo: None, name, loop, length ) #pipeline.seek_simple( gst.FORMAT_TIME, gst.SEEK_FLAG_FLUSH, 0 ) else: self.task_stop( lambda: None, lambda foo: None, name ) return False # don't call us again, mainloop def _updateSoundStatus( self, name, newstatus ): pipeline, status, loop, length, ok_cb, error_cb = self.pipelines[name] if newstatus != status: self.pipelines[name] = pipeline, newstatus, loop, length, ok_cb, error_cb self._object.SoundStatus( name, newstatus, {} ) def task_play( self, ok_cb, error_cb, name, loop, length ): if name in self.pipelines: error_cb( AlreadyPlaying( name ) ) else: # Split options from filename, these may be useful for advanced # settings on MOD and SID files. try: base, ext = name.rsplit( '.', 1 ) except ValueError: # no extension provided return error_cb( UnknownFormat( "Can't guess format from extension" ) ) options = ext.split( ';' ) ext = options.pop( 0 ) file = ".".join( [ base, ext ] ).replace( ' ', r'\ ' ) try: decoder = GStreamerPlayer.decoderMap[ ext ] except KeyError: return error_cb( UnknownFormat( "Known formats are %s" % self.decoderMap.keys() ) ) else: if len(options) > 0: decoder = decoder + " " + " ".join( options ) # parse_launch may burn a few cycles compared to element_factory_make, # however it should still be faster than creating the pipeline from # individual elements in python, since it's all happening in compiled code try: pipeline = gst.parse_launch( 'filesrc location="%s" ! %s ! alsasink' % ( file, decoder ) ) except gobject.GError, e: logger.exception( "could not instanciate pipeline: %s" % e ) return error_cb( PlayerError( "Could not instanciate pipeline due to an internal error." ) ) else: # everything ok, go play bus = pipeline.get_bus() bus.add_signal_watch() bus.connect( "message", self._onMessage, name ) self.pipelines[name] = ( pipeline, "unknown", loop, length, ok_cb, error_cb ) pipeline.set_state( gst.STATE_PLAYING ) def task_stop( self, ok_cb, error_cb, name ): try: pipeline = self.pipelines[name][0] except KeyError: error_cb( NotPlaying( name ) ) else: pipeline.set_state( gst.STATE_READY ) ok_cb() def task_panic( self, ok_cb, error_cb ): for name in self.pipelines: self.pipelines[name][0].set_state( gst.STATE_READY ) ok_cb() #----------------------------------------------------------------------------# class AlsaPlayer( Player ): #----------------------------------------------------------------------------# """ An alsa player, useful for wav format, when the latency of the GStreamerPlayer is too heavy. """ @classmethod def supportedFormats( cls ): if os.path.exists( APLAY_COMMAND ): return [ "wav" ] else: return [] sounds = {} def task_play( self, ok_cb, error_cb, name, loop, length ): if name in self.sounds: error_cb( AlreadyPlaying() ) else: p = processguard.ProcessGuard( [ "/usr/bin/aplay", str(name) ] ) p.execute( onExit = self._onPlayingFinished ) self.sounds[name] = p, loop, length ok_cb() logger.info( "AlsaPlayer playing sound %s" % name ) self._object.SoundStatus( name, "playing", {} ) def task_stop( self, ok_cb, error_cb, name ): if name not in self.sounds: error_cb( NotPlaying() ) else: p, loop, length = self.sounds[name] p.shutdown() del self.sounds[name] ok_cb() logger.info( "AlsaPlayer stopped sound %s" % name ) self._object.SoundStatus( name, "stopped", {} ) def task_panic( self, ok_cb, error_cb ): logger.info( "AlsaPlayer stopping all sounds" ) for key, value in self.sounds.items(): p, loop, length = value p.shutdown() del self.sounds[key] self._object.SoundStatus( key, "stopped", {} ) ok_cb() def _onPlayingFinished( self, pid, exitcode, exitsignal ): logger.info( "AlsaPlayer %d exited with exitcode %d (signal %d)" % ( pid, exitcode, exitsignal ) ) normalShutdown = ( exitcode == 0 ) for key, value in self.sounds.items(): p, loop, length = value if p.hadpid == pid or p.pid == pid: if normalShutdown and loop: logger.debug( "AlsaPlayer restarting sound %s due to loop value" % key ) p.execute( onExit = self._onPlayingFinished ) else: del self.sounds[key] self._object.SoundStatus( key, "stopped", {} ) #----------------------------------------------------------------------------# class AlsaScenarios( object ): #----------------------------------------------------------------------------# """ Controls alsa audio scenarios. """ def __init__( self, dbus_object, statedir, defaultscene ): self._object = dbus_object self._statedir = statedir self._default = defaultscene self._statenames = None # FIXME set default profile (from configuration) # FIXME should be set when this audio object initializes self._current = "unknown" self._stack = [] gobject.idle_add( self._initScenario ) logger.info( " ::: using alsa scenarios in %s, default = %s" % ( statedir, defaultscene ) ) def _initScenario( self ): # gather default profile from preferences if os.path.exists( "%s/%s.state" % ( self._statedir, self._default ) ): self.setScenario( self._default ) logger.info( "default alsa scenario restored" ) else: logger.warning( "default alsa scenario '%s' not found in '%s'. device may start uninitialized" % ( self._default, self._statedir ) ) return False def pushScenario( self, scenario ): current = self._current if self.setScenario( scenario ): self._stack.append( current ) return True else: return False def pullScenario( self ): previous = self._stack.pop() result = self.setScenario( previous ) if result is False: return result else: return previous def getScenario( self ): return self._current def storeScenario( self, scenario ): statename = "%s/%s.state" % ( self._statedir, scenario ) result = subprocess.call( [ "alsactl", "-f", statename, "store" ] ) if result != 0: logger.error( "can't store alsa scenario to %s" % statename ) return False else: # reload scenarios next time self._statenames = None return True def getAvailableScenarios( self ): # FIXME might check timestamp or use inotify if self._statenames is None: try: files = os.listdir( self._statedir ) except OSError: logger.warning( "no state files in %s found" % self._statedir ) self._statenames = [] else: self._statenames = [ state[:-6] for state in files if state.endswith( ".state" ) ] return self._statenames def setScenario( self, scenario ): if not scenario in self.getAvailableScenarios(): return False statename = "%s/%s.state" % ( self._statedir, scenario ) result = subprocess.call( [ "alsactl", "-f", statename, "restore" ] ) if result == 0: # work around ASoC DAPM problem if scenario == "gsmbluetooth": result += subprocess.call( [ "amixer", "sset", "Capture Left Mixer", "Analogue Mix Right" ] ) result += subprocess.call( [ "amixer", "sset", "Capture Left Mixer", "Analogue Mix Left" ] ) if result == 0: self._current = scenario self._object.Scenario( scenario, "user" ) return True else: logger.error( "can't set alsa scenario from %s" % statename ) return False def hasScenario( self, scenario ): return scenario in self.getAvailableScenarios() #----------------------------------------------------------------------------# class Audio( dbus.service.Object ): #----------------------------------------------------------------------------# """ A Dbus Object implementing org.freesmartphone.Device.Audio """ DBUS_INTERFACE = DBUS_INTERFACE_PREFIX + ".Audio" players = {} def __init__( self, bus, index, node ): self.interface = self.DBUS_INTERFACE self.path = DBUS_PATH_PREFIX + "/Audio" dbus.service.Object.__init__( self, bus, self.path ) for player in ( AlsaPlayer, GStreamerPlayer, ): supportedFormats = player.supportedFormats() instance = player( self ) for format in supportedFormats: if format not in self.players: self.players[format] = instance scenario_dir = config.getValue( MODULE_NAME, "scenario_dir", "/etc/alsa/scenario" ) default_scenario = config.getValue( MODULE_NAME, "default_scenario", "default" ) self.scenario = AlsaScenarios( self, scenario_dir, default_scenario ) logger.info( "%s %s initialized. Serving %s at %s" % ( self.__class__.__name__, __version__, self.interface, self.path ) ) logger.debug( "^^^ found players for following formats: '%s'" % self.players.keys() ) def playerForFile( self, name ): try: base, ext = name.rsplit( '.', 1 ) except ValueError: # no extension provided raise UnknownFormat( "Can't guess format from extension" ) options = ext.split( ';' ) ext = options.pop( 0 ) try: player = self.players[ext] except KeyError: raise UnsupportedFormat( "No player registered for format '%s'" % ext ) return player # # dbus info methods # @dbus.service.method( DBUS_INTERFACE, "", "a{sv}", async_callbacks=( "dbus_ok", "dbus_error" ) ) def GetInfo( self, dbus_ok, dbus_error ): info = {} formats = [] for player in self.players.values(): formats += player.supportedFormats() info["name"] = "Default Audio Device" info["formats"] = list( set( formats ) ) info["scenario"] = self.scenario.getScenario() info["scenarios"] = dbus.Array( self.scenario.getAvailableScenarios(), "as" ) dbus_ok( info ) # # dbus sound methods # @dbus.service.method( DBUS_INTERFACE, "sii", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) def PlaySound( self, name, loop, length, dbus_ok, dbus_error ): self.playerForFile( name ).enqueueTask( dbus_ok, dbus_error, "play", name, loop, length ) @dbus.service.method( DBUS_INTERFACE, "s", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) def StopSound( self, name, dbus_ok, dbus_error ): self.playerForFile( name ).enqueueTask( dbus_ok, dbus_error, "stop", name ) @dbus.service.method( DBUS_INTERFACE, "", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) def StopAllSounds( self, dbus_ok, dbus_error ): for player in self.players.values(): player.enqueueTask( dbus_ok, dbus_error, "panic" ) # # dbus scenario methods # # FIXME ugly. error handling should be done by the scenario itself @dbus.service.method( DBUS_INTERFACE, "", "as", async_callbacks=( "dbus_ok", "dbus_error" ) ) def GetAvailableScenarios( self, dbus_ok, dbus_error ): dbus_ok( self.scenario.getAvailableScenarios() ) @dbus.service.method( DBUS_INTERFACE, "", "s", async_callbacks=( "dbus_ok", "dbus_error" ) ) def GetScenario( self, dbus_ok, dbus_error ): dbus_ok( self.scenario.getScenario() ) @dbus.service.method( DBUS_INTERFACE, "s", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) def SetScenario( self, name, dbus_ok, dbus_error ): if not self.scenario.hasScenario( name ): dbus_error( ScenarioInvalid( "available scenarios are: %s" % self.scenario.getAvailableScenarios() ) ) else: if self.scenario.setScenario( name ): dbus_ok() else: dbus_error( DeviceFailed( "unknown error while setting scenario" ) ) @dbus.service.method( DBUS_INTERFACE, "s", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) def PushScenario( self, name, dbus_ok, dbus_error ): if not self.scenario.hasScenario( name ): dbus_error( ScenarioInvalid( "available scenarios are: %s" % self.scenario.getAvailableScenarios() ) ) else: if self.scenario.pushScenario( name ): dbus_ok() else: dbus_error( DeviceFailed( "unknown error while pushing scenario" ) ) @dbus.service.method( DBUS_INTERFACE, "", "s", async_callbacks=( "dbus_ok", "dbus_error" ) ) def PullScenario( self, dbus_ok, dbus_error ): try: previousScenario = self.scenario.pullScenario() except IndexError: dbus_error( ScenarioStackUnderflow( "forgot to push a scenario?" ) ) else: if previousScenario is False: dbus_error( DeviceFailed( "unknown error while pulling scenario" ) ) else: dbus_ok( previousScenario ) @dbus.service.method( DBUS_INTERFACE, "s", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) def StoreScenario( self, name, dbus_ok, dbus_error ): if self.scenario.storeScenario( name ): dbus_ok() else: dbus_error( DeviceFailed( "unknown error while storing scenario" ) ) # # dbus signals # @dbus.service.signal( DBUS_INTERFACE, "ssa{sv}" ) def SoundStatus( self, name, status, properties ): logger.info( "sound status %s %s %s" % ( name, status, properties ) ) @dbus.service.signal( DBUS_INTERFACE, "ss" ) def Scenario( self, scenario, reason ): logger.info( "sound scenario %s %s" % ( scenario, reason ) ) #----------------------------------------------------------------------------# def factory( prefix, controller ): #----------------------------------------------------------------------------# """Instanciate plugins""" return [ Audio( controller.bus, 0, "" ) ] if __name__ == "__main__": import dbus bus = dbus.SystemBus() fso-frameworkd-0.10.1/framework/subsystems/odeviced/helpers.py000066400000000000000000000025031174525413000245350ustar00rootroot00000000000000DBUS_INTERFACE_PREFIX = "org.freesmartphone.Device" DBUS_PATH_PREFIX = "/org/freesmartphone/Device" from string import maketrans import logging logger = logging.getLogger( "odeviced.helpers" ) #============================================================================# def readFromFile( path ): #============================================================================# try: value = open( path, 'r' ).read().strip() except IOError, e: logger.warning( "(could not read from '%s': %s)" % ( path, e ) ) return "N/A" else: logger.debug( "(read %s from '%s')" % ( repr(value), path ) ) return value #============================================================================# def writeToFile( path, value ): #============================================================================# logger.debug( "(writing %s to '%s')" % ( repr(value), path ) ) try: f = open( path, 'w' ) except IOError, e: logger.warning( "(could not write to '%s': %s)" % ( path, e ) ) else: f.write( "%s\n" % value ) #============================================================================# def cleanObjectName( name ): #============================================================================# return name.translate( trans ) trans = maketrans( "-:", "__" ) fso-frameworkd-0.10.1/framework/subsystems/odeviced/idlenotifier.py000066400000000000000000000245001174525413000255510ustar00rootroot00000000000000#!/usr/bin/env python """ Open Device Daemon - A plugin for generic idle state notification The IdleNotifier signalizes while the system goes through different idle states. Another plugin or an application can use the state notifications to act accordingly. Known states and possible use cases: - "awake": after suspend and on startup, power up peripherals and prepare I/O - "busy": receiving input from input events - "idle": not receiving any input - "idle_dim": not receiving input for "a while", dim the display and/or clock down the CPU - "idle_prelock": not receiving input for "a long while", prepare to put some more I/O to sleep - "lock": not receiving input for "very long", lock the display now - "suspend": shut down CPU, suspend to RAM or DISK (C) 2008-2009 Michael 'Mickey' Lauer (C) 2008-2009 Openmoko, Inc. GPLv2 or later """ MODULE_NAME = "odeviced.idlenotifier" __version__ = "0.9.10.4" from helpers import DBUS_INTERFACE_PREFIX, DBUS_PATH_PREFIX, readFromFile, writeToFile from framework.config import config from framework import resource import gobject import dbus.service import itertools, os, sys import errno import logging logger = logging.getLogger( MODULE_NAME ) #=========================================================================# class InvalidState( dbus.DBusException ): #=========================================================================# _dbus_error_name = "org.freesmartphone.IdleNotifier.InvalidState" #=========================================================================# class IdleNotifier( dbus.service.Object ): #=========================================================================# """A Dbus Object implementing org.freesmartphone.Device.IdleNotifier""" DBUS_INTERFACE = DBUS_INTERFACE_PREFIX + ".IdleNotifier" _instance = None @classmethod def instance( klass ): return klass._instance def __init__( self, bus, index, extranodes ): self.__class__._instance = self self.interface = self.DBUS_INTERFACE self.path = DBUS_PATH_PREFIX + "/IdleNotifier/%s" % index dbus.service.Object.__init__( self, bus, self.path ) logger.info( "%s %s initialized. Serving %s at %s", self.__class__.__name__, __version__, self.interface, self.path ) self.defaultTimeouts = dict( awake=-1, busy=-1, idle=10, idle_dim=20, idle_prelock=12, lock=2, suspend=20 ) self.timeouts = self.defaultTimeouts.copy() self.states = "awake busy idle idle_dim idle_prelock lock suspend".split() self.validStates = set(self.states) self.allowedStates = set(self.states) self.state = self.states[0] configvalue = config.getValue( MODULE_NAME, "ignoreinput", "" ) ignoreinput = [ int(value) for value in configvalue.split(',') if value != "" ] self.input = {} for i in itertools.count(): if i in ignoreinput: logger.info( "skipping input node %d due to configuration" % i ) continue try: f = os.open( "/dev/input/event%d" % i, os.O_NONBLOCK ) except OSError, e: logger.debug( "can't open /dev/input/event%d: %s. Assuming it doesn't exist." % ( i, e ) ) break else: self.input[f] = "event%d" % i logger.info( "opened %d input file descriptors" % len( self.input ) ) # override default timeouts with configuration (if set) for key in self.timeouts: timeout = config.getInt( MODULE_NAME, key, self.defaultTimeouts[key] ) self.timeouts[key] = timeout if timeout == 0: self.allowedStates.remove( key ) logger.debug( "(re)setting %s timeout to %d" % ( key, self.timeouts[key] ) ) self.next = None self.timeout = 0 self.setState( "busy" ) if len( self.input ): self.timer = gobject.timeout_add_seconds( 1, self.onTimer ) def prohibitStateTransitionTo( self, state ): # FIXME do some reference counting? self.allowedStates.remove( state ) logger.info( "allowed idle states now: %s " % self.allowedStates ) self.setState( self.state ) def allowStateTransitionTo( self, state ): self.allowedStates.add( state ) logger.info( "allowed idle states now: %s " % self.allowedStates ) self.setState( self.state ) def onTimer( self ): active = False for i in self.input: active |= self.checkActivity( i ) logger.debug( "active = %s", active ) if active: self.setState( "busy" ) else: self.idletime += 1 self.checkTimeout() return True def checkActivity( self, source ): active = False try: data = True while data: data = os.read( source, 4096 ) if data: active = True #logger.debug( "read %d bytes from fd %d ('%s')" % ( len( data ), source, self.input[source] ) ) return active except OSError, e: if e[0] == errno.EAGAIN: return active logger.exception( "error while reading:" ) return False def checkTimeout( self ): if self.idletime >= self.timeout: self.setState( self.next ) def setState( self, state ): newIndex = 0 for x in self.states[1:self.states.index( state )+1]: if not x in self.allowedStates: break newIndex += 1 newState = self.states[newIndex] if not self.state == newState: self.State( newState ) self.state = newState nextIndex = min( newIndex + 1, len( self.states) - 1 ) while nextIndex and not self.states[nextIndex] in self.allowedStates: nextIndex -= 1 self.idletime = 0 self.next = self.states[nextIndex] self.timeout = self.timeouts[ self.next ] # # dbus signals # @dbus.service.signal( DBUS_INTERFACE, "s" ) def State( self, state ): logger.info("%s state change to %s", __name__, state) self.state = state # # dbus methods # @dbus.service.method( DBUS_INTERFACE, "", "s" ) def GetState( self ): return self.state @dbus.service.method( DBUS_INTERFACE, "", "a{si}" ) def GetTimeouts( self ): return self.timeouts @dbus.service.method( DBUS_INTERFACE, "si", "" ) def SetTimeout( self, state, timeout ): if not state in self.validStates: raise InvalidState( "valid states are: %s" % self.validStates ) self.timeouts[state] = timeout # FIXME refcounts instead? if timeout: self.allowedStates.add( state ) else: self.allowedStates.discard( state ) config.setValue(MODULE_NAME, state, timeout) config.sync() @dbus.service.method( DBUS_INTERFACE, "s", "" ) def SetState( self, state ): if state == self.state: logger.debug( "state already active. ignoring request" ) return if not state in self.validStates: raise InvalidState( "valid states are: %s" % self.validStates ) else: self.setState( state ) #=========================================================================# class CpuResource( resource.Resource ): #=========================================================================# def __init__( self, bus ): """ Init. """ self.path = "/org/freesmartphone/Device/CPU" dbus.service.Object.__init__( self, bus, self.path ) resource.Resource.__init__( self, bus, "CPU" ) logger.info( "%s %s initialized." % ( self.__class__.__name__, __version__ ) ) # # dbus org.freesmartphone.Resource [inherited from framework.Resource] # def _enable( self, on_ok, on_error ): """ Enable (inherited from Resource) """ IdleNotifier.instance().prohibitStateTransitionTo( "suspend" ) on_ok() def _disable( self, on_ok, on_error ): """ Disable (inherited from Resource) """ IdleNotifier.instance().allowStateTransitionTo( "suspend" ) on_ok() def _suspend( self, on_ok, on_error ): """ Suspend (inherited from Resource) """ # should actually trigger an error, since suspending CPU is not allowed on_ok() def _resume( self, on_ok, on_error ): """ Resume (inherited from Resource) """ # should actually trigger an error, since suspending CPU is not allowed on_ok() #=========================================================================# class DisplayResource( resource.Resource ): #=========================================================================# def __init__( self, bus ): """ Init. """ self.path = "/org/freesmartphone/Device/Display" dbus.service.Object.__init__( self, bus, self.path ) resource.Resource.__init__( self, bus, "Display" ) logger.info( "%s %s initialized." % ( self.__class__.__name__, __version__ ) ) # # dbus org.freesmartphone.Resource [inherited from framework.Resource] # def _enable( self, on_ok, on_error ): """ Enable (inherited from Resource) """ IdleNotifier.instance().prohibitStateTransitionTo( "idle_dim" ) on_ok() def _disable( self, on_ok, on_error ): """ Disable (inherited from Resource) """ IdleNotifier.instance().allowStateTransitionTo( "idle_dim" ) on_ok() def _suspend( self, on_ok, on_error ): """ Suspend (inherited from Resource) """ # FIXME should we do something here? on_ok() def _resume( self, on_ok, on_error ): """ Resume (inherited from Resource) """ # FIXME should we do something here? on_ok() #----------------------------------------------------------------------------# def factory( prefix, controller ): #----------------------------------------------------------------------------# return [ IdleNotifier( controller.bus, 0, [] ), CpuResource( controller.bus ), DisplayResource( controller.bus ) ] if __name__ == "__main__": import dbus bus = dbus.SystemBus() fso-frameworkd-0.10.1/framework/subsystems/odeviced/info.py000066400000000000000000000035751174525413000240400ustar00rootroot00000000000000#!/usr/bin/env python """ Open Device Daemon - A plugin for gathering device information (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later """ MODULE_NAME = "odeviced.info" __version__ = "0.1.3" from helpers import DBUS_INTERFACE_PREFIX, DBUS_PATH_PREFIX, readFromFile, writeToFile from framework.config import config import dbus.service import logging logger = logging.getLogger( MODULE_NAME ) #----------------------------------------------------------------------------# class Info( dbus.service.Object ): #----------------------------------------------------------------------------# """A Dbus Object implementing org.freesmartphone.Device.Info""" DBUS_INTERFACE = DBUS_INTERFACE_PREFIX + ".Info" def __init__( self, bus, index, node ): self.interface = self.DBUS_INTERFACE self.path = DBUS_PATH_PREFIX + "/Info" dbus.service.Object.__init__( self, bus, self.path ) logger.info( "%s %s initialized. Serving %s at %s" % ( self.__class__.__name__, __version__, self.interface, self.path ) ) # # dbus methods # @dbus.service.method( DBUS_INTERFACE, "", "a{sv}" ) def GetCpuInfo( self ): cpuinfo = readFromFile( "/proc/cpuinfo" ).split( '\n' ) d = {} for line in cpuinfo: try: key, value = line.split( ':' ) except ValueError: # no valid line continue d[key.strip()] = value.strip() return d #----------------------------------------------------------------------------# def factory( prefix, controller ): #----------------------------------------------------------------------------# """Scan for available sysfs nodes and instanciate corresponding dbus server objects""" return [ Info( controller.bus, 0, "" ) ] if __name__ == "__main__": import dbus bus = dbus.SystemBus() fso-frameworkd-0.10.1/framework/subsystems/odeviced/input.py000066400000000000000000000156241174525413000242420ustar00rootroot00000000000000#!/usr/bin/env python """ Open Device Daemon - A plugin for input device peripherals (C) 2008-2009 Michael 'Mickey' Lauer (C) 2008-2009 Openmoko, Inc. GPLv2 or later """ MODULE_NAME = "odeviced.input" __version__ = "0.9.9.5" from pyglet.linux_const import EV_ABS from pyglet.linux import input_device_supports_event_type from framework.patterns import asyncworker from helpers import DBUS_INTERFACE_PREFIX, DBUS_PATH_PREFIX, readFromFile, writeToFile, cleanObjectName from framework.config import config import gobject import dbus.service import itertools, sys, os, time, struct import logging logger = logging.getLogger( MODULE_NAME ) """ struct timeval { (unsigned long) time_t tv_sec; /* seconds */ (unsigned long) suseconds_t tv_usec; /* microseconds */ }; (unsigned short) __u16 type; (unsigned short) __u16 code; (signed int) __s32 value; """ input_event_struct = "@LLHHi" input_event_size = struct.calcsize( input_event_struct ) #----------------------------------------------------------------------------# class Input( dbus.service.Object, asyncworker.AsyncWorker ): #----------------------------------------------------------------------------# """A Dbus Object implementing org.freesmartphone.Device.Input""" DBUS_INTERFACE = DBUS_INTERFACE_PREFIX + ".Input" action = { "key": 1, "switch": 5 } def __init__( self, bus, index, node ): self.interface = self.DBUS_INTERFACE self.path = DBUS_PATH_PREFIX + "/Input" dbus.service.Object.__init__( self, bus, self.path ) asyncworker.AsyncWorker.__init__( self ) logger.info( "%s %s initialized. Serving %s at %s" % ( self.__class__.__name__, __version__, self.interface, self.path ) ) configvalue = config.getValue( MODULE_NAME, "ignoreinput", "" ) ignoreinput = [ int(value) for value in configvalue.split(',') if value != "" ] self.input = {} for i in itertools.count(): if i in ignoreinput: logger.info( "skipping input node %d due to configuration" % ( i ) ) continue try: f = os.open( "/dev/input/event%d" % i, os.O_NONBLOCK ) except OSError, e: logger.debug( "can't open /dev/input/event%d: %s. Assuming it doesn't exist.", i, e ) break else: # Ignore input devices spitting out absolute data (touchscreen, mouse, et. al) if input_device_supports_event_type( f, EV_ABS ): logger.info( "skipping input node %d due to it supporting EV_ABS" % ( i ) ) os.close( f ) continue else: self.input[f] = "event%d" % i logger.info( "opened %d input file descriptors", len( self.input ) ) self.watches = {} self.events = {} self.reportheld = {} for option in config.getOptions( MODULE_NAME ): if option.startswith( "report" ): try: name, typ, code, reportheld = config.getValue( MODULE_NAME, option ).split( ',' ) code = int(code) reportheld = bool(int(reportheld)) except ValueError: logger.warning( "wrong syntax for switch definition '%s': ignoring." % option ) else: self.watchForEvent( name, typ, code, reportheld ) if len( self.input ): self.launchStateMachine() def watchForEvent( self, name, action, inputcode, reportheld ): logger.debug( "adding watch for %s %s %s %s", name, action, inputcode, reportheld ) try: action = self.action[action] except KeyError: logger.error( "don't know how to deal with event action %s", action ) return False else: self.watches[ ( action, inputcode ) ] = name self.reportheld[ ( action, inputcode ) ] = reportheld def launchStateMachine( self ): for i in self.input: gobject.io_add_watch( i, gobject.IO_IN, self.onInputActivity ) def onInputActivity( self, source, condition ): data = os.read( source, 512 ) events = [ data[i:i+input_event_size] for i in range( 0, len(data), input_event_size ) ] for e in events: timestamp, microseconds, typ, code, value = struct.unpack( input_event_struct, e ) # We need more then just second accuracy timestamp = timestamp + microseconds/1000000.0 if typ != 0x00: # ignore EV_SYN (synchronization event) self.enqueue( timestamp, typ, code, value ) if __debug__: logger.debug( "read %d bytes from fd %d ('%s'): %s" % ( len( data ), source, self.input[source], (typ, code, value) ) ) return True def onProcessElement( self, event ): timestamp, typ, code, value = event if ( typ, code ) in self.watches: if value == 0x01: # pressed if self.reportheld[ typ, code ]: # not using gobject.timeout_add_seconds here, since we need to granularity timeout = gobject.timeout_add( 1000, self.callbackKeyHeldTimeout, typ, code ) else: timeout = 0 self.events[ ( typ, code ) ] = timestamp, timeout self.Event( self.watches[ ( typ, code ) ], "pressed", 0 ) elif value == 0x00: # released try: timestamp, timeout = self.events[ ( typ, code ) ] except KeyError: logger.warning( "potential logic problem, key released before pressed. watches are %s events are %s" % ( self.watches, self.events ) ) self.Event( self.watches[ ( typ, code ) ], "released", 0 ) else: delta = int( time.time() - timestamp ) self.Event( self.watches[ ( typ, code ) ], "released", delta ) if timeout: gobject.source_remove( timeout ) del self.events[ ( typ, code ) ] def callbackKeyHeldTimeout( self, typ, code ): timestamp, timeout = self.events[ ( typ, code ) ] self.Event( self.watches[ ( typ, code ) ], "held", int( time.time() - timestamp ) ) return True # call me again, after another second # # dbus signals # @dbus.service.signal( DBUS_INTERFACE, "ssi" ) def Event( self, name, action, seconds ): logger.info( "name %s %s %s" % ( name, action, seconds ) ) #----------------------------------------------------------------------------# def factory( prefix, controller ): #----------------------------------------------------------------------------# """ Initialize dbus plugin objects. """ return [ Input( controller.bus, 0, "" ) ] if __name__ == "__main__": import dbus bus = dbus.SystemBus() fso-frameworkd-0.10.1/framework/subsystems/odeviced/kernel26.py000066400000000000000000000562261174525413000245360ustar00rootroot00000000000000#!/usr/bin/env python """ Open Device Daemon - A plugin for Kernel 2.6 based class interfaces (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later """ MODULE_NAME = "odeviced.kernel26" __version__ = "0.9.9.9" from helpers import DBUS_INTERFACE_PREFIX, DBUS_PATH_PREFIX, readFromFile, writeToFile, cleanObjectName from framework.config import config from framework.patterns.kobject import KObjectDispatcher from framework.patterns.null import Null import dbus.service import gobject import os, time, sys, fcntl import calendar import logging logger = logging.getLogger( MODULE_NAME ) try: import pyrtc except ImportError: logger.error( "pyrtc not present. Can not operate real time clock" ) FBIOBLANK = 0x4611 FB_BLANK_UNBLANK = 0 FB_BLANK_POWERDOWN = 4 #----------------------------------------------------------------------------# class UnsupportedTrigger( dbus.DBusException ): #----------------------------------------------------------------------------# _dbus_error_name = "org.freesmartphone.Device.Display.UnsupportedTrigger" #----------------------------------------------------------------------------# class InvalidParameter( dbus.DBusException ): #----------------------------------------------------------------------------# _dbus_error_name = "org.freesmartphone.InvalidParameter" #----------------------------------------------------------------------------# class Display( dbus.service.Object ): #----------------------------------------------------------------------------# """A Dbus Object implementing org.freesmartphone.Device.Display using the kernel 2.6 backlight class device""" DBUS_INTERFACE = DBUS_INTERFACE_PREFIX + ".Display" SUPPORTS_MULTIPLE_OBJECT_PATHS = True OBJECT_PATH_COUNTER = 0 def __init__( self, bus, index, node ): self.interface = self.DBUS_INTERFACE self.path = DBUS_PATH_PREFIX + "/Display/%s" % cleanObjectName( node.split('/')[-1] ) dbus.service.Object.__init__( self, bus, self.path ) logger.info( "%s %s initialized. Serving %s at %s" % ( self.__class__.__name__, __version__, self.interface, self.path ) ) self.node = node self.max = int( readFromFile( "%s/max_brightness" % self.node ) ) self.current = int( readFromFile( "%s/actual_brightness" % self.node ) ) logger.debug( "current brightness %d, max brightness %d" % ( self.current, self.max ) ) self.fbblank = config.getBool( MODULE_NAME, "fb_blank", True ) logger.info( "framebuffer blanking %s" % ( "enabled" if self.fbblank else "disabled" ) ) # also register object under an incremental path self.path2 = DBUS_PATH_PREFIX + "/Display/%d" % self.__class__.OBJECT_PATH_COUNTER self.add_to_connection( self._connection, self.path2 ) self.__class__.OBJECT_PATH_COUNTER += 1 def _valueToPercent( self, value ): """ convert device dependent value to percentage """ return int( 100.0 / self.max * int( value ) ) def _percentToValue( self, percent ): """ convert percentage to device dependent value """ if percent >= 100: value = self.max elif percent <= 0: value = 0 else: value = int( round( percent / 100.0 * self.max ) ) return value def _setFbPower( self, on ): """ set power on current framebuffer device """ if not self.fbblank: return try: framebuffer = open( "/dev/fb0" ) except IOError: logger.exception( "can't open framebuffer device to issue ioctl" ) else: logger.debug( "issuing ioctl( FBIOBLANK, %s )" % ( "FB_BLANK_UNBLANK" if on else "FB_BLANK_POWERDOWN" ) ) result = fcntl.ioctl( framebuffer, FBIOBLANK, FB_BLANK_UNBLANK if on else FB_BLANK_POWERDOWN ) if result != 0: logger.warning( "issuing ioctl( FBIOBLANK, %s ) returned error %d", ( "FB_BLANK_UNBLANK" if on else "FB_BLANK_POWERDOWN" ), result ) return False # don't call me again # # dbus # @dbus.service.method( DBUS_INTERFACE, "", "s" ) def GetName( self ): return self.node.split("/")[-1] @dbus.service.method( DBUS_INTERFACE, "", "i" ) def GetBrightness( self ): value = readFromFile( "%s/actual_brightness" % self.node ) return self._valueToPercent( value ) @dbus.service.method( DBUS_INTERFACE, "i", "" ) def SetBrightness( self, brightness ): value = self._percentToValue( brightness ) if self.current != value: writeToFile( "%s/brightness" % self.node, str( value ) ) if self.current == 0: # previously, we were off self._setFbPower( True ) elif value == 0: self._setFbPower( False ) self.current = value @dbus.service.method( DBUS_INTERFACE, "", "b" ) def GetBacklightPower( self ): return readFromFile( "%s/bl_power" % self.node ) == "0" @dbus.service.method( DBUS_INTERFACE, "b", "" ) def SetBacklightPower( self, power ): value = 0 if power else 1 writeToFile( "%s/bl_power" % self.node, str( value ) ) #----------------------------------------------------------------------------# class LED( dbus.service.Object ): #----------------------------------------------------------------------------# """A Dbus Object implementing org.freesmartphone.Device.LED using the kernel 2.6 led class device""" DBUS_INTERFACE = DBUS_INTERFACE_PREFIX + ".LED" def __init__( self, bus, index, node ): self.interface = self.DBUS_INTERFACE self.path = DBUS_PATH_PREFIX + "/LED/%s" % cleanObjectName( node.split('/')[-1] ) dbus.service.Object.__init__( self, bus, self.path ) logger.info( "%s %s initialized. Serving %s at %s" % ( self.__class__.__name__, __version__, self.interface, self.path ) ) self.node = node # initial status = off self.SetBrightness( 0 ) # store available triggers for later self.triggers = readFromFile( "%s/trigger" % self.node ).split() logger.debug( "available triggers %s" % self.triggers ) def shutdown( self ): """ Called upon subsystem shutdown. """ self.SetBrightness( 0 ) # # dbus # @dbus.service.method( DBUS_INTERFACE, "", "s" ) def GetName( self ): return self.node.split("/")[-1] @dbus.service.method( DBUS_INTERFACE, "i", "" ) def SetBrightness( self, brightness ): writeToFile( "%s/trigger" % self.node, "none" ) if brightness >= 100: value = 255 elif brightness <= 0: value = 0 else: value = int( round( brightness / 100.0 * 255 ) ) writeToFile( "%s/brightness" % self.node, str( value ) ) @dbus.service.method( DBUS_INTERFACE, "ii", "" ) def SetBlinking( self, delay_on, delay_off ): # validate parameters if "timer" not in self.triggers: raise UnsupportedTrigger( "Timer trigger not available. Available triggers are %s" % self.triggers ) # do it else: writeToFile( "%s/trigger" % self.node, "timer" ) writeToFile( "%s/delay_on" % self.node, str( abs( delay_on ) ) ) writeToFile( "%s/delay_off" % self.node, str( abs( delay_off ) ) ) @dbus.service.method( DBUS_INTERFACE, "iii", "" ) def BlinkSeconds( self, seconds, delay_on, delay_off ): # define stop function def stopIt( self=self ): self.SetBrightness( 0 ) return False # remove timer, don't call again # start self.SetBlinking( delay_on, delay_off ) gobject.timeout_add_seconds( seconds, stopIt ) @dbus.service.method( DBUS_INTERFACE, "ss", "" ) def SetNetworking( self, interface, mode ): # validate parameters if "netdev" not in self.triggers: raise UnsupportedTrigger( "Netdev trigger not available. Available triggers are %s" % self.triggers ) interfaces = os.listdir( "/sys/class/net" ) if interface not in interfaces: raise InvalidParameter( "Interface %s not known. Available interfaces are %s" % ( interface, interfaces ) ) modes = mode.strip().split() for m in modes: if m not in "link rx tx".split(): raise InvalidParameter( "Mode element %s not known. Available elements are 'link rx tx'" % m ) # do it writeToFile( "%s/trigger" % self.node, "netdev" ) writeToFile( "%s/device_name" % self.node, str( interface.strip() ) ) writeToFile( "%s/mode" % self.node, str( mode.strip() ) ) #----------------------------------------------------------------------------# class PowerSupply( dbus.service.Object ): #----------------------------------------------------------------------------# """A Dbus Object implementing org.freesmartphone.Device.PowerSupply using the kernel 2.6 power_supply class device""" DBUS_INTERFACE = DBUS_INTERFACE_PREFIX + ".PowerSupply" def __init__( self, bus, index, node ): self.interface = self.DBUS_INTERFACE self.path = DBUS_PATH_PREFIX + "/PowerSupply/%s" % cleanObjectName( node.split('/')[-1] ) dbus.service.Object.__init__( self, bus, self.path ) logger.info( "%s %s initialized. Serving %s at %s" % ( self.__class__.__name__, __version__, self.interface, self.path ) ) self.node = node self.powerStatus = "unknown" self.online = False self.capacity = -1 self.type_ = readFromFile( "%s/type" % self.node ).lower() self.isBattery = ( self.type_ == "battery" ) # get polling-free battery notifications via kobject/uevent if self.isBattery: KObjectDispatcher.addMatch( "change", "/class/power_supply/%s" % node.split('/')[-1], self.handlePropertyChange ) capacityCheckTimeout = config.getInt( MODULE_NAME, "capacity_check_timeout", 60*5 ) self.capacityWatch = gobject.timeout_add_seconds( capacityCheckTimeout, self.onCapacityCheck ) # FIXME should this rather be handled globally (controller issuing a coldstart on every subsystem)? Yes! gobject.idle_add( self.onColdstart ) def isPresent( self ): toRead = "%s/present" if self.isBattery else "%s/online" present = readFromFile( toRead % self.node ) return ( present == "1" ) def theType( self ): return readFromFile( "%s/type" % self.node ) def onColdstart( self ): data = readFromFile( "%s/uevent" % self.node ) parts = data.split( '\n' ) d = dict( [ x.split('=') for x in parts if '=' in x ] ) self.handlePropertyChange( "coldplug", "", **d ) return False # mainloop: don't call me again def readCapacity( self ): if not self.isBattery: return 100 if not self.isPresent(): return -1 data = readFromFile( "%s/capacity" % self.node ) try: capacity = int( data ) except ValueError: energy_full = readFromFile( "%s/energy_full" % self.node ) energy_now = readFromFile( "%s/energy_now" % self.node ) if energy_full == "N/A" or energy_now == "N/A": return -1 else: return 100 * int(energy_now) / int(energy_full) else: return capacity def onCapacityCheck( self ): if not self.isPresent(): logger.warning( "Power supply %s has been removed. On AC.", self ) self.sendPowerStatusIfChanged( "ac" ) return True # call me again capacity = self.readCapacity() self.sendCapacityIfChanged( capacity ) if self.isPresent(): if capacity > 98: # older batteries will never reach 100 self.sendPowerStatusIfChanged( "full" ) else: # offline if capacity <= 5: self.sendPowerStatusIfChanged( "empty" ) elif capacity <= 10: self.sendPowerStatusIfChanged( "critical" ) return True # call me again def handlePropertyChange( self, action, path, **properties ): if not self.isPresent(): return False # don't call me again logger.debug( "got property action '%s' from uevent for path '%s': %s" % ( action, path, properties ) ) try: self.online = ( properties["POWER_SUPPLY_ONLINE"] == '1' ) except KeyError: pass try: powerStatus = properties["POWER_SUPPLY_STATUS"].lower() except KeyError: pass else: # NOTE1: On some devices, peripherals are connected directly to the battery, which means # they can _not_ drag power from USB. Hence, the battery needs to be recharged frequently, # even while it is full. To cover up for this, we do not send 'charging' if the last status was # full. if powerStatus == "charging" and self.powerStatus == "full": logger.info( "Charging has been restarted, although power is still full. Ignoring" ) else: # NOTE2: "Not Charging" is an interesting state which can have two reasons: # 1.) The charger has been physically inserted but the device has not yet enumerated on USB. # 2.) The battery has been fully charged and we're now just grabbing power from the charger. if powerStatus != "not charging": self.sendPowerStatusIfChanged( powerStatus ) return False # don't call me again def sendPowerStatusIfChanged( self, powerStatus ): if powerStatus != self.powerStatus: self.PowerStatus( powerStatus ) def sendCapacityIfChanged( self, capacity ): if capacity != self.capacity: self.Capacity( capacity ) # # dbus methods # @dbus.service.method( DBUS_INTERFACE, "", "s" ) def GetName( self ): return self.node.split("/")[-1] @dbus.service.method( DBUS_INTERFACE, "", "s" ) def GetType( self ): return self.theType() # FIXME: we might want to remove that -- anyone really interested should rather walk through the sysfs path @dbus.service.method( DBUS_INTERFACE, "", "a{sv}" ) def GetInfo( self ): # AC/BATs differ in lots of nodes. Do we want additional methods for present / online ? keys = [ key for key in os.listdir( self.node ) if key != "uevent" if os.path.isfile( "%s/%s" % ( self.node, key ) ) ] dict = {} for key in keys: dict[key] = readFromFile( "%s/%s" % ( self.node, key ) ) return dict @dbus.service.method( DBUS_INTERFACE, "", "b" ) def IsPresent( self ): return self.isPresent() @dbus.service.method( DBUS_INTERFACE, "", "i" ) def GetCapacity( self ): if self.capacity == -1: self.onCapacityCheck() return self.capacity @dbus.service.method( DBUS_INTERFACE, "", "s" ) def GetPowerStatus( self ): return self.powerStatus # # dbus signals # @dbus.service.signal( DBUS_INTERFACE, "s" ) def PowerStatus( self, status ): self.powerStatus = status logger.info( "power status now %s" % status ) @dbus.service.signal( DBUS_INTERFACE, "i" ) def Capacity( self, percent ): self.capacity = percent logger.info( "capacity now %d" % percent ) #----------------------------------------------------------------------------# class PowerSupplyApm( dbus.service.Object ): #----------------------------------------------------------------------------# """A Dbus Object implementing org.freesmartphone.Device.PowerSupply using the kernel apm or acpi facilities""" DBUS_INTERFACE = DBUS_INTERFACE_PREFIX + ".PowerSupply" def __init__( self, bus, index, node ): self.interface = self.DBUS_INTERFACE self.path = DBUS_PATH_PREFIX + "/PowerSupply/%s" % cleanObjectName( node.split('/')[-1] ) dbus.service.Object.__init__( self, bus, self.path ) logger.info( "%s %s initialized. Serving %s at %s" % ( self.__class__.__name__, __version__, self.interface, self.path ) ) self.node = node def readApm( self ): return open( self.node, "r" ).read().strip().split() # # dbus # @dbus.service.method( DBUS_INTERFACE, "", "s" ) def GetName( self ): return "APM" @dbus.service.method( DBUS_INTERFACE, "", "a{sv}" ) def GetInfo( self ): return {} @dbus.service.method( DBUS_INTERFACE, "", "b" ) def GetOnBattery( self ): d, b, f, AC, BAT, flags, percentage, time, units = self.readApm() return AC != "0x01" @dbus.service.method( DBUS_INTERFACE, "", "i" ) def GetEnergyPercentage( self ): d, b, f, AC, BAT, flags, percentage, time, units = self.readApm() return int( percentage[:-1] ) #----------------------------------------------------------------------------# class RealtimeClock( dbus.service.Object ): #----------------------------------------------------------------------------# """A Dbus Object implementing org.freesmartphone.Device.RealtimeClock using the kernel 2.6 rtc class device""" DBUS_INTERFACE = DBUS_INTERFACE_PREFIX + ".RealtimeClock" SUPPORTS_MULTIPLE_OBJECT_PATHS = True OBJECT_PATH_COUNTER = 0 def __init__( self, bus, index, node ): self.interface = self.DBUS_INTERFACE self.path = DBUS_PATH_PREFIX + "/RTC/%s" % cleanObjectName( node.split('/')[-1] ) dbus.service.Object.__init__( self, bus, self.path ) logger.info( "%s %s initialized. Serving %s at %s" % ( self.__class__.__name__, __version__, self.interface, self.path ) ) self.node = node # also register object under an incremental path self.path2 = DBUS_PATH_PREFIX + "/RTC/%d" % self.__class__.OBJECT_PATH_COUNTER self.add_to_connection( self._connection, self.path2 ) self.__class__.OBJECT_PATH_COUNTER += 1 # # dbus # @dbus.service.method( DBUS_INTERFACE, "", "s" ) def GetName( self ): return self.node.split("/")[-1] @dbus.service.method( DBUS_INTERFACE, "", "i" ) def GetCurrentTime( self ): """Return seconds since epoch (UTC)""" return int( readFromFile( "%s/since_epoch" % self.node ) ) @dbus.service.method( DBUS_INTERFACE, "i", "" ) def SetCurrentTime( self, t ): """Set time by seconds since epoch (UTC)""" pyrtc.rtcSetTime( time.gmtime( t ) ) @dbus.service.signal( DBUS_INTERFACE, "i" ) def WakeupTimeChanged ( self, t ): pass @dbus.service.method( DBUS_INTERFACE, "", "i" ) def GetWakeupTime( self ): """Return wakeup time in seconds since epoch (UTC) if a wakeup time has been set. Return 0, otherwise.""" # the wakealarm attribute is not always present result = pyrtc.rtcReadAlarm() if result is None: logger.error( "Can't read RTC alarm. Returning 0" ) result = ( ( False, False ), 0 ) ( enabled, pending ), t = result return 0 if not enabled else calendar.timegm( t ) @dbus.service.method( DBUS_INTERFACE, "i", "" ) def SetWakeupTime( self, t ): """Set wakeup time in seconds since epoch (UTC). Set 0 to disable.""" if t == 0: pyrtc.rtcDisableAlarm() else: pyrtc.rtcSetAlarm( time.gmtime( t ) ) self.WakeupTimeChanged( t ) #----------------------------------------------------------------------------# def factory( prefix, controller ): #----------------------------------------------------------------------------# """Scan for available sysfs nodes and instanciate corresponding dbus server objects""" bus = controller.bus objects = [] # scan for displays backlightpath = "/sys/class/backlight" if os.path.exists( backlightpath ): for ( index, node ) in enumerate( os.listdir( backlightpath ) ): logger.debug( "scanning %s %s", index, node ) objects.append( Display( bus, index, "%s/%s" % ( backlightpath, node ) ) ) # scan for leds ledpath = "/sys/class/leds" if os.path.exists( ledpath ): for ( index, node ) in enumerate( os.listdir( ledpath ) ): logger.debug( "scanning %s %s", index, node ) objects.append( LED( bus, index, "%s/%s" % ( ledpath, node ) ) ) # scan for power supplies (kernel 2.6.24++) powerpath = "/sys/class/power_supply" if os.path.exists( powerpath ): for ( index, node ) in enumerate( os.listdir( powerpath ) ): logger.debug( "scanning %s %s", index, node ) objects.append( PowerSupply( bus, index+1, "%s/%s" % ( powerpath, node ) ) ) # scan for real time clocks rtcpath = "/sys/class/rtc" if os.path.exists( rtcpath ): for ( index, node ) in enumerate( os.listdir( rtcpath ) ): logger.debug( "scanning %s %s", index, node ) objects.append( RealtimeClock( bus, index, "%s/%s" % ( rtcpath, node ) ) ) return objects if __name__ == "__main__": import dbus bus = dbus.SystemBus() from itertools import count def requestInterfaceForObject( prefix, interface, object ): proxy = bus.get_object( prefix, object ) #print( proxy.Introspect( dbus_interface = "org.freedesktop.DBus.Introspectable" ) ) iface = dbus.Interface(proxy, interface ) try: iface.GetName() except dbus.exceptions.DBusException: return None else: return iface display = [] for i in count(): iface = requestInterfaceForObject( DBUS_INTERFACE_PREFIX, Display.DBUS_INTERFACE, DBUS_PATH_PREFIX+"/Display/%s" % i ) if iface is not None: display.append( iface ) else: break if display: d = display[0] print "name =", d.GetName() print "backlight power =", d.GetBacklightPower() print "brightness =", d.GetBrightness() try: d.SetBacklightPower( True ) d.SetBrightness( 50 ) except dbus.exceptions.DBusException: pass # could be permission denied led = [] for i in count(): iface = requestInterfaceForObject( DBUS_INTERFACE_PREFIX, LED.DBUS_INTERFACE, DBUS_PATH_PREFIX+"/LED/%s" % i ) if iface is not None: led.append( iface ) else: break power = [] for i in count(): iface = requestInterfaceForObject( DBUS_INTERFACE_PREFIX, PowerSupply.DBUS_INTERFACE, DBUS_PATH_PREFIX+"/PowerSupply/%s" % i ) if iface is not None: power.append( iface ) else: break rtc = [] for i in count(): iface = requestInterfaceForObject( DBUS_INTERFACE_PREFIX, RealtimeClock.DBUS_INTERFACE, DBUS_PATH_PREFIX+"/RTC/%s" % i ) if iface is not None: rtc.append( iface ) else: break print "result: ", display, power, led, rtc print "found %d displays..." % len( display ) for o in display: print ">", o.GetName() print "found %d leds..." % len( led ) for o in led: print ">", o.GetName() print "found %d power supplies..." % len(power) for o in power: print ">", o.GetName() print "found %d real time clocks..." % len( rtc ) for o in rtc: print ">", o.GetName() fso-frameworkd-0.10.1/framework/subsystems/odeviced/powercontrol.py000066400000000000000000000100021174525413000256210ustar00rootroot00000000000000#!/usr/bin/env python """ Open Device Daemon - Abstract Power and Resource Management Classes (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: odeviced Module: powercontrol """ __version__ = "0.0.0" MODULE_NAME = "odeviced.powercontrol" from helpers import DBUS_INTERFACE_PREFIX, DBUS_PATH_PREFIX, readFromFile, writeToFile from framework import resource import dbus.service import gobject import logging logger = logging.getLogger( MODULE_NAME ) #----------------------------------------------------------------------------# class GenericPowerControl( dbus.service.Object ): #----------------------------------------------------------------------------# """ An abstract Dbus Object implementing org.freesmartphone.Device.PowerControl. """ DBUS_INTERFACE = DBUS_INTERFACE_PREFIX + ".PowerControl" def __init__( self, bus, name, node ): self.interface = self.DBUS_INTERFACE self.path = DBUS_PATH_PREFIX + "/PowerControl/%s" % name dbus.service.Object.__init__( self, bus, self.path ) logger.info( "%s %s initialized. Serving %s at %s" % ( self.__class__.__name__, __version__, self.interface, self.path ) ) self.node = node self.name = name self.powernode = None self.resetnode = None self.onValue = "1" self.offValue = "0" # # default implementations, feel free to override in descendants # def getPower( self ): return int( readFromFile( self.powernode ) ) def setPower( self, power ): value = self.onValue if power else self.offValue writeToFile( self.powernode, value ) def reset( self ): writeToFile( self.resetnode, "1" ) def sendPowerSignal( self, expectedPower ): if self.getPower() == expectedPower: self.Power( self.name, expectedPower ) else: logger.warning( "%s expected a power change for %s to %s which didn't happen" % ( __name__, self.name, expectedPower ) ) return False # mainloop: don't call me again # # dbus methods # @dbus.service.method( DBUS_INTERFACE, "", "s" ) def GetName( self ): return self.name @dbus.service.method( DBUS_INTERFACE, "", "b" ) def GetPower( self ): return self.getPower() @dbus.service.method( DBUS_INTERFACE, "b", "" ) def SetPower( self, power ): if power != self.getPower(): self.setPower( power ) gobject.timeout_add_seconds( 3, lambda self=self: self.sendPowerSignal( power ) ) else: # FIXME should we issue an error here or just silently ignore? pass @dbus.service.method( DBUS_INTERFACE, "", "" ) def Reset( self ): self.reset() # # dbus signals # @dbus.service.signal( DBUS_INTERFACE, "sb" ) def Power( self, device, power ): logger.info( "%s power for %s changed to %s" % ( __name__, self.name, power ) ) #----------------------------------------------------------------------------# class ResourceAwarePowerControl( GenericPowerControl, resource.Resource ): #----------------------------------------------------------------------------# """ Resource object that maps enabling/disabling/suspending/resuming to the simple power on/off operations provided by GenericPowerControl. """ def __init__( self, bus, name, node ): GenericPowerControl.__init__( self, bus, name, node ) resource.Resource.__init__( self, bus, name ) def _enable( self, on_ok, on_error ): self.SetPower( True ) on_ok() def _disable( self, on_ok, on_error ): self.SetPower( False ) on_ok() def _suspend( self, on_ok, on_error ): self._disable( on_ok, on_error ) def _resume( self, on_ok, on_error ): self._enable( on_ok, on_error ) #----------------------------------------------------------------------------# if __name__ == "__main__": #----------------------------------------------------------------------------# pass fso-frameworkd-0.10.1/framework/subsystems/odeviced/powercontrol_ibm.py000066400000000000000000000073421174525413000264650ustar00rootroot00000000000000#!/usr/bin/env python """ Open Device Daemon - A plugin for IBM ACPI specific power controls (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: odeviced Module: powercontrol_ibm """ MODULE_NAME = "odeviced.powercontrol_ibm" __version__ = "0.0.0" from helpers import readFromFile, writeToFile from powercontrol import ResourceAwarePowerControl import os import logging logger = logging.getLogger( MODULE_NAME ) #----------------------------------------------------------------------------# class IbmBluetoothPowerControl( ResourceAwarePowerControl ): #----------------------------------------------------------------------------# def __init__( self, bus, node ): ResourceAwarePowerControl.__init__( self, bus, "Bluetooth", node ) self.powernode = self.node self.onValue = "enable" self.offValue = "disable" def getPower( self ): return readFromFile( self.powernode ).startswith( "status:\t\tenabled" ) #----------------------------------------------------------------------------# def factory( prefix, controller ): #----------------------------------------------------------------------------# """Scan for available sysfs nodes and instanciate corresponding dbus server objects""" bus = controller.bus def walk( objects, dirname, fnames ): if walk.lookForBT and "bluetooth" in fnames: objects.append( IbmBluetoothPowerControl( bus, "%s/%s" % ( dirname, "bluetooth" ) ) ) walk.lookForBT = False # only have one BT interface # if walk.lookForGPS and "neo1973-pm-gps.0" in fnames: # objects.append( NeoGpsPowerControl( bus, "%s/%s" % ( dirname, "neo1973-pm-gps.0" ) ) ) # walk.lookForGPS = False # only have one GPS interface # if walk.lookForGSM and "neo1973-pm-gsm.0" in fnames: # objects.append( NeoGsmPowerControl( bus, "%s/%s" % ( dirname, "neo1973-pm-gsm.0" ) ) ) # walk.lookForGSM = False # only have one GSM modem # if walk.lookForUSB and "s3c2410-ohci" in fnames: # works both for 1973 and FreeRunner # objects.append( NeoUsbHostPowerControl( bus, "%s/%s" % ( dirname, "neo1973-pm-host.0" ) ) ) # walk.lookForUSB = False # only have one USB host objects = [] # scan for device nodes devicespath = "/proc/acpi/ibm" walk.lookForBT = True walk.lookForGPS = True walk.lookForGSM = True walk.lookForUSB = True os.path.walk( devicespath, walk, objects ) # # check for network interfaces # if ( wireless is not None ) and "eth0" in os.listdir( "/sys/class/net"): # objects.append( NeoWifiPowerControl( bus, "/sys/class/net/eth0" ) ) return objects #----------------------------------------------------------------------------# if __name__ == "__main__": #----------------------------------------------------------------------------# import dbus bus = dbus.SystemBus() from itertools import count def requestInterfaceForObject( prefix, interface, object ): proxy = bus.get_object( prefix, object ) #print( proxy.Introspect( dbus_interface = "org.freedesktop.DBus.Introspectable" ) ) iface = dbus.Interface(proxy, interface ) try: iface.GetName() except dbus.exceptions.DBusException: return None else: return iface device = [] for i in count(): iface = requestInterfaceForObject( DBUS_INTERFACE_PREFIX, GenericPowerControl.DBUS_INTERFACE, DBUS_PATH_PREFIX+"/PowerControl/%s" % i ) if iface is not None: device.append( iface ) else: break for d in device: print( "found interface for '%s' (power status = %d)" % ( d.GetName(), d.GetPower() ) ) fso-frameworkd-0.10.1/framework/subsystems/odeviced/powercontrol_neo.py000066400000000000000000000100301174525413000264630ustar00rootroot00000000000000#!/usr/bin/env python """ Open Device Daemon - A plugin for Neo 1973 and Neo FreeRunner specific power controls (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: odeviced Module: powercontrol_neo """ MODULE_NAME = "odeviced.powercontrol_neo" __version__ = "0.8.1" from helpers import readFromFile, writeToFile from powercontrol import GenericPowerControl, ResourceAwarePowerControl import os, subprocess, sys import logging logger = logging.getLogger( MODULE_NAME ) #----------------------------------------------------------------------------# class NeoBluetoothPowerControl( ResourceAwarePowerControl ): #----------------------------------------------------------------------------# def __init__( self, bus, node ): ResourceAwarePowerControl.__init__( self, bus, "Bluetooth", node ) self.powernode = "%s/%s" % ( self.node, "power_on" ) self.resetnode = "%s/%s" % ( self.node, "reset" ) def setPower( self, power ): ResourceAwarePowerControl.setPower( self, power ) # Neo1973 Bluetooth needs special reset handling after touching power if power: writeToFile( self.resetnode, "1" ) writeToFile( self.resetnode, "0" ) else: writeToFile( self.resetnode, "1" ) #----------------------------------------------------------------------------# class NeoUsbHostPowerControl( GenericPowerControl ): # WARNING: If it's a ResourceAwarePowerControl and there is no ousaged # running on startup, then it will break USBeth by automagically switching # to USB host mode (which may not be what you want...) #----------------------------------------------------------------------------# def __init__( self, bus, node ): GenericPowerControl.__init__( self, bus, "UsbHost", node ) # mode switching self.modenode = "%s/%s" % ( node, "usb_mode" ) # node to provide 5V/100mA to USB gadgets, only present on Neo FreeRunner self.powernode = "%s/neo1973-pm-host.0/hostmode" % os.path.dirname( node ) def setPower( self, power ): if power: writeToFile( self.modenode, "host" ) else: writeToFile( self.modenode, "device" ) GenericPowerControl.setPower( self, power ) #----------------------------------------------------------------------------# class NeoWifiPowerControl( ResourceAwarePowerControl ): #----------------------------------------------------------------------------# def __init__( self, bus, node ): ResourceAwarePowerControl.__init__( self, bus, "WiFi", node ) def setPower( self, power ): powernode = "bind" if power else "unbind" writeToFile( "%s/%s" % ( self.node, powernode ), "s3c2440-sdi" ) def getPower( self ): return "eth0" in os.listdir( "/sys/class/net" ) #----------------------------------------------------------------------------# def factory( prefix, controller ): #----------------------------------------------------------------------------# """Scan for available sysfs nodes and instanciate corresponding dbus server objects""" bus = controller.bus DEVICE_DIR = "/sys/bus/platform/devices" DRIVER_DIR = "/sys/bus/platform/drivers" devices = os.listdir( DEVICE_DIR ) drivers = os.listdir( DRIVER_DIR ) objects = [] if "neo1973-pm-bt.0" in devices: objects.append( NeoBluetoothPowerControl( bus, "%s/%s" % ( DEVICE_DIR, "neo1973-pm-bt.0" ) ) ) if "s3c-ohci" in devices: objects.append( NeoUsbHostPowerControl( bus, "%s/%s" % ( DEVICE_DIR, "s3c-ohci" ) ) ) if "s3c2410-ohci" in devices: objects.append( NeoUsbHostPowerControl( bus, "%s/%s" % ( DEVICE_DIR, "s3c2410-ohci" ) ) ) if "s3c2440-sdi" in drivers: objects.append( NeoWifiPowerControl( bus, "%s/%s" % ( DRIVER_DIR, "s3c2440-sdi" ) ) ) return objects #----------------------------------------------------------------------------# if __name__ == "__main__": #----------------------------------------------------------------------------# pass fso-frameworkd-0.10.1/framework/subsystems/odeviced/pyglet/000077500000000000000000000000001174525413000240255ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/odeviced/pyglet/LICENSE000066400000000000000000000027371174525413000250430ustar00rootroot00000000000000Copyright (c) 2006-2008 Alex Holkner All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of pyglet nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. fso-frameworkd-0.10.1/framework/subsystems/odeviced/pyglet/__init__.py000066400000000000000000000000001174525413000261240ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/odeviced/pyglet/linux.py000066400000000000000000000226261174525413000255460ustar00rootroot00000000000000#!/usr/bin/env python """ Basic Linux Input System Support Based on http://pyglet.googlecode.com/svn/trunk/experimental/input/linux.py """ from linux_const import * from fcntl import ioctl import array import ctypes import errno import os import struct import sys from re import compile re = compile('^libc.so.[0-9]$') libs = os.listdir('/lib') for lib in libs: if re.match(lib): c = ctypes.cdll.LoadLibrary(lib) break _IOC_NRBITS = 8 _IOC_TYPEBITS = 8 _IOC_SIZEBITS = 14 _IOC_DIRBITS = 2 _IOC_NRMASK = ((1 << _IOC_NRBITS)-1) _IOC_TYPEMASK = ((1 << _IOC_TYPEBITS)-1) _IOC_SIZEMASK = ((1 << _IOC_SIZEBITS)-1) _IOC_DIRMASK = ((1 << _IOC_DIRBITS)-1) _IOC_NRSHIFT = 0 _IOC_TYPESHIFT = (_IOC_NRSHIFT+_IOC_NRBITS) _IOC_SIZESHIFT = (_IOC_TYPESHIFT+_IOC_TYPEBITS) _IOC_DIRSHIFT = (_IOC_SIZESHIFT+_IOC_SIZEBITS) _IOC_NONE = 0 _IOC_WRITE = 1 _IOC_READ = 2 def _IOC(dir, type, nr, size): return ((dir << _IOC_DIRSHIFT) | (type << _IOC_TYPESHIFT) | (nr << _IOC_NRSHIFT) | (size << _IOC_SIZESHIFT)) def _IOR(type, nr, struct): request = _IOC(_IOC_READ, ord(type), nr, ctypes.sizeof(struct)) def f(fileno): buffer = struct() if c.ioctl(fileno, request, ctypes.byref(buffer)) < 0: err = ctypes.c_int.in_dll(c, 'errno').value raise OSError(err, errno.errorcode[err]) return buffer return f def _IOR_len(type, nr): def f(fileno, buffer): request = _IOC(_IOC_READ, ord(type), nr, ctypes.sizeof(buffer)) if c.ioctl(fileno, request, ctypes.byref(buffer)) < 0: err = ctypes.c_int.in_dll(c, 'errno').value raise OSError(err, errno.errorcode[err]) return buffer return f def _IOR_str(type, nr): g = _IOR_len(type, nr) def f(fileno, len=256): return g(fileno, ctypes.create_string_buffer(len)).value return f time_t = ctypes.c_long suseconds_t = ctypes.c_long class timeval(ctypes.Structure): _fields_ = ( ('tv_sec', time_t), ('tv_usec', suseconds_t) ) class input_event(ctypes.Structure): _fields_ = ( ('time', timeval), ('type', ctypes.c_uint16), ('code', ctypes.c_uint16), ('value', ctypes.c_int32) ) class input_id(ctypes.Structure): _fields_ = ( ('bustype', ctypes.c_uint16), ('vendor', ctypes.c_uint16), ('product', ctypes.c_uint16), ('version', ctypes.c_uint16), ) class input_absinfo(ctypes.Structure): _fields_ = ( ('value', ctypes.c_int32), ('minimum', ctypes.c_int32), ('maximum', ctypes.c_int32), ('fuzz', ctypes.c_int32), ('flat', ctypes.c_int32), ) #define EVIOCGVERSION _IOR('E', 0x01, int) /* get driver version */ EVIOCGVERSION = _IOR('E', 0x01, ctypes.c_int) #define EVIOCGID _IOR('E', 0x02, struct input_id) /* get device ID */ EVIOCGID = _IOR('E', 0x02, input_id) #define EVIOCGNAME(len) _IOC(_IOC_READ, 'E', 0x06, len) /* get device name */ EVIOCGNAME = _IOR_str('E', 0x06) #define EVIOCGPHYS(len) _IOC(_IOC_READ, 'E', 0x07, len) /* get physical location */ EVIOCGPHYS = _IOR_str('E', 0x07) #define EVIOCGUNIQ(len) _IOC(_IOC_READ, 'E', 0x08, len) /* get unique identifier */ EVIOCGUNIQ = _IOR_str('E', 0x08) #define EVIOCGBIT(ev,len) _IOC(_IOC_READ, 'E', 0x20 + ev, len) /* get event bits */ def EVIOCGBIT(fileno, ev, buffer): return _IOR_len('E', 0x20 + ev)(fileno, buffer) #define EVIOCGABS(abs) _IOR('E', 0x40 + abs, struct input_absinfo) /* get abs value/limits */ def EVIOCGABS(fileno, abs): buffer = input_absinfo() return _IOR_len('E', 0x40 + abs)(fileno, buffer) #define EVIOCGKEY(len) _IOC(_IOC_READ, 'E', 0x18, len) /* get global keystate */ def EVIOCGKEY(fileno, buffer): return _IOR_len('E', 0x18)(fileno, buffer) #define EVIOCGLED(len) _IOC(_IOC_READ, 'E', 0x19, len) /* get all LEDs */ def EVIOCGLED(fileno, buffer): return _IOR_len('E', 0x19)(fileno, buffer) #define EVIOCGSND(len) _IOC(_IOC_READ, 'E', 0x1a, len) /* get all sounds status */ def EVIOCGSND(fileno, buffer): return _IOR_len('E', 0x1a)(fileno, buffer) #define EVIOCGSW(len) _IOC(_IOC_READ, 'E', 0x1b, len) /* get all switch states */ def EVIOCGSW(fileno, buffer): return _IOR_len('E', 0x1b)(fileno, buffer) def get_set_bits(bytes): """ Returns a set of bits that correspond to set bits in the bytefield. """ bits = set() j = 0 for byte in bytes: for i in range(8): if byte & 1: bits.add(j + i) byte >>= 1 j += 8 return bits def input_device_supports_event_type( fileno, evtype ): """ Returns true whether a given input device node supports a given event type. False, otherwise. """ event_types_bits = (ctypes.c_byte * 4)() EVIOCGBIT( fileno, 0, event_types_bits ) return evtype in get_set_bits(event_types_bits) def input_device_key_held( fileno, key ): """ Returns true whether a given key is set. False, otherwise. """ key_state_bits = (ctypes.c_byte * (KEY_MAX+8/8))() EVIOCGKEY( fileno, key_state_bits ) return key in get_set_bits(key_state_bits) event_type_names = { EV_SYN: "EV_SYN", EV_KEY: "EV_KEY", EV_REL: "EV_REL", EV_ABS: "EV_ABS", EV_MSC: "EV_MSC", EV_SW: "EV_SW", EV_LED: "EV_LED", EV_SND: "EV_SND", EV_REP: "EV_REP", EV_FF: "EV_FF", EV_PWR: "EV_PRW", EV_FF_STATUS: "EV_FF_STATUS", } class Element(object): value = None def __init__(self, fileno, event_type, event_code): self.event_type = event_type self.event_code = event_code self.name = '(%x, %x)' % (event_type, event_code) def get_value(self): return self.value def __repr__(self): return '%s(name=%r)' % (self.__class__.__name__, self.name) class AbsoluteElement(Element): def __init__(self, fileno, event_type, event_code): super(AbsoluteElement, self).__init__(fileno, event_type, event_code) absinfo = EVIOCGABS(fileno, event_code) self.value = absinfo.value self.minimum = absinfo.minimum self.maximum = absinfo.maximum def __repr__(self): return '%s(value=%d, minimum=%d, maximum=%d)' % ( self.__class__.__name__, self.value, self.minimum, self.maximum) event_types = { EV_KEY: (KEY_MAX, Element), EV_REL: (REL_MAX, Element), EV_ABS: (ABS_MAX, AbsoluteElement), EV_MSC: (MSC_MAX, Element), EV_LED: (LED_MAX, Element), EV_SND: (SND_MAX, Element), } class Device(object): _fileno = None def __init__(self, filename): self.filename = filename self._init_elements() def _init_elements(self): fileno = os.open(self.filename, os.O_RDONLY) event_version = EVIOCGVERSION(fileno).value id = EVIOCGID(fileno) self.id_bustype = id.bustype self.id_vendor = hex(id.vendor) self.id_product = hex(id.product) self.id_version = id.version self.name = EVIOCGNAME(fileno) try: self.phys = EVIOCGPHYS(fileno) except OSError: self.phys = '' try: self.uniq = EVIOCGUNIQ(fileno) except OSError: self.uniq = '' self.elements = [] self.element_map = {} event_types_bits = (ctypes.c_byte * 4)() EVIOCGBIT(fileno, 0, event_types_bits) for event_type in get_set_bits(event_types_bits): print "device", self.name, "supports event", event_type_names[event_type] if event_type not in event_types: continue max_code, element_class = event_types[event_type] nbytes = max_code // 8 + 1 event_codes_bits = (ctypes.c_byte * nbytes)() EVIOCGBIT(fileno, event_type, event_codes_bits) for event_code in get_set_bits(event_codes_bits): element = element_class(fileno, event_type, event_code) self.element_map[(event_type, event_code)] = element self.elements.append(element) os.close(fileno) def open(self): if self._fileno: return self._fileno = os.open(self.filename, os.O_RDONLY | os.O_NONBLOCK) # XXX HACK -- merge into event loop select() pyglet.clock.schedule(self.dispatch_events) def close(self): if not self._fileno: return os.close(self._fileno) self._fileno = None pyglet.clock.unschedule(self.dispatch_events) def dispatch_events(self, dt=None): # dt HACK if not self._fileno: return events = (input_event * 64)() bytes = c.read(self._fileno, events, ctypes.sizeof(events)) n_events = bytes // ctypes.sizeof(input_event) for event in events[:n_events]: try: element = self.element_map[(event.type, event.code)] element.value = event.value except KeyError: pass _devices = {} def get_devices(): base = '/dev/input' for filename in os.listdir(base): if filename.startswith('event'): path = os.path.join(base, filename) if path in _devices: continue try: _devices[path] = Device(path) except OSError: pass return _devices.values() fso-frameworkd-0.10.1/framework/subsystems/odeviced/pyglet/linux_const.py000066400000000000000000000200451174525413000267450ustar00rootroot00000000000000#!/usr/bin/env python """ Event constants from /usr/include/linux/input.h Based on http://pyglet.googlecode.com/svn/trunk/experimental/input/linux.py """ # Event types EV_SYN = 0x00 EV_KEY = 0x01 EV_REL = 0x02 EV_ABS = 0x03 EV_MSC = 0x04 EV_SW = 0x05 EV_LED = 0x11 EV_SND = 0x12 EV_REP = 0x14 EV_FF = 0x15 EV_PWR = 0x16 EV_FF_STATUS = 0x17 EV_MAX = 0x1f # Synchronization events SYN_REPORT = 0 SYN_CONFIG = 1 # Keys and buttons KEY_RESERVED = 0 KEY_ESC = 1 KEY_1 = 2 KEY_2 = 3 KEY_3 = 4 KEY_4 = 5 KEY_5 = 6 KEY_6 = 7 KEY_7 = 8 KEY_8 = 9 KEY_9 = 10 KEY_0 = 11 KEY_MINUS = 12 KEY_EQUAL = 13 KEY_BACKSPACE = 14 KEY_TAB = 15 KEY_Q = 16 KEY_W = 17 KEY_E = 18 KEY_R = 19 KEY_T = 20 KEY_Y = 21 KEY_U = 22 KEY_I = 23 KEY_O = 24 KEY_P = 25 KEY_LEFTBRACE = 26 KEY_RIGHTBRACE = 27 KEY_ENTER = 28 KEY_LEFTCTRL = 29 KEY_A = 30 KEY_S = 31 KEY_D = 32 KEY_F = 33 KEY_G = 34 KEY_H = 35 KEY_J = 36 KEY_K = 37 KEY_L = 38 KEY_SEMICOLON = 39 KEY_APOSTROPHE = 40 KEY_GRAVE = 41 KEY_LEFTSHIFT = 42 KEY_BACKSLASH = 43 KEY_Z = 44 KEY_X = 45 KEY_C = 46 KEY_V = 47 KEY_B = 48 KEY_N = 49 KEY_M = 50 KEY_COMMA = 51 KEY_DOT = 52 KEY_SLASH = 53 KEY_RIGHTSHIFT = 54 KEY_KPASTERISK = 55 KEY_LEFTALT = 56 KEY_SPACE = 57 KEY_CAPSLOCK = 58 KEY_F1 = 59 KEY_F2 = 60 KEY_F3 = 61 KEY_F4 = 62 KEY_F5 = 63 KEY_F6 = 64 KEY_F7 = 65 KEY_F8 = 66 KEY_F9 = 67 KEY_F10 = 68 KEY_NUMLOCK = 69 KEY_SCROLLLOCK = 70 KEY_KP7 = 71 KEY_KP8 = 72 KEY_KP9 = 73 KEY_KPMINUS = 74 KEY_KP4 = 75 KEY_KP5 = 76 KEY_KP6 = 77 KEY_KPPLUS = 78 KEY_KP1 = 79 KEY_KP2 = 80 KEY_KP3 = 81 KEY_KP0 = 82 KEY_KPDOT = 83 KEY_ZENKAKUHANKAKU = 85 KEY_102ND = 86 KEY_F11 = 87 KEY_F12 = 88 KEY_RO = 89 KEY_KATAKANA = 90 KEY_HIRAGANA = 91 KEY_HENKAN = 92 KEY_KATAKANAHIRAGANA = 93 KEY_MUHENKAN = 94 KEY_KPJPCOMMA = 95 KEY_KPENTER = 96 KEY_RIGHTCTRL = 97 KEY_KPSLASH = 98 KEY_SYSRQ = 99 KEY_RIGHTALT = 100 KEY_LINEFEED = 101 KEY_HOME = 102 KEY_UP = 103 KEY_PAGEUP = 104 KEY_LEFT = 105 KEY_RIGHT = 106 KEY_END = 107 KEY_DOWN = 108 KEY_PAGEDOWN = 109 KEY_INSERT = 110 KEY_DELETE = 111 KEY_MACRO = 112 KEY_MUTE = 113 KEY_VOLUMEDOWN = 114 KEY_VOLUMEUP = 115 KEY_POWER = 116 KEY_KPEQUAL = 117 KEY_KPPLUSMINUS = 118 KEY_PAUSE = 119 KEY_KPCOMMA = 121 KEY_HANGUEL = 122 KEY_HANJA = 123 KEY_YEN = 124 KEY_LEFTMETA = 125 KEY_RIGHTMETA = 126 KEY_COMPOSE = 127 KEY_STOP = 128 KEY_AGAIN = 129 KEY_PROPS = 130 KEY_UNDO = 131 KEY_FRONT = 132 KEY_COPY = 133 KEY_OPEN = 134 KEY_PASTE = 135 KEY_FIND = 136 KEY_CUT = 137 KEY_HELP = 138 KEY_MENU = 139 KEY_CALC = 140 KEY_SETUP = 141 KEY_SLEEP = 142 KEY_WAKEUP = 143 KEY_FILE = 144 KEY_SENDFILE = 145 KEY_DELETEFILE = 146 KEY_XFER = 147 KEY_PROG1 = 148 KEY_PROG2 = 149 KEY_WWW = 150 KEY_MSDOS = 151 KEY_COFFEE = 152 KEY_DIRECTION = 153 KEY_CYCLEWINDOWS = 154 KEY_MAIL = 155 KEY_BOOKMARKS = 156 KEY_COMPUTER = 157 KEY_BACK = 158 KEY_FORWARD = 159 KEY_CLOSECD = 160 KEY_EJECTCD = 161 KEY_EJECTCLOSECD = 162 KEY_NEXTSONG = 163 KEY_PLAYPAUSE = 164 KEY_PREVIOUSSONG = 165 KEY_STOPCD = 166 KEY_RECORD = 167 KEY_REWIND = 168 KEY_PHONE = 169 KEY_ISO = 170 KEY_CONFIG = 171 KEY_HOMEPAGE = 172 KEY_REFRESH = 173 KEY_EXIT = 174 KEY_MOVE = 175 KEY_EDIT = 176 KEY_SCROLLUP = 177 KEY_SCROLLDOWN = 178 KEY_KPLEFTPAREN = 179 KEY_KPRIGHTPAREN = 180 KEY_F13 = 183 KEY_F14 = 184 KEY_F15 = 185 KEY_F16 = 186 KEY_F17 = 187 KEY_F18 = 188 KEY_F19 = 189 KEY_F20 = 190 KEY_F21 = 191 KEY_F22 = 192 KEY_F23 = 193 KEY_F24 = 194 KEY_PLAYCD = 200 KEY_PAUSECD = 201 KEY_PROG3 = 202 KEY_PROG4 = 203 KEY_SUSPEND = 205 KEY_CLOSE = 206 KEY_PLAY = 207 KEY_FASTFORWARD = 208 KEY_BASSBOOST = 209 KEY_PRINT = 210 KEY_HP = 211 KEY_CAMERA = 212 KEY_SOUND = 213 KEY_QUESTION = 214 KEY_EMAIL = 215 KEY_CHAT = 216 KEY_SEARCH = 217 KEY_CONNECT = 218 KEY_FINANCE = 219 KEY_SPORT = 220 KEY_SHOP = 221 KEY_ALTERASE = 222 KEY_CANCEL = 223 KEY_BRIGHTNESSDOWN = 224 KEY_BRIGHTNESSUP = 225 KEY_MEDIA = 226 KEY_UNKNOWN = 240 BTN_MISC = 0x100 BTN_0 = 0x100 BTN_1 = 0x101 BTN_2 = 0x102 BTN_3 = 0x103 BTN_4 = 0x104 BTN_5 = 0x105 BTN_6 = 0x106 BTN_7 = 0x107 BTN_8 = 0x108 BTN_9 = 0x109 BTN_MOUSE = 0x110 BTN_LEFT = 0x110 BTN_RIGHT = 0x111 BTN_MIDDLE = 0x112 BTN_SIDE = 0x113 BTN_EXTRA = 0x114 BTN_FORWARD = 0x115 BTN_BACK = 0x116 BTN_TASK = 0x117 BTN_JOYSTICK = 0x120 BTN_TRIGGER = 0x120 BTN_THUMB = 0x121 BTN_THUMB2 = 0x122 BTN_TOP = 0x123 BTN_TOP2 = 0x124 BTN_PINKIE = 0x125 BTN_BASE = 0x126 BTN_BASE2 = 0x127 BTN_BASE3 = 0x128 BTN_BASE4 = 0x129 BTN_BASE5 = 0x12a BTN_BASE6 = 0x12b BTN_DEAD = 0x12f BTN_GAMEPAD = 0x130 BTN_A = 0x130 BTN_B = 0x131 BTN_C = 0x132 BTN_X = 0x133 BTN_Y = 0x134 BTN_Z = 0x135 BTN_TL = 0x136 BTN_TR = 0x137 BTN_TL2 = 0x138 BTN_TR2 = 0x139 BTN_SELECT = 0x13a BTN_START = 0x13b BTN_MODE = 0x13c BTN_THUMBL = 0x13d BTN_THUMBR = 0x13e BTN_DIGI = 0x140 BTN_TOOL_PEN = 0x140 BTN_TOOL_RUBBER = 0x141 BTN_TOOL_BRUSH = 0x142 BTN_TOOL_PENCIL = 0x143 BTN_TOOL_AIRBRUSH = 0x144 BTN_TOOL_FINGER = 0x145 BTN_TOOL_MOUSE = 0x146 BTN_TOOL_LENS = 0x147 BTN_TOUCH = 0x14a BTN_STYLUS = 0x14b BTN_STYLUS2 = 0x14c BTN_TOOL_DOUBLETAP = 0x14d BTN_TOOL_TRIPLETAP = 0x14e BTN_WHEEL = 0x150 BTN_GEAR_DOWN = 0x150 BTN_GEAR_UP = 0x151 KEY_OK = 0x160 KEY_SELECT = 0x161 KEY_GOTO = 0x162 KEY_CLEAR = 0x163 KEY_POWER2 = 0x164 KEY_OPTION = 0x165 KEY_INFO = 0x166 KEY_TIME = 0x167 KEY_VENDOR = 0x168 KEY_ARCHIVE = 0x169 KEY_PROGRAM = 0x16a KEY_CHANNEL = 0x16b KEY_FAVORITES = 0x16c KEY_EPG = 0x16d KEY_PVR = 0x16e KEY_MHP = 0x16f KEY_LANGUAGE = 0x170 KEY_TITLE = 0x171 KEY_SUBTITLE = 0x172 KEY_ANGLE = 0x173 KEY_ZOOM = 0x174 KEY_MODE = 0x175 KEY_KEYBOARD = 0x176 KEY_SCREEN = 0x177 KEY_PC = 0x178 KEY_TV = 0x179 KEY_TV2 = 0x17a KEY_VCR = 0x17b KEY_VCR2 = 0x17c KEY_SAT = 0x17d KEY_SAT2 = 0x17e KEY_CD = 0x17f KEY_TAPE = 0x180 KEY_RADIO = 0x181 KEY_TUNER = 0x182 KEY_PLAYER = 0x183 KEY_TEXT = 0x184 KEY_DVD = 0x185 KEY_AUX = 0x186 KEY_MP3 = 0x187 KEY_AUDIO = 0x188 KEY_VIDEO = 0x189 KEY_DIRECTORY = 0x18a KEY_LIST = 0x18b KEY_MEMO = 0x18c KEY_CALENDAR = 0x18d KEY_RED = 0x18e KEY_GREEN = 0x18f KEY_YELLOW = 0x190 KEY_BLUE = 0x191 KEY_CHANNELUP = 0x192 KEY_CHANNELDOWN = 0x193 KEY_FIRST = 0x194 KEY_LAST = 0x195 KEY_AB = 0x196 KEY_NEXT = 0x197 KEY_RESTART = 0x198 KEY_SLOW = 0x199 KEY_SHUFFLE = 0x19a KEY_BREAK = 0x19b KEY_PREVIOUS = 0x19c KEY_DIGITS = 0x19d KEY_TEEN = 0x19e KEY_TWEN = 0x19f KEY_DEL_EOL = 0x1c0 KEY_DEL_EOS = 0x1c1 KEY_INS_LINE = 0x1c2 KEY_DEL_LINE = 0x1c3 KEY_FN = 0x1d0 KEY_FN_ESC = 0x1d1 KEY_FN_F1 = 0x1d2 KEY_FN_F2 = 0x1d3 KEY_FN_F3 = 0x1d4 KEY_FN_F4 = 0x1d5 KEY_FN_F5 = 0x1d6 KEY_FN_F6 = 0x1d7 KEY_FN_F7 = 0x1d8 KEY_FN_F8 = 0x1d9 KEY_FN_F9 = 0x1da KEY_FN_F10 = 0x1db KEY_FN_F11 = 0x1dc KEY_FN_F12 = 0x1dd KEY_FN_1 = 0x1de KEY_FN_2 = 0x1df KEY_FN_D = 0x1e0 KEY_FN_E = 0x1e1 KEY_FN_F = 0x1e2 KEY_FN_S = 0x1e3 KEY_FN_B = 0x1e4 KEY_MAX = 0x1ff # Relative axes REL_X = 0x00 REL_Y = 0x01 REL_Z = 0x02 REL_RX = 0x03 REL_RY = 0x04 REL_RZ = 0x05 REL_HWHEEL = 0x06 REL_DIAL = 0x07 REL_WHEEL = 0x08 REL_MISC = 0x09 REL_MAX = 0x0f # Absolute axes ABS_X = 0x00 ABS_Y = 0x01 ABS_Z = 0x02 ABS_RX = 0x03 ABS_RY = 0x04 ABS_RZ = 0x05 ABS_THROTTLE = 0x06 ABS_RUDDER = 0x07 ABS_WHEEL = 0x08 ABS_GAS = 0x09 ABS_BRAKE = 0x0a ABS_HAT0X = 0x10 ABS_HAT0Y = 0x11 ABS_HAT1X = 0x12 ABS_HAT1Y = 0x13 ABS_HAT2X = 0x14 ABS_HAT2Y = 0x15 ABS_HAT3X = 0x16 ABS_HAT3Y = 0x17 ABS_PRESSURE = 0x18 ABS_DISTANCE = 0x19 ABS_TILT_X = 0x1a ABS_TILT_Y = 0x1b ABS_TOOL_WIDTH = 0x1c ABS_VOLUME = 0x20 ABS_MISC = 0x28 ABS_MAX = 0x3f # Misc events MSC_SERIAL = 0x00 MSC_PULSELED = 0x01 MSC_GESTURE = 0x02 MSC_RAW = 0x03 MSC_SCAN = 0x04 MSC_MAX = 0x07 # LEDs LED_NUML = 0x00 LED_CAPSL = 0x01 LED_SCROLLL = 0x02 LED_COMPOSE = 0x03 LED_KANA = 0x04 LED_SLEEP = 0x05 LED_SUSPEND = 0x06 LED_MUTE = 0x07 LED_MISC = 0x08 LED_MAIL = 0x09 LED_CHARGING = 0x0a LED_MAX = 0x0f # Autorepeat values REP_DELAY = 0x00 REP_PERIOD = 0x01 REP_MAX = 0x01 # Sounds SND_CLICK = 0x00 SND_BELL = 0x01 SND_TONE = 0x02 SND_MAX = 0x07 # IDs. ID_BUS = 0 ID_VENDOR = 1 ID_PRODUCT = 2 ID_VERSION = 3 BUS_PCI = 0x01 BUS_ISAPNP = 0x02 BUS_USB = 0x03 BUS_HIL = 0x04 BUS_BLUETOOTH = 0x05 BUS_ISA = 0x10 BUS_I8042 = 0x11 BUS_XTKBD = 0x12 BUS_RS232 = 0x13 BUS_GAMEPORT = 0x14 BUS_PARPORT = 0x15 BUS_AMIGA = 0x16 BUS_ADB = 0x17 BUS_I2C = 0x18 BUS_HOST = 0x19 # Values describing the status of an effect FF_STATUS_STOPPED = 0x00 FF_STATUS_PLAYING = 0x01 FF_STATUS_MAX = 0x01 fso-frameworkd-0.10.1/framework/subsystems/oeventsd/000077500000000000000000000000001174525413000225665ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/oeventsd/__init__.py000066400000000000000000000004241174525413000246770ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ The freesmartphone Events Module - Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Jan 'Shoragan' Lübbe (C) 2008 Guillaume 'Charlie' Chereau (C) 2008 Openmoko, Inc. GPLv2 or later """ fso-frameworkd-0.10.1/framework/subsystems/oeventsd/action.py000066400000000000000000000135221174525413000244200ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ The freesmartphone Events Module - Python Implementation (C) 2008 Jan 'Shoragan' Lübbe (C) 2008 Guillaume 'Charlie' Chereau (C) 2008 Openmoko, Inc. GPLv2 or later Package: oeventsd Module: action """ __version__ = "0.2.1" MODULE_NAME = "oeventsd.action" from parser import AutoFunction from framework.patterns import decorator, dbuscache import Queue import logging logger = logging.getLogger( MODULE_NAME ) #============================================================================# class Action(AutoFunction): #============================================================================# """ Base class for Action objects An action has a `trigger` and `untrigger` methods. By default the untrigger method will do nothing, but if it make sense, the action should define it. """ # # public # # Fixme: the outside to inside evaluation unnecessarily # calls theese, consuming only time def __init__( self ): AutoFunction.__init__( self ) logger.debug("UnamedAction : init") def trigger( self, **kwargs ): logger.debug("UnamedAction : trigger") pass def untrigger( self, **kwargs ): logger.debug("UnamedAction : untrigger") pass def __repr__( self ): return "unamed action" #============================================================================# class ListAction(list, Action): #============================================================================# """ An action that will trigger a sequence of actions This is basically a script. """ def __init__( self, actions ): list.__init__( self, actions ) Action.__init__( self ) def trigger( self, **kwargs ): for action in self: action.trigger( **kwargs ) def untrigger( self, **kwargs ): for action in self: action.untrigger( **kwargs ) #============================================================================# class DebugAction(Action): #============================================================================# """ A special action for debugging purposes """ function_name = 'Debug' def __init__(self, msg): Action.__init__( self ) self.msg = msg def trigger(self, **kargs): logger.info("DebugAction : %s", self.msg) def __repr__(self): return "Debug(\"%s\")" % self.msg #============================================================================# class DBusAction(Action): #============================================================================# """ A special action that will call a DBus method. """ def __init__(self, bus, service, obj, interface, method, *args): Action.__init__( self ) self.bus = bus # some arguments checking assert isinstance(service, str) assert isinstance(obj, str) assert isinstance(interface, str) assert isinstance(method, str) self.bus = bus self.service = service self.obj = obj self.interface = interface self.method = method self.args = args def trigger(self, **kargs): iface = dbuscache.dbusInterfaceForObjectWithInterface( self.service, self.obj, self.interface ) logger.info("call dbus method %s %s(%s)", self.obj, self.method, self.args) # Get the method method = getattr(iface, self.method) # We make the call asynchronous, cause we don't want to block the main loop method( *self.args, **dict( reply_handler=self.on_reply, error_handler=self.on_error ) ) logger.debug( "method called..." ) def on_reply(self, *args): # We don't pass the reply to anything logger.info("method %s responded: %s", self.method, args) def on_error(self, error): logger.error("method %s emited error: %s", self.method, error) def __repr__(self): return "%s(%s)" % (self.method, self.args) #=========================================================================# class PeekholeQueue( Queue.Queue ): #=========================================================================# """ This class extends the Queue with a method to peek at the first element without having to remove this from the queue. """ def peek( self ): if self.empty(): return None else: return self.queue[0] #============================================================================# class QueuedDBusAction( DBusAction ): #============================================================================# q = PeekholeQueue() def enqueue( self, method, args, kargs ): logger.debug( "enqueing dbus call %s.%s", method, args ) relaunch = ( self.q.qsize() == 0 ) self.q.put( ( method, args, kargs ) ) if relaunch: self.workDaQueue() def workDaQueue( self ): logger.debug( "working on queue: %s", self.q ) if self.q.qsize(): method, args, kargs = self.q.peek() # async dbus call now method( *args, **kargs ) def trigger( self, **kargs ): iface = dbuscache.dbusInterfaceForObjectWithInterface( self.service, self.obj, self.interface ) logger.info("queued call dbus method %s %s(%s)", self.obj, self.method, self.args) method = getattr(iface, self.method) self.enqueue( method, self.args, dict( reply_handler=self.on_reply, error_handler=self.on_error ) ) logger.debug( "method enqueued..." ) def on_reply(self, *args): # We don't pass the reply to anything logger.info("signal %s responded : %s", self.method, args) self.q.get() self.workDaQueue() def on_error(self, error): logger.error("signal %s emited an error %s", self.method, error) self.q.get() self.workDaQueue() fso-frameworkd-0.10.1/framework/subsystems/oeventsd/filter.py000066400000000000000000000114441174525413000244310ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ The freesmartphone Events Module - Python Implementation (C) 2008 Jan 'Shoragan' Lübbe (C) 2008 Guillaume 'Charlie' Chereau (C) 2008 Openmoko, Inc. GPLv2 or later Package: oeventsd Module: filter """ __version__ = "0.2.0" MODULE_NAME = "oeventsd.filter" import logging logger = logging.getLogger( MODULE_NAME ) #============================================================================# class Filter( object ): #============================================================================# """Base class for every filter A filter is used after a rule has been triggered to decide if the actions will be called or not. When a rule is triggered, the trigger generate a dict of values, that can be later used by the filter. All the filters need to implement the filter method, taking an arbitrary number of keywords argument (**kargs) representing the event generated dict of values. The method returns True if the filter accept the event, False otherwise. """ def __init__( self, *args, **kwargs ): pass def filter( self, **kargs ): # The default filter is always True # Fixme: unnecessary and time consuming call due to outside to inside evaluations return True def __invert__( self ): """Return the inverted filter of this filter The __invert__ method is called by the `~` operator. """ return InvertFilter( self ) def __or__( self, f ): """Return a filter that is the logical OR operation between this filter and an other filter """ return OrFilter( self, f ) def __and__( self, f ): return AndFilter( self, f ) def enable( self ): """enable the filter This is used because some filter need to connect to external signals, e.g : WhileRule """ pass def disable( self ): """disable the filter""" pass def __repr__( self ): return "base filter" #============================================================================# class AttributeFilter( Filter ): #============================================================================# """This filter is True if all the keywords argument are present in the call and have the given value """ def __init__( self, **kargs ): Filter.__init__( self ) self.kargs = kargs def filter( self, **kargs ): return all( key in kargs and kargs[key] == value for (key, value) in self.kargs.items() ) def __repr__( self ): return "and".join( "%s == %s" % (key, value) for (key, value) in self.kargs.items() ) #============================================================================# class InvertFilter( Filter ): #============================================================================# """This filer returns the negation of the argument filter""" def __init__( self, filter ): Filter.__init__( self ) self.__filter = filter def filter( self, **kargs ): return not self.__filter.filter( **kargs ) def enable( self ): self.__filter.enable() def disable( self ): self.__filter.disable() def __repr__( self ): return "~(%s)" % self.__filter #============================================================================# class AndFilter( Filter ): #============================================================================# """This filter returns the AND logical operation between a list of filters""" def __init__( self, *filters ): Filter.__init__( self ) assert all( isinstance( f, Filter ) for f in filters ) self.filters = filters def filter( self, **kargs ): return all( f.filter( **kargs ) for f in self.filters ) def enable( self ): for f in self.filters: f.enable() def disable( self ): for f in self.filters: f.disable() def __repr__( self ): return "And(%s)" % ','.join( str( f ) for f in self.filters ) #============================================================================# class OrFilter( Filter ): #============================================================================# """This filter returns the OR logical operation between a list of filters""" def __init__( self, *filters ): Filter.__init__( self ) assert all( isinstance( f, Filter ) for f in filters ) self.filters = filters def filter( self, **kargs ): return any( f.filter( **kargs ) for f in self.filters ) def enable( self ): for f in self.filters: f.enable() def disable( self ): for f in self.filters: f.disable() def __repr__( self ): return "Or(%s)" % ','.join( str( f ) for f in self.filters ) fso-frameworkd-0.10.1/framework/subsystems/oeventsd/fso_actions.py000066400000000000000000000511171174525413000254540ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ The freesmartphone Events Module - Python Implementation (C) 2008-2009 Michael 'Mickey' Lauer (C) 2008 Jan 'Shoragan' Lübbe (C) 2008 Guillaume 'Charlie' Chereau (C) 2008 Openmoko, Inc. GPLv2 or later Package: oeventsd Module: fso_actions """ __VERSION__ = "0.5.1.1" MODULE_NAME = "oeventsd" import framework.patterns.tasklet as tasklet from action import Action from action import QueuedDBusAction, DBusAction from framework.controller import Controller from framework.config import installprefix from framework.patterns import dbuscache import gobject import dbus import os, subprocess, shlex import logging logger = logging.getLogger( MODULE_NAME ) #============================================================================# class SetProfile( Action ): #============================================================================# function_name = 'SetProfile' def __init__( self, profile ): self.profile = profile @tasklet.tasklet def __trigger( self ): prefs = dbuscache.dbusInterfaceForObjectWithInterface( "org.freesmartphone.opreferencesd", "/org/freesmartphone/Preferences", "org.freesmartphone.Preferences" ) # First, store the current profile # FIXME: we should use profile push and pop instead self.backup_profile = yield tasklet.WaitDBus( prefs.GetProfile ) # Then we can set the profile yield tasklet.WaitDBus( prefs.SetProfile, self.profile ) def trigger( self, **kargs ): self.__trigger().start() def untrigger( self, **kargs ): # FIXME: how do we handle the case where we untrigger the action # before we finish the trigger tasklet ? SetProfile( self.backup_profile ).trigger() def __repr__( self ): return "SetProfile(%s)" % self.profile #============================================================================# class AudioAction(Action): #============================================================================# """ A dbus action on the freesmartphone audio device """ def __init__(self, path, loop=0, length=0): super(AudioAction, self).__init__() self.path = path self.loop = loop self.length = length def trigger(self, **kargs): QueuedDBusAction( dbus.SystemBus(), 'org.freesmartphone.odeviced', '/org/freesmartphone/Device/Audio', 'org.freesmartphone.Device.Audio', 'PlaySound', self.path, self.loop, self.length).trigger() def untrigger(self, **kargs): QueuedDBusAction( dbus.SystemBus(), 'org.freesmartphone.odeviced', '/org/freesmartphone/Device/Audio', 'org.freesmartphone.Device.Audio', 'StopSound', self.path).trigger() #============================================================================# class SetAudioScenarioAction(DBusAction): #============================================================================# """ A dbus action on the freesmartphone audio device """ def __init__(self, scenario = None, action = 'set' ): bus = dbus.SystemBus() service = 'org.freesmartphone.odeviced' obj = '/org/freesmartphone/Device/Audio' interface = 'org.freesmartphone.Device.Audio' if action == 'set': # FIXME gsmhandset ugly ugly hardcoded super(SetAudioScenarioAction, self).__init__(bus, service, obj, interface, "SetScenario", scenario) else: logger.error( "unhandled action '%s' for Audio scenario" % action ) #============================================================================# class AudioScenarioAction(Action): #============================================================================# function_name = 'SetScenario' def __init__(self, scenario): self.scenario = scenario def trigger(self, **kargs): logger.info("Set Audio Scenario %s", self.scenario) # TODO: retreive the current scenario so that we can use it when we reset the scenario self.backup_scenario = 'stereoout' SetAudioScenarioAction(self.scenario).trigger() def untrigger(self, **kargs): logger.info("Revert Audio Scenario to %s", self.backup_scenario) SetAudioScenarioAction(self.backup_scenario).trigger() def __repr__(self): return "SetScenario(%s)" % self.scenario #============================================================================# class DisplayBrightnessAction(DBusAction): #============================================================================# """ A dbus action on a Display device """ # FIXME: device specific, needs to go away from here / made generic (parametric? just take the first?) function_name = 'SetDisplayBrightness' def __init__(self, target, brightness): bus = dbus.SystemBus() service = 'org.freesmartphone.odeviced' obj = '/org/freesmartphone/Device/Display/%s' % target interface = 'org.freesmartphone.Device.Display' super(DisplayBrightnessAction, self).__init__(bus, service, obj, interface, 'SetBrightness', brightness) #============================================================================# class VibratorAction(Action): #============================================================================# """ A dbus action on the Openmoko Neo Vibrator device """ function_name = 'Vibration' # FIXME: ATM it just takes the first and runs at full power, should fix. def __init__(self, target = 0, mode = "continuous"): self.mode = mode self.target = target def trigger(self, **kargs): if self.mode == "continuous": DBusAction(dbus.SystemBus(), 'org.freesmartphone.odeviced', '/org/freesmartphone/Device/Vibrator/%s' % self.target, 'org.freesmartphone.Device.Vibrator', 'VibratePattern', 999, 300, 700, 90).trigger() elif self.mode == "oneshot": DBusAction(dbus.SystemBus(), 'org.freesmartphone.odeviced', '/org/freesmartphone/Device/Vibrator/%s' % self.target, 'org.freesmartphone.Device.Vibrator', 'Vibrate', 400, 90).trigger() else: logger.warning( "invalid vibration mode '%s', valid are 'continuous' or 'oneshot'" ) def untrigger(self, **kargs): DBusAction(dbus.SystemBus(), 'org.freesmartphone.odeviced', '/org/freesmartphone/Device/Vibrator/%s' % self.target, 'org.freesmartphone.Device.Vibrator', 'Stop').trigger() #============================================================================# class BTHeadsetPlayingAction(Action): #============================================================================# """ A dbus action on the Bluetooth Headset API """ function_name = 'BTHeadsetPlaying' def __init__(self): pass def trigger(self, **kargs): DBusAction(dbus.SystemBus(), 'org.freesmartphone.ophoned', '/org/freesmartphone/Phone', 'org.freesmartphone.Phone', 'SetBTHeadsetPlaying', True).trigger() def untrigger(self, **kargs): DBusAction(dbus.SystemBus(), 'org.freesmartphone.ophoned', '/org/freesmartphone/Phone', 'org.freesmartphone.Phone', 'SetBTHeadsetPlaying', False).trigger() def __repr__(self): return "BTHeadsetPlaying()" #============================================================================# class OccupyResourceAction(Action): #============================================================================# function_name = 'OccupyResource' usageiface = None pending = {} held = {} @classmethod def onResourceAvailable( cls, name, state ): if state: cls.updatePending( name ) # FIXME handle vanishing resources... @classmethod def updatePending( cls, name ): try: counter = cls.pending[name] except KeyError: pass else: if counter > 0: cls.usageiface.RequestResource( name, reply_handler = lambda cls=cls,name=name: cls.onResourceRequestReply( name ), error_handler = lambda cls=cls,name=name, e=None: cls.onResourceRequestError( name, e ) ) else: cls.usageiface.ReleaseResource( name, reply_handler = lambda cls=cls,name=name: cls.onResourceReleaseReply( name ), error_handler = lambda cls=cls,name=name, e=None: cls.onResourceReleaseError( name, e ) ) @classmethod def onResourceRequestReply( cls, name ): logger.debug( "onResourceRequestReply: %s" % name ) amount = cls.pending[name] if amount > 0: del cls.pending[name] cls.held[name] = amount @classmethod def onResourceRequestError( cls, name, e ): logger.debug( "onResourceRequestError: %s: %s" % ( name, e ) ) @classmethod def onResourceReleaseReply( cls, name ): logger.debug( "onResourceReleaseReply: %s" % name ) del cls.pending[name] @classmethod def onResourceReleaseError( cls, name, e ): logger.debug( "onResourceReleaseError: %s: %s" % ( name, e ) ) def __init__( self, resource ): self.resource = resource if self.usageiface is None: self.__class__.usageiface = dbuscache.dbusInterfaceForObjectWithInterface( \ "org.freesmartphone.ousaged", "/org/freesmartphone/Usage", "org.freesmartphone.Usage" ) self.__class__.usageiface.connect_to_signal( "ResourceAvailable", self.__class__.onResourceAvailable ) def __repr__( self ): return "OccupyResource(%s)" % ( self.resource ) def trigger( self, **kargs ): if self.resource in self.__class__.pending: self.__class__.pending[self.resource] = self.__class__.pending[self.resource] + 1 elif self.resource in self.__class__.held: self.__class__.held[self.resource] = self.__class__.held[self.resource] + 1 else: self.__class__.pending[self.resource] = 1 self.__class__.updatePending( self.resource ) def untrigger( self, **kargs ): if self.resource in self.__class__.pending: counter = self.__class__.pending[self.resource] if counter > 1: self.__class__.pending[self.resource] = counter - 1 else: self.__class__.pending[self.resource] = 0 self.__class__.updatePending( self.resource ) elif self.resource in self.__class__.held: counter = self.__class__.held[self.resource] if counter > 1: self.__class__.held[self.resource] = counter - 1 else: del self.__class__.held[self.resource] self.__class__.pending[self.resource] = 0 self.__class__.updatePending( self.resource ) else: logger.error( "Untrigger for resource %s before trigger!? Ignoring..." % self.resource ) #=========================================================================# class UserAlertAction(Action): #=========================================================================# function_name = '___abstract_dont_use_this_directly____' def __init__( self, *args, **kwargs ): Action.__init__( self ) logger.debug( "%s: init", self ) self.audio_action = None self.vibrator_action = None self.eventname = self.__class__.event_name # needs to be populated in derived classes self.vibratormode = self.__class__.vibrator_mode # dito gobject.idle_add( self.initFromMainloop ) def initFromMainloop( self ): self.__init().start() return False # mainloop: don't call me again def cbPreferencesServiceNotify( self, key, value ): """ Audio preferences have changed. Reload. """ k = str(key) if k == "%s-tone" % self.eventname: self.tone = str(value) elif k == "%s-volume" % self.eventname: self.volume = int(value) elif k == "%s-loop" % self.eventname: self.loop = int(value) elif k == "%s-length" % self.eventname: self.length = int(value) elif k == "%s-vibration" % self.eventname: self.vibrate = int(value) self.sound_path = os.path.join( installprefix, "share/sounds/", self.tone ) self.audio_action = AudioAction( self.sound_path, self.loop, self.length ) if self.volume != 0 else None self.vibrator_action = VibratorAction( mode=self.vibratormode ) if self.vibrate != 0 else None logger.debug( "user alert action changed for %s: audio=%s, vibrator=%s", self.eventname, self.audio_action, self.vibrator_action ) @tasklet.tasklet def __init( self ): """ We need to make DBus calls and wait for the result, So we use a tasklet to avoid blocking the mainloop. """ logger.debug( "user alert action init from mainloop for %s", self.eventname ) # We get the 'phone' preferences service and # retreive the tone and volume config values # We are careful to use 'yield' cause the calls could be blocking. try: prefs = dbuscache.dbusInterfaceForObjectWithInterface( "org.freesmartphone.opreferencesd", "/org/freesmartphone/Preferences", "org.freesmartphone.Preferences" ) phone_prefs = yield tasklet.WaitDBus( prefs.GetService, "phone" ) phone_prefs = dbuscache.dbusInterfaceForObjectWithInterface( "org.freesmartphone.opreferencesd", phone_prefs, "org.freesmartphone.Preferences.Service" ) except dbus.DBusException: # preferences daemon probably not present logger.warning( "org.freesmartphone.opreferencesd not present. Can't get alert tones." ) else: # connect to signal for later notifications phone_prefs.connect_to_signal( "Notify", self.cbPreferencesServiceNotify ) # FIXME does that still work if (some of) the entries are missing? self.tone = yield tasklet.WaitDBus( phone_prefs.GetValue, "%s-tone" % self.eventname ) self.volume = yield tasklet.WaitDBus( phone_prefs.GetValue, "%s-volume" % self.eventname ) self.loop = yield tasklet.WaitDBus( phone_prefs.GetValue, "%s-loop" % self.eventname ) self.length = yield tasklet.WaitDBus( phone_prefs.GetValue, "%s-length" % self.eventname ) self.vibrate = yield tasklet.WaitDBus( phone_prefs.GetValue, "%s-vibration" % self.eventname ) self.tone = str(self.tone) self.volume = int(self.volume) self.loop = int(self.loop) self.length = int(self.length) self.vibrate = int(self.vibrate) self.sound_path = os.path.join( installprefix, "share/sounds/", self.tone ) self.audio_action = AudioAction( self.sound_path, self.loop, self.length ) if self.volume != 0 else None self.vibrator_action = VibratorAction( mode=self.vibratormode ) if self.vibrate != 0 else None logger.debug( "user alert action for %s: audio=%s, vibrator=%s", self.eventname, self.audio_action, self.vibrator_action ) def trigger(self, **kargs): logger.info( "UserAlertAction %s play", self.eventname ) if self.audio_action is not None: self.audio_action.trigger() if self.vibrator_action is not None: self.vibrator_action.trigger() def untrigger(self, **kargs): logger.info( "UserAlertAction %s stop", self.eventname ) if self.audio_action is not None: self.audio_action.untrigger() if self.vibrator_action is not None: self.vibrator_action.untrigger() def __repr__(self): return "Abstract UserAlertAction()" #=========================================================================# class RingToneAction(UserAlertAction): #=========================================================================# function_name = 'RingTone' event_name = "ring" vibrator_mode = "continuous" def __repr__(self): return "RingToneAction()" #=========================================================================# class MessageToneAction(UserAlertAction): #=========================================================================# function_name = 'MessageTone' event_name = "message" vibrator_mode = "oneshot" def __repr__(self): return "MessageToneAction()" #=========================================================================# class CommandAction(Action): #=========================================================================# function_name = 'Command' def __init__( self, cmd = 'true', env = {} ): self.cmd = cmd self.env = env def trigger(self, **kargs): logger.info( "CommandAction %s", self.cmd ) env = {} env.update( os.environ ) env.update( self.env ) # FIXME if we are interested in tracking the process, then we # should use glib's spawn async and add a child watch try: subprocess.call( shlex.split( self.cmd ), env = env ) except Exception, e: logger.error( "Error while executing external command '%s': %s", self.cmd, e ) def __repr__(self): return "CommandAction(%s, %s)" % ( self.cmd, self.env ) #============================================================================# class SuspendAction(DBusAction): #============================================================================# """ A dbus action to supend the device """ function_name = 'Suspend' def __init__(self): bus = dbus.SystemBus() service = 'org.freesmartphone.ousaged' obj = '/org/freesmartphone/Usage' interface = 'org.freesmartphone.Usage' super(SuspendAction, self).__init__(bus, service, obj, interface, 'Suspend') #============================================================================# class ShutdownAction(DBusAction): #============================================================================# """ A dbus action to shutdown the device """ function_name = 'Shutdown' def __init__(self): bus = dbus.SystemBus() service = 'org.freesmartphone.ousaged' obj = '/org/freesmartphone/Usage' interface = 'org.freesmartphone.Usage' super(ShutdownAction, self).__init__(bus, service, obj, interface, 'Shutdown') #============================================================================# class ExternalDBusAction(DBusAction): #============================================================================# function_name = "ExternalDBusAction" """ A flexible dbus action """ def __init__(self, bus, service, obj, interface, method, *args): """Create the DBus action arguments: - bus the DBus bus name (or a string : 'system' | 'session') - service the DBus name of the service - obj the DBus path of the object - interface the Dbus interface of the signal - method the DBus name of the method - args the arguments for the method """ # some arguments checking if isinstance(bus, str): if bus == 'system': bus = dbus.SystemBus() elif bus == 'session': bus = dbus.SessionBus() else: raise TypeError("Bad dbus bus : %s" % bus) if not obj: obj = None assert isinstance(service, str), "service is not str" assert obj is None or isinstance(obj, str), "obj is not str or None" assert isinstance(interface, str), "interface is not str" assert isinstance(signal, str), "signal is not str" super(ExternalDBusAction, self).__init__(bus, service, obj, interface, method, *args) def __repr__( self ): return "ExternalDBusAction(bus = %s, service = %s, obj = %s, itf = %s, method = %s)" % (self.bus, self.service, self.obj, self.interface, self.method) #============================================================================# if __name__ == "__main__": #============================================================================# pass fso-frameworkd-0.10.1/framework/subsystems/oeventsd/fso_triggers.py000066400000000000000000000347311174525413000256450ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ The freesmartphone Events Module - Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Jan 'Shoragan' Lübbe (C) 2008 Guillaume 'Charlie' Chereau (C) 2008 Openmoko, Inc. GPLv2 or later Package: oeventsd Module: fso_triggers """ from trigger import DBusTrigger from rule import WhileRule import dbus import logging logger = logging.getLogger('oeventsd.fso_triggers') #============================================================================# class CallStatusTrigger(DBusTrigger): #============================================================================# """Just a sugar trigger for a GSM call status change""" function_name = 'CallStatus' def __init__(self): bus = dbus.SystemBus() super(CallStatusTrigger, self).__init__( bus, 'org.freesmartphone.ogsmd', '/org/freesmartphone/GSM/Device', 'org.freesmartphone.GSM.Call', 'CallStatus' ) def on_signal(self, id, status, properties): logger.info("Receive CallStatus, status = %s", status) self._trigger(id=id, status=status, properties=properties) def __repr__(self): return "CallStatus" # TODO: Maybe this should be an instance of a special class (Condition ?) #============================================================================# class CallListContains(WhileRule): #============================================================================# """This rule keep track of all the calls, and can be used to check for a given status in one of them""" function_name = "CallListContains" def __init__(self, status): self.status = status self.calls = {} # The list of current call object super(CallListContains, self).__init__(CallStatusTrigger()) def trigger(self, id=None, status=None, properties=None, **kargs): logger.debug("Trigger %s", self) self.calls[id] = status if ( self.status in self.calls.values() ) or ( self.status.lower() in self.calls.values() ): super(CallListContains, self).trigger() else: super(CallListContains, self).untrigger() # needed to fix Bug #2305 # The filter functions need a patch, because they evaluate # from outside to inside, that would not corerctly evaluate terms like # Or(CallListContains("outgoing"), CallListContains("active")) # because the CallListContains status gets updates after Or was called def filter( self, id=None, status=None, properties=None, **kargs ): """The filter is True if the rule is triggered""" self.trigger(id, status, properties, **kargs) return self.triggered def __repr__(self): return "CallListContains(%s)" % self.status #============================================================================# class BTHeadsetConnectedTrigger(DBusTrigger): #============================================================================# function_name = 'BTHeadsetConnected' def __init__(self): bus = dbus.SystemBus() super(BTHeadsetConnectedTrigger, self).__init__( bus, 'org.freesmartphone.ophoned', '/org/freesmartphone/Phone', 'org.freesmartphone.Phone', 'BTHeadsetConnected' ) def on_signal(self, status): logger.info("Receive BTConnectedEnabled, status = %s", status) self._trigger(status=status) def __repr__(self): return "BTHeadsetConnected" #============================================================================# class BTHeadsetIsConnected(WhileRule): #============================================================================# function_name = "BTHeadsetIsConnected" def __init__(self): super(BTHeadsetIsConnected, self).__init__(BTHeadsetConnectedTrigger()) def trigger(self, status=None, **kargs): logger.debug("Trigger %s", self) if status: super(BTHeadsetIsConnected, self).trigger() else: super(BTHeadsetIsConnected, self).untrigger() def __repr__(self): return "BTHeadsetIsConnected()" #============================================================================# class NewMissedCallsTrigger(DBusTrigger): #============================================================================# """ A custom dbus trigger for org.freesmartphone.PIM.Calls.NewMissedCalls """ function_name = 'NewMissedCallsTrigger' def __init__(self): bus = dbus.SystemBus() super(NewMissedCallsTrigger, self).__init__( bus, 'org.freesmartphone.opimd', '/org/freesmartphone/PIM/Calls', 'org.freesmartphone.PIM.Calls', 'NewMissedCalls' ) def on_signal(self, status): logger.info("Receive NewMissedCalls = %s" % status) self._trigger(status=status) def __repr__(self): return "NewMissedCallsTrigger" #============================================================================# class NewMissedCalls(WhileRule): #============================================================================# function_name = "NewMissedCalls" def __init__(self): super(NewMissedCalls, self).__init__(NewMissedCallsTrigger()) def trigger(self, status=None, **kargs): logger.debug("Trigger %s", self) logger.info("NewMissedCalls %d", status) if status: super(NewMissedCalls, self).trigger() else: super(NewMissedCalls, self).untrigger() def __repr__(self): return "NewMissedCalls()" #============================================================================# class UnreadMessagesTrigger(DBusTrigger): #============================================================================# """ A custom dbus trigger for org.freesmartphone.PIM.Messages.UnreadMessages """ function_name = 'UnreadMessagesTrigger' def __init__(self): bus = dbus.SystemBus() super(UnreadMessagesTrigger, self).__init__( bus, 'org.freesmartphone.opimd', '/org/freesmartphone/PIM/Messages', 'org.freesmartphone.PIM.Messages', 'UnreadMessages' ) def on_signal(self, status): logger.info("Receive UnreadMessages = %s" % status) self._trigger(status=status) def __repr__(self): return "UnreadMessagesTrigger" #============================================================================# class UnreadMessages(WhileRule): #============================================================================# function_name = "UnreadMessages" def __init__(self): super(UnreadMessages, self).__init__(UnreadMessagesTrigger()) def trigger(self, status=None, **kargs): logger.debug("Trigger %s", self) logger.info("UnreadMessages %d", status) if status: super(UnreadMessages, self).trigger() else: super(UnreadMessages, self).untrigger() def __repr__(self): return "UnreadMessages()" #============================================================================# class IncomingMessageTrigger(DBusTrigger): #============================================================================# """ A custom dbus trigger for org.freesmartphone.GSM.SIM.IncomingStoredMessage TODO: change to opimd interface """ function_name = 'IncomingMessage' def __init__(self): bus = dbus.SystemBus() super(IncomingMessageTrigger, self).__init__( bus, 'org.freesmartphone.ogsmd', '/org/freesmartphone/GSM/Device', 'org.freesmartphone.GSM.SIM', 'IncomingStoredMessage' ) def on_signal(self, index): logger.info("Receive IncomingMessage on index = %s" % index) self._trigger(index=index) def __repr__(self): return "IncomingMessage" #============================================================================# class IncomingUssdTrigger(DBusTrigger): #============================================================================# """ A custom dbus trigger for org.freesmartphone.GSM.Network.IncomingUssd """ function_name = 'IncomingUssd' def __init__(self): bus = dbus.SystemBus() super(IncomingUssdTrigger, self).__init__( bus, 'org.freesmartphone.ogsmd', '/org/freesmartphone/GSM/Device', 'org.freesmartphone.GSM.Network', 'IncomingUssd' ) def on_signal(self, status, content): logger.info("Receive IncomingUssd with status = %s" % status) self._trigger(status=status, content=content) def __repr__(self): return "IncomingUssd" #============================================================================# class PowerStatusTrigger(DBusTrigger): #============================================================================# """ A dbus trigger for org.freesmartphone.Device.PowerSupply.PowerStatus """ function_name = 'PowerStatus' def __init__(self): bus = dbus.SystemBus() super(PowerStatusTrigger, self).__init__( bus, 'org.freesmartphone.odeviced', None, 'org.freesmartphone.Device.PowerSupply', 'PowerStatus' ) def on_signal(self, status): logger.info("Receive PowerStatus, status = %s", status) self._trigger(status=status) def __repr__(self): return "PowerStatus" #============================================================================# class IdleStateTrigger(DBusTrigger): #============================================================================# """ A dbus trigger for org.freesmartphone.Device.IdleNotifier.State """ function_name = 'IdleState' def __init__(self): bus = dbus.SystemBus() super(IdleStateTrigger, self).__init__( bus, 'org.freesmartphone.odeviced', None, 'org.freesmartphone.Device.IdleNotifier', 'State' ) def on_signal(self, status): logger.info("Receive IdleState, status = %s", status) self._trigger(status=status) def __repr__(self): return "IdleState" #============================================================================# class TimeTrigger(DBusTrigger): #============================================================================# """ A dbus trigger for org.freesmartphone.Time.Minute """ function_name = 'Time' def __init__(self, hour, minute): self.hour = hour self.minute = minute bus = dbus.SystemBus() super(TimeTrigger, self).__init__( bus, 'org.freesmartphone.otimed', '/org/freesmartphone/Time', 'org.freesmartphone.Time', 'Minute' ) def on_signal(self, year, mon, day, hour, min, sec, wday, yday, isdst): if self.hour == hour and self.minute == min: logger.debug("%s triggered", self) self._trigger() def __repr__(self): return "Time(%d:%d)" % (self.hour, self.minute) #============================================================================# class InputTrigger(DBusTrigger): #============================================================================# """ A dbus trigger for org.freesmartphone.Input.Event """ function_name = 'InputEvent' def __init__(self): bus = dbus.SystemBus() super(InputTrigger, self).__init__( bus, 'org.freesmartphone.odeviced', '/org/freesmartphone/Device/Input', 'org.freesmartphone.Device.Input', 'Event' ) def on_signal(self, switch, event, duration): logger.debug("%s triggered", self) self._trigger(switch=switch, event=event, duration=duration) def __repr__(self): return "InputTrigger" #============================================================================# class KeypadTrigger(DBusTrigger): #============================================================================# """ A dbus trigger for org.freesmartphone.GSM.Device.KeypadEvent """ function_name = 'KeypadEvent' def __init__(self): bus = dbus.SystemBus() super(KeypadTrigger, self).__init__( bus, 'org.freesmartphone.ogsmd', '/org/freesmartphone/GSM/Device', 'org.freesmartphone.GSM.Device', 'KeypadEvent' ) def on_signal(self, name, pressed): logger.debug("%s triggered", self) self._trigger(name=name, pressed=pressed) def __repr__(self): return "KeypadTrigger" #============================================================================# class BacklightPowerTrigger(DBusTrigger): #============================================================================# """ A dbus trigger for org.freesmartphone.Device.Display.BacklightPower """ function_name = 'BacklightPower' def __init__(self): bus = dbus.SystemBus() super(BacklightPowerTrigger, self).__init__( bus, 'org.freesmartphone.odeviced', None, 'org.freesmartphone.Device.Display', 'BacklightPower' ) def on_signal(self, status): logger.info("Receive BacklightPower, status = %s", status) self._trigger(status=status) def __repr__(self): return "BacklightPower" #============================================================================# class ResourceStateTrigger(DBusTrigger): #============================================================================# """ A dbus trigger for org.freesmartphone.Usage.ResourceChanged """ function_name = 'ResourceState' def __init__(self): bus = dbus.SystemBus() super(ResourceStateTrigger, self).__init__( bus, 'org.freesmartphone.ousaged', '/org/freesmartphone/Usage', 'org.freesmartphone.Usage', 'ResourceChanged' ) def on_signal(self,resource,state,attributes): power_status = "enabled" if state else "disabled" logger.info("%s is now %s", resource, power_status) self._trigger(resource=resource,power_state=power_status) def __repr__(self): return "ResourceState" fso-frameworkd-0.10.1/framework/subsystems/oeventsd/leds_actions.py000066400000000000000000000073671174525413000256240ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ The freesmartphone Events Module - Python Implementation (C) 2008-2009 Michael 'Mickey' Lauer (C) 2008 Jan 'Shoragan' Lübbe (C) 2008 Guillaume 'Charlie' Chereau (C) 2008 Openmoko, Inc. GPLv2 or later Package: oeventsd Module: fso_actions """ MODULE_NAME = "oeventsd" __version__ = "0.5.0" import dbus from framework.patterns import dbuscache, tasklet from action import Action, DBusAction import logging logger = logging.getLogger( MODULE_NAME ) #============================================================================# class Led(object): #============================================================================# """ Led object This class is used to keep track of every "clients" that wants to use the led. The current status of the led id the higher level of all the clients, the levels being : - dark - blink - light """ # We keep a list of all the leds object maped by device name __leds = {} def __new__(cls, device): """We don't create two leds with the same device""" if device in Led.__leds: return Led.__leds[device] ret = object.__new__(cls) ret.__init(device) return ret def __repr__(self): return self.device def __init(self, device): self.device = device self.interface = dbuscache.dbusInterfaceForObjectWithInterface( "org.freesmartphone.odeviced", "/org/freesmartphone/Device/LED/%s" % device, "org.freesmartphone.Device.LED" ) self.users = {} def on_reply(self, *args): # We don't pass the reply to anything pass def on_error(self, error): logger.error("DBus call returned an error") def __turn_on(self): logger.info("turn led %s on", self) self.interface.SetBrightness(100, reply_handler=self.on_reply, error_handler=self.on_error) def __turn_off(self): logger.info("turn led %s off", self) self.interface.SetBrightness(0, reply_handler=self.on_reply, error_handler=self.on_error) def __blink(self): logger.info("blink led %s", self) self.interface.SetBlinking(100, 1500, reply_handler=self.on_reply, error_handler=self.on_error) def turn_on(self, user): self.users[user] = 'on' self.__update() def turn_off(self, user): if user in self.users: del self.users[user] else: logger.warning("try to turn off led %s before having turing it on", self) self.__update() def blink(self, user): self.users[user] = 'blink' self.__update() def __update(self): status = self.users.values() logger.debug("led %s status = %s", self, status) if 'on' in status: self.__turn_on() elif 'blink' in status: self.__blink() else: self.__turn_off() #============================================================================# class LedAction(Action): #============================================================================# """ A dbus action on a LED device """ function_name = 'SetLed' def __init__(self, device, action): Action.__init__( self ) self.led = Led(device) self.action = action if not action in ['light', 'blink']: logger.error("Unhandeled action on led %s : %s", device, action) def trigger(self, **kargs): if self.action == 'light': self.led.turn_on(self) elif self.action == 'blink': self.led.blink(self) def untrigger(self, **kargs): self.led.turn_off(self) def __repr__(self): return "SetLed(%s, %s)" % (self.led, self.action) fso-frameworkd-0.10.1/framework/subsystems/oeventsd/oevents.py000066400000000000000000000134551174525413000246330ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ The freesmartphone Events Module - Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Jan 'Shoragan' Lübbe (C) 2008 Guillaume 'Charlie' Chereau (C) 2008 Openmoko, Inc. GPLv2 or later Package: oeventsd Module: oevents """ __version__ = "0.3.1" import gobject import dbus import os import logging logger = logging.getLogger('oeventsd') from framework.config import config, rootdir rootdir = os.path.join( rootdir, 'oevents' ) from framework.controller import Controller from action import Action from parser import Parser from trigger import TestTrigger # FIXME: treat custom triggers, actions, and filters as plugins and load them on demand from fso_actions import * from fso_triggers import * from leds_actions import * # TODO: # - Add a way to dynamically remove events # - Add a way to add new events when the event conf file is modified #============================================================================# class EventsManager(dbus.service.Object): #============================================================================# """This is the interface to the event service In prcatice we shouldn't have to use this too much, because the events can be defined into a configuration file. """ def __init__(self, bus): # Those attributes are needed by the framework system self.path = '/org/freesmartphone/Events' self.interface = 'org.freesmartphone.Events' self.bus = bus super(EventsManager, self).__init__(bus, self.path) # The set of rules is empty self.rules = [] logger.info( "%s %s initialized. Serving %s at %s", self.__class__.__name__, __version__, self.interface, self.path ) # We need to update the rule every time the 'preferences/rules/enabled-rules' list is modified bus = dbus.SystemBus() bus.add_signal_receiver( self.on_rules_enabled_modified, 'Notify', 'org.freesmartphone.Preferences.Service', 'org.freesmartphone.opreferencesd', '/org/freesmartphone/Preferences/rules' ) def add_rule(self, rule): """Add a new rule into the event manager""" self.rules.append(rule) def on_rules_enabled_modified(self, *args): self.update() def update(self): """Enable the rules that need to be""" logger.info("Updating the rules") # First we need to get the 'enabled-rules' value from the 'rules' preference service try: prefs = Controller.object( "/org/freesmartphone/Preferences" ) except KeyError: # preferences service not online logger.warning( "Can't access /org/freesmartphone/Preferences. Rules will be limited." ) # FIXME can we do something (limited) without preferences or not? return False rules_prefs = prefs.GetService( "rules" ) enabled_rules = rules_prefs.GetValue( "enabled-rules" ) enabled_rules = [str(x) for x in enabled_rules] for rule in self.rules: if rule.name in enabled_rules: rule.enable() else: rule.disable() return False @dbus.service.method( "org.freesmartphone.Events" ) def DebugListRules( self, name ): return repr(self.rules) for rule in self.rules: trigger = rule._Rule__trigger if isinstance( trigger, TestTrigger ) and trigger.name == name: if value: trigger._trigger() else: trigger._untrigger() @dbus.service.method( "org.freesmartphone.Events" , in_signature='sb' ) def TriggerTest( self, name, value = True ): """Trigger or untrigger all the 'Test' triggers with matching names This method is only here for testing purpose. :arguments: name : the name of the Test triggers to trigger/untrigger value : True to trigger, False to untrigger """ for rule in self.rules: trigger = rule._Rule__trigger if isinstance( trigger, TestTrigger ) and trigger.name == name: if value: trigger._trigger() else: trigger._untrigger() @dbus.service.method( "org.freesmartphone.Events" , in_signature='s' ) def AddRule( self, rule_str ): """Parse a rule string and add it into the rule list""" rule_str = str( rule_str ) parser = Parser() rule = parser.parse_rule( rule_str ) logger.info( "Add rule %s", rule ) self.add_rule(rule) self.update() @dbus.service.method( "org.freesmartphone.Events" , in_signature='s' ) def RemoveRule( self, name ): """Remove a rule by name""" for rule in self.rules[:]: if rule.name == name: logger.info( "Removing rule %s", name ) self.rules.remove(rule) @dbus.service.method( "org.freesmartphone.Events" ) def ReloadRules( self ): """Reload all rules""" self.update() #============================================================================# def factory(prefix, controller): #============================================================================# """This is the magic function that will be called by the framework module manager""" events_manager = EventsManager(controller.bus) # Get the initial rules files parser = Parser() rules_file = os.path.join( rootdir, 'rules.yaml' ) rules = parser.parse_rules(open(rules_file).read()) for rule in rules: events_manager.add_rule(rule) # This is to ensure that all the other subsystems are up before we update the events_manager gobject.idle_add( events_manager.update ) # Return the dbus object to the framework return [events_manager] fso-frameworkd-0.10.1/framework/subsystems/oeventsd/parser.py000066400000000000000000000157251174525413000244460ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ The freesmartphone Events Module - Python Implementation (C) 2008 Jan 'Shoragan' Lübbe (C) 2008 Guillaume 'Charlie' Chereau (C) 2008 Openmoko, Inc. GPLv2 or later Package: oeventsd Module: parser """ from filter import AttributeFilter import yaml import re import logging logger = logging.getLogger('oeventsd') try: from yaml import CLoader as Loader from yaml import CDumper as Dumper except ImportError: from yaml import Loader, Dumper #============================================================================# class FunctionMetaClass(type): #============================================================================# """The meta class for Function class""" def __init__(cls, name, bases, dict): super(FunctionMetaClass, cls).__init__(name, bases, dict) if 'name' in dict: cls.register(dict['name'], cls()) #============================================================================# class Function(object): #============================================================================# """Base class for all the rules file functions""" __metaclass__ = FunctionMetaClass functions = {} @classmethod def register(cls, name, func): logger.debug("register function %s", name) cls.functions[name] = func def __call__(self, *args): raise NotImplementedError #============================================================================# class AutoFunctionMetaClass(type): #============================================================================# def __init__(cls, name, bases, dict): # If an action has a class attribute : 'function_name', # Then we create a new function of that name that create this action super(AutoFunctionMetaClass, cls).__init__(name, bases, dict) if 'function_name' in dict: def func(*args): try: return cls(*args) except Exception, e: logger.error("Error while calling function %s : %s", dict['function_name'], e) raise Function.register(dict['function_name'], func) class AutoFunction(object): __metaclass__ = AutoFunctionMetaClass def split_params(s): """ An ugly way to parse function parameters I should use a library for that """ if not s: return [] lev = 0 for i in range(len(s)): if s[i] in '([': lev += 1 if s[i] in ')]': lev -= 1 if s[i] == ',' and lev == 0: return [s[:i]] + split_params(s[i+1:]) return [s] # The following is used to be able to parse instructions on yaml # It only works if we don't use CLoader # TODO: if there is really no way to have automatic string interpretation # from python yaml with Cloader, then remove this totaly pattern = re.compile(r'^(.+?)\((.*?)\)$') def function_constructor(loader, node): value = loader.construct_scalar(node) match = pattern.match(value) name = match.group(1) params = split_params(match.group(2)) params = [yaml.load(p, Loader=loader) for p in params] if not name in Function.functions: raise Exception("Function %s not registered" % name) func = Function.functions[name] return func(*params) # This will only works if we don't use CLoader yaml.add_constructor(u'!Function', function_constructor) yaml.add_implicit_resolver(u'!Function', pattern) class Not(Function): name = 'Not' def __call__(self, a): return ~a class Or(Function): name = 'Or' def __call__(self, a, b): return a | b class And(Function): name = 'And' def __call__(self, a, b): return a & b class HasAttr(Function): name = 'HasAttr' def __call__(self, name, value): kargs = {name:value} return AttributeFilter(**kargs) def as_rule(r): """Turn a dictionary into a rule""" from rule import Rule, WhileRule # needs to be here to prevent circular import assert isinstance(r, dict), type(r) # We have to cases of rules : # Those who can be untriggered ('while') # and those who can't ('trigger') while_rule = 'while' in r trigger = r['trigger'] if not while_rule else r['while'] filters = r.get('filters', []) actions = r['actions'] name = r.get('name', "") ret = Rule(trigger, filters, actions, name) if not while_rule else WhileRule(trigger, filters, actions, name) logger.info( "Created new rule : %s", ret ) return ret #============================================================================# class Parser(object): #============================================================================# def __parse(self, value): """replace all function by the actual returned value of the function in structure parsed by yaml This is a hack, it used to work by using yaml.add_implicit_resolver but unfortunately this won't work with CLoader, so we have to parse all the string instead of letting yaml doing it for us. Beside, by using this there is no way to differentiate a string containing parenthesis from a function. """ if isinstance(value, list): return [self.__parse(v) for v in value] if isinstance(value, dict): return dict((k, self.__parse(v)) for k,v in value.iteritems()) if not isinstance(value, basestring): return value match = pattern.match(value) if not match: return value name = match.group(1) params = split_params(match.group(2)) params = [self.__parse(yaml.load(p, Loader=Loader)) for p in params] if not name in Function.functions: raise Exception("Function %s not registered" % name) func = Function.functions[name] return func(*params) def parse_rules(self, src): """Parse a string for a list of rules""" rules = yaml.load(src, Loader=Loader) ret = [] for r in rules: try: r = self.__parse(r) # We should try to clean that... ret.append(as_rule(r)) except Exception, e: logger.error("can't parse rule %s : %s", r, e) return ret def parse_rule(self, src): """Parse a string for a rules""" rule = yaml.load(src, Loader=Loader) rule = self.__parse(rule) try: return as_rule(rule) except Exception, e: logger.error("can't parse rule %s : %s", rule, e) raise if __name__ == '__main__': src = """ - trigger: CallStatus() filters: HasAttr(status, incoming) actions: PlaySound("/usr/share/sounds/Arkanoid_PSID.sid") - trigger: CallStatus() filters: Not(HasAttr(status, incoming)) actions: StopSound("/usr/share/sounds/Arkanoid_PSID.sid") """ parser = Parser() print parser.parse_rules(src) fso-frameworkd-0.10.1/framework/subsystems/oeventsd/rule.py000066400000000000000000000101001174525413000240770ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ The freesmartphone Events Module - Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Jan 'Shoragan' Lübbe (C) 2008 Guillaume 'Charlie' Chereau (C) 2008 Openmoko, Inc. GPLv2 or later """ __version__ = "0.2.0" MODULE_NAME = "oeventsd.rule" import logging logger = logging.getLogger( MODULE_NAME ) from filter import Filter, AndFilter from action import Action, ListAction from trigger import Trigger #============================================================================# class Rule( Trigger, Action ): #============================================================================# """A Rule is a link betwen a trigger and an action, that can check for conditions Using rule we can refine the behavior of a trigger by giving a filter. When the rule is triggered it will trigger the action only if its filter allow it. """ def __init__( self, trigger, filter = Filter(), action = Action(), name = "" ): """Create a new rule given a trigger, a filter and an action We can give a list of action or a list of filter instead of a single action or filter, in that case the actions will be turned into a ListAction and the filters into an AndFilter. """ Trigger.__init__( self ) Action.__init__( self ) # We accept list OR single value as argument if isinstance( filter, list ): filter = AndFilter( *filter ) if isinstance( action, list ): action = ListAction( action ) assert isinstance(trigger, Trigger) assert isinstance(action, Action) assert isinstance(filter, Filter) self.__trigger = trigger # The trigger will call this rule when triggered trigger.connect( self ) self.__filter = filter self.__action = action self.connect( action ) self.name = name def __repr__( self ): if self.name: return "'%s'" % self.name return "on %s if %s then %s" % ( self.__trigger, self.__filter, self.__action ) def trigger( self, **kargs ): # First we check that ALL the filters match the signal if not self.__filter.filter( **kargs ): return False self._trigger( **kargs ) return True def enable( self ): # It should be enough to enable the trigger and the filter logger.info( "enable rule : %s", self ) self.__trigger.enable() self.__filter.enable() def disable( self ): # It should be enough to disable the trigger and the filter logger.info( "disable rule : %s", self ) self.__trigger.disable() self.__filter.disable() #============================================================================# class WhileRule( Rule, Filter ): #============================================================================# """Special Rule that will also untrigger its action We can also use a WhileRule as a filter, the condition is then true if the rule is currently triggered. """ def __init__( self, *args ): Rule.__init__( self, *args ) Filter.__init__( self, *args ) self.triggered = False def trigger( self, **kargs ): if self.triggered: if not self._Rule__filter.filter( **kargs ): self.untrigger( **kargs ) else: self.triggered = Rule.trigger( self, **kargs ) def untrigger( self, **kargs ): if not self.triggered: #being untriggerd while not triggered is not an error return self._untrigger( **kargs ) self.triggered = False def enable( self ): Rule.enable( self ) def disable( self ): Rule.disable( self ) def filter( self, **kargs ): """The filter is True if the rule is triggered""" return self.triggered def __repr__( self ): if self.name: return "'%s'" % self.name return "While %s if %s then %s" % ( self._Rule__trigger, self._Rule__filter, self._Rule__action ) fso-frameworkd-0.10.1/framework/subsystems/oeventsd/trigger.py000066400000000000000000000116601174525413000246070ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ The freesmartphone Events Module - Python Implementation (C) 2008 Guillaume 'Charlie' Chereau (C) 2008 Openmoko, Inc. GPLv2 or later Package: oeventsd Module: trigger """ __version__ = "0.2.0" MODULE_NAME = "oeventsd.trigger" from parser import AutoFunction import dbus import logging logger = logging.getLogger( MODULE_NAME ) #============================================================================# class Trigger(AutoFunction): #============================================================================# """ A trigger is the initial event that can activate a rule. When a trigger is activated, it call the rule `trigger` method, giving a set of keywork arguments (the signal attributes to the method) Then the rule can decide to start or not its actions. A trigger can also optionaly have a an `untrigger` method. This method will call the `untrigger` method of the connected rules. """ def __init__(self): """ Create a new trigger The trigger need to be initialized with the `init` method before it can trigger the connected rules """ self.__listeners = [] # List of rules that are triggered by this trigger def connect(self, action): """ Connect the trigger to an action This method should only be called by the Rule class """ self.__listeners.append(action) def _trigger(self, **kargs): """ Trigger all the connected rules """ logger.debug("trigger %s", self) for action in self.__listeners: action.trigger(**kargs) def _untrigger(self, **kargs): """ Untrigger all the connected rules """ logger.debug("untrigger %s", self) for action in self.__listeners: action.untrigger(**kargs) def enable(self): """ Enable the trigger The trigger won't trigger the connect rules before this method has been called """ pass def disable(self): """ Disable the trigger """ pass #============================================================================# class DBusTrigger(Trigger): #============================================================================# """ A special trigger that waits for a given DBus signal to trigger its rules """ function_name = 'DbusTrigger' def __init__(self, bus, service, obj, interface, signal): """Create the DBus trigger arguments: - bus the DBus bus name (or a string : 'system' | 'session') - service the DBus name of the service - obj the DBus path of the object - interface the Dbus interface of the signal - signal the DBus name of the signal """ Trigger.__init__( self ) # some arguments checking if isinstance(bus, str): if bus == 'system': bus = dbus.SystemBus() elif bus == 'session': bus = dbus.SessionBus() else: raise TypeError("Bad dbus bus : %s" % bus) if not obj: obj = None assert isinstance(service, str), "service is not str" assert obj is None or isinstance(obj, str), "obj is not str or None" assert isinstance(interface, str), "interface is not str" assert isinstance(signal, str), "signal is not str" self.bus = bus self.service = service self.obj = obj self.interface = interface self.signal = signal self.dbus_match = None def __repr__(self): return "DBusTrigger(%s %s.%s)" % (self.service, self.obj, self.signal) def enable(self): if self.dbus_match is not None: # if the rule is already enabled we do nothing return # Connect to the DBus signal logger.debug("connect trigger to dbus signal %s %s", self.obj, self.signal) self.dbus_match = self.bus.add_signal_receiver( self.on_signal, dbus_interface=self.interface, signal_name=self.signal ) def disable(self): if self.dbus_match is None: # if the rule is already disabled we do nothing return self.dbus_match.remove() self.dbus_match = None def on_signal(self, *args): kargs = dict( ('arg%d' % i, v) for i,v in enumerate(args) ) self._trigger( **kargs ) #============================================================================# class TestTrigger( Trigger ): #============================================================================# """ This trigger can be used ot debug the events system. It is triggered when oeventsd.TriggerTest method is called """ function_name = "Test" def __init__( self, name ): Trigger.__init__( self ) self.name = name def __repr__( self ): return "Test(%s)" % self.name fso-frameworkd-0.10.1/framework/subsystems/ogpsd/000077500000000000000000000000001174525413000220535ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/ogpsd/__init__.py000066400000000000000000000000001174525413000241520ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/ogpsd/eten.py000066400000000000000000000017121174525413000233610ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: UTF-8 -*- """ Open GPS Daemon (C) 2008 Jan 'Shoragan' Lübbe (C) 2008 Daniel Willmann (C) 2008 Stefan Schmidt (C) 2008 Openmoko, Inc. GPLv2 or later """ DEVICE_POWER_PATH = "/sys/bus/platform/devices/neo1973-pm-gps.0/pwron" from nmea import NMEADevice import helpers import logging logger = logging.getLogger('ogpsd') class EtenDevice( NMEADevice ): """E-Ten specific GPS device""" def __init__( self, bus, channel ): # Make sure the GPS is off helpers.writeToFile( DEVICE_POWER_PATH, "0" ) NMEADevice.__init__( self, bus, channel ) def initializeDevice( self ): helpers.writeToFile( DEVICE_POWER_PATH, "1" ) NMEADevice.initializeDevice( self ) def shutdownDevice( self ): NMEADevice.shutdownDevice( self ) helpers.writeToFile( DEVICE_POWER_PATH, "0" ) #vim: expandtab fso-frameworkd-0.10.1/framework/subsystems/ogpsd/factory.py000066400000000000000000000024661174525413000241040ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: UTF-8 -*- """ Open GPS Daemon (C) 2008 Michael 'Mickey' Lauer (C) 2008 Jan 'Shoragan' Lübbe (C) 2008 Daniel Willmann (C) 2008 Openmoko, Inc. GPLv2 or later """ __version__ = "0.0.0" import dbus from framework.config import config from gpsdevice import DummyDevice from nmea import NMEADevice from ubx import UBXDevice from om import GTA02Device from eten import EtenDevice from msm import MSMDevice from gpschannel import * NEEDS_BUSNAMES = [ "org.freedesktop.Gypsy" ] #----------------------------------------------------------------------------# def factory( prefix, controller ): #----------------------------------------------------------------------------# objects = [] devname = config.getValue( "ogpsd", "device", "DummyDevice") channame = config.getValue( "ogpsd", "channel", "GPSChannel") pathname = config.getValue( "ogpsd", "path", "") debug = config.getValue( "ogpsd", "debug_addr", "") channel = globals()[channame]( pathname ) if debug: channel.setDebugChannel( UDPChannel( debug ) ) gpsdev = globals()[devname]( controller.bus, channel ) objects.append( gpsdev ) return objects if __name__ == "__main__": bus = dbus.SystemBus() #vim: expandtab fso-frameworkd-0.10.1/framework/subsystems/ogpsd/gpschannel.py000066400000000000000000000153211174525413000245510ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: UTF-8 -*- """ Open GPS Daemon - Parse NMEA/UBX data (C) 2008 Michael 'Mickey' Lauer (C) 2008 Jan 'Shoragan' Lübbe (C) 2008 Daniel Willmann (C) 2008 Openmoko, Inc. GPLv2 or later """ from framework.config import config import os import sys import serial import socket import gobject import logging logger = logging.getLogger('ogpsd') class GPSChannel( object ): """A GPS Channel :-)""" def __init__( self, path=None ): self.callback = None self.debugChannel = None def setDebugChannel( self, debugChannel ): self.debugChannel = debugChannel self.debugChannel.setCallback( self.send ) def initializeChannel( self ): pass def shutdownChannel( self ): pass def suspendChannel( self ): self.shutdownChannel() def resumeChannel( self ): self.initializeChannel() def setCallback( self, callback ): self.callback = callback def send( self, stream ): raise Exception( "Not implemented" ) class UDPChannel ( GPSChannel ): """Generic UDP reader""" def __init__( self, path ): super(UDPChannel, self).__init__() logger.debug("UDPChannel opens port %s" % path) if ":" in path: self.host, self.port = path.split( ":" ) self.port = int( self.port ) else: self.host = "" self.port = int( path ) self.s = socket.socket( socket.AF_INET, socket.SOCK_DGRAM ) self.s.bind( ("", self.port) ) self.s.setblocking(False) self.datapending = "" self.watchReadyToRead = gobject.io_add_watch( self.s.makefile(), gobject.IO_IN, self.readyToRead ) self.watchReadyToSend = None def readyToRead( self, source, condition ): data = self.s.recv(1024) if self.callback: self.callback(data) return True def readyToSend( self, source, condition ): if self.host: self.s.sendto( self.datapending, (self.host, self.port) ) self.datapending = "" self.watchReadyToSend = None return False def send( self, stream ): self.datapending = self.datapending + stream if not self.watchReadyToSend: self.watchReadyToSend = gobject.io_add_watch( self.s.makefile(), gobject.IO_OUT, self.readyToSend ) class GllinChannel( UDPChannel ): """UDP reader for GTA01, takes care of starting and stopping gllin""" def __init__( self, path="/etc/init.d/gllin" ): super( GllinChannel, self ).__init__( "6000" ) self.gllin = path def initializeChannel( self ): os.system(self.gllin + " start") def shutdownChannel( self ): os.system(self.gllin + " stop") class FileChannel ( GPSChannel ): """File reader, for gta01, gllin""" def __init__(self, path): super(FileChannel, self).__init__() logger.debug("FileChannel opens %s" % path) self.path = path def initializeChannel( self ): self.fd = os.open(self.path, os.O_NONBLOCK + os.O_RDONLY) self.watchReadyToRead = gobject.io_add_watch( self.fd, gobject.IO_IN, self.readyToRead ) def shutdownChannel( self ): gobject.source_remove( self.watchReadyToRead ) os.close( self.fd ) def readyToRead( self, source, condition ): data_array = [] try: while True: data_array.append(os.read(self.fd, 1024)) except OSError: pass if len(data_array) == 1: data = data_array[0] # shortcut for common case else: data = ''.join(data_array) if self.callback: self.callback(data) return True def send( self, stream ): logger.debug( "Tried to send data to a FileChannel, ignoring" ) class SerialChannel( GPSChannel ): """Serial reader""" def __init__( self, path, rtscts = False): super(SerialChannel, self).__init__() # set up serial port object and open it self.serial = serial.Serial() self.serial.port = path self.serial.baudrate = config.getValue( "ogpsd.serialchannel", "baudrate", 9600) self.serial.rtscts = rtscts self.serial.xonxoff = False self.serial.bytesize = serial.EIGHTBITS self.serial.parity = serial.PARITY_NONE self.serial.stopbits = serial.STOPBITS_ONE self.serial.timeout = None self.datapending = "" self.watchReadyToRead = None self.watchReadyToSend = None def initializeChannel( self ): self.serial.open() assert self.serial.isOpen(), "Failure opening device" # set up I/O watches for mainloop self.watchReadyToRead = gobject.io_add_watch( self.serial.fd, gobject.IO_IN, self.readyToRead ) #self.watchReadyToSend = gobject.io_add_watch( self.serial.fd, gobject.IO_OUT, self.readyToSend ) self.watchReadyToSend = None # self.watchHUP = gobject.io_add_watch( self.serial.fd, gobject.IO_HUP, self.hup ) def shutdownChannel( self ): if self.watchReadyToRead: gobject.source_remove( self.watchReadyToRead ) self.watchReadyToRead = None if self.watchReadyToSend: gobject.source_remove( self.watchReadyToSend ) self.watchReadyToSend = None self.serial.close() self.datapending = "" def readyToRead( self, source, condition ): """Called, if data is available on the source.""" assert source == self.serial.fd, "ready to read on bogus source" assert condition == gobject.IO_IN, "ready to read on bogus condition" try: inWaiting = self.serial.inWaiting() except IOError: inWaiting = 0 data = self.serial.read( inWaiting ) if self.debugChannel: self.debugChannel.send( data ) if self.callback: self.callback( data ) return True def readyToSend( self, source, condition ): """Called, if source is ready to receive data.""" assert source == self.serial.fd, "ready to write on bogus source" assert condition == gobject.IO_OUT, "ready to write on bogus condition" self.serial.write( self.datapending ) self.datapending = "" self.watchReadyToSend = None return False def send( self, stream ): if not self.serial.isOpen(): logger.warning( "Attempted to send something while serial is not open." ) return self.datapending = self.datapending + stream if not self.watchReadyToSend: self.watchReadyToSend = gobject.io_add_watch( self.serial.fd, gobject.IO_OUT, self.readyToSend ) #vim: expandtab fso-frameworkd-0.10.1/framework/subsystems/ogpsd/gpsdevice.py000066400000000000000000000276341174525413000244120ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: UTF-8 -*- """ Open GPS Daemon - Parse NMEA/UBX data (C) 2008 Michael 'Mickey' Lauer (C) 2008 Jan 'Shoragan' Lübbe (C) 2008 Daniel Willmann (C) 2008 Openmoko, Inc. GPLv2 or later """ __version__ = "0.9.0" MODULE_NAME = "ogpsd.gpsdevice" DBUS_INTERFACE_PREFIX = "org.freedesktop.Gypsy" DBUS_PATH_PREFIX = "/org/freedesktop/Gypsy" from framework import resource import framework.patterns.tasklet as tasklet import dbus import dbus.service import logging logger = logging.getLogger( MODULE_NAME ) class GPSDevice( resource.Resource ): """An Dbus Object implementing org.freedesktop.Gypsy""" def __init__( self, bus, channel=None ): self._fixstatus = 0 self._position = [ 0, 0, 0.0, 0.0, 0.0 ] self._accuracy = [ 0, 0.0, 0.0, 0.0 ] self._course = [ 0, 0, 0.0, 0.0, 0.0 ] self._satellites = [] self._time = 0 self._users = [] self.channel = channel self.interface = DBUS_INTERFACE_PREFIX self.path = DBUS_PATH_PREFIX self.bus = bus self.usageiface = None bus.add_signal_receiver( self.nameOwnerChangedHandler, "NameOwnerChanged", dbus.BUS_DAEMON_IFACE, dbus.BUS_DAEMON_NAME, dbus.BUS_DAEMON_PATH ) dbus.service.Object.__init__( self, bus, self.path ) resource.Resource.__init__( self, bus, "GPS" ) logger.info("%s initialized. Serving %s at %s" % ( self.__class__.__name__, self.interface, self.path ) ) def nameOwnerChangedHandler( self, name, old_owner, new_owner ): if old_owner and not new_owner: if old_owner in self._users: self.Stop( old_owner, lambda :None, lambda x:None) # # framework.Resource # def _enable( self, on_ok, on_error ): logger.info( "enabling" ) if self.channel is not None: self.channel.initializeChannel() else: logger.warn("GPSDevice has no channel assigned") self.initializeDevice() self.ConnectionStatusChanged( True ) on_ok() def _disable( self, on_ok, on_error ): logger.info( "disabling" ) self.ConnectionStatusChanged( False ) self.shutdownDevice() if self.channel is not None: self.channel.shutdownChannel() else: logger.warn("GPSDevice has no channel assigned") on_ok() def _suspend( self, on_ok, on_error ): logger.info( "suspending" ) self.ConnectionStatusChanged( False ) self.suspendDevice() if self.channel is not None: self.channel.suspendChannel() else: logger.warn("GPSDevice has no channel assigned") on_ok() def _resume( self, on_ok, on_error ): logger.info("resuming") if self.channel is not None: self.channel.resumeChannel() else: logger.warn("GPSDevice has no channel assigned") self.resumeDevice() self.ConnectionStatusChanged( True ) on_ok() def initializeDevice( self ): pass def shutdownDevice( self ): self._reset() def suspendDevice( self ): self.shutdownDevice() def resumeDevice( self ): self.initializeDevice() def _reset( self ): if self._fixstatus: self._fixstatus = 0 self.FixStatusChanged( self._fixstatus ) if self._position[0]: self._position[0] = 0 self.PositionChanged( *self._position ) if self._accuracy[0]: self._accuracy[0] = 0 self.AccuracyChanged( *self._accuracy ) if self._course[0]: self._course[0] = 0 self.CourseChanged( *self._course ) if self._satellites != []: self._satellites = [] self.SatellitesChanged( self._satellites ) if self._time: self._time = 0 self.TimeChanged( self._time ) # # update functions # def _updateFixStatus( self, fixstatus ): if self._fixstatus != fixstatus: self._fixstatus = fixstatus self.FixStatusChanged( self._fixstatus ) def _updatePosition( self, fields, lat, lon, alt ): changed = False if self._position[0] != fields: self._position[0] = fields changed = True if fields: self._position[1] = self._time if fields & (1 << 0) and self._position[2] != lat: self._position[2] = lat changed = True if fields & (1 << 1) and self._position[3] != lon: self._position[3] = lon changed = True if fields & (1 << 2) and self._position[4] != alt: self._position[4] = alt changed = True if changed: self.PositionChanged( *self._position ) def _updateAccuracy( self, fields, pdop, hdop, vdop ): changed = False if self._accuracy[0] != fields: self._accuracy[0] = fields changed = True if fields & (1 << 0) and self._accuracy[1] != pdop: self._accuracy[1] = pdop changed = True if fields & (1 << 1) and self._accuracy[2] != hdop: self._accuracy[2] = hdop changed = True if fields & (1 << 2) and self._accuracy[3] != vdop: self._accuracy[3] = vdop changed = True if changed: self.AccuracyChanged( *self._accuracy ) def _updateCourse( self, fields, speed, heading, climb ): changed = False if self._course[0] != fields: self._course[0] = fields changed = True if fields: self._course[1] = self._time if fields & (1 << 0) and self._course[2] != speed: self._course[2] = speed changed = True if fields & (1 << 1) and self._course[3] != heading: self._course[3] = heading changed = True if fields & (1 << 2) and self._course[4] != climb: self._course[4] = climb changed = True if changed: self.CourseChanged( *self._course ) def _updateSatellites( self, satellites ): # Is this check sufficient or could some SVs switch channels, but # otherwise stay identical? if self._satellites != satellites: self._satellites = satellites self.SatellitesChanged( self._satellites ) def _updateTime( self, time ): if self._time != time: self._time = time self.TimeChanged( self._time ) # Gypsy Server interface # This should be implemented somewhere else once we allow different devices @dbus.service.method( DBUS_INTERFACE_PREFIX + ".Server", "s", "o" ) def Create( self, device ): return DBUS_PATH_PREFIX @dbus.service.method( DBUS_INTERFACE_PREFIX + ".Server", "o", "" ) def Shutdown( self, path ): pass # # dbus methods # @dbus.service.method( DBUS_INTERFACE_PREFIX + ".Device", "", "", sender_keyword="sender", async_callbacks=( "dbus_ok", "dbus_error" ) ) def Start( self, sender, dbus_ok, dbus_error ): if not sender in self._users: self._users.append( sender ) if len(self._users) == 1: if not self.usageiface: usage = self.bus.get_object( "org.freesmartphone.ousaged", "/org/freesmartphone/Usage", follow_name_owner_changes=True ) self.usageiface = dbus.Interface( usage, "org.freesmartphone.Usage" ) def on_error( error ): self._users.remove( sender ) dbus_error( dbus.DBusException("RequestResource failed") ) self.usageiface.RequestResource("GPS", reply_handler=dbus_ok, error_handler=on_error) else: dbus_ok() else: dbus_ok() @dbus.service.method( DBUS_INTERFACE_PREFIX + ".Device", "", "", sender_keyword="sender", async_callbacks=( "dbus_ok", "dbus_error" ) ) def Stop( self, sender, dbus_ok, dbus_error ): if sender in self._users: self._users.remove( sender ) if self._users == []: if not self.usageiface: usage = self.bus.get_object( "org.freesmartphone.ousaged", "/org/freesmartphone/Usage", follow_name_owner_changes=True ) self.usageiface = dbus.Interface( usage, "org.freesmartphone.Usage" ) self.usageiface.ReleaseResource("GPS", reply_handler=dbus_ok, error_handler=dbus_error ) else: dbus_ok() else: dbus_ok() @dbus.service.method( DBUS_INTERFACE_PREFIX + ".Device", "", "b") @resource.checkedsyncmethod def GetConnectionStatus( self ): return self._resourceStatus == "enabled" @dbus.service.method( DBUS_INTERFACE_PREFIX + ".Device", "", "i") @resource.checkedsyncmethod def GetFixStatus( self ): return self._fixstatus @dbus.service.method( DBUS_INTERFACE_PREFIX + ".Position", "", "iiddd" ) @resource.checkedsyncmethod def GetPosition( self ): return self._position @dbus.service.method( DBUS_INTERFACE_PREFIX + ".Accuracy", "", "iddd" ) @resource.checkedsyncmethod def GetAccuracy( self ): return self._accuracy @dbus.service.method( DBUS_INTERFACE_PREFIX + ".Course", "", "iiddd" ) @resource.checkedsyncmethod def GetCourse( self ): return self._course @dbus.service.method( DBUS_INTERFACE_PREFIX + ".Satellite", "", "a(ubuuu)" ) @resource.checkedsyncmethod def GetSatellites( self ): return self._satellites @dbus.service.method( DBUS_INTERFACE_PREFIX + ".Time", "", "i" ) @resource.checkedsyncmethod def GetTime( self ): return self._time # # dbus signals # @resource.queuedsignal @dbus.service.signal( DBUS_INTERFACE_PREFIX + ".Device", "b" ) def ConnectionStatusChanged( self, constatus ): logger.debug( "ConnectionStatusChanged %s" % constatus ) @dbus.service.signal( DBUS_INTERFACE_PREFIX + ".Device", "i" ) @resource.queuedsignal def FixStatusChanged( self, fixstatus ): logger.debug( "FixStatusChanged %s" % fixstatus ) @dbus.service.signal( DBUS_INTERFACE_PREFIX + ".Position", "iiddd" ) @resource.queuedsignal def PositionChanged( self, fields, tstamp, lat, lon, alt ): logger.debug( "PositionChanged (%i) %f, %f %f" % ( fields, lat, lon, alt ) ) @dbus.service.signal( DBUS_INTERFACE_PREFIX + ".Accuracy", "iddd" ) @resource.queuedsignal def AccuracyChanged( self, fields, pdop, hdop, vdop ): logger.debug( "AccuracyChanged (%i) P%f, H%f, V%f" % ( fields, pdop, hdop, vdop ) ) @dbus.service.signal( DBUS_INTERFACE_PREFIX + ".Course", "iiddd" ) @resource.queuedsignal def CourseChanged( self, fields, tstamp, speed, heading, climb ): logger.debug( "CourseChanged (%i) %f, %f°, %f" % ( fields, speed, heading, climb ) ) @dbus.service.signal( DBUS_INTERFACE_PREFIX + ".Satellite", "a(ubuuu)" ) @resource.queuedsignal def SatellitesChanged( self, satellites ): logger.debug( "SatellitesChanged %s" % satellites ) @dbus.service.signal( DBUS_INTERFACE_PREFIX + ".Time", "i" ) @resource.queuedsignal def TimeChanged( self, time ): logger.debug( "TimeChanged %i" % time ) class DummyDevice( GPSDevice ): """A dummy device that reports a static position""" def __init__( self, bus, channel ): super( DummyDevice, self ).__init__( bus ) def initializeDevice( self ): self._updateTime( 1 ) self._updateFixStatus( 3 ) self._updatePosition( 7, 23.54322, -42.65648, 14.4 ) self._updateAccuracy( 7, 2.34, 16.4, 1.4 ) self._updateCourse( 7, 240.4, 45.4, -4.4 ) self._updateSatellites( [(1, False, 13, 164, 0), (13, True, 72, 250, 20), (24, True, 68, 349, 31)] ) #vim: expandtab fso-frameworkd-0.10.1/framework/subsystems/ogpsd/helpers.py000066400000000000000000000016541174525413000240750ustar00rootroot00000000000000import logging logger = logging.getLogger('ogpsd') #============================================================================# def readFromFile( path ): #============================================================================# try: value = open( path, 'r' ).read().strip() except IOError, e: logger.warning( "(could not read from '%s': %s)" % ( path, e ) ) return "N/A" else: logger.debug( "(read '%s' from '%s')" % ( value, path ) ) return value #============================================================================# def writeToFile( path, value ): #============================================================================# logger.debug( "(writing '%s' to '%s')" % ( value, path ) ) try: f = open( path, 'w' ) except IOError, e: logger.warning( "(could not write to '%s': %s)" % ( path, e ) ) else: f.write( "%s\n" % value ) fso-frameworkd-0.10.1/framework/subsystems/ogpsd/msm.py000066400000000000000000000020561174525413000232240ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: UTF-8 -*- """ Open GPS Daemon (C) 2010 Denis 'GNUtoo' Carikli (C) 2008 Jan 'Shoragan' Lübbe (C) 2008 Daniel Willmann (C) 2008 Stefan Schmidt (C) 2008 Openmoko, Inc. GPLv2 or later """ import os,subprocess from nmea import NMEADevice import helpers import logging logger = logging.getLogger('ogpsd') class MSMDevice( NMEADevice ): """MSM SOC specific GPS device""" def __init__( self, bus, channel ): NMEADevice.__init__( self, bus, channel ) self.dev_null = os.open("/dev/null",777) self.gps = None def initializeDevice( self ): NMEADevice.initializeDevice( self ) if not self.gps: self.gps = subprocess.Popen(["gps"],stderr=self.dev_null,stdout=self.dev_null, shell=False) def shutdownDevice( self ): if self.gps: self.gps.kill() self.gps.wait() self.gps = None NMEADevice.shutdownDevice( self ) #vim: expandtab fso-frameworkd-0.10.1/framework/subsystems/ogpsd/nmea.py000066400000000000000000000232231174525413000233470ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: UTF-8 -*- """ Open GPS Daemon - Abstract parser class NMEA parser taken from pygps written by Russell Nelson Copyright, 2001, 2002, Russell Nelson Copyright permissions given by the GPL Version 2. http://www.fsf.org/ (C) 2008 Rod Whitby (C) 2008 Daniel Willmann (C) 2008 Openmoko, Inc. GPLv2 """ __version__ = "0.0.0" import math import string import time from gpsdevice import GPSDevice import logging logger = logging.getLogger('ogpsd') class NMEADevice( GPSDevice ): def __init__( self, bus, channel ): super( NMEADevice, self ).__init__( bus, channel ) self.buffer = "" self.channel.setCallback( self.parse ) self.prn = range(12) self.elevation = range(12) self.azimuth = range(12) self.ss = range(12) self.used = range(12) self.date = '000000' self.time = '000000' self.timestamp = 0 self.mode = 0 self.lat = 0.0 self.lon = 0.0 self.altitude = 0.0 self.track = 0.0 self.speed = 0.0 self.in_view = 0 def parse( self, data ): self.buffer += data while True: try: line, self.buffer = self.buffer.split( "\r\n", 1 ) except: break else: result = self.handle_line( line.strip() ) if result: logger.debug( result ) def checksum(self,sentence, cksum): csum = 0 for c in sentence: csum = csum ^ ord(c) return "%02X" % csum == cksum #$GPGGA,000032.997,0000.0000,N,00000.0000,E,0,00,50.0,0.0,M,,.j...«.æ.ÆV.æ.ÆV.æ.|.VÖL²4jj.h..00032.997,V,0000.0000,N,00000.0006 #Lat: 0.000000 Lon: 0.000000 Alt: 0.000000 Sat: 0 Mod: 1 Time: 11/26/2000 00:00:31 #$GPGGA,000033.997,0000.0000,N,00000.0000,E,0,00,50.0,0.0,M,,,,0000*3C def do_lat_lon(self, words): if not words[0]: return if words[0][-1] == 'N': words[0] = words[0][:-1] words[1] = 'N' if words[0][-1] == 'S': words[0] = words[0][:-1] words[1] = 'S' if words[2][-1] == 'E': words[2] = words[2][:-1] words[3] = 'E' if words[2][-1] == 'W': words[2] = words[2][:-1] words[3] = 'W' if len(words[0]): lat = string.atof(words[0]) frac, intpart = math.modf(lat / 100.0) lat = intpart + frac * 100.0 / 60.0 if words[1] == 'S': lat = -lat self.lat = lat if len(words[2]): lon = string.atof(words[2]) frac, intpart = math.modf(lon / 100.0) lon = intpart + frac * 100.0 / 60.0 if words[3] == 'W': lon = -lon self.lon = lon #$GPRMC,024932.992,V,4443.7944,N,07456.7103,W,,,270402,,*05 #$GPRMC,024933.992,V,4443.7944,N,07456.7103,W,,,270402,,*04 # RMC - Recommended minimum specific GPS/Transit data # RMC,225446,A,4916.45,N,12311.12,W,000.5,054.7,191194,020.3,E*68 # 225446 Time of fix 22:54:46 UTC # A Navigation receiver warning A = OK, V = warning # 4916.45,N Latitude 49 deg. 16.45 min North # 12311.12,W Longitude 123 deg. 11.12 min West # 000.5 Speed over ground, Knots # 054.7 Course Made Good, True # 191194 Date of fix 19 November 1994 # 020.3,E Magnetic variation 20.3 deg East # *68 mandatory checksum def processGPRMC(self, words): self.do_lat_lon(words[2:]) self.date = words[8] day = string.atoi(self.date[0:2]) month = string.atoi(self.date[2:4]) year = 2000 + string.atoi(self.date[4:6]) self.time = words[0][0:6] hours = string.atoi(self.time[0:2]) minutes = string.atoi(self.time[2:4]) seconds = string.atoi(self.time[4:6]) self.timestamp = int(time.mktime((year,month,day,hours,minutes,seconds,-1,-1,-1))) self._updateTime( self.timestamp ) if words[1] == 'V' or words[1] == "A": if words[6]: self.speed = string.atof(words[6]) if words[7]: self.track = string.atof(words[7]) self._updateCourse( 3, self.speed, self.track, 0 ) #$GPGGA,024933.992,4443.7944,N,07456.7103,W,0,00,50.0,192.5,M,,,,0000*27 #$GPGGA,024934.991,4443.7944,N,07456.7103,W,0,00,50.0,192.5,M,,,,0000*23 # GGA - Global Positioning System Fix Data # GGA,123519,4807.038,N,01131.324,E,1,08,0.9,545.4,M,46.9,M, , *42 # 123519 Fix taken at 12:35:19 UTC # 4807.038,N Latitude 48 deg 07.038' N # 01131.324,E Longitude 11 deg 31.324' E # 1 Fix quality: 0 = invalid # 1 = GPS fix # 2 = DGPS fix # 08 Number of satellites being tracked # 0.9 Horizontal dilution of position # 545.4,M Altitude, Metres, above mean sea level # 46.9,M Height of geoid (mean sea level) above WGS84 # ellipsoid # (empty field) time in seconds since last DGPS update # (empty field) DGPS station ID number def processGPGGA(self,words): day = string.atoi(self.date[0:2]) month = string.atoi(self.date[2:4]) year = 2000 + string.atoi(self.date[4:6]) self.time = words[0][0:6] hours = string.atoi(self.time[0:2]) minutes = string.atoi(self.time[2:4]) seconds = string.atoi(self.time[4:6]) self.timestamp = int(time.mktime((year,month,day,hours,minutes,seconds,-1,-1,-1))) self._updateTime( self.timestamp ) self.status = string.atoi(words[5]) self.satellites = string.atoi(words[6]) if self.status > 0: self.do_lat_lon(words[1:]) self.altitude = string.atof(words[8]) # FIXME: Mode self._updatePosition( 7, self.lat, self.lon, self.altitude ) #$GPGSA,A,1,,,,,,,,,,,,,50.0,50.0,50.0*05 #$GPGSA,A,1,,,,,,,,,,,,,50.0,50.0,50.0*05 # GSA - GPS DOP and active satellites # GSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39 # A Auto selection of 2D or 3D fix (M = manual) # 3 3D fix # 04,05... PRNs of satellites used for fix (space for 12) # 2.5 PDOP (dilution of precision) # 1.3 Horizontal dilution of precision (HDOP) # 2.1 Vertical dilution of precision (VDOP) # DOP is an indication of the effect of satellite geometry on # the accuracy of the fix. def processGPGSA(self,words): self.mode = string.atof(words[1]) for n in range(12): if words[n+2]: self.used[n] = 1 else: self.used[n] = 0 pdop = string.atof(words[14]) hdop = string.atof(words[15]) vdop = string.atof(words[16]) # FIXME: Mode... self._updateAccuracy( 7, pdop, hdop, vdop ) self._updateSatellites( (self.prn[n],self.used[n],self.elevation[n],self.azimuth[n],self.ss[n]) for n in range(12) ) self._updateFixStatus( int(self.mode) ) #$GPGSV,3,1,09,14,77,023,,21,67,178,,29,64,307,,30,42,095,*7E #$GPGSV,3,2,09,05,29,057,,11,15,292,,18,08,150,,23,08,143,*7A #$GPGSV,3,3,09,09,05,052,*4B # GSV - Satellites in view # GSV,2,1,08,01,40,083,46,02,17,308,41,12,07,344,39,14,22,228,45*75 # 2 Number of sentences for full data # 1 sentence 1 of 2 # 08 Number of satellites in view # 01 Satellite PRN number # 40 Elevation, degrees # 083 Azimuth, degrees # 46 Signal strength - higher is better # # There my be up to three GSV sentences in a data packet def processGPGSV(self,words): num_sentences = string.atoi(words[0]) current_sentence = string.atoi(words[1]) self.in_view = string.atoi(words[2]) f = 3 n = (current_sentence - 1) * 4 # FIXME: Need to clear the rest of the entries up to 12 while n < self.in_view and f < len(words): if words[f+0]: self.prn[n] = string.atoi(words[f+0]) if words[f+1]: self.elevation[n] = string.atoi(words[f+1]) if words[f+2]: self.azimuth[n] = string.atoi(words[f+2]) if words[f+3]: self.ss[n] = string.atoi(words[f+3]) f = f + 4 n = n + 1 def processPGLOR(self,words): # FIXME: Do we do anything with this sentence? pass def handle_line(self, line): if line[0] == '$': line = string.split(line[1:-1], '*') if len(line) != 2: return # if not self.checksum(line[0], line[1]): # return "Bad checksum" words = string.split(line[0], ',') methodname = "process"+words[0] try: method = getattr( self, methodname ) except AttributeError: logger.error( "Line: %s" % line) return "Unknown sentence" else: try: method( words[1:] ) except Exception, e: logger.error( "Line: %s" % line) logger.error( "Error in %s method: %s" % ( methodname, e ) ) else: return "Not NMEA" #vim: expandtab fso-frameworkd-0.10.1/framework/subsystems/ogpsd/om.py000066400000000000000000000156341174525413000230510ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: UTF-8 -*- """ Open GPS Daemon (C) 2008 Jan 'Shoragan' Lübbe (C) 2008 Daniel Willmann (C) 2008 Openmoko, Inc. GPLv2 or later """ __version__ = "0.9.9.4" MODULE_NAME = "ogpsd" DEVICE_POWER_PATH_OLD = "/sys/bus/platform/devices/neo1973-pm-gps.0/pwron" DEVICE_POWER_PATH_NEW = "/sys/bus/platform/devices/neo1973-pm-gps.0/power_on" from ubx import UBXDevice from ubx import CLIDPAIR from framework.persist import persist import helpers import gobject import os, sys, marshal, time from datetime import datetime, timedelta import logging logger = logging.getLogger( MODULE_NAME ) class GTA02Device( UBXDevice ): """GTA02 specific GPS device""" def __init__( self, bus, channel ): # Kernel specific paths global DEVICE_POWER_PATH_NEW kernel_release = os.uname()[2] if kernel_release >= "2.6.39": DEVICE_POWER_PATH_NEW = "/sys/bus/platform/drivers/gta02-pm-gps/gta02-pm-gps.0/power_on" logger.info( "Kernel >= 2.6.39, gps sysfs updated" ) elif kernel_release >= "2.6.32": DEVICE_POWER_PATH_NEW = "/sys/bus/platform/devices/gta02-pm-gps.0/power_on" logger.info( "Kernel >= 2.6.32, gps sysfs updated" ) # Make sure the GPS is off helpers.writeToFile( DEVICE_POWER_PATH_OLD, "1" ) helpers.writeToFile( DEVICE_POWER_PATH_NEW, "1" ) time.sleep( 0.5 ) helpers.writeToFile( DEVICE_POWER_PATH_OLD, "0" ) helpers.writeToFile( DEVICE_POWER_PATH_NEW, "0" ) self.aidingData = persist.get( "ogpsd", "aidingdata" ) if self.aidingData is None: self.aidingData = { "almanac": {}, "ephemeris": {}, "position": {}, "hui": {} } self.huiTimeout = None super( GTA02Device, self ).__init__( bus, channel ) def initializeDevice( self ): helpers.writeToFile( DEVICE_POWER_PATH_OLD, "1" ) helpers.writeToFile( DEVICE_POWER_PATH_NEW, "1" ) # Wait for the device to be powered up time.sleep(0.5) # Reset the device #self.send("CFG-RST", 4, {"nav_bbr" : 0xffff, "Reset" : 0x01}) super( GTA02Device, self ).initializeDevice() # Load aiding data and only if that succeeds have the GPS chip ask for it if self.aidingData["almanac"] or self.aidingData["ephemeris"] or self.aidingData["position"]: self.send("CFG-MSG", 3, {"Class" : CLIDPAIR["AID-REQ"][0] , "MsgID" : CLIDPAIR["AID-REQ"][1] , "Rate": 1 }) # Enable NAV-POSECEF, AID-REQ (AID-DATA), AID-ALM, AID-EPH messages self.send("CFG-MSG", 3, {"Class" : CLIDPAIR["NAV-POSECEF"][0] , "MsgID" : CLIDPAIR["NAV-POSECEF"][1] , "Rate": 8 }) self.send("CFG-MSG", 3, {"Class" : CLIDPAIR["AID-ALM"][0] , "MsgID" : CLIDPAIR["AID-ALM"][1] , "Rate": 1 }) self.send("CFG-MSG", 3, {"Class" : CLIDPAIR["AID-EPH"][0] , "MsgID" : CLIDPAIR["AID-EPH"][1] , "Rate": 1 }) self.huiTimeout = gobject.timeout_add_seconds( 300, self.requestHuiTimer ) def shutdownDevice( self ): # Disable NAV-POSECEF, AID-REQ (AID-DATA), AID-ALM, AID-EPH messages self.send("CFG-MSG", 3, {"Class" : CLIDPAIR["NAV-POSECEF"][0] , "MsgID" : CLIDPAIR["NAV-POSECEF"][1] , "Rate" : 0 }) self.send("CFG-MSG", 3, {"Class" : CLIDPAIR["AID-REQ"][0] , "MsgID" : CLIDPAIR["AID-REQ"][1] , "Rate" : 0 }) self.send("CFG-MSG", 3, {"Class" : CLIDPAIR["AID-ALM"][0] , "MsgID" : CLIDPAIR["AID-ALM"][1] , "Rate" : 0 }) self.send("CFG-MSG", 3, {"Class" : CLIDPAIR["AID-EPH"][0] , "MsgID" : CLIDPAIR["AID-EPH"][1] , "Rate" : 0 }) if self.huiTimeout is not None: gobject.source_remove( self.huiTimeout ) self.huiTimeout = None super( GTA02Device, self ).shutdownDevice() helpers.writeToFile( DEVICE_POWER_PATH_OLD, "0" ) helpers.writeToFile( DEVICE_POWER_PATH_NEW, "0" ) # Save collected aiding data persist.set( "ogpsd", "aidingdata", self.aidingData ) persist.sync( "ogpsd" ) def handle_NAV_POSECEF( self, data ): data = data[0] if data["Pacc"] < 100000: self.aidingData["position"]["accuracy"] = data["Pacc"] self.aidingData["position"]["x"] = data["ECEF_X"] self.aidingData["position"]["y"] = data["ECEF_Y"] self.aidingData["position"]["z"] = data["ECEF_Z"] def handle_AID_DATA( self, data ): pos = self.aidingData.get("position", {}) # Let's just try with 3km here and see how well this goes pacc = 300000 # in cm (3 km) # GPS week number # FIXME: The Global Positioning System (GPS) epoch is January 6, 1980 UTC. epoch = datetime(1980, 1, 6) now = datetime.utcnow() gpstime = (now - epoch) # Week number wn = gpstime.days / 7 try: leapsecs = self.aidingData["hui"]["UTC_LS"] except: # If we don't have current leap seconds yet assume 14 seconds. leapsecs = 14 # GPS time of week towdelta = gpstime - timedelta(weeks=wn) tow = (towdelta.days * 86400 + towdelta.seconds + leapsecs) * 1000 # Time accuracy needs to be changed, because the RTC is imprecise tacc = 60000 # in ms (1 minute) # We don't want the position to be valid if we don't know it if pos: flags = 0x03 else: flags = 0x02 # Feed gps with ephemeris if self.aidingData.get( "ephemeris", {} ): for k, a in self.aidingData["ephemeris"].iteritems(): logger.debug("Loaded ephemeris for SV %d" % a["SVID"]) self.send("AID-EPH", 104, a); # Feed GPS with position and time self.send("AID-INI", 48, {"X" : pos.get("x", 0) , "Y" : pos.get("y", 0) , "Z" : pos.get("z", 0), \ "POSACC" : pacc, "TM_CFG" : 0 , "WN" : wn , "TOW" : tow , "TOW_NS" : 0 , \ "TACC_MS" : tacc , "TACC_NS" : 0 , "CLKD" : 0 , "CLKDACC" : 0 , "FLAGS" : flags }) if self.aidingData.get( "hui", {} ): self.send("AID-HUI", 72, self.aidingData["hui"]) # Feed gps with almanac if self.aidingData.get( "almanac", {} ): for k, a in self.aidingData["almanac"].iteritems(): logger.debug("Loaded almanac for SV %d" % a["SVID"]) self.send("AID-ALM", 40, a); def handle_AID_ALM( self, data ): data = data[0] # Save only, if there are values if "DWRD0" in data: self.aidingData["almanac"][ data["SVID"] ] = data def handle_AID_EPH( self, data ): data = data[0] # Save only, if there are values if "SF1D0" in data: self.aidingData["ephemeris"][ data["SVID"] ] = data def handle_AID_HUI( self, data ): data = data[0] self.aidingData["hui"] = data def requestHuiTimer( self ): self.send( "AID-HUI", 0, {} ) return True #vim: expandtab fso-frameworkd-0.10.1/framework/subsystems/ogpsd/ubx.py000066400000000000000000000531611174525413000232310ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: UTF-8 -*- """ Open GPS Daemon - UBX parser class (C) 2008 Daniel Willmann (C) 2008 Openmoko, Inc. GPLv2 """ __version__ = "0.0.0" from gpsdevice import GPSDevice from framework import resource import struct import calendar import dbus import dbus.service from framework.config import config import logging logger = logging.getLogger('ogpsd') DBUS_INTERFACE = "org.freesmartphone.GPS" SYNC1=0xb5 SYNC2=0x62 CLASS = { "NAV" : 0x01, "RXM" : 0x02, "INF" : 0x04, "ACK" : 0x05, "CFG" : 0x06, "UPD" : 0x09, "MON" : 0x0a, "AID" : 0x0b, "TIM" : 0x0d, "USR" : 0x40 } CLIDPAIR = { "ACK-ACK" : (0x05, 0x01), "ACK-NACK" : (0x05, 0x00), "AID-ALM" : (0x0b, 0x30), "AID-DATA" : (0x0b, 0x10), "AID-EPH" : (0x0b, 0x31), "AID-HUI" : (0x0b, 0x02), "AID-INI" : (0x0b, 0x01), "AID-REQ" : (0x0b, 0x00), "CFG-ANT" : (0x06, 0x13), "CFG-CFG" : (0x06, 0x09), "CFG-DAT" : (0x06, 0x06), "CFG-EKF" : (0x06, 0x12), "CFG-FXN" : (0x06, 0x0e), "CFG-INF" : (0x06, 0x02), "CFG-LIC" : (0x06, 0x80), "CFG-MSG" : (0x06, 0x01), "CFG-NAV2" : (0x06, 0x1a), "CFG-NMEA" : (0x06, 0x17), "CFG-PRT" : (0x06, 0x00), "CFG-RATE" : (0x06, 0x08), "CFG-RST" : (0x06, 0x04), "CFG-RXM" : (0x06, 0x11), "CFG-SBAS" : (0x06, 0x16), "CFG-TM" : (0x06, 0x10), "CFG-TM2" : (0x06, 0x19), "CFG-TMODE" : (0x06, 0x1d), "CFG-TP" : (0x06, 0x07), "CFG-USB" : (0x06, 0x1b), "INF-DEBUG" : (0x04, 0x04), "INF-ERROR" : (0x04, 0x00), "INF-NOTICE" : (0x04, 0x02), "INF-TEST" : (0x04, 0x03), "INF-USER" : (0x04, 0x07), "INF-WARNING" : (0x04, 0x01), "MON-EXCEPT" : (0x0a, 0x05), "MON-HW" : (0x0a, 0x09), "MON-IO" : (0x0a, 0x02), "MON-IPC" : (0x0a, 0x03), "MON-MSGPP" : (0x0a, 0x06), "MON-RXBUF" : (0x0a, 0x07), "MON-SCHD" : (0x0a, 0x01), "MON-TXBUF" : (0x0a, 0x08), "MON-USB" : (0x0a, 0x0a), "MON-VER" : (0x0a, 0x04), "NAV-CLOCK" : (0x01, 0x22), "NAV-DGPS" : (0x01, 0x31), "NAV-DOP" : (0x01, 0x04), "NAV-EKFSTATUS" : (0x01, 0x40), "NAV-POSECEF" : (0x01, 0x01), "NAV-POSLLH" : (0x01, 0x02), "NAV-POSUTM" : (0x01, 0x08), "NAV-SBAS" : (0x01, 0x32), "NAV-SOL" : (0x01, 0x06), "NAV-STATUS" : (0x01, 0x03), "NAV-SVINFO" : (0x01, 0x30), "NAV-TIMEGPS" : (0x01, 0x20), "NAV-TIMEUTC" : (0x01, 0x21), "NAV-VELECEF" : (0x01, 0x11), "NAV-VELNED" : (0x01, 0x12), "RXM-ALM" : (0x02, 0x30), "RXM-EPH" : (0x02, 0x31), "RXM-POSREQ" : (0x02, 0x40), "RXM-RAW" : (0x02, 0x10), "RXM-SFRB" : (0x02, 0x11), "RXM-SVSI" : (0x02, 0x20), "TIM-SVIN" : (0x0d, 0x04), "TIM-TM" : (0x0d, 0x02), "TIM-TM2" : (0x0d, 0x03), "TIM-TP" : (0x0d, 0x01), "UPD-DOWNL" : (0x09, 0x01), "UPD-EXEC" : (0x09, 0x03), "UPD-MEMCPY" : (0x09, 0x04), "UPD-UPLOAD" : (0x09, 0x02) } CLIDPAIR_INV = dict( [ [v,k] for k,v in CLIDPAIR.items() ] ) MSGFMT = { ("NAV-POSECEF", 20) : ["= buffer_offset + 8: # Find the beginning of a UBX message start = self.buffer.find( chr( SYNC1 ) + chr( SYNC2 ), buffer_offset ) if buffer_offset == 0 and start != 0: logger.debug( "Discarded data not UBX %s" % repr(self.buffer[:start]) ) self.buffer = self.buffer[start:] continue if start == -1 or start + 8 > len(self.buffer): return (cl, id, length) = struct.unpack(" 0: try: fmt_base = [length] + MSGFMT[(clid,length)] fmt_rep = [0, "", []] payload_base = payload except KeyError: format = MSGFMT[(clid, None)] fmt_base = format[:3] fmt_rep = format[3:] payload_base = payload[0] payload_rep = payload[1:] if (length - fmt_base[0])%fmt_rep[0] != 0: logger.error( "Cannot send: Variable length message class \ 0x%x, id 0x%x has wrong length %i" % ( cl, id, length ) ) return stream = stream + struct.pack(fmt_base[1], *[payload_base[i] for i in fmt_base[2]]) if fmt_rep[0] != 0: for i in range(0, (length - fmt_base[0])/fmt_rep[0]): stream = stream + struct.pack(fmt_rep[1], *[payload_rep[i][j] for j in fmt_rep[2]]) stream = stream + struct.pack(" 0: satellites.append( (sat["SVID"], in_use, sat["Elev"], sat["Azim"], sat["CNO"]) ) self._updateSatellites( satellites ) def handle_NAV_TIMEUTC( self, data ): data = data[0] # We have valid GPS time (without leap seconds known) much earlier than # UTC and they differ by ~17secs at the moment. The leap seconds could # be cached so we would know the UTC time +- some seconds much earlier. if data["Valid"] & 0x04: time = calendar.timegm( (data["Year"], data["Month"], data["Day"], data["Hour"], data["Min"], data["Sec"]) ) self._updateTime( time ) # Ignore ACK packets for now def handle_ACK_ACK( self, data ): data = data[0] logger.debug( "Got ACK %s" % data ) if (data["ClsID"], data["MsgID"]) == CLIDPAIR["CFG-PRT"]: self.ack["CFG-PRT"] = 1 @dbus.service.method( "org.freesmartphone.GPS.UBX", "siaa{sv}", "") @resource.checkedsyncmethod def SendDebugPacket( self, clid, length, data ): self.send( clid, length, data ) @dbus.service.method( "org.freesmartphone.GPS.UBX", "s", "b") @resource.checkedsyncmethod def GetDebugFilter( self, clid ): return self.debugfilter.get( clid, False ) @dbus.service.method( "org.freesmartphone.GPS.UBX", "sb", "") @resource.checkedsyncmethod def SetDebugFilter( self, clid, state ): self.debugfilter[clid] = state @dbus.service.signal( "org.freesmartphone.GPS.UBX", "siaa{sv}" ) def DebugPacket( self, clid, length, data ): pass #vim: expandtab fso-frameworkd-0.10.1/framework/subsystems/ogsmd/000077500000000000000000000000001174525413000220505ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/ogsmd/__init__.py000066400000000000000000000000011174525413000241500ustar00rootroot00000000000000 fso-frameworkd-0.10.1/framework/subsystems/ogsmd/device.py000066400000000000000000001015041174525413000236620ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd Module: device """ MODULE_NAME = "ogsmd.device" __version__ = "0.9.14" from framework import resource from framework.config import config from modems import modemFactory, allModems, setCurrentModem import dbus import dbus.service import sys, os, types import logging logger = logging.getLogger( MODULE_NAME ) DBUS_INTERFACE_DEVICE = "org.freesmartphone.GSM.Device" DBUS_INTERFACE_SIM = "org.freesmartphone.GSM.SIM" DBUS_INTERFACE_SMS = "org.freesmartphone.GSM.SMS" DBUS_INTERFACE_NETWORK = "org.freesmartphone.GSM.Network" DBUS_INTERFACE_CALL = "org.freesmartphone.GSM.Call" DBUS_INTERFACE_PDP = "org.freesmartphone.GSM.PDP" DBUS_INTERFACE_CB = "org.freesmartphone.GSM.CB" DBUS_INTERFACE_MONITOR = "org.freesmartphone.GSM.Monitor" DBUS_INTERFACE_RESOURCE = "org.freesmartphone.Resource" DBUS_INTERFACE_DEBUG = "org.freesmartphone.GSM.Debug" DBUS_BUS_NAME_DEVICE = "org.freesmartphone.ogsmd" DBUS_OBJECT_PATH_DEVICE = "/org/freesmartphone/GSM/Device" #=========================================================================# class Device( resource.Resource ): #=========================================================================# """ This class handles the dbus interface of org.freesmartphone.GSM.* """ def __init__( self, bus, modemtype ): """ Init. """ self.bus = bus self.path = DBUS_OBJECT_PATH_DEVICE self.modemtype = modemtype self.modem = None dbus.service.Object.__init__( self, bus, self.path ) resource.Resource.__init__( self, bus, "GSM" ) logger.info( "%s %s initialized." % ( self.__class__.__name__, __version__ ) ) def __del__( self ): """ Destruct. """ pass # # dbus org.freesmartphone.Resource [inherited from framework.Resource] # def _enable( self, on_ok, on_error ): """ Enable (inherited from Resource) """ global mediator Modem, mediator = modemFactory( self.modemtype ) if Modem is None: estring = "Modem %s not in available modems: %s" % ( self.modemtype, allModems() ) logger.error( estring ) on_error( resource.ResourceError( estring ) ) else: self.modem = Modem( self, self.bus ) if self.modem is None: estring = "Cannot create Modem %s" % ( self.modemtype ) logger.error( estring ) on_error( resource.ResourceError( estring ) ) return setCurrentModem( self.modem ) self.modem.open( on_ok, on_error ) def _disable( self, on_ok, on_error ): """ Disable (inherited from Resource) """ if self.modem is not None: self.modem.close() self.modem = None setCurrentModem( self.modem ) on_ok() def _suspend( self, on_ok, on_error ): """ Suspend (inherited from Resource) """ if self.modem is not None: self.modem.prepareForSuspend( on_ok, on_error ) def _resume( self, on_ok, on_error ): """ Resume (inherited from Resource) """ if self.modem is not None: self.modem.recoverFromSuspend( lambda: self._recoverOk( on_ok, on_error ), on_error ) def _recoverOk( self, on_ok, on_error): mediator.NetworkGetStatus( self, lambda x: self._recoverStatusOk( x, on_ok, on_error ), on_error ) def _recoverStatusOk( self, status, on_ok, on_error ): self.Status( status ) on_ok() # # dbus org.freesmartphone.GSM.Device # @dbus.service.method( DBUS_INTERFACE_DEVICE, "", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def CancelCommand( self, dbus_ok, dbus_error ): mediator.CancelCommand( self, dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE_DEVICE, "", "a{sv}", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def GetInfo( self, dbus_ok, dbus_error ): mediator.DeviceGetInfo( self, dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE_DEVICE, "", "a{sv}", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def GetFeatures( self, dbus_ok, dbus_error ): mediator.DeviceGetFeatures( self, dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE_DEVICE, "", "b", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def GetAntennaPower( self, dbus_ok, dbus_error ): mediator.DeviceGetAntennaPower( self, dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE_DEVICE, "b", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def SetAntennaPower( self, power, dbus_ok, dbus_error ): mediator.DeviceSetAntennaPower( self, dbus_ok, dbus_error, power=power ) @dbus.service.method( DBUS_INTERFACE_DEVICE, "", "b", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def GetSimBuffersSms( self, dbus_ok, dbus_error ): mediator.DeviceGetSimBuffersSms( self, dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE_DEVICE, "b", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def SetSimBuffersSms( self, sim_buffers_sms, dbus_ok, dbus_error ): mediator.DeviceSetSimBuffersSms( self, dbus_ok, dbus_error, sim_buffers_sms=sim_buffers_sms ) @dbus.service.method( DBUS_INTERFACE_DEVICE, "", "i", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def GetSpeakerVolume( self, dbus_ok, dbus_error ): mediator.DeviceGetSpeakerVolume( self, dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE_DEVICE, "i", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def SetSpeakerVolume( self, modem_volume, dbus_ok, dbus_error ): mediator.DeviceSetSpeakerVolume( self, dbus_ok, dbus_error, modem_volume=modem_volume ) @dbus.service.method( DBUS_INTERFACE_DEVICE, "", "b", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def GetMicrophoneMuted( self, dbus_ok, dbus_error ): mediator.DeviceGetMicrophoneMuted( self, dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE_DEVICE, "b", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def SetMicrophoneMuted( self, muted, dbus_ok, dbus_error ): mediator.DeviceSetMicrophoneMuted( self, dbus_ok, dbus_error, muted=muted ) @dbus.service.method( DBUS_INTERFACE_DEVICE, "", "si", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def GetPowerStatus( self, dbus_ok, dbus_error ): mediator.DeviceGetPowerStatus( self, dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE_DEVICE, "", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def SetRTC( self, dbus_ok, dbus_error ): mediator.DeviceSetRTC( self, dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE_DEVICE, "", "i", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def GetRTC( self, dbus_ok, dbus_error ): mediator.DeviceGetRTC( self, dbus_ok, dbus_error ) @resource.queuedsignal @dbus.service.signal( DBUS_INTERFACE_DEVICE, "sb" ) def KeypadEvent( self, name, pressed ): logger.info( "key %s has been %s", name, "pressed" if pressed else "released" ) # # dbus org.freesmartphone.GSM.SIM # ### SIM auth @dbus.service.method( DBUS_INTERFACE_SIM, "", "s", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def GetAuthStatus( self, dbus_ok, dbus_error ): mediator.SimGetAuthStatus( self, dbus_ok, dbus_error ) @resource.queuedsignal @dbus.service.signal( DBUS_INTERFACE_SIM, "s" ) def AuthStatus( self, status ): logger.info( "auth status changed to %s", status ) self.modem.setSimPinState( status ) @dbus.service.method( DBUS_INTERFACE_SIM, "s", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def SendAuthCode( self, code, dbus_ok, dbus_error ): mediator.SimSendAuthCode( self, dbus_ok, dbus_error, code=code ) @dbus.service.method( DBUS_INTERFACE_SIM, "ss", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def Unlock( self, puk, new_pin, dbus_ok, dbus_error ): mediator.SimUnlock( self, dbus_ok, dbus_error, puk=puk, new_pin=new_pin ) @dbus.service.method( DBUS_INTERFACE_SIM, "ss", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def ChangeAuthCode( self, old_pin, new_pin, dbus_ok, dbus_error ): mediator.SimChangeAuthCode( self, dbus_ok, dbus_error, old_pin=old_pin, new_pin=new_pin ) @dbus.service.method( DBUS_INTERFACE_SIM, "bs", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def SetAuthCodeRequired( self, required, pin, dbus_ok, dbus_error ): mediator.SimSetAuthCodeRequired( self, dbus_ok, dbus_error, required=required, pin=pin ) @dbus.service.method( DBUS_INTERFACE_SIM, "", "b", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def GetAuthCodeRequired( self, dbus_ok, dbus_error ): mediator.SimGetAuthCodeRequired( self, dbus_ok, dbus_error ) ### SIM info and low-level access @dbus.service.method( DBUS_INTERFACE_SIM, "", "b", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def GetSimReady( self, dbus_ok, dbus_error ): dbus_ok( self.modem.simReady() == True ) @resource.queuedsignal @dbus.service.signal( DBUS_INTERFACE_SIM, "b" ) def ReadyStatus( self, status ): logger.info( "sim ready status %s", status ) self.modem.setSimReady( status ) @dbus.service.method( DBUS_INTERFACE_SIM, "", "a{sv}", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def GetSimInfo( self, dbus_ok, dbus_error ): mediator.SimGetSimInfo( self, dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE_SIM, "s", "s", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def SendGenericSimCommand( self, command, dbus_ok, dbus_error ): mediator.SimSendGenericSimCommand( self, dbus_ok, dbus_error, command=command ) @dbus.service.method( DBUS_INTERFACE_SIM, "iiiiis", "iis", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def SendRestrictedSimCommand( self, command, fileid, p1, p2, p3, data, dbus_ok, dbus_error ): mediator.SimSendRestrictedSimCommand( self, dbus_ok, dbus_error, command=command, fileid=fileid, p1=p1, p2=p2, p3=p3, data=data ) @dbus.service.method( DBUS_INTERFACE_SIM, "", "a(siii)", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def GetHomeZones( self, dbus_ok, dbus_error ): mediator.SimGetHomeZones( self, dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE_SIM, "", "s", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def GetIssuer( self, dbus_ok, dbus_error ): mediator.SimGetIssuer( self, dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE_SIM, "", "a{ss}", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def GetProviderList( self, dbus_ok, dbus_error ): mediator.SimGetProviderList( self, dbus_ok, dbus_error ) ### SIM phonebook @dbus.service.method( DBUS_INTERFACE_SIM, "", "as", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def ListPhonebooks( self, dbus_ok, dbus_error ): mediator.SimListPhonebooks( self, dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE_SIM, "s", "a{sv}", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def GetPhonebookInfo( self, category, dbus_ok, dbus_error ): mediator.SimGetPhonebookInfo( self, dbus_ok, dbus_error, category=category ) @dbus.service.method( DBUS_INTERFACE_SIM, "s", "ii", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def GetPhonebookStorageInfo( self, category, dbus_ok, dbus_error ): mediator.SimGetPhonebookStorageInfo( self, dbus_ok, dbus_error, category=category ) @dbus.service.method( DBUS_INTERFACE_SIM, "sii", "a(iss)", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def RetrievePhonebook( self, category, indexFirst, indexLast, dbus_ok, dbus_error ): mediator.SimRetrievePhonebook( self, dbus_ok, dbus_error, category=category, indexFirst=indexFirst, indexLast=indexLast ) @dbus.service.method( DBUS_INTERFACE_SIM, "si", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def DeleteEntry( self, category, index, dbus_ok, dbus_error ): mediator.SimDeleteEntry( self, dbus_ok, dbus_error, category=category, index=index ) @dbus.service.method( DBUS_INTERFACE_SIM, "siss", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def StoreEntry( self, category, index, name, number, dbus_ok, dbus_error ): mediator.SimStoreEntry( self, dbus_ok, dbus_error, category=category, index=index, name=name, number=number ) @dbus.service.method( DBUS_INTERFACE_SIM, "si", "ss", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def RetrieveEntry( self, category, index, dbus_ok, dbus_error ): mediator.SimRetrieveEntry( self, dbus_ok, dbus_error, category=category, index=index ) ### SIM messagebook @dbus.service.method( DBUS_INTERFACE_SIM, "", "a{sv}", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def GetMessagebookInfo( self, dbus_ok, dbus_error ): mediator.SimGetMessagebookInfo( self, dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE_SIM, "s", "a(isssa{sv})", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def RetrieveMessagebook( self, category, dbus_ok, dbus_error ): mediator.SimRetrieveMessagebook( self, dbus_ok, dbus_error, category=category ) @dbus.service.method( DBUS_INTERFACE_SIM, "i", "sssa{sv}", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def RetrieveMessage( self, index, dbus_ok, dbus_error ): mediator.SimRetrieveMessage( self, dbus_ok, dbus_error, index=index ) @dbus.service.method( DBUS_INTERFACE_SIM, "", "s", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def GetServiceCenterNumber( self, dbus_ok, dbus_error ): mediator.SimGetServiceCenterNumber( self, dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE_SIM, "s", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def SetServiceCenterNumber( self, number, dbus_ok, dbus_error ): mediator.SimSetServiceCenterNumber( self, dbus_ok, dbus_error, number=number ) @dbus.service.method( DBUS_INTERFACE_SIM, "ssa{sv}", "i", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def StoreMessage( self, number, contents, properties, dbus_ok, dbus_error ): mediator.SimStoreMessage( self, dbus_ok, dbus_error, number=number, contents=contents, properties=properties ) @dbus.service.method( DBUS_INTERFACE_SIM, "i", "is", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def SendStoredMessage( self, index, dbus_ok, dbus_error ): mediator.SimSendStoredMessage( self, dbus_ok, dbus_error, index=index ) @dbus.service.method( DBUS_INTERFACE_SIM, "i", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def DeleteMessage( self, index, dbus_ok, dbus_error ): mediator.SimDeleteMessage( self, dbus_ok, dbus_error, index=index ) @resource.queuedsignal @dbus.service.signal( DBUS_INTERFACE_SIM, "i" ) def IncomingStoredMessage( self, index ): logger.info( "incoming message on sim storage index %s", index ) @resource.queuedsignal @dbus.service.signal( DBUS_INTERFACE_SIM, "" ) def MemoryFull( self ): logger.info( "sim memory full" ) # # dbus org.freesmartphone.SMS # @dbus.service.method( DBUS_INTERFACE_SMS, "ssa{sv}", "is", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def SendMessage( self, number, contents, properties, dbus_ok, dbus_error ): mediator.SmsSendMessage( self, dbus_ok, dbus_error, number=number, contents=contents, properties=properties ) @dbus.service.method( DBUS_INTERFACE_SMS, "sa{sv}", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def AckMessage( self, contents, properties, dbus_ok, dbus_error ): mediator.SmsAckMessage( self, dbus_ok, dbus_error, contents=contents, properties=properties ) @dbus.service.method( DBUS_INTERFACE_SMS, "sa{sv}", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def NackMessage( self, contents, properties, dbus_ok, dbus_error ): mediator.SmsNackMessage( self, dbus_ok, dbus_error, contents=contents, properties=properties ) @resource.queuedsignal @dbus.service.signal( DBUS_INTERFACE_SMS, "ssa{sv}" ) def IncomingMessage( self, address, text, properties ): logger.info( "incoming message (unbuffered) from %s", address ) @resource.queuedsignal @dbus.service.signal( DBUS_INTERFACE_SMS, "ssa{sv}" ) def IncomingMessageReceipt( self, number, text, properties ): logger.info( "incoming message delivery report from %s", number ) # # dbus org.freesmartphone.GSM.Network # @dbus.service.method( DBUS_INTERFACE_NETWORK, "", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def Register( self, dbus_ok, dbus_error ): mediator.NetworkRegister( self, dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE_NETWORK, "", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def Unregister( self, dbus_ok, dbus_error ): mediator.NetworkUnregister( self, dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE_NETWORK, "", "a{sv}", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def GetStatus( self, dbus_ok, dbus_error ): mediator.NetworkGetStatus( self, dbus_ok, dbus_error ) @resource.queuedsignal @dbus.service.signal( DBUS_INTERFACE_NETWORK, "a{sv}" ) def Status( self, status ): logger.info( "org.freesmartphone.GSM.Network.Status: %s", status ) @dbus.service.method( DBUS_INTERFACE_NETWORK, "", "i", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def GetSignalStrength( self, dbus_ok, dbus_error ): mediator.NetworkGetSignalStrength( self, dbus_ok, dbus_error ) @resource.queuedsignal @dbus.service.signal( DBUS_INTERFACE_NETWORK, "i" ) def SignalStrength( self, strength ): logger.info( "org.freesmartphone.GSM.Network.SignalStrength: %s", strength ) @dbus.service.method( DBUS_INTERFACE_NETWORK, "", "a(sssss)", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def ListProviders( self, dbus_ok, dbus_error ): mediator.NetworkListProviders( self, dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE_NETWORK, "s", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def RegisterWithProvider( self, operator_code, dbus_ok, dbus_error ): mediator.NetworkRegisterWithProvider( self, dbus_ok, dbus_error, operator_code=operator_code ) @dbus.service.method( DBUS_INTERFACE_NETWORK, "", "ss", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def GetCountryCode( self, dbus_ok, dbus_error ): mediator.NetworkGetCountryCode( self, dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE_NETWORK, "s", "a{sv}", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def GetCallForwarding( self, reason, dbus_ok, dbus_error ): mediator.NetworkGetCallForwarding( self, dbus_ok, dbus_error, reason=reason ) @dbus.service.method( DBUS_INTERFACE_NETWORK, "sssi", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def EnableCallForwarding( self, reason, class_, number, timeout, dbus_ok, dbus_error ): mediator.NetworkEnableCallForwarding( self, dbus_ok, dbus_error, reason=reason, class_=class_, number=number, timeout=timeout ) @dbus.service.method( DBUS_INTERFACE_NETWORK, "ss", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def DisableCallForwarding( self, reason, class_, dbus_ok, dbus_error ): mediator.NetworkDisableCallForwarding( self, dbus_ok, dbus_error, reason=reason, class_=class_ ) @dbus.service.method( DBUS_INTERFACE_NETWORK, "", "s", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def GetCallingIdentification( self, dbus_ok, dbus_error ): mediator.NetworkGetCallingIdentification( self, dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE_NETWORK, "s", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def SetCallingIdentification( self, status, dbus_ok, dbus_error ): mediator.NetworkSetCallingIdentification( self, dbus_ok, dbus_error, status=status ) @dbus.service.method( DBUS_INTERFACE_NETWORK, "s", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def SendUssdRequest( self, request, dbus_ok, dbus_error ): mediator.NetworkSendUssdRequest( self, dbus_ok, dbus_error, request=request ) @resource.queuedsignal @dbus.service.signal( DBUS_INTERFACE_NETWORK, "ss" ) def IncomingUssd( self, mode, message ): logger.info( "org.freesmartphone.GSM.Network.IncomingUssd: %s: %s", mode, message ) @resource.queuedsignal @dbus.service.signal( DBUS_INTERFACE_NETWORK, "ss" ) def CipherStatus( self, gsm, gprs ): logger.info( "org.freesmartphone.GSM.Network.CypherStatus: %s: %s", gsm, gprs ) @resource.queuedsignal @dbus.service.signal( DBUS_INTERFACE_NETWORK, "i" ) def TimeZoneReport( self, timezone ): logger.info( "org.freesmartphone.GSM.Network.TimeZoneReport: %d" ) # # dbus org.freesmartphone.GSM.Call # @dbus.service.method( DBUS_INTERFACE_CALL, "s", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def Emergency( self, number, dbus_ok, dbus_error ): mediator.CallEmergency( self, dbus_ok, dbus_error, number=number ) @resource.queuedsignal @dbus.service.signal( DBUS_INTERFACE_CALL, "isa{sv}" ) def CallStatus( self, index, status, properties ): logger.info( "org.freesmartphone.GSM.Call.CallStatus: %s %s %s", index, status, properties ) @dbus.service.method( DBUS_INTERFACE_CALL, "i", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def Activate( self, index, dbus_ok, dbus_error ): mediator.CallActivate( self, dbus_ok, dbus_error, index=index ) @dbus.service.method( DBUS_INTERFACE_CALL, "i", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def ActivateConference( self, index, dbus_ok, dbus_error ): mediator.CallActivateConference( self, dbus_ok, dbus_error, index=index ) @dbus.service.method( DBUS_INTERFACE_CALL, "i", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def Release( self, index, dbus_ok, dbus_error ): mediator.CallRelease( self, dbus_ok, dbus_error, index=index ) @dbus.service.method( DBUS_INTERFACE_CALL, "", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def ReleaseHeld( self, dbus_ok, dbus_error ): mediator.CallReleaseHeld( self, dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE_CALL, "", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def ReleaseAll( self, dbus_ok, dbus_error ): mediator.CallReleaseAll( self, dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE_CALL, "ss", "i", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def Initiate( self, number, type_, dbus_ok, dbus_error ): mediator.CallInitiate( self, dbus_ok, dbus_error, number=number, calltype=type_ ) @dbus.service.method( DBUS_INTERFACE_CALL, "", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def HoldActive( self, dbus_ok, dbus_error ): mediator.CallHoldActive( self, dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE_CALL, "s", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def Transfer( self, number, dbus_ok, dbus_error ): mediator.CallTransfer( self, dbus_ok, dbus_error, number=number ) @dbus.service.method( DBUS_INTERFACE_CALL, "", "a(isa{sv})", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def ListCalls( self, dbus_ok, dbus_error ): mediator.CallListCalls( self, dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE_CALL, "s", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def SendDtmf( self, tones, dbus_ok, dbus_error ): mediator.CallSendDtmf( self, dbus_ok, dbus_error, tones=tones ) # # dbus org.freesmartphone.GSM.PDP # @dbus.service.method( DBUS_INTERFACE_PDP, "", "as", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def ListAvailableGprsClasses( self, dbus_ok, dbus_error ): mediator.PdpListAvailableGprsClasses( self, dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE_PDP, "", "s", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def GetCurrentGprsClass( self, dbus_ok, dbus_error ): mediator.PdpGetCurrentGprsClass( self, dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE_PDP, "s", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def SetCurrentGprsClass( self, class_, dbus_ok, dbus_error ): mediator.PdpSetCurrentGprsClass( self, dbus_ok, dbus_error, class_=class_ ) @dbus.service.method( DBUS_INTERFACE_PDP, "", "a{sv}", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def GetNetworkStatus( self, dbus_ok, dbus_error ): mediator.PdpGetNetworkStatus( self, dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE_PDP, "sss", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def ActivateContext( self, apn, user, password, dbus_ok, dbus_error ): mediator.PdpActivateContext( self, dbus_ok, dbus_error, apn=apn, user=user, password=password ) @dbus.service.method( DBUS_INTERFACE_PDP, "", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def DeactivateContext( self, dbus_ok, dbus_error ): mediator.PdpDeactivateContext( self, dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE_PDP, "", "s", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def GetContextStatus( self, dbus_ok, dbus_error ): mediator.PdpGetContextStatus( self, dbus_ok, dbus_error ) @resource.queuedsignal @dbus.service.signal( DBUS_INTERFACE_PDP, "isa{sv}" ) def ContextStatus( self, index, status, properties ): logger.info( "org.freesmartphone.GSM.PDP.ContextStatus: %s %s %s", index, status, properties ) @resource.queuedsignal @dbus.service.signal( DBUS_INTERFACE_PDP, "a{sv}" ) def NetworkStatus( self, status ): logger.info( "org.freesmartphone.GSM.PDP.NetworkStatus: %s", status ) # # dbus org.freesmartphone.GSM.CB # @dbus.service.method( DBUS_INTERFACE_CB, "", "s", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def GetCellBroadcastSubscriptions( self, dbus_ok, dbus_error ): mediator.CbGetCellBroadcastSubscriptions( self, dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE_CB, "s", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def SetCellBroadcastSubscriptions( self, channels, dbus_ok, dbus_error ): mediator.CbSetCellBroadcastSubscriptions( self, dbus_ok, dbus_error, channels=channels ) @resource.queuedsignal @dbus.service.signal( DBUS_INTERFACE_CB, "is" ) def IncomingCellBroadcast( self, channel, data ): logger.info( "org.freesmartphone.GSM.CB.IncomingCellBroadcast: %s %s", channel, data ) # # dbus org.freesmartphone.GSM.Monitor # @dbus.service.method( DBUS_INTERFACE_MONITOR, "", "a{sv}", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def GetServingCellInformation( self, dbus_ok, dbus_error ): mediator.MonitorGetServingCellInformation( self, dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE_MONITOR, "", "aa{sv}", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def GetNeighbourCellInformation( self, dbus_ok, dbus_error ): mediator.MonitorGetNeighbourCellInformation( self, dbus_ok, dbus_error ) # # dbus org.freesmartphone.GSM.Debug # WARNING: Do not rely on that, it might vanish any time # @dbus.service.method( DBUS_INTERFACE_DEBUG, "s", "as", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def DebugCommand( self, command, dbus_ok, dbus_error ): mediator.DebugCommand( self, dbus_ok, dbus_error, command=command ) @dbus.service.method( DBUS_INTERFACE_DEBUG, "", "as", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def DebugListChannels( self, dbus_ok, dbus_error ): dbus_ok( self.modem.channels() ) @dbus.service.method( DBUS_INTERFACE_DEBUG, "ss", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def DebugInjectString( self, channel, string, dbus_ok, dbus_error ): self.modem.inject( channel, str(string) ) dbus_ok() @dbus.service.method( DBUS_INTERFACE_DEBUG, "s", "s", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def DebugEcho( self, echo, dbus_ok, dbus_error ): import time time.sleep( 2 ) dbus_ok( echo ) dbus_error( "foo" ) dbus_ok( echo ) #=========================================================================# def factory( prefix, controller ): #=========================================================================# sys.path.append( os.path.dirname( os.path.dirname( __file__ ) ) ) modemtype = config.getValue( "ogsmd", "modemtype", "unspecified" ) device = Device( controller.bus, modemtype ) return [ device ] #=========================================================================# if __name__ == "__main__": #=========================================================================# pass fso-frameworkd-0.10.1/framework/subsystems/ogsmd/error.py000066400000000000000000000113521174525413000235550ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Module: error DBus Exception Classes for org.freesmartphone.GSM* """ from dbus import DBusException #=========================================================================# # GSM exceptions, unspecific to actual interface #=========================================================================# class UnsupportedCommand( DBusException ): _dbus_error_name = "org.freesmartphone.GSM.UnsupportedCommand" class InvalidParameter( DBusException ): _dbus_error_name = "org.freesmartphone.GSM.InvalidParameter" class InternalException( DBusException ): _dbus_error_name = "org.freesmartphone.GSM.InternalError" class NoCommandToCancelException( DBusException ): _dbus_error_name = "org.freesmartphone.GSM.NoCommandToCancel" class CommandCancelled( DBusException ): _dbus_error_name = "org.freesmartphone.GSM.CommandCancelled" #=========================================================================# # Base classes for interface-specific exceptions #=========================================================================# class AbstractDeviceException( DBusException ): _dbus_error_name = "org.freesmartphone.GSM.Device" class AbstractSimException( DBusException ): _dbus_error_name = "org.freesmartphone.GSM.SIM" class AbstractNetworkException( DBusException ): _dbus_error_name = "org.freesmartphone.GSM.Network" class AbstractCallException( DBusException ): _dbus_error_name = "org.freesmartphone.GSM.Call" class AbstractPdpException( DBusException ): _dbus_error_name = "org.freesmartphone.GSM.PDP" #=========================================================================# # Device exceptions #=========================================================================# class DeviceTimeout( AbstractDeviceException ): _dbus_error_name = "org.freesmartphone.GSM.Device.Timeout" class DeviceNotPresent( AbstractDeviceException ): _dbus_error_name = "org.freesmartphone.GSM.Device.NotPresent" class DeviceFailed( AbstractDeviceException ): _dbus_error_name = "org.freesmartphone.GSM.Device.Failed" #=========================================================================# # SIM exceptions #=========================================================================# class SimNotPresent( AbstractSimException ): _dbus_error_name = "org.freesmartphone.GSM.SIM.NotPresent" class SimAuthFailed( AbstractSimException ): _dbus_error_name = "org.freesmartphone.GSM.SIM.AuthFailed" class SimBlocked( AbstractSimException ): _dbus_error_name = "org.freesmartphone.GSM.SIM.Blocked" class SimNotFound( AbstractSimException ): _dbus_error_name = "org.freesmartphone.GSM.SIM.NotFound" class SimMemoryFull( AbstractSimException ): _dbus_error_name = "org.freesmartphone.GSM.SIM.MemoryFull" class SimInvalidIndex( AbstractSimException ): _dbus_error_name = "org.freesmartphone.GSM.SIM.InvalidIndex" class SimNotReady( AbstractSimException ): _dbus_error_name = "org.freesmartphone.GSM.SIM.NotReady" #=========================================================================# # Network exceptions #=========================================================================# class NetworkNotPresent( AbstractNetworkException ): _dbus_error_name = "org.freesmartphone.GSM.Network.NotPresent" class NetworkUnauthorized( AbstractNetworkException ): _dbus_error_name = "org.freesmartphone.GSM.Network.Unauthorized" class NetworkNotSupported( AbstractNetworkException ): _dbus_error_name = "org.freesmartphone.GSM.Network.NotSupported" class NetworkNotFound( AbstractNetworkException ): _dbus_error_name = "org.freesmartphone.GSM.Network.NotFound" #=========================================================================# # Call exceptions #=========================================================================# class CallNoCarrier( AbstractCallException ): _dbus_error_name = "org.freesmartphone.GSM.Call.NoCarrier" class CallNotFound( AbstractCallException ): _dbus_error_name = "org.freesmartphone.GSM.Call.NotFound" class CallNotAnEmergencyNumber( AbstractCallException ): _dbus_error_name = "org.freesmartphone.GSM.Call.NotAnEmergencyNumber" #=========================================================================# # PDP exceptions #=========================================================================# class PdpNoCarrier( AbstractPdpException ): _dbus_error_name = "org.freesmartphone.GSM.PDP.NoCarrier" class PdpNotFound( AbstractPdpException ): _dbus_error_name = "org.freesmartphone.GSM.PDP.NotFound" class PdpUnauthrized( AbstractPdpException ): _dbus_error_name = "org.freesmartphone.GSM.PDP.Unauthorized" fso-frameworkd-0.10.1/framework/subsystems/ogsmd/gsm/000077500000000000000000000000001174525413000226365ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/ogsmd/gsm/__init__.py000066400000000000000000000000001174525413000247350ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/ogsmd/gsm/callback.py000066400000000000000000000005061174525413000247450ustar00rootroot00000000000000from ogsmd.gsm.decor import logged class SimpleCallback( object ): @logged def __init__( self, callback, *args ): self._callback = callback self._args = args @logged def __call__( self, *args ): apply( self._callback, self._args, {} ) @logged def __del__( self ): pass fso-frameworkd-0.10.1/framework/subsystems/ogsmd/gsm/celldb.py000066400000000000000000000077321174525413000244460ustar00rootroot00000000000000from sys import stdin, stdout, stderr, argv from math import sqrt from struct import calcsize, pack, unpack CELL_DB = '/etc/freesmartphone/ogsmd/cell.db' LA_DB = '/etc/freesmartphone/ogsmd/la.db' class SimpleCenter(object): # FIXME port pyproj to the neo and use it def __init__(self): self.points = [] def addPoint(self, point): self.points.append(point) def calc(self): if not self.points: return None x_min = min([point[0] for point in self.points]) x_max = max([point[0] for point in self.points]) x_mid = (x_min+x_max)/2 x_size = x_max-x_min y_min = min([point[1] for point in self.points]) y_max = max([point[1] for point in self.points]) y_mid = (y_min+y_max)/2 y_size = y_max-y_min z_min = min([point[2] for point in self.points]) z_max = max([point[2] for point in self.points]) z_mid = (z_min+z_max)/2 z_size = z_max-z_min size = sqrt(x_size**2 + y_size**2 + z_size**2) return (x_mid, y_mid, z_mid, size, len(self.points)) class ProjCenter(object): def __init__(self): from pyproj import Proj, transform self.pwgs84 = Proj(proj='lonlat',datum='WGS84') self.pecef = Proj(proj='geocent', datum='WGS84') self.points = [] def addPoint(self, point): self.points.append(point) def calc(self): if not self.points: return None ecefs = [] for lat, long, alt in self.points: ecefs.append(transform(self.pwgs84, self.pecef, long, lat, alt)) x_min = min([ecef[0] for ecef in ecefs]) x_max = max([ecef[0] for ecef in ecefs]) x_mid = (x_min+x_max)/2 x_size = x_max-x_min y_min = min([ecef[1] for ecef in ecefs]) y_max = max([ecef[1] for ecef in ecefs]) y_mid = (y_min+y_max)/2 y_size = y_max-y_min z_min = min([ecef[2] for ecef in ecefs]) z_max = max([ecef[2] for ecef in ecefs]) z_mid = (z_min+z_max)/2 z_size = z_max-z_min size = sqrt(x_size**2 + y_size**2 + z_size**2) long, lat, alt = transform(self.pecef, self.pwgs84, x_mid, y_mid, z_mid) return (lat, long, alt, size, len(ecefs)) Center = SimpleCenter def find(f, format, pattern_format, pattern_data): format_size = calcsize(format) pattern_size = calcsize(pattern_format) pattern = pack(pattern_format, *pattern_data) l = 0 f.seek(0, 2) r = f.tell()/format_size while l < r: m = (r+l)/2 f.seek(m*format_size) data = f.read(format_size) if pattern == data[:pattern_size]: return unpack(format, data) elif pattern (C) 2008-2009 Openmoko, Inc. GPLv2 or later Package: ogsmd.gsm Module: channel This module provides communication channel abstractions that transport their data over a (virtual) serial line. """ __version__ = "0.9.18.2" MODULE_NAME = "ogsmd.channel" import parser import gobject # pygobject import serial # pyserial import Queue, fcntl, os, time, types, re # stdlib import logging logger = logging.getLogger( MODULE_NAME ) PRIORITY_RTR = -20 PRIORITY_RTS = -10 PRIORITY_HUP = -5 DEFAULT_CHANNEL_TIMEOUT = 5*60 AUTOPREFIX = re.compile( "A?T?(?P[\+%!*$\^_&@][A-Z]+)" ) # keys are commands, values are autocomputed response lists AUTOPREFIX_CACHE = {} #=========================================================================# class PeekholeQueue( Queue.Queue ): #=========================================================================# """ This class extends the Queue with a method to peek at the first element without having to remove this from the queue. """ def peek( self ): if self.empty(): return None else: return self.queue[0] #=========================================================================# class VirtualChannel( object ): #=========================================================================# """ This class represents a sequential serial transport channel. """ # # public API # def __init__( self, pathfactory, name=None, **kwargs ): """Construct""" self.pathfactory = pathfactory self.name = name or self.__class__.__name__ self.connected = False self.watchReadyToSend = None self.watchReadyToRead = None self.serial = None def __repr__( self ): return "<%s via %s>" % ( self.__class__.__name__, self.serial.port if self.serial is not None else "unknown" ) def open( self, path=None ): """ Allocate a channel and opens a serial port. Returns True, if successful. False, otherwise. """ if self.connected: raise ValueError( "already connected" ) # gather path path = self.pathfactory( self.name ) if path is None or path == "": return False # set up serial port object and open it logger.info( "%s: initializing" % self ) self.serial = serial.Serial() self.serial.port = str( path ) self.serial.baudrate = 115200 self.serial.rtscts = True self.serial.xonxoff = False self.serial.bytesize = serial.EIGHTBITS self.serial.parity = serial.PARITY_NONE self.serial.stopbits = serial.STOPBITS_ONE self.serial.timeout = None try: self.serial.open() except serial.serialutil.SerialException: # Fail gracefully if the device isn't there (yet) return False if not self.serial.isOpen(): return False # nonblocking # fcntl.fcntl( self.serial.fd, fcntl.F_SETFL, os.O_NONBLOCK ) if not self._hookLowLevelInit(): return False # set up I/O watches for mainloop self.watchReadyToRead = gobject.io_add_watch( self.serial.fd, gobject.IO_IN, self._readyToRead, priority=PRIORITY_RTR ) self.watchReadyToSend = gobject.io_add_watch( self.serial.fd, gobject.IO_OUT, self._readyToSend, priority=PRIORITY_RTS ) self.watchHUP = gobject.io_add_watch( self.serial.fd, gobject.IO_HUP, self._hup, priority=PRIORITY_HUP ) self.connected = self.serial.isOpen() return self.connected def isOpen( self ): return self.connected def readyToRead( self, data ): """ Called when a data block has been successfully received from the source. The default implementation does nothing. """ pass def readyToSend( self ): """ Called when the source is ready to receive data. The default implementation does nothing. """ pass def suspend( self, ok_callback, error_callback ): """ Called when the channel needs to be prepared for a suspend. The default implementation does nothing but call the ok_callback """ ok_callback( self ) def resume( self, ok_callback, error_callback ): """ Called when the channel needs to reinit after resume. The default implementation does nothing but call the ok_callback """ ok_callback( self ) def write( self, data ): """Write data to the transport.""" self._write( data ) def freeze( self ): """Pause reading from the transport.""" if not self.watchReadyToRead: logger.warning( "%s: freeze() called without watch being setup", self ) else: gobject.source_remove( self.watchReadyToRead ) def thaw( self ): """Resume reading from the transport.""" if not self.watchReadyToRead: logger.warning( "%s: thaw() called with watch being already setup", self ) else: self.watchReadyToRead = gobject.io_add_watch( self.serial.fd, gobject.IO_IN, self._readyToRead, priority=PRIORITY_RTR ) def close( self ): """ Close the serial port and free the virtual channel. Returns True, if serial port could be closed. False, otherwise. """ if not self.connected: return True if self.watchReadyToSend: gobject.source_remove( self.watchReadyToSend ) if self.watchReadyToRead: gobject.source_remove( self.watchReadyToRead ) if self.watchHUP: gobject.source_remove( self.watchHUP ) if self.serial.isOpen(): self.serial.close() self.connected = self.serial.isOpen() return not self.connected def port( self ): """ Return name of transport. """ return self.serial.port # # hooks # def _hookLowLevelInit( self ): """Override, if your channel needs a special low level init.""" return True def _hookPreReading( self ): """Override, if the channel needs to be prepared for reading.""" pass def _hookPostReading( self ): """Override, if special handling is necessary after reading.""" pass def _hookPreSending( self ): """Override, if special handling is necessary before reading.""" pass def _hookPostSending( self ): """Override, if special handling is necessary after reading.""" pass def _hookHandleHupCondition( self ): """Override, if special handling is necessary on HUP.""" pass # # private API # def _hup( self, source, condition ): """Called, if there is a HUP condition on the source.""" if ( source != self.serial.fd or condition != gobject.IO_HUP ): logger.warning( "ready to read, but bogus condition %d or source %d. Ignoring", condition, source ) return False logger.info( "%s: HUP on socket" % self ) self._hookHandleHupCondition() return True # gobject, call me again def _readyToRead( self, source, condition ): """Called, if data is available on the source.""" if ( source != self.serial.fd or condition != gobject.IO_IN ): logger.warning( "ready to read, but bogus condition %d or source %d. Ignoring", condition, source ) return False #logger.debug( "%s: _readyToRead: watch timeout = %s", self, repr( self.watchTimeout ) ) self._hookPreReading() data = self._lowlevelRead() logger.debug( "%s: got %d bytes: %s" % ( self, len(data), repr(data) ) ) self.readyToRead( data ) self._hookPostReading() return True # gobject, call me again def _lowlevelRead( self ): """Called to read data from the port.""" try: inWaiting = self.serial.inWaiting() except IOError: inWaiting = 0 # should we really continue here? return self.serial.read( inWaiting ) def _lowlevelWrite( self, data ): """Called to write data to the port.""" self.serial.write( data ) def _readyToSend( self, source, condition ): """Called, if source is ready to receive data.""" if ( source != self.serial.fd or condition != gobject.IO_OUT ): logger.warning( "ready to send, but bogus condition %d or source %d. Ignoring", condition, source ) return False #logger.debug( "%s: _readyToSend: watch timeout = %s", self, repr( self.watchTimeout ) ) if False: # make sure nothing has been queued up in the buffer in the meantime while self.serial.inWaiting(): logger.warning( "_readyToSend, but new data already in the buffer. processing" ) self._readyToRead( self.serial.fd, gobject.IO_IN ) self._hookPreSending() self.readyToSend() self.watchReadyToSend = None self._hookPostSending() return False # gobject, don't call me again def __del__( self ): """Destruct""" self.close() #=========================================================================# class QueuedVirtualChannel( VirtualChannel ): #=========================================================================# """ A virtual channel featuring a command queue. Once you put a command into the command queue, it sets up a watch on 'ready-to-send'. Once the watch triggers, one command is taken out of the command queue and sent over the channel. When the response arrives, the next command is taken out of the queue. If there are no more commands, the 'ready-to-send' watch is removed. """ def __init__( self, *args, **kwargs ): """ Initialize. """ VirtualChannel.__init__( self, *args, **kwargs ) self.q = PeekholeQueue() self.installParser() self.watchTimeout = None self.timeout = kwargs.get( "timeout", DEFAULT_CHANNEL_TIMEOUT ) logger.info( "%s: Creating channel with timeout = %d seconds", self, self.timeout ) def installParser( self ): """ Install a low level parser for this channel. Override this, if you need to install a special low level parser. """ self.parser = parser.LowlevelAtParser( self._handleResponseToRequest, self._handleUnsolicitedResponse ) def enqueue( self, data, response_cb=None, error_cb=None, prefixes=None ): """ Enqueue data block for sending over the channel. """ if prefixes is None: try: prefixes = AUTOPREFIX_CACHE[data] except KeyError: AUTOPREFIX_CACHE[data] = prefixes = set( AUTOPREFIX.findall( data ) ) logger.debug( "%s: Autogenerated prefixes for command %s: %s", self, repr(data), prefixes ) if type( data ) == types.UnicodeType: data = str( data ) self.q.put( ( data, response_cb, error_cb, prefixes ) ) if not self.connected: return if self.q.qsize() == 1 and not self.watchReadyToSend: self.watchReadyToSend = gobject.io_add_watch( self.serial.fd, gobject.IO_OUT, self._readyToSend, priority=PRIORITY_RTS ) def pendingCommands( self ): """ Return the number of pending commands. """ return len( self.q.queue ) def isWaitingForResponse( self ): """ Return True, when a command is currently waiting for a response. Return False, otherwise. """ return self.watchTimeout is not None def validPrefixes( self ): """ Return a list of prefixes that are valid for the command in execution. """ if not self.isWaitingForResponse(): return [] return self.q.peek()[3] def cancelCurrentCommand( self ): """ Cancel the command currently in process. """ if self.watchTimeout is None: return self._handleCommandCancellation() def readyToSend( self ): """ Reimplemented for internal purposes. """ if self.q.empty(): self.watchReadyToSend = None return False logger.debug( "%s: sending %d bytes: %s" % ( repr(self), len(self.q.peek()[0]), repr(self.q.peek()[0]) ) ) self._lowlevelWrite( self.q.peek()[0] ) # 0 = request data self.watchTimeout = gobject.timeout_add_seconds( self.timeout, self._handleCommandTimeout ) return False def readyToRead( self, data ): """ Reimplemented for internal purposes. """ # restart timeout //FIXME: only if we were waiting for a response? if self.watchTimeout is not None: gobject.source_remove( self.watchTimeout ) self.watchTimeout = gobject.timeout_add_seconds( self.timeout, self._handleCommandTimeout ) self.parser.feed( data, self.isWaitingForResponse(), self.validPrefixes() ) def handleUnsolicitedResponse( self, response ): """ Override this to handle an unsolicited response. The default implementation does nothing. """ logger.info( "%s: unhandled unsolicited data incoming: %s", self, repr(response) ) def handleResponseToRequest( self, request, response ): """ Override this to handle a response to a request. The default implementation calls the success callback pinned to the request. """ reqstring, ok_cb, error_cb, timeout = request if not ok_cb and not error_cb: logger.debug( "%s: COMPLETED '%s' => %s" % ( repr(self), reqstring.strip(), response ) ) else: logger.debug( "%s: COMPLETED '%s' => %s" % ( repr(self), reqstring.strip(), response ) ) try: # check whether given callback is a generator # if so, advance and give result, if not # call it as usual if hasattr( ok_cb, "send" ): ok_cb.send( response ) else: ok_cb( reqstring.strip(), response ) except Exception, e: logger.exception( "(ignoring) unhandled exception in response callback: %s" % e ) def handleCommandTimeout( self, request ): """ Override this to handle a command timeout. The default implementation calls the error callback pinned to the request. """ reqstring, ok_cb, error_cb, prefixes = request if not ok_cb and not error_cb: logger.debug( "%s: TIMEOUT '%s' => ???" % ( repr(self), reqstring.strip() ) ) else: logger.debug( "%s: TIMEOUT '%s' => ???" % ( repr(self), reqstring.strip() ) ) error_cb( reqstring.strip(), ( "timeout", self.timeout ) ) # # private API # def _handleCommandCancellation( self ): """ Called, when the current command should be cancelled. According to v25ter, this can be done by sending _any_ character to the serial line. """ if self.watchTimeout is None: logger.warning( "no command to cancel" ) return # we have a timer, so lets stop it gobject.source_remove( self.watchTimeout ) self.watchTimeout = None # send EOF to cancel current command logger.debug( "%s: sending EOF" % repr(self) ) self.serial.write( "\x1A" ) logger.debug( "%s: EOF sent" % repr(self) ) # We do _not_ erase the current command and send cancellation ACK, # otherwise we would get an "unsolicited" OK as response. If for # whatever reason we would like to change the semantics, we could do # with something like: # request = self.q.get() # reqstring, ok_cb, error_cb, timeout = request # error_cb( reqstring.strip(), ( "cancel", timeout ) ) def _handleUnsolicitedResponse( self, response ): """ Called from parser, when an unsolicited response has been parsed. """ self.handleUnsolicitedResponse( response ) return self.isWaitingForResponse() # parser needs to know the current status def _handleResponseToRequest( self, response ): """ Called from parser, when a response to a request has been parsed. """ # stop timer if self.watchTimeout is not None: gobject.source_remove( self.watchTimeout ) self.watchTimeout = None # handle response request = self.q.get() self.handleResponseToRequest( request, response ) # relaunch if not self.watchReadyToSend: self.watchReadyToSend = gobject.io_add_watch( self.serial.fd, gobject.IO_OUT, self._readyToSend, priority=PRIORITY_RTS ) return self.isWaitingForResponse() # parser needs to know the current status def _handleCommandTimeout( self ): """ Called from mainloop, when a command does not get a response within a certain timeout. Here, we need to send an EOF to cancel the current command. If we would not, then an eventual response (outside the timeout interval) would be misrecognized as an unsolicited response. """ self.watchTimeout = None self.serial.write( "\x1A" ) self.handleCommandTimeout( self.q.get() ) # relaunch if not self.watchReadyToSend: self.watchReadyToSend = gobject.io_add_watch( self.serial.fd, gobject.IO_OUT, self._readyToSend, priority=PRIORITY_RTS ) return False #=========================================================================# class DelegateChannel( QueuedVirtualChannel ): #=========================================================================# """ This class contains a setDelegate() function that allows convenient handling of incoming unsolicited messages. """ def __init__( self, *args, **kwargs ): QueuedVirtualChannel.__init__( self, *args, **kwargs ) self.prefixmap = { '+': 'plus', '%': 'percent', '@': 'at', '/': 'slash', '#': 'hash', '_': 'underscore', '*': 'star', '&': 'ampersand', 'C': 'C', 'R': 'R', 'N': 'N', } self.delegate = None def setDelegate( self, object ): """ Set a delegate object to which all unsolicited responses are delegated first. """ if self.delegate is not None: logger.warning( "delegate already set. Ignoring" ) return self.delegate = object def _handleUnsolicitedResponse( self, response ): """ Reimplemented for internal purposes. This class changes the semantics of how handleUnsolicitedResponse() is getting called. If a delegate is installed, handleUnsolicitedResponse() will only be getting called, if no appropriate delegate method can be found. """ data = response[0] if self.delegate is None: # no delegate installed, hand over to generic handler return self.handleUnsolicitedResponse( data ) if not data[0] in self.prefixmap: return False if not ':' in data: return False command, values = data.split( ':', 1 ) # convert unsolicited command to a method name command = command.replace( ' ', '_' ) # no spaces in Python identifiers methodname = "%s%s" % ( self.prefixmap[command[0]], command[1:] ) # no special characters try: method = getattr( self.delegate, methodname ) except AttributeError: # no appropriate handler found, hand over to generic handler return self.handleUnsolicitedResponse( data ) else: try: if len( response ) == 2: # unsolicited data contains a PDU method( values.strip(), response[1] ) else: method( values.strip() ) except Exception, e: logger.exception( "(ignoring) unhandled exception in unsolicited response handler: %s" % e ) return False return True # unsolicited response handled OK #=========================================================================# class AtCommandChannel( DelegateChannel ): #=========================================================================# """ This class represents an AT command channel. Commands are prefixed according to v25ter. Multiline commands are handled. """ def enqueue( self, command, response_cb=None, error_cb=None, prefixes=None ): """ Enqueue a single line or multiline command. Multiline commands have a '\r' (NOT '\r\n') embedded after the first line. """ commands = command.split( '\r', 1 ) if len( commands ) == 1: QueuedVirtualChannel.enqueue( self, "AT%s\r\n" % command, response_cb, error_cb, prefixes ) elif len( commands ) == 2: QueuedVirtualChannel.enqueue( self, "AT%s\r" % commands[0], self.onMultilineCommandResponse, self.onMultilineCommandError, prefixes ) QueuedVirtualChannel.enqueue( self, "%s\x1A" % commands[1], response_cb, error_cb, prefixes ) def onMultilineCommandResponse( self, request, response ): if response != []: logger.warning( "multiline command got bogus response '%s' after first line. pushing ERROR to upper layer" ) self._handleResponseToRequest( "+EXT I: INTERNAL" ) def onMultilineCommandError( self, request, error ): logger.error( "multiline command got error after first line. pushing ERROR to upper layer" ) self._handleResponseToRequest( "+EXT I: INTERNAL" ) # you should not need to call this anymore enqueueRaw = QueuedVirtualChannel.enqueue #=========================================================================# if __name__ == "__main__": #=========================================================================# print "no tests written yet :(" fso-frameworkd-0.10.1/framework/subsystems/ogsmd/gsm/const.py000066400000000000000000001223061174525413000243420ustar00rootroot00000000000000#!/usr/bin/env python #coding=utf8 """ The Open GSM Daemon - Python Implementation (C) 2008-2009 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd.gsm Module: const GSM constants, strings, formats, parse patterns, timeouts, you name it. """ __version__ = "0.8.3.3" MODULE_NAME = "ogsmd.const" from framework import config from ogsmd.helpers import BiDict import re, string, os.path import logging logger = logging.getLogger( MODULE_NAME ) #=========================================================================# # format patterns #=========================================================================# # +COPS: (2,"MEDION Mobile","","26203"),(3,"T-Mobile D","TMO D","26201"),(3,"Vodafone.de","Vodafone","26202"),(3,"o2 - de","o2 - de","26207") # +COPS: (2,"E-PLUS","E-PLUS","26203",2),(2,"E-PLUS","E-PLUS","26203",0),(3,"T-Mobile D","T-Mobile D","26201",0),(3,"Vodafone.de","Vodafone.de","26202",0),(3,"Vodafone.de","Vodafone.de","26202",2),(3,"o2 - de","o2 - de","26207",2),(3,"o2 - de","o2 - de","26207",0),(3,"T-Mobile D","T-Mobile D","26201",2) PAT_OPERATOR_LIST = re.compile( '\((?P[123]),"(?P[^"]+?)","(?P[^"]*?)","(?P\d*?)"(?:,(?P\d))?\)') # +CPBR: (1-250),44,17 # +CBPR: (1-50) PAT_PHONEBOOK_INFO = re.compile( '\((?P\d+)-(?P\d+)\)(?:,(?P\d+),(?P\d+))?' ) # +CMGL: 1,"REC READ","491770702810",,"08/04/04,01:21:20+08",145,121 # +CMGL: 2,"REC READ","491770702810","Jim Panse","08/04/04,01:21:20+08",145,121 # +CMGL: 3,"STO UNSENT","85291234567",,,145,136 # +CMGL: 2,"REC READ","Alice-Team",,"08/05/13,09:12:15+08",208,133 # +CMGL: 0,"REC READ","66658369458410197109",,"07/02/19,15:24:26+04",208,156 # +CMGL: 1,"REC UNREAD","84971141051024573110102111",,"08/02/22,15:28:04+00",208,158 # +CMGL: 12,"REC READ","491781809817",,"07/02/22,15:19:06+04",145,137 # +CMGL: 13,"REC READ","491707759006",,"07/03/07,20:33:06+04",145,82 # +CMGL: 14,"REC READ","491703880745",,"07/03/08,15:09:29+04",145,53 # +CMGL: 15,"REC READ","491707759006",,"07/03/09,17:02:34+04",145,60 # +CMGL: 18,"STO UNSENT","",,,128,21 # +CMGL: 19,"STO UNSENT","",,,128,48 # +CMGL: 20,"STO UNSENT","",,,128,10 PAT_SMS_TEXT_HEADER = re.compile( '(?P\d+),"(?P[^"]+)","(?P[^"]*)",(?:"(?P[^"]+)")?,(?:"(?P[^"]+)")?,(?P\d+),(?P\d+)' ) # +CMGL: 1,1,"",125 PAT_SMS_PDU_HEADER = re.compile( '(?P\d+),(?P\d+),(?:"(?P[^"]*)")?,(?P\d+)' ) # +CMGR: "REC READ","Alice-Team",,"08/05/13,09:12:15+08",208,133 PAT_SMS_TEXT_HEADER_SINGLE = re.compile( '"(?P[^"]+)","(?P[^"]+)",(?:"(?P[^"]+)")?,(?:"(?P[^"]+)")?,(?P\d+),(?P\d+)' ) # +CMGR: 1,"",155 PAT_SMS_PDU_HEADER_SINGLE = re.compile( '(?P\d+),(?:"(?P[^"]*)")?,(?P\d+)' ) # "foo" # "" PAT_STRING = re.compile( r'''"([^"]+?)"''' ) # call forwarding PAT_CCFC = re.compile( r'''(?P\d+),(?P\d)(?:,"(?P[^"]+)",(?P\d+)(?:,,(?:,(?P\d+))?)?)?''' ) # list calls PAT_CLCC = re.compile( r'''\+CLCC: (?P\d+),(?P\d+),(?P\d+),(?P\d+),(?P\d+)(?:,"(?P[^"]+)",(?P\d+)?)(?:,"(?P[^"]+)")?''' ) # cell broadcast PAT_CSCB = re.compile( r'''\+CSCB: (?P[01]),"(?P[^"]*)","(?P[^"]*)"''' ) #=========================================================================# def groupDictIfMatch( pattern, string ): #=========================================================================# """ Returns the group dictionary, if the pattern matches. None, otherwise. """ match = pattern.match( string ) return match.groupdict() if match is not None else None #=========================================================================# # "112" // GSM 02.30, Europe # "911" // GSM 02.30, US and Canada # "08" // GSM 02.30, Mexico # "000" // GSM 22.101, Australia # "999" // GSM 22.101, United Kingdom # "110" // GSM 22.101 # "118" // GSM 22.101 # "119" // GSM 22.101 EMERGENCY_NUMBERS = "112 911 08 000 999 110 118 119".split() #=========================================================================# PHONE_NUMBER_DIGITS = "1234567890*+#" #=========================================================================# PHONE_CALL_TYPES = "voice data".split() # FIXME add 'fax' once we're there #=========================================================================# CME = { \ 0: "Phone failure", 1: "No connection to phone", 2: "Phone adapter link reserved", 3: "Operation not allowed", 4: "Operation not supported", 5: "PH_SIM PIN required", 6: "PH_FSIM PIN required", 7: "PH_FSIM PUK required", 10: "SIM not inserted", 11: "SIM PIN required", 12: "SIM PUK required", 13: "SIM failure", 14: "SIM busy", 15: "SIM wrong", 16: "Incorrect password", 17: "SIM PIN2 required", 18: "SIM PUK2 required", 20: "Memory full", 21: "Invalid index", 22: "Not found", 23: "Memory failure", 24: "Text string too long", 25: "Invalid characters in text string", 26: "Dial string too long", 27: "Invalid characters in dial string", 30: "No network service", 31: "Network timeout", 32: "Network not allowed, emergency calls only", 40: "Network personalization PIN required", 41: "Network personalization PUK required", 42: "Network subset personalization PIN required", 43: "Network subset personalization PUK required", 44: "Service provider personalization PIN required", 45: "Service provider personalization PUK required", 46: "Corporate personalization PIN required", 47: "Corporate personalization PUK required", 48: "PH-SIM PUK required", 100: "Unknown error", 103: "GPRS Illegal MS", 106: "GPRS Illegal ME", 107: "GPRS services not allowed", 111: "GPRS PLMN not allowed", 112: "GPRS Location area not allowed", 113: "GPRS Roaming not allowed in this location area", 126: "GPRS Operation temporary not allowed", 132: "GPRS Service operation not supported", 133: "GPRS Requested service option not subscribed", 134: "GPRS Service option temporary out of order", 148: "GPRS Unspecified error", 149: "GPRS PDP authentication failure", 150: "GPRS Invalid mobile class", 256: "Operation temporarily not allowed", 257: "Call barred", 258: "Phone is busy", 259: "User abort", 260: "Invalid dial string", 261: "SS not executed", 262: "SIM Blocked", 263: "Invalid block", # 265: Freescale Neptune Proprietary 265: "Busy, try again", # 512-514: TI Calypso Proprietary 512: "Failed to abort command", 513: "ACM Reset needed", 514: "SIM Application Toolkit busy", # 772: "SIM powered down", } #=========================================================================# CMS = { \ 1 : "Unassigned number", 8 : "Operator determined barring", 10 : "Call bared", 21 : "Short message transfer rejected", 27 : "Destination out of service", 28 : "Unidentified subscriber", 29 : "Facility rejected", 30 : "Unknown subscriber", 38 : "Network out of order", 41 : "Temporary failure", 42 : "Congestion", 47 : "Recources unavailable", 50 : "Requested facility not subscribed", 69 : "Requested facility not implemented", 81 : "Invalid short message transfer reference value", 95 : "Invalid message unspecified", 96 : "Invalid mandatory information", 97 : "Message type non existent or not implemented", 98 : "Message not compatible with short message protocol", 99 : "Information element non-existent or not implemente", 111 : "Protocol error, unspecified", 127 : "Internetworking, unspecified", 128 : "Telematic internetworking not supported", 129 : "Short message type 0 not supported", 130 : "Cannot replace short message", 143 : "Unspecified TP-PID error", 144 : "Data code scheme not supported", 145 : "Message class not supported", 159 : "Unspecified TP-DCS error", 160 : "Command cannot be actioned", 161 : "Command unsupported", 175 : "Unspecified TP-Command error", 176 : "TPDU not supported", 192 : "SC busy", 193 : "No SC subscription", 194 : "SC System failure", 195 : "Invalid SME address", 196 : "Destination SME barred", 197 : "SM Rejected-Duplicate SM", 198 : "TP-VPF not supported", 199 : "TP-VP not supported", 208 : "D0 SIM SMS Storage full", 209 : "No SMS Storage capability in SIM", 210 : "Error in MS", 211 : "Memory capacity exceeded", 212 : "SIM Application Toolkit busy", 213 : "SIM data download error", 255 : "Unspecified error cause", 300 : "ME Failure", 301 : "SMS service of ME reserved", 302 : "Operation not allowed", 303 : "Operation not supported", 304 : "Invalid PDU mode parameter", 305 : "Invalid Text mode parameter", 310 : "SIM not inserted", 311 : "SIM PIN required", 312 : "PH-SIM PIN required", 313 : "SIM failure", 314 : "SIM busy", 315 : "SIM wrong", 316 : "SIM PUK required", 317 : "SIM PIN2 required", 318 : "SIM PUK2 required", 320 : "Memory failure", 321 : "Invalid memory index", 322 : "Memory full", 330 : "SMSC address unknown", 331 : "No network service", 332 : "Network timeout", 340 : "No +CNMA expected", 500 : "Unknown error", 512 : "Failed to abort command", 513 : "ACM Reset Needed", 514 : "Invalid Status", 515 : "Device busy or Invalid Character in string", 516 : "Invalid length", 517 : "Invalid character in PDU", 518 : "Invalid parameter", 519 : "Invalid length or character", 520 : "Invalid character in text", 521 : "Timer expired", 522 : "Operation temporary not allowed", 532 : "SIM not ready", 534 : "Cell Broadcast error unknown", 535 : "Protocol stack busy", 538 : "Invalid parameter", } #=========================================================================# EXT = { \ 0 : "Invalid Parameter", } #=========================================================================# # ISDN User Part Releases Causes are given as information on +CEER as well # as part of unsolicited responses (vendor extensions only). ISUP_RELEASE_CAUSE = { \ 1: "Unallocated (unassigned) number", 2: "No route to specific transit network", 3: "No route to destination", 4: "Send special info tone", 5: "Misdialed trunk prefix", 6: "Channel unacceptable", 7: "Call awarded and being delivered in established channel", 8: "Preemption", 9: "Preemption - circuit reserved for reuse", 10: "10", 11: "11", 12: "12", 13: "13", 14: "14", 15: "15", 16: "Normal call clearing", 17: "User busy", 18: "No user responding", 19: "No answer from user (user alerted)", 20: "Subscriber absent", 21: "Call rejected", 22: "Number changed", 23: "23", 24: "24", 25: "Exchange routing error", 26: "Non-selected user clearing", 27: "Destination out of order", 28: "Invalid number format", 29: "Facility rejected", 30: "Response to STATUS ENQUIRY", 31: "Normal, unspecified", 32: "32", 33: "33", 34: "No circuit/channel available", 35: "35", 36: "36", 37: "37", 38: "Network out of order", 39: "Permanent frame mode connection out of service", 40: "Permanent frame mode connection operational", 41: "Temporary failure", 42: "Switching equipment congestion", 43: "Access information discarded", 44: "Requested channel/circuit not available", 45: "45", 46: "Precedence call blocked", 47: "Resources unavailable, unspecified", 48: "48", 49: "Quality of service unavailable", 50: "Requested facility not subscribed", 51: "51", 52: "52", 53: "Outgoing calls barred within CUG", 54: "54", 55: "Incoming calls barred within CUG", 56: "56", 57: "Bearer capability not authorized", 58: "Bearer capability not presently available", 59: "59", 60: "60", 61: "61", 62: "Inconsistency in designed outg. access inf. and subscr. class", 63: "Service or option not available, unspecified", 64: "64", 65: "Bearer capability not implemented", 66: "Channel type not implemented", 67: "67", 68: "68", 69: "Requested facility not implemented", 70: "Only restricted digital bearer cap. is available", 71: "71", 72: "72", 73: "73", 74: "74", 75: "75", 76: "76", 77: "77", 78: "78", 79: "Service or option not implemented, unspecified", 80: "80", 81: "Invalid call reference value", 82: "Identified channel does not exist", 83: "A suspended call exists, but this call identity does not", 84: "Call identity in use", 85: "No call suspended", 86: "Call having the requested call identity has been cleared", 87: "User not member of CUG", 88: "Incompatible destination", 89: "89", 90: "Non-existing CUG", 91: "Invalid transit network selection", 92: "92", 93: "93", 94: "94", 95: "Invalid message, unspecified", 96: "Mandatory information element is missing", 97: "Message type non-existing or not implemented", 98: "Message incompatible with call state or mesg type non-existent or not implemented", 99: "Information element non-existent or not implemented", 100: "Invalid information element contents", 101: "Message not compatible with call state", 102: "Recovery on timer expiry", 103: "Parameter non-existent or not implemented - passed on", 104: "104", 105: "105", 106: "106", 107: "107", 108: "108", 109: "109", 110: "Message with unrecognized parameter discarded", 111: "Protocol error, unspecified", 112: "112", 113: "113", 114: "114", 115: "115", 116: "116", 117: "117", 118: "118", 119: "119", 120: "120", 121: "121", 122: "122", 123: "123", 124: "124", 125: "125", 126: "126", 127: "Interworking, unspecified", 252: "Call barring on outgoing calls", 253: "Call barring on incoming calls", 254: "Call impossible", 255: "Lower layer failure", } #=========================================================================# # The following is a mapping of GSM MCC codes to country dialing codes # This mapping was generated by cross-referencing # ITU E.212[1] ("Land Mobile Numbering Plan") with the wikipedia list of # country dialing codes[2]. FIXME: It may not be 100% correct. # # [1] http://www.itu.int/itudoc/itu-t/ob-lists/icc/e212_685.html # [2] http://en.wikipedia.org/wiki/List_of_country_calling_codes MCC = { \ 412: ( "+93", "Afghanistan" ), 276: ( "+355", "Albania" ), 603: ( "+213", "Algeria" ), 544: ( "+1684", "American Samoa (US)" ), 213: ( "+376", "Andorra" ), 631: ( "+244", "Angola" ), 365: ( "+1264", "Anguilla" ), 344: ( "+1268", "Antigua and Barbuda" ), 722: ( "+54", "Argentine Republic" ), 283: ( "+374", "Armenia" ), 363: ( "+297", "Aruba (Netherlands)" ), 505: ( "+61", "Australia" ), 232: ( "+43", "Austria" ), 400: ( "+994", "Azerbaijani Republic" ), 364: ( "+1242", "Bahamas" ), 426: ( "+973", "Bahrain" ), 470: ( "+880", "Bangladesh" ), 342: ( "+1246", "Barbados" ), 257: ( "+375", "Belarus" ), 206: ( "+32", "Belgium" ), 702: ( "+501", "Belize" ), 616: ( "+229", "Benin" ), 350: ( "+1441", "Bermuda (UK)" ), 402: ( "+975", "Bhutan" ), 736: ( "+591", "Bolivia" ), 218: ( "+387", "Bosnia and Herzegovina" ), 652: ( "+267", "Botswana" ), 724: ( "+55", "Brazil" ), 348: ( "+1284", "British Virgin Islands (UK)" ), 528: ( "+673", "Brunei Darussalam" ), 284: ( "+359", "Bulgaria" ), 613: ( "+226", "Burkina Faso" ), 642: ( "+257", "Burundi" ), 456: ( "+855", "Cambodia" ), 624: ( "+237", "Cameroon" ), 302: ( "+1", "Canada" ), 625: ( "+238", "Cape Verde" ), 346: ( "+1345", "Cayman Islands (UK)" ), 623: ( "+236", "Central African Republic" ), 622: ( "+235", "Chad" ), 730: ( "+56", "Chile" ), 460: ( "+86", "China" ), 732: ( "+57", "Colombia" ), 654: ( "+269", "Comoros" ), 629: ( "+242", "Republic of the Congo" ), 548: ( "+682", "Cook Islands (NZ)" ), 712: ( "+506", "Costa Rica" ), 612: ( "+225", "Côte d'Ivoire" ), 219: ( "+385", "Croatia" ), 368: ( "+53", "Cuba" ), 280: ( "+357", "Cyprus" ), 230: ( "+420", "Czech Republic" ), 630: ( "+243", "Democratic Republic of the Congo" ), 238: ( "+45", "Denmark" ), 638: ( "+253", "Djibouti" ), 366: ( "+1767", "Dominica" ), 370: ( "+1809", "Dominican Republic" ), 514: ( "+670", "East Timor" ), 740: ( "+593", "Ecuador" ), 602: ( "+20", "Egypt" ), 706: ( "+503", "El Salvador" ), 627: ( "+240", "Equatorial Guinea" ), 657: ( "+291", "Eritrea" ), 248: ( "+372", "Estonia" ), 636: ( "+251", "Ethiopia" ), 288: ( "+298", "Faroe Islands (Denmark)" ), 542: ( "+679", "Fiji" ), 244: ( "+358", "Finland" ), 208: ( "+33", "France" ), 742: ( "+594", "French Guiana (France)" ), 547: ( "+689", "French Polynesia (France)" ), 628: ( "+241", "Gabonese Republic" ), 607: ( "+220", "Gambia" ), 282: ( "+995", "Georgia" ), 262: ( "+49", "Germany" ), 620: ( "+233", "Ghana" ), 266: ( "+350", "Gibraltar (UK)" ), 202: ( "+30", "Greece" ), 290: ( "+299", "Greenland (Denmark)" ), 352: ( "+1473", "Grenada" ), 340: ( "+590", "Guadeloupe (France)" ), 535: ( "+1671", "Guam (US)" ), 704: ( "+502", "Guatemala" ), 611: ( "+224", "Guinea" ), 632: ( "+245", "Guinea-Bissau" ), 738: ( "+592", "Guyana" ), 372: ( "+509", "Haiti" ), 708: ( "+504", "Honduras" ), 454: ( "+852", "Hong Kong (PRC)" ), 216: ( "+36", "Hungary" ), 274: ( "+354", "Iceland" ), 404: ( "+91", "India" ), 405: ( "+91", "India" ), 510: ( "+62", "Indonesia" ), 432: ( "+98", "Iran" ), 418: ( "+964", "Iraq" ), 272: ( "+353", "Ireland" ), 425: ( "+972", "Israel" ), 222: ( "+39", "Italy" ), 338: ( "+1876", "Jamaica" ), 441: ( "+81", "Japan" ), 440: ( "+81", "Japan" ), 416: ( "+962", "Jordan" ), 401: ( "+7", "Kazakhstan" ), 639: ( "+254", "Kenya" ), 545: ( "+686", "Kiribati" ), 467: ( "+850", "Korea North" ), 450: ( "+82", "Korea South" ), 419: ( "+965", "Kuwait" ), 437: ( "+996", "Kyrgyz Republic" ), 457: ( "+856", "Laos" ), 247: ( "+371", "Latvia" ), 415: ( "+961", "Lebanon" ), 651: ( "+266", "Lesotho" ), 618: ( "+231", "Liberia" ), 606: ( "+218", "Libya" ), 295: ( "+423", "Liechtenstein" ), 246: ( "+370", "Lithuania" ), 270: ( "+352", "Luxembourg" ), 455: ( "+853", "Macao (PRC)" ), 294: ( "+389", "Republic of Macedonia" ), 646: ( "+261", "Madagascar" ), 650: ( "+265", "Malawi" ), 502: ( "+60", "Malaysia" ), 472: ( "+960", "Maldives" ), 610: ( "+223", "Mali" ), 278: ( "+356", "Malta" ), 551: ( "+692", "Marshall Islands" ), 340: ( "+596", "Martinique (France)" ), 609: ( "+222", "Mauritania" ), 617: ( "+230", "Mauritius" ), 334: ( "+52", "Mexico" ), 550: ( "+691", "Federated States of Micronesia" ), 259: ( "+373", "Moldova" ), 212: ( "+377", "Monaco" ), 428: ( "+976", "Mongolia" ), 297: ( "+382", "Montenegro" ), 354: ( "+1664", "Montserrat (UK)" ), 604: ( "+212", "Morocco" ), 643: ( "+258", "Mozambique" ), 414: ( "+95", "Myanmar" ), 649: ( "+264", "Namibia" ), 536: ( "+674", "Nauru" ), 429: ( "+977", "Nepal" ), 204: ( "+31", "Netherlands" ), 362: ( "+599", "Netherlands Antilles (Netherlands)" ), 546: ( "+687", "New Caledonia (France)" ), 530: ( "+64", "New Zealand" ), 710: ( "+505", "Nicaragua" ), 614: ( "+227", "Niger" ), 621: ( "+234", "Nigeria" ), 534: ( "+1670", "Northern Mariana Islands (US)" ), 242: ( "+47", "Norway" ), 422: ( "+968", "Oman" ), 410: ( "+92", "Pakistan" ), 552: ( "+680", "Palau" ), 714: ( "+507", "Panama" ), 537: ( "+675", "Papua New Guinea" ), 744: ( "+595", "Paraguay" ), 716: ( "+51", "Peru" ), 515: ( "+63", "Philippines" ), 260: ( "+48", "Poland" ), 351: ( "+351", "Portugal" ), 330: ( "+1787", "Puerto Rico (US)" ), 427: ( "+974", "Qatar" ), 647: ( "+262", "Réunion (France)" ), 226: ( "+40", "Romania" ), 250: ( "+7", "Russian Federation" ), 635: ( "+250", "Rwandese Republic" ), 356: ( "+1869", "Saint Kitts and Nevis" ), 358: ( "+1758", "Saint Lucia" ), 308: ( "+508", "Saint Pierre and Miquelon (France)" ), 360: ( "+1784", "Saint Vincent and the Grenadines" ), 549: ( "+685", "Samoa" ), 292: ( "+378", "San Marino" ), 626: ( "+239", "São Tomé and Príncipe" ), 420: ( "+966", "Saudi Arabia" ), 608: ( "+221", "Senegal" ), 633: ( "+248", "Seychelles" ), 619: ( "+232", "Sierra Leone" ), 525: ( "+65", "Singapore" ), 231: ( "+421", "Slovakia" ), 293: ( "+386", "Slovenia" ), 540: ( "+677", "Solomon Islands" ), 637: ( "+252", "Somalia" ), 655: ( "+27", "South Africa" ), 214: ( "+34", "Spain" ), 413: ( "+94", "Sri Lanka" ), 634: ( "+249", "Sudan" ), 746: ( "+597", "Suriname" ), 653: ( "+268", "Swaziland" ), 240: ( "+46", "Sweden" ), 228: ( "+41", "Switzerland" ), 417: ( "+963", "Syria" ), 466: ( "+886", "Taiwan" ), 436: ( "+992", "Tajikistan" ), 640: ( "+255", "Tanzania" ), 520: ( "+66", "Thailand" ), 615: ( "+228", "Togolese Republic" ), 539: ( "+676", "Tonga" ), 374: ( "+1868", "Trinidad and Tobago" ), 605: ( "+216", "Tunisia" ), 286: ( "+90", "Turkey" ), 438: ( "+993", "Turkmenistan" ), 376: ( "+1649", "Turks and Caicos Islands (UK)" ), 641: ( "+256", "Uganda" ), 255: ( "+380", "Ukraine" ), 424: ( "+971", "United Arab Emirates" ), 430: ( "+971", "United Arab Emirates (Abu Dhabi)" ), 431: ( "+971", "United Arab Emirates (Dubai)" ), 235: ( "+44", "United Kingdom" ), 234: ( "+44", "United Kingdom" ), 310: ( "+1", "United States of America" ), 311: ( "+1", "United States of America" ), 312: ( "+1", "United States of America" ), 313: ( "+1", "United States of America" ), 314: ( "+1", "United States of America" ), 315: ( "+1", "United States of America" ), 316: ( "+1", "United States of America" ), 332: ( "+1340", "United States Virgin Islands (US)" ), 748: ( "+598", "Uruguay" ), 434: ( "+998", "Uzbekistan" ), 541: ( "+678", "Vanuatu" ), 225: ( "+39", "Vatican City State" ), 734: ( "+58", "Venezuela" ), 452: ( "+84", "Viet Nam" ), 543: ( "+681", "Wallis and Futuna (France)" ), 421: ( "+967", "Yemen" ), 645: ( "+260", "Zambia" ), 648: ( "+263", "Zimbabwe" ), } #=========================================================================# PROVIDER_STATUS = { \ 0: "unknown", 1: "available", 2: "current", 3: "forbidden", } #=========================================================================# REGISTER_STATUS = { \ 0: "unregistered", 1: "home", 2: "busy", 3: "denied", 4: "unknown", 5: "roaming", } #=========================================================================# REGISTER_MODE = { \ 0: "automatic", 1: "manual", 2: "unregister", 3: "unknown", 4: "manual;automatic", } #=========================================================================# REGISTER_ACT = { \ 0: "GSM", 1: "Compact GSM", 2: "UMTS", 3: "EDGE", 4: "HSDPA", 5: "HSUPA", 6: "HSDPA/HSUPA", } #=========================================================================# PHONEBOOK_CATEGORY = BiDict ( { \ "contacts": "SM", "dialed": "DC", "received": "RC", "own": "ON", "missed": "MC", "emergency": "EN", "fixed": "FD", # FIXME: Do we need more? } ) #=========================================================================# SMS_STATUS_OUT = { \ "REC READ": "read", "REC UNREAD": "unread", "STO SENT": "sent", "STO UNSENT": "unsent", } #=========================================================================# SMS_STATUS_IN = { \ "read": "REC READ", "unread": "REC UNREAD", "sent": "STO SENT", "unsent": "STO UNSENT", "all": "ALL", } #=========================================================================# SMS_PDU_STATUS_OUT = { \ 0 : "unread", 1 : "read", 2 : "unsent", 3 : "sent", } #=========================================================================# SMS_PDU_STATUS_IN = { \ "unread": 0, "read": 1, "unsent": 2, "sent": 3, "all": 4, } #=========================================================================# SMS_ALPHABET_TO_ENCODING = BiDict( { \ "gsm_default": "gsm_default", "ucs2": "utf_16_be", "binary": None, } ) #=========================================================================# CB_PDU_DCS_LANGUAGE = [ "German", "English", "Italian", "French", "Spanish", "Dutch", "Swedish", "Danish", "Portuguese", "Finnish", "Norwegian", "Greek", "Turkish", "Hungarian", "Polish", None] #=========================================================================# PDUADDR_DEC_TRANS = string.maketrans("abcde", "*#abc") PDUADDR_ENC_TRANS = string.maketrans("*#abc", "abcde") #=========================================================================# CALL_DIRECTION = { \ 0: "outgoing", 1: "incoming", } #=========================================================================# CALL_MODE = BiDict( { \ "voice": 0, "data": 1, "fax": 2, "voice;data:voice": 3, "voice/data:voice": 4, "voice/fax:voice": 5, "voice;data:data": 6, "voice/data:data": 7, "voice/fax:fax": 8, "unknown": 9, } ) #=========================================================================# CALL_STATUS = { \ 0: "active", 1: "held", 2: "outgoing", # we don't distinguish between alerting and outgoing 3: "outgoing", 4: "incoming", 5: "incoming", } #=========================================================================# CALL_FORWARDING_REASON = BiDict( { \ "unconditional": 0, "mobile busy": 1, "no reply": 2, "not reachable": 3, "all": 4, "all conditional": 5, } ) #=========================================================================# CALL_FORWARDING_CLASS = BiDict( { \ "voice" :1, "data": 2, "voice+data":3, # convenience, should use bitfield-test eventually "fax": 4, "voice+data+fax": 7, # convenience, should use bitfield-test eventually "sms": 8, "dcs": 16, "dca": 32, "dpa": 64, "pad": 128, } ) #=========================================================================# CALL_IDENTIFICATION_RESTRICTION = BiDict( { \ "network": 0, # use default "on": 2, # send identity "off": 1, # suppress identity } ) #=========================================================================# CALL_VALID_DTMF = "0123456789*#ABCD" #=========================================================================# DEVICE_POWER_STATUS = { \ 0: "battery", 1: "ac", 2: "usb", 3: "failure", } #=========================================================================# NETWORK_USSD_MODE = { \ 0: "completed", 1: "useraction", 2: "terminated", 3: "localclient", 4: "unsupported", 5: "timeout", } #=========================================================================# NETWORK_CIPHER_STATUS = { \ 0: "disabled", 1: "enabled", } #=========================================================================# # PDU TP definitions follow here according to the appearance in GSM 03.40 # chapter 9.2.3 TP_MTI_INCOMING = BiDict( { \ "sms-deliver" : 0, "sms-submit-report" : 1, "sms-status-report" : 2, "reserved" : 3, } ) TP_MTI_INCOMING.AUTOINVERSE = True TP_MTI_OUTGOING = BiDict( { \ "sms-deliver-report" : 0, "sms-submit" : 1, "sms-command" : 2, "reserved" : 3, } ) TP_MTI_OUTGOING.AUTOINVERSE = True #=========================================================================# TP_VPF = { \ "n/a" : 0, "enhanced" : 1, "relative" : 2, "absolute" : 3, } #=========================================================================# TP_PID = { \ "implicit" : 0, "telex" : 1, "g3-telefax" : 2, "g4-telefax" : 3, "voice-telphone" : 4, "ermes" : 5, "paging" : 6, "videotex" : 7, "teletex" : 8, "teletex-pspdn" : 9, "teletex-cspdn" : 10, "teletex-pstn" : 11, "teletex-isdn" : 12, "uci" : 13, # reserved "message-handling" : 16, "public-x400" : 17, "e-mail" : 18, # reserved "gsm-ms" : 31, } # FIXME incomplete # Missing TP_VPEXT #=========================================================================# TP_ST = { \ # Transaction completed "received" : 0, "forwarded" : 1, "replaced" : 2, # Temporary error, trying again "congestion" : 32, "sme-busy" : 33, "sme-no-response" : 34, "service-rejected" : 35, "qos-na" : 36, "sme-error" : 37, # Permanent error "remote-procedure-error" : 64, "incompatible-destination" : 65, "sme-connection-rejected" : 66, "not-obtainable" : 67, "qos-na" : 68, "internetworking-na" : 69, "vp-expired" : 70, "deleted-by-origin" : 71, "deleted-by-sc" : 72, "nonexistant" : 73, # Temporary error, giving up "congestion" : 96, "sme-busy" : 97, "sme-no-response" : 98, "service-rejected" : 99, "qos-na" : 100, "sme-error" : 101, } #=========================================================================# TP_CT = { \ "request-status-report" : 0, "cancel-status-report" : 1, "delete-sm" : 2, "enable-status-report" : 3, } #=========================================================================# TP_FCS = { \ 0x80: "telematic-unsupported", 0x81: "sm-type0-unsupported", 0x82: "replace-sm-failed", 0x8f: "tp-pid-error", 0x90: "dcs-unsupported", 0x91: "message-class-unsupported", 0x9f: "tp-dcs-error", 0xa0: "cmd-no-action", 0xa1: "cmd-unsupported", 0xaf: "tp-cmd-error", 0xb0: "tpdu-unsupported", 0xc0: "sc-busy", 0xc1: "sc-no-subscription", 0xc2: "sc-failure", 0xc3: "invalid-address", 0xc4: "destination-barred", 0xc5: "rejected-duplicaet", 0xc6: "tp-vfp-unsupported", 0xc7: "tp-vf-unsupported", 0xd0: "sim-storage-full", 0xd1: "no-sim-storage", 0xd2: "ms-error", 0xd3: "memory-exceeded", 0xd4: "stk-busy", 0xd5: "data-download-error", 0xff: "unspecified-error", } #=========================================================================# TP_UDH_IEI = { \ "csm8" : 0, "special-sms" : 1, "port8" : 4, "port16" : 5, "smsc-control" : 6, "udh-source" : 7, "csm16" : 8, "wcmp" : 9, #stk-security #various specific foo } #=========================================================================# GSMALPHABET = u'@£$¥èéùìòÇ\nØø\nÅåΔ_ΦΓΛΩΠΨΣΘΞ�ÆæßÉ !"#¤%&\'()*+,-./'+\ u'0123456789:;<=>?¡ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑܧ¿'+\ u'abcdefghijklmnopqrstuvwxyzäöñüà' GSMEXTBYTE = 27 GSMEXTALPHABET = u' \n ^ {} '+\ u' \\ [~] | '+\ u' € ' #=========================================================================# import types, math #=========================================================================# def cmeString( code ): #=========================================================================# """ Returns the GSM CME string, if found in map. "undefined CME error>", otherwise. """ try: return CME[code] except KeyError: return "" #=========================================================================# def cmsString( code ): #=========================================================================# """ Returns the GSM CMS string, if found in map. "undefined CMS error>", otherwise. """ try: return CMS[code] except KeyError: return "" #=========================================================================# def extString( code ): #=========================================================================# """ Returns the GSM EXT string, if found in map. "undefined EXT error>", otherwise. """ try: return EXT[code] except KeyError: return "" #=========================================================================# def parseError( line ): #=========================================================================# """ Returns a CME, CMS or EXT string, if found in error line. "", otherwise. """ if line.startswith( "+CME ERROR:" ): return "CME", cmeString( int( line.split( ':', 1 )[1] ) ) elif line.startswith( "+CMS ERROR:" ): return "CMS", cmsString( int( line.split( ':', 1 )[1] ) ) elif line.startswith( "+EXT ERROR:" ): return "EXT", extString( int( line.split( ':', 1 )[1] ) ) else: return "" #=========================================================================# def signalQualityToPercentage( signal ): #=========================================================================# """ Returns a percentage depending on a signal quality strength. """ assert type( signal ) == types.IntType if signal == 0 or signal > 31: return 0 else: return int( round( math.log( signal ) / math.log( 31 ) * 100 ) ) #=========================================================================# def phonebookTupleToNumber( nstring, ntype ): #=========================================================================# """ Returns a full number depending on a number string and a number type. """ # FIXME document unknown types # type128: network TR TCELL appears to use it as 4 digit intracompany calls if ntype not in ( 128, 129, 145, 160, 161, 185, 208, 255 ): logger.warning( "Out-of-spec GSM number type seen: %s. Please report." % ntype ) if ntype == 145: # should not include '+' then, but on some modems, it does if nstring[0] == '+': return nstring else: return "+%s" % nstring else: return nstring #=========================================================================# def unicodeToString( uni ): #=========================================================================# """ Returns an iso-8859-1 string for a text given to the modem. """ if type( uni ) == types.StringType(): return uni else: try: result = uni.encode( "iso-8859-1" ) # as set via +CSCS except UnicodeEncodeError: result = "" # log warning return result #=========================================================================# def textToUnicode( text ): #=========================================================================# """ Strip " from a modem text and convert it to unicode. Do nothing, if already unicode. """ stripped = text.strip( '"' ) if type( stripped ) == types.UnicodeType: logger.warning( "textToUnicode called with unicode string, ignoring." ) return stripped try: result = unicode( stripped, "iso-8859-1" ) # as set via +CSCS except UnicodeDecodeError: result = "" logger.error( "textToUnicode called with unconvertable string" ) return result #=========================================================================# def mccToCountryCode( mcc ): #=========================================================================# """ Returns a country code and name for an MCC given from the modem. """ try: code, name = MCC[mcc] except KeyError: code, name = "+???", "" return code, name networksFile = "%s/ogsmd/networks.tab" % config.rootdir #=========================================================================# def parseNetworks( filename ): #=========================================================================# """ Parses the networks file and returns a dictionary. """ networks = {} linenumber = 0 common_header = [] common = {} network_header = [] network = {} if not os.path.exists( filename ): logger.warning( "Network database %s not present" % networksFile ) return {} for line in open( filename, "r" ): linenumber += 1 line = line.rstrip() if not line: # empty line, flush and reset if network: networks[( network["mcc"], network["mnc"] )] = network del network["mcc"] del network["mnc"] common_header = [] common = {} network_header = [] network = {} continue if line[0] == "%": # comment continue if line[0] == "#": # header data = line[1:].split("\t") data = [x.strip().lower() for x in data] if data[0]: common_header = data elif data[1]: network_header = data[1:] else: # data data = line.split("\t") data = [x.strip() for x in data] if data[0]: # new common, should be flushed already if not common_header: logger.warning( "Missing common header near line %i" % linenumber ) continue if common or network_header or network: logger.warning( "Missing empty line near line %i" % linenumber ) continue common = dict( zip( common_header, data ) ) elif data[1]: # new network, flush old if network: networks[( int( network["mcc"] ), int( network["mnc"] ) )] = network del network["mcc"] del network["mnc"] if not common: logger.warning( "Missing common info near line %i" % linenumber ) continue if not network_header: logger.warning( "Missing network header near line %i" % linenumber ) continue network = dict( zip( network_header, data[1:] ) ) if not (network["mcc"]+network["mnc"]).isdigit(): logger.warning( "Invaild MCC or MNC near line %i" % linenumber ) continue network.update( common ) elif data[2]: if not common: logger.warning( "Missing common info near line %i" % linenumber ) continue if not network: logger.warning( "Missing network info near line %i" % linenumber ) continue if len( data[2:] ) != 2: logger.warning( "Missing network info near line %i" % linenumber ) continue network[ data[2] ] = data[3] return networks NETWORKS = parseNetworks( networksFile) #=========================================================================# def ctzvToTimeZone( ctzv ): #=========================================================================# """ Computes the timezone offset out of a value from +CTZV Lets try again: 0x19 -> swap the BCD digits: 0x91, high bit is minus -> -11 this is -2:45 dieter: i think if you try enough variations, you can produce any number ;-) Now the same for a real world example: 105 = 0x69 swap 0x96, high bit set, minus 16, this minus 4 hours Werner: Sure, but one can also read the GSM spec ;-) dieter: that's cheating :) Start with 04.08, check 03.40 and look at 02.42 So it's is really simple ;-) """ bcd = hex( int( ctzv ) & 0xf7 )[2:][::-1] sign = int( ctzv ) & 0x08 == 8 value = int(bcd[0]) * 10 + int(bcd[1]) return -value if sign else value #=========================================================================# if __name__ == "__main__": #=========================================================================# # FIXME use Python unit testing framework print "testing..." assert cmeString(0) == CME[0] assert cmsString(538) == CMS[538] assert parseError( "+CME ERROR: 10" ) == ( "CME", cmeString( 10 ) ) assert parseError( "+CMS ERROR: 520" ) == ( "CMS", cmsString( 520 ) ) print "OK" assert signalQualityToPercentage( 0 ) == 0 assert signalQualityToPercentage( 99 ) == 0 assert signalQualityToPercentage( 31 ) == 100 assert signalQualityToPercentage( 15 ) == 79 print "OK" assert phonebookTupleToNumber( "123456789", 129 ) == "123456789" assert phonebookTupleToNumber( "123456789", 145 ) == "+123456789" print "OK" assert textToUnicode( "B\xf6rse" ) != "" assert unicodeToString( u"\xc3\xa4" ) != "" print "OK" assert mccToCountryCode( 262 ) == ( "+49", "Germany" ) assert mccToCountryCode( 700 ) == ( "+???", "" ) print "OK" assert NETWORKS[( 262, 3 )]["brand"] == "E-Plus" print "OK" assert ctzvToTimeZone( "25" ) == -11 # UTC-2:45 assert ctzvToTimeZone( "35" ) == 32 # UTC+8 assert ctzvToTimeZone( "105" ) == -16 # UTC-4 fso-frameworkd-0.10.1/framework/subsystems/ogsmd/gsm/convert.py000066400000000000000000000215251174525413000246750ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: iso-8859-15 -*- """ The Open GSM Daemon - Python Implementation (C) 2006 Adam Sampson (C) 2008-2009 Michael 'Mickey' Lauer (C) 2008 Daniel 'alphaone' Willmann (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd.gsm Module: convert GSM conversion functions. """ from datetime import datetime from const import GSMALPHABET, GSMEXTBYTE, GSMEXTALPHABET, \ PDUADDR_ENC_TRANS, PDUADDR_DEC_TRANS from codecs import register, CodecInfo #=========================================================================# def flatten(x): #=========================================================================# """flatten(sequence) -> list Returns a single, flat list which contains all elements retrieved from the sequence and all recursively contained sub-sequences (iterables). Examples: >>> [1, 2, [3,4], (5,6)] [1, 2, [3, 4], (5, 6)] >>> flatten([[[1,2,3], (42,None)], [4,5], [6], 7, MyVector(8,9,10)]) [1, 2, 3, 42, None, 4, 5, 6, 7, 8, 9, 10]""" result = [] for el in x: #if isinstance(el, (list, tuple)): if hasattr(el, "__iter__") and not isinstance(el, basestring): result.extend(flatten(el)) else: result.append(el) return result #=========================================================================# def bcd_decode(bs): #=========================================================================# s = "".join(["%1x%1x" % (b & 0xF, b >> 4) for b in bs]) if s[-1] == "f": s = s[:-1] return s #=========================================================================# def bcd_encode(number): #=========================================================================# bcd = [] if type(number) is str: # Need to encode with base 16 for the special "digits" number = [int(i, 16) for i in number] for i in range(0, len(number)-1, 2): bcd.append( int(number[i]) | int(number[i+1]) << 4 ) if len(number)%2 == 1: bcd.append( int(number[-1]) | 0x0f << 4 ) return bcd #=========================================================================# def decodePDUTime(bs): #=========================================================================# [year, month, day, hour, minute, second, timezone] = \ [((n & 0xf) * 10) + (n >> 4) for n in bs] if year >= 90: # I don't know if this is the right cut-off point... year += 1900 else: year += 2000 # Timezone sign bit started out as bit 3 of the nibble-swapped byte. We # converted to bcd, so negative timezones are now offset by 10*(1<<3)=80 if timezone < 80: zone = timezone / 4 else: zone = (timezone - 80) / -4. # Invalid dates will generate a ValueError here which needs catching in # higher levels result = datetime(year, month, day, hour, minute, second) return ( result, zone ) #=========================================================================# def encodePDUTime(timeobj): #=========================================================================# td = timeobj[0] tzone = timeobj[1] year = td.year % 100 zone = 0 # Timezone sign bit will go to bit 3 of the nibble-swapped byte. Right # now this is an offset of 10*(1<<3)=80 if tzone < 0: zone = 80 tzone = -tzone zone += int(tzone * 4) return bcd_encode( [ year/10, year%10, td.month/10, td.month%10, td.day/10, td.day%10, td.hour/10, td.hour%10, td.minute/10, td.minute%10, td.second/10, td.second%10, zone/10, zone%10 ] ) #=========================================================================# def gsm_default_encode( input, errors = 'strict' ): #=========================================================================# result = [] for char in input: try: result.append( GSMALPHABET.index( char ) ) except ValueError: try: extbyte = GSMEXTALPHABET.index( char ) result.append( GSMEXTBYTE ) result.append( extbyte ) except ValueError: raise UnicodeError if errors == 'strict': raise UnicodeError,"invalid SMS character" elif errors == 'replace': result.append(chr(0x3f)) #question mark elif errors == 'ignore': pass else: raise UnicodeError, "unknown error handling" return ''.join( map(chr, result) ), len( input ) #=========================================================================# def gsm_default_decode( input, error = 'strict' ): #=========================================================================# extchar = False result = [] for char in input: byte = ord(char) if byte == GSMEXTBYTE: extchar = True continue if extchar: extchar = False try: result += GSMEXTALPHABET[byte] except IndexError, e: raise UnicodeError, "character %i unknown in GSM extended plane" % (byte) else: try: result += GSMALPHABET[byte] except IndexError, e: raise UnicodeError, "character %i unknown in GSM basic plane" % (byte) return u"".join( result ), len(input) #=========================================================================# def gsmcodec(name): #=========================================================================# if name == "gsm_default": return CodecInfo( gsm_default_encode, gsm_default_decode, name="gsm_default" ) elif name == "gsm_ucs2": return CodecInfo( UnicodeToucs2hex, ucs2hexToUnicode, name="gsm_ucs2" ) register( gsmcodec ) #=========================================================================# def unpack_sevenbit( bs, chop = 0 ): #=========================================================================# """Unpack 7-bit characters""" result = [] offset = (7 - chop) % 7 carry = 0 for b in bs: if not chop: result.append( carry | (b & (0xff >> offset + 1)) << offset ) else: chop = 0 if offset == 6: result.append( b >> 1 ) carry = offset = 0 else: carry = b >> 7 - offset offset += 1 return "".join( map(chr, result) ) #=========================================================================# def pack_sevenbit( text, crop=0 ): #=========================================================================# """Pack 7-bit characters""" bs = [ 0 ] + map( ord, text ) + [ 0 ] result = [] shift = 7 - crop for i in range(len(bs)-1): if shift == 7: shift = 0 continue ch1 = bs[i] & 0x7F ch1 = ch1 >> shift ch2 = bs[(i+1)] & 0x7F ch2 = ch2 << (7-shift) result.append( ( ch1 | ch2 ) & 0xFF ) shift += 1 return result #=========================================================================# def ira_pdu_to_string( pdu ): #=========================================================================# bytes = [ int( pdu[ i:i+2 ], 16 ) for i in range( 0, len(pdu), 2 ) ] return unpack_sevenbit( bytes ).strip() #=========================================================================# def ucs2hexToUnicode( text, errors="strict" ): #=========================================================================# bytes = [] for i in range( 0, len(text), 2 ): try: bytes.append( int( text[i:i+2], 16 ) ) except ValueError: raise UnicodeError if errors == 'strict': raise UnicodeError,"invalid PDU Byte" elif errors == 'replace': bytes.append(0) # replace with 0 elif errors == 'ignore': bytes.append(0) else: raise UnicodeError, "unknown error handling" return "".join( map( chr, bytes ) ).decode("utf_16_be") , len(text) #=========================================================================# def UnicodeToucs2hex( text, errors="strict" ): #=========================================================================# bytes = map( ord, text.encode("utf_16_be") ) return "".join( ( "%02X" % i for i in bytes) ), len(text) #=========================================================================# if __name__ == "__main__": #=========================================================================# assert ira_pdu_to_string( "33DAED46ABD56AB5186CD668341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D100" ) == "347745555103", "ira_pdu_to_string failed" print "OK" assert "00420072006100730069006C002000540065006C00650063006F006D".decode("gsm_ucs2") == "Brasil Telecom", "ucs2hexToUnicode failed" print "OK" fso-frameworkd-0.10.1/framework/subsystems/ogsmd/gsm/decor.py000066400000000000000000000056001174525413000243050ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later """ import logging logger = logging.getLogger('ogsmd') import os FUNCTION_DEBUG = os.environ.get( "FSO_EXCESSIVE_DEBUG", False ) colorclasses = { "MiscChannel": 38, "CallChannel": 35, "UnsolicitedResponseChannel": 31 } #=========================================================================# def logged( fn ): #=========================================================================# """ Decorator that logs the name of a function each time it is called. If the function is a bound method, it also prints the classname. """ if not FUNCTION_DEBUG: return fn import inspect, random def logIt( *args, **kwargs ): calldepth = len( inspect.stack() ) try: classname = args[0].__class__.__name__ except AttributeError: classname = "" colorpre = "" colorpost = "" if classname: if classname not in colorclasses: colorclasses[classname] = random.randrange( 30, 47 ) colorpre = "\033[1;%dm" % colorclasses[classname] colorpost = "\033[m" # print colorpre, logger.debug("%s> %s.%s: ENTER %s,%s", '|...' * calldepth, classname, fn.__name__, args[1:], kwargs ), # print colorpost result = fn( *args, **kwargs ) # print colorpre, logger.debug("%s> %s.%s: LEAVE", '|...' * calldepth, classname, fn.__name__ ) # print colorpost return result logIt.__dict__ = fn.__dict__ logIt.__name__ = fn.__name__ logIt.__doc__ = fn.__doc__ return logIt #=========================================================================# def cached( fn ): #=========================================================================# """ Decorator that caches the last function result. """ def wrapper( self, *args, **kwargs ): if fn.args != args or fn.kwargs != kwargs: result = fn( self, *args, **kwargs ) fn.args = args fn.kwargs = kwargs fn.result = result return result else: return fn.result fn.args = None fn.kwargs = None fn.result = None wrapper.__dict__ = fn.__dict__ wrapper.__name__ = fn.__name__ wrapper.__doc__ = fn.__doc__ return wrapper #=========================================================================# def dbuscalls( fn ): #=========================================================================# """ Call it like: @dbuscalls def getInfoFromObject(): yield object.method() """ def dbusGen( *args, **kwargs ): return fn( args, kwargs ) dbusGen.__dict__ = fn.__dict__ dbusGen.__name__ = fn.__name__ dbusGen.__doc__ = fn.__doc__ return dbusGen fso-frameworkd-0.10.1/framework/subsystems/ogsmd/gsm/gprs.py000066400000000000000000000034301174525413000241630ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd.gsm Module: gprs This module provides a database with GPRS settings for GPRS network providers. """ providerdb = {} #=========================================================================# def addProvider( name, *aliases, **params ): #=========================================================================# providerdb[name] = {} providerdb[name].update( params ) for alias in aliases: providerdb[alias] = providerdb[name] #=========================================================================# addProvider( "T-Mobile", apn = "internet.t-d1.de", dns1 = "193.254.160.1", auth = "PAP", qos = "1,3,4,3,0,0" ) addProvider( "Vodafone", apn = "web.vodafone.de", dns1 = "139.7.30.125", dns2 = "139.7.30.126", iphc = True, auth = "CHAP", qos = "1,3,4,3,7,31" ) addProvider( "E-Plus", "Base", "simyo", apn = "internet.eplus.de", dns1 = "212.23.97.2", dns2 = "212.23.97.3", auth = "PAP", qos = "1,2,4,3,9,31" ) addProvider( "O2", "Alice", apn = "internet", dns1 = "195.182.96.28", dns2 = "195.182.96.61", auth = "PAP", qos = "1,0,0,0,0,0" ) addProvider( "Telfort", "KPN", apn = "internet", dns1 = "0.0.0.0", dns2 = "0.0.0.0", auth = "CHAP", qos = "1,0,0,0,0,0" ) #=========================================================================# if __name__ == "__main__": pass fso-frameworkd-0.10.1/framework/subsystems/ogsmd/gsm/parser.py000066400000000000000000000267571174525413000245250ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd.gsm Module: parser """ __version__ = "0.8.5.2" import os DEBUG = os.environ.get( "FSO_DEBUG_PARSER", False ) #=========================================================================# class StateBasedLowlevelAtParser( object ): #=========================================================================# """ A state machine based lowlevel AT response parser. Requirements: * Support feeding data from the channel in chunks of arbitrary lengths [ok] * Support solicited and unsolicited responses (on the same channel) [ok] * Support single (e.g. +CGMR) and multi requests (+CGMR;+CGMM;+CGMI;+CGSN) [ok] * Handle one-line and multi-line responses [ok] * Handle multiline unsolicited responses (e.g. +CBM) [ok, but kind of ugly] * Handle multiline requests by supporting continuation '\r\n> ' [ok, but see NOTE] Todo: * Detect echo mode and adjust itself (or warn) * Handle multiline answers with empty lines (e.g. in SMS) * Seamless handover to binary mode parsers / data connections """ def __init__( self, response, unsolicited ): self.response = response self.unsolicited = unsolicited self.state = self.reset() def reset( self, yankSolicited=True, keepPrefixes=False ): if yankSolicited: self.lines = [] if keepPrefixes: self.continuationPrefixes = self.validPrefixes else: self.continuationPrefixes = set([]) self.ulines = [] self.curline = "" self.hasPdu = False return self.state_start def feed( self, bytes, haveCommand, validPrefixes ): # NOTE: the continuation query relies on '\r\n> ' not being # fragmented... question: is that always correct? If not, # we better keep the state. We could also enhance the signature # to support handing a haveContinuation parameter over to here. self.haveCommand = haveCommand self.validPrefixes = self.continuationPrefixes.union(validPrefixes) if bytes == "\r\n> ": if DEBUG: print "PARSER DEBUG: got continuation character. sending empty response" self.response( [] ) self.state = self.reset( keepPrefixes=True ) return for b in bytes: if DEBUG: print "PARSER DEBUG: [%s] feeding %s to %s" % ( ( "solicited" if self.haveCommand else "unsolicited" ), repr(b), self.state ) nextstate = self.state( b ) if nextstate is None: print "PARSER DEBUG: WARNING: UNDEFINED PARSER STATE! Do not know where to go from %s upon receiving %s" % ( self.state, repr(b) ) print "previous bytes were:", repr(bytes) print "current byte is:", repr(b) print "lines:", repr(self.lines) print "curline:", repr(self.curline) print "solicited:", self.haveCommand self.state = self.reset() break else: self.state = nextstate def state_start( self, b ): if b == '\r': return self.state_start_r # this is unusal, but we are forgiving if b == '\n': return self.state_inline # this is even more unusual, but we are _really_ forgiving return self.state_inline( b ) def state_start_r( self, b ): if b == '\n': return self.state_inline def state_inline( self, b ): # FIXME checking the number of " in self.curline violates # the state machine layer and slows down the parser. # We better map this to the state machine instead. if b not in "\r\n" or self.curline.count('"')%2: self.curline += b return self.state_inline else: if b == "\r": return self.state_inline_r # usually this should not happen, but some SMS are badly formatted if b == '\n': return self.lineCompleted() def state_inline_r( self, b ): if b == '\r': return self.state_inline_multipleR if b == '\n': return self.lineCompleted() def state_inline_multipleR( self, b ): if b == '\r': return self.state_inline_multipleR if b == '\n': return self.lineCompleted( True ) def lineCompleted( self, multipleR = False ): if self.haveCommand: return self.solicitedLineCompleted( multipleR ) else: return self.unsolicitedLineCompleted( multipleR ) def solicitedLineCompleted( self, multipleR = False ): if DEBUG: print "PARSER DEBUG: [perhaps solicited] line completed, line=", repr(self.curline), "previous lines=", self.lines # skip empty lines [is this always legal?] if self.curline == "": return self.state_start if self.isTerminationLine(): if DEBUG: print "PARSER DEBUG: [solicited] response completed" self.lines.append( self.curline ) self.response( self.lines ) return self.reset() elif self.hasPdu: self.hasPdu = False self.lines.append( self.curline ) self.curline = "" return self.state_start elif self.isUnsolicitedLine(): if DEBUG: print "PARSER DEBUG: [unsolicited] response detected within solicited" return self.unsolicitedLineCompleted( multipleR ) else: self.hasPdu = self.isSolicitedPduLine() self.lines.append( self.curline ) self.curline = "" return self.state_start def isUnsolicitedLine( self ): """ Check whether the line starts with a prefix that indicates a valid response to our command. """ if self.validPrefixes == []: # everything allowed return False for prefix in self.validPrefixes: if DEBUG: print "PARSER DEBUG: checking whether %s starts with valid prefix %s" % ( repr(self.curline), repr(prefix) ) if prefix == "PDU": if self.curline[0] in "0123456789ABCDEF": if DEBUG: print "PARSER DEBUG: yes; must be really solicited" return False elif self.curline.startswith( prefix ): if DEBUG: print "PARSER DEBUG: yes; must be really solicited" return False if DEBUG: print "PARSER DEBUG: no match; must be unsolicited" return True # no prefix did match def isTerminationLine( self ): if self.curline == "OK" \ or self.curline == "ERROR" \ or self.curline.startswith( "+CME ERROR" ) \ or self.curline.startswith( "+CMS ERROR" ) \ or self.curline.startswith( "+EXT ERROR" ) \ or self.curline.startswith( "BUSY" ) \ or self.curline.startswith( "CONNECT" ) \ or self.curline.startswith( "NO ANSWER" ) \ or self.curline.startswith( "NO CARRIER" ) \ or self.curline.startswith( "NO DIALTONE" ): return True else: return False def isUnsolicitedPduLine( self ): if self.curline.startswith( "+CBM:" ) \ or self.curline.startswith( "+CDS:" ) \ or self.curline.startswith( "+CMT:" ): return True else: return False def isSolicitedPduLine( self ): if self.curline.startswith( "+CMGL:" ) \ or self.curline.startswith( "+CMGR:" ): return True else: return False def unsolicitedLineCompleted( self, multipleR = False ): if DEBUG: print "PARSER DEBUG: [unsolicited] line completed, line=", repr(self.curline) self.ulines.append( self.curline ) if self.hasPdu: if DEBUG: print "PARSER DEBUG: [unsolicited] line pdu completed, sending." if not self.curline: if DEBUG: print "Empty line before PDU, ignoring" # We have some cases where there is an empty line before the pdu self.ulines.pop() return self.state_inline self.hasPdu = False self.unsolicited( self.ulines ) return self.reset( False ) # Now this is slightly suboptimal. I tried hard to prevent even more protocol knowledge # infecting this parser, but I can't seem to find another way to detect a multiline # unsolicited response. Ideally, GSM would clearly indicate whether a PDU is following # or not, but alas, that's not the case. if self.curline: if self.isUnsolicitedPduLine(): if DEBUG: print "PARSER DEBUG: message has PDU, waiting for 2nd line." self.hasPdu = True self.curline = "" return self.state_inline else: self.unsolicited( self.ulines ) return self.reset( False ) else: if DEBUG: print "PARSER DEBUG: [unsolicited] message with empty line. Ignoring." return self.state_inline #=========================================================================# LowlevelAtParser = StateBasedLowlevelAtParser #=========================================================================# #=========================================================================# class QualcommGsmViolationParser( StateBasedLowlevelAtParser ): #=========================================================================# """ The HTC modems violate v250.ter AT format specification. Although v1 is set, they omit the '\n' as error termination. We need to check after every \r whether it's a termination, since the error may not come. """ def state_inline( self, b ): # FIXME checking the number of " in self.curline violates # the state machine layer and slows down the parser. # We better map this to the state machine instead. if b not in "\r\n" or self.curline.count('"')%2: self.curline += b return self.state_inline else: if b == "\r": if self.curline.startswith( "+CME ERROR" ) \ or self.curline.startswith( "+CMS ERROR" ) \ or self.curline.startswith( "+EXT ERROR" ): return self.lineCompleted() else: return self.state_inline_r # usually this should not happen, but some SMS are badly formatted if b == '\n': return self.lineCompleted() #=========================================================================# if __name__ == "__main__": #=========================================================================# import sys, random, time responses = [] unsoliciteds = [] def response( chunk ): print "response =", repr(chunk) responses.append( chunk ) def unsolicited( chunk ): print "unsolicited =", repr(chunk) unsoliciteds.append( chunk ) p = LowlevelAtParser( response, unsolicited ) random.seed( time.time() ) # todo use input to read command lines while True: read = sys.stdin.read( random.randint( 5, 20 ) ) if read == "": break else: p.feed( read, True ) time.sleep( 0.01 ) print repr(p.lines) print repr(responses) print repr(unsolicited) fso-frameworkd-0.10.1/framework/subsystems/ogsmd/gsm/sms.py000066400000000000000000001425161174525413000240230ustar00rootroot00000000000000#!/usr/bin/env python """ PDU parser - Python Implementation Based on desms.py by Adam Sampson (C) 2008 Daniel Willman (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd.gsm Module: pdu """ from ogsmd.gsm.convert import * from ogsmd.gsm.const import CB_PDU_DCS_LANGUAGE, TP_MTI_INCOMING, TP_MTI_OUTGOING, SMS_ALPHABET_TO_ENCODING, TP_FCS import math from array import array from datetime import datetime class SMSError(Exception): pass # ** Dekodieren # smsobject = SMS.decode( pdu ) # print "von", smsobject.sender(), "um", smsobject.arrivalTime(), "via" smsobject.serviceCenter(), ... # print "uses charset", smsobject.charset(), ... # ** Enkodieren # smsobject = encodeSMS( peer, sender, serviceCenter ) # pdu = smsobject.pdu() # * AbstractSms (uebernimmt codierung/decodierung) # * SmsMessagePart (repraesentiert eine SMS "on the wire") # * SmsMessage (repraesentiert eine -- moeglicherweise Multipart -- Nachricht) # * Weitere, fuer spezifische SMS-Typen (Status Report) eigene Klassen? Ggfs. zu komplex. class PDUAddress: @classmethod def guess( cls, number ): if number[0] == "+": number = number[1:] ntype = 1 elif number.isdigit(): # The type of number is unknown number = number ntype = 0 else: number = number ntype = 5 return cls( ntype, 1, number ) @classmethod def decode( cls, bs ): num_type = ( bs[0] & 0x70) >> 4 num_plan = ( bs[0] & 0x0F ) number = bs[1:] if len(number) == 0: number = "" elif num_type == 5: # Alphanumeric Address number = unpack_sevenbit( number ) number = number.decode( "gsm_default" ) # On some occasions when names are n*8-1 characters long # there are exactly 7 padding bits left which will result # in the "@" character being appended to the name. if len(number) % 8 == 0 and number[-1] == "@": number = number[:-1] else: number = bcd_decode( number ) # Every occurence of the padding semi-octet should be removed number = number.replace( "f", "" ) # Decode special "digits" number = number.translate( PDUADDR_DEC_TRANS ) return cls( num_type, num_plan, number ) def __init__( self, type, dialplan, number ): self.type = type self.dialplan = dialplan self.number = number def __str__( self ): prefix = "" if self.type == 1: prefix = "+" return prefix + self.number def pdu( self ): if self.type == 5: number = self.number.encode("gsm_default") enc = pack_sevenbit(number) length = len(enc)*2 if (len(self.number)*7)%8 <= 4: length -= 1 else: # Encode special "digits" number = str(self.number).translate(PDUADDR_ENC_TRANS) enc = bcd_encode(number) length = len(number) return flatten( [length, 0x80 | self.type << 4 | self.dialplan, enc] ) class SMS(object): @classmethod def decode( cls, pdu, smstype ): # first convert the string into a bytestream try: bytes = array('B', [ int( pdu[i:i+2], 16 ) for i in range(0, len(pdu), 2) ]) except ValueError: raise SMSError, "PDU malformed" if smstype == "sms-submit": sms = SMSSubmit() elif smstype == "sms-deliver": sms = SMSDeliver() elif smstype == "sms-submit-report": sms = SMSSubmitReport() elif smstype == "sms-deliver-report": sms = SMSDeliverReport() elif smstype == "sms-status-report": sms = SMSStatusReport() elif smstype == "sms-command": sms = SMSCommand() elif smstype == "guess-submit": try: sms = SMSSubmit() sms.parse( bytes ) return sms except SMSError: sms = SMSDeliver() elif smstype == "guess-deliver": try: sms = SMSDeliver() sms.parse( bytes ) return sms except SMSError: sms = SMSStatusReport() else: raise SMSError, "Invalid type %s" % (smstype) sms.parse( bytes ) return sms def __init__( self ): self.error = [] def _parse_sca( self, bytes, offset ): # SCA - Service Center address sca_len = bytes[offset] offset += 1 if sca_len > 0: sca = PDUAddress.decode( bytes[offset:offset+sca_len] ) else: sca = False return ( sca, sca_len + 1 ) def _parse_address( self, bytes, offset ): # XXX: Is this correct? Can we detect the @-padding issue in address_len? address_len = 1 + (bytes[offset] + 1) / 2 offset += 1 address = PDUAddress.decode( bytes[offset:offset+address_len] ) return ( address, address_len + 1 ) def _parse_userdata( self, ud_len, bytes ): offset = 0 self.udh = {} if self.pdu_udhi: # Decode the headers udh_len = bytes[offset] offset += 1 while offset < udh_len: # Information Element iei = bytes[offset] offset += 1 ie_len = bytes[offset] offset += 1 ie_data = bytes[offset:offset+ie_len] offset += ie_len # FIXME self.udh[iei] = ie_data # We need to lose the padding bits before the start of the # seven-bit packed data, which means we need to figure out how # many there are... # See the diagram on page 58 of GSM_03.40_6.0.0.pdf. userdata = "".join( map( chr, bytes[offset:] ) ) if self.dcs_alphabet == "gsm_default": padding_size = ((7 * ud_len) - (8 * (offset))) % 7 userdata = unpack_sevenbit(bytes[offset:], padding_size) septets = ud_len - int( math.ceil( (offset*8)/7.0 ) ) userdata = userdata[:septets] if not self.dcs_alphabet is None: try: self.ud = userdata.decode( self.dcs_alphabet ) except UnicodeError, e: self.error.append("Userdata corrupt") try: self.ud = userdata.decode( self.dcs_alphabet, 'replace') except UnicodeError, e: self.ud = "" else: # Binary message self.data = [ ord(x) for x in userdata ] self.ud = "This is a binary message" def _getType( self ): return self.mtimap[self.pdu_mti] def _setType( self, smstype ): if TP_MTI_INCOMING.has_key(smstype): self.mtimap = TP_MTI_INCOMING elif TP_MTI_OUTGOING.has_key(smstype): self.mtimap = TP_MTI_OUTGOING else: raise SMSError, "Invalid SMS type %s" % (smstype) self.pdu_mti = self.mtimap[smstype] type = property( _getType, _setType ) def _getDCS( self ): # TODO throw exceptions on invalid combinations if self.dcs_mwi_type is None: dcs = 0 dcs |= self.dcs_compressed << 5 if self.dcs_alphabet is None : dcs |= 0x1 << 2 elif self.dcs_alphabet == "utf_16_be": dcs |= 0x2 << 2 if not self.dcs_mclass is None: dcs |= 1 << 4 dcs |= self.dcs_mclass else: # not self.dcs_mwi_type is None if self.dcs_discard: group = 0xC else: if self.dcs_alphabet == "gsm_default": group = 0xD elif self.dcs_alphabet == "utf_16_be": group = 0xE else: raise "Invalid alphabet" dcs = group << 4 dcs |= self.dcs_mwi_indication << 3 dcs |= self.dcs_mwi_type return dcs def _setDCS( self, dcs ): self.dcs_alphabet = "gsm_default" self.dcs_compressed = False self.dcs_discard = False self.dcs_mwi_indication = None self.dcs_mwi_type = None self.dcs_mclass = None group = ( dcs & 0xF0 ) >> 4 if 0x0 <= group <= 0x3: # general data coding indication self.dcs_compressed = bool( dcs & ( 1 << 5 ) ) if dcs & ( 1 << 4 ): # has message class self.dcs_mclass = dcs & 0x3 if (dcs >> 2) & 0x3 == 0x1: self.dcs_alphabet = None elif (dcs >> 2) & 0x3 == 0x2: self.dcs_alphabet = "utf_16_be" elif 0x4 <= group <= 0xB: # reserved coding groups pass elif 0xC <= group <= 0xE: # MWI groups self.dcs_mwi_indication = bool( dcs & 0x8 ) # dcs & 0x4 (bit 2) is reserved as 0 self.dcs_mwi_type = [ "voicemail", "fax", "email", "other" ][ dcs & 0x3 ] if group == 0xC: # discard message self.dcs_discard = True elif group == 0xD: # MWI group: store message (GSM-default) pass elif group == 0xE: # MWI group: store message (USC2) self.dcs_alphabet = "utf_16_be" elif group == 0xF: # data coding/message class # dcs & 0x8 (bit 3) is reserved as 0 if dcs & 0x4: self.dcs_alphabet = None self.dcs_mclass = dcs & 0x3 dcs = property( _getDCS, _setDCS ) def _get_udh( self ): map = {} # Parse User data headers if 0 in self.udh: # UDH for concatenated short messages is a list of ID, # total number of messages, position of message in csm map["csm_id"] = self.udh[0][0] map["csm_num"] = self.udh[0][1] map["csm_seq"] = self.udh[0][2] if 1 in self.udh: # Special SMS Message indication # WARNING this element could appear multiple times in a message map["message-indication-type"] = self.udh[1][0] map["message-indication-count"] = self.udh[1][1] if 4 in self.udh: # Application Port addressing (8-bit) map["dst_port"] = self.udh[4][0] map["src_port"] = self.udh[4][1] map["port_size"] = 8 if 5 in self.udh: # Application Port addressing (16-bit) map["dst_port"] = self.udh[5][0]*256 + self.udh[5][1] map["src_port"] = self.udh[5][2]*256 + self.udh[5][3] map["port_size"] = 16 if 6 in self.udh: # SMSC Control Parameters map["smsc-control"] = self.udh[6][0] #if 7 in self.udh: # UDH Source Indicator if 8 in self.udh: # Concatenated shor messages (16-bit reference) map["csm_id"] = self.udh[8][0]*256 + self.udh[8][1] map["csm_num"] = self.udh[8][2] map["csm_seq"] = self.udh[8][3] #if 9 in self.udh: # Wireless Control Message Protocol return map def _set_udh( self, properties ): for k,v in properties.items(): if k == "csm_id": if "csm_num" in properties and "csm_seq" in properties: if v > 255: # Use 16-bit IDs self.udh[8] = [ v/256, v%256, properties["csm_num"], properties["csm_seq"] ] else: self.udh[0] = [ v, properties["csm_num"], properties["csm_seq"] ] if k == "message-indication-type": if "message-indication-count" in properties: self.udh[1] = [ v, properties["message-indication-count"] ] if k == "port_size": if "src_port" in properties and "dst_port" in properties: if v == 8: self.udh[4] = [ properties["dst_port"], properties["src_port"] ] elif v == 16: self.udh[5] = [ properties["dst_port"]/256, properties["dst_port"]%256, properties["src_port"]/256, properties["src_port"]%256 ] if k == "smsc-control": self.udh[6] = v def _getProperties( self ): map = {} map["type"] = self.type if len(self.error) > 0: map["error"] = self.error return map def _setProperties( self, properties ): pass properties = property( _getProperties, _setProperties ) def _getUdhi( self ): return self.udh def _setUdhi( self, value ): raise "UDHI is readonly" udhi = property( _getUdhi, _setUdhi ) def serviceCenter( self ): pass def __repr__( self ): pass class SMSDeliver(SMS): def parse( self, bytes ): """ Decode an sms-deliver message """ offset = 0 (self.sca, skip) = self._parse_sca( bytes, offset ) offset += skip # PDU type pdu_type = bytes[offset] # pdu_mti should already be set by the class if self.pdu_mti != pdu_type & 0x03: raise SMSError, "Decoded MTI doesn't match %i != %i" % (self.pdu_mti, pdu_type & 0x03) self.pdu_mms = pdu_type & 0x04 != 0 self.pdu_sri = pdu_type & 0x20 != 0 self.pdu_udhi = pdu_type & 0x40 != 0 self.pdu_rp = pdu_type & 0x80 != 0 offset += 1 (self.addr, skip) = self._parse_address(bytes, offset) offset += skip # PID - Protocol identifier self.pid = bytes[offset] offset += 1 # DCS - Data Coding Scheme self.dcs = bytes[offset] offset += 1 # SCTS - Service Centre Time Stamp try: self.scts = decodePDUTime( bytes[offset:offset+7] ) except ValueError, e: self.error.append("Service Center Timestamp invalid") offset += 7 # UD - User Data ud_len = bytes[offset] offset += 1 self._parse_userdata( ud_len, bytes[offset:] ) def __init__( self ): self.type = "sms-deliver" self.sca = False self.pdu_mms = True self.pdu_rp = False self.pdu_udhi = False self.pdu_sri = False self.udh = {} self.ud = "" self.pid = 0 self.dcs_alphabet = "gsm_default" self.dcs_compressed = False self.dcs_discard = False self.dcs_mwi_indication = None self.dcs_mwi_type = None self.dcs_mclass = None self.scts = (datetime(1980, 01, 01, 00, 00, 00), 0) self.error = [] def _getProperties( self ): map = {} map.update( SMS._getProperties( self ) ) map["pid"] = self.pid map["more-messages-to-send"] = not self.pdu_mms # This field is backwards! map["reply-path"] = self.pdu_rp map["status-report-indicator"] = self.pdu_sri # XXX Do we want to convey more info here? # map["originating-address"] map["alphabet"] = SMS_ALPHABET_TO_ENCODING.revlookup(self.dcs_alphabet) if self.dcs_mclass != None: map["message-class"] = self.dcs_mclass if map["alphabet"] == "binary": map["data"] = self.data # FIXME Return correct time with timezoneinfo map["timestamp"] = self.scts[0].ctime() + " %+05i" % (self.scts[1]*100) map.update( self._get_udh() ) return map def _setProperties( self, properties ): self._set_udh( properties ) for k,v in properties.items(): if k == "pid": self.pid = v if k == "more-messages-to-send": self.pdu_mms = not v if k == "reply-path": self.pdu_rp = v if k == "status-report-indicator": self.pdu_sri = v if k == "alphabet": self.dcs_alphabet = SMS_ALPHABET_TO_ENCODING[v] if k == "message-class" and 0 <= v < 4: self.dcs_mclass = v if k == "data": self.data = v if k == "timestamp": # TODO parse the timestamp correctly pass properties = property( _getProperties, _setProperties ) def pdu( self ): pdubytes = array('B') if self.sca: scabcd = self.sca.pdu() # SCA has non-standard length scabcd[0] = len( scabcd ) - 1 pdubytes.extend( scabcd ) else: pdubytes.append( 0 ) pdu_type = self.pdu_mti if self.pdu_rp: pdu_type += 0x80 if self.udhi: pdu_type += 0x40 if self.pdu_sri: pdu_type += 0x20 if self.pdu_mms: pdu_type += 0x04 pdubytes.append( pdu_type ) pdubytes.extend( self.addr.pdu() ) pdubytes.append( self.pid ) # We need to check whether we can encode the message with the # GSM default charset now, because self.dcs might change if not self.dcs_alphabet is None: try: pduud = self.ud.encode( self.dcs_alphabet ) except UnicodeError: self.dcs_alphabet = "utf_16_be" pduud = self.ud.encode( self.dcs_alphabet ) else: # Binary message pduud = "".join([ chr(x) for x in self.data ]) pdubytes.append( self.dcs ) pdubytes.extend( encodePDUTime( self.scts ) ) # User data if self.udhi: pduudh = flatten([ (k, len(v), v) for k,v in self.udh.items() ]) pduudhlen = len(pduudh) else: pduudhlen = -1 padding = 0 if self.dcs_alphabet == "gsm_default": udlen = int( math.ceil( (pduudhlen*8 + 8 + len(pduud)*7)/7.0 ) ) padding = (7 * udlen - (8 + 8 * (pduudhlen))) % 7 pduud = pack_sevenbit( pduud, padding ) else: pduud = map( ord, pduud ) udlen = len( pduud ) + 1 + pduudhlen pdubytes.append( udlen ) if self.udhi: pdubytes.append( pduudhlen ) pdubytes.extend( pduudh ) pdubytes.extend( pduud ) return "".join( [ "%02X" % (i) for i in pdubytes ] ) def __repr__( self ): return """sms-deliver: Type: %s ServiceCenter: %s TimeStamp: %s PID: 0x%x DCS: 0x%x Number: %s Headers: %s Alphabet: %s Message: %s """ % (self.type, self.sca, self.scts, self.pid, self.dcs, self.addr, self.udh, self.dcs_alphabet, repr(self.ud)) class SMSDeliverReport(SMS): def parse( self, bytes, ack=True ): """ Decode an sms-deliver-report message """ # self.ack indicates whether this is sms-deliver-report for RP-ACK or RP-ERROR self.ack = ack offset = 0 # PDU type pdu_type = bytes[offset] # pdu_mti should already be set by the class if self.pdu_mti != pdu_type & 0x03: raise SMSError, "Decoded MTI doesn't match %i != %i" % (self.pdu_mti, pdu_type & 0x03) self.pdu_udhi = pdu_type & 0x40 != 0 offset += 1 if not self.ack: self.fcs = bytes[offset] offset += 1 # PI - Parameter Indicator pi = bytes[offset] self.pdu_pidi = pi & 0x01 != 0 self.pdu_dcsi = pi & 0x02 != 0 self.pdu_udli = pi & 0x04 != 0 offset += 1 # PID - Protocol identifier if self.pdu_pidi: self.pid = bytes[offset] offset += 1 # DCS - Data Coding Scheme if self.pdu_dcsi: self.dcs = bytes[offset] offset += 1 # UD - User Data if self.pdu_udli: ud_len = bytes[offset] offset += 1 self._parse_userdata( ud_len, bytes[offset:] ) def __init__( self, ack=True ): self.type = "sms-deliver-report" self.ack = ack self.pdu_udhi = False self.pdu_pidi = False self.pdu_dcsi = False self.pdu_udli = False self.udh = {} self.ud = "" self.pid = 0 self.fcs = 0xff self.dcs_alphabet = "gsm_default" self.dcs_compressed = False self.dcs_discard = False self.dcs_mwi_indication = None self.dcs_mwi_type = None self.dcs_mclass = None self.error = [] def _getProperties( self ): map = {} map.update( SMS._getProperties( self ) ) if not self.ack: map["fcs"] = self.fcs if self.fcs in TP_FCS: map["failure-cause"] = TP_FCS[self.fcs] if self.pdu_pidi: map["pid"] = self.pid if self.pdu_dcsi: map["alphabet"] = SMS_ALPHABET_TO_ENCODING.revlookup(self.dcs_alphabet) if self.dcs_mclass != None: map["message-class"] = self.dcs_mclass if map["alphabet"] == "binary": map["data"] = self.data map.update(self._get_udh()) return map def _setProperties( self, properties ): self._set_udh( properties ) for k,v in properties.items(): if k == "fcs": self.fcs = v if k == "pid": self.pdu_pidi = True self.pid = v if k == "alphabet": self.pdu_dcsi = True self.dcs_alphabet = SMS_ALPHABET_TO_ENCODING[v] if k == "message-class" and 0 <= v < 4: self.pdu_dcsi = True self.dcs_mclass = v if k == "data": self.data = v properties = property( _getProperties, _setProperties ) def pdu( self ): pdubytes = array('B') pdu_type = self.pdu_mti if self.udhi: pdu_type += 0x40 pdubytes.append( pdu_type ) if not self.ack: pdubytes.append( self.fcs ) pi = 0x00 if self.pdu_pidi: pi += 1 if self.pdu_dcsi: pi += 2 if len(self.ud) > 0: pi += 4 pdubytes.append( pi ) if self.pdu_pidi: pdubytes.append( self.pid ) if self.pdu_dcsi: # We need to check whether we can encode the message with the # GSM default charset now, because self.dcs might change if not self.dcs_alphabet is None: try: pduud = self.ud.encode( self.dcs_alphabet ) except UnicodeError: self.dcs_alphabet = "utf_16_be" pduud = self.ud.encode( self.dcs_alphabet ) else: # Binary message pduud = "".join([ chr(x) for x in self.data ]) pdubytes.append( self.dcs ) if len(self.ud) > 0: # User data if self.udhi: pduudh = flatten([ (k, len(v), v) for k,v in self.udh.items() ]) pduudhlen = len(pduudh) else: pduudhlen = -1 padding = 0 if self.dcs_alphabet == "gsm_default": udlen = int( math.ceil( (pduudhlen*8 + 8 + len(pduud)*7)/7.0 ) ) padding = (7 * udlen - (8 + 8 * (pduudhlen))) % 7 pduud = pack_sevenbit( pduud, padding ) else: pduud = map( ord, pduud ) udlen = len( pduud ) + 1 + pduudhlen pdubytes.append( udlen ) if self.udhi: pdubytes.append( pduudhlen ) pdubytes.extend( pduudh ) pdubytes.extend( pduud ) return "".join( [ "%02X" % (i) for i in pdubytes ] ) def __repr__( self ): return """sms-deliver-report: Type: %s Timestamp: %s """ % (self.type, self.scts) class SMSSubmit(SMS): def parse( self, bytes ): """ Decode an sms-submit message """ offset = 0 (self.sca, skip) = self._parse_sca( bytes, offset ) offset += skip # PDU type pdu_type = bytes[offset] # pdu_mti should already be set by the class if self.pdu_mti != pdu_type & 0x03: raise SMSError, "Decoded MTI doesn't match %i != %i" % (self.pdu_mti, pdu_type & 0x03) self.pdu_rd = pdu_type & 0x04 != 0 self.pdu_vpf = (pdu_type & 0x18)>>3 self.pdu_srr = pdu_type & 0x20 != 0 self.pdu_udhi = pdu_type & 0x40 != 0 self.pdu_rp = pdu_type & 0x80 != 0 offset += 1 # MR - Message Reference self.mr = bytes[offset] offset += 1 (self.addr, skip) = self._parse_address( bytes, offset ) offset += skip # PID - Protocol identifier self.pid = bytes[offset] offset += 1 # DCS - Data Coding Scheme self.dcs = bytes[offset] offset += 1 # VP - Validity Period FIXME if self.pdu_vpf == 2: # Relative self.vp = bytes[offset] offset += 1 elif self.pdu_vpf == 3: # Absolute try: self.vp = decodePDUTime( bytes[offset:offset+7] ) except ValueError, e: self.error.append("Validity Period invalid") from datetime import datetime self.vp = (datetime(1980, 01, 01, 00, 00, 00), 0) offset += 7 # UD - User Data ud_len = bytes[offset] offset += 1 self._parse_userdata( ud_len, bytes[offset:] ) def __init__( self ): self.type = "sms-submit" self.sca = False self.pdu_rd = False self.pdu_vpf = 0 self.vp = False self.pdu_rp = False self.pdu_udhi = False self.pdu_srr = False self.mr = 0 self.udh = {} self.ud = "" self.pid = 0 self.dcs_alphabet = "gsm_default" self.dcs_compressed = False self.dcs_discard = False self.dcs_mwi_indication = None self.dcs_mwi_type = None self.dcs_mclass = None self.error = [] def _getProperties( self ): map = {} map.update( SMS._getProperties( self ) ) map["reject-duplicates"] = self.pdu_rd map["reply-path"] = self.pdu_rp map["status-report-request"] = self.pdu_srr map["message-reference"] = self.mr # XXX Do we want to convey more info here? # map["destination-address"] map["pid"] = self.pid # XXX Validity period and format #map["validity-period"] = self.scts[0].ctime() + " %+05i" % (self.scts[1]*100) map["alphabet"] = SMS_ALPHABET_TO_ENCODING.revlookup(self.dcs_alphabet) if self.dcs_mclass != None: map["message-class"] = self.dcs_mclass if map["alphabet"] == "binary": map["data"] = self.data map.update(self._get_udh()) return map def _setProperties( self, properties ): self._set_udh( properties ) for k,v in properties.items(): if k == "reject-duplicates": self.pdu_rd = v if k == "reply-path": self.pdu_rp = v if k == "status-report-request": self.pdu_srr = v if k == "message-reference": self.mr = v if k == "pid": self.pid = v if k == "alphabet": self.dcs_alphabet = SMS_ALPHABET_TO_ENCODING[v] if k == "message-class" and 0 <= v < 4: self.dcs_mclass = v if k == "data": self.data = v properties = property( _getProperties, _setProperties ) def pdu( self ): pdubytes = array('B') if self.sca: scabcd = self.sca.pdu() # SCA has non-standard length scabcd[0] = len( scabcd ) - 1 pdubytes.extend( scabcd ) else: pdubytes.append( 0 ) pdu_type = self.pdu_mti if self.pdu_rp: pdu_type += 0x80 if self.udhi: pdu_type += 0x40 if self.pdu_srr: pdu_type += 0x20 if self.pdu_rp: pdu_type += 0x04 pdu_type |= self.pdu_vpf<<3 # XXX: Allow setting VPF field pdubytes.append( pdu_type ) pdubytes.append( self.mr ) pdubytes.extend( self.addr.pdu() ) pdubytes.append( self.pid ) # We need to check whether we can encode the message with the # GSM default charset now, because self.dcs might change if not self.dcs_alphabet is None: try: pduud = self.ud.encode( self.dcs_alphabet ) except UnicodeError: self.dcs_alphabet = "utf_16_be" pduud = self.ud.encode( self.dcs_alphabet ) else: # Binary message pduud = "".join([ chr(x) for x in self.data ]) pdubytes.append( self.dcs ) # VP - Validity Period FIXME if self.pdu_vpf == 2: # Relative pdubytes.append( self.vp ) elif self.pdu_vpf == 3: # Absolute pdubytes.extend( encodePDUTime( self.vp ) ) # User data if self.udhi: pduudh = flatten([ (k, len(v), v) for k,v in self.udh.items() ]) pduudhlen = len(pduudh) else: pduudhlen = -1 padding = 0 if self.dcs_alphabet == "gsm_default": udlen = int( math.ceil( (pduudhlen*8 + 8 + len(pduud)*7)/7.0 ) ) padding = (7 * udlen - (8 + 8 * (pduudhlen))) % 7 pduud = pack_sevenbit( pduud, padding ) else: pduud = map( ord, pduud ) udlen = len( pduud ) + 1 + pduudhlen pdubytes.append( udlen ) if self.udhi: pdubytes.append( pduudhlen ) pdubytes.extend( pduudh ) pdubytes.extend( pduud ) return "".join( [ "%02X" % (i) for i in pdubytes ] ) def __repr__( self ): return """sms-submit: Type: %s ServiceCenter: %s ValidityPeriod: %s PID: 0x%x DCS: 0x%x Number: %s Headers: %s Alphabet: %s Message: %s """ % (self.type, self.sca, self.vp, self.pid, self.dcs, self.addr, self.udh, self.dcs_alphabet, repr(self.ud)) class SMSSubmitReport(SMS): def parse( self, bytes, ack=True ): """ Decode an sms-submit-report message """ self.ack = ack offset = 0 # PDU type pdu_type = bytes[offset] # pdu_mti should already be set by the class if self.pdu_mti != pdu_type & 0x03: raise SMSError, "Decoded MTI doesn't match %i != %i" % (self.pdu_mti, pdu_type & 0x03) # XXX Is 0x04 the correct bit for UDHI? GSM 03.40 says "Bits 7-2 in the TP-MTI are # presently unused...", but that leaves only room for the mti field... # On page 45 (sms-submit-report for RP-ERROR) it says that Bits 7 and 5-2 are unused # which would leave udhi at the same place as in other messages self.pdu_udhi = pdu_type & 0x40 != 0 offset += 1 if not self.ack: self.fcs = bytes[offset] offset += 1 # PI - Parameter Indicator pi = bytes[offset] self.pdu_pidi = pi & 0x01 != 0 self.pdu_dcsi = pi & 0x02 != 0 self.pdu_udli = pi & 0x04 != 0 offset += 1 try: self.scts = decodePDUTime( bytes[offset:offset+7] ) except ValueError, e: self.error.append("Service Center Time Stamp invalid") offset += 7 # PID - Protocol identifier if self.pdu_pidi: self.pid = bytes[offset] offset += 1 # DCS - Data Coding Scheme if self.pdu_dcsi: self.dcs = bytes[offset] offset += 1 # UD - User Data if self.pdu_udli: ud_len = bytes[offset] offset += 1 self._parse_userdata( ud_len, bytes[offset:] ) def __init__( self, ack=True ): self.ack = ack self.type = "sms-submit-report" self.scts = False self.pdu_udhi = False self.pdu_pidi = False self.pdu_dcsi = False self.pdu_udli = False self.udh = {} self.ud = "" self.fcs = 0xff self.dcs_alphabet = "gsm_default" self.dcs_compressed = False self.dcs_discard = False self.dcs_mwi_indication = None self.dcs_mwi_type = None self.dcs_mclass = None self.scts = (datetime(1980, 01, 01, 00, 00, 00), 0) self.error = [] def _getProperties( self ): map = {} map.update( SMS._getProperties( self ) ) map["timestamp"] = self.scts[0].ctime() + " %+05i" % (self.scts[1]*100) if not self.ack: map["fcs"] = self.fcs if self.fcs in TP_FCS: map["failure-cause"] = TP_FCS[self.fcs] if self.pdu_pidi: map["pid"] = self.pid if self.pdu_dcsi: map["alphabet"] = SMS_ALPHABET_TO_ENCODING.revlookup(self.dcs_alphabet) if self.dcs_mclass != None: map["message-class"] = self.dcs_mclass if map["alphabet"] == "binary": map["data"] = self.data map.update(self._get_udh()) return map def _setProperties( self, properties ): self._set_udh( properties ) for k,v in properties.items(): if k == "fcs": self.fcs = v if k == "pid": self.pdu_pidi = True self.pid = v if k == "alphabet": self.pdu_dcsi = True self.dcs_alphabet = SMS_ALPHABET_TO_ENCODING[v] if k == "message-class" and 0 <= v < 4: self.pdu_dcsi = True self.dcs_mclass = v if k == "data": self.data = v properties = property( _getProperties, _setProperties ) def pdu( self ): pdubytes = array('B') pdu_type = self.pdu_mti if self.udhi: pdu_type += 0x40 pdubytes.append( pdu_type ) if not self.ack: pdubytes.append( self.fcs ) pi = 0x00 if self.pdu_pidi: pi += 1 if self.pdu_dcsi: pi += 2 if self.pdu_udli: pi += 4 pdubytes.append( pi ) pdubytes.extend( encodePDUTime( self.scts ) ) if self.pdu_pidi: pdubytes.append( self.pid ) if self.pdu_dcsi: # We need to check whether we can encode the message with the # GSM default charset now, because self.dcs might change if not self.dcs_alphabet is None: try: pduud = self.ud.encode( self.dcs_alphabet ) except UnicodeError: self.dcs_alphabet = "utf_16_be" pduud = self.ud.encode( self.dcs_alphabet ) else: # Binary message pduud = "".join([ chr(x) for x in self.data ]) pdubytes.append( self.dcs ) if len(self.ud) > 0: # User data if self.udhi: pduudh = flatten([ (k, len(v), v) for k,v in self.udh.items() ]) pduudhlen = len(pduudh) else: pduudhlen = -1 padding = 0 if self.dcs_alphabet == "gsm_default": udlen = int( math.ceil( (pduudhlen*8 + 8 + len(pduud)*7)/7.0 ) ) padding = (7 * udlen - (8 + 8 * (pduudhlen))) % 7 pduud = pack_sevenbit( pduud, padding ) else: pduud = map( ord, pduud ) udlen = len( pduud ) + 1 + pduudhlen pdubytes.append( udlen ) if self.udhi: pdubytes.append( pduudhlen ) pdubytes.extend( pduudh ) pdubytes.extend( pduud ) return "".join( [ "%02X" % (i) for i in pdubytes ] ) def __repr__( self ): return """sms-submit-report: Type: %s Timestamp: %s """ % (self.type, self.scts) class SMSStatusReport(SMS): def parse( self, bytes ): """ Decode an sms-status-report message """ offset = 0 (self.sca, skip) = self._parse_sca( bytes, offset ) offset += skip # PDU type pdu_type = bytes[offset] # pdu_mti should already be set by the class if self.pdu_mti != pdu_type & 0x03: raise SMSError, "Decoded MTI doesn't match %i != %i" % (self.pdu_mti, pdu_type & 0x03) self.pdu_mms = pdu_type & 0x04 != 0 self.pdu_srq = pdu_type & 0x20 != 0 self.pdu_udhi = pdu_type & 0x40 != 0 offset += 1 # MR - Message Reference self.mr = bytes[offset] offset += 1 (self.addr, skip) = self._parse_address( bytes, offset ) offset += skip # SCTS - Service Centre Time Stamp try: self.scts = decodePDUTime( bytes[offset:offset+7] ) except ValueError, e: self.error.append("Service Center Timestamp invalid") offset += 7 # DT - Discharge Time try: self.dt = decodePDUTime( bytes[offset:offset+7] ) except ValueError, e: self.error.append("Discharge Time invalid") offset += 7 self.st = bytes[offset] offset += 1 if len(bytes) == offset: return # PI - Parameter Indicator pi = bytes[offset] self.pdu_pidi = pi & 0x01 != 0 self.pdu_dcsi = pi & 0x02 != 0 self.pdu_udli = pi & 0x04 != 0 offset += 1 if self.pdu_pidi: self.pid = bytes[offset] offset += 1 if self.pdu_dcsi: self.dcs = bytes[offset] offset += 1 if self.pdu_udli: # UD - User Data ud_len = bytes[offset] offset += 1 self._parse_userdata( ud_len, bytes[offset:] ) def __init__( self ): self.type = "sms-status-report" self.sca = False self.pdu_mms = True # more-messages-to-send is backwards! self.pdu_srq = False self.pdu_udhi = False self.pdu_pidi = False self.pdu_dcsi = False self.pdu_udli = False self.st = 0 self.mr = 0 self.udh = {} self.ud = "" self.pid = 0 self.dcs_alphabet = "gsm_default" self.dcs_compressed = False self.dcs_discard = False self.dcs_mwi_indication = None self.dcs_mwi_type = None self.dcs_mclass = None self.scts = (datetime(1980, 01, 01, 00, 00, 00), 0) self.dt = (datetime(1980, 01, 01, 00, 00, 00), 0) self.error = [] def statusToString( self, status ): message = "Reserved" if status < 0x20: message = "Completed: " if status == 0: message += "Delivered" elif status == 1: message += "Unconfirmed" elif status == 2: message += "Replaced" else: message += "Reserved" elif 0x20 <= status < 0x40 or 0x60 <= status < 0x80: message = "Temporary error" if status & 0x40: message += " (giving up)" message += ": " status = status & 0x1f if status == 0x00: message += "Congestion" elif status == 0x01: message += "SME busy" elif status == 0x02: message += "No response" elif status == 0x03: message += "Rejected" elif status == 0x04: message += "QoS N/A" elif status == 0x05: message += "Error in SME" else: message += "Reserved" elif status < 0x50: message = "Permanent error: " if status == 0x40: message += "Remote procedure" elif status == 0x41: message += "Incompatible destination" elif status == 0x42: message += "Connection rejected" elif status == 0x43: message += "Not obtainable" elif status == 0x44: message += "QoS N/A" elif status == 0x45: message += "No interworking available" elif status == 0x46: message += "SM Validity Period expired" elif status == 0x47: message += "SM deleted by originating SME" elif status == 0x48: message += "SM deleted by SC admin" elif status == 0x49: message += "SM does not exist" else: message += "Reserved" return message def _getProperties( self ): map = {} map.update( SMS._getProperties( self ) ) map["more-messages-to-send"] = not self.pdu_mms # This field is backwards! if self.pdu_srq: map["status-report-qualifier"] = "sms-command" else: map["status-report-qualifier"] = "sms-submit" map["message-reference"] = self.mr # XXX Do we want to convey more info here? # map["destination-address"] map["timestamp"] = self.scts[0].ctime() + " %+05i" % (self.scts[1]*100) map["discharge-time"] = self.dt[0].ctime() + " %+05i" % (self.dt[1]*100) map["status"] = self.st map["status-message"] = self.statusToString(self.st) if self.pdu_pidi: map["pid"] = self.pid if self.pdu_dcsi: map["alphabet"] = SMS_ALPHABET_TO_ENCODING.revlookup(self.dcs_alphabet) if self.dcs_mclass != None: map["message-class"] = self.dcs_mclass if map["alphabet"] == "binary": map["data"] = self.data map.update(self._get_udh()) return map def _setProperties( self, properties ): self._set_udh( properties ) for k,v in properties.items(): if k == "more-messages-to-send": self.pdu_mms = not v if k == "status-report-qualifier": if v == "sms-submit": self.pdu_srq = False elif v == "sms-command": self.pdu_srq = True if k == "message-reference": self.mr = v if k == "status": self.st = v if k == "pid": self.pdu_pidi = True self.pid = v if k == "alphabet": self.pdu_dcsi = True self.dcs_alphabet = SMS_ALPHABET_TO_ENCODING[v] if k == "message-class" and 0 <= v < 4: self.pdu_dcsi = True self.dcs_mclass = v if k == "data": self.data = v properties = property( _getProperties, _setProperties ) def pdu( self ): pdubytes = array('B') if self.sca: scabcd = self.sca.pdu() # SCA has non-standard length scabcd[0] = len( scabcd ) - 1 pdubytes.extend( scabcd ) else: pdubytes.append( 0 ) pdu_type = self.pdu_mti if self.pdu_mms: pdu_type += 0x04 if self.pdu_srq: pdu_type += 0x20 if self.udhi: pdu_type += 0x40 pdubytes.append( pdu_type ) pdubytes.append( self.mr ) pdubytes.extend( self.addr.pdu() ) pdubytes.extend( encodePDUTime( self.scts ) ) pdubytes.extend( encodePDUTime( self.dt ) ) pdubytes.append( self.st ) if self.pdu_pidi or self.pdu_dcsi or len(self.ud) > 0: pi = 0x00 if self.pdu_pidi: pi += 1 if self.pdu_dcsi: pi += 2 if len(self.ud) > 0: pi += 4 pdubytes.append( self.pi ) if self.pdu_pidi: pdubytes.append( self.pid ) if self.pdu_dcsi: pdubytes.append( self.dcs ) # We need to check whether we can encode the message with the # GSM default charset now, because self.dcs might change if not self.dcs_alphabet is None: try: pduud = self.ud.encode( self.dcs_alphabet ) except UnicodeError: self.dcs_alphabet = "utf_16_be" pduud = self.ud.encode( self.dcs_alphabet ) else: # Binary message pduud = "".join([ chr(x) for x in self.data ]) pdubytes.append( self.dcs ) if len(self.ud > 0): # User data if self.udhi: pduudh = flatten([ (k, len(v), v) for k,v in self.udh.items() ]) pduudhlen = len(pduudh) else: pduudhlen = -1 padding = 0 if self.dcs_alphabet == "gsm_default": udlen = int( math.ceil( (pduudhlen*8 + 8 + len(pduud)*7)/7.0 ) ) padding = (7 * udlen - (8 + 8 * (pduudhlen))) % 7 pduud = pack_sevenbit( pduud, padding ) else: pduud = map( ord, pduud ) udlen = len( pduud ) + 1 + pduudhlen pdubytes.append( udlen ) if self.udhi: pdubytes.append( pduudhlen ) pdubytes.extend( pduudh ) pdubytes.extend( pduud ) return "".join( [ "%02X" % (i) for i in pdubytes ] ) def __repr__( self ): return """sms-submit: Type: %s ServiceCenter: %s Timestamp: %s Discharge Time: %s Number: %s """ % (self.type, self.sca, self.scts, self.dt, self.addr) class SMSCommand(SMS): pass class CellBroadcast(SMS): @classmethod def decode( cls, pdu): # first convert the string into a bytestream bytes = [ int( pdu[i:i+2], 16 ) for i in range(0, len(pdu), 2) ] cb = cls() cb.sn = bytes[0] << 8 | bytes[1] cb.mid = bytes[2] << 8 | bytes[3] cb.dcs = bytes[4] cb.page = bytes[5] userdata = "".join( map( chr, bytes[6:] ) ) if cb.dcs_alphabet == "gsm_default": userdata = unpack_sevenbit(bytes[6:]) if not cb.dcs_alphabet is None: # \n is the padding character in CB messages so strip it cb.ud = userdata.decode( cb.dcs_alphabet ).strip("\n") else: cb.ud = userdata return cb def __init__(self): self.dcs_alphabet = "gsm_default" self.dcs_language = None self.dcs_language_indication = False self.dcs_compressed = False self.dcs_mclass = None def _getDCS( self ): if self.dcs_language_indication is None: group = 0x01 dcs = 0x00 # FIXME: Why is language ucs2? if self.dcs_language == "utf_16_be": dcs = 0x01 dcs = group << 4 | dcs else: # not self.dcs_language_indication is None if self.dcs_mclass is None: if self.dcs_language == "Czech": group = 0x02 dcs = 0x00 else: group = 0x00 dcs = CB_PDU_DCS_LANGUAGE.index(self.dcs_language) else: # General data coding group = 0x05 if self.dcs_compressed: group |= 0x02 if self.dcs_alphabet is None : dcs |= 0x1 << 2 elif self.dcs_alphabet == "utf_16_be": dcs |= 0x2 << 2 dcs |= self.dcs_mclass dcs = group << 4 | dcs return dcs def _setDCS( self, dcs ): self.dcs_alphabet = "gsm_default" self.dcs_language = None self.dcs_language_indication = False self.dcs_compressed = False self.dcs_mclass = None group = ( dcs & 0xF0 ) >> 4 if group == 0x00: # language using the default alphabet self.dcs_language = CB_PDU_DCS_LANGUAGE[dcs & 0x0F] elif group == 0x01: # Message with language indication self.dcs_language_indication = True if (dcs & 0x0F) == 0x01: self.dcs_alphabet = "utf_16_be" elif group == 0x02: if (dcs & 0x0F) == 0x00: self.language = "Czech" elif group == 0x03: # Reserved pass elif 0x04 <= group <= 0x07: # General data coding if (dcs & 0x20): self.dcs_compressed = True if (dcs & 0x10): self.dcs_mclass = (dcs & 0x03) if (dcs & 0x0C) >> 2 == 1: self.dcs_alphabet = None elif (dcs & 0x0C) >> 2 == 2: self.dcs_alphabet = "utf_16_be" elif 0x08 <= group <= 0x0D: # Reserved pass elif group == 0x0E: # WAP specific pass elif group == 0x0F: # data coding/message class # dcs & 0x8 (bit 3) is reserved as 0 if dcs & 0x4: self.dcs_alphabet = None self.dcs_mclass = dcs & 0x3 dcs = property( _getDCS, _setDCS ) def pdu( self ): # We don't need to generate the PDU for Cell Broadcasts pass def __repr__(self): return """CellBroadcast SN: %i MID: %i Page: %i Alphabet: %s Language: %s Message: %s""" % (self.sn, self.mid, self.page, self.dcs_alphabet, self.dcs_language, repr(self.ud)) # vim: expandtab shiftwidth=4 tabstop=4 fso-frameworkd-0.10.1/framework/subsystems/ogsmd/helpers.py000066400000000000000000000073561174525413000240770ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: iso-8859-15 -*- """ (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd Module: helpers """ import logging logger = logging.getLogger('ogsmd') #========================================================================# def writeToFile( path, value ): #========================================================================# logger.debug( "(writing '%s' to '%s')" % ( value, path ) ) try: f = open( path, 'w' ) except IOError, e: logger.warning( "(could not write to '%s': %s)" % ( path, e ) ) else: f.write( "%s\n" % value ) #=========================================================================# def safesplit( string, delim, max=-1 ): #=========================================================================# """A split function which is quote sign aware.""" items = string.split(delim) safeitems = [] safeitem = "" for i in items: safeitem = delim.join( [safeitem, i] ) if safeitem.count('"')%2 == 0: safeitems.append( safeitem[1:] ) safeitem = "" if max < len(safeitems): return safeitems[:max] + [delim.join(safeitems[max:])] else: return safeitems #=========================================================================# class BiDict( object ): #=========================================================================# """A bidirectional dictionary.""" AUTOINVERSE = False def __init__( self, adict = {} ): self._d = adict.copy() self._back = {} self._syncBack() def _syncBack( self ): for key, value in self._d.iteritems(): self._back[value] = key assert len( self._d) == len( self._back ), "logic error" def __getitem__( self, key ): if not self.AUTOINVERSE: return self._d[key] else: try: return self._d[key] except KeyError: return self._back[key] def revlookup( self, key ): return self._back[key] def __setitem__( self, key, value ): if value in self._d: raise ValueError( "value is already a key" ) elif key in self._back: raise ValueError( "key is already a value" ) else: try: oldvalue = self._d[key] except KeyError: pass else: del self._back[oldvalue] self._d[key] = value self._back[value] = key assert len( self._d) == len( self._back ), "logic error" def __delitem__( self, key ): try: value = self._d[key] except KeyError: value = self._back[key] del self._back[key] del self._d[value] else: del self._d[key] del self._back[value] assert len( self._d) == len( self._back ), "logic error" def __repr__( self ): return "%s + %s" % ( self._d, self._back ) def keys( self ): if not self.AUTOINVERSE: return self._d.keys() else: return self._d.keys() + self._back.keys() def has_key( self, k ): if not self.AUTOINVERSE: return self._d.has_key(k) else: return self._d.has_key(k) + self._back.has_key(k) #=========================================================================# if __name__ == "__main__": #=========================================================================# d = BiDict( {"x":"y" } ) d["foo"] = "bar" try: d["bar"] = 10 # should bail out except ValueError: pass else: assert False, "axiom violated" assert d["bar"] == "foo" del d["bar"] fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/000077500000000000000000000000001174525413000233345ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/__init__.py000066400000000000000000000050721174525413000254510ustar00rootroot00000000000000#!/usr/bin/env python """ freesmartphone.org ogsmd - Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd.modems """ import sys import logging logger = logging.getLogger( "ogsmd.modems" ) theModem = None theMediator = None modemmap = { \ "ericsson_F3507g": "EricssonF3507g", "cinterion_mc75": "CinterionMc75", "freescale_neptune": "FreescaleNeptune", "muxed4line": "Muxed4Line", "option": "Option", "qualcomm_msm": "QualcommMsm", "sierra": "Sierra", "singleline": "SingleLine", "ti_calypso": "TiCalypso", } allModems = modemmap.keys def modemFactory( modemtype ): logger.debug( "requested to build modem '%s'" % modemtype ) if modemtype not in modemmap: return None, None global theMediator if modemtype == "singleline": from singleline.modem import SingleLine as Modem import singleline.mediator as mediator elif modemtype == "muxed4line": from muxed4line.modem import Muxed4Line as Modem import muxed4line.mediator as mediator elif modemtype == "ti_calypso": from ti_calypso.modem import TiCalypso as Modem import ti_calypso.mediator as mediator elif modemtype == "freescale_neptune": from freescale_neptune.modem import FreescaleNeptune as Modem import freescale_neptune.mediator as mediator elif modemtype == "sierra": from sierra.modem import Sierra as Modem import sierra.mediator as mediator elif modemtype == "option": from option.modem import Option as Modem import option.mediator as mediator elif modemtype == "cinterion_mc75": from cinterion_mc75.modem import CinterionMc75 as Modem import cinterion_mc75.mediator as mediator elif modemtype == "ericsson_F3507g": from ericsson_F3507g.modem import EricssonF3507g as Modem import ericsson_F3507g.mediator as mediator elif modemtype == "qualcomm_msm": from qualcomm_msm.modem import QualcommMsm as Modem import qualcomm_msm.mediator as mediator else: assert False, "must never reach this" sys.exit( -1 ) global theMediator theMediator = mediator return Modem, theMediator def currentModem(): global theModem if theModem is not None: return theModem else: logger.error( "current modem requested before set: exiting" ) sys.exit( -1 ) def setCurrentModem( modem ): global theModem theModem = modem fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/abstract/000077500000000000000000000000001174525413000251375ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/abstract/__init__.py000066400000000000000000000000001174525413000272360ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/abstract/calling.py000066400000000000000000000342451174525413000271320ustar00rootroot00000000000000#!/usr/bin/env python """ freesmartphone.org ogsmd - Python Implementation (C) 2008-2009 Michael 'Mickey' Lauer (C) 2008-2009 Openmoko, Inc. GPLv2 or later Package: ogsmd.modems.abstract Module: calling New style abstract call handling """ __version__ = "0.9.1.4" MODULE_NAME = "ogsmd.callhandler" import mediator from ogsmd import error from ogsmd.gsm import const from framework.patterns.processguard import ProcessGuard import logging logger = logging.getLogger( MODULE_NAME ) #=========================================================================# class CallHandler( object ): #=========================================================================# _instance = None @classmethod def getInstance( klass, dbus_object=None ): if klass._instance is None and dbus_object is not None: klass._instance = CallHandler( dbus_object ) return klass._instance def __init__( self, dbus_object ): self._object = dbus_object self._calls = {} self._calls[1] = { "status": "release" } self._calls[2] = { "status": "release" } # we can have at least 2 calls, more will be added when coming in self.unsetHook() def setHook( self, hook ): self._hook = hook def unsetHook( self ): self._hook = lambda *args, **kwargs: None def isBusy( self ): return self._calls[1]["status"] != "release" or self._calls[2]["status"] != "release" def status( self ): return self._calls[1]["status"], self._calls[2]["status"] # # additional support for data call handling with a customizable data call handler # def onActivateResult( self, request, response ): """ Called after ATA """ if response[0].startswith( "CONNECT" ): # data call succeeded self._onDataCallEstablished() def onInitiateResult( self, request, response ): """ Called after ATDxyz """ if response[0].startswith( "CONNECT" ): # data call succeeded self._onDataCallEstablished() def _onDataCallEstablished( self ): logger.debug( "data call established" ) # if this is a data call, add the port where communication happens self.csdid = callId = 1 if self._calls[1]["status"] == "active" else 2 self.csdchan = channel = self._object.modem.channel( "CallMediator" ) self.statusChangeFromNetwork( callId, { "status": "connect", "port": channel.port() } ) # check whether we have a data call handler registered dataCallHandler = self._object.modem.data( "data-call-handler" ) if dataCallHandler is not None: self.csdchan.freeze() csd_commandline = dataCallHandler.split() if not dataCallHandler.startswith( "/bin/sleep" ): # for debugging csd_commandline += [ channel.port(), self._calls[callId]["direction"] ] self.csdproc = ProcessGuard( csd_commandline ) logger.info( "launching csd handler as commandline %s" % csd_commandline ) self.csdproc.execute( onExit=self._spawnedProcessDone ) else: logger.info( "no csd handler registered" ) def _spawnedProcessDone( self, pid, exitcode, exitsignal ): """ Called after CSD Handler exit. """ logger.info( "csd handler exited with code %d, signal %d" % ( exitcode, exitsignal ) ) # unfreeze self.csdchan.thaw() # release call and resume normal operation # self.release( self.csdid, self._object.modem.channel( "MiscMediator" ) ) self.releaseAll( self._object.modem.channel( "MiscMediator" ) ) # # called from mediators # def initiate( self, dialstring, commchannel ): result = self.feedUserInput( "initiate", dialstring, commchannel ) self._hook( "initiate", result ) return result def activate( self, index, commchannel ): result = self.feedUserInput( "activate", index=index, channel=commchannel ) self._hook( "activate", result ) return result def activateConference( self, index, commchannel ): result = self.feedUserInput( "conference", index=index, channel=commchannel ) self._hook( "conference", result ) return result def release( self, index, commchannel ): result = self.feedUserInput( "release", index=index, channel=commchannel ) self._hook( "release", result ) return result def releaseAll( self, commchannel ): result = self.feedUserInput( "dropall", channel=commchannel ) self._hook( "dropall", result ) return result def hold( self, commchannel ): result = self.feedUserInput( "hold", channel=commchannel ) self._hook( "hold", result ) return result # # called from unsolicited response delegates # def ring( self ): for callId, info in self._calls.items(): if info["status"] == "incoming": self._updateStatus( callId ) break # can't be more than one call incoming at once (GSM limitation) # FIXME is the above comment really true? def statusChangeFromNetwork( self, callId, info ): if not self._calls.has_key(callId): self._calls[callId] = { "status": "release" } lastStatus = self._calls[callId].copy() self._calls[callId].update( info ) if self._calls[callId]["status"] == "release": # release signal always without properties self._calls[callId] = { "status": "release" } if self._calls[callId]["status"] != "incoming": # suppress sending the same signal twice if lastStatus != self._calls[callId]: self._updateStatus( callId ) else: self._updateStatus( callId ) def statusChangeFromNetworkByStatus( self, status, info ): calls = [call for call in self._calls.items() if call[1]["status"] == status] if not len(calls) == 1: raise error.InternalException( "non-unique call state '%'" % status ) self.statusChangeFromNetwork( calls[0][0], info ) # # internal # def _updateStatus( self, callId ): """send dbus signal indicating call status for a callId""" self._object.CallStatus( callId, self._calls[callId]["status"], self._calls[callId] ) def feedUserInput( self, action, *args, **kwargs ): # simple actions # FIXME might rather want to consider using the state machine, since that would be more clear if action == "dropall": kwargs["channel"].enqueue( 'H' ) return True try: state = "state_%s_%s" % ( self._calls[1]["status"], self._calls[2]["status"] ) method = getattr( self, state ) except AttributeError: logger.exception( "unhandled state '%s' in state machine. calls are %s" % ( state, repr(self._calls) ) ) raise error.InternalException( "unhandled state '%s' in state machine. calls are %s" % ( state, repr(self._calls) ) ) else: return method( action, *args, **kwargs ) # # deal with responses from call control commands # def responseFromChannel( self, request, response ): logger.debug( "response from channel to %s = %s", request, response ) def errorFromChannel( self, request, response ): logger.error( "error from channel to %s = %s", request, response ) # # synchronize status # def syncStatus( self, request, response ): mediator.CallListCalls( self._object, self.syncStatus_ok, self.syncStatus_err ) def syncStatus_ok( self, calls ): if len( calls ) > 1: logger.warning( "unhandled case" ) logger.warning( "calls is %s", calls) #return # synthesize status change from network for call in calls: callid, status, properties = call self.statusChangeFromNetwork( callid, {"status": status} ) def syncStatus_err( self, request, error ): logger.error( "error from channel to %s = %s", request, error ) # # state machine actions following. micro states: # # release: line idle, call has been released # incoming: remote party is calling, network is alerting us # outgoing: local party is calling, network is alerting remote party # active: local and remote party talking # held: remote party held # An important command here is +CHLD= # Description # ----------------- # 0 Release all held calls or set the busy state for the waiting call. # 1 Release all active calls. # 1x Release only call x. # 2 Put active calls on hold (and activate the waiting or held call). # 2x Put active calls on hold and activate call x. # 3 Add the held calls to the active conversation. # 4 Add the held calls to the active conversation, and then detach the local subscriber from the conversation. # # action with 1st call, 2nd call released # def state_release_release( self, action, *args, **kwargs ): if action == "initiate": dialstring, commchannel = args commchannel.enqueue( "D%s" % dialstring, self.onInitiateResult, self.errorFromChannel ) return 1 def state_incoming_release( self, action, *args, **kwargs ): if action == "release" and kwargs["index"] == 1: kwargs["channel"].enqueue( 'H' ) return True elif action == "activate" and kwargs["index"] == 1: # FIXME handle data calls here kwargs["channel"].enqueue( 'A', self.onActivateResult ) return True def state_outgoing_release( self, action, *args, **kwargs ): if action == "release" and kwargs["index"] == 1: command = self._object.modem.data( "cancel-outgoing-call" ) kwargs["channel"].enqueue( command ) return True def state_active_release( self, action, *args, **kwargs ): if action == "release" and kwargs["index"] == 1: kwargs["channel"].enqueue( 'H' ) return True elif action == "hold": # put active call on hold without accepting any waiting or held # this is not supported by all modems / networks # thus we must call syncStatus to check self.channel = kwargs["channel"] kwargs["channel"].enqueue( "+CHLD=2", self.syncStatus ) return True # FIXME add state_release_active def state_held_release( self, action, *args, **kwargs ): # state not supported by all modems if action == "release" and kwargs["index"] == 1: kwargs["channel"].enqueue( 'H' ) return True elif action == "activate" and kwargs["index"] == 1: # activate held call self.channel = kwargs["channel"] kwargs["channel"].enqueue( "+CHLD=2", self.syncStatus ) return True elif action == "initiate": dialstring, commchannel = args commchannel.enqueue( "D%s" % dialstring, self.onInitiateResult, self.errorFromChannel ) return 2 # # 1st call active, 2nd call call incoming or on hold # def state_active_incoming( self, action, *args, **kwargs ): if action == "release": if kwargs["index"] == 1: # release active call, waiting call becomes active kwargs["channel"].enqueue( "+CHLD=1" ) return True elif kwargs["index"] == 2: # reject waiting call, sending busy signal kwargs["channel"].enqueue( "+CHLD=0" ) return True elif action == "activate": if kwargs["index"] == 2: # put active call on hold, take waiting call kwargs["channel"].enqueue( "+CHLD=2" ) return True elif action == "conference": # put active call on hold, take waiting call, add held call to conversation kwargs["channel"].enqueue( "+CHLD=2;+CHLD=3" ) return True def state_active_held( self, action, *args, **kwargs ): if action == "release": if kwargs["index"] == 1: # release active call, (auto)activate the held call kwargs["channel"].enqueue( "+CHLD=11" ) return True elif kwargs["index"] == 2: # release held call kwargs["channel"].enqueue( "+CHLD=12" ) return True else: # Fixme: we can have a 3rd call incoming that cannot be accepted, however, but still rejected # TI Calypso indicates the 3rd call, but refuses the index on commanding??? logger.warning("FIXME: callid >2 (%s), don't know what to do", kwargs["index"]) elif action == "activate": # put active call on hold, activate held call kwargs["channel"].enqueue( "+CHLD=2" ) return True elif action == "conference": kwargs["channel"].enqueue( "+CHLD=3" ) return True elif action == "connect": kwargs["channel"].enqueue( "+CHLD=4" ) return True def state_held_active( self, action, *args, **kwargs ): # should be the same as the reversed state return self.state_active_held( action, *args, **kwargs ) # both calls active def state_active_active( self, action, *args, **kwargs ): if action == "release": if kwargs["index"] == 1: # release only call 1 kwargs["channel"].enqueue( "+CHLD=11" ) return True elif kwargs["index"] == 2: kwargs["channel"].enqueue( "+CHLD=12" ) return True elif action == "activate": if kwargs["index"] == 1: # put 2nd call on hold kwargs["channel"].enqueue( "+CHLD=21" ) return True elif kwargs["index"] == 2: # put 1st call on hold kwargs["channel"].enqueue( "+CHLD=22" ) return True elif action == "connect": kwargs["channel"].enqueue( "+CHLD=4" ) return True fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/abstract/channel.py000066400000000000000000000140501174525413000271210ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2007-2008 M. Dietrich (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd.modems.abstract Module: channel """ __version__ = "0.9.1" MODULE_NAME = "ogsmd.modems.abstract.channel" from ogsmd.gsm.decor import logged from ogsmd.gsm.channel import AtCommandChannel import gobject import logging logger = logging.getLogger( MODULE_NAME ) #=========================================================================# class AbstractModemChannel( AtCommandChannel ): #=========================================================================# def __init__( self, *args, **kwargs ): AtCommandChannel.__init__( self, *args, **kwargs ) self.callback = None # NOTE: might make it a weak-reference, so that garbage collection # does not get disturbed by the cirular modem/channel reference self._modem = kwargs["modem"] self._commands = {} self._populateCommands() self._sendCommands( "init" ) # FIXME add warm start handling (querying CFUN and CPIN status) here: # 1. Query CFUN to check whether we are powered # 2. Query CPIN to check whether we are READY # 3. Try to read a message or a phonebook entry # 4. If that works, send the SIM ready signal def modemStateAntennaOn( self ): """ Called, when the modem signalizes the antenna being powered on. """ self._sendCommands( "antenna" ) def modemStateSimUnlocked( self ): """ Called, when the modem signalizes the SIM being unlocked. Override this in your concrete class to issue sending org.freesmartphone.GSM.SIM.ReadyStatus( true ) eventually. """ # FIXME we might want to make this a 'pure virtual' method # don't hammer modem too early with the additional commands # FIXME it's actually modem specific whether we can send the command directly # after +CPIN: READY or not, so we should not have this here gobject.timeout_add_seconds( 10, self._sendCommands, "sim" ) def modemStateSimReady( self ): """ Called, when the modem signalizes the SIM data can be read. """ gobject.timeout_add_seconds( 1, self._sendCommands, "sim" ) def suspend( self, ok_callback, error_callback ): """ Called, when the channel needs to configure the modem for suspending. """ def done( request, response, self=self, ok_callback=ok_callback ): ok_callback( self ) self._sendCommandsNotifyDone( "suspend", done ) def resume( self, ok_callback, error_callback ): def done( request, response, self=self, ok_callback=ok_callback ): ok_callback( self ) self._sendCommandsNotifyDone( "resume", done ) # # internal API # def _sendCommands( self, state ): commands = self._commands[state] if commands: for command in commands: try: commandstring = command() except TypeError: # not a callable commandstring = command self.enqueue( commandstring ) def _sendCommandsNotifyDone( self, state, done_callback ): # FIXME no error handling, just checking when the results are through commands = self._commands[state] if commands: for command in commands[:-1]: try: commandstring = command() except TypeError: # not a callable commandstring = command self.enqueue( commandstring ) command = commands[-1] try: commandstring = command() except TypeError: commandstring = command self.enqueue( commandstring, done_callback, done_callback ) else: done_callback( "", "" ) def _populateCommands( self ): """ Populate the command queues to be sent on modem state changes. """ c = [] # reset c.append( '' ) # wakeup c.append( 'Z' ) # soft reset c.append( 'E0V1' ) # echo off, verbose result on # error and result reporting reporting c.append( '+CMEE=1' ) # report mobile equipment errors: in numerical format c.append( '+CRC=1' ) # cellular result codes: enable extended format c.append( '+CSCS="UCS2"' ) # character set conversion: use UCS2 c.append( '+CSDH=1' ) # show text mode parameters: show values c.append( '+CSNS=0' ) # single numbering scheme: voice # sms c.append( '+CMGF=0' ) # message format: enable pdu mode, disable text mode # unsolicited c.append( '+CLIP=0' ) # calling line identification presentation: disable c.append( '+COLP=0' ) # connected line identification presentation: disable c.append( '+CCWA=0' ) # call waiting: disable self._commands["init"] = c c = [] c.append( '+CSMS=1' ) # GSM Phase 2+ commands: enable def sms_and_cb( self=self ): if self._modem.data( "sim-buffers-sms" ): return "+CNMI=%s" % self._modem.data( "sms-buffered-cb" ) else: return "+CNMI=%s" % self._modem.data( "sms-direct-cb" ) c.append( sms_and_cb ) self._commands["sim"] = c c = [] self._commands["antenna"] = c c = [] self._commands["suspend"] = c c = [] self._commands["resume"] = c def setIntermediateResponseCallback( self, callback ): assert self.callback is None, "callback already set" self.callback = callback def handleUnsolicitedResponse( self, response ): if self.callback is not None: self.callback( response ) else: logger.warning( "UNHANDLED INTERMEDIATE: %s", response ) fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/abstract/mediator.py000066400000000000000000002511451174525413000273250ustar00rootroot00000000000000##!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008-2009 Daniel Willmann (C) 2008-2009 Michael 'Mickey' Lauer (C) 2008-2009 Openmoko, Inc. GPLv2 or later Package: ogsmd.modems.abstract Module: mediator TODO: * refactor to using yield more often * refactor to using more regexps * refactor modem error handling (not command error handling), this is not something we need to do for each and every command. Might do it for the yield stuff, then gradually migrate functions to yield * decouple from calling dbus result, we might want to reuse these functions in non-exported methods as well * recover from traceback in parsing / compiling result code * refactor parameter validation """ __version__ = "0.9.19.2" MODULE_NAME = "ogsmd.modems.abstract.mediator" from ogsmd import error as DBusError from ogsmd.gsm import const, convert from ogsmd.gsm.decor import logged from ogsmd.helpers import safesplit from ogsmd.modems import currentModem import ogsmd.gsm.sms import gobject import re, time, calendar import logging logger = logging.getLogger( MODULE_NAME ) #=========================================================================# class AbstractMediator( object ): #=========================================================================# @logged def __init__( self, dbus_object, dbus_ok, dbus_error, **kwargs ): assert self.__class__.__name__ != "AbstractMediator", "can not instanciate abstract base class" self._object = dbus_object self._ok = dbus_ok self._error = dbus_error self.__dict__.update( **kwargs ) self._commchannel = None def trigger( self ): assert False, "pure virtual function called" @logged def responseFromChannel( self, request, response ): if response[-1].startswith( "ERROR" ) or response[-1].startswith( "NO CARRIER" ): self._error( DBusError.DeviceFailed( "command %s failed" % request ) ) elif response[-1].startswith( "+CM" ) or response[-1].startswith( "+EXT" ): self._handleCmeCmsExtError( response[-1] ) elif response[-1].startswith( "OK" ): self._ok() else: assert False, "should never reach that" @logged def errorFromChannel( self, request, err ): category, details = err if category == "timeout": self._error( DBusError.DeviceTimeout( "device did not answer within %d seconds" % details ) ) else: self._error( DBusError.DeviceFailed( "%s: %s" % ( category, repr(details ) ) ) ) @logged def __del__( self, *args, **kwargs ): pass def _rightHandSide( self, line ): try: result = line.split( ':', 1 )[1] except IndexError: result = line return result.strip() # FIXME compute errors based on actual class name to ease generic error parsing. Examples: # 1.) CME 3 ("Not allowed") is sent upon trying to # register to a network, as well as trying to read a phonebook # entry from the SIM with an index out of bounds -- we must # not map these two to the same org.freesmartphone.GSM error. # 2.) CME 32 ("Network not allowed") => SimBlocked is sent if we # are not already registered. This may be misleading. @logged def _handleCmeCmsExtError( self, line ): category, text = const.parseError( line ) code = int( line.split( ':', 1 )[1] ) e = DBusError.DeviceFailed( "Unhandled %s ERROR: %s" % ( category, text ) ) if category == "CME": if code == 3: # seen as result of +COPS=0 or +CLCK=... w/ auth state = SIM PIN # seen as result of +CPBR w/ index out of bounds e = DBusError.NetworkUnauthorized() elif code == 4: # seen as result of +CCFC=4,2 e = DBusError.NetworkNotSupported() elif code == 10: e = DBusError.SimNotPresent() elif code == 16: e = DBusError.SimAuthFailed( "SIM Authorization code not accepted" ) elif code in ( 21, 22 ): # invalid phonebook index, phonebook entry not found e = DBusError.SimInvalidIndex() elif code == 30: e = DBusError.NetworkNotPresent() elif code in ( 32, 262 ): # 32 if SIM card is not activated e = DBusError.SimBlocked( text ) elif code in ( 5, 6, 7, 11, 12, 15, 17, 18, 48 ): e = DBusError.SimAuthFailed( text ) elif code == 100: e = DBusError.SimNotReady( "Antenna powered off or SIM not unlocked yet" ) # TODO launch idle task that sends an new auth status signal elif category == "CMS": if code == 310: e = DBusError.SimNotPresent() elif code in ( 311, 312, 316, 317, 318 ): e = DBusError.SimAuthFailed() elif code == 321: # invalid message index e = DBusError.SimNotFound() elif code == 322: e = DBusError.SimMemoryFull() elif category == "EXT": if code == 0: if isinstance( self, SimMediator ): e = DBusError.SimInvalidIndex() # invalid parameter on phonebook index e.g. else: e = DBusError.InvalidParameter() else: assert False, "should never reach that" self._error( e ) #=========================================================================# class AbstractYieldSupport( object ): #=========================================================================# """ This class adds support for simplifying control flow by using Python generators to implement coroutines. By inheriting from this class, you can use the following syntax: def trigger( self ): for iteration in ( 1,2,3,4 ): request, response, error = yield( "+CFUN=1" ) if error is None: self._ok( response ) else: self.errorFromChannel( request, error ) """ def __init__( self, *args, **kwargs ): self.generator = self.trigger() if self.generator is not None: toEnqueue = self.generator.next() if type( toEnqueue ) == type( tuple() ): command, prefixes = toEnqueue self._commchannel.enqueue( command, self.genResponseFromChannel, self.genErrorFromChannel, prefixes ) else: self._commchannel.enqueue( toEnqueue, self.genResponseFromChannel, self.genErrorFromChannel ) def trigger( self ): assert False, "pure virtual method called" @logged def genResponseFromChannel( self, request, response ): try: toEnqueue = self.generator.send( ( request, response, None ) ) except StopIteration: pass else: if type( toEnqueue ) == type( tuple() ): command, prefixes = toEnqueue self._commchannel.enqueue( command, self.genResponseFromChannel, self.genErrorFromChannel, prefixes ) else: self._commchannel.enqueue( toEnqueue, self.genResponseFromChannel, self.genErrorFromChannel ) @logged def genErrorFromChannel( self, request, error ): try: toEnqueue = self.generator.send( ( request, None, error ) ) except StopIteration: pass else: if type( toEnqueue ) == type( tuple() ): command, prefixes = toEnqueue self._commchannel.enqueue( command, self.genResponseFromChannel, self.genErrorFromChannel, prefixes ) else: self._commchannel.enqueue( toEnqueue, self.genResponseFromChannel, self.genErrorFromChannel ) #=========================================================================# class DeviceMediator( AbstractMediator, AbstractYieldSupport ): #=========================================================================# def __init__( self, *args, **kwargs ): AbstractMediator.__init__( self, *args, **kwargs ) # this is a bit ugly, but how should we get the channel elsewhere? self._commchannel = self._object.modem.communicationChannel( "DeviceMediator" ) AbstractYieldSupport.__init__( self, *args, **kwargs ) #=========================================================================# class SimMediator( AbstractMediator, AbstractYieldSupport ): #=========================================================================# def __init__( self, *args, **kwargs ): AbstractMediator.__init__( self, *args, **kwargs ) # this is a bit ugly, but how should we get the channel elsewhere? self._commchannel = self._object.modem.communicationChannel( "SimMediator" ) AbstractYieldSupport.__init__( self, *args, **kwargs ) #=========================================================================# class SmsMediator( AbstractMediator, AbstractYieldSupport ): #=========================================================================# def __init__( self, *args, **kwargs ): AbstractMediator.__init__( self, *args, **kwargs ) # this is a bit ugly, but how should we get the channel elsewhere? self._commchannel = self._object.modem.communicationChannel( "SmsMediator" ) AbstractYieldSupport.__init__( self, *args, **kwargs ) #=========================================================================# class NetworkMediator( AbstractMediator, AbstractYieldSupport ): #=========================================================================# def __init__( self, *args, **kwargs ): AbstractMediator.__init__( self, *args, **kwargs ) # this is a bit ugly, but how should we get the channel elsewhere? self._commchannel = self._object.modem.communicationChannel( "NetworkMediator" ) AbstractYieldSupport.__init__( self, *args, **kwargs ) #=========================================================================# class CallMediator( AbstractMediator, AbstractYieldSupport ): #=========================================================================# def __init__( self, *args, **kwargs ): AbstractMediator.__init__( self, *args, **kwargs ) # this is a bit ugly, but how should we get the channel elsewhere? self._commchannel = self._object.modem.communicationChannel( "CallMediator" ) AbstractYieldSupport.__init__( self, *args, **kwargs ) #=========================================================================# class PdpMediator( AbstractMediator, AbstractYieldSupport ): #=========================================================================# def __init__( self, *args, **kwargs ): AbstractMediator.__init__( self, *args, **kwargs ) # this is a bit ugly, but how should we get the channel elsewhere? self._commchannel = self._object.modem.communicationChannel( "PdpMediator" ) AbstractYieldSupport.__init__( self, *args, **kwargs ) #=========================================================================# class CbMediator( AbstractMediator, AbstractYieldSupport ): #=========================================================================# def __init__( self, *args, **kwargs ): AbstractMediator.__init__( self, *args, **kwargs ) # this is a bit ugly, but how should we get the channel elsewhere? self._commchannel = self._object.modem.communicationChannel( "CbMediator" ) AbstractYieldSupport.__init__( self, *args, **kwargs ) #=========================================================================# class DebugMediator( AbstractMediator, AbstractYieldSupport ): #=========================================================================# def __init__( self, *args, **kwargs ): AbstractMediator.__init__( self, *args, **kwargs ) # this is a bit ugly, but how should we get the channel elsewhere? self._commchannel = self._object.modem.communicationChannel( "DebugMediator" ) AbstractYieldSupport.__init__( self, *args, **kwargs ) #=========================================================================# class MonitorMediator( AbstractMediator, AbstractYieldSupport ): #=========================================================================# def __init__( self, *args, **kwargs ): AbstractMediator.__init__( self, *args, **kwargs ) # this is a bit ugly, but how should we get the channel elsewhere? self._commchannel = self._object.modem.communicationChannel( "MonitorMediator" ) AbstractYieldSupport.__init__( self, *args, **kwargs ) # # import singletons # from .calling import CallHandler from .pdp import Pdp # # Device Mediators # #=========================================================================# class DeviceGetInfo( DeviceMediator ): #=========================================================================# def trigger( self ): # According to GSM 07.07, it's legal to not answer quoting the prefixes for these four informational # requests, hence we allow all prefixes. NOTE: Yes, this opens a slight possibility of unsolicited # creeping unnoticed into. To fix this properly, we would need to enhance the prefixmap to also specify # something like: [ "+CGMR", "+CGMM", "+CGMI", "+CGSN", "plaintext" ], "plaintext" being everything # else that does _not_ look like a response. self._commchannel.enqueue( "+CGMR;+CGMM;+CGMI;+CGSN", self.responseFromChannel, self.errorFromChannel, prefixes=[""] ) def responseFromChannel( self, request, response ): if response[-1] != "OK": DeviceMediator.responseFromChannel( self, request, response ) else: result = {} if len( response ) > 1: result["revision"] = self._rightHandSide( response[0] ) if len( response ) > 2: result["model"] = self._rightHandSide( response[1] ) if len( response ) > 3: result["manufacturer"] = self._rightHandSide( response[2] ) if len( response ) > 4: result["imei"] = self._rightHandSide( response[3] ) self._ok( result ) #=========================================================================# class DeviceGetAntennaPower( DeviceMediator ): #=========================================================================# def trigger( self ): self._commchannel.enqueue( "+CFUN?", self.responseFromChannel, self.errorFromChannel ) @logged def responseFromChannel( self, request, response ): if ( response[-1] == "OK" ): self._ok( not self._rightHandSide( response[0] ) == "0" ) else: DeviceMediator.responseFromChannel( self, request, response ) #=========================================================================# class DeviceSetAntennaPower( DeviceMediator ): #=========================================================================# # FIXME: Do not call CPIN? directly, use the GetAuthStatus mediator instead! def trigger( self ): self._commchannel.enqueue( "+CPIN?", self.intermediateResponse, self.errorFromChannel ) def intermediateResponse( self, request, response ): if not response[-1] == "OK": pin_state = "UNKNOWN" else: pin_state = self._rightHandSide( response[0] ).strip( '"' ) # some modems include " if pin_state != self._object.modem._simPinState: self._object.AuthStatus( pin_state ) self._commchannel.enqueue( "+CFUN=%d" % self.power, self.responseFromChannel, self.errorFromChannel ) @logged def responseFromChannel( self, request, response ): # FIXME: So far I have not seen any modem where +CFUN=1 _really_ fails # (yes, they may respond with a +CME error, but still they turn on full functionality) # If this is not the case, then we need to add a +CFUN? check here, before toggling # stateAntennaOn if self.power: self._object.modem.stateAntennaOn() if response[-1] == "OK": self._ok() else: DeviceMediator.responseFromChannel( self, request, response ) self._commchannel.enqueue( "+CPIN?", self.intermediateResponse2, self.errorFromChannel ) def intermediateResponse2( self, request, response ): if not response[-1] == "OK": # unknown PIN state pin_state = "UNKNOWN" else: pin_state = self._rightHandSide( response[0] ).strip( '"' ) # some modems include " if pin_state != self._object.modem._simPinState: self._object.AuthStatus( pin_state ) #=========================================================================# class DeviceGetFeatures( DeviceMediator ): #=========================================================================# def trigger( self ): result = {} request, response, error = yield( "+GCAP", [""] ) # free format allowed as per GSM 07.07 if error is None and response[-1] == "OK": if "GSM" in response[0]: result["GSM"] = "TA" # terminal adapter else: result["GSM"] = "?" # some modems lie about their GSM capabilities if "FCLASS" in response[0]: result["FAX"] = "" # basic capability, checking for details in a second request, response, error = yield( "+FCLASS?", [""] ) # free format allowed as per GSM 07.07 if error is None and response[-1] == "OK": result["FAX"] = self._rightHandSide( response[0] ).strip( '"' ) request, response, error = yield( "+CGCLASS?", [""] ) # free format allowed as per GSM 07.07 if error is None and response[-1] == "OK": result["GPRS"] = self._rightHandSide( response[0] ).strip( '"' ) else: result["GPRS"] = "?" self._ok( result ) #=========================================================================# class DeviceGetSimBuffersSms( DeviceMediator ): #=========================================================================# def trigger( self ): # CNMI needs to be issued on the unsolicited channel, otherwise +CMT wont go there commchannel = self._object.modem.communicationChannel( "UnsolicitedMediator" ) commchannel.enqueue( "+CNMI?", self.responseFromChannel, self.errorFromChannel ) @logged def responseFromChannel( self, request, response ): if ( response[-1] == "OK" ): mode, mt, bm, ds, bfr = safesplit( self._rightHandSide( response[0] ), ',' ) sim_buffers_sms = ( int( mt ) == 1 ) self._ok( sim_buffers_sms ) else: DeviceMediator.responseFromChannel( self, request, response ) #=========================================================================# class DeviceSetSimBuffersSms( DeviceMediator ): #=========================================================================# def trigger( self ): self._object.modem.setData( "sim-buffers-sms", self.sim_buffers_sms ) if self._object.modem.data( "sim-buffers-sms" ): params = self._object.modem.data( "sms-buffered-cb" ) else: params = self._object.modem.data( "sms-direct-cb" ) # CNMI needs to be issued on the unsolicited channel, otherwise +CMT wont go there commchannel = self._object.modem.communicationChannel( "UnsolicitedMediator" ) commchannel.enqueue( "+CNMI=%s" % params, self.responseFromChannel, self.errorFromChannel ) #=========================================================================# class DeviceGetSpeakerVolume( DeviceMediator ): #=========================================================================# def trigger( self ): low, high = self._object.modem.data( "speaker-volume-range", ( None, None ) ) if low is None: request, response, error = yield( "+CLVL=?" ) if error is None and response[-1] == "OK": low, high = self._rightHandSide( response[0] ).strip( "()" ).split( '-' ) low, high = int(low), int(high) self._object.modem.setData( "speaker-volume-range", ( low, high ) ) else: # command not supported, assume 0-255 self._object.modem.setData( "speaker-volume-range", ( 0, 255 ) ) # send it request, response, error = yield( "+CLVL?" ) if response[-1] == "OK" and response[0].startswith( "+CLVL" ): low, high = self._object.modem.data( "speaker-volume-range", ( None, None ) ) value = int( self._rightHandSide( response[0] ) ) * 100 / ( high-low ) self._ok( value ) else: DeviceMediator.responseFromChannel( self, request, response ) #=========================================================================# class DeviceSetSpeakerVolume( DeviceMediator ): #=========================================================================# def trigger( self ): if 0 <= self.modem_volume <= 100: low, high = self._object.modem.data( "speaker-volume-range", ( None, None ) ) if low is None: request, response, error = yield( "+CLVL=?" ) if error is None and response[-1] == "OK": low, high = self._rightHandSide( response[0] ).strip( "()" ).split( '-' ) low, high = int(low), int(high) self._object.modem.setData( "speaker-volume-range", ( low, high ) ) else: # command not supported, assume 0-255 self._object.modem.setData( "speaker-volume-range", ( 0, 255 ) ) value = low + self.modem_volume * (high-low) / 100 request, response, error = yield( "+CLVL=%d" % value ) if error is not None: self.errorFromChannel( request, error ) else: self.responseFromChannel( request, response ) else: self._error( DBusError.InvalidParameter( "Volume needs to be within [ 0, 100 ]." ) ) #=========================================================================# class DeviceGetMicrophoneMuted( DeviceMediator ): #=========================================================================# def trigger( self ): self._commchannel.enqueue( "+CMUT?", self.responseFromChannel, self.errorFromChannel ) @logged def responseFromChannel( self, request, response ): if response[-1] == "OK" and response[0].startswith( "+CMUT" ): value = int( self._rightHandSide( response[0] ) ) self._ok( value == 1 ) else: DeviceMediator.responseFromChannel( self, request, response ) #=========================================================================# class DeviceSetMicrophoneMuted( DeviceMediator ): #=========================================================================# def trigger( self ): self._commchannel.enqueue( "+CMUT=%d" % self.muted, self.responseFromChannel, self.errorFromChannel ) #=========================================================================# class DeviceGetPowerStatus( DeviceMediator ): #=========================================================================# def trigger( self ): self._commchannel.enqueue( "+CBC", self.responseFromChannel, self.errorFromChannel ) def responseFromChannel( self, request, response ): if response[-1] != "OK": DeviceMediator.responseFromChannel( self, request, response ) else: values = safesplit( self._rightHandSide( response[0] ), ',' ) if len( values ) > 0: status = const.DEVICE_POWER_STATUS.get( int(values[0]), "unknown" ) else: status = "unknown" if len( values ) > 1: level = int(values[1]) else: level = -1 self._ok( status, level ) #=========================================================================# class DeviceSetRTC( DeviceMediator ): #=========================================================================# def trigger( self ): # FIXME: gather timezone offset and supply timezone = "+00" timestring = time.strftime("%y/%m/%d,%H:%M:%S" + timezone) self._commchannel.enqueue( "+CCLK=\"%s\"" % timestring, self.responseFromChannel, self.errorFromChannel ) #=========================================================================# class DeviceGetRTC( DeviceMediator ): # i #=========================================================================# def trigger( self ): self._commchannel.enqueue( "+CCLK?", self.responseFromChannel, self.errorFromChannel ) def responseFromChannel( self, request, response ): if response[-1] != "OK": DeviceMediator.responseFromChannel( self, request, response ) else: dat, tim = self._rightHandSide( response[0] ).strip( '"' ).split( ',' ) # timezone not yet supported if tim[-3] == '+': tim = tim[-3] # some modems strip the leading zero for one-digit chars, hence we need to split and reassemble on our own year, month, day = dat.split( '/' ) hour, minute, second = tim.split( ':' ) timestruct = time.strptime( "%02d/%02d/%02d,%02d:%02d:%02d" % ( int(year), int(month), int(day), int(hour), int(minute), int(second) ), "%y/%m/%d,%H:%M:%S" ) self._ok( calendar.timegm( timestruct ) ) # # SIM Mediators # #=========================================================================# class SimGetAuthStatus( SimMediator ): #=========================================================================# # FIXME: Add SIM PIN known/unknown logic here in order to prepare for changing SetAntennaPower() semantics def trigger( self ): self._commchannel.enqueue( "+CPIN?", self.responseFromChannel, self.errorFromChannel ) @logged def responseFromChannel( self, request, response ): if response[-1] == "OK": pin_state = self._rightHandSide( response[0] ).strip( '"' ) # some modems include " self._ok( pin_state ) else: SimMediator.responseFromChannel( self, request, response ) #=========================================================================# class SimSendAuthCode( SimMediator ): #=========================================================================# def trigger( self ): self._commchannel.enqueue( '+CPIN="%s"' % self.code, self.responseFromChannel, self.errorFromChannel ) @logged def responseFromChannel( self, request, response ): if response[-1] == "OK": self._ok() # send auth status signal if response[0].startswith( "+CPIN" ): self._object.AuthStatus( self._rightHandSide( response[0] ) ) else: SimMediator.responseFromChannel( self, request, response ) #=========================================================================# class SimUnlock( SimMediator ): #=========================================================================# def trigger( self ): self._commchannel.enqueue( '+CPIN="%s","%s"' % ( self.puk, self.new_pin ), self.responseFromChannel, self.errorFromChannel ) @logged def responseFromChannel( self, request, response ): if response[-1] == "OK": self._ok() # send auth status signal if response[0].startswith( "+CPIN" ): self._object.AuthStatus( self._rightHandSide( response[0] ) ) else: SimMediator.responseFromChannel( self, request, response ) #=========================================================================# class SimChangeAuthCode( SimMediator ): #=========================================================================# def trigger( self ): self._commchannel.enqueue( '+CPWD="SC","%s","%s"' % ( self.old_pin, self.new_pin ), self.responseFromChannel, self.errorFromChannel ) #=========================================================================# class SimGetAuthCodeRequired( SimMediator ): #=========================================================================# def trigger( self ): self._commchannel.enqueue( '+CLCK="SC",2', self.responseFromChannel, self.errorFromChannel ) def responseFromChannel( self, request, response ): if response[-1] != "OK": SimMediator.responseFromChannel( self, request, response ) else: # we only look at the status parameter, we ignore the class parameters values = self._rightHandSide( response[0] ).split( ',' ) self._ok( values[0] == "1" ) #=========================================================================# class SimSetAuthCodeRequired( SimMediator ): #=========================================================================# def trigger( self ): self._commchannel.enqueue( '+CLCK="SC",%d,"%s"' % ( self.required, self.pin ), self.responseFromChannel, self.errorFromChannel ) #=========================================================================# class SimGetSimInfo( SimMediator ): #=========================================================================# def trigger( self ): result = {} # imsi request, response, error = yield( "+CIMI", [""] ) # free format allowed as per GSM 07.07 if error is not None: self.errorFromChannel( request, error ) else: if response[-1] != "OK": SimMediator.responseFromChannel( self, request, response ) else: # not using self._rightHandSide() here since some modems # do not include the +CIMI: prefix imsi = result["imsi"] = response[0].replace( "+CIMI: ", "" ).strip( '"' ) code, name = const.mccToCountryCode( int( imsi[:3] ) ) result["dial_prefix"] = code result["country"] = name request, response, error = yield( "+CNUM" ) if error is not None: self.errorFromChannel( request, error ) else: if response[-1] != "OK": # it's perfectly ok for the subscriber number to be not present on the SIM self._ok( result ) else: subscriber_numbers = [] for line in response[:-1]: alpha, number, ntype = safesplit( self._rightHandSide( line ), "," ) subscriber_numbers.append( ( alpha.replace( '"', "" ), const.phonebookTupleToNumber( number, int(ntype) ) ) ) result["subscriber_numbers"] = subscriber_numbers self._ok( result ) #=========================================================================# class SimSendGenericSimCommand( SimMediator ): #=========================================================================# def trigger( self ): message = "%d,%s" % ( len( self.command ), self.command ) self._commchannel.enqueue( "+CSIM=%s" % message, self.responseFromChannel, self.errorFromChannel ) @logged def responseFromChannel( self, request, response ): if response[-1] != "OK": SimMediator.responseFromChannel( self, request, response ) else: length, result = safesplit( self._rightHandSide( response[0] ), ',' ) self._ok( result ) #=========================================================================# class SimSendRestrictedSimCommand( SimMediator ): #=========================================================================# def trigger( self ): message = "%d,%d,%d,%d,%d,%s" % ( self.command, self.fileid, self.p1, self.p2, self.p3, self.data ) self._commchannel.enqueue( "+CRSM=%s" % message, self.responseFromChannel, self.errorFromChannel ) @logged def responseFromChannel( self, request, response ): if response[-1] != "OK": SimMediator.responseFromChannel( self, request, response ) else: values = safesplit( self._rightHandSide( response[0] ), ',' ) if len( values ) == 2: result = [ int(values[0]), int(values[1]), "" ] elif len( values ) == 3: result = [ int(values[0]), int(values[1]), values[2] ] else: assert False, "parsing error" self._ok( *result ) #=========================================================================# class SimGetHomeZones( SimMediator ): # a(siii) #=========================================================================# def trigger( self ): self._commchannel.enqueue( "+CRSM=176,28512,0,0,123", self.responseFromChannel, self.errorFromChannel ) def addHomeZone( self, data, number, result ): #print data[0:52] if int( data[0:2], 16 ) == number: x = int( data[2:10], 16 ) y = int( data[10:18], 16 ) r = int( data[18:26], 16 ) nameraw = data[28:52] name = "" for index in xrange( 0, 24, 2 ): c = int(nameraw[index:index+2],16) if 32 < c < 128: name += chr(c) else: break if x+y+r: result.append( [ name, x, y, r ] ) def responseFromChannel( self, request, response ): if response[-1] != "OK": SimMediator.responseFromChannel( self, request, response ) try: sw1, sw2, payload = safesplit( self._rightHandSide( response[0] ), "," ) except ValueError: # response did not include a payload self._ok( [] ) else: if int(sw1) != 144 or int(sw2) != 0: # command succeeded as per GSM 11.11, 9.4.1 self._ok( [] ) else: result = [] for i in xrange( 4 ): self.addHomeZone( payload[34+52*i:34+52*(i+1)], i+1, result ) self._ok( result ) #=========================================================================# class SimGetIssuer( SimMediator ): # s #=========================================================================# def trigger( self ): self._commchannel.enqueue( "+CRSM=176,28486,0,0,17", self.responseFromChannel, self.errorFromChannel ) def responseFromChannel( self, request, response ): if response[-1] != "OK": SimMediator.responseFromChannel( self, request, response ) try: sw1, sw2, payload = safesplit( self._rightHandSide( response[0] ), "," ) except ValueError: # response did not include a payload self._error( DBusError.SimNotFound( "Elementary record not present or unreadable" ) ) else: if int(sw1) != 144 or int(sw2) != 0: # command succeeded as per GSM 11.11, 9.4.1 self._error( DBusError.SimNotFound( "Elementary record not present or unreadable" ) ) else: nameraw = payload[2:] name = "" for index in xrange( 0, 24, 2 ): c = int(nameraw[index:index+2],16) if 32 < c < 128: name += chr(c) else: break self._ok( name ) #=========================================================================# class SimGetProviderList( SimMediator ): # a{ss} #=========================================================================# def trigger( self ): self._commchannel.enqueue( "+COPN", self.responseFromChannel, self.errorFromChannel ) def responseFromChannel( self, request, response ): if response[-1] != "OK": SimMediator.responseFromChannel( self, request, response ) else: charset = currentModem()._charsets["DEFAULT"] result = {} for line in response[:-1]: mccmnc, name = safesplit( self._rightHandSide( line ), ',' ) # Some modems contain provider tables with illegal characters try: uname = name.strip('" ').decode(charset) except UnicodeError: # Should we even add this to the list if we cannot decode it? # XXX: It looks like this should actually be decodable, it's just (again) # a problem with different charsets... uname = "" result[ mccmnc.strip( '" ').decode(charset) ] = uname return self._ok( result ) #=========================================================================# class SimListPhonebooks( SimMediator ): #=========================================================================# def trigger( self ): self._commchannel.enqueue( "+CPBS=?", self.responseFromChannel, self.errorFromChannel ) @logged def responseFromChannel( self, request, response ): charset = currentModem()._charsets["DEFAULT"] if ( response[-1] == "OK" ): result = [] for pb in re.findall( const.PAT_STRING, response[0] ): try: result.append( const.PHONEBOOK_CATEGORY.revlookup(pb.decode(charset)) ) except KeyError: pass self._ok( result ) else: SimMediator.responseFromChannel( self, request, response ) # # FIXME: we should try harder here -- if a modem does not support # +CBPS=?, then we could iterate through our list of known phonebooks # and try to select it +CPBS="..." and build the list up from these results #=========================================================================# class SimGetPhonebookInfo( SimMediator ): #=========================================================================# def trigger( self ): charset = currentModem()._charsets["DEFAULT"] try: self.pbcategory = const.PHONEBOOK_CATEGORY[self.category] except KeyError: self._error( DBusError.InvalidParameter( "valid categories are %s" % const.PHONEBOOK_CATEGORY.keys() ) ) else: self._commchannel.enqueue( '+CPBS="%s";+CPBR=?' % self.pbcategory.encode(charset), self.responseFromChannel, self.errorFromChannel ) def responseFromChannel( self, request, response ): if response[-1] != "OK": SimMediator.responseFromChannel( self, request, response ) else: result = {} match = const.PAT_PHONEBOOK_INFO.match( self._rightHandSide( response[0] ) ) result["min_index"] = int(match.groupdict()["lowest"]) result["max_index"] = int(match.groupdict()["highest"]) try: result["number_length"] = int(match.groupdict()["numlen"]) result["name_length"] = int(match.groupdict()["textlen"]) self._object.modem.setPhonebookSizes( self.pbcategory, result["number_length"], result["name_length"] ) except KeyError: pass # store in modem class for later use self._object.modem.setPhonebookIndices( self.pbcategory, result["min_index"], result["max_index"] ) self._ok( result ) #=========================================================================# class SimGetPhonebookStorageInfo( SimMediator ): #=========================================================================# def trigger( self ): charset = currentModem()._charsets["DEFAULT"] try: self.pbcategory = const.PHONEBOOK_CATEGORY[self.category] except KeyError: self._error( DBusError.InvalidParameter( "valid categories are %s" % const.PHONEBOOK_CATEGORY.keys() ) ) else: self._commchannel.enqueue( '+CPBS="%s";+CPBS?' % self.pbcategory.encode(charset), self.responseFromChannel, self.errorFromChannel ) def responseFromChannel( self, request, response ): if response[-1] != "OK": SimMediator.responseFromChannel( self, request, response ) else: name, used, total = safesplit( self._rightHandSide( response[0] ), "," ) used = int( used ) total = int( total ) self._ok( used , total) #=========================================================================# class SimRetrievePhonebook( SimMediator ): #=========================================================================# def trigger( self ): charset = currentModem()._charsets["DEFAULT"] try: self.pbcategory = const.PHONEBOOK_CATEGORY[self.category] except KeyError: self._error( DBusError.InvalidParameter( "valid categories are %s" % const.PHONEBOOK_CATEGORY.keys() ) ) else: if self.indexFirst != -1: minimum = self.indexFirst maximum = self.indexLast else: minimum, maximum = self._object.modem.phonebookIndices( self.pbcategory ) if minimum is None: # don't know yet SimGetPhonebookInfo( self._object, self.tryAgain, self.reportError, category=self.category ) else: self._commchannel.enqueue( '+CPBS="%s";+CPBR=%d,%d' % ( self.pbcategory.encode(charset), minimum, maximum ), self.responseFromChannel, self.errorFromChannel ) @logged def responseFromChannel( self, request, response ): defcharset = currentModem()._charsets["DEFAULT"] charset = currentModem()._charsets["PHONEBOOK"] if response[-1] != "OK": SimMediator.responseFromChannel( self, request, response ) else: result = [] for entry in response[:-1]: index, number, ntype, name = safesplit( self._rightHandSide( entry ), ',' ) index = int( index ) number = number.strip( '"' ).decode(defcharset) ntype = int( ntype ) name = name.strip('"').decode(charset) result.append( ( index, name, const.phonebookTupleToNumber( number, ntype ) ) ) self._ok( result ) def tryAgain( self, result ): charset = currentModem()._charsets["DEFAULT"] minimum, maximum = self._object.modem.phonebookIndices( self.pbcategory ) if minimum is None: # still? raise DBusError.InternalException( "can't get valid phonebook indices for phonebook %s from modem" % self.pbcategory ) else: self._commchannel.enqueue( '+CPBS="%s";+CPBR=%d,%d' % ( self.pbcategory.encode(charset), minimum, maximum ), self.responseFromChannel, self.errorFromChannel ) def reportError( self, result ): self._error( result ) #=========================================================================# class SimDeleteEntry( SimMediator ): #=========================================================================# def trigger( self ): try: self.pbcategory = const.PHONEBOOK_CATEGORY[self.category] except KeyError: self._error( DBusError.InvalidParameter( "valid categories are %s" % const.PHONEBOOK_CATEGORY.keys() ) ) else: self._commchannel.enqueue( '+CPBS="%s";+CPBW=%d,,,' % ( self.pbcategory, self.index ), self.responseFromChannel, self.errorFromChannel ) #=========================================================================# class SimStoreEntry( SimMediator ): #=========================================================================# def trigger( self ): charset = currentModem()._charsets["PHONEBOOK"] defcharset = currentModem()._charsets["DEFAULT"] try: self.pbcategory = const.PHONEBOOK_CATEGORY[self.category] except KeyError: self._error( DBusError.InvalidParameter( "valid categories are %s" % const.PHONEBOOK_CATEGORY.keys() ) ) else: number, ntype = currentModem().numberToPhonebookTuple( self.number ) name = self.name.strip('"') numlength, textlength = self._object.modem.phonebookSizes( self.pbcategory ) if numlength is not None: if len(number) > numlength: number = number[:numlength] if textlength is not None: if len(name) > textlength: name = name[:textlength] name = name.encode(charset) self._commchannel.enqueue( '+CPBS="%s";+CPBW=%d,"%s",%d,"%s"' % ( self.pbcategory.encode(defcharset), self.index, number, ntype, name ), self.responseFromChannel, self.errorFromChannel ) #=========================================================================# class SimRetrieveEntry( SimMediator ): #=========================================================================# def trigger( self ): charset = currentModem()._charsets["DEFAULT"] try: self.pbcategory = const.PHONEBOOK_CATEGORY[self.category] except KeyError: self._error( DBusError.InvalidParameter( "valid categories are %s" % const.PHONEBOOK_CATEGORY.keys() ) ) else: self._commchannel.enqueue( '+CPBS="%s";+CPBR=%d' % ( self.pbcategory.encode(charset), self.index ), self.responseFromChannel, self.errorFromChannel ) @logged def responseFromChannel( self, request, response ): charset = currentModem()._charsets["PHONEBOOK"] if response[-1] != "OK": SimMediator.responseFromChannel( self, request, response ) else: if len( response ) == 1: self._ok( "", "" ) else: if response[0].startswith( "+CPBR" ): index, number, ntype, name = safesplit( self._rightHandSide( response[0] ), ',' ) index = int( index ) number = number.strip( '"' ) ntype = int( ntype ) name = name.strip('"').decode(charset) self._ok( name, const.phonebookTupleToNumber( number, ntype ) ) #=========================================================================# class SimGetServiceCenterNumber( SimMediator ): #=========================================================================# def trigger( self ): self._commchannel.enqueue( "+CSCA?", self.responseFromChannel, self.errorFromChannel ) @logged def responseFromChannel( self, request, response ): if ( response[-1] == "OK" ): result = safesplit( self._rightHandSide( response[0] ), ',' ) if len( result ) == 2: number, ntype = result else: number, ntype = result, 145 number = number.replace( '+', '' ) # normalize self._ok( const.phonebookTupleToNumber( number.strip( '"' ), int(ntype) ) ) else: SimMediator.responseFromChannel( self, request, response ) #=========================================================================# class SimGetMessagebookInfo( SimMediator ): #=========================================================================# def trigger( self ): self._commchannel.enqueue( '+CPMS="SM","SM","SM"', self.responseFromChannel, self.errorFromChannel ) @logged def responseFromChannel( self, request, response ): if response[-1] != "OK": SimMediator.responseFromChannel( self, request, response ) else: afull, amax, bfull, bmax, cfull, cmax = safesplit( self._rightHandSide( response[0] ), ',' ) result = {} # FIXME Can we safely ignore all but the first tuple always? result.update( first=1, last=int(amax), used=int(afull) ) self._ok( result ) #=========================================================================# class SimRetrieveMessagebook( SimMediator ): #=========================================================================# def trigger( self ): try: category = const.SMS_PDU_STATUS_IN[self.category] except KeyError: self._error( DBusError.InvalidParameter( "valid categories are %s" % const.SMS_PDU_STATUS_IN.keys() ) ) else: self._commchannel.enqueue( '+CMGL=%i' % category, self.responseFromChannel, self.errorFromChannel ) @logged def responseFromChannel( self, request, response ): # some modems (TI Calypso for a start) do return +CMS Error: 321 here (not found) # if you request a category for which no messages are found if response[-1] in ( "OK", "+CMS ERROR: 321" ): result = [] inbody = False for line in response[:-1]: #print "parsing line", line if line.startswith( "+CMGL" ): #print "line is header line" header = const.PAT_SMS_PDU_HEADER.match( self._rightHandSide(line) ) index = int(header.groupdict()["index"]) status = const.SMS_PDU_STATUS_OUT[int(header.groupdict()["status"])] if "read" in status: direction = "guess-deliver" else: direction = "guess-submit" length = int(header.groupdict()["pdulen"]) inbody = True elif inbody == True: # Now we decode the actual PDU inbody = False try: sms = ogsmd.gsm.sms.SMS.decode( line, direction ) except UnicodeError: # Report an error so ogsmd doesn't bail out and we can # see which PDU makes trouble result.append( ( index, status, "Error decoding", "Error decoding", {} ) ) else: result.append( ( index, status, str(sms.addr), sms.ud, sms.properties ) ) else: logger.warning( "SinRetrieveMessagebook encountered strange answer to AT+CMGL: '%s'" % line ) self._ok( result ) else: SimMediator.responseFromChannel( self, request, response ) #=========================================================================# class SimRetrieveMessage( SimMediator ): #=========================================================================# def trigger( self ): self._commchannel.enqueue( '+CMGR=%d' % self.index, self.responseFromChannel, self.errorFromChannel ) @logged def responseFromChannel( self, request, response ): if response[-1] != "OK": SimMediator.responseFromChannel( self, request, response ) else: inbody = False for line in response[:-1]: #print "parsing line", line if line.startswith( "+CMGR" ): #print "line is header line" header = const.PAT_SMS_PDU_HEADER_SINGLE.match( self._rightHandSide(line) ) status = const.SMS_PDU_STATUS_OUT[int(header.groupdict()["status"])] if "read" in status: direction = "guess-deliver" else: direction = "guess-submit" length = int(header.groupdict()["pdulen"]) inbody = True elif inbody == True: inbody = False # Now we decode the actual PDU sms = ogsmd.gsm.sms.SMS.decode( line, direction ) result = ( status, str(sms.addr), sms.ud, sms.properties ) else: logger.warning( "SimRetrieveMessage encountered strange answer to AT+CMGR: '%s'" % line ) self._ok( *result ) #=========================================================================# class SimSetServiceCenterNumber( SimMediator ): #=========================================================================# def trigger( self ): if not self.number.startswith( '+' ): self.number = "+%s" % self.number self._commchannel.enqueue( '+CSCA="%s",145' % self.number, self.responseFromChannel, self.errorFromChannel ) #=========================================================================# class SimStoreMessage( SimMediator ): #=========================================================================# def trigger( self ): sms = ogsmd.gsm.sms.SMSSubmit() # Use PDUAddress sms.addr = ogsmd.gsm.sms.PDUAddress.guess( self.number ) sms.ud = self.contents sms.properties = self.properties pdu = sms.pdu() self._commchannel.enqueue( '+CMGW=%i\r%s' % ( len(pdu)/2-1, pdu), self.responseFromChannel, self.errorFromChannel ) def responseFromChannel( self, request, response ): if response[-1] != "OK": SimMediator.responseFromChannel( self, request, response ) else: self._ok( int(self._rightHandSide(response[0])) ) #=========================================================================# class SimSendStoredMessage( SimMediator ): #=========================================================================# def trigger( self ): self._commchannel.enqueue( "+CMSS=%d" % self.index, self.responseFromChannel, self.errorFromChannel ) def responseFromChannel( self, request, response ): if response[-1] != "OK": SimMediator.responseFromChannel( self, request, response ) else: timestamp = "" result = safesplit( self._rightHandSide(response[0]), ',' ) mr = result[0] if len(result) == 2: ackpdu = ogsmd.gsm.sms.SMS.decode( result[1].strip('"'), "sms-submit-report" ) timestamp = ackpdu.properties["timestamp"] self._ok( int(mr), timestamp ) #=========================================================================# class SimDeleteMessage( SimMediator ): #=========================================================================# def trigger( self ): self._commchannel.enqueue( "+CMGD=%d" % self.index, self.responseFromChannel, self.errorFromChannel ) # # SMS Mediators # #=========================================================================# class SmsSendMessage( SmsMediator ): #=========================================================================# def trigger( self ): sms = ogsmd.gsm.sms.SMSSubmit() # Use PDUAddress sms.addr = ogsmd.gsm.sms.PDUAddress.guess( self.number ) sms.ud = self.contents sms.properties = self.properties pdu = sms.pdu() self._commchannel.enqueue( '+CMGS=%i\r%s' % ( len(pdu)/2-1, pdu), self.responseFromChannel, self.errorFromChannel ) def responseFromChannel( self, request, response ): if response[-1] != "OK": SmsMediator.responseFromChannel( self, request, response ) else: timestamp = "" result = safesplit( self._rightHandSide(response[0]), ',' ) mr = result[0] if len(result) == 2: ackpdu = ogsmd.gsm.sms.SMS.decode( result[1].strip('"'), "sms-submit-report" ) timestamp = ackpdu.properties["timestamp"] self._ok( int(mr), timestamp ) #=========================================================================# class SmsAckMessage( SmsMediator ): #=========================================================================# def trigger( self ): sms = ogsmd.gsm.sms.SMSDeliverReport(True) sms.ud = self.contents sms.properties = self.properties pdu = sms.pdu() commchannel = self._object.modem.communicationChannel( "UnsolicitedMediator" ) commchannel.enqueue( '+CNMA=1,%i\r%s' % ( len(pdu)/2, pdu), self.responseFromChannel, self.errorFromChannel ) #=========================================================================# class SmsNackMessage( SmsMediator ): #=========================================================================# def trigger( self ): sms = ogsmd.gsm.sms.SMSDeliverReport(False) sms.ud = self.contents sms.properties = self.properties pdu = sms.pdu() commchannel = self._object.modem.communicationChannel( "UnsolicitedMediator" ) commchannel.enqueue( '+CNMA=2,%i\r%s' % ( len(pdu)/2, pdu), self.responseFromChannel, self.errorFromChannel ) # # Network Mediators # #=========================================================================# class NetworkRegister( NetworkMediator ): #=========================================================================# def trigger( self ): self._commchannel.enqueue( "+COPS=0,0", self.responseFromChannel, self.errorFromChannel ) #=========================================================================# class NetworkUnregister( NetworkMediator ): #=========================================================================# def trigger( self ): self._commchannel.enqueue( "+COPS=2,0", self.responseFromChannel, self.errorFromChannel ) #=========================================================================# class NetworkGetStatus( NetworkMediator ): #=========================================================================# def trigger( self ): charset = currentModem()._charsets["DEFAULT"] # query strength request, response, error = yield( "+CSQ" ) result = {} if error is not None: self.errorFromChannel( request, error ) else: if response[-1] != "OK" or len( response ) == 1: pass else: result["strength"] = const.signalQualityToPercentage( int(safesplit( self._rightHandSide( response[0] ), ',' )[0]) ) # +CSQ: 22,99 # query registration status and lac/cid request, response, error = yield( "+CREG?" ) if error is not None: self.errorFromChannel( request, error ) elif response[-1] != "OK" or len( response ) == 1: pass else: oldreg = safesplit( self._rightHandSide( response[-2] ), ',' )[0] request, response, error = yield( "+CREG=2;+CREG?;+CREG=%s" % oldreg ) if error is not None: self.errorFromChannel( request, error ) elif response[-1] != "OK" or len( response ) == 1: pass else: result[ "registration"] = const.REGISTER_STATUS[int(safesplit( self._rightHandSide( response[-2] ), ',' )[1])] values = safesplit( self._rightHandSide( response[-2] ), ',' ) if len( values ) == 4: # have lac and cid now result["lac"] = values[2].strip( '"' ).decode(charset) result["cid"] = values[3].strip( '"' ).decode(charset) # query operator name and numerical code request, response, error = yield( "+COPS=3,0;+COPS?;+COPS=3,2;+COPS?" ) if error is not None: self.errorFromChannel( request, error ) else: if response[-1] != "OK" or len( response ) != 3: pass else: # first parse the alphanumerical response set values = safesplit( self._rightHandSide( response[-3] ), ',' ) result["mode"] = const.REGISTER_MODE[int(values[0])] if len( values ) > 2: result["provider"] = values[2].strip( '" ' ).decode(charset) # remove empty provider if not result["provider"]: del result["provider"] if len( values ) == 4: result["act"] = const.REGISTER_ACT[int( values[3] )] else: # AcT defaults to GSM result["act"] = const.REGISTER_ACT[ 0 ] # then parse the numerical response set values = safesplit( self._rightHandSide( response[-2] ), ',' ) if len( values ) > 2: mccmnc = values[2].strip( '"' ).decode(charset) result["code"] = mccmnc # Some providers' name may be unknown to the modem hence not show up in +COPS=3,0;+COPS? # In this case try to gather the name from our network database if not "provider" in result: network = const.NETWORKS.get( ( int( mccmnc[:3]), int( mccmnc[3:] ) ), {} ) if "brand" in network: result["provider"] = network["brand"] elif "operator" in network: result["provider"] = network["operator"] else: result["provider"] = "Unknown" # UGLY special check for some modems, which return a strength of 0, if you # call +CSQ too early after a (re)registration. In that case, we just # leave the strength out of the result try: if result["registration"] in "home roaming denied".split() and result["strength"] == 0: del result["strength"] except KeyError: pass self._ok( result ) #=========================================================================# class NetworkGetSignalStrength( NetworkMediator ): # i #=========================================================================# def trigger( self ): self._commchannel.enqueue( '+CSQ', self.responseFromChannel, self.errorFromChannel ) @logged def responseFromChannel( self, request, response ): if response[-1] != "OK": NetworkMediator.responseFromChannel( self, request, response ) result = const.signalQualityToPercentage( int(safesplit( self._rightHandSide( response[0] ), ',' )[0]) ) # +CSQ: 22,99 self._ok( result ) #=========================================================================# class NetworkListProviders( NetworkMediator ): # a{sv} #=========================================================================# def trigger( self ): self._commchannel.enqueue( "+COPS=?", self.responseFromChannel, self.errorFromChannel ) @logged def responseFromChannel( self, request, response ): charset = currentModem()._charsets["DEFAULT"] if response[0] == "OK": self._ok( [] ) if response[-1] == "OK": result = [] for operator in const.PAT_OPERATOR_LIST.finditer( response[0] ): index = operator.groupdict()["code"].decode(charset) status = const.PROVIDER_STATUS[int(operator.groupdict()["status"])] name = operator.groupdict()["name"].decode(charset) shortname = operator.groupdict()["shortname"].decode(charset) act = operator.groupdict()["act"] if act is None or act == "": act = "0" # AcT defaults to GSM act = const.REGISTER_ACT[int(act)] if not name.strip(): name = const.NETWORKS.get( ( int( index[:3]), int( index[3:] ) ), {} ) if "brand" in name: name = name["brand"] elif "operator" in name: name = name["operator"] else: name = "Unknown" result.append( ( index, status, name, shortname, act ) ) self._ok( result ) else: NetworkMediator.responseFromChannel( self, request, response ) # XXX: Where is this used? def _providerTuple( self, provider ): provider.replace( '"', "" ) values = safesplit( provider[1:-1], ',' ) return int( values[3] ), const.PROVIDER_STATUS[int(values[0])], values[1], values[2] #=========================================================================# class NetworkRegisterWithProvider( NetworkMediator ): #=========================================================================# def trigger( self ): charset = currentModem()._charsets["DEFAULT"] opcode = self.operator_code.encode(charset) self._commchannel.enqueue( '+COPS=1,2,"%s"' % opcode, self.responseFromChannel, self.errorFromChannel ) #=========================================================================# class NetworkGetCountryCode( NetworkMediator ): #=========================================================================# def trigger( self ): self._commchannel.enqueue( "+COPS=3,2;+COPS?;+COPS=3,0", self.responseFromChannel, self.errorFromChannel ) def responseFromChannel( self, request, response ): if response[-1] == "OK" and len( response ) > 1: values = self._rightHandSide( response[0] ).split( ',' ) if len( values ) != 3: self._error( DBusError.NetworkNotFound( "Not registered to any provider" ) ) else: mcc = int( values[2].strip( '"' )[:3] ) code, name = const.mccToCountryCode( mcc ) self._ok( code, name ) #=========================================================================# class NetworkGetCallForwarding( NetworkMediator ): # a{sv} #=========================================================================# def trigger( self ): try: reason = const.CALL_FORWARDING_REASON[self.reason] except KeyError: self._error( DBusError.InvalidParameter( "valid reasons are %s" % const.CALL_FORWARDING_REASON.keys() ) ) else: self._commchannel.enqueue( "+CCFC=%d,2" % reason, self.responseFromChannel, self.errorFromChannel ) @logged def responseFromChannel( self, request, response ): if response[-1] == "OK": result = {} for line in response[:-1]: match = const.PAT_CCFC.match( self._rightHandSide( line ) ) print match.groupdict enabled = bool( int( match.groupdict()["enabled"] ) ) class_ = int( match.groupdict()["class"] ) number = match.groupdict()["number"] ntype = int( match.groupdict()["ntype"] or 129 ) seconds = int( match.groupdict()["seconds"] or 0 ) if not enabled: result = {} break else: result[ const.CALL_FORWARDING_CLASS.revlookup(class_) ] = ( enabled, const.phonebookTupleToNumber( number, ntype ), seconds ) self._ok( result ) else: NetworkMediator.responseFromChannel( self, request, response ) #=========================================================================# class NetworkEnableCallForwarding( NetworkMediator ): #=========================================================================# def trigger( self ): try: reason = const.CALL_FORWARDING_REASON[self.reason] except KeyError: self._error( DBusError.InvalidParameter( "valid reasons are %s" % const.CALL_FORWARDING_REASON.keys() ) ) try: class_ = const.CALL_FORWARDING_CLASS[self.class_] except KeyError: self._error( DBusError.InvalidParameter( "valid classes are %s" % const.CALL_FORWARDING_CLASS.keys() ) ) number, ntype = currentModem().numberToPhonebookTuple( self.number ) if self.reason == "no reply" and self.timeout > 0: self._commchannel.enqueue( """+CCFC=%d,3,"%s",%d,%d,,,%d""" % ( reason, number, ntype, class_, self.timeout ), self.responseFromChannel, self.errorFromChannel ) else: self._commchannel.enqueue( """+CCFC=%d,3,"%s",%d,%d""" % ( reason, number, ntype, class_ ), self.responseFromChannel, self.errorFromChannel ) #=========================================================================# class NetworkDisableCallForwarding( NetworkMediator ): #=========================================================================# def trigger( self ): try: reason = const.CALL_FORWARDING_REASON[self.reason] except KeyError: self._error( DBusError.InvalidParameter( "valid reasons are %s" % const.CALL_FORWARDING_REASON.keys() ) ) try: class_ = const.CALL_FORWARDING_CLASS[self.class_] except KeyError: self._error( DBusError.InvalidParameter( "valid classes are %s" % const.CALL_FORWARDING_CLASS.keys() ) ) self._commchannel.enqueue( "+CCFC=%d,4,,,%d" % ( reason, class_ ), self.responseFromChannel, self.errorFromChannel ) #=========================================================================# class NetworkGetCallingIdentification( NetworkMediator ): # s #=========================================================================# def trigger( self ): self._commchannel = self._object.modem.communicationChannel( "CallMediator" ) # exceptional, since this is a call-specific command self._commchannel.enqueue( "+CLIR?", self.responseFromChannel, self.errorFromChannel ) @logged def responseFromChannel( self, request, response ): if response[-1] == "OK" and len( response ) > 1: status, adjustment = safesplit( self._rightHandSide( response[0] ), ',' ) self._ok( const.CALL_IDENTIFICATION_RESTRICTION.revlookup( int(status) ) ) # We can't rely on NetworkMediator.responseFromChannel since dbus_ok # needs to be called with arguments elif response[-1] == "OK": self._ok( "unknown" ) else: NetworkMediator.responseFromChannel( self, request, response ) #=========================================================================# class NetworkSetCallingIdentification( NetworkMediator ): # s #=========================================================================# def trigger( self ): try: restriction = const.CALL_IDENTIFICATION_RESTRICTION[self.status] except KeyError: self._error( DBusError.InvalidParameter( "valid restrictions are %s" % const.CALL_IDENTIFICATION_RESTRICTION.keys() ) ) self._commchannel = self._object.modem.communicationChannel( "CallMediator" ) # exceptional, since this is a call-specific command self._commchannel.enqueue( "+CLIR=%d" % restriction, self.responseFromChannel, self.errorFromChannel ) #=========================================================================# class NetworkSendUssdRequest( NetworkMediator ): # s #=========================================================================# def trigger( self ): charset = currentModem()._charsets["USSD"] # FIXME request code validation # when using UCS2 we need to encode the request, although it is just a number :/ request = self.request.encode(charset) commchannel = self._object.modem.communicationChannel( "UnsolicitedMediator" ) # exceptional, since CUSD is semi-unsolicited commchannel.enqueue( '+CUSD=1,"%s",15' % request, self.responseFromChannel, self.errorFromChannel, prefixes=["NONE"] ) # # Call Mediators # #=========================================================================# class CallEmergency( CallMediator ): #=========================================================================# def trigger( self ): if self.number in const.EMERGENCY_NUMBERS: # FIXME once we have a priority queue, insert these with maximum priority self._commchannel.enqueue( 'H' ) # hang up (just in case) self._commchannel.enqueue( '+CFUN=1;+COPS=0,0' ) self._commchannel.enqueue( 'D%s;' % self.number ) # dial emergency number else: self._error( DBusError.CallNotAnEmergencyNumber( "valid emergency numbers are %s" % const.EMERGENCY_NUMBERS ) ) #=========================================================================# class CallTransfer( CallMediator ): #=========================================================================# def trigger( self ): number, ntype = currentModem().numberToPhonebookTuple( self.number ) self._commchannel.enqueue( '+CTFR="%s",%d' % ( number, ntype ), self.responseFromChannel, self.errorFromChannel ) #=========================================================================# class CallListCalls( NetworkMediator ): # a(isa{sv}) #=========================================================================# """ CallListCalls is a NetworkMediator since its commands should not be issued on the call channel. """ def trigger( self ): self._commchannel.enqueue( "+CLCC", self.responseFromChannel, self.errorFromChannel ) def responseFromChannel( self, request, response ): if response[-1] == "OK": result = [] for line in response[:-1]: if not line: # some modems might include empty lines here, one for every (not present) call... continue gd = const.groupDictIfMatch( const.PAT_CLCC, line ) if gd is None: logger.warning( "+CLCC parsing error for line '%s'" % line ) continue index = int( gd["id"] ) stat = int( gd["stat"] ) direction = const.CALL_DIRECTION[ int( gd["dir"] ) ] mode = const.CALL_MODE.revlookup( int( gd["mode"] ) ) number, ntype = gd["number"], gd["ntype"] properties = { "direction": direction, "type": mode } if number is not None: properties["peer"] = const.phonebookTupleToNumber( number, int(ntype) ) c = ( index, const.CALL_STATUS[ stat ], properties ) result.append( c ) self._ok( result ) else: NetworkMediator.responseFromChannel( self, request, response ) #=========================================================================# class CallSendDtmf( CallMediator ): #=========================================================================# def trigger( self ): self.tonelist = [ tone.upper() for tone in self.tones if tone.upper() in const.CALL_VALID_DTMF ] self.tonelist.reverse() if not self.tonelist: self._error( DBusError.InvalidParameter( "not enough valid tones" ) ) else: self._commchannel.enqueue( "+VTS=%s" % self.tonelist.pop(), self.responseFromChannel, self.errorFromChannel ) def responseFromChannel( self, request, response ): if response[-1] == "OK": if self.tonelist: self._commchannel.enqueue( "+VTS=%s" % self.tonelist.pop(), self.responseFromChannel, self.errorFromChannel ) else: self._ok() else: CallMediator.responseFromChannel( self, request, response ) #=========================================================================# class CallInitiate( CallMediator ): #=========================================================================# def trigger( self ): # check parameters if self.calltype not in const.PHONE_CALL_TYPES: self._error( DBusError.InvalidParameter( "invalid call type. Valid call types are: %s" % const.PHONE_CALL_TYPES ) ) return for digit in self.number: if digit not in const.PHONE_NUMBER_DIGITS: self._error( DBusError.InvalidParameter( "invalid number digit. Valid number digits are: %s" % const.PHONE_NUMBER_DIGITS ) ) return # do the work if self.calltype == "voice": dialstring = "%s;" % self.number else: dialstring = self.number line = CallHandler.getInstance().initiate( dialstring, self._commchannel ) if line is None: self._error( DBusError.CallNoCarrier( "unable to dial" ) ) else: self._ok( line ) #=========================================================================# class CallRelease( CallMediator ): #=========================================================================# def trigger( self ): if CallHandler.getInstance().release( self.index, self._commchannel ) is not None: self._ok() else: self._error( DBusError.CallNotFound( "no such call to release" ) ) #=========================================================================# class CallReleaseAll( CallMediator ): #=========================================================================# def trigger( self ): # need to use misc channel here, so that it can also work during outgoing call # FIXME might rather want to consider using the state machine after all (see below) CallHandler.getInstance().releaseAll( self._object.modem.channel( "MiscMediator" ) ) self._ok() #=========================================================================# class CallActivate( CallMediator ): #=========================================================================# def trigger( self ): if CallHandler.getInstance().activate( self.index, self._commchannel ) is not None: self._ok() else: self._error( DBusError.CallNotFound( "no such call to activate" ) ) #=========================================================================# class CallActivateConference( CallMediator ): #=========================================================================# def trigger( self ): if CallHandler.getInstance().activateConference( self.index, self._commchannel ) is not None: self._ok() else: self._error( DBusError.CallNotFound( "no such calls to put into conference" ) ) #=========================================================================# class CallHoldActive( CallMediator ): #=========================================================================# def trigger( self ): if CallHandler.getInstance().hold( self._commchannel ) is not None: self._ok() else: self._error( DBusError.CallNotFound( "no such call to hold" ) ) # # PDP Mediators # #=========================================================================# class PdpListAvailableGprsClasses( PdpMediator ): # as #=========================================================================# def trigger( self ): self._commchannel.enqueue( "+CGCLASS=?", self.responseFromChannel, self.errorFromChannel ) @logged def responseFromChannel( self, request, response ): if response[-1] == "OK" and len( response ) > 1: self._ok( re.findall( const.PAT_STRING, response[0] ) ) else: PdpMediator.responseFromChannel( self, request, response ) #=========================================================================# class PdpGetCurrentGprsClass( PdpMediator ): # s #=========================================================================# def trigger( self ): self._commchannel.enqueue( "+CGCLASS?", self.responseFromChannel, self.errorFromChannel ) @logged def responseFromChannel( self, request, response ): if response[-1] == "OK" and len( response ) > 1: self._ok( re.findall( const.PAT_STRING, response[0] )[0] ) else: PdpMediator.responseFromChannel( self, request, response ) #=========================================================================# class PdpSetCurrentGprsClass( PdpMediator ): #=========================================================================# def trigger( self ): self._commchannel.enqueue( '+CGCLASS="%s"' % self.class_, self.responseFromChannel, self.errorFromChannel ) #=========================================================================# class PdpGetNetworkStatus( PdpMediator ): #=========================================================================# def trigger( self ): result = {} # query registration status and lac/cid request, response, error = yield( "+CGREG?" ) if error is not None: self.errorFromChannel( request, error ) elif response[-1] != "OK" or len( response ) == 1: pass else: oldreg = safesplit( self._rightHandSide( response[-2] ), ',' )[0] request, response, error = yield( "+CGREG=2;+CGREG?;+CGREG=%s" % oldreg ) if error is not None: self.errorFromChannel( request, error ) elif response[-1] != "OK" or len( response ) == 1: pass else: charset = currentModem()._charsets["DEFAULT"] result[ "registration"] = const.REGISTER_STATUS[int(safesplit( self._rightHandSide( response[-2] ), ',' )[1])] values = safesplit( self._rightHandSide( response[-2] ), ',' ) if len( values ) >= 4: # have lac and cid now result["lac"] = values[2].strip( '"' ).decode(charset) result["cid"] = values[3].strip( '"' ).decode(charset) if len( values ) == 5: result["act"] = const.REGISTER_ACT[ int(values[4]) ] else: # AcT defaults to GSM result["act"] = const.REGISTER_ACT[ 0 ] self._ok( result ) #=========================================================================# class PdpActivateContext( PdpMediator ): #=========================================================================# def trigger( self ): pdpConnection = Pdp.getInstance( self._object ) if pdpConnection.isActive(): self._ok() else: pdpConnection.setParameters( self.apn, self.user, self.password ) pdpConnection.activate() self._ok() #=========================================================================# class PdpDeactivateContext( PdpMediator ): #=========================================================================# def trigger( self ): # the right way... leading to a hanging pppd :( #self._commchannel.enqueue( '+CGACT=0', self.responseFromChannel, self.errorFromChannel ) # the workaround pdpConnection = Pdp.getInstance( self._object ) if pdpConnection.isActive(): pdpConnection.deactivate() self._ok() #=========================================================================# class PdpGetContextStatus( PdpMediator ): #=========================================================================# def trigger( self ): self._ok( Pdp.getInstance( self._object ).status() ) # # CB Mediators # #=========================================================================# class CbGetCellBroadcastSubscriptions( CbMediator ): # s #=========================================================================# def trigger( self ): request, response, err = yield( "+CSCB?" ) if err is not None: self.errorFromChannel( request, err ) else: if response[-1] != "OK": self.responseFromChannel( request, response ) else: # +CSCB: 0,"0-999","0-3,5" gd = const.groupDictIfMatch( const.PAT_CSCB, response[0] ) assert gd is not None, "parsing error" drop = gd["drop"] == '1' channels = gd["channels"] encodings = gd["encodings"] if not drop: if channels == "": self._ok( "none" ) elif channels == "0-999": self._ok( "all" ) else: self._ok( channels ) else: if channels == "": # drop nothing = accept 0-999 self._ok( "all" ) self._error( DBusError.InternalException( "+CSCB: 1 not yet handled" ) ) #=========================================================================# class CbSetCellBroadcastSubscriptions( CbMediator ): #=========================================================================# def trigger( self ): if self.channels == "all": message = '1,"",""' elif self.channels == "none": message = '0,"",""' else: message = '0,"%s","0-3,5"' % self.channels self._commchannel.enqueue( "+CSCB=%s" % message, self.responseFromChannel, self.errorFromChannel ) # # Monitor Mediators # #=========================================================================# class MonitorGetServingCellInformation( MonitorMediator ): #=========================================================================# def trigger( self ): self._error( DBusError.UnsupportedCommand( "org.freesmartphone.GSM.Monitor.GetServingCellInformation" ) ) #=========================================================================# class MonitorGetNeighbourCellInformation( MonitorMediator ): #=========================================================================# def trigger( self ): self._error( DBusError.UnsupportedCommand( "org.freesmartphone.GSM.Monitor.GetNeighbourCellInformation" ) ) # # Debug Mediators # #=========================================================================# class DebugCommand( DebugMediator ): #=========================================================================# def trigger( self ): self._commchannel.enqueueRaw( "%s" % self.command, self.responseFromChannel, self.errorFromChannel, prefixes = [""] ) def responseFromChannel( self, request, response ): self._ok( response ) #=========================================================================# if __name__ == "__main__": #=========================================================================# pass fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/abstract/modem.py000066400000000000000000000263331174525413000266210ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd.modems.abstract Module: modem """ # FIXME: The modem should really be a sigleton __version__ = "0.9.9.5" MODULE_NAME = "ogsmd.modem.abstract" from framework.config import config import gobject import sys, types import logging logger = logging.getLogger( MODULE_NAME ) FALLBACK_TIMEOUT = 30 #=========================================================================# class AbstractModem( object ): #=========================================================================# """This class abstracts a GSM Modem.""" instance = None def __init__( self, dbus_object, bus, *args, **kwargs ): """ Initialize """ assert self.__class__.__name__ != "AbstractModem", "can't instanciate pure virtual class" # FIXME make sure only one modem exists AbstractModem.instance = self self._channels = {} # container for channel instances self._object = dbus_object # dbus object self._bus = bus # dbus bus self._simPinState = "unknown" # SIM PIN state self._simReady = "unknown" # SIM data access state self._data = {} # misc modem-wide data, set/get from channels self._phonebookIndices = {} # min. index, max. index self._phonebookSizes = {} # number length, name length self._data["sim-buffers-sms"] = True self._data["sms-buffered-cb"] = "2,1,2,1,1" self._data["sms-buffered-nocb"] = "2,1,0,0,0" # FIXME: Might be bad as default, since not all modems necessarily support that self._data["sms-direct-cb"] = "2,2,2,1,1" # what about a,3,c,d,e? self._data["sms-direct-nocb"] = "2,2,0,0,0" # dito self._data["pppd-configuration"] = [ \ '115200', 'nodetach', 'crtscts', 'defaultroute', 'debug', 'hide-password', 'holdoff', '3', 'ipcp-accept-local', 'ktune', #'lcp-echo-failure', '10', #'lcp-echo-interval', '20', 'ipcp-max-configure', '4', 'lock', 'noauth', #'demand', 'noipdefault', 'novj', 'novjccomp', #'persist', 'proxyarp', 'replacedefaultroute', 'usepeerdns', ] self._data["pppd-does-setup-and-teardown"] = True # default is using connect and disconnect scripts self._charsets = { \ "DEFAULT": "gsm_default", "PHONEBOOK": "gsm_ucs2", "USSD": "gsm_ucs2", } self._data["cancel-outgoing-call"] = "H" # default will kill all connections self._data["data-call-handler"] = config.getValue( "ogsmd", "data_call_handler", None ) def open( self, on_ok, on_error ): """ Triggers opening the channels on this modem. The actual opening will happen from inside mainloop. """ self._counter = len( self._channels ) if ( self._counter ): gobject.idle_add( self._initChannels, on_ok, on_error ) def close( self ): # SYNC """ Closes the communication channels. """ # FIXME: A really good way would be to stop accepting new commands, # giving it time to drain the queues, and then closing all channels. for channel in self._channels.values(): # FIXME: We're throwing away the result here :/ channel.close() def reinit( self ): """ Closes and reopens the communication channels, also triggering resending the initialization commands on every channel. """ self.close() for channel in self._channels.values(): channel._sendCommands( "init" ) # just enqueues them for later # FIXME no error handling yet self.open( lambda: None, lambda foo: None ) def data( self, key, defaultValue=None ): return self._data.get( key, defaultValue ) def setData( self, key, value ): self._data[key] = value def numberToPhonebookTuple( self, nstring ): """ Returns a phonebook tuple. """ if type( nstring ) != types.StringType(): # even though we set +CSCS="UCS2" (modem charset), the name is always encoded in text format, not PDU. nstring = nstring.encode( "iso-8859-1" ) if nstring[0] == '+': return nstring[1:], 145 else: return nstring, 129 def channel( self, category ): """ Returns the communication channel for certain command category. """ sys.exit( -1 ) # pure virtual method called def channels( self ): """ Returns the names of the communication channels. """ return self._channels.keys() def inject( self, channel, string ): """ Injects a string to a channel. """ self._channels[channel].readyToRead( string ) def simPinState( self ): """ Returns the SIM PIN state """ return self._simPinState def simReady( self ): """ Returns the SIM availability state. """ return self._simReady def stateAntennaOn( self ): """ Notify channels that the antenna is now powered on. """ for channel in self._channels.itervalues(): channel.modemStateAntennaOn() def setSimPinState( self, state ): """ Set and notify channels about a new SIM PIN state. """ self._simPinState = state if state == "READY": for channel in self._channels.itervalues(): channel.modemStateSimUnlocked() def setSimReady( self, ready ): """ Set and notify channels about a SIM data accessibility. """ self._simReady = ready if ready == True: for channel in self._channels.itervalues(): channel.modemStateSimReady() def setPhonebookIndices( self, category, first, last ): """ Set phonebook valid indices interval for a given phonebook """ self._phonebookIndices[category] = first, last def phonebookIndices( self, category ): try: first, last = self._phonebookIndices[category] except KeyError: return None, None else: return first, last def setPhonebookSizes( self, category, numlength, textlength ): """ Set phonebook names and number sizes for a given phonebook """ self._phonebookSizes[category] = numlength, textlength def phonebookSizes( self, category ): try: numlength, textlength = self._phonebookSizes[category] except KeyError: return None, None else: return numlength, textlength def prepareForSuspend( self, ok_callback, error_callback ): """ Prepares the modem for suspend. """ # FIXME can we refactor this into a generic useful callback/object adapter class? class MyOk(object): def __init__( self, *args, **kwargs ): assert args == (), "only keyword arguments allowed" for key, value in kwargs.iteritems(): if key.startswith( "CL_" ): setattr( self.__class__, key[3:], value ) else: setattr( self, key, value ) def __call__( self, channel ): logger.debug( "prepareForSuspend ACK from channel %s received" % channel ) self.__class__.counter -= 1 if self.__class__.counter == 0: self.ok() class MyError(MyOk): def __call__( self, channel ): logger.debug( "prepareForSuspend NACK from channel %s received" % channel ) self.__class__.counter -= 1 if self.__class__.counter == 0: self.error() ok = MyOk( CL_counter = len( self._channels ), ok = ok_callback ) error = MyError( error = error_callback ) for channel in self._channels.values(): channel.suspend( ok, error ) def recoverFromSuspend( self, ok_callback, error_callback ): """ Recovers the modem from suspend. """ # FIXME can we refactor this into a generic useful callback/object adapter class? class MyOk(object): def __init__( self, *args, **kwargs ): assert args == (), "only keyword arguments allowed" for key, value in kwargs.iteritems(): if key.startswith( "CL_" ): setattr( self.__class__, key[3:], value ) else: setattr( self, key, value ) def __call__( self, channel ): logger.debug( "recoverFromSuspend ACK from channel %s received" % channel ) self.__class__.counter -= 1 if self.__class__.counter == 0: self.ok() class MyError(MyOk): def __call__( self, channel ): logger.debug( "recoverFromSuspend NACK from channel %s received" % channel ) self.__class__.counter -= 1 if self.__class__.counter == 0: self.error() ok = MyOk( CL_counter = len( self._channels ), ok = ok_callback ) error = MyError( error = error_callback ) for channel in self._channels.values(): channel.resume( ok, error ) # # internal API # def _initChannels( self, on_ok, on_error, iteration=1 ): if iteration == 7: # we did try to open the modem 5 times -- giving up now on_error(None) # FIXME no error handling yet # try to open all channels for channel in self._channels: if not self._channels[channel].isOpen(): logger.debug( "trying to open channel %s" % channel ) if not self._channels[channel].open(): logger.error( "could not open channel %s, retrying in 2 seconds" % channel ) gobject.timeout_add_seconds( 2, self._initChannels, on_ok, on_error, iteration+1 ) else: self._counter -= 1 if not self._counter: on_ok() return False # don't call me again @classmethod def communicationChannel( cls, category ): return cls.instance.channel( category ) #=========================================================================# fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/abstract/overlay.py000066400000000000000000000032731174525413000271770ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later """ import os import stat import shutil #=========================================================================# class OverlayFile( object ): #=========================================================================# backupPath = "/var/tmp/ogsmd/" classInitDone = False def __init__( self, name, overlay ): self._classInit() self.name = os.path.abspath( name ) if os.path.exists( self.name ): self.backupname = "%s/%s" % ( self.__class__.backupPath, self.name.replace( '/', ',' ) ) else: self.backupname = None self.overlay = overlay def store( self ): """Store the overlay""" if self.backupname is not None: shutil.copy( self.name, self.backupname ) f = open( self.name, "w" ) f.write( self.overlay ) del f # FIXME store chmod os.chmod( self.name, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO ) def restore( self ): """Restore original content""" if self.backupname is not None: shutil.copy( self.backupname, self.name ) # FIXME restore chmod def __del__( self ): pass def _classInit( self ): if not OverlayFile.classInitDone: if os.path.exists( OverlayFile.backupPath ) and os.path.isdir( OverlayFile.backupPath ): pass else: # FIXME lets do that without shell os.system( "mkdir -p %s" % OverlayFile.backupPath ) OverlayFile.classInitDone = True fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/abstract/pdp.py000066400000000000000000000167731174525413000263120ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later This module is based on pyneod/pypppd.py (C) 2008 M. Dietrich. Package: ogsmd.modems.abstract Module: pdp """ __version__ = "0.3.0" MODULE_NAME = "ogsmd.modems.abstract.pdp" from .mediator import AbstractMediator from .overlay import OverlayFile from framework.patterns.kobject import KObjectDispatcher from framework.patterns.processguard import ProcessGuard import gobject import os, subprocess, signal, copy import logging logger = logging.getLogger( MODULE_NAME ) #=========================================================================# class Pdp( AbstractMediator ): #=========================================================================# """ Encapsulates the state of (all) PDP connections on a modem """ _instance = None @classmethod def getInstance( klass, dbus_object=None ): if klass._instance is None and dbus_object is not None: klass._instance = Pdp( dbus_object ) return klass._instance def __init__( self, dbus_object, **kwargs ): AbstractMediator.__init__( self, dbus_object, None, None, **kwargs ) self._callchannel = self._object.modem.communicationChannel( "PdpMediator" ) self._netchannel = self._object.modem.communicationChannel( "NetworkMediator" ) self.state = "release" # initial state self.ppp = None self.overlays = [] # FIXME: add match only while running pppd KObjectDispatcher.addMatch( "addlink", "", self._onAddLinkEvent ) def _onInterfaceChange( action, path, **kwargs ): logger.debug( "detected interface change", action, path ) # # public # def setParameters( self, apn, user, password ): self.pds = copy.copy( self.__class__.PPP_DAEMON_SETUP ) self.pds[self.__class__.PPP_CONNECT_CHAT_FILENAME] = self.__class__.PPP_DAEMON_SETUP[self.__class__.PPP_CONNECT_CHAT_FILENAME] % apn apn, user, password = str(apn), str(user), str(password) # check whether pppd needs to handle setup and teardown if self._object.modem.data( "pppd-does-setup-and-teardown" ): # merge with modem specific options self.ppp_options = self.__class__.PPP_OPTIONS_GENERAL + self._object.modem.data( "pppd-configuration" ) else: self.ppp_options = self._object.modem.data( "pppd-configuration" ) # merge with user and password settings if user: logger.info( "configuring ppp for user '%s' w/ password '%s'" % ( user, password ) ) self.ppp_options += [ "user", user ] self.pds[self.__class__.PPP_PAP_SECRETS_FILENAME] = '%s * "%s" *\n' % ( user or '*', password ) self.pds[self.__class__.PPP_CHAP_SECRETS_FILENAME] = '%s * "%s" *\n'% ( user or '*', password ) self.childwatch_source = None def isActive( self ): return self.state == "active" def activate( self ): self._activate() def deactivate( self ): self._deactivate() def status( self ): return self.state # # private # def _prepareFiles( self ): for filename, overlay in self.pds.iteritems(): logger.debug( "preparing file %s" % filename ) f = OverlayFile( filename, overlay=overlay ) f.store() self.overlays.append( f ) def _recoverFiles( self ): for f in self.overlays: logger.debug( "recovering file %s" % f.name ) f.restore() self.overlays = [] def _activate( self ): if self.ppp is not None and self.ppp.isRunning(): raise Exception( "already active" ) self.port = str( self._object.modem.dataPort() ) if not self.port: raise Exception( "no device" ) logger.debug( "activate got port %s" % self.port ) ppp_commandline = [ self.__class__.PPP_BINARY, self.port ] + self.ppp_options logger.info( "launching ppp as commandline %s" % ppp_commandline ) self._prepareFiles() self.ppp = ProcessGuard( ppp_commandline ) self.ppp.execute( onExit=self._spawnedProcessDone, onError=self._onPppError, onOutput=self._onPppOutput ) logger.info( "pppd launched. See syslog (e.g. logread -f) for output." ) # FIXME that's somewhat premature. we might adopt the following states: # "setup", "active", "shutdown", "release" self._updateState( "outgoing" ) def _updateState( self, newstate ): if newstate != self.state: self.state = newstate self._object.ContextStatus( 1, newstate, {} ) def _deactivate( self ): logger.info( "shutting down pppd" ) self.ppp.shutdown() def _spawnedProcessDone( self, pid, exitcode, exitsignal ): logger.info( "pppd exited with code %d, signal %d" % ( exitcode, exitsignal ) ) # FIXME check whether this was a planned exit or not, if not, try to recover self._updateState( "release" ) self._recoverFiles() # FIXME at this point, the default route might be wrong, if someone killed pppd # force releasing context and attachment to make sure that # the next ppp setup will find the data port in command mode self._netchannel.enqueue( "+CGACT=0;+CGATT=0", lambda a,b:None, lambda a,b:None ) def _onAddLinkEvent( self, action, path, **properties ): """ Called by KObjectDispatcher """ try: device = properties["dev"] flags = properties["flags"] except KeyError: pass # not enough information else: if device == "ppp0" and "UP" in flags: self._updateState( "active" ) def _onPppError( self, text ): print "ppp error:", repr(text) def _onPppOutput( self, text ): print "ppp output:", repr(text) # class wide constants constants PPP_CONNECT_CHAT_FILENAME = "/var/tmp/ogsmd/gprs-connect-chat" PPP_DISCONNECT_CHAT_FILENAME = "/var/tmp/ogsmd/gprs-disconnect-chat" PPP_PAP_SECRETS_FILENAME = "/etc/ppp/pap-secrets" PPP_CHAP_SECRETS_FILENAME = "/etc/ppp/chap-secrets" PPP_OPTIONS_GENERAL = [ "connect", PPP_CONNECT_CHAT_FILENAME, "disconnect", PPP_DISCONNECT_CHAT_FILENAME ] PPP_BINARY = "/usr/sbin/pppd" PPP_DAEMON_SETUP = {} PPP_DAEMON_SETUP[ PPP_CONNECT_CHAT_FILENAME ] = r"""#!/bin/sh -e exec /usr/sbin/chat -v\ 'ABORT' 'BUSY'\ 'ABORT' 'DELAYED'\ 'ABORT' 'ERROR'\ 'ABORT' 'NO ANSWER'\ 'ABORT' 'NO CARRIER'\ 'ABORT' 'NO DIALTONE'\ 'ABORT' 'RINGING'\ 'ABORT' 'VOICE'\ 'TIMEOUT' '5'\ '' '+++AT'\ 'OK-\k\k\k\d+++ATH-OK' 'ATE0Q0V1'\ 'OK' 'AT+CMEE=2'\ 'OK' 'AT+CGDCONT=1,"IP","%s"'\ 'TIMEOUT' '180'\ 'OK' 'ATD*99#'\ 'CONNECT' '\d\c' """ PPP_DAEMON_SETUP[ PPP_DISCONNECT_CHAT_FILENAME ] = r"""#!/bin/sh -e echo disconnect script running... """ PPP_DAEMON_SETUP["/etc/ppp/ip-up.d/08setupdns"] = """#!/bin/sh -e cp /var/run/ppp/resolv.conf /etc/resolv.conf """ PPP_DAEMON_SETUP["/etc/ppp/ip-down.d/92removedns"] = """#!/bin/sh -e echo nameserver 127.0.0.1 > /etc/resolv.conf """ PPP_DAEMON_SETUP[PPP_PAP_SECRETS_FILENAME] = '* * "%s" *\n' % '' PPP_DAEMON_SETUP[PPP_CHAP_SECRETS_FILENAME] = '* * "%s" *\n'% '' #=========================================================================# if __name__ == "__main__": #=========================================================================# pass fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/abstract/unsolicited.py000066400000000000000000000245311174525413000300400ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008 Daniel Willmann (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd.modems.abstract Module: unsolicited """ __version__ = "0.9.9.5" MODULE_NAME = "ogsmd.modems.abstract.unsolicited" import calling, pdp from ogsmd.gsm.decor import logged from ogsmd.gsm import const, convert from ogsmd.helpers import safesplit from ogsmd.modems import currentModem import ogsmd.gsm.sms import logging logger = logging.getLogger( MODULE_NAME ) import gobject KEYCODES = {} #=========================================================================# class AbstractUnsolicitedResponseDelegate( object ): #=========================================================================# def __init__( self, dbus_object, mediator ): self._object = dbus_object self._mediator = mediator self._callHandler = calling.CallHandler.getInstance( dbus_object ) self._callHandler.setHook( self._cbCallHandlerAction ) self.lac = None self.cid = None self._syncTimeout = None def _sendStatus( self ): self._object.Status( self.operator, self.register, self.strength ) # # unsolicited callbacks (alphabetically sorted, please keep it that way) # # PDU mode: +CBM: 88\r\n001000DD001133DAED46ABD56AB5186CD668341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D100 # or in text mode: # +CBM: 16,221,0,1,1\r\n347747555093\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\n" def plusCBM( self, righthandside, pdu ): """ Cell Broadcast Message """ values = safesplit( righthandside, ',' ) if len( values ) == 1: # PDU MODE cb = ogsmd.gsm.sms.CellBroadcast.decode(pdu) sn = cb.sn channel = cb.mid dcs = cb.dcs page = cb.page data = cb.ud elif len( values ) == 5: # TEXT MODE sn, mid, dcs, page, pages = values channel = int(mid) data = pdu else: logger.warning( "unrecognized +CBM cell broadcast notification, please fix me..." ) return self._object.IncomingCellBroadcast( channel, data ) # +CGEV: ME DEACT "IP","010.161.025.237",1 # +CGEV: ME DETACH def plusCGEV( self, righthandside ): """ Gprs Context Event """ values = safesplit( righthandside, ',' ) if len( values ) == 1: # detach, but we're not having an IP context online if values[0] == "ME DETACH": # FIXME this will probably lead to a zombie instance = pdp.Pdp.getInstance() if instance is not None: instance.deactivate() # FIXME force dropping the dataport, this will probably kill the zombie elif len( values ) >= 3: # detach while we were attached pass # +CGREG: 2 # +CGREG: 1,"000F","5B4F def plusCGREG( self, righthandside ): """ Gprs Registration Status Update """ charset = currentModem()._charsets["DEFAULT"] values = safesplit( righthandside, ',' ) status = {} status["registration"] = const.REGISTER_STATUS[int(values[0])] if len( values ) >= 3: status["lac"] = values[1].strip( '"' ).decode(charset) status["cid"] = values[2].strip( '"' ).decode(charset) self._object.NetworkStatus( status ) # +CREG: 1,"000F","032F" # +CREG: 1,"000F","032F",2 def plusCREG( self, righthandside ): """ Network Registration Status Update """ charset = currentModem()._charsets["DEFAULT"] values = safesplit( righthandside, ',' ) self.register = const.REGISTER_STATUS[int(values[0])] if len( values ) >= 3: self.lac = values[1].strip( '"' ).decode(charset) self.cid = values[2].strip( '"' ).decode(charset) self._mediator.NetworkGetStatus( self._object, self.statusOK, self.statusERR ) # +CLIP: "+496912345678",145,,,,0 def plusCLIP( self, righthandside ): """ Connecting Line Identification Presence """ number, ntype, rest = safesplit( righthandside, ',', 2 ) number = number.replace( '"', '' ) logger.warning( "plusCLIP not handled -- please fix me" ) #self._mediator.Call.clip( self._object, const.phonebookTupleToNumber( number, int(ntype ) ) ) # +CMT: "004D00690063006B006500790020007000720069",22 # 0791947107160000000C9194712716464600008001301131648003D9B70B def plusCMT( self, righthandside, pdu ): """ Message Transfer Indication """ header = safesplit( righthandside, ',' ) length = int(header[1]) # Now we decode the actual PDU sms = ogsmd.gsm.sms.SMS.decode( pdu, "sms-deliver" ) self._object.IncomingMessage( str(sms.addr), sms.ud, sms.properties ) # +CDS: \r\n def plusCDS( self, righthandside, pdu ): """ Incoming delivery report """ sms = ogsmd.gsm.sms.SMS.decode( pdu, "sms-status-report" ) self._object.IncomingMessageReceipt( str(sms.addr), sms.ud, sms.properties ) # Always acknoledge a status report right away sms = ogsmd.gsm.sms.SMSDeliverReport(True) pdu = sms.pdu() commchannel = self._object.modem.communicationChannel( "UnsolicitedMediator" ) # XXX: Replace the lambda with an actual funtion in error case? commchannel.enqueue( '+CNMA=1,%i\r%s' % ( len(pdu)/2, pdu), lambda x,y: None, lambda x,y: None ) # +CKEV: 19,1 # +CKEV: 19,0 def plusCKEV( self, righthandside ): values = safesplit( righthandside, ',' ) keyname = KEYCODES.get( int( values[0] ), "unknown" ) pressed = bool( int( values[1] ) ) self._object.KeypadEvent( keyname, pressed ) # +CMTI: "SM",7 def plusCMTI( self, righthandside ): """ Message Transfer Indication """ storage, index = safesplit( righthandside, ',' ) if storage != '"SM"': logger.warning( "unhandled +CMTI message notification" ) else: self._object.IncomingStoredMessage( int(index) ) # +CRING: VOICE # +CRING: REL ASYNC def plusCRING( self, calltype ): """ Incoming call """ self._syncCallStatus( "RING" ) self._startTimeoutIfNeeded() # +CMS ERROR: 322 def plusCMS_ERROR( self, righthandside ): """ Incoming unsolicited error Seen, when we are using SMS in SIM-buffered mode. """ errornumber = int( righthandside ) if errornumber == 322: self._object.MemoryFull() # +CTZV: 35 (observed in Taipei, UTC+7) # +CTZV: 105 (observed in UTC-4) def plusCTZV( self, righthandside ): """ Incoming Timezone Report """ self._object.TimeZoneReport( const.ctzvToTimeZone( righthandside ) ) # +CUSD: 0," Aktuelles Guthaben: 10.00 EUR.",0' def plusCUSD( self, righthandside ): """ Incoming USSD result """ charset = currentModem()._charsets["USSD"] values = safesplit( righthandside, ',' ) if len( values ) == 1: mode = const.NETWORK_USSD_MODE[int(values[0])] self._object.IncomingUssd( mode, "" ) elif len( values ) == 3: mode = const.NETWORK_USSD_MODE[int(values[0])] message = values[1].strip( '" ' ).decode(charset) self._object.IncomingUssd( mode, message ) else: logger.warning( "Ignoring unknown format: '%s'" % righthandside ) # # helpers # def statusOK( self, status ): if self.lac is not None: status["lac"] = self.lac if self.cid is not None: status["cid"] = self.cid self._object.Status( status ) # send dbus signal def statusERR( self, values ): logger.warning( "statusERR... ignoring" ) def _startTimeoutIfNeeded( self ): if self._syncTimeout is None: self._syncTimeout = gobject.timeout_add_seconds( 1, self._cbSyncTimeout ) def _syncCallStatus( self, initiator ): self._mediator.CallListCalls( self._object, self._syncCallStatus_ok, self._syncCallStatus_err ) def _syncCallStatus_ok( self, calls ): seen = [] for callid, status, properties in calls: seen.append( callid ) self._callHandler.statusChangeFromNetwork( callid, {"status": status} ) # synthesize remaining calls if not 1 in seen: self._callHandler.statusChangeFromNetwork( 1, {"status": "release"} ) if not 2 in seen: self._callHandler.statusChangeFromNetwork( 2, {"status": "release"} ) def _syncCallStatus_err( self, request, error ): logger.warning( "+CLCC didn't succeed -- ignoring" ) def _cbSyncTimeout( self, *args, **kwargs ): """ Called by the glib mainloop while anything call-related happens. """ logger.debug( "sync timeout while GSM is not idle" ) self._syncCallStatus( "SYNC TIMEOUT" ) if self._callHandler.isBusy(): logger.debug( "call handler is busy" ) return True # glib mainloop: please call me again else: logger.debug( "call handler is not busy" ) self._syncTimeout = None return False # glib mainloop: don't call me again def _cbCallHandlerAction( self, action, result ): """ Called by the call handler once a user-initiated action has been performed. """ self._syncCallStatus( "MEDIATOR ACTION" ) logger.debug( "call handler action %s w/ result %s" % ( action, result ) ) if result is not False: if action == "initiate": first, second = self._callHandler.status() if first == "release": self._callHandler.statusChangeFromNetwork( 1, {"status": "outgoing"} ) else: self._callHandler.statusChangeFromNetwork( 2, {"status": "outgoing"} ) self._startTimeoutIfNeeded() fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/cinterion_mc75/000077500000000000000000000000001174525413000261615ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/cinterion_mc75/__init__.py000066400000000000000000000000001174525413000302600ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/cinterion_mc75/channel.py000066400000000000000000000047641174525413000301560ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon -- Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd.modems.cinterion_mc75 Module: channel """ from ogsmd.modems.abstract.channel import AbstractModemChannel from ogsmd.gsm.callback import SimpleCallback import itertools, select import logging logger = logging.getLogger( "ogsmd.modems.cinterion_mc75.channel" ) #=========================================================================# class CinterionModemChannel( AbstractModemChannel ): #=========================================================================# pass #=========================================================================# class MiscChannel( CinterionModemChannel ): #=========================================================================# def __init__( self, *args, **kwargs ): if not "timeout" in kwargs: kwargs["timeout"] = 60*60 CinterionModemChannel.__init__( self, *args, **kwargs ) self.callback = None def setIntermediateResponseCallback( self, callback ): assert self.callback is None, "callback already set" self.callback = callback def handleUnsolicitedResponse( self, response ): if self.callback is not None: self.callback( response ) else: print "CALLCHANNEL: UNHANDLED INTERMEDIATE: ", response #=========================================================================# class UnsolicitedResponseChannel( CinterionModemChannel ): #=========================================================================# def __init__( self, *args, **kwargs ): CinterionModemChannel.__init__( self, *args, **kwargs ) def _populateCommands( self ): CinterionModemChannel._populateCommands( self ) c = self._commands["init"] # enable unsolicited codes c.append( "+CLIP=1" ) # calling line identification presentation enable c.append( "+COLP=1" ) # connected line identification presentation enable c.append( "+CCWA=1" ) # call waiting: send unsol. code c.append( "+CSSN=1,1") # supplementary service notifications: send unsol. code c.append( "+CRC=1" ) # cellular result codes: extended c.append( "+CSNS=0" ) # single numbering scheme: voice c.append( "+CTZU=1" ) # timezone update c.append( "+CTZR=1" ) # timezone reporting c.append( "+CREG=2" ) # registration information (TODO not all modems support that) fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/cinterion_mc75/mediator.py000066400000000000000000000004511174525413000303370ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon -- Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd.modems.cinterion_mc75 Module: mediator """ from ogsmd.modems.abstract.mediator import * # add overrides here fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/cinterion_mc75/modem.py000066400000000000000000000065521174525413000276440ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon -- Python Implementation (C) 2008-2009 Michael 'Mickey' Lauer (C) 2008-2009 Openmoko, Inc. GPLv2 or later Package: ogsmd.modems.cinterion_mc75 Module: modem """ __version__ = "0.1.0" MODULE_NAME = "ogsmd.modems.cinterion_mc75" MODEM_DEVICE_PATH = "/dev/ttySAC1" MODEM_SYSFS_POWER_PATH = "/sys/bus/platform/devices/om-3d7k.0/gsm_power" import mediator from ogsmd.modems.abstract.modem import AbstractModem from ogsmd.helpers import writeToFile from .channel import UnsolicitedResponseChannel, MiscChannel from .unsolicited import UnsolicitedResponseDelegate from dbus import Interface from time import sleep import logging logger = logging.getLogger( MODULE_NAME ) #=========================================================================# class CinterionMc75( AbstractModem ): #=========================================================================# def __init__( self, *args, **kwargs ): AbstractModem.__init__( self, *args, **kwargs ) self._channelmap = { "ogsmd.misc":1, "ogsmd.unsolicited":2 } # VC 1 self._channels["MISC"] = MiscChannel( self.pathfactory, "ogsmd.misc", modem=self ) # VC 2 self._channels["UNSOL"] = UnsolicitedResponseChannel( self.pathfactory, "ogsmd.unsolicited", modem=self ) # VC 3 # GPRS # configure channels self._channels["UNSOL"].setDelegate( UnsolicitedResponseDelegate( self._object, mediator ) ) # muxer object self._muxeriface = None def _modemOn( self ): """ Lowlevel initialize this modem. """ logger.debug( "reset-cycling modem" ) writeToFile( MODEM_SYSFS_POWER_PATH, "0\n" ) sleep( 1 ) writeToFile( MODEM_SYSFS_POWER_PATH, "1\n" ) sleep( 1 ) logger.debug( "reset cycle complete" ) sleep( 2 ) # FIXME open device node and listen for \r\n^SYSSTART\r\n return True def _modemOff( self ): """ Lowlevel deinitialize this modem. """ writeToFile( MODEM_SYSFS_POWER_PATH, "0\n" ) def channel( self, category ): """ Return proper outgoing channel for command category. """ if category in ( "UnsolicitedMediator", "NetworkMediator" ): return self._channels["UNSOL"] else: return self._channels["MISC"] def pathfactory( self, name ): """ Allocate a new channel from the MUXer. Overridden for internal purposes. """ logger.info( "Requesting new channel from multiplexer" ) if self._muxeriface is None: muxer = self._bus.get_object( "org.freesmartphone.omuxerd", "/org/freesmartphone/GSM/Muxer" ) self._muxeriface = Interface( muxer, "org.freesmartphone.GSM.MUX" ) # power on modem if not self._modemOn(): self._muxeriface = None return "" # FIXME: emit error? if not self._muxeriface.HasAutoSession(): # abyss needs an open session before we can allocate channels self._muxeriface.OpenSession( True, 98, MODEM_DEVICE_PATH, 115200 ) pts, vc = self._muxeriface.AllocChannel( name, self._channelmap[name] ) return str(pts) def dataPort( self ): return self.pathfactory( "ogsmd.gprs" ) fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/cinterion_mc75/unsolicited.py000066400000000000000000000012031174525413000310510ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon -- Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd.modems.cinterion_mc75 Module: unsolicited """ from ogsmd.modems.abstract.unsolicited import AbstractUnsolicitedResponseDelegate class UnsolicitedResponseDelegate( AbstractUnsolicitedResponseDelegate ): def __init__( self, *args, **kwargs ): AbstractUnsolicitedResponseDelegate.__init__( self, *args, **kwargs ) #self._callHandler.unsetHook() # we have special call handling that doesn't need stock hooks # No we don't (yet) :) fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/ericsson_F3507g/000077500000000000000000000000001174525413000261145ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/ericsson_F3507g/__init__.py000066400000000000000000000000001174525413000302130ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/ericsson_F3507g/channel.py000066400000000000000000000043211174525413000300760ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd.modems.ericsson_F3507g Module: channel """ from ogsmd.gsm.decor import logged from ogsmd.modems.abstract.channel import AbstractModemChannel #=========================================================================# class EricssonChannel( AbstractModemChannel ): #=========================================================================# def _populateCommands( self ): """ Populate the command queues to be sent on modem state changes. """ c = [] # reset c.append( 'Z' ) # soft reset c.append( 'E0V1' ) # echo off, verbose result on # error and result reporting reporting c.append( '+CMEE=1' ) # report mobile equipment errors: in numerical format c.append( '+CRC=1' ) # cellular result codes: enable extended format c.append( '+CSCS="UCS2"' ) # character set conversion: use 8859-1 (latin 1) c.append( '+CSDH=1' ) # show text mode parameters: show values c.append( '+CSNS=0' ) # single numbering scheme: voice # sms c.append( '+CMGF=0' ) # message format: enable pdu mode, disable text mode c.append( '+CSMS=1' ) # GSM Phase 2+ commands: enable # unsolicited c.append( '+CLIP=1' ) # calling line identification presentation: disable c.append( '+COLP=1' ) # connected line identification presentation: disable c.append( '+CCWA=1' ) # call waiting: disable c.append( '+CTZR=1' ) # timezone reporting: enable c.append( '+CREG=2' ) # registration information: enable c.append( '+CGREG=2' ) # GPRS registration information: enable c.append( '+CGEREP=2') # Packet domain event reporting: enable self._commands["init"] = c c = [] c.append( "+CNMI=2,1,2,1,1" ) # buffer sms on SIM, report CB directly self._commands["sim"] = c c = [] self._commands["antenna"] = c fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/ericsson_F3507g/mediator.py000066400000000000000000000004441174525413000302740ustar00rootroot00000000000000#!/usr/bin/env python """ The GSM Daemon - Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd.modems.ericsson_F3507g Module: mediator """ from ogsmd.modems.abstract.mediator import * # add overrides here fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/ericsson_F3507g/modem.py000066400000000000000000000052261174525413000275740ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008 Michael 'Mickey' Lauer GPLv2 or later Package: ogsmd.modems.ericsson_F3507g Module: modem """ __version__ = "0.1.0" MODULE_NAME = "ogsmd.modems.ericsson_F3507g" THINKPAD_POWER_PATH="/sys/bus/platform/devices/thinkpad_acpi/wwan_enable" import mediator from ..abstract.modem import AbstractModem from .channel import EricssonChannel from .unsolicited import UnsolicitedResponseDelegate from ogsmd.gsm.decor import logged from ogsmd.gsm.channel import AtCommandChannel from ogsmd.helpers import writeToFile #=========================================================================# class EricssonF3507g( AbstractModem ): #=========================================================================# @logged def __init__( self, *args, **kwargs ): AbstractModem.__init__( self, *args, **kwargs ) self._charsets = { \ "DEFAULT": "gsm_ucs2", "PHONEBOOK": "gsm_ucs2", "USSD": "gsm_ucs2", } # One line for unsolicited messages and modem communication # ttyACM1 will be used for data and ttyACM2 will be used for GPS data self._channels["SINGLE"] = EricssonChannel( self.pathfactory, "/dev/ttyACM0", modem=self ) # configure channel self._channels["SINGLE"].setDelegate( UnsolicitedResponseDelegate( self._object, mediator ) ) self._data["pppd-configuration"] = [ \ "115200", "nodetach", "crtscts", "noipdefault", ":10.0.0.1", "local", 'defaultroute', 'debug', 'hide-password', 'ipcp-accept-local', #"lcp-echo-failure", "10", #"lcp-echo-interval", "3", "noauth", #"demand", "noipdefault", "novj", "novjccomp", "persist", ] def open( self, on_ok, on_error ): """ Power on modem """ writeToFile( THINKPAD_POWER_PATH, "1" ) # call default implementation (open all channels) AbstractModem.open( self, on_ok, on_error ) def close( self ): # SYNC """ Power down modem """ # call default implementation (closing all channels) AbstractModem.close( self ) writeToFile( THINKPAD_POWER_PATH, "0" ) def channel( self, category ): return self._channels["SINGLE"] def pathfactory( self, name ): return name def dataPort( self ): # FIXME remove duplication and just use pathfactory return "/dev/ttyACM1" fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/ericsson_F3507g/unsolicited.py000066400000000000000000000006111174525413000310060ustar00rootroot00000000000000#!/usr/bin/env python """ The GSM Daemon - Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd.modems.ericsson_F3507g Module: unsolicited """ from ogsmd.modems.abstract.unsolicited import AbstractUnsolicitedResponseDelegate class UnsolicitedResponseDelegate( AbstractUnsolicitedResponseDelegate ): pass fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/freescale_neptune/000077500000000000000000000000001174525413000270235ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/freescale_neptune/__init__.py000066400000000000000000000000001174525413000311220ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/freescale_neptune/channel.py000066400000000000000000000101711174525413000310050ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2007-2008 M. Dietrich (C) 2008 Michael 'Mickey' Lauer GPLv2 or later Package: ogsmd.modems.freescale_neptune Module: channel Freescale Neptune specific modem channels """ __version__ = "0.8.0" MODULE_NAME = "ogsmd.neptune_freescale" from ogsmd.modems.abstract.channel import AbstractModemChannel import gobject #=========================================================================# class EzxMuxChannel( AbstractModemChannel ): #=========================================================================# def __init__( self, *args, **kwargs ): AbstractModemChannel.__init__( self, *args, **kwargs ) #=========================================================================# class CallAndNetworkChannel( EzxMuxChannel ): #=========================================================================# def __init__( self, *args, **kwargs ): EzxMuxChannel.__init__( self, *args, **kwargs ) # FIXME we can't do this, since it is modem-wide (not VC-wide) #self.enqueue( "+CMER=0,0,0,0,0" ) # unsolicited event reporting: none def _populateCommands( self ): AbstractModemChannel._populateCommands( self ) # default command init c = self._commands["init"] = [] c.append( '+EPOM=1,0' ) # Ezx Power On Modem c.append( '+EAPF=12,1,0' ) # ?? # GSM unsolicited c.append( '+CRC=1' ) # Extended ring report c.append( '+CLIP=1' ) # calling line identification presentation enable c.append( '+COLP=1' ) # connected line identification presentation enable c.append( '+CCWA=1' ) # call waiting c.append( "+CSSN=1,1" ) # supplementary service notifications: send unsol. code c.append( '+CTZU=1' ) # timezone update c.append( '+CTZR=1' ) # timezone reporting c.append( '+CREG=2' ) # registration information (NOTE not all modems support =2) c.append( "+CAOC=2" ) # advice of charge: send unsol. code # GPRS unsolicited c.append( "+CGEREP=2,1" ) c.append( "+CGREG=2" ) self._commands["sim"] = [] #=========================================================================# class SmsChannel( EzxMuxChannel ): #=========================================================================# def __init__( self, *args, **kwargs ): EzxMuxChannel.__init__( self, *args, **kwargs ) def _populateCommands( self ): AbstractModemChannel._populateCommands( self ) # default command init self._commands["init"] = [] # This modem needs a special SIM init sequence otherwise GSM 07.07 SMS commands won't succeed c = self._commands["sim"] = [] c.append( "+CRRM" ) # FIXME if this returns an error, we might have no SIM inserted c.append( "+EPMS?" ) c.append( "+EMGL=4" ) #=========================================================================# class SimChannel( EzxMuxChannel ): #=========================================================================# def __init__( self, *args, **kwargs ): EzxMuxChannel.__init__( self, *args, **kwargs ) def _populateCommands( self ): AbstractModemChannel._populateCommands( self ) # default command init self._commands["init"] = [] #=========================================================================# class MiscChannel( EzxMuxChannel ): #=========================================================================# def __init__( self, *args, **kwargs ): EzxMuxChannel.__init__( self, *args, **kwargs ) # FIXME we can't do this, since it is modem-wide (not VC-wide) #self.enqueue( "+CMER=0,0,0,0,0" ) # unsolicited event reporting: none def modemStateSimUnlocked( self, *args, **kwargs ): # we _should_ be ready now, alas we can't check for sure :( self._modem._object.ReadyStatus( True ) def _populateCommands( self ): AbstractModemChannel._populateCommands( self ) # default command init c = self._commands["init"] = [] c.append( '+USBSTAT=255,1' ) # charge self._commands["sim"] = [] fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/freescale_neptune/mediator.py000066400000000000000000000233251174525413000312060ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008-2009 Michael 'Mickey' Lauer GPLv2 or later Package: ogsmd.modems.freescale_neptune Module: mediator """ __version__ = "0.5.1.2" MODULE_NAME = "ogsmd.modems.freescale_neptune.mediator" from ogsmd.modems.abstract.mediator import * import ogsmd.modems.abstract.mediator as AbstractMediator from ogsmd.gsm.decor import logged from ogsmd.gsm import const from ogsmd.helpers import safesplit import logging logger = logging.getLogger( MODULE_NAME ) # add overrides here # FIXME probably not the right place and way to do that import re # modem violating 05.05 here # the ',' before the name was not supposed to be optional # +CMGL: 6,1,125 PAT_SMS_PDU_HEADER = re.compile( '(?P\d+),(?P\d+)(?:,"(?P[^"]*)")?,(?P\d+)' ) # modem violating 05.05 here: # the ',' before the name was not supposed to be optional # +CMGR: 1,155 PAT_SMS_PDU_HEADER_SINGLE = re.compile( '(?P\d+)(?:,"(?P[^"]*)")?,(?P\d+)' ) const.PAT_SMS_PDU_HEADER = PAT_SMS_PDU_HEADER const.PAT_SMS_PDU_HEADER_SINGLE = PAT_SMS_PDU_HEADER_SINGLE import time #=========================================================================# class DeviceGetInfo( DeviceMediator ): #=========================================================================# """ Modem not implementing any of +CGMR;+CGMM;+CGMI -- only +CGSN is supported """ def trigger( self ): self._commchannel.enqueue( "+CGSN", self.responseFromChannel, self.errorFromChannel ) @logged def responseFromChannel( self, request, response ): if response[-1] != "OK": DeviceMediator.responseFromChannel( self, request, response ) else: result = { "manufacturer": "Motorola", "model": "Neptune Freescale Modem", "imei": self._rightHandSide( response[0] ).strip( '"' ) } self._ok( result ) #=========================================================================# class SimGetAuthStatus( SimMediator ): #=========================================================================# # FIXME: Add SIM PIN known/unknown logic here in order to prepare for changing SetAntennaPower() semantics def trigger( self ): self._commchannel.enqueue( "+CPIN?", self.responseFromChannel, self.errorFromChannel ) @logged def responseFromChannel( self, request, response ): if response[-1] == "OK": pin_state = self._rightHandSide( response[0] ).strip( '"' ) # some modems include " self._ok( pin_state ) else: """ Modem violating GSM 07.07 here. +CPIN? does not work """ #SimMediator.responseFromChannel( self, request, response ) pin_state = self._object.modem._simPinState self._ok( pin_state ) #=========================================================================# class DeviceSetAntennaPower( AbstractMediator.DeviceSetAntennaPower ): #=========================================================================# """ In contrast to AbstractMediator.DeviceSetAntennaPower, PIN handling has been removed here, since after powercycling we will get an URC indicating the PIN status. """ def trigger( self ): if not self.power: time.sleep(2.0) cmd = "+CFUN=%d" % ( 1 if self.power else 0 ) self._commchannel.enqueue( cmd, self.responseFromChannel, self.errorFromChannel ) def responseFromChannel( self, request, response ): if response[-1] == "OK": if self.power: time.sleep( 2.0 ) self._ok() else: DeviceMediator.responseFromChannel( self, request, response ) #=========================================================================# class SimSendAuthCode( SimMediator ): #=========================================================================# """ Modem violating GSM 07.07 here. Format seems to be +CPIN=,"", where 1 is PIN1, 2 may be PIN2 or PUK1 """ def trigger( self ): self._commchannel.enqueue( '+CPIN=1,"%s"' % self.code, self.responseFromChannel, self.errorFromChannel ) @logged def responseFromChannel( self, request, response ): if response[-1] == "OK": self._ok() # send auth status signal self._object.AuthStatus( "READY" ) else: SimMediator.responseFromChannel( self, request, response ) #=========================================================================# class SimListPhonebooks( SimMediator ): #=========================================================================# """ Modem not supporting +CPBS=? here, but supporting the phonebooks "SM" and "ON". Workaround until the abstract mediator implements traversing through the list of known phonebooks trying to select them. """ def trigger( self ): self._ok( "contacts own fixed".split() ) #=========================================================================# class NetworkRegister( NetworkMediator ): #=========================================================================# def trigger( self ): time.sleep( 4.0 ) self._commchannel.enqueue( "+COPS=0,0", self.responseFromChannel, self.errorFromChannel ) #=========================================================================# class NetworkUnregister( NetworkMediator ): #=========================================================================# def trigger( self ): self._commchannel.enqueue( "+COPS=2,0", self.responseFromChannel, self.errorFromChannel ) @logged def responseFromChannel( self, request, response ): if response[-1] == "OK": time.sleep( 4.0 ) self._ok() else: NetworkMediator.responseFromChannel( self, request, response ) #=========================================================================# class NetworkGetStatus( NetworkMediator ): #=========================================================================# """ Modem violating GSM 07.07 here. No matter which answering format you specify with +COPS=..., +COPS? will always return the numerical ID of the provider as a string. We might have +ESPN? to the rescue, but that always returns an empty string for me. So until this is cleared, we have to use code matching with our database (mobile_network_code). Oh, by the way, did I mention that +CREG? is not implemented either? *sigh* """ def trigger( self ): request, response, error = yield( "+CSQ" ) result = {} if error is not None: self.errorFromChannel( request, error ) else: if response[-1] != "OK" or len( response ) == 1: pass else: result["strength"] = const.signalQualityToPercentage( int(safesplit( self._rightHandSide( response[0] ), ',' )[0]) ) # +CSQ: 22,99 request, response, error = yield( "+COPS?" ) if error is not None: self.errorFromChannel( request, error ) else: if response[-1] != "OK" or len( response ) == 1: pass else: values = safesplit( self._rightHandSide( response[0] ), ',' ) if len( values ) < 3: result["mode"] = const.REGISTER_MODE[int(values[0])] result["registration"] = "unregistered" else: result["mode"] = const.REGISTER_MODE[int(values[0])] roaming = self._object.modem.data( "network:roaming", False ) result["registration"] = "roaming" if roaming else "home" mccmnc = values[2].strip( '"' ).replace( '-', '' ) if mccmnc == "": result["registration"] = "busy" else: result["code"] = mccmnc network = const.NETWORKS.get( ( mccmnc[:3], mccmnc[3:] ), {} ) if "brand" in network: result["provider"] = network["brand"] elif "operator" in network: result["provider"] = network["operator"] else: result["provider"] = "Unknown" self._ok( result ) # # CB Mediators # #=========================================================================# class CbGetCellBroadcastSubscriptions( CbMediator ): # s #=========================================================================# """ Modem violating 05.05 here, with +CSCB we can only specify whether CB messages are accepted or not at all. """ def trigger( self ): request, response, error = yield( "+CSCB?" ) if error is not None: self.errorFromChannel( request, error ) else: if response[-1] != "OK": self.responseFromChannel( request, response ) else: rhs = self._rightHandSide( response[0] ) if rhs == "1": self._ok( "none" ) else: self._ok( "all" ) #=========================================================================# class CbSetCellBroadcastSubscriptions( CbMediator ): #=========================================================================# """ Modem violating 05.05 here, with +CSCB we can only specify whether CB messages are accepted or not all. """ def trigger( self ): if self.channels != "none": message = '0' else: message = '1' self._commchannel.enqueue( "+CSCB=%s" % message, self.responseFromChannel, self.errorFromChannel ) fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/freescale_neptune/modem.py000066400000000000000000000144021174525413000304770ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008-2009 Michael 'Mickey' Lauer GPLv2 or later Package: ogsmd.modems.freescale_neptune Module: modem Freescale Neptune modem class """ __version__ = "0.3.1" MODULE_NAME = "ogsmd.modems.freescale_neptune" EZXD_PROCESS_NAME = "ezxd" import mediator from framework.patterns.utilities import killall from ogsmd.modems.abstract.modem import AbstractModem from .channel import CallAndNetworkChannel, MiscChannel, SmsChannel, SimChannel from .unsolicited import UnsolicitedResponseDelegate from ogsmd.gsm.decor import logged from ogsmd.gsm.channel import AtCommandChannel import logging logger = logging.getLogger( MODULE_NAME ) import types import os import sys import errno import fcntl import termios import array import time import subprocess muxfds = [] initDone = False #=========================================================================# class FreescaleNeptune( AbstractModem ): #=========================================================================# """ Support for the Freescale Neptune embedded modem as found in the Motorola EZX Linux Smartphones E680, A780, A910, A1200, A1600, ROKR E2, ROKR E6, and more. We have a hardwired multiplexing mode configuration as follows: ---------------------------------------------------------------- DLC Description Cmd Device Mode ---------------------------------------------------------------- 0 Control Channel - - 1 Voice Call & Network MM /dev/mux0 Modem 2 SMS MO /dev/mux1 Phonebook 3 SMS MT /dev/mux2 Phonebook 4 Phonebook SIM /dev/mux3 Phonebook 5 Misc /dev/mux4 Phonebook 6 CSD / Fax /dev/mux5 /dev/mux8 Modem 7 GPRS 1 /dev/mux6 /dev/mux9 Modem 8 GPRS 2 /dev/mux7 /dev/mux10 Modem 9 Logger CMD /dev/mux11 10 Logger Data /dev/mux12 11 Test CMD /dev/mux13 12 AGPS /dev/mux14 13 NetMonitor /dev/mux15 ---------------------------------------------------------------- ... """ @logged def __new__( cls, *args, **kwargs ): global initDone if not initDone: ret = cls._freescale_neptune_modemOn() if ret == False: return None initDone = True return AbstractModem.__new__( cls, *args, **kwargs ) @logged def __init__( self, *args, **kwargs ): AbstractModem.__init__( self, *args, **kwargs ) # /dev/mux0 self._channels[ "CallAndNetwork" ] = CallAndNetworkChannel( self.pathfactory, "/dev/mux1", modem=self ) # /dev/mux2 self._channels[ "Sms" ] = SmsChannel( self.pathfactory, "/dev/mux3", modem=self ) # /dev/mux4 self._channels[ "Sim" ] = SimChannel( self.pathfactory, "/dev/mux4", modem=self ) # /dev/mux6 self._channels[ "Misc" ] = MiscChannel( self.pathfactory, "/dev/mux5", modem=self ) # configure channels self._channels["CallAndNetwork"].setDelegate( UnsolicitedResponseDelegate( self._object, mediator ) ) self._channels["Sms"].setDelegate( UnsolicitedResponseDelegate( self._object, mediator ) ) self._channels["Sim"].setDelegate( UnsolicitedResponseDelegate( self._object, mediator ) ) self._channels["Misc"].setDelegate( UnsolicitedResponseDelegate( self._object, mediator ) ) def numberToPhonebookTuple( self, nstring ): """ Modem violating GSM 07.07 here. It always includes the '+' for international numbers, although this should only be encoded via ntype = '145'. """ if type( nstring ) != types.StringType(): # even though we set +CSCS="UCS2" (modem charset), the name is always encoded in text format, not PDU. nstring = nstring.encode( "iso-8859-1" ) if nstring[0] == '+': return nstring, 145 else: return nstring, 129 def channel( self, category ): if category == "CallMediator" or category == "DeviceMediator" : return self._channels["CallAndNetwork"] elif category == "UnsolicitedMediator": return self._channels["Sms"] elif category == "SimMediator": return self._channels["Sim"] else: return self._channels["Misc"] def pathfactory(self, name): return name @staticmethod def _freescale_neptune_modemOn(): global muxfds logger.debug("********************** Modem init **********************") subprocess.check_call(['modprobe', 'ohci-hcd']) time.sleep(2) subprocess.check_call(['modprobe', 'moto-usb-ipc']) subprocess.check_call(['modprobe', 'ts27010mux']) N_TS2710 = 19 dlci_lines = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] devpath = "/dev/ttyIPC0" counter = 10 # Loop when opening /dev/ttyIPC0 to have some tolerance ipc = None while True: logger.debug("Trying to open %s... (%d)" % (devpath, counter)) counter -= 1 try: ipc = os.open(devpath, os.O_RDWR) except OSError as e: if e.errno == errno.ENODEV and counter > 0: time.sleep(2) continue if ipc or counter == 0: break if not ipc: logger.error("Error opening %s" % devpath) return False logger.debug("Setting ldisc") line = array.array('i', [N_TS2710]) ret = fcntl.ioctl(ipc, termios.TIOCSETD, line, 1) if ret != 0: logger.error("ioctl error %s" % devpath) return False for dlci in dlci_lines: devpath = "/dev/mux%d" % dlci try: fd = os.open(devpath, os.O_RDWR | os.O_NOCTTY) except OSError as e: logger.error("%s: %s" % (devpath, e.strerror)) #return False logger.debug("Opened %s" % devpath) muxfds.append(fd) return True fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/freescale_neptune/unsolicited.py000066400000000000000000000155471174525413000317330ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008-2009 Michael 'Mickey' Lauer GPLv2 or later """ __version__ = "0.8.2.0" MODULE_NAME = "ogsmd.modems.freescale_neptune.unsolicited" from ogsmd.modems.abstract.unsolicited import AbstractUnsolicitedResponseDelegate from ogsmd.gsm.decor import logged from ogsmd.gsm import const from ogsmd.helpers import safesplit import ogsmd.gsm.sms import logging logger = logging.getLogger( MODULE_NAME ) KEYCODES = { 19: "power" } #=========================================================================# class UnsolicitedResponseDelegate( AbstractUnsolicitedResponseDelegate ): #=========================================================================# def __init__( self, *args, **kwargs ): AbstractUnsolicitedResponseDelegate.__init__( self, *args, **kwargs ) self._callHandler.unsetHook() # we have special call handling that doesn't need stock hooks # # GSM standards # # +CCWA: def plusCCWA( self, righthandside ): pass # +CESS: 3, 14 def plusCESS( self, righthandside ): logger.debug("EZX: CESS", righthandside) def plusCIEV( self, righthandside ): """ Indicator Event Reporting. Based on 3GPP TS 07.07, Chapter 8.9, but slightly extended. As +CIND=? gives us a hint (one of the few test commands EZX exposes), we conclude: 0: battery charge level (0-5) 1: signal level (0-5) 2: service availability (0-1) 3: call active? (0-1) 4: voice mail (message) (0-1) 5: transmit activated by voice activity (0-1) 6: call progress (0-3) [0:no more in progress, 1:incoming, 2:outgoing, 3:ringing] 7: roaming (0-2) 8: sms storage full (0-1) 11: ??? 20: ??? (SIM not inserted?) """ indicator, value = ( int(x) for x in safesplit( righthandside, ',' ) ) try: method = getattr( self, "CIEV_%d" % indicator ) except AttributeError: logger.debug("EZX: unhandled CIEV", indicator, value) else: method( value ) def CIEV_0( self, chargelevel ): logger.debug("EZX: CHARGE LEVEL:", chargelevel) def CIEV_1( self, signallevel ): self._object.SignalStrength( 20*signallevel ) logger.debug("EZX: SIGNAL: ", signallevel) def CIEV_2( self, service ): logger.debug("EZX: SERVICE:", bool(service)) def CIEV_3( self, present ): logger.debug("EZX: CALL PRESENT:", bool(present)) self._syncCallStatus( "CIEV" ) def CIEV_4( self, voicemail ): logger.debug("EZX: VOICEMAIL:", bool(voicemail)) def CIEV_5( self, voice_activity ): logger.debug("EZX: VOICE ACTIVITY:", bool(voice_activity)) def CIEV_6( self, call_progress ): logger.debug("EZX: CALL PROGRESS:", call_progress) def CIEV_7( self, roaming ): logger.debug("EZX: ROAMING:", roaming) self._object.modem.setData( "network:roaming", bool(roaming) ) # +CLIP: "+4969123456789",145 def plusCLIP( self, righthandside ): number, ntype = safesplit( righthandside, ',' ) if number and ntype: peer = const.phonebookTupleToNumber( number[1:-1], int(ntype) ) self._mediator.Call.clip( self._object, peer) # +CMT: 139 # 07919471060040340409D041767A5C060000903021417134408A41767A5C0625DDE6B70E247D87DB69F719947683C86539A858D581C2E273195D7693CBA0A05B5E37974130568D062A56A5AF66DAED6285DDEB77BB5D7693CBA0A05B5E3797413096CC062A56A5AF66DAED0235CB683928E936BFE7A0BA9B5E968356B45CEC66C3E170369A8C5673818E757A19242DA7E7E510 def plusCMT( self, righthandside, pdu ): """ Message Transfer Indication. Modem violating 07.07 here, the header was NOT supposed to be optional. """ length = int(righthandside) # Now we decode the actual PDU sms = ogsmd.gsm.sms.SMS.decode( pdu, "sms-deliver" ) self._object.IncomingMessage( str(sms.addr), sms.ud, sms.properties ) # EZX does not honor +CRM, hence +CRING is not being sent def plusCRING( self, calltype ): pass # +CSSU: 2,,"",128 # +CSSU: 10 def plusCSSU( self, righthandside ): values = safesplit( righthandside, ',' ) if len( values ) == 4: code, index, number, type_ = values else: code = values[0] # # ... # # # Freescale Neptune proprietary URCs # # +CCTP: 1, "+491234567" def plusCCTP( self, righthandside ): callnumber, peer = safesplit( righthandside, ',' ) callnumber = int(callnumber) peer = peer.strip( '"' ) # synthesize call status self._callHandler.statusChangeFromNetwork( callnumber, {"status": "outgoing", "peer":peer } ) # +CMSM: 0 # 0 = SIM inserted, locked # 3 = SIM inserted, unlocked def plusCMSM( self, righthandside ): # FIXME: Some firmware versions support +CPIN?, so we better use SimGetAuthStatus here. code = int( righthandside ) if code == 0: self._object.AuthStatus( "SIM PIN" ) else: self._object.AuthStatus( "READY" ) # +EKEV: 19,1 # +EKEV: 19,0 def plusEKEV( self, righthandside ): values = safesplit( righthandside, ',' ) keyname = KEYCODES.get( int( values[0] ), "unknown" ) pressed = bool( int( values[1] ) ) self._object.KeypadEvent( keyname, pressed ) # +EOPER: 5,"262-03" # +EOPER: 7 # 0 = busy # 5 = home # 7 = unregistered def plusEOPER( self, righthandside ): values = safesplit( righthandside, ',' ) status = {} if len( values ) == 1: status["registration"] = "unregistered" else: # FIXME: This is not correct. Need to listen for the roaming status as well roaming = self._object.modem.data( "network:roaming", False ) status["registration"] = "roaming" if roaming else "home" status["provider"] = values[1] self._object.Status( status ) # RING: 1 def RING( self, calltype ): self._syncCallStatus( "RING" ) # # helpers # def _syncCallStatus( self, initiator ): self._mediator.CallListCalls( self._object, self._syncCallStatus_ok, self._syncCallStatus_err ) def _syncCallStatus_ok( self, calls ): seen = [] for callid, status, properties in calls: seen.append( callid ) self._callHandler.statusChangeFromNetwork( callid, {"status": status} ) # synthesize remaning calls if not 1 in seen: self._callHandler.statusChangeFromNetwork( 1, {"status": "release"} ) if not 2 in seen: self._callHandler.statusChangeFromNetwork( 2, {"status": "release"} ) def _syncCallStatus_err( self, request, error ): logger.warning( "AT ERROR from CLCC: %s", error ) fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/muxed4line/000077500000000000000000000000001174525413000254125ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/muxed4line/__init__.py000066400000000000000000000000001174525413000275110ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/muxed4line/channel.py000066400000000000000000000046141174525413000274010ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2007-2008 M. Dietrich (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd.modems.muxed4line Module: channel """ from ogsmd.modems.abstract.channel import AbstractModemChannel from ogsmd.gsm.callback import SimpleCallback #=========================================================================# class CallChannel( AbstractModemChannel ): #=========================================================================# def __init__( self, *args, **kwargs ): if not "timeout" in kwargs: kwargs["timeout"] = 60*60 AbstractModemChannel.__init__( self, *args, **kwargs ) self.callback = None def setIntermediateResponseCallback( self, callback ): assert self.callback is None, "callback already set" self.callback = callback def handleUnsolicitedResponse( self, response ): if self.callback is not None: self.callback( response ) else: print "CALLCHANNEL: UNHANDLED INTERMEDIATE: ", response #=========================================================================# class MiscChannel( AbstractModemChannel ): #=========================================================================# pass #=========================================================================# class UnsolicitedResponseChannel( AbstractModemChannel ): #=========================================================================# def __init__( self, *args, **kwargs ): AbstractModemChannel.__init__( self, *args, **kwargs ) def _populateCommands( self ): AbstractModemChannel._populateCommands( self ) c = self._commands["init"] # enable unsolicited codes c.append( "+CLIP=1" ) # calling line identification presentation enable c.append( "+COLP=1" ) # connected line identification presentation enable c.append( "+CCWA=1" ) # call waiting: send unsol. code c.append( "+CSSN=1,1") # supplementary service notifications: send unsol. code c.append( "+CRC=1" ) # cellular result codes: extended c.append( "+CSNS=0" ) # single numbering scheme: voice c.append( "+CTZU=1" ) # timezone update c.append( "+CTZR=1" ) # timezone reporting c.append( "+CREG=2" ) # registration information (TODO not all modems support that) fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/muxed4line/mediator.py000066400000000000000000000004441174525413000275720ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd.modems.muxed4line Module: mediator """ from ogsmd.modems.abstract.mediator import * # add overrides here fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/muxed4line/modem.py000066400000000000000000000042471174525413000270740ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd.modems.muxed4line Module: modem """ __version__ = "1.0.0" MODULE_NAME = "ogsmd.modems.muxed4line" import mediator from ogsmd.modems.abstract.modem import AbstractModem from .channel import CallChannel, UnsolicitedResponseChannel, MiscChannel from .unsolicited import UnsolicitedResponseDelegate from ogsmd.gsm.decor import logged from ogsmd.gsm.channel import AtCommandChannel #=========================================================================# class Muxed4Line( AbstractModem ): #=========================================================================# @logged def __init__( self, *args, **kwargs ): AbstractModem.__init__( self, *args, **kwargs ) # VC 1 self._channels["CALL"] = CallChannel( self.pathfactory, "ogsmd.call", modem=self ) # VC 2 self._channels["UNSOL"] = UnsolicitedResponseChannel( self.pathfactory, "ogsmd.unsolicited", modem=self ) # VC 3 self._channels["MISC"] = MiscChannel( self.pathfactory, "ogsmd.misc", modem=self ) # VC 4 # GPRS # configure channels self._channels["UNSOL"].setDelegate( UnsolicitedResponseDelegate( self._object, mediator ) ) def channel( self, category ): if category == "CallMediator": return self._channels["CALL"] elif category == "UnsolicitedMediator": return self._channels["UNSOL"] else: return self._channels["MISC"] def pathfactory( self, name ): """ Allocate a new channel from the MUXer. Overridden for internal purposes. """ muxer = self._bus.get_object( "org.pyneo.muxer", "/org/pyneo/Muxer" ) return str( muxer.AllocChannel( name, dbus_interface="org.freesmartphone.GSM.MUX" ) ) def dataPort( self ): # FIXME remove duplication and just use pathfactory muxer = self._bus.get_object( "org.pyneo.muxer", "/org/pyneo/Muxer" ) return muxer.AllocChannel( "ogsmd.gprs", dbus_interface="org.freesmartphone.GSM.MUX" ) fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/muxed4line/unsolicited.py000066400000000000000000000006111174525413000303040ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd.modems.muxed4line Module: unsolicited """ from ogsmd.modems.abstract.unsolicited import AbstractUnsolicitedResponseDelegate class UnsolicitedResponseDelegate( AbstractUnsolicitedResponseDelegate ): pass fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/option/000077500000000000000000000000001174525413000246445ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/option/__init__.py000066400000000000000000000000001174525413000267430ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/option/channel.py000066400000000000000000000044361174525413000266350ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2007-2008 M. Dietrich (C) 2008 Michael 'Mickey' Lauer (C) 2008 Daniel Willmann (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd.modems.option Module: channel """ from ogsmd.gsm.decor import logged from ogsmd.modems.abstract.channel import AbstractModemChannel #=========================================================================# class OptionChannel( AbstractModemChannel ): #=========================================================================# def _populateCommands( self ): """ Populate the command queues to be sent on modem state changes. """ c = [] # reset c.append( 'Z' ) # soft reset c.append( 'E0V1' ) # echo off, verbose result on # error and result reporting reporting c.append( '+CMEE=1' ) # report mobile equipment errors: in numerical format c.append( '+CRC=1' ) # cellular result codes: enable extended format #c.append( '+CSCS="UCS2"' ) # character set conversion: use 8859-1 (latin 1) c.append( '+CSDH=1' ) # show text mode parameters: show values #c.append( '+CSNS=0' ) # single numbering scheme: voice # sms c.append( '+CMGF=0' ) # message format: enable pdu mode, disable text mode c.append( '+CSMS=1' ) # GSM Phase 2+ commands: enable # unsolicited c.append( '+CLIP=1' ) # calling line identification presentation: disable c.append( '+COLP=1' ) # connected line identification presentation: disable c.append( '+CCWA=1' ) # call waiting: disable #c.append( '+CTZR=1' ) # timezone reporting: enable #c.append( '+CREG=2' ) # registration information: enable #c.append( '+CGREG=2' ) # GPRS registration information: enable c.append( '+CGEREP=2') # Packet domain event reporting: enable self._commands["init"] = c c = [] # c.append( "+CNMI=2,1,2,1,1" ) # buffer sms on SIM, report CB directly self._commands["sim"] = c c = [] self._commands["antenna"] = c fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/option/mediator.py000066400000000000000000000004401174525413000270200ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd.modems.option Module: mediator """ from ogsmd.modems.abstract.mediator import * # add overrides here fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/option/modem.py000066400000000000000000000037651174525413000263320ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Daniel Willmann (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd.modems.option Module: modem """ __version__ = "0.1.0" MODULE_NAME = "ogsmd.modems.option" import mediator from ..abstract.modem import AbstractModem from .channel import OptionChannel from .unsolicited import UnsolicitedResponseDelegate from ogsmd.gsm.decor import logged from ogsmd.gsm.channel import AtCommandChannel #=========================================================================# class Option( AbstractModem ): #=========================================================================# @logged def __init__( self, *args, **kwargs ): AbstractModem.__init__( self, *args, **kwargs ) # The one and only serial line self._channels["UNSOL"] = OptionChannel( self.pathfactory, "/dev/ttyUSB2", modem=self ) # configure channels self._channels["UNSOL"].setDelegate( UnsolicitedResponseDelegate( self._object, mediator ) ) self._data["pppd-configuration"] = [ \ "115200", "nodetach", "crtscts", "noipdefault", ":10.0.0.1", "local", 'defaultroute', 'debug', 'hide-password', 'ipcp-accept-local', #"lcp-echo-failure", "10", #"lcp-echo-interval", "3", "noauth", #"demand", "noipdefault", "novj", "novjccomp", "persist" ] def channel( self, category ): return self._channels["UNSOL"] def pathfactory( self, name ): return name def dataPort( self ): # FIXME remove duplication and just use pathfactory return "/dev/ttyUSB0" fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/option/unsolicited.py000066400000000000000000000005231174525413000275400ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later """ from ogsmd.modems.abstract.unsolicited import AbstractUnsolicitedResponseDelegate class UnsolicitedResponseDelegate( AbstractUnsolicitedResponseDelegate ): pass fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/qualcomm_msm/000077500000000000000000000000001174525413000260265ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/qualcomm_msm/__init__.py000066400000000000000000000000001174525413000301250ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/qualcomm_msm/channel.py000066400000000000000000000076331174525413000300210ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd.modems.qualcomm_msm Module: channel """ __version__ = "0.4.0" MODULE_NAME = "ogsmd.modems.qualcomm_msm.channel" from ogsmd.modems.abstract.channel import AbstractModemChannel from ogsmd.gsm import parser import itertools, select import logging logger = logging.getLogger( MODULE_NAME ) #=========================================================================# class SingleLineChannel( AbstractModemChannel ): #=========================================================================# def __init__( self, *args, **kwargs ): if not "timeout" in kwargs: kwargs["timeout"] = 60*60 AbstractModemChannel.__init__( self, *args, **kwargs ) def _populateCommands( self ): """ Populate the command queues to be sent on modem state changes. """ AbstractModemChannel._populateCommands( self ) # prepopulated c = self._commands["init"] c.remove( "Z" ) # do not reset, otherwise it will fall back to V1 # reenable unsolicited responses, we don't have a seperate channel # so we need to process them here as well c.append( "+CLIP=1" ) # calling line identification presentation enable c.append( "+COLP=1" ) # connected line identification presentation enable c.append( "+CCWA=1" ) # call waiting c.append( "+CSSN=1,1" ) # supplementary service notifications: send unsol. code c.append( "+CTZU=1" ) # timezone update c.append( "+CTZR=1" ) # timezone reporting c.append( "+CREG=2" ) # registration information (NOTE not all modems support =2) c.append( "+CAOC=2" ) # advice of charge: send unsol. code # GPRS unsolicited c.append( "+CGEREP=2,1" ) c.append( "+CGREG=2" ) def installParser( self ): """ Install a specific low level parser for this channel. """ self.parser = parser.QualcommGsmViolationParser( self._handleResponseToRequest, self._handleUnsolicitedResponse ) def _hookLowLevelInit( self ): """ Low level initialization of channel. Qualcomm default mode is V0, which completely disturbs our parser. We send a special init here until it responds in V1 mode. """ for i in itertools.count(): logger.debug( "(modem init... try #%d)", i+1 ) select.select( [], [self.serial.fd], [], 0.5 ) self.serial.write( "ATE0Q0V1\r\n" ) r, w, x = select.select( [self.serial.fd], [], [], 0.5 ) if r: try: buf = self.serial.inWaiting() # FIXME remove catchall here except: self.serial.close() path = self.pathfactory( self.name ) if not path: # path is None or "" return False self.serial.port = str( path ) self.serial.open() buf = self.serial.inWaiting() ok = self.serial.read(buf).strip() logger.debug( "read: %s", ok ) if "OK" in ok or "AT" in ok: break logger.debug( "(modem not responding)" ) if i == 5: logger.debug( "(reopening modem)" ) self.serial.close() path = self.pathfactory( self.name ) if not path: # path is None or "" return False self.serial.port = str( path ) self.serial.open() if i == 10: logger.warning( "(can't read from modem. giving up)" ) self.serial.close() return False logger.info( "%s: responding OK" % self ) self.serial.flushInput() return True fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/qualcomm_msm/mediator.py000066400000000000000000000027061174525413000302110ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd.modems.qualcomm_msm Module: mediator """ from ogsmd.modems.abstract.mediator import * __version__ = "0.1.0" MODULE_NAME = "ogsmd.modems.qualcomm_msm.mediator" #=========================================================================# class PdpActivateContext( PdpMediator ): #=========================================================================# def trigger( self ): pdpConnection = Pdp.getInstance( self._object ) if pdpConnection.isActive(): self._ok() else: pdpConnection.setParameters( self.apn, self.user, self.password ) self._commchannel.enqueue( '+CGDCONT=1,"IP","%s"' % self.apn, self.responseFromChannel, self.errorFromChannel ) def responseFromChannel( self, request, response ): if response[-1] != "OK": PdpMediator.responseFromChannel( self, request, response ) else: self._commchannel.enqueue( "D*99***1#", self.responseFromChannel2, self.errorFromChannel ) def responseFromChannel2( self, request, response ): if response[-1] == "NO CARRIER": PdpMediator.responseFromChannel( self, request, response ) else: pdpConnection = Pdp.getInstance( self._object ) pdpConnection.activate() self._ok() fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/qualcomm_msm/modem.py000066400000000000000000000036731174525413000275120ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd.modems.qualcomm_msm Module: modem """ __version__ = "1.1.1" MODULE_NAME = "ogsmd.modems.qualcomm_msm.modem" import mediator from ..abstract.modem import AbstractModem from .channel import SingleLineChannel from .unsolicited import UnsolicitedResponseDelegate from framework.config import config #=========================================================================# class QualcommMsm( AbstractModem ): #=========================================================================# def __init__( self, *args, **kwargs ): AbstractModem.__init__( self, *args, **kwargs ) # The one and only serial line self._channels["SINGLE"] = SingleLineChannel( self.portfactory, "ogsmd", modem=self ) # configure channels self._channels["SINGLE"].setDelegate( UnsolicitedResponseDelegate( self._object, mediator ) ) # This modem handles setup and teardown of data connections on its own self._data["pppd-does-setup-and-teardown"] = False # This modem has a special ppp configuration self._data["pppd-configuration"] = [ \ 'nodetach', 'debug', 'defaultroute', "local", 'noipdefault', 'novj', "novjccomp", #'persist', 'proxyarp', 'replacedefaultroute', 'usepeerdns', ] def channel( self, category ): # we do not care about a category here, we only have one channel return self._channels["SINGLE"] def portfactory( self, name ): return config.getValue( "ogsmd", "serial", default="/dev/smd0" ) def dataPort( self ): # FIXME remove duplication and just use pathfactory return config.getValue( "ogsmd", "data", default="/dev/smd7" ) fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/qualcomm_msm/unsolicited.py000066400000000000000000000021301174525413000307160ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008-2009 Michael 'Mickey' Lauer GPLv2 or later Package: ogsmd.modems.qualcomm_msm Module: unsolicited """ __version__ = "0.0.1.0" MODULE_NAME = "ogsmd.modems.qualcomm_msm.unsolicited" from ogsmd.modems.abstract.unsolicited import AbstractUnsolicitedResponseDelegate from ogsmd.gsm import const from ogsmd.helpers import safesplit import ogsmd.gsm.sms import logging logger = logging.getLogger( MODULE_NAME ) class UnsolicitedResponseDelegate( AbstractUnsolicitedResponseDelegate ): def __init__( self, *args, **kwargs ): AbstractUnsolicitedResponseDelegate.__init__( self, *args, **kwargs ) # # GSM standards # # # Proprietary URCs # # @HTCCSQ: 2 def atHTCCSQ( self, righthandside ): """Indicates signal strength""" value = int( righthandside ) self._object.SignalStrength( 20*value ) # +PB_READY def plusPB_READY( self, righthandside ): """Indicates phonebook readyness""" self._object.ReadyStatus( True ) fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/sierra/000077500000000000000000000000001174525413000246215ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/sierra/__init__.py000066400000000000000000000000001174525413000267200ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/sierra/channel.py000066400000000000000000000043461174525413000266120ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2007-2008 M. Dietrich (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd.modems.sierra Module: channel """ from ogsmd.gsm.decor import logged from ogsmd.modems.abstract.channel import AbstractModemChannel #=========================================================================# class SierraChannel( AbstractModemChannel ): #=========================================================================# def _populateCommands( self ): """ Populate the command queues to be sent on modem state changes. """ c = [] # reset c.append( 'Z' ) # soft reset c.append( 'E0V1' ) # echo off, verbose result on # error and result reporting reporting c.append( '+CMEE=1' ) # report mobile equipment errors: in numerical format c.append( '+CRC=1' ) # cellular result codes: enable extended format # c.append( '+CSCS="8859-1"' ) # character set conversion: use 8859-1 (latin 1) c.append( '+CSDH=1' ) # show text mode parameters: show values c.append( '+CSNS=0' ) # single numbering scheme: voice # sms c.append( '+CMGF=0' ) # message format: enable pdu mode, disable text mode c.append( '+CSMS=1' ) # GSM Phase 2+ commands: enable # unsolicited c.append( '+CLIP=1' ) # calling line identification presentation: disable c.append( '+COLP=1' ) # connected line identification presentation: disable c.append( '+CCWA=1' ) # call waiting: disable c.append( '+CTZR=1' ) # timezone reporting: enable c.append( '+CREG=2' ) # registration information: enable c.append( '+CGREG=2' ) # GPRS registration information: enable c.append( '+CGEREP=2') # Packet domain event reporting: enable self._commands["init"] = c c = [] # c.append( "+CNMI=2,1,2,1,1" ) # buffer sms on SIM, report CB directly self._commands["sim"] = c c = [] self._commands["antenna"] = c fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/sierra/mediator.py000066400000000000000000000004401174525413000267750ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd.modems.sierra Module: mediator """ from ogsmd.modems.abstract.mediator import * # add overrides here fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/sierra/modem.py000066400000000000000000000022351174525413000262760ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008 Michael 'Mickey' Lauer GPLv2 or later Package: ogsmd.modems.sierra Module: modem """ __version__ = "0.1.0" MODULE_NAME = "ogsmd.modems.sierra" import mediator from ..abstract.modem import AbstractModem from .channel import SierraChannel from .unsolicited import UnsolicitedResponseDelegate from ogsmd.gsm.decor import logged from ogsmd.gsm.channel import AtCommandChannel #=========================================================================# class Sierra( AbstractModem ): #=========================================================================# @logged def __init__( self, *args, **kwargs ): AbstractModem.__init__( self, *args, **kwargs ) # The one and only serial line self._channels["SINGLE"] = SierraChannel( self.pathfactory, "/dev/ttyUSB0", modem=self ) # configure channels self._channels["SINGLE"].setDelegate( UnsolicitedResponseDelegate( self._object, mediator ) ) def channel( self, category ): return self._channels["SINGLE"] def pathfactory( self, name ): return name fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/sierra/unsolicited.py000066400000000000000000000006051174525413000275160ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd.modems.sierra Module: unsolicited """ from ogsmd.modems.abstract.unsolicited import AbstractUnsolicitedResponseDelegate class UnsolicitedResponseDelegate( AbstractUnsolicitedResponseDelegate ): pass fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/singleline/000077500000000000000000000000001174525413000254655ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/singleline/__init__.py000066400000000000000000000000001174525413000275640ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/singleline/channel.py000066400000000000000000000032431174525413000274510ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2007-2008 M. Dietrich (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd.modems.singeline Module: channel """ from ogsmd.modems.abstract.channel import AbstractModemChannel #=========================================================================# class SingleLineChannel( AbstractModemChannel ): #=========================================================================# def __init__( self, *args, **kwargs ): if not "timeout" in kwargs: kwargs["timeout"] = 60*60 AbstractModemChannel.__init__( self, *args, **kwargs ) def _populateCommands( self ): """ Populate the command queues to be sent on modem state changes. """ AbstractModemChannel._populateCommands( self ) # prepopulated c = self._commands["init"] # reenable unsolicited responses, we don't have a seperate channel # so we need to process them here as well c.append( '+CLIP=1' ) # calling line identification presentation enable c.append( '+COLP=1' ) # connected line identification presentation enable c.append( '+CCWA=1' ) # call waiting c.append( "+CSSN=1,1" ) # supplementary service notifications: send unsol. code c.append( '+CTZU=1' ) # timezone update c.append( '+CTZR=1' ) # timezone reporting c.append( '+CREG=2' ) # registration information (NOTE not all modems support =2) c.append( "+CAOC=2" ) # advice of charge: send unsol. code # GPRS unsolicited c.append( "+CGEREP=2,1" ) c.append( "+CGREG=2" ) fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/singleline/mediator.py000066400000000000000000000004441174525413000276450ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd.modems.singleline Module: mediator """ from ogsmd.modems.abstract.mediator import * # add overrides here fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/singleline/modem.py000066400000000000000000000024031174525413000271370ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd.modems.singleline Module: modem """ __version__ = "1.0.0" MODULE_NAME = "singleline" import mediator from ..abstract.modem import AbstractModem from .channel import SingleLineChannel from .unsolicited import UnsolicitedResponseDelegate from framework.config import config #=========================================================================# class SingleLine( AbstractModem ): #=========================================================================# def __init__( self, *args, **kwargs ): AbstractModem.__init__( self, *args, **kwargs ) # The one and only serial line self._channels["SINGLE"] = SingleLineChannel( self.portfactory, "ogsmd", modem=self ) # configure channels self._channels["SINGLE"].setDelegate( UnsolicitedResponseDelegate( self._object, mediator ) ) def channel( self, category ): # we do not care about a category here, we only have one channel return self._channels["SINGLE"] def portfactory( self, name ): return config.getValue( "ogsmd", "serial", default="/dev/ttySAC0" ) fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/singleline/unsolicited.py000066400000000000000000000007601174525413000303640ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd.modems.singleline Module: unsolicited """ from ..abstract.unsolicited import AbstractUnsolicitedResponseDelegate class UnsolicitedResponseDelegate( AbstractUnsolicitedResponseDelegate ): def __init__( self, *args, **kwargs ): AbstractUnsolicitedResponseDelegate.__init__( self, *args, **kwargs ) fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/ti_calypso/000077500000000000000000000000001174525413000255025ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/ti_calypso/__init__.py000066400000000000000000000000001174525413000276010ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/ti_calypso/channel.py000066400000000000000000000331231174525413000274660ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008-2009 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. (C) 2007-2008 M. Dietrich GPLv2 or later Package: ogsmd.modems.ti_calypso Module: channel TI Calypso specific modem channels """ __version__ = "0.9.10.6" from framework.config import config from ogsmd.gsm.callback import SimpleCallback from ogsmd.gsm.decor import logged from ogsmd.modems.abstract.channel import AbstractModemChannel import gobject import time, itertools, select, os import logging logger = logging.getLogger('ogsmd') #=========================================================================# LOW_LEVEL_BUFFER_SIZE = 32768 #=========================================================================# # MMI_AEC_REQ : 0283 = Long AEC, 105 = SPENH, 187 = AEC+SPENH, 1 = STOP # aec_control register bits | 0 0 Sa t2|t1 g3 g2 g1|g0 e2 e1 ak| # bit 0 : ACK bit : set to 1 in order to warn DSP that a new command # is present. # bit 1 : enable AEC # bit 2 : enable SPENH (= Speech Enhancement = noise reduction) # bit 3 : additionnal AEC gain attenuation (lsb) # bit 4 : additionnal AEC gain attenuation (msb) # bit 5 : additionnal SPENH gain attenuation (lsb) # bit 6 : additionnal SPENH gain attenuation (msb) # bit 7 : reset trigger for AEC # bit 8 : reset trigger for SPENH # bit 9 : AEC selector 0 : short AEC, 1 : long AEC # # for Short AEC 0083 # for long AEC 0283 # for long AEC -6 dB 028B # for long AEC -12 dB 0293 # for long AEC -18 dB 029B # for SPENH 0105 # for SPENH -6 dB 0125 # for SPENH -12 dB 0145 # for SPENH -18 dB 0165 # for BOTH 0187 # for STOP ALL 0001 (all bits reset + ACK to 1 to warn the DSP) AEC_NR_MAP = { \ "short-aec": "0083", "long-aec": "0283", "long-aec:6db": "028B", "long-aec:12db": "0293", "long-aec:18db": "029B", "nr": "0105", "nr:6db": "0125", "nr:12db": "0145", "nr:18db": "0165", "aec+nr": "0187", "none": "0001", } def createDspCommand(): dspMode = config.getValue( "ogsmd", "ti_calypso_dsp_mode", "aec+nr" ) return "%N" + AEC_NR_MAP.get( dspMode, "aec+nr" ) #=========================================================================# class CalypsoModemChannel( AbstractModemChannel ): #=========================================================================# modem_communication_timestamp = 1 def __init__( self, *args, **kwargs ): AbstractModemChannel.__init__( self, *args, **kwargs ) @logged def _hookLowLevelInit( self ): """ Low level initialization of channel. This is actually an ugly hack which is unfortunately necessary since the TI multiplexer obviously has problems wrt. to initialization (swallowing first bunch of commands now and then...) To work around this, we send 'ATE0\r\n' until we actually get an 'OK' from the modem. We try this for 5 times, then we reopen the serial line. If after 10 times we still have no response, we assume that the modem is broken and fail. """ for i in itertools.count(): logger.debug( "(modem init... try #%d)", i+1 ) select.select( [], [self.serial.fd], [], 0.5 ) self.serial.write( "ATE0Q0V1\r\n" ) r, w, x = select.select( [self.serial.fd], [], [], 0.5 ) if r: try: buf = self.serial.inWaiting() # FIXME remove catchall here except: self.serial.close() path = self.pathfactory( self.name ) if not path: # path is None or "" return False self.serial.port = str( path ) self.serial.open() buf = self.serial.inWaiting() ok = self.serial.read(buf).strip() logger.debug( "read: %s", ok ) if "OK" in ok or "AT" in ok: break logger.debug( "(modem not responding)" ) if i == 5: logger.debug( "(reopening modem)" ) self.serial.close() path = self.pathfactory( self.name ) if not path: # path is None or "" return False self.serial.port = str( path ) self.serial.open() if i == 10: logger.warning( "(can't read from modem. giving up)" ) self.serial.close() return False logger.info( "%s: responding OK" % self ) self.serial.flushInput() # reset global modem communication timestamp if CalypsoModemChannel.modem_communication_timestamp: CalypsoModemChannel.modem_communication_timestamp = time.time() return True # # send %CUNS on every channel as init # def _populateCommands( self ): AbstractModemChannel._populateCommands( self ) self._commands["init"].append( "%CUNS=2" ) # # TI Calypso has a deep sleep mode, effective after 8 seconds, # from which we need to wake up by sending a special character # (plus a small waiting time) # def _hookPreReading( self ): pass # only writes should reset the timer # this fixes finally ticket #435 def _hookPostReading( self ): pass def _lowlevelRead( self ): return os.read( self.serial.fd, LOW_LEVEL_BUFFER_SIZE ) def _lowlevelWrite( self, data ): os.write( self.serial.fd, data ) def _hookPreSending( self ): if CalypsoModemChannel.modem_communication_timestamp: current_time = time.time() if current_time - CalypsoModemChannel.modem_communication_timestamp > 5: logger.debug( "(%s: last communication with modem was %d seconds ago. Sending EOF to wakeup)", self, int(current_time - CalypsoModemChannel.modem_communication_timestamp) ) self.serial.write( "\x1a" ) time.sleep( 0.4 ) CalypsoModemChannel.modem_communication_timestamp = current_time def _hookPostSending( self ): if CalypsoModemChannel.modem_communication_timestamp: CalypsoModemChannel.modem_communication_timestamp = time.time() # # Since we are using a multiplexer, a hang-up condition on one channel is a good indication # that the underlying multiplexer died. In this case, we need to completely reinit # def _hookHandleHupCondition( self ): logger.warning( "HUP condition on modem channel. The multiplexer is probably dead. Launching reinit..." ) logger.debug( "Closing the modem..." ) self._modem.reinit() #=========================================================================# class CallChannel( CalypsoModemChannel ): #=========================================================================# def __init__( self, *args, **kwargs ): if not "timeout" in kwargs: kwargs["timeout"] = 60*60 CalypsoModemChannel.__init__( self, *args, **kwargs ) self.callback = None def _populateCommands( self ): CalypsoModemChannel._populateCommands( self ) self._commands["sim"] = [] self._commands["antenna"] = [] def setIntermediateResponseCallback( self, callback ): assert self.callback is None, "callback already set" self.callback = callback def handleUnsolicitedResponse( self, response ): if self.callback is not None: self.callback( response ) else: logger.warning( "CALLCHANNEL: UNHANDLED INTERMEDIATE: %s", response ) #=========================================================================# class MiscChannel( CalypsoModemChannel ): #=========================================================================# def _populateCommands( self ): CalypsoModemChannel._populateCommands( self ) self._commands["sim"] = [] self._commands["antenna"] = [] #=========================================================================# class UnsolicitedResponseChannel( CalypsoModemChannel ): #=========================================================================# def __init__( self, *args, **kwargs ): CalypsoModemChannel.__init__( self, *args, **kwargs ) self._reenableUnsolicitedTimer = None def _populateCommands( self ): CalypsoModemChannel._populateCommands( self ) c = self._commands["init"] # GSM unsolicited c.append( '+CLIP=1' ) # calling line identification presentation enable c.append( '+COLP=0' ) # connected line identification presentation: disable # DO NOT enable +COLP=1 on the Calypso, it will raise the chance to drop into FSO #211 #c.append( '+COLP=1' ) # connected line identification presentation: enable c.append( '+CCWA=1' ) # call waiting c.append( "+CSSN=1,1" ) # supplementary service notifications: send unsol. code c.append( '+CTZU=1' ) # timezone update c.append( '+CTZR=1' ) # timezone reporting c.append( '+CREG=2' ) # registration information (NOTE not all modems support =2) c.append( "+CAOC=2" ) # advice of charge: send unsol. code # GPRS unsolicited c.append( "+CGEREP=2,1" ) c.append( "+CGREG=2" ) # calypso proprietary unsolicited c.append( "%CPI=3" ) # call progress indication: enable with call number ID, GSM Cause, and ALS c.append( "%CSCN=1,2,1,2" ) # show service change: call control service and supplementary service c.append( "%CSQ=1" ) # signal strength: send unsol. code c.append( "%CPRI=1" ) # gsm cipher indication: send unsol. code c.append( "%CNIV=1" ) c.append( "%CSTAT=1" ) # machine specific (might not be the best place here) c.append( '@ST="-26"' ) # audio side tone: set to minimum deepSleepMode = config.getValue( "ogsmd", "ti_calypso_deep_sleep", "adaptive" ) if deepSleepMode == "never": c.append( "%SLEEP=2" ) # sleep mode: disable all else: c.append( "%SLEEP=4" ) # sleep mode: enable all c.append( createDspCommand() ) c = self._commands["antenna"] c.append( "+CLVL=255" ) # audio output: set to maximum c = self._commands["suspend"] def sms_no_cb( self=self ): if self._modem.data( "sim-buffers-sms" ): return "+CNMI=%s" % self._modem.data( "sms-buffered-nocb" ) else: return "+CNMI=%s" % self._modem.data( "sms-direct-nocb" ) c.append( sms_no_cb ) c.append( "+CTZU=0" ) c.append( "+CTZR=0" ) c.append( "+CREG=0" ) c.append( "+CGREG=0" ) c.append( "+CGEREP=0,0" ) c.append( "%CSQ=0" ) c.append( "%CPRI=0" ) c.append( "%CBHZ=0" ) # home zone cell broadcast: disable c = self._commands["resume"] c.insert( 0, "+CTZU=1" ) c.insert( 0, "+CTZR=1" ) c.insert( 0, "+CREG=2" ) c.insert( 0, "+CGREG=2" ) c.insert( 0, "+CGEREP=2,1" ) c.insert( 0, "%CSQ=1" ) # signal strength: send unsol. code c.insert( 0, "%CPRI=1" ) # gsm cipher indication: send unsol. code c.insert( 0, "%CNIV=1" ) c += self._commands["sim"] # reenable notifications def homezone( self=self ): return "%CBHZ=1" if self._modem.data( "homezone-enabled", False ) else "%CBHZ=0" c.append( homezone ) def close( self ): if self.delegate.checkForRecamping: if not self.delegate.recampingTimeout is None: gobject.source_remove( self.delegate.recampingTimeout ) self.delegate.recampingTimeout = None CalypsoModemChannel.close( self ) # # Do not send the reinit commands right after suspend, give us a bit time # to drain the buffer queue, as we might have been waken from GSM # FIXME At the end of the day, this is a workaround that tries to reduce # the possibilities of unsolicited responses woven in solicited responses. # We need to be crystal-clear here in realizing that this is a bandaid not # only for a conceptual problem with the AT protocol, but also our imperfect # AT lowlevel parser. If we would give the parser a list for every valid answer # forevery request, we would be able to identify unsolicited responses woven # in solicited reponses with much more confidence. # Note to self: Remember this for the forthcoming reimplementation of ogsmsd in Vala :) # :M: def resume( self, ok_callback, error_callback ): logger.debug( "TI Calypso specific resume handling... sending reinit in 5 seconds..." ) def done( request, response, self=self, ok_callback=ok_callback ): self.reenableUnsolicitedTimer = None return False # mainloop: don't call me again self._reenableUnsolicitedTimer = gobject.timeout_add_seconds( 10, self._sendCommandsNotifyDone, "resume", done ) ok_callback( self ) def suspend( self, ok_callback, error_callback ): # check whether we suspend before the unsolicited messages have been reenabled if self._reenableUnsolicitedTimer is not None: logger.debug( "TI Calypso specific resume handling... killing reenable-unsolicited-timer." ) gobject.source_remove( self._reenableUnsolicitedTimer ) CalypsoModemChannel.suspend( self, ok_callback, error_callback ) fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/ti_calypso/mediator.py000066400000000000000000000156341174525413000276710ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd.modems.ti_calypso Module: mediator """ __version__ = "1.1.2" from ogsmd.modems.abstract.mediator import * import logging logger = logging.getLogger( "ogsmd" ) #=========================================================================# class CbSetCellBroadcastSubscriptions( CbSetCellBroadcastSubscriptions ): # s #=========================================================================# # reimplemented for special TI Calypso %CBHZ handling def responseFromChannel( self, request, response ): if response[-1] != "OK": CbMediator.responseFromChannel( self, request, response ) else: firstChannel = 0 lastChannel = 0 if self.channels == "all": firstChannel = 0 lastChannel = 999 elif self.channels == "none": pass else: if "-" in self.channels: first, last = self.channels.split( '-' ) firstChannel = int( first ) lastChannel = int( last ) else: firstChannel = lastChannel = int( self.channels ) logger.debug( "listening to cell broadcasts on channels %d - %d" % ( firstChannel, lastChannel ) ) homezone = firstChannel <= 221 <= lastChannel self._object.modem.setData( "homezone-enabled", homezone ) if homezone: self._commchannel.enqueue( "%CBHZ=1" ) else: self._commchannel.enqueue( "%CBHZ=0" ) self._ok() #=========================================================================# class MonitorGetServingCellInformation( MonitorMediator ): #=========================================================================# """ 1 arfcn Current Channel Number 2 c1 Path Loss Criterion C1 3 c2 Cell-reselection Criterion C2 4 rxlev Received Field Strength (rxlev/2)+2= AT+CSQ response value 5 bsic Base Station ID Code 6 cell_id Cell Indentifier 7 dsc Downlink Signaling Counter actual value 8 txlev Transmit Power Level 9 tn Timeslot Number 10 rlt Radio Link Timeout Counter 11 tav Timing Advance 12 rxlev_f Received Field Strength full 13 rxlev_s Received Field Strength sub 14 rxqual_f Received Quality full 15 rxqual_s Received Quality sub 16 lac Location Area Code 17 cba Cell Bar Access 18 cbq Cell Bar Qualifier 19 ctype Cell Type Indicator NA/GSM/GPRS 20 vocoder Vocoder Sig/speech/efr/amr/14.4/9.6/4.8/2.4 """ params = "arfcn c1 c2 rxlev bsic cid dsc txlev tn rlt tav rxlev_f rxlev_s rxqual_f rxqual_s lac cba cbq ctype vocoder".split() params.reverse() stringparams = "cid lac".split() def trigger( self ): self._commchannel.enqueue( "%EM=2,1", self.responseFromChannel, self.errorFromChannel, ["%EM", "PDU"] ) def responseFromChannel( self, request, response ): if response[-1] != "OK": MonitorMediator.responseFromChannel( self, request, response ) else: result = {} values = self._rightHandSide( response[0] ).split( ',' ) params = self.params[:] for value in values: param = params.pop() if param in self.stringparams: result[param] = "%04X" % int( value ) else: result[param] = int( value ) if params: self._error( DBusError.DeviceFailed( "Incomplete response from modem" ) ) self._ok( result ) #=========================================================================# class MonitorGetNeighbourCellInformation( MonitorMediator ): #=========================================================================# """ 1 no_ncells Number of neighbor cells 2 arfcn_nc BCCH channel Channel no 0 - 5 3 c1_nc Path Loss Criterion C1 Channel no 0 - 5 4 c2_nc Cell-Reselection Criterion C2 Channel no 0 - 5 5 rxlev_nc Receive Field Strength Channel no 0 - 5 6 bsic_nc Base Station ID Code Channel no 0 - 5 7 cell_id_nc Cell Identity Channel no 0 - 5 8 lac_nc Location Area Code Channel no 0 - 5 9 frame_offset Frame Offset Channel no 0 - 5 10 time-alignmnt Time Alignment Channel no 0 - 5 11 cba_nc Cell Bar Access Channel no 0 - 5 12 cbq_nc Cell Bar Qualifier Channel no 0 - 5 13 ctype Cell Type Indicator Channel no 0 - 5 14 rac Routing Area Code Channel no 0 - 5 15 cell_resel_offset Cell resection Offset Channel no 0 - 5 16 temp_offset Temporary Offset Channel no 0 - 5 17 rxlev_acc_min Rxlev access min Channel no 0 - 5 """ params = "arfcn c1 c2 rxlev bsic cid lac foffset timea cba cbq ctype rac roffset toffset rxlevam".split() params.reverse() stringparams = "cid lac".split() def trigger( self ): self._commchannel.enqueue( "%EM=2,3", self.responseFromChannel, self.errorFromChannel, ["%EM", "PDU"] ) def responseFromChannel( self, request, response ): if response[-1] != "OK": MonitorMediator.responseFromChannel( self, request, response ) else: result = [] count = int( self._rightHandSide( response[0] ) ) if count > 0: for cell in xrange( count ): result.append( {} ) params = self.params[:] for line in response[1:-1]: param = params.pop() for index, value in enumerate( line.split( ',' ) ): if index < count: if param in self.stringparams: result[index][param] = "%04X" % int( value ) else: result[index][param] = int( value ) if params: self._error( DBusError.DeviceFailed( "Incomplete response from modem" ) ) self._ok( result ) #=========================================================================# if __name__ == "__main__": #=========================================================================# pass fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/ti_calypso/modem.py000066400000000000000000000202561174525413000271620ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008-2009 Michael 'Mickey' Lauer (C) 2008-2009 Openmoko, Inc. GPLv2 or later Package: ogsmd.modems.ti_calypso Module: modem """ __version__ = "0.9.9.10" MODULE_NAME = "ogsmd.modems.ti_calypso" DEVICE_CALYPSO_PATH = "/dev/ttySAC0" SYSFS_CALYPSO_POWER_PATH = "/sys/bus/platform/devices/neo1973-pm-gsm.0/power_on" SYSFS_CALYPSO_RESET_PATH = "/sys/bus/platform/devices/neo1973-pm-gsm.0/reset" SYSFS_CALYPSO_FLOW_CONTROL_PATH = "/sys/bus/platform/devices/neo1973-pm-gsm.0/flowcontrolled" import mediator from framework.config import config from framework.patterns.utilities import killall from ogsmd.modems.abstract.modem import AbstractModem from .channel import CallChannel, UnsolicitedResponseChannel, MiscChannel from .unsolicited import UnsolicitedResponseDelegate from ogsmd.gsm.channel import AtCommandChannel from ogsmd.helpers import writeToFile from dbus import Interface from time import sleep import serial import logging logger = logging.getLogger( MODULE_NAME ) import os #=========================================================================# class TiCalypso( AbstractModem ): #=========================================================================# def __init__( self, *args, **kwargs ): # kernel specific paths global SYSFS_CALYPSO_POWER_PATH global SYSFS_CALYPSO_RESET_PATH global SYSFS_CALYPSO_FLOW_CONTROL_PATH kernel_release = os.uname()[2] if kernel_release >= "2.6.32": SYSFS_CALYPSO_POWER_PATH = "/sys/bus/platform/devices/gta02-pm-gsm.0/power_on" SYSFS_CALYPSO_RESET_PATH = "/sys/bus/platform/devices/gta02-pm-gsm.0/reset" SYSFS_CALYPSO_FLOW_CONTROL_PATH = "/sys/bus/platform/devices/gta02-pm-gsm.0/flowcontrolled" logger.info( "Kernel >=2.6.32, gsm sysfs path updated" ) AbstractModem.__init__( self, *args, **kwargs ) self._channelmap = { "ogsmd.call":1, "ogsmd.unsolicited":2, "ogsmd.misc":3, "ogsmd.gprs":4 } # VC 1 self._channels["CALL"] = CallChannel( self.pathfactory, "ogsmd.call", modem=self ) # VC 2 self._channels["UNSOL"] = UnsolicitedResponseChannel( self.pathfactory, "ogsmd.unsolicited", modem=self ) # VC 3 self._channels["MISC"] = MiscChannel( self.pathfactory, "ogsmd.misc", modem=self ) # VC 4 # FIXME pre-allocate GPRS channel for pppd? # configure channels self._channels["UNSOL"].setDelegate( UnsolicitedResponseDelegate( self._object, mediator ) ) # configure behaviour using special commands self._data["cancel-outgoing-call"] = "%CHLD=I" # muxer mode self._muxercommand = config.getValue( "ogsmd", "ti_calypso_muxer", "gsm0710muxd" ) # muxer object self._muxeriface = None def _modemOn( self ): """ Lowlevel initialize this modem. """ logger.debug( "reset-cycling modem" ) writeToFile( SYSFS_CALYPSO_POWER_PATH, "0\n" ) sleep( 1 ) writeToFile( SYSFS_CALYPSO_RESET_PATH, "0\n" ) sleep( 1 ) writeToFile( SYSFS_CALYPSO_POWER_PATH, "1\n" ) sleep( 1 ) writeToFile( SYSFS_CALYPSO_RESET_PATH, "1\n" ) sleep( 1 ) writeToFile( SYSFS_CALYPSO_RESET_PATH, "0\n" ) sleep( 1 ) logger.debug( "reset cycle complete" ) device = serial.Serial() device.port = DEVICE_CALYPSO_PATH device.baudrate = 115200 device.rtscts = True device.xonxoff = False device.bytesize = serial.EIGHTBITS device.parity = serial.PARITY_NONE device.stopbits = serial.STOPBITS_ONE device.timeout = 1 logger.debug( "opening port now" ) device.open() device.write( "\0xf9\0xf9" ) device.flush() sleep( 0.2 ) device.write( "\0x7E\0x03\0xEF\0xC3\0x01\0x70\0x7E" ) device.flush() sleep( 0.2 ) device.write( "\r\nAT\r\n" ) device.flush() result = device.read( 64 ) logger.debug( "got %s", repr(result) ) ok = False for retries in xrange( 5 ): logger.debug( "port open, sending ATE0" ) device.write( "ATE0\r\n" ) device.flush() result = device.read( 64 ) logger.debug( "got %s", repr(result) ) if "OK" in result: ok = True break device.close() return ok def _modemOff( self ): device = serial.Serial() device.port = DEVICE_CALYPSO_PATH device.baudrate = 115200 device.rtscts = True device.xonxoff = False device.bytesize = serial.EIGHTBITS device.parity = serial.PARITY_NONE device.stopbits = serial.STOPBITS_ONE device.timeout = 1 logger.debug( "opening port now" ) device.open() device.write( "\0xf9\0xf9" ) device.flush() sleep( 0.2 ) device.write( "\0x7E\0x03\0xEF\0xC3\0x01\0x70\0x7E" ) device.flush() sleep( 0.2 ) device.write( "\r\nAT@POFF\r\n" ) device.flush() sleep( 0.2 ) writeToFile( SYSFS_CALYPSO_POWER_PATH, "0\n" ) def close( self ): # SYNC """ Close modem. Overriden for internal purposes. """ # call default implementation (closing all channels) AbstractModem.close( self ) killall( self._muxercommand ) # don't let two processes kill the power if self._muxercommand != "gsm0710muxd": self._modemOff() def channel( self, category ): """ Return proper channel. Overridden for internal purposes. """ if category == "CallMediator": return self._channels["CALL"] elif category == "UnsolicitedMediator": return self._channels["UNSOL"] else: return self._channels["MISC"] def pathfactory( self, name ): """ Allocate a new channel from the MUXer. Overridden for internal purposes. """ logger.info( "Requesting new channel from '%s'", self._muxercommand ) if self._muxercommand == "gsm0710muxd": if self._muxeriface is None: muxer = self._bus.get_object( "org.pyneo.muxer", "/org/pyneo/Muxer" ) self._muxeriface = Interface( muxer, "org.freesmartphone.GSM.MUX" ) return str( self._muxeriface.AllocChannel( name ) ) elif self._muxercommand == "fso-abyss": if self._muxeriface is None: muxer = self._bus.get_object( "org.freesmartphone.omuxerd", "/org/freesmartphone/GSM/Muxer" ) self._muxeriface = Interface( muxer, "org.freesmartphone.GSM.MUX" ) # power on modem if not self._modemOn(): self._muxeriface = None return "" # FIXME: emit error? if not self._muxeriface.HasAutoSession(): # abyss needs an open session before we can allocate channels self._muxeriface.OpenSession( True, 98, "serial", DEVICE_CALYPSO_PATH, 115200 ) pts, vc = self._muxeriface.AllocChannel( name, self._channelmap[name] ) return str(pts) def dataPort( self ): return self.pathfactory( "ogsmd.gprs" ) def prepareForSuspend( self, ok_callback, error_callback ): """overridden for internal purposes""" # FIXME still no error handling here def post_ok( ok_callback=ok_callback ): writeToFile( SYSFS_CALYPSO_FLOW_CONTROL_PATH, "1" ) ok_callback() AbstractModem.prepareForSuspend( self, post_ok, error_callback ) def recoverFromSuspend( self, ok_callback, error_callback ): writeToFile( SYSFS_CALYPSO_FLOW_CONTROL_PATH, "0" ) AbstractModem.recoverFromSuspend( self, ok_callback, error_callback ) fso-frameworkd-0.10.1/framework/subsystems/ogsmd/modems/ti_calypso/unsolicited.py000066400000000000000000000366361174525413000304140ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later """ __version__ = "0.8.5" from .channel import createDspCommand from framework.config import config from ogsmd.modems import currentModem from ogsmd.modems.abstract.unsolicited import AbstractUnsolicitedResponseDelegate from ogsmd.gsm import const from ogsmd.helpers import safesplit import gobject import time import logging logger = logging.getLogger( "ogsmd" ) #=========================================================================# class UnsolicitedResponseDelegate( AbstractUnsolicitedResponseDelegate ): #=========================================================================# def __init__( self, *args, **kwargs ): AbstractUnsolicitedResponseDelegate.__init__( self, *args, **kwargs ) self._callHandler.unsetHook() # we have special call handling that doesn't need stock hooks self.fullReadyness = "unknown" self.subsystemReadyness = { "PHB": False, "SMS": False } self.checkForRecamping = ( config.getValue( "ogsmd", "ti_calypso_deep_sleep", "adaptive" ) == "adaptive" ) if self.checkForRecamping: # initialize deep sleep vars self.lastStatus = "busy" self.lastTime = 0 self.firstReregister = 0 self.lastReregister = 0 self.reregisterIntervals = [] self.recampingTimeout = None def _checkRecampingBug( self ): logger.debug( "checking for TI Calypso recamping bug..." ) logger.debug( "reregistering %d times within %d seconds. unreg/reg-Intervals are: %s" % ( len(self.reregisterIntervals), self.lastReregister-self.firstReregister, [ "%.2f" % interval for interval in self.reregisterIntervals ] ) ) reregisterCounter = 0 for reregisterInterval in self.reregisterIntervals: if reregisterInterval < 3.5: # only an immediate unregister followed by register counts as a reregister reregisterCounter += 1 probeMinutes = ( self.lastReregister - self.firstReregister ) / 60.0 recampingFactor = reregisterCounter / probeMinutes logger.debug( "reregistering factor: %.2f recampings/minute" % recampingFactor ) # heuristics now if reregisterCounter > 5 and recampingFactor > 0.3: self._detectedRecampingBug() return False def _detectedRecampingBug( self ): logger.info( "This TI Calypso device suffers from the recamping bug. Turning off sleep mode to recover." ) # recover from recamping bug... self._object.modem.channel( "MISC" ).enqueue( "%SLEEP=2" ) # ...but launch trigger to give it another chance (it's also depending on BTS) if not self.recampingTimeout is None: # If recamping happens while there's still a timeout set warn the user and extend the timeout logger.warning( "Recamping bug occured, but TI Calypso is still out of deep sleep mode." ) gobject.source_remove( self.recampingTimeout ) self.recampingTimeout = gobject.timeout_add_seconds( 60*60, self._reactivateDeepSleep ) def _reactivateDeepSleep( self ): logger.info( "Reenabling deep sleep mode on TI Calypso (giving it another chance)" ) self.lastStatus = "busy" self.lastTime = 0 self.firstReregister = 0 self.lastReregister = 0 self.reregisterIntervals = [] self._object.modem.channel( "MISC" ).enqueue( "%SLEEP=4" ) # We don't want to be called again self.recampingTimeout = None return False # Overridden from AbstractUnsolicitedResponseDelegate to check for the # TI Calypso Deep Sleep Recamping bug: http://docs.openmoko.org/trac/ticket/1024 def plusCREG( self, righthandside ): # call base implementation AbstractUnsolicitedResponseDelegate.plusCREG( self, righthandside ) # do we care for recamping? if not self.checkForRecamping: return # check for recamping values = safesplit( righthandside, ',' ) register = const.REGISTER_STATUS[int(values[0])] if self.lastStatus == "unregistered": if self.register in "home roaming".split(): self.reregisterIntervals.append( time.time() - self.lastTime ) if len( self.reregisterIntervals ) == 1: self.firstReregister = time.time() gobject.idle_add( self._checkRecampingBug ) self.lastReregister = time.time() self.lastStatus = register self.lastTime = time.time() # +CRING is only honored, if we didn't receive %CPI def plusCRING( self, calltype ): status = self._callHandler.status() if "incoming" in status: # looks like %CPI has been received before, ignoring logger.debug( "+CRING, but call status already ok: ignoring" ) return else: logger.warning( "+CRING without previous %CPI: alerting" ) AbstractUnsolicitedResponseDelegate.plusCRING( self, calltype ) # +CLIP is not used on TI Calypso. See %CPI def plusCLIP( self, righthandside ): pass # +CCWA is not used on TI Calypso. See %CPI def plusCCWA( self, righthandside ): pass # +CSSU: 2,,"",128 # 0 Forwarded call # 1 CUG (plus index) # 2 remotely put on hold # 3 remote hold released # 4 Multiparty call entered # 5 Call on hold has been released # 6 - # 7,8 ? def plusCSSU( self, righthandside ): code, index, number, type = safesplit( righthandside, "," ) info = {} if code == "0": info["forwarded"] = True elif code == "1": info["cug"] = True elif code == "2": info["remotehold"] = True elif code == "3": info["remotehold"] = False else: logger.info( "unhandled +CSSU code '%s'" % code ) if info: # This is not failsafe since we don't know the call ID if code in "23": self._callHandler.statusChangeFromNetworkByStatus( "active", info ) else: self._callHandler.statusChangeFromNetworkByStatus( "incoming", info ) # # TI Calypso proprietary # # %CCCN: 0,0,A10E02010402011030068101428F0101 # FIXME: need decoder for this # reponses to conference AT+CHLD=3 with one call held and one active: # failed (with e-plus) # %CCCN: 1,1,A10602010002017C # ^ fail ? # +CMS ERROR: 320 HEX data packet: 7e 0d ef 0d 0a 2b 43... # ^ channel 3(MISC)? TI firmware bug? # %CCCN: 0,1,A306020100020112 # # # succeded (with D2) # %CCCN: 1,1,A10602010102017C # ^ ok ? # %CCCN: 0,1,A203020101 # # # call to homezone number while in homezone # this is sent while the call is incoming # %CCCN: 0,0,A10E0201000201103006810120850101 # calling a alice sim # %CCCN: 0,0,A10E0201000201103006810128840107 # def percentCCCN( self, righthandside ): direction, callId, ie = safesplit( righthandside, "," ) # this is ASN.1 BER, but we don't want a full decoder here info = {} if ie[0:8]+ie[10:30] == "A10E020102011030068101428F01": info["held"] = bool( int( ie[30:32], 16 ) ) if info: self._callHandler.statusChangeFromNetwork( int(callId)+1, info ) # %CPI: 1,0,0,0,1,0,"+491772616464",145,,,0 def percentCPI( self, righthandside ): """ Call Progress Indication: callId = call number in internal call table (same as call number in CCLD) msgType = 0:setup, 1:disconnect, 2:alert, 3:call proceed, 4:sync, 5:progress, 6:connected, 7:release, 8:reject (from network), 9:request (MO Setup), 10: hold ibt = 1, if in-band-tones enabled tch = 1, if traffic channel assigned direction = 0:MO, 1:MT, 2:CCBS, 3:MO-autoredial mode = 0:voice, 1:data, 2:fax, ..., 9 [see gsm spec bearer type] number = "number" [gsm spec] ntype = number type [gsm spec] alpha = "name", if number found in SIM phonebook [gsm spec] cause = GSM Network Cause [see gsm spec, section 04.08 annex H] line = 0, if line 1. 1, if line2. Typical chunks during a call: ... case A: incoming (MT) ... %CPI: 1,0,0,0,1,0,"+496912345678",145,,,0 ( setup call, MT, line one, no traffic channel yet ) +CRING: VOICE %CPI: 1,0,0,1,1,0,"+496912345678",145,,,0 ( setup call, MT, line one, traffic channel assigned ) %CPI: 1,4,0,1,1,0,"+496912345678",145,,,0 ( sync call, MT, line one, traffic channel assigned ) %CPI: 1,0,0,1,1,0,"+496912345678",145,,,0 ( setup call, MT, line one, traffic channel assigned ) +CRING: VOICE %CPI: 1,4,0,1,1,0,"+496912345678",145,,,0 ( sync call, MT, line one, traffic channel assigned ) +CRING: VOICE ... case A.1: remote line hangs up ... %CPI: 1,1,0,1,1,0,"+496912345678",145,,,0 ( disconnect call, MT line one, traffic channel assigned ) %CPI: 1,7,0,0,,,,,,,0 (release from network, traffic channel freed) (NO CARRIER, if call was connected, i.e. local accepted) ... case A.2: local accept (ATA) ... %CPI: 1,6,0,1,1,0,"+496912345678",145,,,0 ( connected call, MT, line one, traffic channel assigned ) => from here see case A.1 or A.3 ... case A.3: local reject (ATH) ... %CPI: 1,1,0,1,1,0,"+496912345678",145,,,0 ( disconnect call, MT line one, traffic channel assigned ) %CPI: 1,7,0,0,,,,,,,0 (release from network, traffic channel freed) ... case B: outgoing (MO) ... %CPI: 1,9,0,0,0,0,"+496912345678",145,,,0 ( request call, MO, line one, no traffic channel yet ) %CPI: 1,3,0,0,0,0,"+496912345678",145,,,0 ( call call, MO, line one, no traffic channel yet ) %CPI: 1,4,0,1,0,0,"+496912345678",145,,,0 ( sync call, MO, line one, traffic channel assigned ) %CPI: 1,2,1,1,0,0,"+496912345678",145,,,0 ( alert call, MO, line one, traffic channel assigned ) (at this point, it is ringing on the other side) ... case B.1: remote line rejects or sends busy... %CPI: 1,6,0,1,0,0,"+496912345678",145,,,0 ( connect call, MO, line one, traffic channel assigned ) (at this point, ATD returns w/ OK) %CPI: 1,1,0,1,0,0,"+496912345678",145,,17,0 ( disconnect call, MO, line one, traffic channel assigned ) (at this point, BUSY(17) or NO CARRIER(16) is sent) %CPI: 1,7,0,0,,,,,,,0 (release from network, traffic channel freed) ... case B.2: remote line accepts... %CPI: 1,6,0,1,0,0,"+496912345678",145,,,0 ( connect call, MO, line one, traffic channel assigned ) (at this point, ATD returns w/ OK) ... case B.3: local cancel ... ? ... case C.1: first call active, second incoming and accepted (puts first on hold) %CPI: 1,10,0,1,0,0,"+496912345678",145,,,0 %CPI: 2,6,0,1,1,0,"+496912345679",129,,,0 """ callId, msgType, ibt, tch, direction, mode, number, ntype, alpha, cause, line = safesplit( righthandside, "," ) if msgType == "10": msgType ="A" # stupid hack to have single char types #devchannel = self._object.modem.communicationChannel( "DeviceMediator" ) #devchannel.enqueue( "+CPAS;+CEER" ) info = {} # Report number, reason, and line, if available if number and ntype: info["peer"] = const.phonebookTupleToNumber( number[1:-1], int(ntype) ) if cause: info["reason"] = const.ISUP_RELEASE_CAUSE.get( int(cause), "unknown cause" ) if line: info["line"] = int( line ) # Always report the direction if direction == "0": info.update ( { "direction": "outgoing" } ) elif direction == "1": info.update ( { "direction": "incoming" } ) # Report mode if mode: info["mode"] = const.CALL_MODE.revlookup( int(mode) ) # Compute status if msgType == "0": # setup (MT) info.update ( { "status": "incoming" } ) elif msgType == "6": # connected (MO & MT) info.update( { "status": "active" } ) elif msgType == "1": # disconnected (MO & MT) # FIXME try to gather reason for disconnect? info.update( { "status": "release" } ) elif msgType == "8": # network reject (MO) info.update( { "status": "release", "reason": "no service" } ) elif msgType == "9": # request (MO) info.update( { "status": "outgoing" } ) elif msgType == "3": # Sometimes setup is not sent?! info.update( { "status": info["direction"] } ) elif msgType == "A": # hold info.update( { "status": "held" } ) if msgType in "013689A": self._callHandler.statusChangeFromNetwork( int(callId), info ) # DSP bandaid if msgType in "34": currentModem().channel( "MiscMediator" ).enqueue( createDspCommand() ) # %CPRI: 1,2 def percentCPRI( self, righthandside ): gsm, gprs = safesplit( righthandside, ',' ) cipher_gsm = const.NETWORK_CIPHER_STATUS.get( int(gsm), "unknown" ) cipher_gprs = const.NETWORK_CIPHER_STATUS.get( int(gprs), "unknown" ) self._object.CipherStatus( cipher_gsm, cipher_gprs ) # %CSSN: 1,0,A11502010802013B300D04010F0408AA510C0683C16423 def percentCSSN( self, righthandside ): direction, transPart, ie = safesplit( righthandside, "," ) # %CSTAT: PHB,0 # %CSTAT: SMS,0 # %CSTAT: RDY,1 # %CSTAT: EONS,1 def percentCSTAT( self, righthandside ): """ TI Calypso subsystem status report PHB is phonebook, SMS is messagebook. RDY is supposed to be sent, after PHB and SMS both being 1, however it's not sent on all devices. EONS is completely undocumented. Due to RDY being unreliable, we wait for PHB and SMS sending availability and then synthesize a global SimReady signal. """ subsystem, available = safesplit( righthandside, "," ) if not bool(int(available)): # not ready if subsystem in ( "PHB", "SMS" ): self.subsystemReadyness[subsystem] = False logger.info( "subsystem %s readyness now %s" % ( subsystem, self.subsystemReadyness[subsystem] ) ) if not self.fullReadyness == False: self._object.ReadyStatus( False ) self.fullReadyness = False else: # ready if subsystem in ( "PHB", "SMS" ): self.subsystemReadyness[subsystem] = True logger.info( "subsystem %s readyness now %s" % ( subsystem, self.subsystemReadyness[subsystem] ) ) newFullReadyness = self.subsystemReadyness["PHB"] and self.subsystemReadyness["SMS"] if newFullReadyness and ( not self.fullReadyness == True ): self._object.ReadyStatus( True ) self.fullReadyness = True logger.info( "full readyness now %s" % self.fullReadyness ) # %CSQ: 17, 0, 1 def percentCSQ( self, righthandside ): """ signal strength report """ strength, snr, quality = safesplit( righthandside, "," ) self._object.SignalStrength( const.signalQualityToPercentage( int(strength) ) ) # send dbus signal fso-frameworkd-0.10.1/framework/subsystems/ogsmd/server.py000066400000000000000000000251451174525413000237370ustar00rootroot00000000000000#!/usr/bin/env python """ The Open GSM Daemon - Python Implementation (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: ogsmd Module: server """ MODULE_NAME = "ogsmd.server" __version__ = "0.2.0" from framework import resource from framework.patterns import tasklet from gsm import const, celldb import dbus import dbus.service from dbus import DBusException import math import sys, os import types import logging logger = logging.getLogger( MODULE_NAME ) from device import DBUS_INTERFACE_DEVICE, \ DBUS_OBJECT_PATH_DEVICE, \ DBUS_BUS_NAME_DEVICE, \ DBUS_INTERFACE_SIM, \ DBUS_INTERFACE_NETWORK, \ DBUS_INTERFACE_PDP, \ DBUS_INTERFACE_CB DBUS_INTERFACE_DATA = "org.freesmartphone.GSM.Data" DBUS_INTERFACE_HZ = "org.freesmartphone.GSM.HZ" DBUS_INTERFACE_PHONE = "org.freesmartphone.GSM.Phone" DBUS_OBJECT_PATH_SERVER = "/org/freesmartphone/GSM/Server" HOMEZONE_DEBUG = False #=========================================================================# class Server( dbus.service.Object ): #=========================================================================# """ Open Phone Server aggregated functions: - HomeZone Ideas: - watch for clients on bus and send coldplug status - monitor device aliveness and restart, if necessary """ def __init__( self, bus ): self.path = DBUS_OBJECT_PATH_SERVER dbus.service.Object.__init__( self, bus, self.path ) logger.info( "%s %s initialized." % ( self.__class__.__name__, __version__ ) ) self.bus = bus self.homezones = None self.zone = "unknown" self.setupSignals() self._autoRegister = False self._service = "unknown" self._autoOnline = False self._online = "unknown" self.pin = None self.user = None self.password = None def setupSignals( self ): device = self.bus.get_object( DBUS_BUS_NAME_DEVICE, DBUS_OBJECT_PATH_DEVICE ) self.fso_cb = dbus.Interface( device, DBUS_INTERFACE_CB ) self.fso_cb.connect_to_signal( "IncomingCellBroadcast", self.onIncomingCellBroadcast ) self.fso_sim = dbus.Interface( device, DBUS_INTERFACE_SIM ) self.fso_network = dbus.Interface( device, DBUS_INTERFACE_NETWORK ) self.fso_network.connect_to_signal( "Status", self.onIncomingGsmNetworkStatus ) self.fso_pdp = dbus.Interface( device, DBUS_INTERFACE_PDP ) self.fso_pdp.connect_to_signal( "ContextStatus", self.onIncomingGsmContextStatus ) self.fso_device = dbus.Interface( device, DBUS_INTERFACE_DEVICE ) def __del__( self ): server = None # # Callbacks # def onIncomingCellBroadcast( self, channel, data ): def gotHomezones( homezones, self=self ): logger.info( "got SIM homezones: %s", homezones ) self.homezones = homezones # debug code, if you have no homezones on your SIM. To test, use: # gsm.DebugInjectString("UNSOL","+CBM: 16,221,0,1,1\r\n347747555093\r\r\r\n") if HOMEZONE_DEBUG: self.homezones = [ ( "city", 347747, 555093, 1000 ), ( "home", 400000, 500000, 1000 ) ] self.checkInHomezones() if channel == 221: # home zone cell broadcast if len( data ) != 12: return self.x, self.y = int( data[:6] ), int( data[6:] ) logger.info( "home zone cell broadcast detected: %s %s", self.x, self.y ) if self.homezones is None: # never tried to read them logger.info( "trying to read home zones from SIM" ) self.fso_sim.GetHomeZones( reply_handler=gotHomezones, error_handler=lambda error:None ) else: self.checkInHomezones() def checkInHomezones( self ): status = "" for zname, zx, zy, zr in self.homezones: if self.checkInHomezone( self.x, self.y, zx, zy, zr ): status = zname break self.HomeZoneStatus( status ) def checkInHomezone( self, x, y, zx, zy, zr ): logger.info( "matching whether %s %s is in ( %s, %s, %s )" % ( x, y, zx, zy, zr ) ) dist = math.sqrt( math.pow( x-zx, 2 ) + math.pow( y-zy, 2 ) ) * 10 maxdist = math.sqrt( zr ) * 10 return dist < maxdist def onIncomingGsmNetworkStatus( self, status ): reg = status["registration"] if reg in "home roaming".split(): self._updateServiceStatus( "online" ) else: self._updateServiceStatus( "offline" ) if reg in "unregistered denied".split() and self._autoRegister: self.Register( self.pin, lambda:None, lambda Foo:None ) def _updateServiceStatus( self, status ): if self._service != status: self._service = status self.ServiceStatus( status ) def replyGetContextStatus( self, status ): self.onIncomingGsmContextStatus( 0, status, {} ) def onIncomingGsmContextStatus( self, index, status, properties ): online = status if online == "active": self._updateOnlineStatus( "online" ) else: self._updateOnlineStatus( "offline" ) if online in "release".split() and self._autoOnline: self.GoOnline( self.apn, self.user, self.password, lambda:None, lambda Foo:None ) def _updateOnlineStatus( self, status ): if self._online != status: self._online = status self.OnlineStatus( status ) # # dbus org.freesmartphone.GSM.Data # @dbus.service.method( DBUS_INTERFACE_DATA, "ss", "a{sv}" ) def GetNetworkInfo( self, mcc, mnc ): return const.NETWORKS.get( ( int(mcc), int(mnc) ), {} ) # FIXME Return NaN when no cell found? Support signal strengths? @dbus.service.method( DBUS_INTERFACE_DATA, "ssa(uu)", "bddddu" ) def GetCellLocation( self, mcc, mnc, cells ): mcc = int( mcc ) mnc = int( mnc ) cells = [map(int, cell) for cell in cells] center = celldb.get_center( mcc, mnc, cells ) if center: return ( True, ) +center la = celldb.get_la( mcc, mnc, cells[0][0] ) if la: return ( True, ) + la + ( 0, ) return ( False, 0.0, 0.0, 0.0, 0.0, 0 ) # # dbus org.freesmartphone.GSM.HZ # @dbus.service.method( DBUS_INTERFACE_HZ, "", "s", async_callbacks=( "dbus_ok", "dbus_error" ) ) def GetHomeZoneStatus( self, dbus_ok, dbus_error ): dbus_ok( self.zone ) @dbus.service.signal( DBUS_INTERFACE_HZ, "s" ) def HomeZoneStatus( self, zone ): self.zone = zone logger.info( "home zone status now %s" % zone ) @dbus.service.method( DBUS_INTERFACE_HZ, "", "as", async_callbacks=( "dbus_ok", "dbus_error" ) ) def GetKnownHomeZones( self, dbus_ok, dbus_error ): def gotHomezones( homezones, self=self, dbus_ok=dbus_ok ): logger.info( "got SIM homezones: %s", homezones ) self.homezones = homezones # debug code, if you have no homezones on your SIM. To test, use: # gsm.DebugInjectString("UNSOL","+CBM: 16,221,0,1,1\r\n347747555093\r\r\r\n") if HOMEZONE_DEBUG: self.homezones = [ ( "city", 347747, 555093, 1000 ), ( "home", 400000, 500000, 1000 ) ] dbus_ok( [ zone[0] for zone in self.homezones ] ) self.fso_sim.GetHomeZones( reply_handler=gotHomezones, error_handler=lambda error:None ) # # dbus org.freesmartphone.GSM.Phone (auto register) # @dbus.service.method( DBUS_INTERFACE_PHONE, "s", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) def StartAutoRegister( self, pin, dbus_ok, dbus_error ): self.pin = pin self._autoRegister = True dbus_ok() self.fso_network.GetStatus( reply_handler=self.onIncomingGsmNetworkStatus, error_handler=lambda Foo:None ) @dbus.service.method( DBUS_INTERFACE_PHONE, "", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) def StopAutoRegister( self, dbus_ok, dbus_error ): self._autoRegister = False dbus_ok() @dbus.service.method( DBUS_INTERFACE_PHONE, "s", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) def Register( self, pin, dbus_ok, dbus_error ): @tasklet.tasklet def worker( self, pin ): try: yield tasklet.WaitDBus( self.fso_device.SetAntennaPower, True ) except dbus.DBusException, e: # may be locked if e.get_dbus_name() != "org.freesmartphone.GSM.SIM.AuthFailed": raise else: yield tasklet.WaitDBus( self.fso_sim.SendAuthCode, pin ) yield tasklet.WaitDBus( self.fso_network.Register ) worker( self, pin ).start_dbus( dbus_ok, dbus_error ) @dbus.service.signal( DBUS_INTERFACE_PHONE, "s" ) def ServiceStatus( self, service ): logger.info( "service status now %s" % service ) # # dbus org.freesmartphone.GSM.Phone (auto online) # @dbus.service.method( DBUS_INTERFACE_PHONE, "sss", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) def StartAutoOnline( self, apn, user, password, dbus_ok, dbus_error ): self.apn = apn self.user = user self.password = password self._autoOnline = True dbus_ok() self.fso_pdp.GetContextStatus( reply_handler=self.replyGetContextStatus, error_handler=lambda Foo:None ) @dbus.service.method( DBUS_INTERFACE_PHONE, "", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) def StopAutoOnline( self, dbus_ok, dbus_error ): self._autoOnline = False dbus_ok() @dbus.service.method( DBUS_INTERFACE_PHONE, "sss", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) def GoOnline( self, apn, user, password, dbus_ok, dbus_error ): self.fso_pdp.ActivateContext( apn, user, password, reply_handler=dbus_ok, error_handler=dbus_error ) @dbus.service.signal( DBUS_INTERFACE_PHONE, "s" ) def OnlineStatus( self, online ): logger.info( "online status now %s" % online ) #=========================================================================# def factory( prefix, controller ): #=========================================================================# return [ Server( controller.bus ) ] #=========================================================================# if __name__ == "__main__": #=========================================================================# pass fso-frameworkd-0.10.1/framework/subsystems/onetworkd/000077500000000000000000000000001174525413000227535ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/onetworkd/__init__.py000066400000000000000000000000001174525413000250520ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/onetworkd/dhcp.py000066400000000000000000000036551174525413000242540ustar00rootroot00000000000000#!/usr/bin/env python """ Network (C) 2009 Michael 'Mickey' Lauer GPLv2 or later Package: onetworkd Module: dhcp Support for the Dynamic Host Configuration Protocol """ MODULE_NAME = "onetworkd" __version__ = "0.0.1" from framework.patterns.utilities import killall from helpers import readFromFile, writeToFile import subprocess import logging logger = logging.getLogger( MODULE_NAME ) ETC_RESOLV_CONF = "/etc/resolv.conf" ETC_UDHCPD_CONF = "/etc/udhcpd.conf" #============================================================================# def launchDaemon(): #============================================================================# killall( "udhcpd" ) subprocess.call( "udhcpd" ) #============================================================================# def prepareDaemonConfigurationForInterface( iface ): #============================================================================# name = iface.name() address = iface.ipAddress4() nameservers = "" resolv_conf = readFromFile( ETC_RESOLV_CONF ).split( '\n' ) for line in resolv_conf: if line.startswith( "nameserver" ): nameserver = line.strip().split( ' ' )[1] nameservers += nameserver nameservers += " " conf_file = daemon_conf_file_template % ( name, nameservers, address ) writeToFile( ETC_UDHCPD_CONF, conf_file ) #============================================================================# daemon_conf_file_template = """# freesmartphone.org /etc/udhcpd.conf start 192.168.0.20 # lease range end 192.168.0.199 # lease range interface %s # listen on interface option dns %s # grab from resolv.conf option subnet 255.255.255.0 opt router %s # address of interface option lease 864000 # 10 days of seconds """ #============================================================================# fso-frameworkd-0.10.1/framework/subsystems/onetworkd/helpers.py000066400000000000000000000032221174525413000247660ustar00rootroot00000000000000from string import maketrans import logging logger = logging.getLogger('onetworkd') #============================================================================# def isValidAddress( address ): #============================================================================# parts = address.split( '.' ) if len( parts ) != 4: return False try: for part in parts: if 0 > int( part ) or 255 < int ( part ): return False return True except ValueError: return False #============================================================================# def readFromFile( path ): #============================================================================# try: value = open( path, 'r' ).read().strip() except IOError, e: logger.warning( "(could not read from '%s': %s)" % ( path, e ) ) return "N/A" else: logger.debug( "(read '%s' from '%s')" % ( value, path ) ) return value #============================================================================# def writeToFile( path, value ): #============================================================================# logger.debug( "(writing '%s' to '%s')" % ( value, path ) ) try: f = open( path, 'w' ) except IOError, e: logger.warning( "(could not write to '%s': %s)" % ( path, e ) ) else: f.write( "%s\n" % value ) #============================================================================# def cleanObjectName( name ): #============================================================================# return name.translate( trans ) trans = maketrans( "-:", "__" ) fso-frameworkd-0.10.1/framework/subsystems/onetworkd/network.py000066400000000000000000000040001174525413000250100ustar00rootroot00000000000000#!/usr/bin/env python """ Network (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: onetworkd Module: network """ MODULE_NAME = "onetworkd" __version__ = "0.0.1" import gobject import os import socket import fcntl import struct import logging logger = logging.getLogger( MODULE_NAME ) #============================================================================# class Network( dict ): #============================================================================# def __init__( self ): gobject.idle_add( self._sync ) def _sync( self ): # FIXME add listener so that this gets called whenever a change in # interfaces occurs interfaces = os.listdir( "/sys/class/net" ) # first pass: remove for interface in self: if interface not in interfaces: logger.debug( "interface %s no longer present -- removing" % interface ) del self[interface] # second pass: add for interface in os.listdir( "/sys/class/net" ): if interface not in self: logger.debug( "new interface %s -- adding" % interface ) self[interface] = Interface( interface ) return False #============================================================================# class Interface( object ): #============================================================================# def __init__( self, name ): self._name = name def name( self ): return self._name def ipAddress4( self ): s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) return socket.inet_ntoa(fcntl.ioctl( s.fileno(), 0x8915, # SIOCGIFADDR struct.pack('256s', self._name[:15]) )[20:24]) #============================================================================# theNetwork = Network() #============================================================================# if __name__ == "__main__": pass fso-frameworkd-0.10.1/framework/subsystems/onetworkd/sharing.py000066400000000000000000000100401174525413000247530ustar00rootroot00000000000000#!/usr/bin/env python """ Network Connection Sharing (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: onetworkd Module: sharing """ MODULE_NAME = "onetworkd" __version__ = "0.0.1" from network import theNetwork from helpers import isValidAddress, writeToFile import dhcp import dbus import dbus.service import gobject import os, subprocess import logging logger = logging.getLogger( MODULE_NAME ) DBUS_INTERFACE_NETWORK = "org.freesmartphone.Network" DBUS_OBJECT_PATH = "/org/freesmartphone/Network" #============================================================================# class NoInterface( dbus.DBusException ): #============================================================================# _dbus_error_name = "org.freesmartphone.Network.NoInterface" #============================================================================# class NoAddress( dbus.DBusException ): #============================================================================# _dbus_error_name = "org.freesmartphone.Network.NoAddress" #============================================================================# class InternalError( dbus.DBusException ): #============================================================================# _dbus_error_name = "org.freesmartphone.Network.InternalError" #============================================================================# class ConnectionSharing( dbus.service.Object ): #============================================================================# def __init__( self, bus ): self.path = DBUS_OBJECT_PATH dbus.service.Object.__init__( self, bus, self.path ) self.bus = bus # # dbus org.freesmartphone.Network # @dbus.service.method( DBUS_INTERFACE_NETWORK, "s", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) def StartConnectionSharingWithInterface( self, interface, dbus_ok, dbus_error ): """ This should be roughly equivalent to: #!/bin/sh iptables -I INPUT 1 -s 192.168.0.200 -j ACCEPT iptables -I OUTPUT 1 -s 192.168.0.202 -j ACCEPT iptables -A POSTROUTING -t nat -j MASQUERADE -s 192.168.0.0/24 echo 1 > /proc/sys/net/ipv4/ip_forward """ try: iface = theNetwork[ str(interface) ] except KeyError: dbus_error( NoInterface( "%s is not a valid interface. Known interfaces are %s" % ( interface, theNetwork.keys() ) ) ) return source_address = iface.ipAddress4() commands = [] commands.append( "iptables -I INPUT 1 -s 192.168.0.0/24 -j ACCEPT" ) commands.append( "iptables -I OUTPUT 1 -s %s -j ACCEPT" % source_address ) commands.append( "iptables -A POSTROUTING -t nat -j MASQUERADE -s 192.168.0.0/24" ) for command in commands: logger.debug( "issuing command '%s'" % command ) result = subprocess.call( command.split( ' ' ) ) logger.debug( "command result = %d" % result ) if result != 0: dbus_error( InternalError( "%s gave returncode %d" % ( command, result ) ) ) return writeToFile( "/proc/sys/net/ipv4/ip_forward", "1" ) dhcp.prepareDaemonConfigurationForInterface( iface ) dhcp.launchDaemon() dbus_ok() def StopConnectionSharing( self, interface, dbus_ok, dbus_error ): """ stop dhcpd daemons clear iptables """ #============================================================================# def factory(prefix, controller): #============================================================================# """This is the magic function that will be called by the framework module manager""" return [ ConnectionSharing( controller.bus ) ] #============================================================================# if __name__ == "__main__": #============================================================================# pass fso-frameworkd-0.10.1/framework/subsystems/ophoned/000077500000000000000000000000001174525413000223735ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/ophoned/__init__.py000066400000000000000000000000261174525413000245020ustar00rootroot00000000000000 # The ophoned module fso-frameworkd-0.10.1/framework/subsystems/ophoned/gsm.py000066400000000000000000000123671174525413000235440ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ The Open Phone Daemon - Python Implementation (C) 2008 Guillaume "Charlie" Chereau (C) 2008 Openmoko, Inc. GPLv2 or later Package: ophoned """ import dbus import dbus.service from dbus import DBusException from framework import helpers from protocol import Protocol, Call, ProtocolUnusable import protocol import logging logger = logging.getLogger('ophoned.gsm') class GSMProtocol(protocol.Protocol): """Phone protocol using ogsm service """ def name(self): """The name of this protocol""" return 'GSM' def __init__(self, phone): super(GSMProtocol, self).__init__(phone) # We create all the interfaces to GSM/Device try: self.gsm = phone.bus.get_object( 'org.freesmartphone.ogsmd', '/org/freesmartphone/GSM/Device', introspect=False, follow_name_owner_changes=True ) self.gsm.connect_to_signal('CallStatus', self.on_call_status) except Exception, e: raise protocol.ProtocolUnusable(e.message) self.calls_by_id = {} # This will contain a list of all the created calls @helpers.exceptionlogger def on_call_status(self, id, status, properties ): """This method is called every time ogsmd emmits a status change""" # First we convert the arguments into python values id = int(id) status = str(status).lower() logger.debug("call_status: %d %s %s", id, status, properties) # If the call is incoming, we create a new call object for this call and notify the phone service # else, we dispatch the signal to the apropriate call object if status in ['incoming', 'outgoing']: # XXX: one problem here : if no client is waiting for the event, then # The call object will never be removed. # Is there a way to check if the signal has listeners, and only create the call object if so ? if id in self.calls_by_id: logger.warning("call %d already registered", id) return peer = str(properties.get('peer', "Unknown")) call = self.CreateCall(peer) # Don't forget to register the call gsm id : call.gsm_id = id self.calls_by_id[id] = call call.on_call_status(status, properties) else: assert id in self.calls_by_id self.calls_by_id[id].on_call_status(status, properties) class Call(protocol.Call): def __init__(self, protocol, handle, peer): super(GSMProtocol.Call, self).__init__(protocol, handle, peer) self.gsm_id = None # Used to identify the call by the gsm protocole def on_call_status(self, status, properties ): status = str(status).lower() if status == 'outgoing': self.Outgoing() elif status == 'active': self.Activated() elif status == 'release': self.Released() self.status = status # We make the call asynchronous, because we can't block the framwork mainloop on it ! @dbus.service.method( 'org.freesmartphone.Phone.Call', in_signature='', out_signature='s', async_callbacks=("dbus_ok","dbus_error") ) def Initiate(self, dbus_ok, dbus_error): """Initiate the call""" super(GSMProtocol.Call, self).Initiate() def on_initiate(id): self.gsm_id = id self.protocol.calls_by_id[self.gsm_id] = self dbus_ok(self.status) self.gsm_id = self.protocol.gsm.Initiate( self.id, "voice", reply_handler = on_initiate, error_handler = dbus_error ) return '' # We make the call asynchronous, because we can't block the framwork mainloop on it ! @dbus.service.method( 'org.freesmartphone.Phone.Call', in_signature='', out_signature='s', async_callbacks=("dbus_ok","dbus_error") ) def Activate(self, dbus_ok, dbus_error): """Activate the call""" def on_activate(): dbus_ok(super(GSMProtocol.Call, self).Activate()) self.protocol.gsm.Activate( self.gsm_id, reply_handler = on_activate, error_handler = dbus_error ) return '' # We make the call asynchronous, because we can't block the framwork mainloop on it ! @dbus.service.method( 'org.freesmartphone.Phone.Call', in_signature='', out_signature='s', async_callbacks=("dbus_ok","dbus_error") ) def Release(self, dbus_ok, dbus_error): """Release the call""" def on_release(): dbus_ok(super(GSMProtocol.Call, self).Release()) if self.status != 'Released': # We add this check so that we can release a call several time self.protocol.gsm.Release(self.gsm_id, reply_handler = on_release, error_handler = dbus_error) return '' def Released(self): """Emited when the call is released""" # We can remove the call from the protocol list del self.protocol.calls_by_id[self.gsm_id] super(GSMProtocol.Call, self).Released() fso-frameworkd-0.10.1/framework/subsystems/ophoned/headset.py000066400000000000000000000175271174525413000243760ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ Open Phone Daemon - BlueZ headset interface (C) 2009 Jan 'Shoragan' Lübbe (C) 2009 Openmoko, Inc. GPLv2 or later Package: ophoned Module: headset FIXME: This is still device specific... """ __version__ = "0.9.0.0" MODULE_NAME = "ophoned.headset" import dbus, alsaaudio, gobject, subprocess import logging logger = logging.getLogger( MODULE_NAME ) class HeadsetError( dbus.DBusException ): _dbus_error_name = "org.freesmartphone.Phone.HeadsetError" class HeadsetManager( object ): def __init__( self, bus, onAnswerRequested = None, onConnectionStatus = None ): self.bus = bus self._onAnswerRequested = onAnswerRequested self._matchAnswerRequested = None self._onConnectionStatus = onConnectionStatus self._matchDisconnected = None self.address = None self.pcm_device = "hw:0,1" self.pcm_play = None self.pcm_cap = None self.connected = False self.playing = False self._kickPCM() usage = self.bus.get_object( 'org.freesmartphone.ousaged', '/org/freesmartphone/Usage', follow_name_owner_changes=True ) self.usageiface = dbus.Interface( usage, 'org.freesmartphone.Usage' ) logger.info( "usage ok: %s" % self.usageiface ) def _kickPCM( self ): try: hpcm_play = alsaaudio.PCM( alsaaudio.PCM_PLAYBACK, alsaaudio.PCM_NONBLOCK, "hw:0,0" ) hpcm_cap = alsaaudio.PCM( alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NONBLOCK, "hw:0,0" ) del hpcm_play del hpcm_cap except alsaaudio.ALSAAudioError: pass def _startPCM( self ): self._stopPCM() self.pcm_play = alsaaudio.PCM( alsaaudio.PCM_PLAYBACK, alsaaudio.PCM_NONBLOCK, self.pcm_device ) self.pcm_play.setchannels(1) self.pcm_play.setrate(8000) self.pcm_play.setformat(alsaaudio.PCM_FORMAT_S16_LE) self.pcm_play.setperiodsize(500000) self.pcm_cap = alsaaudio.PCM( alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NONBLOCK, self.pcm_device ) self.pcm_cap.setchannels(1) self.pcm_cap.setrate(8000) self.pcm_cap.setformat(alsaaudio.PCM_FORMAT_S16_LE) self.pcm_cap.setperiodsize(500000) def _stopPCM( self ): if not self.pcm_play is None: self.pcm_play.close() self.pcm_play = None if not self.pcm_cap is None: self.pcm_cap.close() self.pcm_cap = None def _connectBT( self ): bluez_manager_proxy = self.bus.get_object( "org.bluez", "/", ) self.bluez_manager = dbus.Interface( bluez_manager_proxy, "org.bluez.Manager", ) bluez_adapter_proxy = self.bus.get_object( "org.bluez", self.bluez_manager.DefaultAdapter(), ) self.bluez_adapter = dbus.Interface( bluez_adapter_proxy, "org.bluez.Adapter", ) bluez_device_proxy = self.bus.get_object( "org.bluez", self.bluez_adapter.FindDevice( self.address ), ) self.bluez_device_headset = dbus.Interface( bluez_device_proxy, "org.bluez.Headset", ) self.bluez_device_headset.Connect( reply_handler=self.cbHeadsetConnetReply, error_handler=self.cbHeadsetConnectError ) def cbHeadsetConnectError( self, e ): if e.get_dbus_name() == "org.bluez.Error.AlreadyConnected": self.cbHeadsetConnetReply() else: logger.info( "BT connect error" ) raise def cbHeadsetConnetReply( self ): logger.info( "BT connect ok" ) if self._onAnswerRequested: self._matchAnswerRequested = self.bluez_device_headset.connect_to_signal( 'AnswerRequested', self._onAnswerRequested ) self._matchDisconnected = self.bluez_device_headset.connect_to_signal( 'Disconnected', self._onDisconnected ) self.connected = True if self._onConnectionStatus: self._onConnectionStatus( self.connected ) def _startBT( self ): try: self.bluez_device_headset.Play() except dbus.exceptions.DBusException, e: if e.get_dbus_name() == "org.bluez.Error.AlreadyConnected": pass else: raise def _stopBT( self ): self.bluez_device_headset.Stop() def _disconnectBT( self ): if self._matchAnswerRequested: self._matchAnswerRequested.remove() self._matchAnswerRequested = None if self._matchDisconnected: self._matchDisconnected.remove() self._matchDisconnected = None # if disconnect fails for any reason, we # still cancel all BT, such that the audio # will get routed back to the headset try: self.bluez_device_headset.Disconnect() except: pass self.bluez_device_headset = None self.bluez_adapter = None self.bluez_manager = None self.connected = False if self._onConnectionStatus: self._onConnectionStatus( self.connected ) def _onDisconnected( self ): self._disconnectBT() logger.info( "got disconnected" ) if self.address: self.monitor = gobject.timeout_add_seconds( 10, self._handleMonitorTimeout ) def _updateConnected( self ): if self.address and not self.connected: self._connectBT() def _handleMonitorTimeout( self ): try: self._updateConnected() except: logger.debug( "_handleMonitorTimeout failed:", exc_info=True ) if self.address and not self.connected: return True else: return False def setAddress( self, address ): if self.address != address: if self.connected: self.setPlaying( False ) self._disconnectBT() if self.address and not address: self.usageiface.ReleaseResource( "Bluetooth", reply_handler=self.cbReleaseReply, error_handler=self.cbReleaseError, ) if not self.address and address: try: self.usageiface.RequestResource( "Bluetooth", reply_handler=self.cbRequestReply, error_handler=self.cbRequestError, ) except: pass self.monitor = gobject.timeout_add_seconds( 10, self._handleMonitorTimeout ) self.address = address def cbRequestReply( self ): logger.info( "Requested Bluetooth" ) def cbRequestError( self, e ): log_dbus_error( e, "error while requesting Bluetooth" ) logger.info( "Requested Bluetooth with error" ) def cbReleaseReply( self ): logger.info( "Released Bluetooth" ) def cbReleaseError( self, e ): log_dbus_error( e, "error while releasing Bluetooth" ) logger.info( "Released Bluetooth with error" ) def getConnected( self ): return self.connected def setPlaying( self, playing ): if not self.playing and playing: if not self.connected: raise HeadsetError("No connected") self._startPCM() self._startBT() self.playing = True elif self.playing and not playing: self._stopBT() self._stopPCM() self.playing = False def getPlaying( self ): return self.playing if __name__=="__main__": m = HeadsetManager( dbus.SystemBus() ) fso-frameworkd-0.10.1/framework/subsystems/ophoned/ophoned.py000066400000000000000000000207731174525413000244120ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ The Open Phone Daemon - Python Implementation (C) 2008 Guillaume "Charlie" Chereau (C) 2008 Openmoko, Inc. GPLv2 or later Package: ophoned Implementation of the dbus objects. """ MODULE_NAME = "ophoned" __version__ = "0.1.0.1" from framework import helpers from framework.controller import Controller from framework.patterns import dbuscache from protocol import Protocol, ProtocolUnusable from test import TestProtocol from gsm import GSMProtocol from headset import HeadsetManager import dbus import dbus.service from dbus import DBusException import gobject import logging logger = logging.getLogger( MODULE_NAME ) class Phone(dbus.service.Object): """The Phone object is used to create Call objects using different protocols""" def __init__(self, bus): # Those two attributes are needed by the framwork self.bus = bus self.path = '/org/freesmartphone/Phone' self.interface = "org.freesmartphone.Phone" super(Phone, self).__init__(bus, self.path) self.protocols = {} self.active_call = None self.gsm = bus.get_object( 'org.freesmartphone.ousaged', '/org/freesmartphone/Usage', introspect = False, follow_name_owner_changes = True ) # FIXME # FxH disbled because no clients are there yet to remove to call objects (see remark in gsm.py) # Moreover, the removal of the GSM protocol when suspending causes exceptions after resume (stale references?) # self.gsm.connect_to_signal( 'ResourceChanged', self.on_resource_changed ) self.headset = HeadsetManager( self.bus, self.on_bt_answer_requested, self.on_bt_connection_status ) gobject.idle_add( self.on_startup ) def on_startup( self ): opreferencesd = Controller.object( "/org/freesmartphone/Preferences" ) self.preferences = opreferencesd.GetService( "phone" ) address = self.preferences.GetValue( "bt-headset-address" ) self.headset.setAddress( address ) self.bus.add_signal_receiver( self.on_preferences_changed, 'Notify', 'org.freesmartphone.Preferences.Service', 'org.freesmartphone.opreferencesd', '/org/freesmartphone/Preferences/phone' ) def on_preferences_changed (self, key, value ): if key == "bt-headset-address": self.headset.setAddress( value ) def on_resource_changed( self, resourcename, state, attributes ): if resourcename == "GSM": if state and not "GSM" in self.protocols: self.protocols["GSM"] = GSMProtocol( self ) elif not state and "GSM" in self.protocols: self.protocols["GSM"].fini() del self.protocols["GSM"] def on_bt_answer_requested( self ): logger.info( "BT-Headset: AnswerRequested (active call = %s)", self.active_call ) if self.active_call: if self.active_call.GetStatus() in ['incoming']: self.Accept( dbus_ok = helpers.drop_dbus_result, dbus_error = helpers.log_dbus_error ) else: self.Hangup( dbus_ok = helpers.drop_dbus_result, dbus_error = helpers.log_dbus_error ) def on_bt_connection_status( self, connected ): logger.info("BT-Headset: ConnectionStatus = %s", connected) self.BTHeadsetConnected( connected ) @dbus.service.method('org.freesmartphone.Phone', in_signature='ssb', out_signature='o') def CreateCall(self, number, protocol = None, force = True): """ Return a new Call targeting the given number, with an optional protocol. If the protocol is not provided, the service will determine the best protocol to use. if force is set to true, then we kill the channel if it is already opened parameters: number -- A string representing the number of the peer protocol -- The name of the protocol as returned by InitProtocols, if None the best protocol will be used. Default to None force -- If true, we destroy any already present call object to this number. Default to True """ if self.protocols is None: self.InitProtocols() number = str(number) # first we guess the best protocol to use if protocol: protocol = self.protocols[str(protocol)] else: # Here we need to guess the best protocol for the number protocol = self.protocols["Test"] # Then we just ask the protocol class ret = protocol.CreateCall(number, force = force) return ret @dbus.service.method('org.freesmartphone.Phone', in_signature='b', out_signature='') def SetBTHeadsetPlaying( self, playing ): self.headset.setPlaying( playing ) @dbus.service.signal('org.freesmartphone.Phone', signature='b') def BTHeadsetConnected(self, connected): pass # FIXME handle multiple calls correctly @dbus.service.method( 'org.freesmartphone.Phone', in_signature='', out_signature='', async_callbacks=("dbus_ok","dbus_error") ) def Accept(self, dbus_ok, dbus_error): logger.info( "Accept (active call = %s)", self.active_call ) if self.active_call: self.active_call.Activate( dbus_ok = dbus_ok, dbus_error = dbus_error ) @dbus.service.method( 'org.freesmartphone.Phone', in_signature='', out_signature='', async_callbacks=("dbus_ok","dbus_error") ) def Hangup(self, dbus_ok, dbus_error): logger.info( "Hangup (active call = %s)", self.active_call ) if self.active_call: self.active_call.Release( dbus_ok = dbus_ok, dbus_error = dbus_error ) @dbus.service.signal('org.freesmartphone.Phone', signature='o') def CallCreated(self, call): """Emitted when a new call has been created""" logger.info( "CallCreated (%s)", call ) self.active_call = call @dbus.service.signal('org.freesmartphone.Phone', signature='o') def CallReleased(self, call): """Emitted when a call has been released""" logger.info( "CallReleased (%s)", call ) if self.active_call == call: self.active_call = None def factory(prefix, controller): """This is the magic function that will be called bye the framework module manager""" try: # We use a try because the module manager ignores the exceptions in the factory phone = Phone(controller.bus) return [phone] except Exception, e: # XXX: remove that logger.error("%s", e) # Just so that if an exception is raised, we can at least see the error message raise def generate_doc(): """This function can be used to generate a wiki style documentation for the DBus API It should be replaced by doxygen """ from protocol import Call objects = [Phone, Call] services = {} for obj in objects: for attr_name in dir(obj): attr = getattr(obj, attr_name) if hasattr(attr, '_dbus_interface'): if hasattr(attr, '_dbus_is_method'): func = {} func['name'] = attr_name func['args'] = ','.join(attr._dbus_args) func['in_sig'] = attr._dbus_in_signature func['out_sig'] = attr._dbus_out_signature func['doc'] = attr.__doc__ funcs, sigs = services.setdefault(attr._dbus_interface, [[],[]]) funcs.append(func) if hasattr(attr, '_dbus_is_signal'): sig = {} sig['name'] = attr_name sig['args'] = ','.join(attr._dbus_args) sig['sig'] = attr._dbus_signature sig['doc'] = attr.__doc__ funcs, sigs = services.setdefault(attr._dbus_interface, [[],[]]) sigs.append(sig) for name, funcs in services.items(): print '= %s =' % name for func in funcs[0]: print """ == method %(name)s(%(args)s) == * in: %(in_sig)s * out: %(out_sig)s * %(doc)s""" % func for sig in funcs[1]: print """ == signal %(name)s(%(args)s) == * out: %(sig)s * %(doc)s""" % sig print if __name__ == '__main__': import sys generate_doc() sys.exit(0) import gobject import dbus.mainloop.glib dbus.mainloop.glib.DBusGMainLoop( set_as_default=True ) mainloop = gobject.MainLoop() bus = dbus.SystemBus() name = dbus.service.BusName("org.freesmartphone.ophoned", bus) phone = Phone(bus) mainloop.run() fso-frameworkd-0.10.1/framework/subsystems/ophoned/protocol.py000066400000000000000000000122731174525413000246130ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ The Open Phone Daemon - Python Implementation (C) 2008 Guillaume "Charlie" Chereau (C) 2008 Openmoko, Inc. GPLv2 or later Package: ophoned """ import dbus import dbus.service from dbus import DBusException import logging logger = logging.getLogger('ophoned.protocol') class ProtocolMetaClass(type): """The meta class for Protocole class We use this meta class to keep track of all the Protocols instances """ def __init__(cls, name, bases, dict): super(ProtocolMetaClass, cls).__init__(name, bases, dict) if bases[0] != object: # We don't want to register the base Protocol class Protocol.subclasses[name] = cls class ProtocolUnusable(Exception): """This exception can be raised in the __init__ method of any protocol to indicate that it can't be used""" def __init__(self, msg = None): super(ProtocolUnusable, self).__init__(msg) class Protocol(object): """Represent a phone call protocol To create a new protocol just subclass this class One instance of each protocol will be created by the meta class at import time. Every Protocol class should define an inner Call class """ __metaclass__ = ProtocolMetaClass subclasses = {} # Contain all the subclasses of Protocol def name(self): raise NotImplemented def __init__(self, phone): logger.info("creating protocol %s", self.name() ) self.phone = phone self.path = "/org/freesmartphone/Phone/%s" % self.name() self.next_call_handle = 0 self.calls = {} # We keep a map : number -> call for every calls def fini( self ): logger.info("removing protocol %s", self.name() ) def CreateCall(self, peer): """Return a new chanel targeting the given number if force is True and a call on this number is already present, then the call will be removed """ # Every Protocl class need to define an inner Call class call = self.__class__.Call(self, self.next_call_handle, peer) self.calls[call.handle] = call self.next_call_handle += 1 return call def remove(self, call): """This mehtod is called when a call need to be removed""" del self.calls[call.handle] class Call(dbus.service.Object): """A Call object represents a communication channel""" def __init__(self, protocol, handle, peer): """Create a new Call arguments: protocol -- The protocol object we use for this call handle -- A unique handle for the call """ self.path = "%s/%s" % (protocol.path, handle) super(Call, self).__init__(protocol.phone.bus, self.path) self.protocol = protocol self.handle = handle self.peer = peer # TODO: change the name to number, because in fact it is exactly that self.status = 'Idle' self.protocol.phone.CallCreated(self) @dbus.service.method('org.freesmartphone.Phone.Call', in_signature='', out_signature='s') def GetPeer(self): """Return the number of the peer (usually the number of the call)""" return self.peer @dbus.service.method('org.freesmartphone.Phone.Call', in_signature='', out_signature='s') def Initiate(self): """Initiate the call The call will be effectively initiated when we receive the 'Activated' Signal """ self.status = 'Initiating' return self.status @dbus.service.method( 'org.freesmartphone.Phone.Call', in_signature='', out_signature='s', async_callbacks=("dbus_ok","dbus_error") ) def Activate(self, dbus_ok, dbus_error): """Accept the call""" self.status = 'Activating' dbus_ok(self.status) @dbus.service.method( 'org.freesmartphone.Phone.Call', in_signature='', out_signature='s', async_callbacks=("dbus_ok","dbus_error") ) def Release(self, dbus_ok, dbus_error): """Release the call""" self.status = 'Releasing' dbus_ok(self.status) @dbus.service.method('org.freesmartphone.Phone.Call', in_signature='', out_signature='s') def GetStatus(self): """Return the current status of the call""" return self.status @dbus.service.method('org.freesmartphone.Phone.Call', in_signature='', out_signature='') def Remove(self): """Remove the call object when it is not needed anymore After the call has been removed, its DBus object is released, so we can't receive events from it anymore """ self.remove_from_connection() self.protocol.remove(self) @dbus.service.signal('org.freesmartphone.Phone.Call', signature='') def Outgoing(self): """Emitted when the call status changes to Outgoing""" self.status = 'Outgoing' @dbus.service.signal('org.freesmartphone.Phone.Call', signature='') def Released(self): """Emitted when the call status changes to Released""" self.status = 'Released' self.protocol.phone.CallReleased(self) @dbus.service.signal('org.freesmartphone.Phone.Call', signature='') def Activated(self): """Emitted when the call status changes to Active""" self.status = 'Active' fso-frameworkd-0.10.1/framework/subsystems/ophoned/test.py000066400000000000000000000033251174525413000237270ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ The Open Phone Daemon - Python Implementation (C) 2008 Guillaume "Charlie" Chereau (C) 2008 Openmoko, Inc. GPLv2 or later Package: ophoned Implementation of the dbus objects. """ import gobject import protocol class TestProtocol(protocol.Protocol): """This special protocol is just used to emulate a real call """ def name(self): return 'Test' def __init__(self, bus): super(TestProtocol, self).__init__(bus) class Call(protocol.Call): def __init__(self, proto, handle, peer): super(TestProtocol.Call, self).__init__(proto, handle, peer) def Initiate(self): """Initiate the call""" # Now since this is a test we simulate outgoing even after a short time... def on_timeout(*args): if self.status == 'Initiating': self.Outgoing() gobject.timeout_add(1000, on_timeout) return super(TestProtocol.Call, self).Initiate() def Outgoing(self): """Emited when the call is outgoing""" super(TestProtocol.Call, self).Outgoing() # Since this is a test, after a while we activate the call def on_timeout(*args): if self.status == 'Outgoing': self.Activated() gobject.timeout_add(1000, on_timeout) def Release(self): """Release the call""" # Since this is a test, after a while we release the call def on_timeout(*args): if self.status == 'Releasing': self.Released() gobject.timeout_add(1000, on_timeout) return super(TestProtocol.Call, self).Release() fso-frameworkd-0.10.1/framework/subsystems/opimd/000077500000000000000000000000001174525413000220475ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/opimd/TODO000066400000000000000000000041221174525413000225360ustar00rootroot00000000000000* Disable "no-buffering" mode and start using "commit" when a write is wanted instead of all the time. Might fix the corruptions. * Fix FIXMEs * Add a BLOB type * Fix bug that Adding a new missed call doesn't check if it's really New, or just an already read one added later. * Remove the EntryUpdated and UpdatedEntry signals, that is, keep only one. (Not sure it's needed, must check) * Make more distinct differenation between data and validation. ATM they are mixed (faster), but this shouldn't be that much of an overhead anyway, since there are only a small number of "items" in a query. * Clean up all the old relics * Support "connecting" backends maybe using @DOMAIN, i.e @Contacts will be a field containing a dictionary with (list containing dicotinaries when needed?) the matching contact should find a way to say how to match? Or maybe @DOMAIN_field = true? and compare is by using @DOMAIN = compare_field. Probably best would be @DOMAIN = {} (i.e a dict with stuff) * When connecting domains should support restriction based on the connected domain. * Add calculated fields, either make it possible to request type and have a default "generator" for it, or make more complex stuff. - Only on query, can't compare. * Add more logical conditioning like putting a list of conditions a field called %or will mean they are connected with or, same with %and. * Should prefixing a field with ! mean not? * Discuss breaking API? for instance Peer instead of Sender/Recipient etc * Make queries save ids, not full entries * Fix missing check_single_entry * FSO handlers, add a timer to check again if the framework is even loaded (when opimd starts before the framework) * Clean internal use of path, move completely to Id internally * Add auto reload of configs * When updating Queries, we should do something fancier, since if the query is a "connected" query, we should do a lot to make sure everything works. * Add GetComplexContent for do complex connections (i.e number resolving) for a single contact. * Add signals about fields addition/deletion. -- Tasks * and Tasks is Contacts + UnfinishedTasks support :P fso-frameworkd-0.10.1/framework/subsystems/opimd/__init__.py000077500000000000000000000000001174525413000241510ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/opimd/db/000077500000000000000000000000001174525413000224345ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/opimd/db/upgrade-2.1.sql000066400000000000000000000010331174525413000250770ustar00rootroot00000000000000-- delete deprecated MessageRead values DELETE FROM messages_boolean WHERE (field_name = 'MessageRead' or field_name = 'MessageSent') AND value = 1; -- invert MessageRead and MessageSent to be used as New UPDATE messages_boolean SET value = NOT value WHERE field_name = 'MessageRead' OR field_name = 'MessageSent'; -- rename MessageRead and MessageSent to New UPDATE messages_boolean SET field_name = 'New' WHERE field_name = 'MessageRead' OR field_name = 'MessageSent'; -- update version info REPLACE INTO info VALUES('version', '2.1'); fso-frameworkd-0.10.1/framework/subsystems/opimd/db_handler.py000066400000000000000000000611451174525413000245120ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Openmoko PIM Daemon # SQLite Backend Plugin # # http://openmoko.org/ # # Copyright (C) 2009 by Thomas "Heinervdm" Zimmermann (zimmermann@vdm-design.de) # Sebastian dos Krzyszkowiak (seba.dos1@gmail.com) # Tom "TAsn" Hacohen (tom@stosb.com) # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # """opimd SQLite Backend Plugin""" import os import sqlite3 import logging logger = logging.getLogger('opimd') from dbus import Array import dbus from domain_manager import DomainManager from type_manager import TypeManager from helpers import * import framework.patterns.tasklet as tasklet from framework.config import config, rootdir import re import db_upgrade try: import phoneutils from phoneutils import normalize_number #Old versions do not include compare yet try: from phoneutils import numbers_compare except: logger.info("Can't get phoneutils.numbers_compare, probably using an old libphone-utils, creating our own") raise phoneutils.init() except: #Don't create a compare function so it won't try to look using index def normalize_number(a): return a def numbers_compare(a, b): a = normalize_number(str(a)) b = normalize_number(str(b)) return cmp(a, b) #I use ints because there's no boolean in sqlite def regex_matches(string, pattern): try: if re.search(unicode(pattern), unicode(string)) == None: return 0 return 1 except Exception, exp: logger.error("While matching regex (pattern = %s, string = %s) got: %s",unicode(pattern), unicode(string), exp) return 0 def dict_factory(description, row, skip_field = None): """Used for creating column-based dictionaries from simple resultset rows (ie lists)""" d = {} for idx, col in enumerate(description): if col[0] != skip_field: d[col[0]] = row[idx] return d rootdir = os.path.join( rootdir, 'opim' ) _SQLITE_FILE_NAME = os.path.join(rootdir,'pim.db') class DbHandler(object): con = None db_prefix = "generic" #FIXME: should change both to sets instead of lists tables = None table_types = None def __init__(self): self.tables = [] if self.table_types == None: self.table_types = [] #A list of all the basic types that deserve a table, maybe in the future # group the rest by sql type self.table_types.extend(['entryid', 'generic']) self.init_db() def __repr__(self): return self.name def __del__(self): self.con.commit() self.con.close() def init_db(self): try: new_db = not os.path.isfile(_SQLITE_FILE_NAME) self.con = sqlite3.connect(_SQLITE_FILE_NAME, isolation_level=None) self.con.text_factory = sqlite3.OptimizedUnicode self.con.create_collation("compare_numbers", numbers_compare) self.con.create_function("regex_matches", 2, regex_matches) cur = self.con.cursor() cur.execute(""" CREATE TABLE IF NOT EXISTS info ( field_name TEXT PRIMARY KEY, value TEXT NOT NULL ) """) if new_db: cur.execute("INSERT INTO info VALUES('version', ?)", (db_upgrade.DB_VERSIONS[-1], )) self.con.commit() cur.close() except Exception, exp: logger.error("""The following errors occured when trying to init db: %s\n%s""", _SQLITE_FILE_NAME, str(exp)) raise def create_db(self): try: cur = self.con.cursor() check, version = db_upgrade.check_version(cur) if check == db_upgrade.DB_UNSUPPORTED: raise Exception("Unsupported database version %s" % (version)) elif check == db_upgrade.DB_NEEDS_UPGRADE: db_upgrade.upgrade(version, cur, self.con) self.con.commit() #Creates basic db structue (tables and basic indexes) more complex #indexes should be done per backend cur.executescript(""" CREATE TABLE IF NOT EXISTS """ + self.db_prefix + """ ( """ + self.db_prefix + """_id INTEGER PRIMARY KEY, name TEXT ); CREATE TABLE IF NOT EXISTS """ + self.db_prefix + """_fields ( field_name TEXT PRIMARY KEY, type TEXT ); CREATE INDEX IF NOT EXISTS """ + self.db_prefix + """_fields_field_name ON """ + self.db_prefix + """_fields(field_name); CREATE INDEX IF NOT EXISTS """ + self.db_prefix + """_fields_type ON """ + self.db_prefix + """_fields(type); """) self.tables = [] for type in self.table_types: cur.executescript("CREATE TABLE IF NOT EXISTS " + \ self.db_prefix + "_" + type + \ " (" + self.db_prefix + "_" + type + "_id INTEGER PRIMARY KEY," \ + self.db_prefix + \ "_id REFERENCES " + self.db_prefix + \ "(" + self.db_prefix + "_id), field_name TEXT, value " + \ self.get_db_type_name(type) + " NOT NULL);" + \ "CREATE INDEX IF NOT EXISTS " + \ self.db_prefix + "_" + type + "_" + self.db_prefix + \ "_id ON " + self.db_prefix + "_" + type + \ "(" + self.db_prefix + "_id);" ) self.tables.append(self.db_prefix + "_" + type) cur.execute(self.get_create_type_index(type)) self.con.commit() cur.close() except Exception, exp: logger.error("""The following errors occured when trying to create db: %s\n%s""", _SQLITE_FILE_NAME, str(exp)) raise def get_create_type_index(self, type): if type == "phonenumber": return "CREATE INDEX IF NOT EXISTS " + self.db_prefix + "_" + type + \ "_value ON " + self.db_prefix + "_" + type + "(value COLLATE compare_numbers)" return "" def get_table_name(self, field): if self.domain.is_reserved_field(field): return None type = self.domain.field_type_from_name(field) table = self.get_table_name_from_type(type) if table: return table else: return self.db_prefix + '_generic' def get_table_name_from_type(self, type): name = self.db_prefix + "_" + type if name in self.tables: return name else: return None def get_value_compare_string(self, type, field, operator): if type == "phonenumber" or TypeManager.Types.get(type) in (int, float, long, bool): return " value " + operator + " ? " else: #FIXME: raise error if operator is not '=' if (operator == '!=' or operator == "="): return " regex_matches(value, ?) "+operator+" 1 " def get_value_compare_object(self, type, field, value): if type == "phonenumber": return normalize_number(str(value)) return self.get_value_object(type, field, value) def get_value_object(self, type, field, value): #FIMXE use field if type in TypeManager.Types: return TypeManager.Types[type](value) else: return str(value) return str(value) def get_db_type_name(self, type): if type == 'phonenumber': return "TEXT COLLATE compare_numbers" python_type = TypeManager.Types.get(type) if python_type in (int, long, bool): return "INTEGER" elif python_type == float: return "REAL" elif python_type in (str, unicode): return "TEXT" else: return "TEXT" def build_retrieve_query(self, join_parameters): query = "" not_first = False for table in self.tables: if not_first: query = query + " UNION " not_first = True query = query + "SELECT field_name, value FROM " + table + \ " WHERE " + self.db_prefix + "_id=:id" #FIXME: sholud be a nice hash table and not a boolean if table == self.db_prefix + "_phonenumber" and join_parameters.get('resolve'): query = query + " UNION SELECT '@Contacts', contacts_id FROM " \ + table + " JOIN contacts_phonenumber USING (value)" \ + " WHERE " + self.db_prefix + "_id=:id " return query def build_search_query(self, query_desc): """Recieves a dictionary and makes an sql query that returns all the id's of those who meet the dictionaries restrictions""" params = [] not_first = False if '_at_least_one' in query_desc: table_join_operator = " UNION " else: table_join_operator = " INTERSECT " query = "" for name, value in query_desc.iteritems(): #skip system fields if name.startswith('_'): #FIXME: put this in a central place! if name not in ('_at_least_one', '_sortdesc', '_sortby', '_limit', '_limit_start', '_resolve_phonenumber', '_retrieve_full_contact'): raise InvalidField("Query rule '%s' does not exist." % (name, )) else: continue elif name.startswith('@'): if name[1:] not in DomainManager.get_domains(): raise InvalidField("Domain '%s' does not exist." % (name[1:], )) else: continue if not_first: query = query + table_join_operator not_first = True #handle type searching if name.startswith('<') or name.startswith('>'): pos = 1 if (name[1] == '='): pos = 2 operator = name[:pos] name = name[pos:] elif name.startswith('!'): operator = '!=' name = name[1:] else: operator = '=' if name.startswith('$'): field_type = name[1:] table = self.get_table_name_from_type(field_type) if not table: raise InvalidField("Type '%s' does not exist." % (field_type, )) query = query + "SELECT DISTINCT " + self.db_prefix + "_id FROM " + \ table + " WHERE (" else: field_type = self.domain.field_type_from_name(name) table = self.get_table_name(name) if not table: raise InvalidField("Field '%s' is reserved for internal use." % (name, )) query = query + "SELECT DISTINCT " + self.db_prefix + "_id FROM " + \ table + " WHERE field_name = ? AND (" params.append(str(name)) #If multi values, make OR connections comp_string = self.get_value_compare_string(field_type, name, operator) if type(value) == Array or type(value) == list: first_val = True for val in value: if first_val: first_val = False else: query = query + " OR " query = query + comp_string params.append(self.get_value_compare_object(field_type, name, val)) else: query = query + comp_string params.append(self.get_value_compare_object(field_type, name, value)) query = query + ")" #If there are no restrictions get everything if query == "": query = "SELECT " + self.db_prefix + "_id FROM " + self.db_prefix if '_sortby' in query_desc: sortby = query_desc['_sortby'] query = "SELECT DISTINCT " + self.db_prefix + "_id FROM (" + query + \ ") JOIN " + self.get_table_name(sortby) + " USING (" + \ self.db_prefix + "_id) WHERE field_name = ? ORDER BY value" params.append(sortby) if '_sortdesc' in query_desc: query = query + " DESC" limit_start = 0 if '_limit_start' in query_desc: try: limit_start = int(query_desc['_limit_start']) except: raise InvalidField("_limit_start should be an integer value") limit_end = -1 if '_limit' in query_desc: try: limit_end = int(query_desc['_limit']) except: raise InvalidField("_limit should be an integer value") if (limit_start != 0 or limit_end != -1): query = query + " LIMIT ?,?" params.extend([limit_start, limit_end]) return {'Query':query, 'Parameters':params} def build_sql_query(self, query_desc): """Modify a raw SQL query with some others rules.""" query = query_desc['sql'] params = [] for name, value in query_desc.iteritems(): #skip system fields if name.startswith('_'): #FIXME: put this in a central place! if name not in ('_limit', '_limit_start', '_resolve_phonenumber', '_retrieve_full_contact'): raise InvalidField("Query rule '%s' does not exist." % (name, )) else: continue elif name.startswith('@'): if name[1:] not in DomainManager.get_domains(): raise InvalidField("Domain '%s' does not exist." % (name[1:], )) else: continue limit_start = 0 if '_limit_start' in query_desc: try: limit_start = int(query_desc['_limit_start']) except: raise InvalidField("_limit_start should be an integer value") limit_end = -1 if '_limit' in query_desc: try: limit_end = int(query_desc['_limit']) except: raise InvalidField("_limit should be an integer value") if (limit_start != 0 or limit_end != -1): query = "SELECT * FROM (" + query + ") LIMIT ?,?" params.extend([limit_start, limit_end]) return {'Query':query, 'Parameters':params} def sanitize_result(self, raw): map = {} for (field, name) in raw: if field in map: if type(map[field]) == list: map[field].append(name) else: map[field] = [map[field], name] else: map[field] = name return map def get_full_result(self, raw_result, join_parameters, description = None): if raw_result == None: return None #convert from a list of tuples of ids to a list of ids ids = map(lambda x: x[0], raw_result) # if we have 'description' we can pass other columns to get_content() # to be included in the returned result set through dbus response if description: try: columns = map(lambda x: x[0], cursor.description) skip_field = columns[0] except: skip_field = None other_fields = map(lambda x: dict_factory(description, x, skip_field), raw_result) else: other_fields = [] return self.get_content(ids, join_parameters, other_fields) def query(self, query_desc): #FIXME: join_parametrs should be cool, and not just a simple hash join_parameters = {} query = self.build_search_query(query_desc) if query == None: logger.error("Failed creating search query for %s", str(query_desc)) raise QueryFailed("Failed creating search query.") if query_desc.get('_resolve_phonenumber'): join_parameters['resolve'] = True if query_desc.get('_retrieve_full_contact'): join_parameters['full'] = True cur = self.con.cursor() cur.execute(query['Query'], query['Parameters']) res = self.get_full_result(cur.fetchall(), join_parameters, cur.description) cur.close() return res def raw_sql(self, query_desc): #FIXME: join_parametrs should be cool, and not just a simple hash join_parameters = {} query = self.build_sql_query(query_desc) if query == None: logger.error("Failed creating threads query for %s", str(query_desc)) raise QueryFailed("Failed creating threads query.") if query_desc.get('_resolve_phonenumber'): join_parameters['resolve'] = True if query_desc.get('_retrieve_full_contact'): join_parameters['full'] = True cur = self.con.cursor() cur.execute(query['Query'], query['Parameters']) res = self.get_full_result(cur.fetchall(), join_parameters, cur.description) cur.close() return res def get_content(self, ids, join_parameters, other_fields = []): cur = self.con.cursor() res = [] query = self.build_retrieve_query(join_parameters) row_index = 0 for id in ids: cur.execute(query, {'id': id}) tmp = self.sanitize_result(cur.fetchall()) #FIXME: Here we check for @Contacts, but we should handle crazier joins. if join_parameters.get('full') and tmp.has_key('@Contacts'): contact_domain = DomainManager.get_domain_handler('Contacts') if type(tmp.get('@Contacts')) != list: #make it a list for easier handling tmp['@Contacts'] = [tmp['@Contacts'],] tmp['@Contacts'] = map(lambda x: dbus.Dictionary(x, signature='sv'), contact_domain.db_handler.get_content(tmp['@Contacts'], {})) if len(tmp['@Contacts']) == 1: tmp['@Contacts'] = tmp['@Contacts'][0] #get full contact content! pass tmp['Path'] = self.domain.id_to_path(id) tmp['EntryId'] = id # include any other custom field from query try: for field, value in other_fields[row_index].iteritems(): tmp[field] = value except IndexError: pass row_index += 1 res.append(tmp) cur.close() return res def add_field_type(self, name, type): cur = self.con.cursor() cur.execute("INSERT INTO " + self.db_prefix + "_fields (field_name, type) " \ "VALUES (?, ?)", (name, type)) if self.get_table_name(name) != self.db_prefix + "_generic": cur.execute("INSERT INTO " + self.get_table_name(name) + " (" + self.db_prefix + "_id, field_name, value)" + \ " SELECT " + self.db_prefix + "_id, field_name, value FROM " + self.db_prefix + "_generic" + \ " WHERE field_name = ?;", (name, )) cur.execute("DELETE FROM " + self.db_prefix + "_generic WHERE field_name = ?;" , (name, )) self.con.commit() cur.close() def remove_field_type(self, name): cur = self.con.cursor() cur.execute("DELETE FROM " + self.db_prefix + "_fields WHERE field_name = ?", (name, )) if self.get_table_name(name) != self.db_prefix + "_generic": cur.execute("INSERT INTO " + self.db_prefix + "_generic (" + self.db_prefix + "_id, field_name, value)" + \ " SELECT " + self.db_prefix + "_id, field_name, value FROM " + self.get_table_name(name) + \ " WHERE field_name = ?;", (name, )) cur.execute("DELETE FROM " + self.get_table_name(name) + " WHERE field_name = ?;" , (name, )) self.con.commit() cur.close() def load_field_types(self): cur = self.con.cursor() raw_res = cur.execute("SELECT * FROM " + self.db_prefix + "_fields").fetchall() cur.close() res = {} for row in raw_res: res[row[0]] = row[1] return res def entry_exists(self, id): cur = self.con.cursor() cur.execute('SELECT ' + self.db_prefix + '_id FROM ' + self.db_prefix + ' WHERE ' + self.db_prefix + '_id = ?', (id, )) count = cur.rowcount cur.close() return (count > 0) def add_entry(self, entry_data): cur = self.con.cursor() cur.execute("INSERT INTO " + self.db_prefix + " (name) VALUES('')") eid = cur.lastrowid for field in entry_data: table = self.get_table_name(field) field_type = self.domain.field_type_from_name(field) if table == None: continue if type(entry_data[field]) == Array or type(entry_data[field]) == list: for value in entry_data[field]: if value != "" and value != None: cur.execute('INSERT INTO ' + table + ' (' + self.db_prefix + '_id, Field_name, Value) VALUES (?,?,?)', (eid, field, self.get_value_object(field_type, field, value))) else: if entry_data[field] != "" and entry_data[field] != None: cur.execute('INSERT INTO ' + table + ' (' + self.db_prefix + '_id, Field_name, Value) VALUES (?,?,?)', \ (eid, field, self.get_value_object(field_type, field, entry_data[field]))) self.con.commit() cur.close() return eid def upd_entry(self, eid, entry_data): #FIXME: most of it can be merged with add_entry cur = self.con.cursor() for field in entry_data: table = self.get_table_name(field) field_type = self.domain.field_type_from_name(field) if table == None: continue cur.execute("DELETE FROM " + table + " WHERE " + self.db_prefix + \ "_id = ? AND field_name = ?", (eid, field)) if type(entry_data[field]) == Array or type(entry_data[field]) == list: for value in entry_data[field]: if value != "" and value != None: cur.execute("INSERT INTO " + table + " (" + self.db_prefix + "_id, Field_name, Value) VALUES (?,?,?)", (eid, field, self.get_value_object(field_type, field, value))) elif entry_data[field] == "" or entry_data[field] == None: pass else: cur.execute("INSERT INTO " + table + " (" + self.db_prefix + "_id, Field_name, Value) VALUES (?,?,?)", (eid, field, self.get_value_object(field_type, field, entry_data[field]))) self.con.commit() cur.close() def del_entry(self, eid): cur = self.con.cursor() cur.execute("DELETE FROM " + self.db_prefix + " WHERE " + self.db_prefix + "_id=?",(eid,)) if cur.rowcount == 0: cur.close() return True for table in self.tables: cur.execute("DELETE FROM " + table + " WHERE " + self.db_prefix + "_id=?",(eid,)) self.con.commit() cur.close() return False fso-frameworkd-0.10.1/framework/subsystems/opimd/db_upgrade.py000066400000000000000000000061531174525413000245220ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Openmoko PIM Daemon # SQLite database upgrade # # http://openmoko.org/ # # Copyright (C) 2009 by Thomas "Heinervdm" Zimmermann (zimmermann@vdm-design.de) # Sebastian dos Krzyszkowiak (seba.dos1@gmail.com) # Tom "TAsn" Hacohen (tom@stosb.com) # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # """ opimd SQLite database upgrade database versions: 1.0 - old table schema 2.0 - new table schema 2.1 - MessageSent and MessageRead changed to use only New for both """ import sys, os import sqlite3 import logging logger = logging.getLogger('opimd') import framework.patterns.tasklet as tasklet from framework.config import config, rootdir DB_VERSIONS = ( "1.0", "2.0", "2.1" ) # values returned by check_version DB_OK=0 # database is ok DB_NEEDS_UPGRADE=1 # database needs upgrade DB_UNSUPPORTED=2 # database version not supported (too new) def check_version(cur): """Checks if the database is supported and if it needs to be upgraded.""" cur.execute("SELECT value FROM info WHERE field_name = 'version'") version_info = cur.fetchone() # no version info -- try to upgrade it to the latest if version_info == None or len(version_info) == 0: return (DB_NEEDS_UPGRADE, None) version = version_info[0] try: ver_index = DB_VERSIONS.index(version) except: # unknown version (too new for us?) return (DB_UNSUPPORTED, version) if ver_index < len(DB_VERSIONS) - 1: return (DB_NEEDS_UPGRADE, version) # current version - no upgrade needed return (DB_OK, version) def upgrade(version, cur, con): """Upgrades the database to the latest version. @param version the current database version. @return True if the database has been upgraded, False if it doesn't need any otherwise throws an exception """ latest = DB_VERSIONS[-1] # just to be sure if version == latest: return False base_path = os.path.dirname(__file__) # begin to run upgrade script from the current version to the latest one version_index = DB_VERSIONS.index(version) if version != None else 0 for i in range(version_index, len(DB_VERSIONS)): try: sql = open(os.path.join(base_path, 'db', 'upgrade-%s.sql' % (DB_VERSIONS[i])), 'r') except: continue cur.executescript(sql.read()) sql.close() con.commit() return True fso-frameworkd-0.10.1/framework/subsystems/opimd/docs/000077500000000000000000000000001174525413000227775ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/opimd/docs/TODO000066400000000000000000000015141174525413000234700ustar00rootroot00000000000000Near future: * CopyTo, MoveTo (in entries and in queries) * use dbus.ObjectPath("foo") and "o" dbus signature * Initialized signal in source * uninitialize SIM backend on GSM resource release In mean-time: * write documentation After that: * prelimiting and postlimiting * sorting by multiple fields (_sortby:['Surname','Name']) * quering by field type * backends configuration, status * finished inteligent queries (ContactDeleted, ContactUpdated) * updating with SIM-Messages-FSO * adding new fields to for example SIM entry (by merging) Think about: * vcard support in SIM-Messages-FSO * RSS-Messages backend (handler) * Google-Contacts backend * Evolution backends Finishing: * faster merging and avoiding duplicates * using backend engine to query instead of cache when possible * more optimalization, fine-tuning etc. * world domination! fso-frameworkd-0.10.1/framework/subsystems/opimd/docs/api_overview.txt000066400000000000000000000051241174525413000262410ustar00rootroot00000000000000NOTE! * This document is outdated and needs to be updated. * Object path / method Interface name PIM/ + Messages/ OK .PIM.Messages - Query() OK + Queries/ OK .PIM.MessageQuery + n OK - GetResultCount() OK - Rewind() OK - Skip() OK - GetMessagePath() OK - GetResult() OK - GetMultipleResults() OK - Dispose() OK * MessageAdded ## * MessageDeleted ## * MessageChanged ## - GetFolderNames() OK - GetFolderOverview() -- - GetFolderPathFromName() OK + Folders/ OK .PIM.MessageFolder + n OK - GetMessageCount() OK - GetMessagePaths() OK * MessageMoved OK + n OK .PIM.Message - GetContent() OK - GetMultipleFields() OK - Update() OK - Delete() OK - MoveToFolder() OK + OnDemandAttachments/ -- + n -- * NewMessage OK * IncomingMessage OK * UnreadMessages OK + Contacts/ OK .PIM.Contacts - Add() OK - GetSingleContactSingleField() OK - Query() OK + Queries/ OK .PIM.ContactQuery + n OK - GetResultCount() OK - Rewind() OK - Skip() OK - GetContactPath() OK - GetResult() OK - GetMultipleResults() OK - Dispose() OK * ContactAdded ## * ContactDeleted ## * ContactChanged ## + n OK .PIM.Contact - GetContent() OK - GetMultipleFields() OK - Update() OK - Delete() OK * NewContact OK + Calls/ OK .PIM.Calls - Add() OK - GetSingleCallSingleField() OK - Query() OK + Queries/ OK .PIM.CallQuery + n OK - GetResultCount() OK - Rewind() OK - Skip() OK - GetCallPath() OK - GetResult() OK - GetMultipleResults() OK - Dispose() OK * CallAdded ## * CallDeleted ## * CallChanged ## + n OK .PIM.Contact - GetContent() OK - GetMultipleFields() OK - Update() OK - Delete() OK * NewCall OK * MissedCall OK * NewMissedCalls OK + Sources/ OK .PIM.Sources - InitAllEntries() OK - GetDomains() OK - GetBackends() OK - GetDefaultBackend() OK - GetEntryCount() OK + n OK .PIM.Source - GetName() OK - GetSupportedPIMDomains() OK - GetStatus() OK - GetProperties() OK - SetAsDefault() OK - Init() - Enable() OK - Disable() OK - GetEnabled() OK - Connect() -- - Disconnect() -- fso-frameworkd-0.10.1/framework/subsystems/opimd/docs/contact_fields.txt000066400000000000000000000045751174525413000265340ustar00rootroot00000000000000* NOTE! * This document is outdated and needs to be updated. Please don't be suggested with it at actual state. ----- Contacts --------------------------------------------------------------- DESC COUNT FIELD NAME CLASS URI SCHEME ------------------------------------------------------------------ ----- Internal use only ------------------------------------------ Unique ID 1 Hash md5://? id://? Semantic links 0-X LinkedWith dbus://[spouse|partner|coworker|bestfriend|...]@URI (link using md5?) - or - entrylist:// (comma-seperated list of numeric IDs with types) Link blacklist DoNotLinkWith entrylist:// (comma-seperated list of numeric IDs) ----- Non-internal fields with (virtually) static data --------------------- Self reference 1 URI dbus:// Is organization 0-1 IsOrg 0 or 1 ----- User modifyable fields ------------------------------------------------ Title 0-1 Title none Name 0-1 Name none Surname 0-1 Surname none Nickname 0-1 Nickname none [Name or Nickname must be set at least] Address 0-X Address address:// Birthdate 0-1 Birthdate date:// Date of marr. 0-1 MarrDate date:// Picture 0-X Picture file:// Note 0-X Note none Partner 0-1 Partner none or dbus:// -> contact URI Spouse 0-1 Spouse none or dbus:// -> contact URI Blog URL 0-X BlogURL http:// Blog feed URL 0-X BlogFeed feed:// Homepage URL 0-X Homepage http:// Calendar URI 0-X Calendar ??? Free/Busy URL 0-X FreeBusy http://, https://, ftp:// or file:// Phone 0-X Phone (general) tel: or sip: Cell phone 0-X Cellphone tel: or sip: Car phone 0-X Carphone tel: or sip: Pager 0-X Pager tel: or sip: eMail address 0-X EMail mailto:// First met at.. 0-1 MetAt geoloc:// AIM 0-X AIM aim:// MSN 0-X MSN msnim:// ICQ 0-X ICQ icq:// yahoo IM 0-X YIM yim:// jabber IM 0-X Jabber jabber:// gadugadu 0-X GaduGadu gg:// Home fax 0-X HomeFax none, sip: or sips: Home phone 0-X HomePhone none, sip: or sips: Home location 0-1 HomeLoc geoloc:// -- Work Assistant 0-X Assistant none or dbus:// -> contact URI Fax at work 0-X WorkFax tel:, sip: or sips: Phone at work 0-X WorkPhone tel:, sip: or sips: Work eMail 0-X WorkEMail mailto:// Work location 0-X WorkLoc geoloc:// Works for... 0-X WorksFor none or dbus:// -> contact URI Department 0-1 Department none -- free/busy URL (fburl) icscalendar fso-frameworkd-0.10.1/framework/subsystems/opimd/docs/message_fields.txt000066400000000000000000000025111174525413000265110ustar00rootroot00000000000000* NOTE! * This document is outdated and needs to be updated. Please don't be suggested with it at actual state. ----- Messages --------------------------------------------------------------- DESC COUNT FIELD NAME CLASS URI SCHEME ------------------------------------------------------------------ ----- Internal use only ------------------------------------------ Unique ID 1 Hash md5://? id://? Semantic links 0-X LinkedWith dbus://[spouse|partner|coworker|bestfriend|...]@URI (link using md5?) - or - entrylist:// (comma-seperated list of numeric IDs with types) Link blacklist DoNotLinkWith entrylist:// (comma-seperated list of numeric IDs) ----- Non-internal fields with static data --------------------- Self reference 1 URI dbus:// Source 1 Source dbus:// Date 1 Date YYYY Direction 1 Direction in or out Title 0-1 Title none Sender 1 Sender none, tel:, sip:... Recipient 1-X Recipient none, tel:, sip:... Attachment 0-X Attachment file:// or base64:// if < 1KB Transmit Loc 0-1 Position Thread Text 0-1 Text none or file:// or dbus:// [Inline only if <1K bytes] ----- User modifyable fields ------------------------------------------------ Message read? 0-1 MessageRead 0 or 1 Message sent? 0-1 MessageSent 0 or 1 Processing now? 0-1 Processing 0 or 1 Folder fso-frameworkd-0.10.1/framework/subsystems/opimd/domain_manager.py000066400000000000000000000056441174525413000253730ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Openmoko PIM Daemon # Domain Plugin Manager # # http://openmoko.org/ # http://pyneo.org/ # # Copyright (C) 2008 by Soeren Apel (abraxa@dar-clan.de) # Copyright (C) 2008-2009 by Openmoko, Inc. # Copyright (C) 2009 Michael 'Mickey' Lauer # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # """opimd Domain Plugin Manager""" MODULE_NAME = "opimd" from dbus.service import FallbackObject as DBusFBObject import logging logger = logging.getLogger( MODULE_NAME ) # We use a meta class to automaticaly register all the domain subclasses #----------------------------------------------------------------------------# class DomainMetaClass(type(DBusFBObject)): #----------------------------------------------------------------------------# def __init__(cls, name, bases, dict): super(DomainMetaClass, cls).__init__(name, bases, dict) if DBusFBObject in bases: return Domain._all_domains_cls.append(cls) #----------------------------------------------------------------------------# class Domain(DBusFBObject): #----------------------------------------------------------------------------# """Base class for all domains""" __metaclass__ = DomainMetaClass _all_domains_cls = [] #----------------------------------------------------------------------------# class DomainManager(object): #----------------------------------------------------------------------------# _domains = {} # List containing the domain objects #----------------------------------------------------------------------------# @classmethod def init(cls): for domain_cls in Domain._all_domains_cls: cls.register(domain_cls) @classmethod def register(cls, domain_cls): cls._domains[domain_cls.name] = domain_cls() logger.info("Registered domain %s", domain_cls.name) @classmethod def get_domain_handler(cls, domain): return cls._domains[domain] if (domain in cls._domains) else None @classmethod def get_domains(cls): return cls._domains.keys() @classmethod def enumerate_dbus_objects(cls): for handler in cls._domains.values(): for obj in handler.get_dbus_objects(): yield obj fso-frameworkd-0.10.1/framework/subsystems/opimd/helpers.py000066400000000000000000000062111174525413000240630ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """ Open PIM Daemon (C) 2008 by Soeren Apel (C) 2009 Michael 'Mickey' Lauer (C) 2008-2009 Openmoko, Inc. (C) 2009 Sebastian Krzyszkowiak GPLv2 or later Helpers """ from dbus import DBusException, Array #----------------------------------------------------------------------------# def field_value_to_list(field_value): #----------------------------------------------------------------------------# if isinstance(field_value, (list, Array)): return field_value else: return [ field_value ] #----------------------------------------------------------------------------# class InvalidDomain( DBusException ): #----------------------------------------------------------------------------# """Raised when a domain is invalid""" _dbus_error_name = "org.freesmartphone.PIM.InvalidDomain" #----------------------------------------------------------------------------# class InvalidField( DBusException ): #----------------------------------------------------------------------------# """Raised when a field name or type is invalid""" _dbus_error_name = "org.freesmartphone.PIM.InvalidField" #----------------------------------------------------------------------------# class InvalidQueryID( DBusException ): #----------------------------------------------------------------------------# """Raised when a submitted query ID is invalid / out of range""" _dbus_error_name = "org.freesmartphone.PIM.InvalidQueryID" #----------------------------------------------------------------------------# class AmbiguousKey( DBusException ): #----------------------------------------------------------------------------# """Raised when a given message field name is present more than once and it's unclear which to modify""" _dbus_error_name = "org.freesmartphone.PIM.AmbiguousKey" #----------------------------------------------------------------------------# class InvalidEntryID( DBusException ): #----------------------------------------------------------------------------# """Raised when a submitted entry ID is invalid / out of range""" _dbus_error_name = "org.freesmartphone.PIM.InvalidEntryID" #----------------------------------------------------------------------------# class NoMoreEntries( DBusException ): #----------------------------------------------------------------------------# """Raised when there are no more entries to be listed""" _dbus_error_name = "org.freesmartphone.PIM.NoMoreEntries" #----------------------------------------------------------------------------# class InvalidData( DBusException ): #----------------------------------------------------------------------------# """Raised when data passed to method are not valid""" _dbus_error_name = "org.freesmartphone.PIM.InvalidData" #----------------------------------------------------------------------------# class QueryFailed( DBusException ): #----------------------------------------------------------------------------# """Raised when can't query for some reason""" _dbus_error_name = "org.freesmartphone.PIM.QueryFailed" fso-frameworkd-0.10.1/framework/subsystems/opimd/opimd.py000066400000000000000000000050231174525413000235310ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # Openmoko PIM Daemon # Settings storage # # http://openmoko.org/ # http://pyneo.org/ # # (C) 2008 Soeren Apel # (C) 2008-2009 Openmoko, Inc. # (C) 2009 Michael 'Mickey' Lauer # (C) 2009 by Sebastian Krzyszkowiak # (C) 2009 Tom "TAsn" Hacohen # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # DBUS_BUS_NAME_FSO = "org.freesmartphone.opimd" DBUS_PATH_BASE_FSO = "/org/freesmartphone/PIM" DIN_BASE_FSO = "org.freesmartphone.PIM" MODULE_NAME = "opimd" import logging logger = logging.getLogger( MODULE_NAME ) try: import phoneutils except ImportError: logger.error('Couldn\'t import phoneutils! Can\'t use normalizing phone numbers. Check if you have python-phoneutils installed.') # We import the domain modules, so that there classes get registered import pimd_contacts import pimd_messages import pimd_calls import pimd_dates import pimd_notes import pimd_tasks from domain_manager import DomainManager from type_manager import TypeManager INIT = False #----------------------------------------------------------------------------# def factory( prefix, subsystem ): #----------------------------------------------------------------------------# """ frameworkd factory method. """ # TODO Check for exceptions global INIT if INIT: return [] try: phoneutils.init() except: logger.error('Failed to init libphone-utils!') DomainManager.init() type_manager = TypeManager() dbus_objects = [] # Create a list of all d-bus objects for dbus_obj in DomainManager.enumerate_dbus_objects(): logger.debug( "adding object %s" % dbus_obj ) dbus_objects.append(dbus_obj) dbus_objects.append(type_manager) INIT = True return dbus_objects fso-frameworkd-0.10.1/framework/subsystems/opimd/pimd_calls.py000066400000000000000000000372471174525413000245450ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """ Open PIM Daemon (C) 2008 by Soeren Apel (C) 2008 Openmoko, Inc. (C) 2009 Michael 'Mickey' Lauer (C) 2009 Sebastian Krzyszkowiak (C) 2009 Tom "TAsn" Hacohen GPLv2 or later Calls Domain Plugin Establishes the 'calls' PIM domain and handles all related requests """ from dbus.service import FallbackObject as DBusFBObject from dbus.service import signal as dbus_signal from dbus.service import method as dbus_method from dbus import SystemBus import time import re import logging logger = logging.getLogger('opimd') from domain_manager import DomainManager, Domain from helpers import * from opimd import * from query_manager import QueryMatcher, SingleQueryHandler from framework.config import config, busmap from pimd_generic import GenericDomain from db_handler import DbHandler #----------------------------------------------------------------------------# _DOMAIN_NAME = "Calls" _DBUS_PATH_CALLS = DBUS_PATH_BASE_FSO + '/' + _DOMAIN_NAME _DIN_CALLS_BASE = DIN_BASE_FSO _DBUS_PATH_QUERIES = _DBUS_PATH_CALLS + '/Queries' _DIN_CALLS = _DIN_CALLS_BASE + '.' + 'Calls' _DIN_ENTRY = _DIN_CALLS_BASE + '.' + 'Call' _DIN_QUERY = _DIN_CALLS_BASE + '.' + 'CallQuery' _DIN_FIELDS = _DIN_CALLS_BASE + '.' + 'Fields' #----------------------------------------------------------------------------# class CallsDbHandler(DbHandler): #----------------------------------------------------------------------------# name = 'Calls' domain = None #----------------------------------------------------------------------------# def __init__(self, domain): self.domain = domain self.db_prefix = self.name.lower() self.table_types = ['phonenumber', 'date', 'boolean'] super(CallsDbHandler, self).__init__() self.create_db() def get_create_type_index(self, type): if type == "date": return "CREATE INDEX IF NOT EXISTS " + self.db_prefix + "_" + type + \ "_value ON " + self.db_prefix + "_" + type + "(value DESC)" return super(CallsDbHandler, self).get_create_type_index(type) #----------------------------------------------------------------------------# class QueryManager(DBusFBObject): #----------------------------------------------------------------------------# _queries = None db_handler = None _next_query_id = None # Note: _queries must be a dict so we can remove queries without messing up query IDs def __init__(self, db_handler): """Creates a new QueryManager instance @param entries Set of Entry objects to use""" self.db_handler = db_handler self._queries = {} self._next_query_id = 0 # Initialize the D-Bus-Interface DBusFBObject.__init__( self, conn=busmap["opimd"], object_path=_DBUS_PATH_QUERIES ) # Still necessary? self.interface = _DIN_CALLS self.path = _DBUS_PATH_QUERIES def process_query(self, query, dbus_sender): """Handles a query and returns the dbus path of the newly created query result @param query Query to evaluate @param dbus_sender Sender's unique name on the bus @return dbus path of the query result""" query_handler = SingleQueryHandler(query, self.db_handler, dbus_sender) query_id = self._next_query_id self._next_query_id += 1 self._queries[query_id] = query_handler return _DBUS_PATH_QUERIES + '/' + str(query_id) def check_new_entry(self, entry_id): """Checks whether a newly added entry matches one or more queries so they can signal clients @param entry_id Call ID of the call that was added""" for (query_id, query_handler) in self._queries.items(): if query_handler.check_new_entry(entry_id): entry_path = self.id_to_path(entry_id) self.EntryAdded(entry_path, rel_path='/' + str(query_id)) def check_query_id_ok( self, num_id ): """ Checks whether a query ID is existing. Raises InvalidQueryID, if not. """ if not num_id in self._queries: raise InvalidQueryID( "Existing query IDs: %s" % self._queries.keys() ) def EntryAdded(self, path, rel_path=None): self.CallAdded(path, rel_path=rel_path) @dbus_signal(_DIN_QUERY, "s", rel_path_keyword="rel_path") def CallAdded(self, path, rel_path=None): pass @dbus_method(_DIN_QUERY, "", "i", rel_path_keyword="rel_path") def GetResultCount(self, rel_path): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) return self._queries[num_id].get_result_count() @dbus_method(_DIN_QUERY, "", "", rel_path_keyword="rel_path", sender_keyword="sender") def Rewind(self, rel_path, sender): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) self._queries[num_id].rewind(sender) @dbus_method(_DIN_QUERY, "i", "", rel_path_keyword="rel_path", sender_keyword="sender") def Skip(self, num_entries, rel_path, sender): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) self._queries[num_id].skip(sender, num_entries) @dbus_method(_DIN_QUERY, "", "s", rel_path_keyword="rel_path", sender_keyword="sender") def GetCallPath(self, rel_path, sender): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) return self._queries[num_id].get_entry_path(sender) @dbus_method(_DIN_QUERY, "", "a{sv}", rel_path_keyword="rel_path", sender_keyword="sender") def GetResult(self, rel_path, sender): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) return self._queries[num_id].get_result(sender) @dbus_method(_DIN_QUERY, "i", "aa{sv}", rel_path_keyword="rel_path", sender_keyword="sender") def GetMultipleResults(self, num_entries, rel_path, sender): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) return self._queries[num_id].get_multiple_results(sender, num_entries) @dbus_method(_DIN_QUERY, "", "", rel_path_keyword="rel_path") def Dispose(self, rel_path): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) # Make sure no one else references the query handler before we remove our reference to it # Otherwise, garbage collection won't actually free its memory self._queries[num_id].dispose() self._queries.__delitem__(num_id) #----------------------------------------------------------------------------# class CallDomain(Domain, GenericDomain): #----------------------------------------------------------------------------# name = _DOMAIN_NAME db_handler = None query_manager = None _dbus_path = None fso_handler = None _new_missed_calls = None DEFAULT_FIELDS = { 'Peer' : 'phonenumber', 'Line' : 'integer', 'Type' : 'string', 'New' : 'boolean', 'Answered' : 'boolean', 'Direction' : 'string', 'Duration' : 'number', 'Timestamp' : 'date', 'Timezone' : 'timezone' } def __init__(self): """Creates a new CallDomain instance""" self._dbus_path = _DBUS_PATH_CALLS self.db_handler = CallsDbHandler(self) self.query_manager = QueryManager(self.db_handler) # Initialize the D-Bus-Interface Domain.__init__( self, conn=busmap["opimd"], object_path=DBUS_PATH_BASE_FSO + '/' + self.name ) self.load_field_types() self.add_default_fields() # Keep frameworkd happy self.interface = _DIN_CALLS self.path = _DBUS_PATH_CALLS self._new_missed_calls = len(self.db_handler.query({'Answered':0, 'Direction': 'in', 'New': 1})) self.fso_handler = CallsLogFSO(self) #---------------------------------------------------------------------# # dbus methods and signals # #---------------------------------------------------------------------# def NewEntry(self, path): self.NewCall(path) @dbus_signal(_DIN_CALLS, "s") def NewCall(self, path): pass @dbus_method(_DIN_CALLS, "a{sv}", "s") def Add(self, entry_data): """Adds a entry to the list, assigning it to the default backend and saving it @param entry_data List of fields; format is [Key:Value, Key:Value, ...] @return Path of the newly created d-bus entry object""" #FIXME: move to a better place (function) and fix the reject bug path = self.add(entry_data) if entry_data.has_key('Direction') and entry_data.has_key('Answered') and \ entry_data['Direction'] == 'in' and not int(entry_data['Answered']): self._new_missed_calls += 1 self.MissedCall(path) self.NewMissedCalls(self._new_missed_calls) return path @dbus_method(_DIN_CALLS, "a{sv}s", "s") def GetSingleEntrySingleField(self, query, field_name): """Returns the first entry found for a query, making it real easy to query simple things @param query The query object @param field_name The name of the field to return @return The requested data""" return self.get_single_entry_single_field(query, field_name) @dbus_method(_DIN_CALLS, "a{sv}", "s", sender_keyword="sender") def Query(self, query, sender): """Processes a query and returns the dbus path of the resulting query object @param query Query @param sender Unique name of the query sender on the bus @return dbus path of the query object, e.g. /org.freesmartphone.PIM/Entries/Queries/4""" return self.query_manager.process_query(query, sender) @dbus_method(_DIN_ENTRY, "", "a{sv}", rel_path_keyword="rel_path") def GetContent(self, rel_path): num_id = int(rel_path[1:]) # Make sure the requested entry exists self.check_entry_id(num_id) return self.get_content(num_id) @dbus_method(_DIN_ENTRY, "s", "a{sv}", rel_path_keyword="rel_path") def GetMultipleFields(self, field_list, rel_path): num_id = int(rel_path[1:]) return self.get_multiple_fields(num_id, field_list) @dbus_signal(_DIN_CALLS, "s") def DeletedCall(self, path): pass @dbus_signal(_DIN_ENTRY, "", rel_path_keyword="rel_path") def CallDeleted(self, rel_path=None): pass def EntryDeleted(self, rel_path=None): self.CallDeleted(rel_path=rel_path) self.DeletedCall(_DBUS_PATH_CALLS+rel_path) @dbus_method(_DIN_ENTRY, "", "", rel_path_keyword="rel_path") def Delete(self, rel_path): num_id = int(rel_path[1:]) call = self.get_content(num_id) if call.has_key('New') and int(call.get('New')) and not call.get('Answered') and call.get('Direction') == 'in': self._new_missed_calls -= 1 self.NewMissedCalls(self._new_missed_calls) self.delete(num_id) def EntryUpdated(self, data, rel_path=None): self.CallUpdated(data, rel_path=rel_path) self.UpdatedCall(_DBUS_PATH_CALLS+rel_path, data) @dbus_signal(_DIN_CALLS, "sa{sv}") def UpdatedCall(self, path, data): pass @dbus_signal(_DIN_ENTRY, "a{sv}", rel_path_keyword="rel_path") def CallUpdated(self, data, rel_path=None): pass @dbus_method(_DIN_ENTRY, "a{sv}", "", rel_path_keyword="rel_path") def Update(self, data, rel_path): num_id = int(rel_path[1:]) call = self.get_content(num_id) #FIXME: make sure we cover all cases - do like in messages if call.has_key('New') and data.has_key('New') and call.has_key('Answered') and call.has_key('Direction'): if not int(call['Answered']) and call['Direction'] == 'in': if int(call['New']) and not int(data['New']): self._new_missed_calls -= 1 self.NewMissedCalls(self._new_missed_calls) elif not int(call['New']) and int(data['New']): self._new_missed_calls += 1 self.NewMissedCalls(self._new_missed_calls) self.MissedCall(_DBUS_PATH_CALLS+ '/' + str(num_id)) self.update(num_id, data) @dbus_method(_DIN_FIELDS, "ss", "") def AddField(self, name, type): self.add_new_field(name, type) @dbus_method(_DIN_FIELDS, "", "a{ss}") def ListFields(self): return self.list_fields() @dbus_method(_DIN_FIELDS, "s", "as") def ListFieldsWithType(self, type): return self.list_fields_with_type(type) @dbus_method(_DIN_FIELDS, "s", "") def DeleteField(self, name): self.remove_field(name) @dbus_method(_DIN_FIELDS, "s", "s") def GetType(self, name): return self.field_type_from_name(name) @dbus_signal(_DIN_CALLS, "s") def MissedCall(self, path): pass @dbus_signal(_DIN_CALLS, "i") def NewMissedCalls(self, amount): pass @dbus_method(_DIN_CALLS, "", "i") def GetNewMissedCalls(self): return self._new_missed_calls #----------------------------------------------------------------------------# class CallsLogFSO(object): #----------------------------------------------------------------------------# name = 'FSO-CallsLog-Handler' domain = None props = None handler = None #----------------------------------------------------------------------------# def __init__(self, domain): self.domain = domain self.props = {} self.handler = False self.enable() def __repr__(self): return self.name def handle_call_status(self, line, call_status, call_props): if not self.props.has_key(line): self.props[line] = {} self.props[line]['Line'] = str(line) if not self.props[line].has_key('Answered'): self.props[line]['Answered']=0 if call_props.has_key('mode'): self.props[line]['Type']='gsm_'+call_props['mode'] if call_props.has_key('peer'): peer = call_props["peer"] elif self.props[line].has_key('Peer'): peer = self.props[line]['Peer'] if call_status.lower() == "incoming": try: self.props[line]['Peer'] = peer except: pass self.props[line]['Direction'] = 'in' elif call_status.lower() == "outgoing": self.props[line]['Peer'] = peer self.props[line]['Direction'] = 'out' elif call_status.lower() == "active": self.props[line]['Answered'] = 1 self.props[line]['Timestamp'] = int(time.time()) elif call_status.lower() == "release": if self.props[line].has_key('Timestamp'): self.props[line]['Duration'] = int(time.time() - self.props[line]['Timestamp']) else: self.props[line]['Timestamp'] = int(time.time()) self.props[line]['Timezone'] = time.tzname[time.daylight] self.props[line]['New']=1 #FIXME: Bug when rejecting call, fix self.domain.Add(self.props[line]) del self.props[line] def disable(self): if self.handler: self.signal.remove() self.handler = False def enable(self): bus = SystemBus() if not self.handler: self.signal = bus.add_signal_receiver(self.handle_call_status, signal_name='CallStatus', dbus_interface='org.freesmartphone.GSM.Call', bus_name='org.freesmartphone.ogsmd') self.handler = True self._initialized = True fso-frameworkd-0.10.1/framework/subsystems/opimd/pimd_contacts.py000066400000000000000000000270731174525413000252610ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """ Open PIM Daemon (C) 2008 by Soeren Apel (C) 2008 Openmoko, Inc. (C) 2009 Michael 'Mickey' Lauer (C) 2009 Sebastian Krzyszkowiak (C) 2009 Tom "TAsn" Hacohen GPLv2 or later Contacts Domain Plugin Establishes the 'contacts' PIM domain and handles all related requests """ from dbus.service import FallbackObject as DBusFBObject from dbus.service import signal as dbus_signal from dbus.service import method as dbus_method import re import logging logger = logging.getLogger('opimd') from domain_manager import DomainManager, Domain from helpers import * from opimd import * from query_manager import QueryMatcher, SingleQueryHandler from framework.config import config, busmap from pimd_generic import GenericDomain from db_handler import DbHandler #----------------------------------------------------------------------------# _DOMAIN_NAME = "Contacts" _DBUS_PATH_CONTACTS = DBUS_PATH_BASE_FSO + '/' + _DOMAIN_NAME _DIN_CONTACTS_BASE = DIN_BASE_FSO _DBUS_PATH_QUERIES = _DBUS_PATH_CONTACTS + '/Queries' _DIN_CONTACTS = _DIN_CONTACTS_BASE + '.' + 'Contacts' _DIN_ENTRY = _DIN_CONTACTS_BASE + '.' + 'Contact' _DIN_QUERY = _DIN_CONTACTS_BASE + '.' + 'ContactQuery' _DIN_FIELDS = _DIN_CONTACTS_BASE + '.' + 'Fields' #----------------------------------------------------------------------------# class ContactsDbHandler(DbHandler): #----------------------------------------------------------------------------# name = 'Contacts' domain = None #----------------------------------------------------------------------------# def __init__(self, domain): self.domain = domain self.db_prefix = self.name.lower() self.table_types = ['phonenumber', 'name', 'email'] #Uses basic stuff already assumed to be initalized here (otherwise made generic) super(ContactsDbHandler, self).__init__() self.create_db() cur = self.con.cursor() self.con.commit() cur.close() #----------------------------------------------------------------------------# class QueryManager(DBusFBObject): #----------------------------------------------------------------------------# _queries = None db_handler = None _next_query_id = None # Note: _queries must be a dict so we can remove queries without messing up query IDs def __init__(self, db_handler): """Creates a new QueryManager instance @param entries Set of Entry objects to use""" self.db_handler = db_handler self._queries = {} self._next_query_id = 0 # Initialize the D-Bus-Interface DBusFBObject.__init__( self, conn=busmap["opimd"], object_path=_DBUS_PATH_QUERIES ) # Still necessary? self.interface = _DIN_CONTACTS self.path = _DBUS_PATH_QUERIES def process_query(self, query, dbus_sender): """Handles a query and returns the dbus path of the newly created query result @param query Query to evaluate @param dbus_sender Sender's unique name on the bus @return dbus path of the query result""" query_handler = SingleQueryHandler(query, self.db_handler, dbus_sender) query_id = self._next_query_id self._next_query_id += 1 self._queries[query_id] = query_handler return _DBUS_PATH_QUERIES + '/' + str(query_id) def check_new_entry(self, entry_id): """Checks whether a newly added entry matches one or more queries so they can signal clients @param entry_id Contact ID of the contact that was added""" for (query_id, query_handler) in self._queries.items(): if query_handler.check_new_entry(entry_id): entry_path = self.id_to_path(entry_id) self.EntryAdded(entry_path, rel_path='/' + str(query_id)) def check_query_id_ok( self, num_id ): """ Checks whether a query ID is existing. Raises InvalidQueryID, if not. """ if not num_id in self._queries: raise InvalidQueryID( "Existing query IDs: %s" % self._queries.keys() ) def EntryAdded(self, path, rel_path=None): self.ContactAdded(path, rel_path=rel_path) @dbus_signal(_DIN_QUERY, "s", rel_path_keyword="rel_path") def ContactAdded(self, path, rel_path=None): pass @dbus_method(_DIN_QUERY, "", "i", rel_path_keyword="rel_path") def GetResultCount(self, rel_path): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) return self._queries[num_id].get_result_count() @dbus_method(_DIN_QUERY, "", "", rel_path_keyword="rel_path", sender_keyword="sender") def Rewind(self, rel_path, sender): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) self._queries[num_id].rewind(sender) @dbus_method(_DIN_QUERY, "i", "", rel_path_keyword="rel_path", sender_keyword="sender") def Skip(self, num_entries, rel_path, sender): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) self._queries[num_id].skip(sender, num_entries) @dbus_method(_DIN_QUERY, "", "s", rel_path_keyword="rel_path", sender_keyword="sender") def GetContactPath(self, rel_path, sender): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) return self._queries[num_id].get_entry_path(sender) @dbus_method(_DIN_QUERY, "", "a{sv}", rel_path_keyword="rel_path", sender_keyword="sender") def GetResult(self, rel_path, sender): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) return self._queries[num_id].get_result(sender) @dbus_method(_DIN_QUERY, "i", "aa{sv}", rel_path_keyword="rel_path", sender_keyword="sender") def GetMultipleResults(self, num_entries, rel_path, sender): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) return self._queries[num_id].get_multiple_results(sender, num_entries) @dbus_method(_DIN_QUERY, "", "", rel_path_keyword="rel_path") def Dispose(self, rel_path): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) # Make sure no one else references the query handler before we remove our reference to it # Otherwise, garbage collection won't actually free its memory self._queries[num_id].dispose() self._queries.__delitem__(num_id) #----------------------------------------------------------------------------# class ContactDomain(Domain, GenericDomain): #----------------------------------------------------------------------------# name = _DOMAIN_NAME db_handler = None query_manager = None _dbus_path = None DEFAULT_FIELDS = { 'Name' : 'name', 'Nickname' : 'name', 'Surname' : 'name', 'Home phone' : 'phonenumber', 'Mobile phone' : 'phonenumber', 'Work phone' : 'phonenumber', 'Phone' : 'phonenumber', 'Address' : 'address', 'Birthday' : 'date', 'E-mail' : 'email', 'Photo' : 'photo', 'Affiliation' : 'text', 'Note' : 'text' } def __init__(self): """Creates a new ContactDomain instance""" self._dbus_path = _DBUS_PATH_CONTACTS self.db_handler = ContactsDbHandler(self) self.query_manager = QueryManager(self.db_handler) # Initialize the D-Bus-Interface Domain.__init__( self, conn=busmap["opimd"], object_path=DBUS_PATH_BASE_FSO + '/' + self.name ) self.load_field_types() self.add_default_fields() # Keep frameworkd happy self.interface = _DIN_CONTACTS self.path = _DBUS_PATH_CONTACTS #---------------------------------------------------------------------# # dbus methods and signals # #---------------------------------------------------------------------# def NewEntry(self, path): self.NewContact(path) @dbus_signal(_DIN_CONTACTS, "s") def NewContact(self, path): pass @dbus_method(_DIN_CONTACTS, "a{sv}", "s") def Add(self, entry_data): """Adds a entry to the list, assigning it to the default backend and saving it @param entry_data List of fields; format is [Key:Value, Key:Value, ...] @return Path of the newly created d-bus entry object""" return self.add(entry_data) @dbus_method(_DIN_CONTACTS, "a{sv}s", "s") def GetSingleEntrySingleField(self, query, field_name): """Returns the first entry found for a query, making it real easy to query simple things @param query The query object @param field_name The name of the field to return @return The requested data""" return self.get_single_entry_single_field(query, field_name) @dbus_method(_DIN_CONTACTS, "a{sv}", "s", sender_keyword="sender") def Query(self, query, sender): """Processes a query and returns the dbus path of the resulting query object @param query Query @param sender Unique name of the query sender on the bus @return dbus path of the query object, e.g. /org.freesmartphone.PIM/Entries/Queries/4""" return self.query_manager.process_query(query, sender) @dbus_method(_DIN_ENTRY, "", "a{sv}", rel_path_keyword="rel_path") def GetContent(self, rel_path): num_id = int(rel_path[1:]) # Make sure the requested entry exists self.check_entry_id(num_id) res = self.get_content(num_id) return res @dbus_method(_DIN_ENTRY, "s", "a{sv}", rel_path_keyword="rel_path") def GetMultipleFields(self, field_list, rel_path): num_id = int(rel_path[1:]) return self.get_multiple_fields(num_id, field_list) @dbus_signal(_DIN_CONTACTS, "s") def DeletedContact(self, path): pass @dbus_signal(_DIN_ENTRY, "", rel_path_keyword="rel_path") def ContactDeleted(self, rel_path=None): pass def EntryDeleted(self, rel_path=None): self.ContactDeleted(rel_path=rel_path) self.DeletedContact(_DBUS_PATH_CONTACTS+rel_path) @dbus_method(_DIN_ENTRY, "", "", rel_path_keyword="rel_path") def Delete(self, rel_path): num_id = int(rel_path[1:]) self.delete(num_id) def EntryUpdated(self, data, rel_path=None): self.ContactUpdated(data, rel_path=rel_path) self.UpdatedContact(_DBUS_PATH_CONTACTS+rel_path, data) @dbus_signal(_DIN_CONTACTS, "sa{sv}") def UpdatedContact(self, path, data): pass @dbus_signal(_DIN_ENTRY, "a{sv}", rel_path_keyword="rel_path") def ContactUpdated(self, data, rel_path=None): pass @dbus_method(_DIN_ENTRY, "a{sv}", "", rel_path_keyword="rel_path") def Update(self, data, rel_path): num_id = int(rel_path[1:]) self.update(num_id, data) @dbus_method(_DIN_FIELDS, "ss", "") def AddField(self, name, type): self.add_new_field(name, type) @dbus_method(_DIN_FIELDS, "", "a{ss}") def ListFields(self): return self.list_fields() @dbus_method(_DIN_FIELDS, "s", "as") def ListFieldsWithType(self, type): return self.list_fields_with_type(type) @dbus_method(_DIN_FIELDS, "s", "") def DeleteField(self, name): self.remove_field(name) @dbus_method(_DIN_FIELDS, "s", "s") def GetType(self, name): return self.field_type_from_name(name) fso-frameworkd-0.10.1/framework/subsystems/opimd/pimd_dates.py000066400000000000000000000250241174525413000245350ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """ Open PIM Daemon (C) 2008 by Soeren Apel (C) 2008 Openmoko, Inc. (C) 2009 Michael 'Mickey' Lauer (C) 2009 Sebastian Krzyszkowiak (C) 2009 Tom "TAsn" Hacohen GPLv2 or later Dates Domain Plugin Establishes the 'dates' PIM domain and handles all related requests """ from dbus.service import FallbackObject as DBusFBObject from dbus.service import signal as dbus_signal from dbus.service import method as dbus_method import re import logging logger = logging.getLogger('opimd') from domain_manager import DomainManager, Domain from helpers import * from opimd import * from query_manager import QueryMatcher, SingleQueryHandler from framework.config import config, busmap from pimd_generic import GenericDomain from db_handler import DbHandler #----------------------------------------------------------------------------# _DOMAIN_NAME = "Dates" _DBUS_PATH_DATES = DBUS_PATH_BASE_FSO + '/' + _DOMAIN_NAME _DIN_DATES_BASE = DIN_BASE_FSO _DBUS_PATH_QUERIES = _DBUS_PATH_DATES + '/Queries' _DIN_DATES = _DIN_DATES_BASE + '.' + 'Dates' _DIN_ENTRY = _DIN_DATES_BASE + '.' + 'Date' _DIN_QUERY = _DIN_DATES_BASE + '.' + 'DateQuery' _DIN_FIELDS = _DIN_DATES_BASE + '.' + 'Fields' #----------------------------------------------------------------------------# class DatesDbHandler(DbHandler): #----------------------------------------------------------------------------# name = 'Dates' domain = None #----------------------------------------------------------------------------# def __init__(self, domain): self.domain = domain self.db_prefix = self.name.lower() self.table_types = ['text', 'longtext', 'date', 'boolean'] super(DatesDbHandler, self).__init__() self.create_db() #----------------------------------------------------------------------------# class QueryManager(DBusFBObject): #----------------------------------------------------------------------------# _queries = None db_handler = None _next_query_id = None # Note: _queries must be a dict so we can remove queries without messing up query IDs def __init__(self, db_handler): """Creates a new QueryManager instance @param entries Set of Entry objects to use""" self.db_handler = db_handler self._queries = {} self._next_query_id = 0 # Initialize the D-Bus-Interface DBusFBObject.__init__( self, conn=busmap["opimd"], object_path=_DBUS_PATH_QUERIES ) # Still necessary? self.interface = _DIN_DATES self.path = _DBUS_PATH_QUERIES def process_query(self, query, dbus_sender): """Handles a query and returns the dbus path of the newly created query result @param query Query to evaluate @param dbus_sender Sender's unique name on the bus @return dbus path of the query result""" query_handler = SingleQueryHandler(query, self.db_handler, dbus_sender) query_id = self._next_query_id self._next_query_id += 1 self._queries[query_id] = query_handler return _DBUS_PATH_QUERIES + '/' + str(query_id) def check_new_entry(self, entry_id): """Checks whether a newly added entry matches one or more queries so they can signal clients @param entry_id Date ID of the date that was added""" for (query_id, query_handler) in self._queries.items(): if query_handler.check_new_entry(entry_id): entry_path = self.id_to_path(entry_id) self.EntryAdded(entry_path, rel_path='/' + str(query_id)) def check_query_id_ok( self, num_id ): """ Checks whether a query ID is existing. Raises InvalidQueryID, if not. """ if not num_id in self._queries: raise InvalidQueryID( "Existing query IDs: %s" % self._queries.keys() ) def EntryAdded(self, path, rel_path=None): self.DateAdded(path, rel_path=rel_path) @dbus_signal(_DIN_QUERY, "s", rel_path_keyword="rel_path") def DateAdded(self, path, rel_path=None): pass @dbus_method(_DIN_QUERY, "", "i", rel_path_keyword="rel_path") def GetResultCount(self, rel_path): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) return self._queries[num_id].get_result_count() @dbus_method(_DIN_QUERY, "", "", rel_path_keyword="rel_path", sender_keyword="sender") def Rewind(self, rel_path, sender): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) self._queries[num_id].rewind(sender) @dbus_method(_DIN_QUERY, "i", "", rel_path_keyword="rel_path", sender_keyword="sender") def Skip(self, num_entries, rel_path, sender): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) self._queries[num_id].skip(sender, num_entries) @dbus_method(_DIN_QUERY, "", "s", rel_path_keyword="rel_path", sender_keyword="sender") def GetDatePath(self, rel_path, sender): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) return self._queries[num_id].get_entry_path(sender) @dbus_method(_DIN_QUERY, "", "a{sv}", rel_path_keyword="rel_path", sender_keyword="sender") def GetResult(self, rel_path, sender): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) return self._queries[num_id].get_result(sender) @dbus_method(_DIN_QUERY, "i", "aa{sv}", rel_path_keyword="rel_path", sender_keyword="sender") def GetMultipleResults(self, num_entries, rel_path, sender): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) return self._queries[num_id].get_multiple_results(sender, num_entries) @dbus_method(_DIN_QUERY, "", "", rel_path_keyword="rel_path") def Dispose(self, rel_path): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) # Make sure no one else references the query handler before we remove our reference to it # Otherwise, garbage collection won't actually free its memory self._queries[num_id].dispose() self._queries.__delitem__(num_id) #----------------------------------------------------------------------------# class DateDomain(Domain, GenericDomain): #----------------------------------------------------------------------------# name = _DOMAIN_NAME db_handler = None query_manager = None _dbus_path = None def __init__(self): """Creates a new DateDomain instance""" self._dbus_path = _DBUS_PATH_DATES self.db_handler = DatesDbHandler(self) self.query_manager = QueryManager(self.db_handler) # Initialize the D-Bus-Interface Domain.__init__( self, conn=busmap["opimd"], object_path=DBUS_PATH_BASE_FSO + '/' + self.name ) self.load_field_types() # Keep frameworkd happy self.interface = _DIN_DATES self.path = _DBUS_PATH_DATES #---------------------------------------------------------------------# # dbus methods and signals # #---------------------------------------------------------------------# def NewEntry(self, path): self.NewDate(path) @dbus_signal(_DIN_DATES, "s") def NewDate(self, path): pass @dbus_method(_DIN_DATES, "a{sv}", "s") def Add(self, entry_data): """Adds a entry to the list, assigning it to the default backend and saving it @param entry_data List of fields; format is [Key:Value, Key:Value, ...] @return Path of the newly created d-bus entry object""" return self.add(entry_data) @dbus_method(_DIN_DATES, "a{sv}s", "s") def GetSingleEntrySingleField(self, query, field_name): """Returns the first entry found for a query, making it real easy to query simple things @param query The query object @param field_name The name of the field to return @return The requested data""" return self.get_single_entry_single_field(query, field_name) @dbus_method(_DIN_DATES, "a{sv}", "s", sender_keyword="sender") def Query(self, query, sender): """Processes a query and returns the dbus path of the resulting query object @param query Query @param sender Unique name of the query sender on the bus @return dbus path of the query object, e.g. /org.freesmartphone.PIM/Entries/Queries/4""" return self.query_manager.process_query(query, sender) @dbus_method(_DIN_ENTRY, "", "a{sv}", rel_path_keyword="rel_path") def GetContent(self, rel_path): num_id = int(rel_path[1:]) # Make sure the requested entry exists self.check_entry_id(num_id) return self.get_content(num_id) @dbus_method(_DIN_ENTRY, "s", "a{sv}", rel_path_keyword="rel_path") def GetMultipleFields(self, field_list, rel_path): num_id = int(rel_path[1:]) return self.get_multiple_fields(num_id, field_list) @dbus_signal(_DIN_DATES, "s") def DeletedDate(self, path): pass @dbus_signal(_DIN_ENTRY, "", rel_path_keyword="rel_path") def DateDeleted(self, rel_path=None): pass def EntryDeleted(self, rel_path=None): self.DateDeleted(rel_path=rel_path) self.DeletedDate(_DBUS_PATH_DATES+rel_path) @dbus_method(_DIN_ENTRY, "", "", rel_path_keyword="rel_path") def Delete(self, rel_path): num_id = int(rel_path[1:]) self.delete(num_id) def EntryUpdated(self, data, rel_path=None): self.DateUpdated(data, rel_path=rel_path) self.UpdatedDate(_DBUS_PATH_DATES+rel_path, data) @dbus_signal(_DIN_DATES, "sa{sv}") def UpdatedDate(self, path, data): pass @dbus_signal(_DIN_ENTRY, "a{sv}", rel_path_keyword="rel_path") def DateUpdated(self, data, rel_path=None): pass @dbus_method(_DIN_ENTRY, "a{sv}", "", rel_path_keyword="rel_path") def Update(self, data, rel_path): num_id = int(rel_path[1:]) self.update(num_id, data) @dbus_method(_DIN_FIELDS, "ss", "") def AddField(self, name, type): self.add_new_field(name, type) @dbus_method(_DIN_FIELDS, "", "a{ss}") def ListFields(self): return self.list_fields() @dbus_method(_DIN_FIELDS, "s", "as") def ListFieldsWithType(self, type): return self.list_fields_with_type(type) @dbus_method(_DIN_FIELDS, "s", "") def DeleteField(self, name): self.remove_field(name) @dbus_method(_DIN_FIELDS, "s", "s") def GetType(self, name): return self.field_type_from_name(name) fso-frameworkd-0.10.1/framework/subsystems/opimd/pimd_generic.py000066400000000000000000000267241174525413000250610ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """ Open PIM Daemon (C) 2008 Soeren Apel (C) 2008 Openmoko, Inc. (C) 2009 Michael 'Mickey' Lauer (C) 2009 Sebastian Krzyszkowiak (C) 2009 Tom "TAsn" Hacohen (tom@stosb.com) GPLv2 or later Generic Domain From those domain classes others inherit. """ DBUS_BUS_NAME_FSO = "org.freesmartphone.opimd" DBUS_PATH_BASE_FSO = "/org/freesmartphone/PIM" DIN_BASE_FSO = "org.freesmartphone.PIM" import dbus from dbus.service import FallbackObject as DBusFBObject from dbus.service import signal as dbus_signal from dbus.service import method as dbus_method import re import logging logger = logging.getLogger('opimd') from type_manager import TypeManager from domain_manager import Domain from helpers import * from query_manager import QueryMatcher, SingleQueryHandler from framework.config import config, busmap import os,pickle _CONF_PATH = '/etc/freesmartphone/opim/' #----------------------------------------------------------------------------# #_DOMAIN_NAME = "Generic" #_DBUS_PATH_DOMAIN = DBUS_PATH_BASE_FSO + '/' + _DOMAIN_NAME _DIN_DOMAIN_BASE = DIN_BASE_FSO #_DBUS_PATH_QUERIES = _DBUS_PATH_DOMAIN + '/Queries' _DIN_ENTRIES = _DIN_DOMAIN_BASE + '.' + 'Entries' _DIN_ENTRY = _DIN_DOMAIN_BASE + '.' + 'Entry' _DIN_QUERY = _DIN_DOMAIN_BASE + '.' + 'EntryQuery' _DIN_FIELDS = _DIN_DOMAIN_BASE + '.' + 'Fields' #----------------------------------------------------------------------------# class QueryManager(DBusFBObject): #----------------------------------------------------------------------------# _queries = None _entries = None _next_query_id = None domain_name = None # Note: _queries must be a dict so we can remove queries without messing up query IDs def __init__(self, entries, domain_name): """Creates a new QueryManager instance @param entries Set of entry objects to use""" self._entries = entries self._queries = {} self._next_query_id = 0 self.domain_name = domain_name # Initialize the D-Bus-Interface DBusFBObject.__init__( self, conn=busmap["opimd"], object_path=DBUS_PATH_BASE_FSO + '/' + self.domain_name + '/Queries' ) # Still necessary? #self.interface = _DIN_ENTRIES #self.path = DBUS_PATH_BASE_FSO + '/' + self.domain_name + '/Queries' def process_query(self, query, dbus_sender): """Handles a query and returns the dbus path of the newly created query result @param query Query to evaluate @param dbus_sender Sender's unique name on the bus @return dbus path of the query result""" query_handler = SingleQueryHandler(query, self.db_handler, dbus_sender) query_id = self._next_query_id self._next_query_id += 1 self._queries[query_id] = query_handler return DBUS_PATH_BASE_FSO + '/' + self.domain_name + '/Queries/' + str(query_id) def check_new_entry(self, entry_id): """Checks whether a newly added entry matches one or more queries so they can signal clients @param entry_id entry ID of the entry that was added""" #FIXME: TBD for (query_id, query_handler) in self._queries.items(): if query_handler.check_new_entry(entry_id): entry = self._entries[entry_id] entry_path = entry['Path'] self.EntryAdded(entry_path, rel_path='/' + str(query_id)) def check_query_id_ok( self, num_id ): """ Checks whether a query ID is existing. Raises InvalidQueryID, if not. """ if not num_id in self._queries: raise InvalidQueryID( "Existing query IDs: %s" % self._queries.keys() ) @dbus_signal(_DIN_QUERY, "o", rel_path_keyword="rel_path") def EntryAdded(self, path, rel_path=None): pass @dbus_method(_DIN_QUERY, "", "i", rel_path_keyword="rel_path") def GetResultCount(self, rel_path): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) return self._queries[num_id].get_result_count() @dbus_method(_DIN_QUERY, "", "", rel_path_keyword="rel_path", sender_keyword="sender") def Rewind(self, rel_path, sender): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) self._queries[num_id].rewind(sender) @dbus_method(_DIN_QUERY, "i", "", rel_path_keyword="rel_path", sender_keyword="sender") def Skip(self, num_entries, rel_path, sender): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) self._queries[num_id].skip(sender, num_entries) @dbus_method(_DIN_QUERY, "", "s", rel_path_keyword="rel_path", sender_keyword="sender") def GetEntryPath(self, rel_path, sender): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) return self._queries[num_id].get_entry_path(sender) @dbus_method(_DIN_QUERY, "", "a{sv}", rel_path_keyword="rel_path", sender_keyword="sender") def GetResult(self, rel_path, sender): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) return self._queries[num_id].get_result(sender) @dbus_method(_DIN_QUERY, "i", "aa{sv}", rel_path_keyword="rel_path", sender_keyword="sender") def GetMultipleResults(self, num_entries, rel_path, sender): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) return self._queries[num_id].get_multiple_results(sender, num_entries) @dbus_method(_DIN_QUERY, "", "", rel_path_keyword="rel_path") def Dispose(self, rel_path): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) # Make sure no one else references the query handler before we remove our reference to it # Otherwise, garbage collection won't actually free its memory self._queries[num_id].dispose() self._queries.__delitem__(num_id) #----------------------------------------------------------------------------# class GenericDomain(): #----------------------------------------------------------------------------# name = 'Generic' db_handler = None query_manager = None _dbus_path = None FieldTypes = None DEFAULT_FIELDS = [] """Reserved types""" _SYSTEM_FIELDS = { 'Path' : 'objectpath', 'EntryId' : 'entryid' } #FIXME: doesn't get called def __init__(self): """Creates a new GenericDomain instance""" self.FieldTypes = {} def get_dbus_objects(self): """Returns a list of all d-bus objects we manage @return List of d-bus objects""" return (self, self.query_manager) def id_to_path(self, entry_id): path = self._dbus_path+ '/' + str(entry_id) return path def path_to_id(self, entry_path): id = entry_path.rpartition('/') return int(id[2]) def is_reserved_field(self, field): return field[:1] in ('_', '<', '>', '$', '@') or self.is_system_field(field) def is_system_field(self, field): return (field in self._SYSTEM_FIELDS) def load_field_types(self): self.FieldTypes = self.db_handler.load_field_types() if not self.FieldTypes: self.FieldTypes = {} def field_type_from_name(self, name): if name in self.FieldTypes: return self.FieldTypes[name] else: return 'generic' def add_default_fields(self): #Add default fields if don't exist for field in self.DEFAULT_FIELDS: try: self.add_new_field(field, self.DEFAULT_FIELDS[field]) except InvalidField: #If field already exists or can't add default field, just continue pass def add_new_field(self, name, type): if name in self.FieldTypes: raise InvalidField ( "Field '%s' already exists." % (name, )) if self.is_reserved_field(name): raise InvalidField ( "Field '%s' is reserved." % (name, )) if type not in TypeManager.Types: raise InvalidField ( "Type '%s' is invalid." % (type,)) self.FieldTypes[str(name)] = str(type) #must be last, assumes already loaded self.db_handler.add_field_type(name, type) def remove_field(self, name): if self.is_reserved_field(name): raise InvalidField ( "Field '%s' is reserved." % (name, )) if name not in self.FieldTypes: raise InvalidField ( "Field '%s' does not exist." % (name, )) if name in self.DEFAULT_FIELDS: raise InvalidField ( "Field '%s' must be defined for this domain to operate properly." % (name, )) self.db_handler.remove_field_type(name) del self.FieldTypes[name] def list_fields(self): return self.FieldTypes def list_fields_with_type(self, type): fields = [] for field in self.FieldTypes: if self.FieldTypes[field]==type: fields.append(field) return fields def check_entry_id( self, num_id ): """ Checks whether the given entry id is valid. Raises InvalidEntryID, if not. """ if self.db_handler.entry_exists(num_id): raise InvalidEntryID() def add(self, entry_data): eid = self.db_handler.add_entry(entry_data) result = self.id_to_path(eid) # As we just added a new entry, we check it against all queries to see if it matches self.query_manager.check_new_entry(eid) self.NewEntry(result) return result def update(self, num_id, data): # Make sure the requested entry exists if self.db_handler.entry_exists(num_id): raise InvalidEntryID() self.db_handler.upd_entry(num_id, data) self.EntryUpdated(data, rel_path='/'+str(num_id)) def delete(self, num_id): # Make sure the requested entry exists #self.check_entry_id(num_id) if self.db_handler.del_entry(num_id): raise InvalidEntryID() self.EntryDeleted(rel_path='/'+str(num_id)) def get_multiple_fields(self, num_id, field_list): # Make sure the requested entry exists self.check_entry_id(num_id) #We leave legacy support for , delimited list if type(field_list) == Array or type(field_list) == list: fields = field_list else: fields = field_list.split(',') map(lambda x: x.strip(), fields) entry = self.get_content(num_id) for key in entry.keys(): if key not in fields and "$" + self.field_type_from_name(key) not in fields: del entry[key] return entry def get_single_entry_single_field(self, query, field_name): result = "" # Only return one entry query['_limit'] = 1 matcher = QueryMatcher(query) res = matcher.match(self.db_handler) if len(res) > 0 and field_name in res[0]: return res[0][field_name] else: return "" def get_content(self, num_id): self.check_entry_id(num_id) #FIXME: should I do something instead of this none? do we need extra filtering here? res = self.db_handler.get_content([num_id, ], {}) if len(res) > 0: return res[0] else: return {} def get_full_content(self, rel_path): num_id = int(rel_path[1:]) # Make sure the requested entry exists self.check_entry_id(num_id) self.get_content(num_id) return self.get_content(num_id) fso-frameworkd-0.10.1/framework/subsystems/opimd/pimd_messages.py000066400000000000000000000706041174525413000252500ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """ Open PIM Daemon (C) 2008 by Soeren Apel (C) 2008 Openmoko, Inc. (C) 2009 Michael 'Mickey' Lauer (C) 2009 Sebastian Krzyszkowiak (C) 2009 Tom "TAsn" Hacohen GPLv2 or later Messages Domain Plugin Establishes the 'messages' PIM domain and handles all related requests """ from dbus.service import FallbackObject as DBusFBObject from dbus.service import signal as dbus_signal from dbus.service import method as dbus_method #ogsmd interaction from dbus import SystemBus from dbus.proxies import Interface from dbus.exceptions import DBusException from functools import partial import dbus import time import re import logging logger = logging.getLogger('opimd') from domain_manager import DomainManager, Domain from helpers import * from opimd import * from query_manager import SingleQueryHandler, SingleRawSQLQueryHandler import framework.patterns.tasklet as tasklet from framework.config import config, busmap from pimd_generic import GenericDomain from db_handler import DbHandler #----------------------------------------------------------------------------# _DOMAIN_NAME = "Messages" _DBUS_PATH_MESSAGES = DBUS_PATH_BASE_FSO + '/' + _DOMAIN_NAME _DIN_MESSAGES_BASE = DIN_BASE_FSO _DBUS_PATH_QUERIES = _DBUS_PATH_MESSAGES + '/Queries' _DIN_MESSAGES = _DIN_MESSAGES_BASE + '.' + 'Messages' _DIN_ENTRY = _DIN_MESSAGES_BASE + '.' + 'Message' _DIN_QUERY = _DIN_MESSAGES_BASE + '.' + 'MessageQuery' _DIN_FIELDS = _DIN_MESSAGES_BASE + '.' + 'Fields' #----------------------------------------------------------------------------# class MessagesDbHandler(DbHandler): #----------------------------------------------------------------------------# name = 'Messages' domain = None #----------------------------------------------------------------------------# def __init__(self, domain): self.domain = domain self.db_prefix = self.name.lower() self.table_types = ['phonenumber', 'text', 'date', 'boolean'] super(MessagesDbHandler, self).__init__() self.create_db() def get_create_type_index(self, type): if type == "date": return "CREATE INDEX IF NOT EXISTS " + self.db_prefix + "_" + type + \ "_value ON " + self.db_prefix + "_" + type + "(value DESC)" return super(MessagesDbHandler, self).get_create_type_index(type) #----------------------------------------------------------------------------# class QueryManager(DBusFBObject): #----------------------------------------------------------------------------# _queries = None db_handler = None _next_query_id = None # Note: _queries must be a dict so we can remove queries without messing up query IDs def __init__(self, db_handler): """Creates a new QueryManager instance @param entries Set of Entry objects to use""" self.db_handler = db_handler self._queries = {} self._next_query_id = 0 # Initialize the D-Bus-Interface DBusFBObject.__init__( self, conn=busmap["opimd"], object_path=_DBUS_PATH_QUERIES ) # Still necessary? self.interface = _DIN_MESSAGES self.path = _DBUS_PATH_QUERIES def process_query(self, query, dbus_sender): """Handles a query and returns the dbus path of the newly created query result @param query Query to evaluate @param dbus_sender Sender's unique name on the bus @return dbus path of the query result""" query_handler = SingleQueryHandler(query, self.db_handler, dbus_sender) query_id = self._next_query_id self._next_query_id += 1 self._queries[query_id] = query_handler return _DBUS_PATH_QUERIES + '/' + str(query_id) def process_query_threads(self, query, dbus_sender): """Handles a query for threads and returns the dbus path of the newly created query result @param dbus_sender Sender's unique name on the bus @return dbus path of the query result""" db_prefix = self.db_handler.db_prefix query['sql'] = """ SELECT m.""" + db_prefix + """_id """ + db_prefix + """_id, ( SELECT COUNT(*) FROM """ + db_prefix + """_phonenumber WHERE field_name = 'Peer' AND value = p1.value ) TotalCount, ( SELECT COUNT(*) FROM ( """ + db_prefix + """_boolean b JOIN """ + db_prefix + """_phonenumber p ON b.""" + db_prefix + """_id = p.""" + db_prefix + """_id AND b.field_name = 'New' ) JOIN """ + db_prefix + """_text x ON b.""" + db_prefix + """_id = x.""" + db_prefix + """_id AND x.field_name = 'Direction' WHERE b.value = '1' AND x.value = 'in' AND p.field_name = 'Peer' AND p.value = p1.value ) UnreadCount FROM ( """ + db_prefix + """ m JOIN """ + db_prefix + """_date t USING (""" + db_prefix + """_id) ) JOIN """ + db_prefix + """_phonenumber p1 USING (""" + db_prefix + """_id) WHERE t.field_name = 'Timestamp' AND t.value IN ( SELECT MAX(timestamp) timestamp FROM ( ( SELECT date_t.""" + db_prefix + """_id AS """ + db_prefix + """_id, date_t.value AS timestamp FROM """ + db_prefix + """_date AS date_t WHERE date_t.field_name = 'Timestamp' ) res_t JOIN """ + db_prefix + """_phonenumber num_t ON res_t.""" + db_prefix + """_id = num_t.""" + db_prefix + """_id AND num_t.field_name = 'Peer' ) GROUP BY num_t.value ) ORDER BY t.value DESC """ query_handler = SingleRawSQLQueryHandler(query, self.db_handler, dbus_sender) query_id = self._next_query_id self._next_query_id += 1 self._queries[query_id] = query_handler return _DBUS_PATH_QUERIES + '/' + str(query_id) def check_new_entry(self, entry_id): """Checks whether a newly added entry matches one or more queries so they can signal clients @param entry_id Message ID of the message that was added""" for (query_id, query_handler) in self._queries.items(): if query_handler.check_new_entry(entry_id): entry_path = self.id_to_path(entry_id) self.EntryAdded(entry_path, rel_path='/' + str(query_id)) def check_query_id_ok( self, num_id ): """ Checks whether a query ID is existing. Raises InvalidQueryID, if not. """ if not num_id in self._queries: raise InvalidQueryID( "Existing query IDs: %s" % self._queries.keys() ) def EntryAdded(self, path, rel_path=None): self.MessageAdded(path, rel_path=rel_path) @dbus_signal(_DIN_QUERY, "s", rel_path_keyword="rel_path") def MessageAdded(self, path, rel_path=None): pass @dbus_method(_DIN_QUERY, "", "i", rel_path_keyword="rel_path") def GetResultCount(self, rel_path): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) return self._queries[num_id].get_result_count() @dbus_method(_DIN_QUERY, "", "", rel_path_keyword="rel_path", sender_keyword="sender") def Rewind(self, rel_path, sender): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) self._queries[num_id].rewind(sender) @dbus_method(_DIN_QUERY, "i", "", rel_path_keyword="rel_path", sender_keyword="sender") def Skip(self, num_entries, rel_path, sender): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) self._queries[num_id].skip(sender, num_entries) @dbus_method(_DIN_QUERY, "", "s", rel_path_keyword="rel_path", sender_keyword="sender") def GetMessagePath(self, rel_path, sender): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) return self._queries[num_id].get_entry_path(sender) @dbus_method(_DIN_QUERY, "", "a{sv}", rel_path_keyword="rel_path", sender_keyword="sender") def GetResult(self, rel_path, sender): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) return self._queries[num_id].get_result(sender) @dbus_method(_DIN_QUERY, "i", "aa{sv}", rel_path_keyword="rel_path", sender_keyword="sender") def GetMultipleResults(self, num_entries, rel_path, sender): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) return self._queries[num_id].get_multiple_results(sender, num_entries) @dbus_method(_DIN_QUERY, "", "", rel_path_keyword="rel_path") def Dispose(self, rel_path): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) # Make sure no one else references the query handler before we remove our reference to it # Otherwise, garbage collection won't actually free its memory self._queries[num_id].dispose() self._queries.__delitem__(num_id) ##----------------------------------------------------------------------------# class MessageDomain(Domain, GenericDomain): #----------------------------------------------------------------------------# name = _DOMAIN_NAME fso_handler = None db_handler = None query_manager = None _dbus_path = None _unread_messages = None DEFAULT_FIELDS = { 'Peer' : 'phonenumber', 'Source' : 'text', 'Direction' : 'text', 'New' : 'boolean', 'Timestamp' : 'date', 'Timezone' : 'timezone', 'Content' : 'text' } def __init__(self): """Creates a new MessageDomain instance""" self._dbus_path = _DBUS_PATH_MESSAGES self.db_handler = MessagesDbHandler(self) self.query_manager = QueryManager(self.db_handler) # Initialize the D-Bus-Interface Domain.__init__( self, conn=busmap["opimd"], object_path=DBUS_PATH_BASE_FSO + '/' + self.name ) self.load_field_types() self.add_default_fields() # Keep frameworkd happy self.interface = _DIN_MESSAGES self.path = _DBUS_PATH_MESSAGES self.fso_handler = MessagesFSO(self) self._unread_messages = len(self.db_handler.query({'Direction': 'in', 'New':1})) #---------------------------------------------------------------------# # dbus methods and signals # #---------------------------------------------------------------------# def NewEntry(self, path): self.NewMessage(path) @dbus_signal(_DIN_MESSAGES, "s") def NewMessage(self, path): pass @dbus_method(_DIN_MESSAGES, "a{sv}", "s") def Add(self, entry_data): """Adds a message to the list, assigning it to the default backend and saving it @param message_data List of fields; format is [Key:Value, Key:Value, ...] @return URI of the newly created d-bus message object""" unread = entry_data.get('New') #Make sure is boolean if unread == None: unread = 0 else: unread = int(unread) if entry_data.get('Direction') == 'in' and unread: self._unread_messages += 1 self.UnreadMessages(self._unread_messages) return self.add(entry_data) @dbus_method(_DIN_MESSAGES, "a{sv}", "s") def AddIncoming(self, entry_data): """Adds a message to the list, and send signal about incoming message @param message_data List of fields; format is [Key:Value, Key:Value, ...] @return URI of the newly created d-bus message object""" message_id = self.Add(entry_data) self.IncomingMessage(message_id) return message_id @dbus_method(_DIN_MESSAGES, "a{sv}s", "s") def GetSingleEntrySingleField(self, query, field_name): """Returns the first message found for a query, making it real easy to query simple things @param query The query object @param field_name The name of the field to return @return The requested data""" return self.get_single_entry_single_field(query, field_name) @dbus_method(_DIN_MESSAGES, "a{sv}", "s", sender_keyword="sender") def Query(self, query, sender): """Processes a query and returns the URI of the resulting query object @param query Query @param sender Unique name of the query sender on the bus @return URI of the query object, e.g. /org.pyneo.PIM/Messages/Queries/4""" return self.query_manager.process_query(query, sender) @dbus_method(_DIN_MESSAGES, "a{sv}", "s", sender_keyword="sender") def QueryThreads(self, query, sender): """Creates a new query for threads and returns the URI of the resulting query object @param sender Unique name of the query sender on the bus @return URI of the query object, e.g. /org.pyneo.PIM/Messages/Queries/4""" return self.query_manager.process_query_threads(query, sender) @dbus_method(_DIN_MESSAGES, "", "i") def GetUnreadMessages(self): return self._unread_messages @dbus_signal(_DIN_MESSAGES, "i") def UnreadMessages(self, amount): pass @dbus_signal(_DIN_MESSAGES, "s") def IncomingMessage(self, message_path): pass @dbus_method(_DIN_ENTRY, "", "a{sv}", rel_path_keyword="rel_path") def GetContent(self, rel_path): num_id = int(rel_path[1:]) self.check_entry_id(num_id) return self.get_content(num_id) @dbus_method(_DIN_ENTRY, "s", "a{sv}", rel_path_keyword="rel_path") def GetMultipleFields(self, field_list, rel_path): num_id = int(rel_path[1:]) return self.get_multiple_fields(num_id, field_list) @dbus_signal(_DIN_MESSAGES, "s") def DeletedMessage(self, path): pass @dbus_signal(_DIN_ENTRY, "", rel_path_keyword="rel_path") def MessageDeleted(self, rel_path=None): pass def EntryDeleted(self, rel_path=None): self.MessageDeleted(rel_path=rel_path) self.DeletedMessage(_DBUS_PATH_MESSAGES+rel_path) @dbus_method(_DIN_ENTRY, "", "", rel_path_keyword="rel_path") def Delete(self, rel_path): num_id = int(rel_path[1:]) self.check_entry_id(num_id) message = self.get_content(num_id) unread = message.get('New') #Make sure is boolean if unread == None: unread = 0 else: unread = int(unread) if unread and message.get('Direction') == 'in': self._unread_messages -= 1 self.UnreadMessages(self._unread_messages) self.delete(num_id) def EntryUpdated(self, data, rel_path=None): self.MessageUpdated(data, rel_path=rel_path) self.UpdatedMessage(_DBUS_PATH_MESSAGES+rel_path, data) @dbus_signal(_DIN_MESSAGES, "sa{sv}") def UpdatedMessage(self, path, data): pass @dbus_signal(_DIN_ENTRY, "a{sv}", rel_path_keyword="rel_path") def MessageUpdated(self, data, rel_path=None): pass @dbus_method(_DIN_ENTRY, "a{sv}", "", rel_path_keyword="rel_path") def Update(self, data, rel_path): num_id = int(rel_path[1:]) self.check_entry_id(num_id) message = self.get_content(num_id) #FIXME: What if it was outgoing and is now incoming? old_unread = message['New'] if message.has_key('New') else False new_unread = data['New'] if data.has_key('New') else False old_in = message['Direction'] == 'in' if message.has_key('Direction') else False new_in = data['Direction'] == 'in' if data.has_key('Direction') else old_in if old_unread and old_in and not new_unread: self._unread_messages -= 1 self.UnreadMessages(self._unread_messages) elif (not old_unread and new_unread and new_in) or \ (old_unread and not old_in and new_unread and new_in): self._unread_messages += 1 self.UnreadMessages(self._unread_messages) self.update(num_id, data) @dbus_method(_DIN_FIELDS, "ss", "") def AddField(self, name, type): self.add_new_field(name, type) @dbus_method(_DIN_FIELDS, "", "a{ss}") def ListFields(self): return self.list_fields() @dbus_method(_DIN_FIELDS, "s", "as") def ListFieldsWithType(self, type): return self.list_fields_with_type(type) @dbus_method(_DIN_FIELDS, "s", "") def DeleteField(self, name): self.remove_field(name) @dbus_method(_DIN_FIELDS, "s", "s") def GetType(self, name): return self.field_type_from_name(name) #----------------------------------------------------------------------------# class MessagesFSO(object): #----------------------------------------------------------------------------# name = 'FSO-Messages-Handler' _gsm_sim_iface = None _UNAVAILABLE_PART = '' domain = None #----------------------------------------------------------------------------# def __init__(self, domain): self.domain = domain self.signals = False self.ready_signal = False self.enable() def __repr__(self): return self.name def dbus_ok(self, *args, **kargs): pass def dbus_err(self, *args, **kargs): pass def process_single_entry(self, data): (status, number, text, props) = data entry = {} #FIXME: removing status and sanitize this function remove seq/etc after getting second message logger.debug("Processing entry \"%s\"...", text) entry['New'] = 1 if status in ('read', 'unread'): entry['Direction'] = 'in' else: entry['Direction'] = 'out' if status in ('read', 'sent'): entry['New'] = 0 entry['Peer'] = number # TODO Handle text properly, i.e. make it on-demand if >1KiB entry['Content'] = text entry['Source'] = 'SMS' entry['SMS-combined_message'] = 0 if props.has_key('timestamp'): try: timestamp = props['timestamp'][:len(props['timestamp'])-6] entry['Timezone'] = props['timestamp'][len(props['timestamp'])-5:] entry['Timestamp'] = int(time.mktime(time.strptime(timestamp))) except ValueError: logger.error("Couldn't handle timestamp!") if props.has_key('csm_seq'): entry['SMS-combined_message'] = 1 entry['SMS-complete_message'] = 0 entry['SMS-csm_seq'+str(props['csm_seq'])+'_content'] = text for field in props: entry['SMS-'+field] = props[field] logger.debug("Message is incoming!") if entry['SMS-combined_message']: logger.debug("It's CSM!") register = 0 try: path = self.domain.GetSingleEntrySingleField({'Direction':'in', 'SMS-combined_message':1, 'SMS-complete_message':0, 'SMS-csm_num':entry['SMS-csm_num'], 'SMS-csm_id':entry['SMS-csm_id'], 'Source':'SMS'},'Path') if path: id = self.domain.path_to_id(path) result = self.domain.get_content(id) new_content = '' complete = 1 edit_data = {} # Make the whole content for i in range(1, entry['SMS-csm_num']+1): if i==entry['SMS-csm_seq']: new_content += entry['Content'] edit_data['SMS-csm_seq'+str(i)+'_content'] = entry['Content'] else: try: new_content += result['SMS-csm_seq'+str(i)+'_content'] except KeyError: new_content += self._UNAVAILABLE_PART complete = 0 if complete: edit_data['SMS-complete_message']=1 edit_data['Content'] = new_content edit_data['New'] = 1 self.domain.Update(edit_data, '/' + str(id)) else: register = 1 if entry['SMS-csm_seq']>1: entry['Content']=self._UNAVAILABLE_PART+entry['Content'] if entry['SMS-csm_seq'] (C) 2008 Openmoko, Inc. (C) 2009 Michael 'Mickey' Lauer (C) 2009 Sebastian Krzyszkowiak (C) 2009 Tom "TAsn" Hacohen GPLv2 or later Notes Domain Plugin Establishes the 'notes' PIM domain and handles all related requests """ from dbus.service import FallbackObject as DBusFBObject from dbus.service import signal as dbus_signal from dbus.service import method as dbus_method import re import logging logger = logging.getLogger('opimd') from domain_manager import DomainManager, Domain from helpers import * from opimd import * from query_manager import QueryMatcher, SingleQueryHandler from framework.config import config, busmap from pimd_generic import GenericDomain from db_handler import DbHandler #----------------------------------------------------------------------------# _DOMAIN_NAME = "Notes" _DBUS_PATH_NOTES = DBUS_PATH_BASE_FSO + '/' + _DOMAIN_NAME _DIN_NOTES_BASE = DIN_BASE_FSO _DBUS_PATH_QUERIES = _DBUS_PATH_NOTES + '/Queries' _DIN_NOTES = _DIN_NOTES_BASE + '.' + 'Notes' _DIN_ENTRY = _DIN_NOTES_BASE + '.' + 'Note' _DIN_QUERY = _DIN_NOTES_BASE + '.' + 'NoteQuery' _DIN_FIELDS = _DIN_NOTES_BASE + '.' + 'Fields' #----------------------------------------------------------------------------# class NotesDbHandler(DbHandler): #----------------------------------------------------------------------------# name = 'Notes' domain = None #----------------------------------------------------------------------------# def __init__(self, domain): self.domain = domain self.db_prefix = self.name.lower() self.table_types = ['text', 'longtext', 'date', 'boolean'] super(NotesDbHandler, self).__init__() self.create_db() #----------------------------------------------------------------------------# class QueryManager(DBusFBObject): #----------------------------------------------------------------------------# _queries = None db_handler = None _next_query_id = None # Note: _queries must be a dict so we can remove queries without messing up query IDs def __init__(self, db_handler): """Creates a new QueryManager instance @param entries Set of Entry objects to use""" self.db_handler = db_handler self._queries = {} self._next_query_id = 0 # Initialize the D-Bus-Interface DBusFBObject.__init__( self, conn=busmap["opimd"], object_path=_DBUS_PATH_QUERIES ) # Still necessary? self.interface = _DIN_NOTES self.path = _DBUS_PATH_QUERIES def process_query(self, query, dbus_sender): """Handles a query and returns the dbus path of the newly created query result @param query Query to evaluate @param dbus_sender Sender's unique name on the bus @return dbus path of the query result""" query_handler = SingleQueryHandler(query, self.db_handler, dbus_sender) query_id = self._next_query_id self._next_query_id += 1 self._queries[query_id] = query_handler return _DBUS_PATH_QUERIES + '/' + str(query_id) def check_new_entry(self, entry_id): """Checks whether a newly added entry matches one or more queries so they can signal clients @param entry_id Note ID of the note that was added""" for (query_id, query_handler) in self._queries.items(): if query_handler.check_new_entry(entry_id): entry_path = self.id_to_path(entry_id) self.EntryAdded(entry_path, rel_path='/' + str(query_id)) def check_query_id_ok( self, num_id ): """ Checks whether a query ID is existing. Raises InvalidQueryID, if not. """ if not num_id in self._queries: raise InvalidQueryID( "Existing query IDs: %s" % self._queries.keys() ) def EntryAdded(self, path, rel_path=None): self.NoteAdded(path, rel_path=rel_path) @dbus_signal(_DIN_QUERY, "s", rel_path_keyword="rel_path") def NoteAdded(self, path, rel_path=None): pass @dbus_method(_DIN_QUERY, "", "i", rel_path_keyword="rel_path") def GetResultCount(self, rel_path): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) return self._queries[num_id].get_result_count() @dbus_method(_DIN_QUERY, "", "", rel_path_keyword="rel_path", sender_keyword="sender") def Rewind(self, rel_path, sender): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) self._queries[num_id].rewind(sender) @dbus_method(_DIN_QUERY, "i", "", rel_path_keyword="rel_path", sender_keyword="sender") def Skip(self, num_entries, rel_path, sender): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) self._queries[num_id].skip(sender, num_entries) @dbus_method(_DIN_QUERY, "", "s", rel_path_keyword="rel_path", sender_keyword="sender") def GetNotePath(self, rel_path, sender): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) return self._queries[num_id].get_entry_path(sender) @dbus_method(_DIN_QUERY, "", "a{sv}", rel_path_keyword="rel_path", sender_keyword="sender") def GetResult(self, rel_path, sender): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) return self._queries[num_id].get_result(sender) @dbus_method(_DIN_QUERY, "i", "aa{sv}", rel_path_keyword="rel_path", sender_keyword="sender") def GetMultipleResults(self, num_entries, rel_path, sender): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) return self._queries[num_id].get_multiple_results(sender, num_entries) @dbus_method(_DIN_QUERY, "", "", rel_path_keyword="rel_path") def Dispose(self, rel_path): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) # Make sure no one else references the query handler before we remove our reference to it # Otherwise, garbage collection won't actually free its memory self._queries[num_id].dispose() self._queries.__delitem__(num_id) #----------------------------------------------------------------------------# class NoteDomain(Domain, GenericDomain): #----------------------------------------------------------------------------# name = _DOMAIN_NAME db_handler = None query_manager = None _dbus_path = None def __init__(self): """Creates a new NoteDomain instance""" self._dbus_path = _DBUS_PATH_NOTES self.db_handler = NotesDbHandler(self) self.query_manager = QueryManager(self.db_handler) # Initialize the D-Bus-Interface Domain.__init__( self, conn=busmap["opimd"], object_path=DBUS_PATH_BASE_FSO + '/' + self.name ) self.load_field_types() # Keep frameworkd happy self.interface = _DIN_NOTES self.path = _DBUS_PATH_NOTES #---------------------------------------------------------------------# # dbus methods and signals # #---------------------------------------------------------------------# def NewEntry(self, path): self.NewNote(path) @dbus_signal(_DIN_NOTES, "s") def NewNote(self, path): pass @dbus_method(_DIN_NOTES, "a{sv}", "s") def Add(self, entry_data): """Adds a entry to the list, assigning it to the default backend and saving it @param entry_data List of fields; format is [Key:Value, Key:Value, ...] @return Path of the newly created d-bus entry object""" return self.add(entry_data) @dbus_method(_DIN_NOTES, "a{sv}s", "s") def GetSingleEntrySingleField(self, query, field_name): """Returns the first entry found for a query, making it real easy to query simple things @param query The query object @param field_name The name of the field to return @return The requested data""" return self.get_single_entry_single_field(query, field_name) @dbus_method(_DIN_NOTES, "a{sv}", "s", sender_keyword="sender") def Query(self, query, sender): """Processes a query and returns the dbus path of the resulting query object @param query Query @param sender Unique name of the query sender on the bus @return dbus path of the query object, e.g. /org.freesmartphone.PIM/Entries/Queries/4""" return self.query_manager.process_query(query, sender) @dbus_method(_DIN_ENTRY, "", "a{sv}", rel_path_keyword="rel_path") def GetContent(self, rel_path): num_id = int(rel_path[1:]) # Make sure the requested entry exists self.check_entry_id(num_id) return self.get_content(num_id) @dbus_method(_DIN_ENTRY, "s", "a{sv}", rel_path_keyword="rel_path") def GetMultipleFields(self, field_list, rel_path): num_id = int(rel_path[1:]) return self.get_multiple_fields(num_id, field_list) @dbus_signal(_DIN_NOTES, "s") def DeletedNote(self, path): pass @dbus_signal(_DIN_ENTRY, "", rel_path_keyword="rel_path") def NoteDeleted(self, rel_path=None): pass def EntryDeleted(self, rel_path=None): self.NoteDeleted(rel_path=rel_path) self.DeletedNote(_DBUS_PATH_NOTES+rel_path) @dbus_method(_DIN_ENTRY, "", "", rel_path_keyword="rel_path") def Delete(self, rel_path): num_id = int(rel_path[1:]) self.delete(num_id) def EntryUpdated(self, data, rel_path=None): self.NoteUpdated(data, rel_path=rel_path) self.UpdatedNote(_DBUS_PATH_NOTES+rel_path, data) @dbus_signal(_DIN_NOTES, "sa{sv}") def UpdatedNote(self, path, data): pass @dbus_signal(_DIN_ENTRY, "a{sv}", rel_path_keyword="rel_path") def NoteUpdated(self, data, rel_path=None): pass @dbus_method(_DIN_ENTRY, "a{sv}", "", rel_path_keyword="rel_path") def Update(self, data, rel_path): num_id = int(rel_path[1:]) self.update(num_id, data) @dbus_method(_DIN_FIELDS, "ss", "") def AddField(self, name, type): self.add_new_field(name, type) @dbus_method(_DIN_FIELDS, "", "a{ss}") def ListFields(self): return self.list_fields() @dbus_method(_DIN_FIELDS, "s", "as") def ListFieldsWithType(self, type): return self.list_fields_with_type(type) @dbus_method(_DIN_FIELDS, "s", "") def DeleteField(self, name): self.remove_field(name) @dbus_method(_DIN_FIELDS, "s", "s") def GetType(self, name): return self.field_type_from_name(name) fso-frameworkd-0.10.1/framework/subsystems/opimd/pimd_tasks.py000066400000000000000000000250121174525413000245570ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """ Open PIM Daemon (C) 2008 by Soeren Apel (C) 2008 Openmoko, Inc. (C) 2009 Michael 'Mickey' Lauer (C) 2009 Sebastian Krzyszkowiak (C) 2009 Tom "TAsn" Hacohen GPLv2 or later Tasks Domain Plugin Establishes the 'tasks' PIM domain and handles all related requests """ from dbus.service import FallbackObject as DBusFBObject from dbus.service import signal as dbus_signal from dbus.service import method as dbus_method import re import logging logger = logging.getLogger('opimd') from domain_manager import DomainManager, Domain from helpers import * from opimd import * from query_manager import QueryMatcher, SingleQueryHandler from framework.config import config, busmap from pimd_generic import GenericDomain from db_handler import DbHandler #----------------------------------------------------------------------------# _DOMAIN_NAME = "Tasks" _DBUS_PATH_TASKS = DBUS_PATH_BASE_FSO + '/' + _DOMAIN_NAME _DIN_TASKS_BASE = DIN_BASE_FSO _DBUS_PATH_QUERIES = _DBUS_PATH_TASKS + '/Queries' _DIN_TASKS = _DIN_TASKS_BASE + '.' + 'Tasks' _DIN_ENTRY = _DIN_TASKS_BASE + '.' + 'Task' _DIN_QUERY = _DIN_TASKS_BASE + '.' + 'TaskQuery' _DIN_FIELDS = _DIN_TASKS_BASE + '.' + 'Fields' #----------------------------------------------------------------------------# class TasksDbHandler(DbHandler): #----------------------------------------------------------------------------# name = 'Tasks' domain = None #----------------------------------------------------------------------------# def __init__(self, domain): self.domain = domain self.db_prefix = self.name.lower() self.table_types = ['text', 'longtext', 'date', 'boolean'] super(TasksDbHandler, self).__init__() self.create_db() #----------------------------------------------------------------------------# class QueryManager(DBusFBObject): #----------------------------------------------------------------------------# _queries = None db_handler = None _next_query_id = None # Note: _queries must be a dict so we can remove queries without messing up query IDs def __init__(self, db_handler): """Creates a new QueryManager instance @param entries Set of Entry objects to use""" self.db_handler = db_handler self._queries = {} self._next_query_id = 0 # Initialize the D-Bus-Interface DBusFBObject.__init__( self, conn=busmap["opimd"], object_path=_DBUS_PATH_QUERIES ) # Still necessary? self.interface = _DIN_TASKS self.path = _DBUS_PATH_QUERIES def process_query(self, query, dbus_sender): """Handles a query and returns the dbus path of the newly created query result @param query Query to evaluate @param dbus_sender Sender's unique name on the bus @return dbus path of the query result""" query_handler = SingleQueryHandler(query, self.db_handler, dbus_sender) query_id = self._next_query_id self._next_query_id += 1 self._queries[query_id] = query_handler return _DBUS_PATH_QUERIES + '/' + str(query_id) def check_new_entry(self, entry_id): """Checks whether a newly added entry matches one or more queries so they can signal clients @param entry_id Task ID of the task that was added""" for (query_id, query_handler) in self._queries.items(): if query_handler.check_new_entry(entry_id): entry_path = self.id_to_path(entry_id) self.EntryAdded(entry_path, rel_path='/' + str(query_id)) def check_query_id_ok( self, num_id ): """ Checks whether a query ID is existing. Raises InvalidQueryID, if not. """ if not num_id in self._queries: raise InvalidQueryID( "Existing query IDs: %s" % self._queries.keys() ) def EntryAdded(self, path, rel_path=None): self.TaskAdded(path, rel_path=rel_path) @dbus_signal(_DIN_QUERY, "s", rel_path_keyword="rel_path") def TaskAdded(self, path, rel_path=None): pass @dbus_method(_DIN_QUERY, "", "i", rel_path_keyword="rel_path") def GetResultCount(self, rel_path): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) return self._queries[num_id].get_result_count() @dbus_method(_DIN_QUERY, "", "", rel_path_keyword="rel_path", sender_keyword="sender") def Rewind(self, rel_path, sender): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) self._queries[num_id].rewind(sender) @dbus_method(_DIN_QUERY, "i", "", rel_path_keyword="rel_path", sender_keyword="sender") def Skip(self, num_entries, rel_path, sender): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) self._queries[num_id].skip(sender, num_entries) @dbus_method(_DIN_QUERY, "", "s", rel_path_keyword="rel_path", sender_keyword="sender") def GetTaskPath(self, rel_path, sender): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) return self._queries[num_id].get_entry_path(sender) @dbus_method(_DIN_QUERY, "", "a{sv}", rel_path_keyword="rel_path", sender_keyword="sender") def GetResult(self, rel_path, sender): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) return self._queries[num_id].get_result(sender) @dbus_method(_DIN_QUERY, "i", "aa{sv}", rel_path_keyword="rel_path", sender_keyword="sender") def GetMultipleResults(self, num_entries, rel_path, sender): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) return self._queries[num_id].get_multiple_results(sender, num_entries) @dbus_method(_DIN_QUERY, "", "", rel_path_keyword="rel_path") def Dispose(self, rel_path): num_id = int(rel_path[1:]) self.check_query_id_ok( num_id ) # Make sure no one else references the query handler before we remove our reference to it # Otherwise, garbage collection won't actually free its memory self._queries[num_id].dispose() self._queries.__delitem__(num_id) #----------------------------------------------------------------------------# class TaskDomain(Domain, GenericDomain): #----------------------------------------------------------------------------# name = _DOMAIN_NAME db_handler = None query_manager = None _dbus_path = None def __init__(self): """Creates a new TaskDomain instance""" self._dbus_path = _DBUS_PATH_TASKS self.db_handler = TasksDbHandler(self) self.query_manager = QueryManager(self.db_handler) # Initialize the D-Bus-Interface Domain.__init__( self, conn=busmap["opimd"], object_path=DBUS_PATH_BASE_FSO + '/' + self.name ) self.load_field_types() # Keep frameworkd happy self.interface = _DIN_TASKS self.path = _DBUS_PATH_TASKS #---------------------------------------------------------------------# # dbus methods and signals # #---------------------------------------------------------------------# def NewEntry(self, path): self.NewTask(path) @dbus_signal(_DIN_TASKS, "s") def NewTask(self, path): pass @dbus_method(_DIN_TASKS, "a{sv}", "s") def Add(self, entry_data): """Adds a entry to the list, assigning it to the default backend and saving it @param entry_data List of fields; format is [Key:Value, Key:Value, ...] @return Path of the newly created d-bus entry object""" return self.add(entry_data) @dbus_method(_DIN_TASKS, "a{sv}s", "s") def GetSingleEntrySingleField(self, query, field_name): """Returns the first entry found for a query, making it real easy to query simple things @param query The query object @param field_name The name of the field to return @return The requested data""" return self.get_single_entry_single_field(query, field_name) @dbus_method(_DIN_TASKS, "a{sv}", "s", sender_keyword="sender") def Query(self, query, sender): """Processes a query and returns the dbus path of the resulting query object @param query Query @param sender Unique name of the query sender on the bus @return dbus path of the query object, e.g. /org.freesmartphone.PIM/Entries/Queries/4""" return self.query_manager.process_query(query, sender) @dbus_method(_DIN_ENTRY, "", "a{sv}", rel_path_keyword="rel_path") def GetContent(self, rel_path): num_id = int(rel_path[1:]) # Make sure the requested entry exists self.check_entry_id(num_id) return self.get_content(num_id) @dbus_method(_DIN_ENTRY, "s", "a{sv}", rel_path_keyword="rel_path") def GetMultipleFields(self, field_list, rel_path): num_id = int(rel_path[1:]) return self.get_multiple_fields(num_id, field_list) @dbus_signal(_DIN_TASKS, "s") def DeletedTask(self, path): pass @dbus_signal(_DIN_ENTRY, "", rel_path_keyword="rel_path") def TaskDeleted(self, rel_path=None): pass def EntryDeleted(self, rel_path=None): self.TaskDeleted(rel_path=rel_path) self.DeletedTask(_DBUS_PATH_TASKS+rel_path) @dbus_method(_DIN_ENTRY, "", "", rel_path_keyword="rel_path") def Delete(self, rel_path): num_id = int(rel_path[1:]) self.delete(num_id) def EntryUpdated(self, data, rel_path=None): self.TaskUpdated(data, rel_path=rel_path) self.UpdatedTask(_DBUS_PATH_TASKS+rel_path, data) @dbus_signal(_DIN_TASKS, "sa{sv}") def UpdatedTask(self, path, data): pass @dbus_signal(_DIN_ENTRY, "a{sv}", rel_path_keyword="rel_path") def TaskUpdated(self, data, rel_path=None): pass @dbus_method(_DIN_ENTRY, "a{sv}", "", rel_path_keyword="rel_path") def Update(self, data, rel_path): num_id = int(rel_path[1:]) self.update(num_id, data) @dbus_method(_DIN_FIELDS, "ss", "") def AddField(self, name, type): self.add_new_field(name, type) @dbus_method(_DIN_FIELDS, "", "a{ss}") def ListFields(self): return self.list_fields() @dbus_method(_DIN_FIELDS, "s", "as") def ListFieldsWithType(self, type): return self.list_fields_with_type(type) @dbus_method(_DIN_FIELDS, "s", "") def DeleteField(self, name): self.remove_field(name) @dbus_method(_DIN_FIELDS, "s", "s") def GetType(self, name): return self.field_type_from_name(name) fso-frameworkd-0.10.1/framework/subsystems/opimd/query_manager.py000066400000000000000000000233771174525413000252740ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Open PIM Daemon # Query Plugin Manager # # http://freesmartphone.org/ # # Copyright (C) 2008 by Soeren Apel (abraxa@dar-clan.de) # Copyright (C) 2008-2009 by Openmoko, Inc. # Copyright (C) 2009 Michael 'Mickey' Lauer # Copyright (C) 2009 Sebastian dos Krzyszkowiak # Copyright (C) 2009 Tom "TAsn" Hacohen # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # """opimd Query Plugin Manager""" MODULE_NAME = "opimd" from dbus.service import FallbackObject as DBusFBObject from helpers import * from operator import itemgetter import db_handler import logging logger = logging.getLogger( MODULE_NAME ) class BaseQueryMatcher(object): query_obj = None def __init__(self, query): """Evaluates a query @param query Query to evaluate, must be a dict""" self.query_obj = query def match(self, db_handler): """Tries to match a db_handler to the current query @param a db_handler @return List of entry IDs that match""" assert(self.query_obj, "Query object is empty, cannot match!") matches = [] #----------------------------------------------------------------------------# class QueryMatcher(BaseQueryMatcher): #----------------------------------------------------------------------------# def match(self, db_handler): """Tries to match a db_handler to the current query @param a db_handler @return List of entry IDs that match""" BaseQueryMatcher.match(self, db_handler) return db_handler.query(self.query_obj) #----------------------------------------------------------------------------# class RawSQLQueryMatcher(BaseQueryMatcher): #----------------------------------------------------------------------------# def match(self, db_handler): """Tries to match a db_handler to the current query @param a db_handler @return List of entry IDs that match""" def match(self, db_handler): """Tries to match a db_handler to the current query @param a db_handler @return List of entry IDs that match""" BaseQueryMatcher.match(self, db_handler) return db_handler.raw_sql(self.query_obj) #----------------------------------------------------------------------------# class BaseQueryHandler(object): """A base query handler to extend from.""" #----------------------------------------------------------------------------# db_handler = None query = None # The query this handler is processing _entries = None cursors = None # The next entry we'll serve, depending on the client calling us def __init__(self, query, db_handler, matcher, dbus_sender): """Creates a new BaseQueryHandler instance @param query Query to evaluate @param db_handler database handler @param matcher prebuilt query matcher @param dbus_sender Sender's unique name on the bus""" self.query = query self.sanitize_query() self.db_handler = db_handler self._entries = matcher.match(self.db_handler) self.cursors = {} # TODO Register with all entries to receive updates def dispose(self): """Unregisters from all entries to allow this instance to be eaten by GC""" # TODO Unregister from all entries pass def sanitize_query(self): """Makes sure the query meets the criteria that related code uses to omit wasteful sanity checks""" # For get_result_and_advance(): # Make sure the _result_fields list has no whitespaces, e.g. "a, b, c" should be "a,b,c" # Reasoning: entry.get_fields() has no fuzzy matching for performance reasons # Also, we remove any empty list elements created by e.g. "a, b, c," try: field_list = self.query['_result_fields'] fields = field_list.split(',') new_field_list = [] for field_name in fields: field_name = field_name.strip() if field_name: new_field_list.append(field_name) self.query['_result_fields'] = ','.join(new_field_list) except KeyError: # There's no _result_fields entry to sanitize pass def get_result_count(self): """Determines the number of results for this query @return Number of result entries""" return len(self._entries) def rewind(self, dbus_sender): """Resets the cursor for a given d-bus sender to the first result entry @param dbus_sender Sender's unique name on the bus""" self.cursors[dbus_sender] = 0 def skip(self, dbus_sender, num_entries): """Skips n result entries of the result set @param dbus_sender Sender's unique name on the bus @param num_entries Number of result entries to skip""" if not self.cursors.has_key(dbus_sender): self.cursors[dbus_sender] = 0 self.cursors[dbus_sender] += num_entries def get_entry_path(self, dbus_sender): """Determines the Path of the next entry that the cursor points at and advances to the next result entry @param dbus_sender Sender's unique name on the bus @return Path of the entry""" # If the sender is not in the list of cursors it just means that it is starting to iterate if not self.cursors.has_key(dbus_sender): self.cursors[dbus_sender] = 0 # Check whether we've reached the end of the entry list try: result = self._entries[self.cursors[dbus_sender]] except IndexError: raise NoMoreEntries( "All results have been submitted" ) self.cursors[dbus_sender] += 1 return result['Path'] def get_result(self, dbus_sender): """Extracts the requested fields from the next entry in the result set and advances the cursor @param dbus_sender Sender's unique name on the bus @return Dict containing field_name/field_value pairs""" # If the sender is not in the list of cursors it just means that it is starting to iterate if not self.cursors.has_key(dbus_sender): self.cursors[dbus_sender] = 0 # Check whether we've reached the end of the entry list try: result = self._entries[self.cursors[dbus_sender]] except IndexError: raise NoMoreEntries( "All results have been submitted" ) self.cursors[dbus_sender] += 1 return result def get_multiple_results(self, dbus_sender, num_entries): """Creates a list containing n dicts which represent the corresponding entries from the result set @note If there are less entries than num_entries, only the available entries will be returned @param dbus_sender Sender's unique name on the bus @param num_entries Number of result set entries to return @return List of dicts with field_name/field_value pairs""" result = [] if num_entries < 0: num_entries = self.get_result_count() for i in range(num_entries): try: entry = self.get_result(dbus_sender) result.append(entry) except NoMoreEntries: """Don't want to raise an error in that case""" break return result def check_new_entry(self, entry_id): """Checks whether a newly added entry matches this so it can signal clients @param entry_id entry ID of the entry that was added @return True if entry matches this query, False otherwise @todo Currently this messes up the order of the result set if a specific order was desired""" return False # TODO Register with the new entry to receive changes # We *should* reset all cursors *if* the result set is ordered, however # in order to prevent confusion, this is left for the client to do. # Rationale: clients with unordered queries can just use get_result() # and be done with it. For those, theres's no need to re-read all results. # Let clients know that this result set changed #----------------------------------------------------------------------------# class SingleQueryHandler(BaseQueryHandler): """Handles a single dictionary based query.""" #----------------------------------------------------------------------------# def __init__(self, query, db_handler, dbus_sender): """Creates a new SingleQueryHandler instance @param query Query to evaluate @param entries Set of Entry objects to use @param dbus_sender Sender's unique name on the bus""" BaseQueryHandler.__init__(self, query, db_handler, QueryMatcher(query), dbus_sender) class SingleRawSQLQueryHandler(BaseQueryHandler): """Handles a single raw SQL based query.""" def __init__(self, query, db_handler, dbus_sender): """Creates a new SingleRawSQLQueryHandler instance @param query Query to evaluate @param db_handler database handler @param dbus_sender Sender's unique name on the bus""" BaseQueryHandler.__init__(self, query, db_handler, RawSQLQueryMatcher(query), dbus_sender) fso-frameworkd-0.10.1/framework/subsystems/opimd/type_manager.py000066400000000000000000000043051174525413000250760ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """ Open PIM Daemon (C) 2009 by Sebastian Krzyszkowiak GPLv2 or later Type manager """ DBUS_BUS_NAME_FSO = "org.freesmartphone.opimd" DBUS_PATH_BASE_FSO = "/org/freesmartphone/PIM" DIN_BASE_FSO = "org.freesmartphone.PIM" from domain_manager import DomainManager from helpers import * from framework.config import config, busmap from dbus.service import FallbackObject as DBusFBObject from dbus.service import signal as dbus_signal from dbus.service import method as dbus_method import re import logging logger = logging.getLogger('opimd') #----------------------------------------------------------------------------# _DBUS_PATH_TYPES = DBUS_PATH_BASE_FSO + '/Types' _DIN_TYPES = DIN_BASE_FSO + '.Types' #Consist of type and python type (latter is for internal use) #Allowed: int, str, unicode, float (and long?). # unicode should be used for all user related strings that may have unicode # in, even phonenumber _TYPES = { 'objectpath': str, 'phonenumber': unicode, 'number': float, 'integer': int, 'address': unicode, 'email': unicode, 'name': unicode, 'date': int, 'uri': unicode, 'photo': unicode, 'text': unicode, 'longtext': unicode, 'boolean': int, 'timezone': unicode, 'entryid': int, 'generic': unicode } #----------------------------------------------------------------------------# class TypeManager(DBusFBObject): #----------------------------------------------------------------------------# Types = _TYPES def __init__(self): """Initializes the type manager""" # Initialize the D-Bus-Interface DBusFBObject.__init__( self, conn=busmap["opimd"], object_path=_DBUS_PATH_TYPES ) # Still necessary? self.interface = _DIN_TYPES self.path = _DBUS_PATH_TYPES @dbus_method(_DIN_TYPES, "", "as") def List(self): return self.Types fso-frameworkd-0.10.1/framework/subsystems/opreferencesd/000077500000000000000000000000001174525413000235635ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/opreferencesd/__init__.py000066400000000000000000000001111174525413000256650ustar00rootroot00000000000000 # The opreferencesd module from opreferences import PreferencesManager fso-frameworkd-0.10.1/framework/subsystems/opreferencesd/configuration.py000066400000000000000000000024731174525413000270120ustar00rootroot00000000000000""" The Preference Deamon - Python Implementation (C) 2008 Guillaume 'Charlie' Chereau (C) 2008 Openmoko, Inc. GPLv2 or later Package: opreferencesd Module: opreferences """ import logging logger = logging.getLogger('opreferencesd') import yaml # To parse the yaml files class Configuration(dict): """Configuration dict associated to a conf file A Configuration is a map of key -> values. It can be synchronized with a conf file in the yaml format. A typical conf file looks like this : vibration: Yes ring-volume: 9 """ def __init__(self, file): self.file = file conf = open(file).read() conf = yaml.load(conf) self.update(conf) def __setitem__(self, name, value): logger.debug("Set value %s = %s (%s)", name, value, type(value)) super(Configuration, self).__setitem__(name, value) # For the moment we update the file each time we modify a value # It is not optimal if you set a lot of values self.flush() def flush(self): """Save the content of the configuration into its file""" logger.debug('flushing %s' % self.file) file = open(self.file, 'w') file.write(yaml.dump(dict(self), default_flow_style=False)) file.close() fso-frameworkd-0.10.1/framework/subsystems/opreferencesd/helpers.py000066400000000000000000000004111174525413000255730ustar00rootroot00000000000000 import dbus DBUS_PREFERENCES_OBJECT_PATH = "/org/freesmartphone/Preferences" DBUS_PREFERENCES_INTERFACE = "org.freesmartphone.opreferencesd" class PreferencesException(dbus.DBusException): _dbus_error_name = 'org.freesmartphone.opreferencesd.Exception' fso-frameworkd-0.10.1/framework/subsystems/opreferencesd/opreferences.py000066400000000000000000000147331174525413000266250ustar00rootroot00000000000000#!/usr/bin/env python """ The Preference Deamon - Python Implementation (C) 2008 Guillaume 'Charlie' Chereau (C) 2008 Openmoko, Inc. GPLv2 or later Package: opreferencesd Module: opreferences """ __version__ = "0.2.0" import yaml # To parse the yaml files import os # All the dbus modules import dbus import dbus.service import dbus.mainloop.glib import gobject from framework.config import config, rootdir rootdir = os.path.join( rootdir, 'opreferences' ) from schema import Schema, Parameter from service import Service, NoServiceError from configuration import Configuration import logging logger = logging.getLogger('opreferencesd') class DBusNoServiceError(dbus.DBusException): _dbus_error_name = "org.freesmartphone.Preferences.NoServiceError" #============================================================================# class PreferencesManager(dbus.service.Object): #============================================================================# """This is the class for the main object from wich we access the configuration services """ def __init__(self, bus, schema_dir = './schema', conf_dir = './conf'): """Create a PreferencesManager object arguments: schema_dir -- The directory containing the schema files conf_dir -- The directory containing the configuration files """ self.path = "/org/freesmartphone/Preferences" super(PreferencesManager, self).__init__(bus, self.path) self.interface = "org.freesmartphone.Preferences" self.bus = bus self.schema_dir = schema_dir self.conf_dir = conf_dir self.profiles = ['default'] self.services = {} logger.info( "%s %s initialized. Serving %s at %s", self.__class__.__name__, __version__, self.interface, self.path ) logger.info(" ::: using schema path : %s", schema_dir) logger.info(" ::: using conf path : %s", conf_dir) logger.info(" ::: services : %s", self.GetServices()) @dbus.service.method("org.freesmartphone.Preferences", in_signature='', out_signature='as') def GetServices(self): """Return the list of all available services""" ret = [] for f in os.listdir(self.schema_dir): if f.endswith('.yaml'): ret.append(f[:-5]) return ret @dbus.service.method("org.freesmartphone.Preferences", in_signature='s', out_signature='o') def GetService(self, name): """Return a given service arguments: name -- the name of the service, as returned by `GetServices` """ logger.info("GetService %s", name) name = str(name) if name in self.services: return self.services[name] try: ret = Service(self, name) except NoServiceError: logger.error("service does not exist : %s", name) raise DBusNoServiceError, name self.services[name] = ret return ret @dbus.service.method("org.freesmartphone.Preferences", in_signature='', out_signature='s') def GetProfile(self): """Retrieve the current top profile""" return self.profiles[0] @dbus.service.method("org.freesmartphone.Preferences", in_signature='s', out_signature='') def SetProfile(self, profile): """Set the current profile""" logger.info("SetProfile to %s", profile) profile = str(profile) assert profile in self.GetProfiles() self.profiles = [profile, 'default'] for s in self.services.itervalues(): s.on_profile_changed(profile) self.Changed(profile) # Let the system know the profile changed @dbus.service.method("org.freesmartphone.Preferences", in_signature='', out_signature='as') def GetProfiles(self): """Return a list of all the available profiles""" profiles_service = self.GetService('profiles') return profiles_service.GetValue('profiles') @dbus.service.signal("org.freesmartphone.Preferences", signature='s') def Changed(self, profile): """Signal used to notify of a profile change""" logger.debug("Notify change in profile to '%s'", profile) #============================================================================# def generate_doc(): #============================================================================# """This function can be used to generate a wiki style documentation for the DBus API It should be replaced by doxygen """ objects = [PreferencesManager, Service] services = {} for obj in objects: for attr_name in dir(obj): attr = getattr(obj, attr_name) if hasattr(attr, '_dbus_interface'): if hasattr(attr, '_dbus_is_method'): func = {} func['name'] = attr_name func['args'] = ','.join(attr._dbus_args) func['in_sig'] = attr._dbus_in_signature func['out_sig'] = attr._dbus_out_signature func['doc'] = attr.__doc__ funcs, sigs = services.setdefault(attr._dbus_interface, [[],[]]) funcs.append(func) if hasattr(attr, '_dbus_is_signal'): sig = {} sig['name'] = attr_name sig['args'] = ','.join(attr._dbus_args) sig['sig'] = attr._dbus_signature sig['doc'] = attr.__doc__ funcs, sigs = services.setdefault(attr._dbus_interface, [[],[]]) sigs.append(sig) for name, funcs in services.items(): print '= %s =' % name for func in funcs[0]: print """ == method %(name)s(%(args)s) == * in: %(in_sig)s * out: %(out_sig)s * %(doc)s""" % func for sig in funcs[1]: print """ == signal %(name)s(%(args)s) == * out: %(sig)s * %(doc)s""" % sig print #============================================================================# def factory(prefix, controller): #============================================================================# """This is the magic function that will be called bye the framework module manager""" # Get the root dir containing the schema and conf dirs # We can set a list of possible path in the config file schema_dir = os.path.join( rootdir, 'schema' ) conf_dir = os.path.join( rootdir, 'conf' ) pref_manager = PreferencesManager(controller.bus, schema_dir, conf_dir) return [pref_manager] fso-frameworkd-0.10.1/framework/subsystems/opreferencesd/schema.py000066400000000000000000000051411174525413000253760ustar00rootroot00000000000000 from framework import helpers import dbus import yaml # To parse the yaml files class Schema(dict): """A Schema is used to define the properties of parameters A Schema is always associated with a file in the yaml format. A example schema file looks like this : vibration: # The name of the parameter type: bool # The type default: yes # default value profilable: yes # set to yes if the parameter depends of the profile ring-volume: type: int default: 10 profilable: yes """ # This map the type string to the actual python types str_to_types = {'int' : int, 'bool' : bool, 'str' : str, 'var': object, 'dict': dict, 'list':list} types_to_str = dict( (v,k) for k,v in str_to_types.iteritems() ) @classmethod def from_dict(cls, d): ret = cls() for key,v in d.iteritems(): ret[key] = Parameter.from_dict(v) return ret @classmethod def from_file(cls, file): file = open(file).read() dict = yaml.load(file) return cls.from_dict(dict) class Parameter(object): """Represents a parameter description in a schema file""" def __init__(self, type, default = None, profilable = False): self.type = type self.default = default self.profilable = profilable def __repr__(self): return repr((self.type, self.default, self.profilable)) @classmethod def from_dict(cls, d): """Create a new parameter from a dictionary""" type = d.get('type', 'var') type = Schema.str_to_types[type] default = d.get('default', None) profilable = d.get('profilable', False) return cls(type, default, profilable) def to_dbus(self, v): """Convert a value to a dbus object of the parameter type""" if self.type == int: return dbus.Int32(v) if self.type == str: return dbus.String(v) if self.type == bool: return dbus.Boolean(v) if self.type == dict: return dbus.Dictionary(v, 'sv') if self.type == list: return dbus.Array(v, 'v') if self.type == object: return v if v is None: return '' raise TypeError, "can't convert parameter of type %s to dbus object" % self.type def from_dbus(self, v): v = helpers.dbus_to_python(v) if isinstance(v, self.type): return v else: raise TypeError, "type %s does not match schema (%s)" % (type(v), self.type) fso-frameworkd-0.10.1/framework/subsystems/opreferencesd/service.py000066400000000000000000000137111174525413000256000ustar00rootroot00000000000000 # All the dbus modules import dbus import dbus.service from schema import Schema from configuration import Configuration from framework import helpers import logging logger = logging.getLogger('opreferencesd') class NoServiceError(Exception): pass class NoValueError(Exception): pass class Service(dbus.service.Object): """ Class that deals with configuration values of a given service The service can set and get the value of parameters. The services are used to group related parameters together. Basically, every application using the config server should use its own service name. For each service we need a schema file describing the parameters the service provides. The configurations values are stored in yaml file. Each conf file contains all the parameters for a given service in a given context. The conf files are organised with the following file hierachy : conf/$(service)/$(profile).yaml All the parameters that are independant of the profile are stored in the 'default' profile file. When we set or get parameters, the service server takes into account the current profile, so the applications using the service don't need to know about the current profile. """ def __init__(self, manager, name): self.manager = manager self.name = name try: self.schema = Schema.from_file('%s/%s.yaml' % (self.manager.schema_dir, name)) except IOError: raise NoServiceError # Only at this point we can safely register the dbus object super(Service, self).__init__(manager.bus, '%s/%s' % ('/org/freesmartphone/Preferences', name)) self.confs = {} # all the conf files def __str__(self): return self.name def get_conf(self, profile): """Return the conf instance for a given profile""" if profile in self.confs: return self.confs[profile] try: conf_path = '%s/%s/%s.yaml' % (self.manager.conf_dir, self.name, profile) conf = Configuration(conf_path) except IOError: logger.info("no conf file : '%s'", conf_path) conf = None self.confs[profile] = conf return conf @dbus.service.method('org.freesmartphone.Preferences.Service', in_signature='', out_signature='as') def GetKeys(self): """Retrieve all the keys of the service This method should be used only for introspection purpose. """ # Here we have to be careful, because if we just return the keys actually in the configuration file, # we ommit to add the keys that are not set but have default value. # On the other hand, if we only return the keys defined in the schema, we ommit the keys that are in a dictionary. # So what we do is return the union of the actual keys and the keys defined in the schema ret = set(self.schema.keys()) return list(ret) @dbus.service.method('org.freesmartphone.Preferences.Service', in_signature='s', out_signature='v') def GetValue(self, key): """get a parameter value arguments: key -- the name of the key """ key = str(key) logger.debug("Service %s : Getting key %s", self, key) parameter = self.schema[key] for profile in self.manager.profiles: try: conf = self.get_conf(profile) if not conf: # There is no conf file for this profile continue ret = conf[key] break except KeyError: pass else: logger.info("Service %s : can't find key %s, using default", self, key) ret = parameter.default if ret is None: raise NoValueError # We have to call this method to give the proper type to the ret value ret = parameter.to_dbus(ret) return ret @dbus.service.method('org.freesmartphone.Preferences.Service', in_signature='sv', out_signature='') @helpers.exceptionlogger def SetValue(self, key, value): """set a parameter value for a service, in the current profile""" key = str(key) logger.debug("Service %s : Setting key %s = %s", self, key, value) parameter = self.schema[key] # XXX: # Here we always set the value into the top profile configuration file # Shouldn't we use the profile that actually defines the value if there is one ? profile = self.manager.profiles[0] if parameter.profilable else 'default' value = parameter.from_dbus(value) conf = self.get_conf(profile) conf[key] = value self.Notify(key, value) # We don't forget to notify the listeners @dbus.service.method('org.freesmartphone.Preferences.Service', in_signature='s', out_signature='b') def IsProfilable(self, key): """Return true if a parameter depends on the current profile""" key = str(key) parameter = self.schema[key] return parameter.profilable @dbus.service.method('org.freesmartphone.Preferences.Service', in_signature='s', out_signature='s') def GetType(self, key): """Return a string representing the type of the parameter""" key = str(key) parameter = self.schema[key] return Schema.types_to_str[parameter.type] @dbus.service.signal('org.freesmartphone.Preferences.Service', signature='sv') def Notify(self, key, value): """signal used to notify a parameter change""" logger.debug("Notify change in value %s/%s", self.name, key) def on_profile_changed(self, profile): """called everytime we the global profile is changed""" for key in self.GetKeys(): if self.IsProfilable(key): self.Notify(key, self.GetValue(key)) fso-frameworkd-0.10.1/framework/subsystems/otimed/000077500000000000000000000000001174525413000222205ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/otimed/__init__.py000066400000000000000000000000021174525413000243210ustar00rootroot00000000000000 fso-frameworkd-0.10.1/framework/subsystems/otimed/alarm.py000066400000000000000000000135521174525413000236740ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: UTF-8 -*- """ Open Time Daemon - Alarm Support (C) 2008 Jan 'Shoragan' Lübbe (C) 2008 Openmoko, Inc. GPLv2 or later Package: otimed Module: alarm """ MODULE_NAME = "otimed.alarm" __version__ = "0.1.4.1" DBUS_INTERFACE_PREFIX = "org.freesmartphone.Time.Alarm" DBUS_PATH_PREFIX = "/org/freesmartphone/Time/Alarm" import dbus import dbus.service import gobject import os, sys, time from heapq import heappush, heappop, heapify import logging logger = logging.getLogger( MODULE_NAME ) import framework def drop_dbus_result( *args ): if args: logger.warning( "unhandled dbus result: %s", args ) def log_dbus_error( desc ): def dbus_error( e, desc = desc ): logger.error( "%s (%s %s: %s)" % ( desc, e.__class__.__name__, e.get_dbus_name(), e.get_dbus_message() ) ) return dbus_error #----------------------------------------------------------------------------# class Alarm( object ): #----------------------------------------------------------------------------# """This class represents an alarm that has been set by an application Every application can have only one Alarm set. If the application has no well-known bus name, the Alarm will be cleared when then application disconnects from the bus. """ def __init__( self, bus, busname, timestamp ): self.bus = bus self.busname = busname self.timestamp = timestamp def __cmp__( self, other ): return cmp( self.timestamp, other.timestamp ) def _replyCallback( self ): pass def fire( self ): proxy = self.bus.get_object( self.busname, "/" ) iface = dbus.Interface( proxy, "org.freesmartphone.Notification" ) iface.Alarm( reply_handler=drop_dbus_result, error_handler=log_dbus_error( "error while calling Alarm on %s" % self.busname ) ) #----------------------------------------------------------------------------# class AlarmController( dbus.service.Object ): #----------------------------------------------------------------------------# """A Dbus Object implementing org.freesmartphone.Time.Alarm""" DBUS_INTERFACE = DBUS_INTERFACE_PREFIX def __init__( self, bus ): self.bus = bus self.interface = self.DBUS_INTERFACE self.path = DBUS_PATH_PREFIX super(AlarmController, self).__init__( bus, self.path ) self.alarms = {} self.queue = [] self.timer = None bus.add_signal_receiver( self._nameOwnerChangedHandler, "NameOwnerChanged", dbus.BUS_DAEMON_IFACE, dbus.BUS_DAEMON_NAME, dbus.BUS_DAEMON_PATH ) # gather realtime clock dbus object o = bus.get_object( "org.freesmartphone.odeviced", "/org/freesmartphone/Device/RTC/0", follow_name_owner_changes=True, introspect=False ) self.rtc = dbus.Interface( o, "org.freesmartphone.Device.RealtimeClock" ) logger.info( "%s %s initialized. Serving %s at %s", self.__class__.__name__, __version__, self.interface, self.path ) def _nameOwnerChangedHandler( self, name, old_owner, new_owner ): # TODO what happens when something changes it busname? if old_owner and not new_owner: if old_owner[0] == ':': self.ClearAlarm( old_owner, old_owner ) def _verifyNameOwner( self, wellkown, connection ): if wellkown == connection: return True proxy = self.bus.get_object( dbus.BUS_DAEMON_NAME, dbus.BUS_DAEMON_PATH ) iface = dbus.Interface( proxy, dbus.BUS_DAEMON_IFACE ) return connection == iface.GetNameOwner( wellkown ) def _schedule( self ): now = int(time.time()) if not self.timer is None: gobject.source_remove( self.timer ) self.timer = None while self.queue and self.queue[0].timestamp <= now: alarm = heappop( self.queue ) del self.alarms[ alarm.busname ] alarm.fire() if self.queue: self.timer = gobject.timeout_add_seconds( self.queue[0].timestamp - int(time.time()), self._schedule ) self.rtc.SetWakeupTime( self.queue[0].timestamp, reply_handler=drop_dbus_result, error_handler=log_dbus_error( "RTC error; can not set wakeup time" ) ) # # dbus methods # @dbus.service.method( DBUS_INTERFACE, "si", "", sender_keyword='sender' ) # TODO maybe something bigger than int32 def SetAlarm( self, busname, timestamp, sender ): if not busname: busname = sender if not busname[0] == ':' and not self._verifyNameOwner( busname, sender ): raise dbus.DBusException( "The bus name %s is not owned by %s" % (busname, sender) ) old = self.alarms.get( busname, None ) alarm = Alarm( self.bus, busname, timestamp ) self.alarms[ busname ] = alarm if old is None: heappush(self.queue, alarm) else: self.queue[self.queue.index( old )] = alarm heapify( self.queue ) self._schedule() @dbus.service.method( DBUS_INTERFACE, "s", "", sender_keyword='sender' ) def ClearAlarm( self, busname, sender ): if not self._verifyNameOwner( busname, sender ): raise dbus.DBusException( "The bus name %s is not owned by %s" % (busname, sender) ) old = self.alarms.pop( busname, None ) if not old is None: self.queue.remove( old ) heapify( self.queue ) self._schedule() #----------------------------------------------------------------------------# def factory( prefix, controller ): #----------------------------------------------------------------------------# objects = [] objects.append( AlarmController( controller.bus ) ) return objects fso-frameworkd-0.10.1/framework/subsystems/otimed/clock.py000066400000000000000000000025501174525413000236670ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ Open Time Daemon - Kernel clock interface (C) 2009 Jan 'Shoragan' Lübbe (C) 2009 Openmoko, Inc. GPLv2 or later Package: otimed Module: clock """ from ctypes import * from ctypes.util import find_library import math ADJ_OFFSET = 0x0001 class TIMEVAL(Structure): _fields_ = [ ("sec", c_long), ("usec", c_long), ] class TIMEX(Structure): _fields_ = [ ("modes", c_int), ("offset", c_long), ("freq", c_long), ("maxerror", c_long), ("esterror", c_long), ("status", c_int), ("constant", c_long), ("precision", c_long), ("tolerance", c_long), ("time", TIMEVAL), ("tick", c_long), ("ppsfreq", c_long), ("jitter", c_long), ("shift", c_int), ("stabil", c_long), ("jitcnt", c_long), ("calcnt", c_long), ("errcnt", c_long), ("stbcnt", c_long), ("reserved", c_int32 * 12), ] libc = CDLL(find_library("c")) def adjust(delta): tx = TIMEX() print libc.adjtimex(byref(tx)) dsec = int(math.floor(delta)) dusec = int( (delta - dsec) * 1000000 ) usec = tx.time.usec + dusec overflow, usec = divmod( usec, 1000000 ) sec = tx.time.sec + dsec + overflow tv = TIMEVAL(sec, usec) print libc.settimeofday(byref(tv), None) fso-frameworkd-0.10.1/framework/subsystems/otimed/otimed.py000066400000000000000000000265641174525413000240700ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: UTF-8 -*- """ The Time Daemon - Python Implementation (C) 2008 Guillaume 'Charlie' Chereau (C) 2008 Jan 'Shoragan' Lübbe (C) 2008 Openmoko, Inc. GPLv2 or later Package: otimed Module: otimed """ __version__ = "0.2.2.1" MODULE_NAME = "otimed" import clock from framework.config import config from framework.patterns import dbuscache from datetime import datetime, timedelta from math import sqrt import os import shutil import socket import struct import time # All the dbus modules import dbus import dbus.service import dbus.mainloop.glib import gobject import logging logger = logging.getLogger( MODULE_NAME ) def getOutput(cmd): from subprocess import Popen, PIPE return Popen(cmd, shell=True, stdout=PIPE).communicate()[0] def toSeconds( delta ): return delta.days*24*60*60+delta.seconds+delta.microseconds*0.000001 def drop_dbus_result( *args ): if args: logger.warning( "unhandled dbus result: %s", args ) def log_dbus_error( desc ): def dbus_error( e, desc = desc ): logger.error( "%s (%s %s: %s)" % ( desc, e.__class__.__name__, e.get_dbus_name(), e.get_dbus_message() ) ) return dbus_error #============================================================================# class TimeSource( object ): #============================================================================# def __init__( self, bus ): self.offset = None self.bus = bus #============================================================================# class GPSTimeSource( TimeSource ): #============================================================================# def __init__( self, bus ): TimeSource.__init__( self, bus ) self.invalidTimeout = None self.bus.add_signal_receiver( self._handleTimeChanged, "TimeChanged", "org.freedesktop.Gypsy.Time", None, None ) def _handleTimeChanged( self, t ): if t: self.offset = datetime.utcfromtimestamp( t ) - datetime.utcnow() logger.debug( "GPS: offset=%f", toSeconds( self.offset ) ) else: self.offset = None logger.debug( "GPS: time invalid" ) if not self.invalidTimeout is None: gobject.source_remove( self.invalidTimeout ) self.invalidTimeout = gobject.timeout_add_seconds( 300, self._handleInvalidTimeout ) def _handleInvalidTimeout( self ): self.offset = None self.invalidTimeout = None logger.debug( "GPS: timeout" ) return False def __repr__( self ): return "<%s>" % ( self.__class__.__name__, ) #============================================================================# class NTPTimeSource( TimeSource ): #============================================================================# def __init__( self, bus, server, interval = 600 ): TimeSource.__init__( self, bus ) self.server = server self.interval = interval self.socket = socket.socket( socket.AF_INET, socket.SOCK_DGRAM ) self.socket.bind( ('', 123) ) self.socket.setblocking( False ) self.updateTimeout = gobject.timeout_add_seconds( self.interval, self._handleUpdateTimeout ) self.dataWatch = gobject.io_add_watch( self.socket.makefile(), gobject.IO_IN, self._handleData ) self._handleUpdateTimeout() def _handleUpdateTimeout( self ): logger.debug( "NTP: requesting timestamp" ) self.offset = None # FIXME do everything async data = '\x1b' + 47 * '\0' try: self.socket.sendto( data, ( self.server, 123 )) except: logger.debug( "NTP: failed to request timestamp" ) # reenable timeout return True def _handleData( self, source, condition ): epoch = 2208988800L data, address = self.socket.recvfrom( 1024 ) if data: s, f = struct.unpack( '!12I', data )[10:12] s -= epoch t = s + f/(2.0**32) self.offset = datetime.utcfromtimestamp( t ) - datetime.utcnow() logger.debug( "NTP: offset=%f", toSeconds( self.offset ) ) else: self.offset = None logger.warning( "NTP: no timestamp received" ) def __repr__( self ): return "<%s checking %s every %s seconds>" % ( self.__class__.__name__, self.server, self.interval ) #============================================================================# class GSMZoneSource( object ): #============================================================================# TIMEOUT = 24*60*60 def __init__( self, bus ): self.zonetab = [] self.zone = None self.mccmnc = None self.mccmnc_ts = 0.0 self.isocode = None self.offset = None self.offset_ts = 0.0 self.bus = bus self.bus.add_signal_receiver( self._handleNetworkStatusChanged, "Status", "org.freesmartphone.GSM.Network", None, None ) self.bus.add_signal_receiver( self._handleTimeZoneReport, "TimeZoneReport", "org.freesmartphone.GSM.Network", None, None ) self.gsmdata = dbuscache.dbusInterfaceForObjectWithInterface( "org.freesmartphone.ogsmd", "/org/freesmartphone/GSM/Server", "org.freesmartphone.GSM.Data" ) def _handleTimeZoneReport( self, report ): # CTZV is offset * 4 offset = report / 4.0 self.offset_ts = time.time() if self.offset == offset: return self.offset = offset logger.debug( "GSM: Offset=%i", offset ) self.update() def _handleNetworkStatusChanged( self, status ): self.mccmnc = None self.mccmnc_ts = 0.0 self.isocode = None if not "code" in status: logger.debug( "GSM: no network code" ) return code = status["code"] self.mccmnc_ts = time.time() if self.mccmnc == code: return self.mccmnc = code mcc = code[:3] mnc = code[3:] logger.debug( "GSM: MCC=%s MNC=%s", mcc, mnc ) self.gsmdata.GetNetworkInfo( mcc, mnc, reply_handler=self._handleNetworkInfoReply, error_handler=log_dbus_error( "error while calling org.freesmartphone.GSM.Data.GetNetworkInfo" ) ) def _handleNetworkInfoReply( self, info ): self.isocode = None if not "iso" in info: logger.debug( "GSM: no ISO-Code for this network" ) return if self.isocode == info["iso"]: return self.isocode = info["iso"] logger.debug( "GSM: ISO-Code %s", info["iso"] ) self.update() def update( self ): if not self.zonetab: logger.debug( "GSM: loading zone.tab" ) for line in open( "/usr/share/zoneinfo/zone.tab", "r" ): if line: self.zonetab.append( line.rstrip().split( "\t" ) ) self.zone = None now = time.time() if now - self.mccmnc_ts > self.TIMEOUT: self.mccmnc = None self.isocode = None if now - self.offset_ts > self.TIMEOUT: self.offset = None logger.debug( "GSM: determining time zone (isocode=%s offset=%s)", self.isocode, self.offset ) zones = [] if not self.isocode is None: for zone in self.zonetab: if zone[0] == self.isocode: logger.debug( "GSM: found zone %s", zone ) zones.append( zone[2] ) if not self.offset is None: if not len( zones ) == 1: if round(self.offset) == 0.0: zone = "Etc/GMT" else: # CTZV needs to be inverted to get the TZ used in zoneinfo # and we don't have fractional offsets :/ zone = "Etc/GMT%+i" % round(self.offset*-1) zones = [ zone ] if not zones: logger.info( "GSM: no zone found" ) return if len(zones) > 1: logger.info( "GSM: multiple zones found" ) return if self.zone == zones[0]: return self.zone = zones[0] logger.info( "GSM: timezone '%s'", self.zone ) try: try: os.remove( "/etc/localtime" ) except: pass shutil.copyfile( "/usr/share/zoneinfo/"+self.zone, "/etc/localtime" ) except: logger.warning( "failed to install time zone file to /etc/localtime" ) return True def __repr__( self ): return "<%s>" % ( self.__class__.__name__, ) #============================================================================# class Time( dbus.service.Object ): #============================================================================# def __init__( self, bus ): self.path = "/org/freesmartphone/Time" super( Time, self ).__init__( bus, self.path ) self.interface = "org.freesmartphone.Time" self.bus = bus timesources = [x.strip().upper() for x in config.getValue( "otimed", "timesources", "GPS,NTP").split( ',' )] zonesources = [x.strip().upper() for x in config.getValue( "otimed", "zonesources", "GSM").split( ',' )] ntpserver = config.getValue( "otimed", "ntpserver", "134.169.172.1").strip() self.timesources = [] if 'GPS' in timesources: self.timesources.append( GPSTimeSource( self.bus ) ) if 'NTP' in timesources: self.timesources.append( NTPTimeSource( self.bus, ntpserver ) ) logger.info( "loaded timesources %s", self.timesources ) self.zonesources = [] if 'GSM' in zonesources: self.zonesources.append( GSMZoneSource( self.bus ) ) logger.info( "loaded zonesources %s", self.zonesources ) self.interval = 90 self.updateTimeout = gobject.timeout_add_seconds( self.interval, self._handleUpdateTimeout ) def _handleUpdateTimeout( self ): logger.debug( "checking time sources" ) offsets = [] for source in self.timesources: if not source.offset is None: offsets.append( toSeconds( source.offset ) ) if not offsets: logger.debug( "no working time source" ) return True n = len( offsets ) mean = sum( offsets ) / n sd = sqrt( sum( (x-mean)**2 for x in offsets ) / n ) logger.info( "offsets: n=%i mean=%f sd=%f", n, mean, sd ) if sd < 15.0 < abs( mean ): logger.info( "adjusting clock by %f seconds" % mean ) d = timedelta( seconds=mean ) for source in self.timesources: if not source.offset is None: source.offset = source.offset - d clock.adjust( mean ) getOutput( "hwclock --systohc --utc" ) # reenable timeout return True #============================================================================# def factory( prefix, controller ): #============================================================================# """This is the magic function that will be called by the framework module manager""" time_service = Time( controller.bus ) return [time_service] fso-frameworkd-0.10.1/framework/subsystems/ousaged/000077500000000000000000000000001174525413000223665ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/ousaged/__init__.py000066400000000000000000000000001174525413000244650ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/ousaged/generic.py000066400000000000000000000241031174525413000243540ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: UTF-8 -*- """ Open Usage Daemon - Generic reference counted Resource Management (C) 2008 Michael 'Mickey' Lauer (C) 2008 Jan 'Shoragan' Lübbe (C) 2008 Openmoko, Inc. GPLv2 or later Package: ousaged Module: generic """ MODULE_NAME = "ousaged" __version__ = "0.7.1" DBUS_INTERFACE_PREFIX = "org.freesmartphone.Usage" DBUS_PATH_PREFIX = "/org/freesmartphone/Usage" from .resources import ClientResource from .lowlevel import resumeReason import framework.patterns.tasklet as tasklet from framework.patterns import dbuscache import gobject import dbus import dbus.service import time, os, subprocess import logging logger = logging.getLogger( MODULE_NAME ) logger.warning( "THIS SUBSYSTEM IMPLEMENTATION is DEPRECATED. Please use fsousaged instead." ) #----------------------------------------------------------------------------# # DBus Exceptions specifications specific to this module class ResourceUnknown( dbus.DBusException ): _dbus_error_name = "org.freesmartphone.Usage.ResourceUnknown" class ResourceExists( dbus.DBusException ): _dbus_error_name = "org.freesmartphone.Usage.ResourceExists" class ResourceInUse( dbus.DBusException ): _dbus_error_name = "org.freesmartphone.Usage.ResourceInUse" #----------------------------------------------------------------------------# class GenericUsageControl( dbus.service.Object ): #----------------------------------------------------------------------------# """ A Dbus Object implementing org.freesmartphone.Usage. """ DBUS_INTERFACE = DBUS_INTERFACE_PREFIX def __init__( self, bus ): self.interface = self.DBUS_INTERFACE self.path = DBUS_PATH_PREFIX dbus.service.Object.__init__( self, bus, self.path ) self.resources = {} bus.add_signal_receiver( self._nameOwnerChangedHandler, "NameOwnerChanged", dbus.BUS_DAEMON_IFACE, dbus.BUS_DAEMON_NAME, dbus.BUS_DAEMON_PATH ) logger.info( "%s initialized. Serving %s at %s", self.__class__.__name__, self.interface, self.path ) self.idlenotifier = dbuscache.dbusInterfaceForObjectWithInterface( "org.freesmartphone.odeviced", "/org/freesmartphone/Device/IdleNotifier/0", "org.freesmartphone.Device.IdleNotifier" ) def _addResource( self, resource ): if not self.resources.get(resource.name, None) is None: raise ResourceExists( "Resource %s already registered" % resource.name ) self.resources[resource.name] = resource self.ResourceAvailable( resource.name, True ) def _getResource( self, resourcename ): r = self.resources.get(resourcename, None) if r is None: raise ResourceUnknown( "Unknown resource %s" % resourcename ) return r @tasklet.tasklet def _suspend( self ): """ The actual suspending tasklet, phase 1 (suspending resources) """ self.SystemAction( "suspend" ) # send as early as possible logger.info( "suspending all resources..." ) for resource in self.resources.values(): logger.debug( "suspending %s", resource.name ) yield resource._suspend() logger.info( "...completed" ) # FIXME is this additional indirection necessary? gobject.idle_add( self._suspend2 ) def _suspend2( self ): self._kernelSuspendAndResume().start() return False @tasklet.tasklet def _kernelSuspendAndResume( self ): """ The actual resuming tasklet. """ # FIXME might want to traverse /etc/apm.d/... and launch them scripts logger.info( "triggering kernel suspend" ) if False: # for debugging import time time.sleep( 5 ) else: # ---------------> Good Night! open( "/sys/power/state", "w" ).write( "mem\n" ) # ---------------< Good Morning! reason = resumeReason() logger.info( "kernel has resumed - reason = %s" % reason ) if reason == "LowBattery": logger.info( "kernel resumed because of low battery. Emergency Shutdown!" ) subprocess.call( "shutdown -h now &", shell=True ) # FIXME trigger shutdown quit yield None else: logger.info( "resuming resources..." ) for resource in self.resources.values(): logger.debug( "resuming %s", resource.name ) yield resource._resume() logger.info( "...completed." ) dbuscache.dbusCallAsyncDontCare( self.idlenotifier.SetState, "busy" ) # trigger idlenotifier gobject.idle_add( lambda self=self:self.SystemAction( "resume" ) and False ) # send as late as possible # keep track of resource name owners def _nameOwnerChangedHandler( self, name, old_owner, new_owner ): if old_owner and not new_owner: for resource in self.resources.values(): resource.cleanup( old_owner ).start() # # dbus methods # @dbus.service.method( DBUS_INTERFACE, "", "as" ) def ListResources( self ): return self.resources.keys() @dbus.service.method( DBUS_INTERFACE, "s", "as" ) def GetResourceUsers( self, resourcename ): return self._getResource( resourcename ).users @dbus.service.method( DBUS_INTERFACE, "s", "b" ) def GetResourceState( self, resourcename ): return self._getResource( resourcename ).isEnabled @dbus.service.method( DBUS_INTERFACE, "s", "s" ) def GetResourcePolicy( self, resourcename ): return self._getResource( resourcename ).policy @dbus.service.method( DBUS_INTERFACE, "ss", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) def SetResourcePolicy( self, resourcename, policy, dbus_ok, dbus_error ): self._getResource( resourcename ).setPolicy( policy ).start_dbus( dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE, "s", "", sender_keyword='sender', async_callbacks=( "dbus_ok", "dbus_error" ) ) def RequestResource( self, resourcename, sender, dbus_ok, dbus_error ): """ Called by a client to request a resource. This call will return imediatly, even if the resource need to perform some enabling actions. """ try: resource = self.resources[resourcename] except KeyError: dbus_error( ResourceUnknown( "Known resources are %s" % self.resources.keys() ) ) else: resource.request( sender ).start_dbus( dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE, "s", "", sender_keyword='sender', async_callbacks=( "dbus_ok", "dbus_error" ) ) def ReleaseResource( self, resourcename, sender, dbus_ok, dbus_error ): """ Called by a client to release a previously requested resource. This call will return imediatly, even if the resource need to perform some disabling actions. """ try: resource = self.resources[resourcename] except KeyError: dbus_error( ResourceUnknown( "Known resources are %s" % self.resources.keys() ) ) else: resource.release( sender ).start_dbus( dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE, "so", "", sender_keyword='sender', async_callbacks=( "dbus_ok", "dbus_error" ) ) def RegisterResource( self, resourcename, path, sender, dbus_ok, dbus_error ): """ Register a new resource from a client. The client must provide a name for the resource, and a dbus object path to an object implementing org.freesmartphone.Resource interface. """ if resourcename in self.resources: dbus_error( ResourceExists( "Resource %s already exists" % resourcename ) ) else: logger.info( "Register new resource %s", resourcename ) resource = ClientResource( self, resourcename, path, sender ) self._addResource( resource ) dbus_ok() @dbus.service.method( DBUS_INTERFACE, "", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) def Suspend( self, dbus_ok, dbus_error ): """ Suspend all resources and the system. """ # Call the _suspend task connected to the dbus callbacks self._suspend().start_dbus( dbus_ok, dbus_error ) @dbus.service.method( DBUS_INTERFACE, "", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) def Shutdown( self, dbus_ok, dbus_error ): """ Shutdown the system. """ logger.info( "System shutting down..." ) self.SystemAction( "shutdown" ) # send signal dbus_ok() # FIXME this is not a clean shutdown subprocess.call( "shutdown -h now &", shell=True ) @dbus.service.method( DBUS_INTERFACE, "", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) def Reboot( self, dbus_ok, dbus_error ): """ Reboot the system. """ logger.info( "System rebooting..." ) # FIXME should we cleanly shutdown resources here -- will it matter? self.SystemAction( "reboot" ) # send signal dbus_ok() subprocess.call( "reboot &", shell=True ) # # dbus signals # @dbus.service.signal( DBUS_INTERFACE, "sba{sv}" ) def ResourceChanged( self, resourcename, state, attributes ): pass @dbus.service.signal( DBUS_INTERFACE, "sb" ) def ResourceAvailable( self, resourcename, state ): pass @dbus.service.signal( DBUS_INTERFACE, "s" ) def SystemAction( self, action ): pass #----------------------------------------------------------------------------# def factory( prefix, controller ): #----------------------------------------------------------------------------# return [ GenericUsageControl( controller.bus ) ] #----------------------------------------------------------------------------# if __name__ == "__main__": #----------------------------------------------------------------------------# passfso-frameworkd-0.10.1/framework/subsystems/ousaged/helpers.py000066400000000000000000000026501174525413000244050ustar00rootroot00000000000000import logging logger = logging.getLogger( "ousaged.helpers" ) #============================================================================# def readFromFile( path ): #============================================================================# try: value = open( path, 'r' ).read().strip() except IOError, e: logger.warning( "(could not read from '%s': %s)" % ( path, e ) ) return "N/A" else: logger.debug( "(read %s from '%s')" % ( repr(value), path ) ) return value #============================================================================# def writeToFile( path, value ): #============================================================================# logger.debug( "(writing '%s' to '%s')" % ( value, path ) ) try: f = open( path, 'w' ) except IOError, e: logger.warning( "(could not write to '%s': %s)" % ( path, e ) ) else: f.write( "%s\n" % value ) #============================================================================# def hardwareName(): #============================================================================# value = readFromFile( "/proc/cpuinfo" ) for line in value.split( '\n' ): try: left, right = line.split( ':' ) except ValueError: continue else: if left.strip().startswith( "Hardware" ): return right.strip() return "unknown" fso-frameworkd-0.10.1/framework/subsystems/ousaged/lowlevel.py000066400000000000000000000066301174525413000245760ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: UTF-8 -*- """ Open Usage Daemon - Generic reference counted Resource Management (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: ousaged Module: lowlevel Low level (device specific) suspend/resume handling. """ MODULE_NAME = "ousaged" __version__ = "0.0.1" from helpers import readFromFile, writeToFile, hardwareName import logging logger = logging.getLogger( MODULE_NAME ) #============================================================================# class GenericResumeReason( object ): #============================================================================# """ Generic resume reason class. """ def gather( self ): return "unknown" #============================================================================# class OpenmokoResumeReason( object ): #============================================================================# """ Resume reason class for Openmoko GTA01 (Neo 1973) and GTA02 (Neo FreeRunner). """ SYSFS_RESUME_REASON_PATH = "/sys/bus/platform/devices/neo1973-resume.0/resume_reason" SYSFS_RESUME_SUBREASON_PATH = "/sys/class/i2c-adapter/i2c-0/0-0073/resume_reason" def __init__( self ): self._intmap1 = { \ "EINT00_ACCEL1": "Accelerometer", "EINT01_GSM": "GSM", "EINT02_BLUETOOTH": "Bluetooth", "EINT03_DEBUGBRD": "Debug", "EINT04_JACK": "Headphone", "EINT05_WLAN": "Wifi", "EINT06_AUXKEY": "Auxkey", "EINT07_HOLDKEY": "Holdkey", "EINT08_ACCEL2": "Accelerometer", "EINT09_PMU": "PMU", "EINT10_NULL": "invalid", "EINT11_NULL": "invalid", "EINT12_GLAMO": "GFX", "EINT13_NULL": "invalid", "EINT14_NULL": "invalid", "EINT15_NULL": "invalid", } self._intmap2 = { \ "0000000200": "LowBattery", "0002000000": "PowerKey", } def gather( self ): reasons = readFromFile( self.__class__.SYSFS_RESUME_REASON_PATH ).split( '\n' ) for line in reasons: if line.startswith( "*" ): reason = line[2:] break else: print "nope" logger.info( "No resume reason marked in %s" % self.__class__.SYSFS_RESUME_REASON_PATH ) return "unknown" if reason == "EINT09_PMU": logger.debug( "PMU resume reason marked in %s" % self.__class__.SYSFS_RESUME_REASON_PATH ) value = readFromFile( self.__class__.SYSFS_RESUME_SUBREASON_PATH ) try: subreason = self._intmap2[value] except KeyError: logger.debug( "Unknown subreason for PMU resume" ) return "PMU" else: return subreason else: return self._intmap1.get( reason, "unknown" ) #============================================================================# hardware = hardwareName() if hardware in "GTA01 GTA02".split(): ResumeReason = OpenmokoResumeReason else: ResumeReason = GenericResumeReason resumeReasonObj = ResumeReason() resumeReason = resumeReasonObj.gather #============================================================================# if __name__ == "__main__": #============================================================================# pass fso-frameworkd-0.10.1/framework/subsystems/ousaged/resources.py000066400000000000000000000222201174525413000247500ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: UTF-8 -*- """ Open Usage Daemon - Generic reference counted Resource Management (C) 2008 Michael 'Mickey' Lauer (C) 2008 Jan 'Shoragan' Lübbe (C) 2008 Openmoko, Inc. GPLv2 or later Package: ousaged Module: resources """ MODULE_NAME = "ousaged" __version__ = "0.6.2" from framework.config import config from framework.patterns import tasklet, dbuscache import gobject import dbus import dbus.service import time, os, subprocess import logging logger = logging.getLogger( MODULE_NAME ) #----------------------------------------------------------------------------# # DBus Exceptions specifications specific to this module class PolicyUnknown( dbus.DBusException ): _dbus_error_name = "org.freesmartphone.Usage.PolicyUnknown" class PolicyDisabled( dbus.DBusException ): _dbus_error_name = "org.freesmartphone.Usage.PolicyDisabled" class UserExists( dbus.DBusException ): _dbus_error_name = "org.freesmartphone.Usage.UserExists" class UserUnknown( dbus.DBusException ): _dbus_error_name = "org.freesmartphone.Usage.UserUnknown" class ResourceInUse( dbus.DBusException ): _dbus_error_name = "org.freesmartphone.Usage.ResourceInUse" #----------------------------------------------------------------------------# class AbstractResource( object ): #----------------------------------------------------------------------------# """ Abstract base class for a resource. This is the internal class used by the resource manager to keep track of a resource. Every resource has a name, a list of current users, and a policy. Valid policies are: * auto: Reference counted, this is the default, * disabled: The resource is always off, * enabled: The resource is always on. """ VALID_POLICIES = "disabled auto enabled".split() def __init__( self, usageControl, name = "Abstract" ): """ Create a new resource `usageControl` : the resource controler object that will handle this resource `name` : the name of the resource """ self.usageControl = usageControl self.name = str(name) self.users = [] self.policy = 'auto' self.isEnabled = False @tasklet.tasklet def _enable( self ): """ Enable the resource. """ yield None @tasklet.tasklet def _disable( self ): """ Disable the resource. """ yield None @tasklet.tasklet def _suspend( self ): """ Called before the system is going to suspend. """ yield None @tasklet.tasklet def _resume( self ): """ Called after a system resume. """ yield None @tasklet.tasklet def _update( self ): if not self.isEnabled and ( self.users or self.policy == 'enabled' ): logger.debug( "Enabling %s", self.name ) ts = time.time() yield self._enable() logger.info( "Enabled %s in %.1f seconds", self.name, time.time()-ts ) self.isEnabled = True elif self.isEnabled and not ( self.users or self.policy == 'enabled' ): logger.debug( "Disabling %s", self.name ) ts = time.time() yield self._disable() logger.info( "Disabled %s in %.1f seconds", self.name, time.time()-ts ) self.isEnabled = False @tasklet.tasklet def setPolicy( self, policy ): if not policy in AbstractResource.VALID_POLICIES: raise PolicyUnknown( "Unknown resource policy. Valid policies are %s" % AbstractResource.VALID_POLICIES ) if self.users: if policy == "disabled": raise ResourceInUse( "Can't disable %s. Current users are: %s" % ( self.name, self.users ) ) if self.policy != policy: self.policy = policy yield self._update() self.usageControl.ResourceChanged( self.name, self.isEnabled, { "policy": self.policy, "refcount": len( self.users ) } ) @tasklet.tasklet def request( self, user ): if not self.policy in [ 'auto', 'enabled' ]: raise PolicyDisabled( "Requesting %s not allowed by resource policy." % ( self.name ) ) if user in self.users: raise UserExists( "User %s already requested %s" % ( user, self.name ) ) self.users.append( user ) yield self._update() self.usageControl.ResourceChanged( self.name, self.isEnabled, {"policy": self.policy, "refcount": len( self.users )} ) @tasklet.tasklet def release( self, user ): if not user in self.users: raise UserUnknown( "User %s did not request %s before releasing it" % ( user, self.name ) ) self.users.remove( user ) yield self._update() self.usageControl.ResourceChanged( self.name, self.isEnabled, {"policy": self.policy, "refcount": len( self.users )} ) @tasklet.tasklet def cleanup( self, user ): if user in self.users: yield self.release( user ) logger.info( "Releasing %s for vanished user %s", self.name, user ) #----------------------------------------------------------------------------# class DummyResource( AbstractResource ): #----------------------------------------------------------------------------# """ This is a dummy resource class that does nothing. """ def __init__( self, usageControl, name ): AbstractResource.__init__( self , usageControl, name ) @tasklet.tasklet def _enable( self ): yield None @tasklet.tasklet def _disable( self ): yield None #----------------------------------------------------------------------------# class ODeviceDResource( AbstractResource ): #----------------------------------------------------------------------------# """ This is a resource class for objects controlled by the odeviced subsystem. """ DEPRECATED = True def __init__( self, usageControl, name ): AbstractResource.__init__( self , usageControl, name ) self.bus = dbus.SystemBus() @tasklet.tasklet def _enable( self ): proxy = self.bus.get_object( "org.freesmartphone.odeviced", "/org/freesmartphone/Device/PowerControl/" + self.name ) iface = dbus.Interface( proxy, "org.freesmartphone.Device.PowerControl" ) yield tasklet.WaitDBus( iface.SetPower, True) @tasklet.tasklet def _disable( self ): proxy = self.bus.get_object( "org.freesmartphone.odeviced", "/org/freesmartphone/Device/PowerControl/" + self.name ) iface = dbus.Interface( proxy, "org.freesmartphone.Device.PowerControl" ) yield tasklet.WaitDBus( iface.SetPower, False ) #----------------------------------------------------------------------------# class ClientResource( AbstractResource ): #----------------------------------------------------------------------------# """ A resource controlled by an external client. The client needs to expose a dbus object implementing org.freesmartphone.Resource. It can register using the RegisterResource of /org/freesmartphone/Usage. If the client is written in python, it can use the framework.Resource class. """ sync_resources_with_lifecycle = config.getValue( "ousaged", "sync_resources_with_lifecycle", "always" ) def __init__( self, usageControl, name, path, sender ): """ Create a new ClientResource Only the resource manager should call this method """ super( ClientResource, self ).__init__( usageControl, name ) self.path = path self.sender = sender self.iface = None if self.sync_resources_with_lifecycle in ( "always", "startup" ): self._disable().start() @tasklet.tasklet def _enable( self ): """Simply call the client Enable method""" if self.iface is None: self.iface = dbuscache.dbusInterfaceForObjectWithInterface( self.sender, self.path, "org.freesmartphone.Resource" ) yield tasklet.WaitDBus( self.iface.Enable ) @tasklet.tasklet def _disable( self ): """Simply call the client Disable method""" if self.iface is None: self.iface = dbuscache.dbusInterfaceForObjectWithInterface( self.sender, self.path, "org.freesmartphone.Resource" ) yield tasklet.WaitDBus( self.iface.Disable ) @tasklet.tasklet def _suspend( self ): """Simply call the client Suspend method""" if self.iface is None: self.iface = dbuscache.dbusInterfaceForObjectWithInterface( self.sender, self.path, "org.freesmartphone.Resource" ) yield tasklet.WaitDBus( self.iface.Suspend ) @tasklet.tasklet def _resume( self ): """Simply call the client Resume method""" if self.iface is None: self.iface = dbuscache.dbusInterfaceForObjectWithInterface( self.sender, self.path, "org.freesmartphone.Resource" ) yield tasklet.WaitDBus( self.iface.Resume ) #----------------------------------------------------------------------------# if __name__ == "__main__": #----------------------------------------------------------------------------# pass fso-frameworkd-0.10.1/framework/subsystems/testing/000077500000000000000000000000001174525413000224145ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/testing/__init__.py000066400000000000000000000000001174525413000245130ustar00rootroot00000000000000fso-frameworkd-0.10.1/framework/subsystems/testing/testing.py000066400000000000000000000100771174525413000244500ustar00rootroot00000000000000#!/usr/bin/env python """ Dummy Subsystem for Testing Purposes (C) 2008-2009 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later Package: testing Module: testing """ MODULE_NAME = "testing" __version__ = "0.0.0" from framework import resource import dbus import dbus.service import gobject import logging logger = logging.getLogger( MODULE_NAME ) import time DBUS_INTERFACE = "org.freesmartphone.Testing" DBUS_OBJECT_PATH = "/org/freesmartphone/Testing" #============================================================================# class Resource( resource.Resource ): #============================================================================# def __init__( self, bus ): self.path = DBUS_OBJECT_PATH self.bus = bus self.virgin = True dbus.service.Object.__init__( self, bus, self.path ) resource.Resource.__init__( self, bus, "TEST" ) logger.info("%s %s at %s initialized.", self.__class__.__name__, __version__, self.path ) # default behaviour: everything works self.catmap = { "enabling":"ok", "disabling":"ok", "suspending":"ok", "resuming":"ok" } # # framework.Resource # def _enable( self, on_ok, on_error ): logger.info( "enabling" ) time.sleep( 5.0 ) self._doit( "enabling", on_ok, on_error ) def _disable( self, on_ok, on_error ): logger.info( "disabling" ) if self.virgin == True: self.virgin = False else: time.sleep( 5.0 ) self._doit( "disabling", on_ok, on_error ) def _suspend( self, on_ok, on_error ): logger.info( "suspending" ) time.sleep( 5.0 ) self._doit( "suspending", on_ok, on_error ) def _resume( self, on_ok, on_error ): logger.info("resuming") time.sleep( 5.0 ) self._doit( "resuming", on_ok, on_error ) def _doit( self, category, on_ok, on_error ): action = self.catmap[ category ] if action == "ok": on_ok() elif action == "error": on_error( "unspecified" ) elif action == "veto": on_error( resource.SuspendVeto( "not allowed to suspend this resource" ) ) else: foobar # # dbus interface # @dbus.service.method( DBUS_INTERFACE, "", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def SetResourceBehaviour( self, category, behaviour, dbus_ok, dbus_error ): try: value = self.catmap[category] except KeyError: dbus_error( "unknown category, valid categories are: %s" % self.catmap.keys() ) else: if behaviour not in "ok error veto".split(): dbus_error( "unknown behaviour. valid behaviours are: ok error veto" ) self.catmap[category] = str( behaviour ) dbus_ok() @dbus.service.method( DBUS_INTERFACE, "", "aa{sv}", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def ReturnTest( self, dbus_ok, dbus_error ): d = {"foo":"bar"} dbus_ok( [d,d] ) @dbus.service.method( DBUS_INTERFACE, "", "", async_callbacks=( "dbus_ok", "dbus_error" ) ) @resource.checkedmethod def SignalTest( self, dbus_ok, dbus_error ): self.Test( dict(yo="kurt") ) dbus_ok() @dbus.service.signal( DBUS_INTERFACE, "a{sv}" ) def Test( self, asv ): logger.info( "emitting signal" ) #============================================================================# def factory(prefix, controller): #============================================================================# """This is the magic function that will be called by the framework module manager""" return [ Resource( controller.bus ) ] #============================================================================# if __name__ == "__main__": #============================================================================# pass fso-frameworkd-0.10.1/setup.py000066400000000000000000000056071174525413000162430ustar00rootroot00000000000000from distutils.core import setup from distutils.extension import Extension # We do not require Cython currently #from Cython.Distutils import build_ext import os def getDir( dirname ): return [ dirname+'/'+x for x in os.listdir( dirname ) ] # Get the list of all the packages packages = [ x[0] for x in os.walk( "framework" ) ] setup( name = "The FreeSmartphone Framework Daemon", version = "0.10.1", author = "Michael 'Mickey' Lauer et. al.", author_email = "mlauer@vanille-media.de", url = "http://www.freesmartphone.org", #ext_modules = [ # Extension("framework.subsystems.odeviced.wireless", ["framework/subsystems/odeviced/wireless.pyx"], libraries = []) # ], #cmdclass = {'build_ext': build_ext}, packages = packages, package_data={'framework/subsystems/opimd': ["db/*.sql"]}, scripts = [ "framework/frameworkd", "tools/cli-framework", "tools/dump-netlink", "tools/remove-tel", "tools/opimd_fix_db", "tools/opimd_convert_db" ], data_files = [ ("../../etc/dbus-1/system.d", ["etc/dbus-1/system.d/frameworkd.conf"] ), ("../../etc/init.d", ["etc/init.d/frameworkd"] ), ("../../etc/freesmartphone/opreferences/schema/", ["etc/freesmartphone/opreferences/schema/phone.yaml"]), ("../../etc/freesmartphone/opreferences/schema/", ["etc/freesmartphone/opreferences/schema/profiles.yaml"]), ("../../etc/freesmartphone/opreferences/schema/", ["etc/freesmartphone/opreferences/schema/rules.yaml"]), ("../../etc/freesmartphone/opreferences/conf/profiles/", ["etc/freesmartphone/opreferences/conf/profiles/default.yaml"]), ("../../etc/freesmartphone/opreferences/conf/phone", ["etc/freesmartphone/opreferences/conf/phone/default.yaml"]), ("../../etc/freesmartphone/opreferences/conf/phone", ["etc/freesmartphone/opreferences/conf/phone/silent.yaml"]), ("../../etc/freesmartphone/opreferences/conf/phone", ["etc/freesmartphone/opreferences/conf/phone/vibrate.yaml"]), ("../../etc/freesmartphone/opreferences/conf/phone", ["etc/freesmartphone/opreferences/conf/phone/ring.yaml"]), ("../../etc/freesmartphone/opreferences/conf/rules", ["etc/freesmartphone/opreferences/conf/rules/default.yaml"]), ("../../etc/freesmartphone/opreferences/conf/rules", ["etc/freesmartphone/opreferences/conf/rules/silent.yaml"]), ("../../etc/freesmartphone/opreferences/conf/rules", ["etc/freesmartphone/opreferences/conf/rules/vibrate.yaml"]), ("../../etc/freesmartphone/opreferences/conf/rules", ["etc/freesmartphone/opreferences/conf/rules/ring.yaml"]), ("../../etc/freesmartphone/oevents", ["etc/freesmartphone/oevents/rules.yaml"]), ("../../etc/freesmartphone/persist", ["etc/freesmartphone/persist/README"]), ("freesmartphone/examples/", getDir( "examples" ) ), ] ) fso-frameworkd-0.10.1/tests/000077500000000000000000000000001174525413000156635ustar00rootroot00000000000000fso-frameworkd-0.10.1/tests/README000066400000000000000000000061241174525413000165460ustar00rootroot00000000000000#=========================================================================# Test suite for the framework #=========================================================================# The tests directory contains all the tests script. The goal of those scripts is to automaticaly perform call to the framework dbus API and check for errors. We use python unittest module for our test suites. the 'test' module (test.py) also provides a few conveniant methods for writting new test cases. #=========================================================================# To start all the tests #=========================================================================# 1 - Make sure that the framework is running. 2 - Edit the file 'tests.conf' to reflect the current conditions of the test. 3 - Run ./test.py Some tests can take time (specially when they fail due a timeout) If a test fails it can mean that the framework is broken, OR that the test is broken, that is why the tests should be relatively simple. #=========================================================================# To start only specific tests #=========================================================================# Same thing, but run directly the test script implementing the needed tests instead of test.py. e.g: python -N sim.py TODO: make it possible to specify a given test module as an argument of test.py #=========================================================================# To create new tests #=========================================================================# You need to create a new python module that define a few unittests. Then add the name of the module in the `modules` list in test.py (TODO: make it a config option.) The test module provides a few useful decorators for your test : * taskletTest : This decorator defines that your test will run into a Tasklet. It will automatically start the gobject main loop before you start the test and stop it at the end of the test. * request : This decorator defines that a test can only be performed if a given config option is set. For example if a test require the presence of a sim card, it should use : @request("sim.present", True) The test module also provides some convenience functions for checking result types: * testDbus... #=========================================================================# Rules for creating new tests: #=========================================================================# * Every test function has the following format: def test__( self ): """""" is incremental per class, this defines the order in which the tests are performed. Examples: 000, 001, 012 is a description of the function complex you are testing Rule of thumb: Use _one_ test for every dbus method and/or signal. Don't take shortcuts and try to test too many functions in one test! lists the dbus function that you are testing in the test. * Test signals and methods individually * Test return values * Test error conditions * Test good values and out-of-bound values fso-frameworkd-0.10.1/tests/dtest/000077500000000000000000000000001174525413000170065ustar00rootroot00000000000000fso-frameworkd-0.10.1/tests/dtest/README000066400000000000000000000016131174525413000176670ustar00rootroot00000000000000DTest ----- DTest is a framework to test the high level DBus API of FSO. It uses gabriel to connect to the mobile device and control/query the DBus API. DTest can also use two different FSO devices to test usage scenarios that need the control of both ends. Requirements ------------ * gabriel on the testing machine * socat on the FSO device(s) Usage ----- Just run ./dtest -p [-s ] and all tests in tests/ will be run. If you only want to run tests found in certain files you need to add the files with the -t option ( -t sample will only run tests found in tests/sample.py). Testing ------- To add your own test cases just add a file in tests/ and subclass fsotest.FSOTestCase. This class gives you access to the different DBus busses and will provide some more convenience functions like DBus interface and object caches. # vim: textwidth=80 fso-frameworkd-0.10.1/tests/dtest/dbus_test.py000077500000000000000000000050251174525413000213610ustar00rootroot00000000000000#!/usr/bin/env python """ FSO DBus API high level Testsuite (C) 2009 Daniel Willmann GPLv2 or later """ import dbus, glib, gobject import sys, os, optparse, time import subprocess, signal import testloader class MyOptionParser( optparse.OptionParser ): def __init__( self ): optparse.OptionParser.__init__( self ) self.add_option( "-t", "--tests", dest = "tests", help = "Tests to run (all)", metavar = "test1,test2,test3", action = "store" ) self.add_option( "-p", "--primary", metavar = "IP", dest = "primary", help = "IP of the primary phone (for gabriel)", action = "store", ) self.add_option( "-s", "--secondary", metavar = "IP", dest = "secondary", help = "IP of the secondary phone (for gabriel)", action = "store", ) if __name__ == "__main__": bus_pri = None bus_sec = None options = MyOptionParser() options.parse_args( sys.argv ) GABRIEL_CONNECT = lambda x,y: ( "gabriel", "--host=%s"%(x), "--username=root", "--password=", "-d","unix:path=/var/run/dbus/system_bus_socket", "-b", y ) # Start gabriel to the devices gabriel_pri = subprocess.Popen(GABRIEL_CONNECT(options.values.primary, "primary")) if options.values.secondary == "": gabriel_sec = subprocess.Popen(GABRIEL_CONNECT(options.values.secondary, "secondary")) else: gabriel_sec = None time.sleep(2) error = gabriel_pri.poll() if not error == None: print "WARNING: Gabriel exited with errorcode %i"%(error) print "Aborting" sys.exit(1) os.environ['DBUS_SESSION_BUS_ADDRESS'] = "unix:abstract=primary" bus_pri = dbus.bus.BusConnection( dbus.bus.BUS_SESSION ) if not gabriel_sec == None: error = gabriel_sec.poll() if not error == None: print "WARNING: Gabriel exited with errorcode %i"%(error) print "Aborting" sys.exit(1) os.environ['DBUS_SESSION_BUS_ADDRESS'] = "unix:abstract=secondary" bus_sec = dbus.bus.BusConnection( dbus.bus.BUS_SESSION ) testLoader = testloader.TestLoader.getInstance( options.values.tests, bus_pri, bus_sec ) testLoader.runTests() # This only works for python >2.6 #gabriel_pri.kill() # XXX: Why doesn't TERM work here?! os.kill( gabriel_pri.pid, signal.SIGKILL ) if not gabriel_sec == None: os.kill( gabriel_sec.pid, signal.SIGKILL ) fso-frameworkd-0.10.1/tests/dtest/fsotest.py000066400000000000000000000021651174525413000210530ustar00rootroot00000000000000""" FSO DBus API high level Testsuite (C) 2009 Daniel Willmann GPLv2 or later unittest.TestCase with convenience functions for FSO testing """ import unittest import dbus import testloader class FSOTestCase(unittest.TestCase): _objectcache = {} _interfacecache = {} @classmethod def getInterface( klass, name, path, interface ): key = (name, path, interface) if not key in klass._interfacecache: obj = klass.getObject( name, path) klass._interfacecache[key] = dbus.Interface( obj, interface ) return klass._interfacecache[key] @classmethod def getObject( klass, name, path ): key = (name, path) if not key in klass._objectcache: bus_pri = testloader.TestLoader.getInstance().primary_bus klass._objectcache[key] = bus_pri.get_object( name, path ) return klass._objectcache[key] def setUp( self ): self.bus_pri = testloader.TestLoader.getInstance().primary_bus self.bus_sec = testloader.TestLoader.getInstance().secondary_bus def tearDown( self ): pass fso-frameworkd-0.10.1/tests/dtest/testloader.py000066400000000000000000000027761174525413000215420ustar00rootroot00000000000000""" FSO DBus API high level Testsuite (C) 2009 Daniel Willmann GPLv2 or later Testloader to add and run the tests """ import unittest import sys, os class TestLoader(object): _instance = None @classmethod def getInstance( klass, tests = None, bus_pri = None, bus_sec = None ): if klass._instance is None: klass._instance = TestLoader( tests, bus_pri, bus_sec ) return klass._instance def __init__( self, tests, bus_pri, bus_sec ): self.tests = tests self.primary_bus = bus_pri self.secondary_bus = bus_sec self.suite = unittest.TestSuite() sys.path.append( os.path.dirname( os.path.abspath( os.path.curdir ))) if self.tests is None: self.tests = self.findAllTestFiles() else: self.tests = self.tests.split(",") for test in self.tests: self.addTestCasesInFile( test ) def findAllTestFiles( self ): tests = os.listdir("tests/") tests = [ test[:-3] for test in tests if test.endswith(".py") ] return tests def addTestCasesInFile( self, file ): name = "tests.%s"%(file) try: __import__( name ) module = sys.modules[name] self.suite.addTest( unittest.defaultTestLoader.loadTestsFromModule(module) ) except Exception, e: print "Error processing test %s" % ( file ) print e def runTests( self ): unittest.TextTestRunner(verbosity=2).run(self.suite) fso-frameworkd-0.10.1/tests/dtest/tests/000077500000000000000000000000001174525413000201505ustar00rootroot00000000000000fso-frameworkd-0.10.1/tests/dtest/tests/__init__.py000066400000000000000000000000001174525413000222470ustar00rootroot00000000000000fso-frameworkd-0.10.1/tests/dtest/tests/sample.py000066400000000000000000000011021174525413000217750ustar00rootroot00000000000000""" FSO DBus API high level Testsuite (C) 2009 Daniel Willmann GPLv2 or later Sample testing file """ import unittest import fsotest, dbus class SampleTest(fsotest.FSOTestCase): def test_foo( self ): """ Dummy test always succeeds """ return def test_usage( self ): """ Try to get the resource list """ test = self.bus_pri.get_object( 'org.freesmartphone.ousaged', '/org/freesmartphone/Usage' ) testiface = dbus.Interface( test, 'org.freesmartphone.Usage' ) result = testiface.ListResources() fso-frameworkd-0.10.1/tests/dtest/tests/usage.py000066400000000000000000000024101174525413000216230ustar00rootroot00000000000000""" FSO DBus API high level Testsuite (C) 2009 Daniel Willmann GPLv2 or later Test Usage interface """ import unittest import fsotest, dbus class UsageTest(fsotest.FSOTestCase): def setUp( self ): fsotest.FSOTestCase.setUp( self ) self.usage = fsotest.FSOTestCase.getInterface( 'org.freesmartphone.ousaged', '/org/freesmartphone/Usage', 'org.freesmartphone.Usage' ) def test_usagelist( self ): """ Try to get the resource list """ result = self.usage.ListResources() def test_requestrelease( self ): """ Request/Release resource and check state """ reslist = self.usage.ListResources() self.assert_("TEST" in reslist) resusr = self.usage.GetResourceUsers("TEST") self.usage.RequestResource("TEST") self.assertEquals(len(resusr)+1, len(self.usage.GetResourceUsers("TEST"))) self.assertRaises(dbus.DBusException, self.usage.RequestResource, "TEST" ) self.assertEquals(len(resusr)+1, len(self.usage.GetResourceUsers("TEST"))) self.usage.ReleaseResource("TEST") self.assertEquals(len(resusr), len(self.usage.GetResourceUsers("TEST"))) def test_policychange( self ): """ Change the resource policy and check """ pass fso-frameworkd-0.10.1/tests/oevents.py000077500000000000000000000020251174525413000177220ustar00rootroot00000000000000#!/usr/bin/python -N """ framework tests (C) 2008 Guillaume 'Charlie' Chereau (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later """ import unittest import gobject import threading import dbus from dbus.mainloop.glib import DBusGMainLoop DBusGMainLoop(set_as_default=True) import test class BaseTest(unittest.TestCase): def setUp(self): # We connect to the DBus object self.bus = dbus.SystemBus() self.events = self.bus.get_object('org.freesmartphone.oeventsd', '/org/freesmartphone/Events') def test_add_rule(self): """Try to add a rule and then remove it""" rule = '{trigger: Test("test_add_rule"), actions: Debug("trigger test add rule"), name: my_test}' self.events.AddRule(rule) self.events.RemoveRule('my_test') if __name__ == '__main__': test.check_debug_mode() suite = unittest.defaultTestLoader.loadTestsFromTestCase(BaseTest) result = unittest.TextTestRunner(verbosity=3).run(suite) fso-frameworkd-0.10.1/tests/ogsmd2.py000066400000000000000000000061321174525413000174320ustar00rootroot00000000000000#!/usr/bin/env python """ Open Device Daemon - Controller (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later """ import unittest import gobject import threading import dbus from dbus.mainloop.glib import DBusGMainLoop DBusGMainLoop(set_as_default=True) import test import framework.patterns.tasklet as tasklet class GSMTests(unittest.TestCase): """Some test cases for the gsm subsystem""" def setUp(self): self.bus = dbus.SystemBus() self.usage = self.bus.get_object('org.freesmartphone.ousaged', '/org/freesmartphone/Usage') self.usage = dbus.Interface(self.usage, 'org.freesmartphone.Usage') # Get the gsm interface gsm = self.bus.get_object('org.freesmartphone.ogsmd', '/org/freesmartphone/GSM/Device') self.gsm_device = dbus.Interface(gsm, 'org.freesmartphone.GSM.Device') self.gsm_network = dbus.Interface(gsm, 'org.freesmartphone.GSM.Network') self.gsm_call = dbus.Interface(gsm, 'org.freesmartphone.GSM.Call') self.usage.RequestResource('GSM') def tearDown(self): self.usage.ReleaseResource('GSM') @test.request("sim.present", True) def test_set_antenna_power(self): """Try to set the antenna power off and on""" self.gsm_device.SetAntennaPower(False) assert not self.gsm_device.GetAntennaPower() self.gsm_device.SetAntennaPower(True) assert self.gsm_device.GetAntennaPower() @test.request("sim.present", True) @test.taskletTest def test_register(self): """Try to register on the network""" self.gsm_device.SetAntennaPower(True) self.gsm_network.Register() # Check that we get the Status signal time_out = 30 while True: status = yield(tasklet.WaitDBusSignal(self.gsm_network, 'Status', time_out)) if 'provider' in status: break @test.request(("operator.present", True), ("operator.has_phone", True)) @test.taskletTest def test_call(self): """Try to make a call""" number = test.config.get('operator', 'phone_number') self.test_register() test.operator.tell("Going to call %s" % number) self.gsm_call.Initiate(number, 'voice') time_out = 30 id, state, properties = yield(tasklet.WaitDBusSignal(self.gsm_call, 'CallStatus', time_out)) assert state == 'outgoing', state assert test.operator.query("Does the phone start to ring ?") test.operator.tell("Please answer the phone.") id, state, properties = yield(tasklet.WaitDBusSignal(self.gsm_call, 'CallStatus', time_out)) assert state == 'active', state self.gsm_call.Release(id) id, state, properties = yield(tasklet.WaitDBusSignal(self.gsm_call, 'CallStatus', time_out)) assert state == 'release', state assert test.operator.query("Has the call been released?") if __name__ == '__main__': suite = unittest.defaultTestLoader.loadTestsFromTestCase(GSMTests) result = unittest.TextTestRunner(verbosity=3).run(suite) fso-frameworkd-0.10.1/tests/opimd.py000066400000000000000000000044571174525413000173570ustar00rootroot00000000000000#!/usr/bin/python -N """ framework tests (C) 2008 Guillaume 'Charlie' Chereau (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later """ import unittest import gobject import threading import dbus from dbus.mainloop.glib import DBusGMainLoop DBusGMainLoop(set_as_default=True) import test class PimTests(unittest.TestCase): """Some test cases for the pim subsystem""" def setUp(self): self.bus = dbus.SystemBus() # Get the pim interface pim_sources = self.bus.get_object('org.freesmartphone.opimd', '/org/freesmartphone/PIM/Sources') self.pim_sources = dbus.Interface(pim_sources, 'org.freesmartphone.PIM.Sources') pim_contacts = self.bus.get_object('org.freesmartphone.opimd', '/org/freesmartphone/PIM/Contacts') self.pim_contacts = dbus.Interface(pim_contacts, 'org.freesmartphone.PIM.Contacts') def test_add(self): """Try to add a contact and check that a query get this contact""" self.pim_sources.InitAllEntries() # Add a new contact self.pim_contacts.Add({'Name':"gui", 'Phone':"0123456789"}) # check that the contact is present in the lists query_path = self.pim_contacts.Query({'Name':"gui"}) query = self.bus.get_object('org.freesmartphone.opimd', query_path) query = dbus.Interface(query, 'org.freesmartphone.PIM.ContactQuery') count = query.GetResultCount() assert count >= 1 for i in range(count): res = query.GetResult() if res.get('Name', None) == "gui" and res.get('Phone', None) == "0123456789": break else: self.fail("Can't find the added contact") def test_query(self): """Try to make a query on the contacts""" self.pim_sources.InitAllEntries() query_path = self.pim_contacts.Query({'Name':"gui"}) query = self.bus.get_object('org.freesmartphone.opimd', query_path) query = dbus.Interface(query, 'org.freesmartphone.PIM.ContactQuery') count = query.GetResultCount() results = query.GetMultipleResults(count) if __name__ == '__main__': suite = unittest.defaultTestLoader.loadTestsFromTestCase(PimTests) result = unittest.TextTestRunner(verbosity=3).run(suite) fso-frameworkd-0.10.1/tests/opreferencesd.py000066400000000000000000000055001174525413000210610ustar00rootroot00000000000000#!/usr/bin/python -N """ framework tests (C) 2008 Guillaume 'Charlie' Chereau (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later """ import unittest import gobject import threading import dbus from dbus.mainloop.glib import DBusGMainLoop DBusGMainLoop(set_as_default=True) import test class BaseTest(unittest.TestCase): def setUp(self): # We connect to the DBus object self.bus = dbus.SystemBus() self.preferences = self.bus.get_object('org.freesmartphone.opreferencesd', '/org/freesmartphone/Preferences') def test_profiles(self): """Test that we can get the profiles, and that we have a 'default' profile""" profiles = self.preferences.GetProfiles() assert 'default' in profiles assert 'silent' in profiles # Set the profile to default self.preferences.SetProfile('default') profile = self.preferences.GetProfile() assert profile == 'default' # Set the default profile to silent self.preferences.SetProfile('silent') profile = self.preferences.GetProfile() assert profile == 'silent' def test_services(self): """Try to get the 'profile' service""" services = self.preferences.GetServices() assert 'profiles' in services # Try to get a service that doesn't exist try: self.preferences.GetService('This_profile_does_not_exist') except dbus.DBusException, e: assert e._dbus_error_name == "org.freesmartphone.Preferences.NoServiceError", e._dbus_error_name else: assert False, "The error should be raised" # Now a valid one (profiles) path = self.preferences.GetService('profiles') assert(path == '/org/freesmartphone/Preferences/profiles') @test.taskletTest def test_get_key(self): """Try to get a valid key""" path = self.preferences.GetService('profiles') profiles = self.bus.get_object('org.freesmartphone.opreferencesd', path) profiles = dbus.Interface(profiles, 'org.freesmartphone.Preferences.Service') value = profiles.GetValue('profiles') yield True @test.taskletTest def test_get_invalid_key(self): """Try to get an invalid key""" path = self.preferences.GetService('profiles') profiles = self.bus.get_object('org.freesmartphone.opreferencesd', path) profiles = dbus.Interface(profiles, 'org.freesmartphone.Preferences.Service') self.assertRaises(dbus.exceptions.DBusException, profiles.GetValue, 'This_key_does_not_exist') yield True if __name__ == '__main__': test.check_debug_mode() suite = unittest.defaultTestLoader.loadTestsFromTestCase(BaseTest) result = unittest.TextTestRunner(verbosity=3).run(suite) fso-frameworkd-0.10.1/tests/org.freesmartphone.Device.py000066400000000000000000000107051174525413000232460ustar00rootroot00000000000000#!/usr/bin/env python """ (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later """ from test import request as REQUIRE from test import testDbusValueIsInteger, \ testDbusDictionaryWithStringValues, \ testDbusDictionaryWithIntegerValues, \ testDbusType, \ taskletTest import framework.patterns.tasklet as tasklet import types, unittest, gobject, threading import dbus, dbus.mainloop dbus.mainloop.glib.DBusGMainLoop( set_as_default=True ) SOUND_RESOURCE = "/usr/share/sounds/Arkanoid_PSID.sid" SIGNAL_TIMEOUT = 5 #=========================================================================# class DeviceAudioTest( unittest.TestCase ): #=========================================================================# """Tests for org.freesmartphone.GSM.Device.*""" def setUp(self): self.bus = dbus.SystemBus() obj = self.bus.get_object( "org.freesmartphone.odeviced", "/org/freesmartphone/Device/Audio" ) self.audio = dbus.Interface( obj, "org.freesmartphone.Device.Audio" ) def tearDown( self ): pass # # Tests # def test_000_PlaySound( self ): """org.freesmartphone.Device.Audio.PlaySound""" try: self.audio.PlaySound( "/this/resource/hopefully/not/there.wav", 0, 0 ) except dbus.DBusException, e: assert e.get_dbus_name() == "org.freesmartphone.Device.Audio.PlayerError", "wrong error returned" else: assert False, "PlayerError expected" try: self.audio.PlaySound( "/this/resource/hopefully/not/there.unknownFormat", 0, 0 ) except dbus.DBusException, e: assert e.get_dbus_name() == "org.freesmartphone.Device.Audio.UnknownFormat", "wrong error returned" else: assert False, "UnknownFormat expected" self.audio.PlaySound( SOUND_RESOURCE, 0, 0 ) try: self.audio.PlaySound( SOUND_RESOURCE, 0, 0 ) except dbus.DBusException, e: assert e.get_dbus_name() == "org.freesmartphone.Device.Audio.AlreadyPlaying", "wrong error returned" else: assert False, "AlreadyPlaying expected" def test_001_StopSound( self ): """org.freesmartphone.Device.Audio.StopSound""" try: self.audio.StopSound( "/this/resource/not.there" ) except dbus.DBusException, e: assert e.get_dbus_name() == "org.freesmartphone.Device.Audio.NotPlaying", "wrong error returned" else: assert False, "NotPlaying expected" self.audio.StopSound( SOUND_RESOURCE ) @taskletTest def test_002_SoundStatus( self ): """org.freesmartphone.Device.Audio.SoundStatus (playing)""" self.audio.StopAllSounds() self.audio.PlaySound( SOUND_RESOURCE, 0, 0, reply_handler=lambda:None, error_handler=lambda Foo:None ) try: result = yield ( tasklet.WaitDBusSignal( self.audio, 'SoundStatus', SIGNAL_TIMEOUT ) ) testDbusType( result, types.TupleType ) assert result[0] == SOUND_RESOURCE, "expected '%s' as key" % SOUND_RESOURCE assert result[1] == "playing", "expected 'playing' as value for sound resource" finally: self.audio.StopAllSounds() @taskletTest def test_003_SoundStatus( self ): """org.freesmartphone.Device.Audio.SoundStatus (stopped)""" self.audio.StopAllSounds() self.audio.PlaySound( SOUND_RESOURCE, 0, 0 ) self.audio.StopAllSounds( reply_handler=lambda:None, error_handler=lambda Foo:None ) result = yield ( tasklet.WaitDBusSignal( self.audio, 'SoundStatus', SIGNAL_TIMEOUT ) ) testDbusType( result, types.TupleType ) assert result[0] == SOUND_RESOURCE, "expected '%s' as key" % SOUND_RESOURCE assert result[1] == "stopped", "expected 'stopped' as value for sound resource" #=========================================================================# if __name__ == "__main__": #=========================================================================# suites = [] # suites.append( unittest.defaultTestLoader.loadTestsFromTestCase( DeviceAudioTest ) ) # suites.append( unittest.defaultTestLoader.loadTestsFromTestCase( GsmSimTest ) ) # FIXME this is not conform with unit tests, but for now we only test this file anyways # will fix later for suite in suites: result = unittest.TextTestRunner( verbosity=3 ).run( suite ) fso-frameworkd-0.10.1/tests/org.freesmartphone.GSM.py000066400000000000000000000653561174525413000225110ustar00rootroot00000000000000#!/usr/bin/env python """ (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later """ from test import request as REQUIRE from test import testDbusValueIsInteger, \ testDbusDictionaryWithStringValues, \ testDbusDictionaryWithIntegerValues, \ testDbusType, \ testDbusError, \ taskletTest import framework.patterns.tasklet as tasklet import types, unittest, gobject, threading import dbus, dbus.mainloop dbus.mainloop.glib.DBusGMainLoop( set_as_default=True ) SIM_PIN = "9797" # FIXME submit via configuration SIGNAL_TIMEOUT_LOW = 5 SIGNAL_TIMEOUT_MID = 60 SIGNAL_TIMEOUT_HIGH = 60*5 #=========================================================================# def testPhoneNumber( value ): #=========================================================================# if value.startswith( '+' ): value = value[1:] for digit in value: assert digit in "0123456789", "wrong digit '%s' in phone number '%s'" % ( digit, value ) #=========================================================================# class GsmDeviceTest( unittest.TestCase ): #=========================================================================# """Tests for org.freesmartphone.GSM.Device.*""" def setUp(self): self.bus = dbus.SystemBus() obj = self.bus.get_object( "org.freesmartphone.ogsmd", "/org/freesmartphone/GSM/Device" ) self.device = dbus.Interface( obj, "org.freesmartphone.GSM.Device" ) def tearDown( self ): pass # # Tests # @REQUIRE( ( "sim.present", True ), ( "sim.locked", True ) ) def test_000_AntennaPower( self ): """org.freesmartphone.GSM.Device.[Get|Set]AntennaPower""" self.device.SetAntennaPower(False) result = self.device.GetAntennaPower() assert type( result ) is dbus.Boolean, "wrong type returned" assert result == False, "can't turn antenna power off" # only SOME modems can't do that, so don't rely on the exception try: self.device.SetAntennaPower(True) except dbus.DBusException: pass result = self.device.GetAntennaPower() assert type( result ) is dbus.Boolean, "wrong type returned" assert result == True, "can't turn antenna power on" @REQUIRE( ( "sim.present", True ), ( "sim.locked", False ) ) def test_001_AntennaPower( self ): """org.freesmartphone.GSM.Device.[Get|Set]AntennaPower""" self.device.SetAntennaPower(False) result = self.device.GetAntennaPower() assert type( result ) is dbus.Boolean, "wrong type returned" assert result == False, "can't turn antenna power off" self.device.SetAntennaPower(True) result = self.device.GetAntennaPower() assert type( result ) is dbus.Boolean, "wrong type returned" assert result == True, "can't turn antenna power on" def test_002_GetInfo( self ): """org.freesmartphone.GSM.Device.GetInfo""" result = self.device.GetInfo() assert type( result ) is dbus.Dictionary, "wrong type returned" for key in result.keys(): assert type( key ) is dbus.String, "wrong type returned" for value in result.values(): assert type( value ) is dbus.String, "wrong type returned" assert "manufacturer" in result, "mandatory entry missing" assert "imei" in result, "mandatory entry missing" assert len( result["imei"] ) == 15, "wrong length for IMEI" def test_003_GetFeatures( self ): """org.freesmartphone.GSM.Device.GetFeatures""" result = self.device.GetFeatures() assert type( result ) is dbus.Dictionary, "wrong type returned" for key in result.keys(): assert type( key ) is dbus.String, "wrong type returned" for value in result.values(): assert type( value ) is dbus.String, "wrong type returned" assert "GSM" in result, "mandatory entry missing" # NOTE: Missing here is [Get|Set]SimBuffersSms -- this needs an # unlocked SIM and an operator -- hence is part of a higher level test # in GsmSmsTest #=========================================================================# class GsmSimTest( unittest.TestCase ): #=========================================================================# """Tests for org.freesmartphone.GSM.SIM.*""" def setUp(self): self.bus = dbus.SystemBus() obj = self.bus.get_object( "org.freesmartphone.ogsmd", "/org/freesmartphone/GSM/Device" ) self.device = dbus.Interface( obj, "org.freesmartphone.GSM.Device" ) self.sim = dbus.Interface( obj, "org.freesmartphone.GSM.SIM" ) def tearDown( self ): pass # # Tests # @REQUIRE( "sim.present", True ) def test_001_GetSimInfo( self ): """org.freesmartphone.GSM.SIM.GetSimInfo""" # some modems allow that, some not try: result = self.sim.GetSimInfo() except dbus.DBusException, e: assert e.get_dbus_name() == "org.freesmartphone.GSM.SIM.AuthFailed", "wrong error returned" else: assert type( result ) is dbus.Dictionary, "wrong type returned" for key in result.keys(): assert type( key ) is dbus.String, "wrong type returned" assert "imsi" in result, "mandatory entry missing" assert len( result["imsi"] ) == 15, "wrong length for IMSI" # FIXME check optional arguments @REQUIRE( "sim.present", False ) def test_002_GetSimInfo( self ): """org.freesmartphone.GSM.SIM.GetSimInfo""" # FIXME check whether we get the correct exception @REQUIRE( ( "sim.present", True ), ( "sim.locked", True ) ) def test_003_GetAuthStatus( self ): """org.freesmartphone.GSM.SIM.AuthStatus""" # power-cycle, so we reset the PIN self.device.SetAntennaPower( False ) # some modems return CMS ERROR here, some not :/ try: self.device.SetAntennaPower( True ) except dbus.DBusException, e: assert e.get_dbus_name() == "org.freesmartphone.GSM.SIM.AuthFailed", "wrong error returned" else: pass result = self.sim.GetAuthCode() assert type( result ) == dbus.String, "wrong type returned" assert result == "SIM PIN", "unexpected auth code" @REQUIRE( ( "sim.present", True ), ( "sim.locked", False ) ) def test_004_GetAuthStatus( self ): """org.freesmartphone.GSM.SIM.AuthStatus""" # power-cycle, so we reset the PIN self.device.SetAntennaPower( False ) self.device.SetAntennaPower( True ) result = self.sim.GetAuthCode() assert type( result ) == dbus.String, "wrong type returned" assert result == "READY", "unexpected auth code" @REQUIRE( ( "sim.present", True ), ( "sim.locked", True ) ) def test_005_SendAuthCode( self ): """org.freesmartphone.GSM.SIM.SendAuthCode""" # power-cycle, so we reset the PIN self.device.SetAntennaPower( False ) try: self.device.SetAntennaPower( True ) except dbus.DBusException: pass try: self.sim.SendAuthCode( "WRONG" ) except dbus.DBusException, e: assert e.get_dbus_name() == "org.freesmartphone.GSM.SIM.AuthFailed", "wrong error returned" self.sim.SendAuthCode( SIM_PIN ) assert self.sim.GetAuthCode() == "READY", "can't unlock SIM" # FIXME might add missing tests for # * ChangeAuthCode # * Unlock # * SetAuthCodeRequired # * GetAuthCodeRequired # * SendGenericSimCommand # * SendRestrictedSimCommand # * GetHomeZones @REQUIRE( "sim.present", True ) def test_010_ListPhonebooks( self ): """org.fresmartphone.GSM.SIM.ListPhonebooks""" result = self.sim.ListPhonebooks() testDbusType( result, dbus.Array ) for value in result: testDbusType( value, dbus.String ) @REQUIRE( "sim.present", True ) def test_011_RetrievePhonebook( self ): """org.fresmartphone.GSM.SIM.RetrievePhonebook""" try: self.sim.RetrievePhonebook( "this/phonebook/not/there" ) except dbus.DBusException, e: assert e.get_dbus_name() == "org.freesmartphone.GSM.InvalidParameter" else: assert False, "InvalidParameter expected" for phonebook in self.sim.ListPhonebooks(): result = self.sim.RetrievePhonebook( phonebook ) testDbusType( result, dbus.Array ) for entry in result: testDbusType( entry, dbus.Struct ) assert len( entry ) == 3, "wrong length for struct" testDbusValueIsInteger( entry[0] ) assert type( entry[1] ) == dbus.String, "type for name not string" assert type( entry[2] ) == dbus.String, "type for number not string" @REQUIRE( "sim.present", True ) def test_012_GetPhonebookInfo( self ): """org.freesmartphone.GSM.SIM.GetPhonebookInfo""" try: self.sim.GetPhonebookInfo( "this/phonebook/not/there" ) except dbus.DBusException, e: assert e.get_dbus_name() == "org.freesmartphone.GSM.InvalidParameter" else: assert False, "InvalidParameter expected" for phonebook in self.sim.ListPhonebooks(): result = self.sim.GetPhonebookInfo( phonebook ) assert type( result ) is dbus.Dictionary, "wrong type returned" for key in result.keys(): assert type( key ) is dbus.String, "wrong type returned" for value in result.values(): testDbusValueIsInteger( value ) assert "min_index" in result, "mandatory entry missing" assert "max_index" in result, "mandatory entry missing" assert "name_length" in result, "mandatory entry missing" assert "number_length" in result, "mandatory entry missing" @REQUIRE( "sim.present", True ) def test_013_RetrieveEntry( self ): """org.freesmartphone.GSM.SIM.RetrieveEntry""" try: self.sim.RetrieveEntry( "this/phonebook/not/there", 1 ) # should faile except dbus.DBusException, e: assert e.get_dbus_name() == "org.freesmartphone.GSM.InvalidParameter" else: assert False, "InvalidParameter expected" for phonebook in self.sim.ListPhonebooks(): info = self.sim.GetPhonebookInfo( phonebook ) try: result = self.sim.RetrieveEntry( phonebook, info["min_index"]-1 ) # should fail except dbus.DBusException, e: assert e.get_dbus_name() == "org.freesmartphone.GSM.SIM.InvalidIndex", "wrong error returned" else: assert False, "InvalidIndex expected" try: result = self.sim.RetrieveEntry( phonebook, info["max_index"]+1 ) # should fail except dbus.DBusException, e: assert e.get_dbus_name() == "org.freesmartphone.GSM.SIM.InvalidIndex", "wrong error returned" else: assert False, "InvalidIndex expected" result = self.sim.RetrieveEntry( phonebook, info["min_index"] ) assert type( result ) == types.TupleType, "wrong type returned" assert len( result ) == 2, "wrong length for struct" assert type( result[0] ) == dbus.String, "type for name not string" assert type( result[1] ) == dbus.String, "type for number not string" result = self.sim.RetrieveEntry( phonebook, info["max_index"] ) assert type( result ) == types.TupleType, "wrong type returned" assert len( result ) == 2, "wrong length for struct" assert type( result[0] ) == dbus.String, "type for name not string" assert type( result[1] ) == dbus.String, "type for number not string" @REQUIRE( "sim.present", True ) def test_014_StoreEntry( self ): """org.freesmartphone.GSM.SIM.StoreEntry (national)""" try: index = self.sim.GetPhonebookInfo( "contacts" )["max_index"] except dbus.DBusException: return self.sim.StoreEntry( "contacts", index, "Dr. med. Wurst", "123456789" ) newname, newnumber = self.sim.RetrieveEntry( "contacts", index ) assert newname == "Dr. med. Wurst" and newnumber == "123456789", "could not store entry on SIM" # # FIXME what should happen if we give an empty name and/or number? # @REQUIRE( "sim.present", True ) def test_012_StoreEntry( self ): """org.freesmartphone.GSM.SIM.StoreEntry (international)""" try: index = self.sim.GetPhonebookInfo( "contacts" )["max_index"] except dbus.DBusException: return self.sim.StoreEntry( "contacts", index, "Dr. med. Wurst", "+49123456789" ) newname, newnumber = self.sim.RetrieveEntry( "contacts", index ) assert newname == "Dr. med. Wurst" and newnumber == "+49123456789", "could not store entry on SIM" @REQUIRE( "sim.present", True ) def test_013_DeleteEntry( self ): """org.freesmartphone.GSM.SIM.DeleteEntry""" try: index = self.sim.GetPhonebookInfo( "contacts" )["max_index"] except dbus.DBusException: return self.sim.DeleteEntry( "contacts", index ) @REQUIRE( "sim.present", True ) def test_020_GetServiceCenterNumber( self ): """org.freesmartphone.GSM.SIM.GetServiceCenterNumber""" result = self.sim.GetServiceCenterNumber() assert type( result ) == dbus.String, "expected a string" testPhoneNumber( result ) @REQUIRE( "sim.present", True ) def test_021_SetServiceCenterNumber( self ): """org.freesmartphone.GSM.SIM.SetServiceCenterNumber""" NEW = "+49123456789" old = self.sim.GetServiceCenterNumber() new = self.sim.SetServiceCenterNumber( NEW ) assert self.sim.GetServiceCenterNumber() == NEW, "can't change SMS service center number" self.sim.SetServiceCenterNumber( old ) @REQUIRE( "sim.present", True ) def test_031_GetMessageBookInfo( self ): """org.freesmartphone.GSM.SIM.GetMessagebookInfo""" result = self.sim.GetMessagebookInfo() testDbusType( result, dbus.Dictionary ) for key, value in result.items(): assert key in "first last used".split() testDbusValueIsInteger( value ) @REQUIRE( "sim.present", True ) def test_032_RetrieveMessageBook( self ): """org.freesmartphone.GSM.SIM.RetrieveMessagebook""" try: index = self.sim.RetrieveMessagebook( "this_no_messagebook" ) except dbus.DBusException, e: assert e.get_dbus_name() == "org.freesmartphone.GSM.InvalidParameter", "wrong error returned" else: assert False, "expected InvalidParameter" result = self.sim.RetrieveMessagebook( "all" ) testDbusType( result, dbus.Array ) for entry in result: testDbusType( entry, dbus.Struct ) assert len( entry ) == 5, "expected 5 elements for one entry" index, category, peer, contents, properties = entry testDbusValueIsInteger( index ) assert category in "read unread sent unsent".split(), "unexpected category '%s', valid are 'read unread sent unsent'" % category testDbusType( peer, dbus.String ) # can be number or name (if found on SIM) testDbusType( contents, dbus.String ) testDbusType( properties, dbus.Dictionary ) for category in "read unread sent unsent".split(): result = self.sim.RetrieveMessagebook( category ) for entry in result: index, cat, number, contents, properties = entry assert cat == category, "expected category '%s', got '%s'" % ( category, cat ) @REQUIRE( "sim.present", True ) def test_033_RetrieveMessage( self ): """org.freesmartphone.GSM.SIM.RetrieveMessage""" for index in xrange( 1, 255 ): try: entry = self.sim.RetrieveMessage( index ) except dbus.DBusException, e: assert e.get_dbus_name() == "org.freesmartphone.GSM.SIM.NotFound", "unexpected error returned" else: testDbusType( entry, types.TupleType ) assert len( entry ) == 4, "expected 4 elements for one entry" category, peer, contents, properties = entry assert category in "read unread sent unsent".split(), "unexpected category '%s', valid are 'read unread sent unsent'" % category testDbusType( peer, dbus.String ) # can be number or name (if found on SIM) testDbusType( contents, dbus.String ) testDbusType( properties, dbus.Dictionary ) # FIXME add missing tests for: # * StoreMessage # * SendStoredMessage #=========================================================================# class GsmNetworkTest( unittest.TestCase ): #=========================================================================# """Tests for org.freesmartphone.GSM.Network.*""" def setUp(self): self.bus = dbus.SystemBus() obj = self.bus.get_object( "org.freesmartphone.ogsmd", "/org/freesmartphone/GSM/Device" ) self.device = dbus.Interface( obj, "org.freesmartphone.GSM.Device" ) self.sim = dbus.Interface( obj, "org.freesmartphone.GSM.SIM" ) self.network = dbus.Interface( obj, "org.freesmartphone.GSM.Network" ) def tearDown( self ): pass @REQUIRE( "sim.present", True ) def test_000_Register( self ): """org.freesmartphone.GSM.Network.Register""" self.device.SetAntennaPower( False ) try: self.device.SetAntennaPower( True ) except dbus.DBusException, e: self.sim.SendAuthCode( SIM_PIN ) self.network.Register() @REQUIRE( "sim.present", False ) def test_001_Register( self ): """org.freesmartphone.GSM.Network.Register""" self.device.SetAntennaPower( False ) self.device.SetAntennaPower( True ) try: self.network.Register() except dbus.DBusException, e: assert e.get_dbus_name() == "org.freesmartphone.GSM.Network.EmergencyOnly" @REQUIRE( "sim.present", True ) def test_002_GetStatus( self ): """org.freesmartphone.GSM.Network.GetStatus (unregistered)""" self.network.Unregister() result = self.network.GetStatus() assert type( result ) == dbus.Dictionary, "dictionary expected" assert "registration" in result, "mandatory 'registration' tuple missing" assert result["registration"] == "unregistered", "expected registration = 'unregistered', got '%s' instead" % result["registration"] assert "mode" in result, "mandatory 'mode' tuple missing" assert result["mode"] == "unregister" for key in result: assert key in "registration mode provider code strength lac cid".split(), "unexpected key '%s'" % key @REQUIRE( "sim.present", True ) def test_003_GetStatus( self ): """org.freesmartphone.GSM.Network.GetStatus (registered)""" self.network.Register() result = self.network.GetStatus() assert type( result ) == dbus.Dictionary, "dictionary expected" assert "registration" in result, "mandatory 'registration' tuple missing" assert result["registration"] == "home", "expected registration = 'home', got '%s' instead" % result["registration"] assert "mode" in result, "mandatory 'mode' tuple missing" assert result["mode"] == "automatic" for key in result: assert key in "registration mode provider code strength lac cid".split(), "unexpected key '%s'" % key def test_004_GetSignalStrength( self ): """org.freesmartphone.GSM.Network.GetSignalStrength""" result = self.network.GetSignalStrength() testDbusValueIsInteger( result ) assert 0 <= result <= 100, "signal strength value out of bounds" @REQUIRE( "sim.present", True ) @taskletTest def test_005_Status( self ): """org.freesmartphone.GSM.Network.Status (unregistered)""" self.network.Register() self.network.Unregister( reply_handler=lambda:None, error_handler=lambda Foo:None ) result = yield ( tasklet.WaitDBusSignal( self.network, 'Status', SIGNAL_TIMEOUT_MID ) ) assert type( result ) == dbus.Dictionary, "dictionary expected" assert "registration" in result, "mandatory 'registration' tuple missing" assert result["registration"] in "unregistered home busy denied unknown roaming".split(), "unexpected setting for registration" assert "mode" in result, "mandatory 'mode' tuple missing" assert result["mode"] == "unregister" for key in result: assert key in "registration mode provider code strength lac cid".split(), "unexpected key '%s'" % key assert result["registration"] == "unregistered" @REQUIRE( "sim.present", True ) @taskletTest def test_006_Status( self ): """org.freesmartphone.GSM.Network.Status (home)""" self.network.Unregister() self.network.Register( reply_handler=lambda:None, error_handler=lambda Foo:None ) result = yield ( tasklet.WaitDBusSignal( self.network, 'Status', SIGNAL_TIMEOUT_MID ) ) assert result["registration"] == "home" @REQUIRE( "sim.present", True ) def test_010_ListOperators( self ): """org.freesmartphone.GSM.Network.ListProviders""" self.network.Register() result = self.network.ListProviders( timeout=60000 ) assert type( result ) == dbus.Array, "array expected, got '%s' instead" % type( result ) for value in result: testDbusType( value, dbus.Struct ) assert len( value ) == 4, "expected a 4-tuple, got a %d-tuple" % len( value ) code, status, longname, shortname = value testDbusValueIsInteger( code ) testDbusType( status, dbus.String ) testDbusType( longname, dbus.String ) testDbusType( shortname, dbus.String ) assert status in "forbidden current home".split(), "unexpected status '%s', valid are 'forbidden', 'current', 'home'" % status @REQUIRE( "sim.present", True ) def test_011_RegisterWithProvider( self ): """org.freesmartphone.GSM.Network.RegisterWithProvider""" result = self.network.ListProviders( timeout=60000 ) for code, status, longname, shortname in result: if status == "forbidden": try: self.network.RegisterWithProvider( code ) except dbus.DBusException, e: assert e.get_dbus_name() == "org.freesmartphone.GSM.SIM.Blocked", "unexpected error" else: assert False, "expected error SIM.Blocked" break @REQUIRE( "sim.present", True ) def test_012_GetNetworkCountryCode( self ): """org.freesmartphone.GSM.Network.GetNetworkCountryCode""" self.network.Unregister() try: self.network.GetNetworkCountryCode() except dbus.DBusException, e: testDbusError( e, "org.freesmartphone.GSM.Network.NotFound" ) else: assert False, "expected Network.NotFound" self.network.Register() result = self.network.GetNetworkCountryCode() testDbusType( result, types.TupleType ) assert len( result ) == 2, "expected 2 parameters, got %d" % len( result ) testPhoneNumber( result[0] ) testDbusType( result[1], dbus.String ) # FIXME: add missing tests for # * GetCallForwarding # * EnableCallForwarding # * DisableCallForwarding @REQUIRE( "sim.present", True ) def test_030_GetCallingIdentification( self ): """org.freesmartphone.GSM.Network.GetCallingIdentification""" result = self.network.GetCallingIdentification() testDbusType( result, dbus.String ) assert result in "on off network".split() @REQUIRE( "sim.present", True ) def test_031_SetCallingIdentification( self ): """org.freesmartphone.GSM.Network.SetCallingIdentification""" try: self.network.SetCallingIdentification( "this not valid" ) except dbus.DBusException, e: testDbusError( e, "org.freesmartphone.GSM.InvalidParameter" ) else: assert False, "expected InvalidParameter" old = self.network.GetCallingIdentification() self.network.SetCallingIdentification( old ) # * SendUssdRequest # * ... signals ... #=========================================================================# class GsmCbTest( unittest.TestCase ): #=========================================================================# """Tests for org.freesmartphone.GSM.CB.*""" def setUp(self): self.bus = dbus.SystemBus() obj = self.bus.get_object( "org.freesmartphone.ogsmd", "/org/freesmartphone/GSM/Device" ) self.device = dbus.Interface( obj, "org.freesmartphone.GSM.Device" ) self.sim = dbus.Interface( obj, "org.freesmartphone.GSM.SIM" ) self.network = dbus.Interface( obj, "org.freesmartphone.GSM.Network" ) self.cb = dbus.Interface( obj, "org.freesmartphone.GSM.CB" ) def tearDown( self ): pass @REQUIRE( "sim.present", True ) def test_000_GetCellBroadcastSubscription( self ): """org.freesmartphone.GSM.Network.GetCellBroadcastSubscriptions""" result = self.cb.GetCellBroadcastSubscriptions() testDbusType( result, dbus.String ) @REQUIRE( "sim.present", True ) def test_000_GetCellBroadcastSubscriptions( self ): """org.freesmartphone.GSM.Network.GetCellBroadcastSubscriptions""" self.cb.SetCellBroadcastSubscriptions( "all" ) self.cb.SetCellBroadcastSubscriptions( "221" ) self.cb.SetCellBroadcastSubscriptions( "none" ) assert self.cb.GetCellBroadcastSubscriptions() == "none", "can't set cell broadcast subscriptions" # FIXME unfortunately we can't test incoming cell broadcast subscriptions without a simulated modem #=========================================================================# if __name__ == "__main__": #=========================================================================# suites = [] #suites.append( unittest.defaultTestLoader.loadTestsFromTestCase( GsmDeviceTest ) ) #suites.append( unittest.defaultTestLoader.loadTestsFromTestCase( GsmSimTest ) ) #suites.append( unittest.defaultTestLoader.loadTestsFromTestCase( GsmNetworkTest ) ) suites.append( unittest.defaultTestLoader.loadTestsFromTestCase( GsmCbTest ) ) # FIXME this is not conform with unit tests, but for now we only test this file anyways # will fix later for suite in suites: result = unittest.TextTestRunner( verbosity=3 ).run( suite ) fso-frameworkd-0.10.1/tests/ousaged.py000066400000000000000000000100471174525413000176660ustar00rootroot00000000000000#!/usr/bin/env python """ Open Device Daemon - Controller (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later """ import gobject import threading import dbus import dbus.service from dbus.mainloop.glib import DBusGMainLoop DBusGMainLoop(set_as_default=True) import time import framework from framework.patterns.tasklet import Tasklet, WaitDBusSignal, WaitDBus, Sleep verbose = True def dbus_to_python(v): """This function convert a dbus object to a python object""" if isinstance(v, dbus.Int32): return int(v) if isinstance(v, dbus.String): return str(v) if isinstance(v, dbus.Dictionary): return dict( (dbus_to_python(k), dbus_to_python(v)) for k,v in v.iteritems() ) if isinstance(v, dbus.Array): return [dbus_to_python(x) for x in v] if isinstance(v, dbus.Struct): return tuple(dbus_to_python(x) for x in v) print "Can't convert type %s" % type(v) return v def vprint(msg, *args): """Only print if we are in verbose mode""" if verbose: print msg % args class ClientResource( dbus.service.Object ): """This is our test client resource object""" def __init__(self): bus = dbus.SystemBus() dbus.service.Object.__init__( self, bus, '/org/freesmatrphone/Test' ) self.enabled = False @dbus.service.method( 'org.freesmartphone.Resource', "", "" ) def Enable( self ): print "Enable" assert not self.enabled self.enabled = True @dbus.service.method( 'org.freesmartphone.Resource', "", "" ) def Disable( self ): print "Disable" assert self.enabled self.enabled = False @dbus.service.method( 'org.freesmartphone.Resource', "", "" ) def Suspend( self ): print "Suspend" @dbus.service.method( 'org.freesmartphone.Resource', "", "" ) def Resume( self ): print "Resumed" class Test(Tasklet): def run(self): """This is the main task of the Test class. It runs in a tasklet, so I can use yield to block without using thread """ print "== Connect to dbus services ==" self.bus = dbus.SystemBus() self.usage = self.bus.get_object('org.freesmartphone.ousaged', '/org/freesmartphone/Usage') print "OK" yield self.test_client_resource() yield self.test_gsm() yield self.test_suspend() def test_client_resource(self): print "== Test client resource ==" # Create and register the 'test' resource resource = ClientResource() self.usage.RegisterResource( 'test', resource ) # request the resource yield WaitDBus( self.usage.RequestResource, 'test' ) # release the resource yield WaitDBus( self.usage.ReleaseResource, 'test' ) print "OK" yield True def test_suspend( self ): print "== Test client resource ==" # Suspend the system # Warning : if we run this test via ssh over USB, # then we are going to lose the connection yield WaitDBus( self.usage.Suspend ) def test_errors( self ): print "== Test some errors cases ==" # We request an unknown resource try: yield WaitDBus( self.usage.RequestResource, 'LKHLKJL' ) except: # TODO: filter on the proper exception pass else: assert False, "We should have received a dbus exception" def test_gsm( self ): print "== Test gsm resource ==" yield WaitDBus( self.usage.RequestResource, 'GSM' ) print "sleep 10 seconds" yield Sleep(10) yield WaitDBus( self.usage.ReleaseResource, 'GSM' ) if __name__ == '__main__': loop = gobject.MainLoop() def on_start(): try: yield Test() finally: loop.quit() # whatever happend, we need to stop the mainloop at the end gobject.idle_add(Tasklet(generator=on_start()).start) loop.run() print "Exit" fso-frameworkd-0.10.1/tests/sim.py000066400000000000000000000070731174525413000170340ustar00rootroot00000000000000#!/usr/bin/python -N """ framework tests (C) 2008 Guillaume 'Charlie' Chereau (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later """ import unittest import gobject import threading import dbus from dbus.mainloop.glib import DBusGMainLoop DBusGMainLoop(set_as_default=True) import test from framework.patterns.tasklet import WaitDBusSignal def retry_on_sim_not_ready(func): """This decorator will make the test retry if the sim was not ready """ @test.taskletTest def ret(*args, **kargs): bus = dbus.SystemBus() gsm = bus.get_object('org.freesmartphone.ogsmd', '/org/freesmartphone/GSM/Device') sim = dbus.Interface(gsm, 'org.freesmartphone.GSM.SIM') try: yield func(*args, **kargs) except dbus.DBusException, ex: # XXX: the org.freesmartphone.GSM.Device.Failed shouldn't be here ! if ex.get_dbus_name() in ['org.freesmartphone.GSM.SIM.NotReady', 'org.freesmartphone.GSM.Device.Failed']: # We give the SIM 30 seconds to get ready yield WaitDBusSignal(sim, 'ReadyStatus', 30) yield func(*args, **kargs) raise # All other dbus exception are raised ret.__dict__ = func.__dict__ ret.__name__ = func.__name__ ret.__doc__ = func.__doc__ return ret class SimTests(unittest.TestCase): def setUp(self): # We connect to the DBus object, and request the 'GSM' service self.bus = dbus.SystemBus() self.usage = self.bus.get_object('org.freesmartphone.ousaged', '/org/freesmartphone/Usage') self.usage = dbus.Interface(self.usage, 'org.freesmartphone.Usage') # Get the sim interface gsm = self.bus.get_object('org.freesmartphone.ogsmd', '/org/freesmartphone/GSM/Device') self.gsm_device = dbus.Interface(gsm, 'org.freesmartphone.GSM.Device') self.sim = dbus.Interface(gsm, 'org.freesmartphone.GSM.SIM') self.usage.RequestResource('GSM') self.gsm_device.SetAntennaPower(True) def tearDown(self): self.usage.ReleaseResource('GSM') @test.request(("sim.present", True), ("sim.has_contacts", True)) @retry_on_sim_not_ready def test_get_contacts(self): """Try to get the contacts list""" contacts = self.sim.RetrievePhonebook('contacts') assert(contacts) @test.request("sim.present", True) @retry_on_sim_not_ready def test_add_contact(self): """Try to add a new contact""" info = self.sim.GetPhonebookInfo('contacts') min_index = int(info['min_index']) self.sim.StoreEntry('contacts', min_index, "TEST", "0123456789") @test.request("sim.present", True) @retry_on_sim_not_ready def test_add_message(self): """Try to add a new text message""" msg = u"hello" number = '012345678' prop = dict(type='SMS_DELIVER', alphabet='gsm_default') index = self.sim.StoreMessage(number, msg, prop) @test.request("sim.present", True) @retry_on_sim_not_ready def test_add_binary_message(self): """Try to add a new binary message""" msg = 'some binary data \x00 <-- with a null char' number = '012345678' prop = dict(type='SMS_DELIVER', alphabet='binary') index = self.sim.StoreMessage(number, msg, prop) if __name__ == '__main__': suite = unittest.defaultTestLoader.loadTestsFromTestCase(SimTests) result = unittest.TextTestRunner(verbosity=3).run(suite) fso-frameworkd-0.10.1/tests/sms.py000077500000000000000000000423601174525413000170470ustar00rootroot00000000000000#!/usr/bin/env python """ SMS Testsuite (C) 2009 Daniel Willmann GPLv2 or later """ import unittest import gobject import threading import dbus from dbus.mainloop.glib import DBusGMainLoop DBusGMainLoop(set_as_default=True) # Add ogsmd stuff to PYTHONPATH import sys sys.path.extend(["../", "../framework/subsystems/"]) import test import datetime import framework.patterns.tasklet as tasklet from framework.subsystems.ogsmd.gsm.sms import * class SMSTests(unittest.TestCase): """Some test cases for the sms subsystem""" def setUp(self): # This sms-deliver PDU would be reencoded with a different length value # "07918167830071F1040BD0C7F7FBCC2E0300008080203200748078D0473BED2697D9F3B20E442DCFE9A076793E0F9FCBA07B9A8E0691C3EEF41C0D1AA3C3F2F0985E96CF75A00EE301E22C1C2C109B217781642E50B87E76816433DD0C066A81E60CB70B347381C2F5B30B" self.pdus_sms_deliver = [ "0791448720900253040C914497035290960000500151614414400DD4F29C9E769F41E17338ED06", "0791448720003023440C91449703529096000050015132532240A00500037A020190E9339A9D3EA3E920FA1B1466B341E472193E079DD3EE73D85DA7EB41E7B41C1407C1CBF43228CC26E3416137390F3AABCFEAB3FAAC3EABCFEAB3FAAC3EABCFEAB3FAAC3EABCFEAB3FADC3EB7CFED73FBDC3EBF5D4416D9457411596457137D87B7E16438194E86BBCF6D16D9055D429548A28BE822BA882E6370196C2A8950E291E822BA88", "0791448720003023440C91449703529096000050015132537240310500037A02025C4417D1D52422894EE5B17824BA8EC423F1483C129BC725315464118FCDE011247C4A8B44", "07914477790706520414D06176198F0EE361F2321900005001610013334014C324350B9287D12079180D92A3416134480E", "0791448720003023440C91449703529096000050016121855140A005000301060190F5F31C447F83C8E5327CEE0221EBE73988FE0691CB65F8DC05028190F5F31C447F83C8E5327CEE028140C8FA790EA2BF41E472193E7781402064FD3C07D1DF2072B90C9FBB402010B27E9E83E86F10B95C86CF5D2064FD3C07D1DF2072B90C9FBB40C8FA790EA2BF41E472193E7781402064FD3C07D1DF2072B90C9FBB402010B27E9E83E8", "0791448720003023440C91449703529096000050016121850240A0050003010602DE2072B90C9FBB402010B27E9E83E86F10B95C86CF5D201008593FCF41F437885C2EC3E72E100884AC9FE720FA1B442E97E1731708593FCF41F437885C2EC3E72E100884AC9FE720FA1B442E97E17317080442D6CF7310FD0D2297CBF0B90B040221EBE73988FE0691CB65F8DC05028190F5F31C447F83C8E5327CEE028140C8FA790EA2BF41", "0791448720003023440C91449703529096000050016121854240A0050003010603C8E5327CEE0221EBE73988FE0691CB65F8DC05028190F5F31C447F83C8E5327CEE028140C8FA790EA2BF41E472193E7781402064FD3C07D1DF2072B90C9FBB402010B27E9E83E86F10B95C86CF5D201008593FCF41F437885C2EC3E72E10B27E9E83E86F10B95C86CF5D201008593FCF41F437885C2EC3E72E100884AC9FE720FA1B442E97E1", "0791448720003023400C91449703529096000050016121858240A0050003010604E62E100884AC9FE720FA1B442E97E17317080442D6CF7310FD0D2297CBF0B90B040221EBE73988FE0691CB65F8DC0542D6CF7310FD0D2297CBF0B90B040221EBE73988FE0691CB65F8DC05028190F5F31C447F83C8E5327CEE028140C8FA790EA2BF41E472193E7781402064FD3C07D1DF2072B90C9FBB402010B27E9E83E86F10B95C86CF5D", "0791448720003023400C91449703529096000050016121853340A005000301060540C8FA790EA2BF41E472193E7781402064FD3C07D1DF2072B90C9FBB402010B27E9E83E86F10B95C86CF5D201008593FCF41F437885C2EC3E72E100884AC9FE720FA1B442E97E17317080442D6CF7310FD0D2297CBF0B90B84AC9FE720FA1B442E97E17317080442D6CF7310FD0D2297CBF0B90B040221EBE73988FE0691CB65F8DC05028190", "0791448720003023440C914497035290960000500161218563402A050003010606EAE73988FE0691CB65F8DC05028190F5F31C447F83C8E5327CEE0281402010", "07918167830071F1040BD0C7F7FBCC2E030000808010800120804AD0473BED2697D9F3B20E644CCBDBE136835C6681CCF2B20B147381C2F5B30B04C3E96630500B1483E96030501A34CDB7C5E9B71B847AB2CB2062987D0E87E5E414", "0791447758100650040C9194714373238200008080312160304019D4F29C0E6A97E7F3F0B90CB2A7C3A0791A7E0ED3CB2E", "0791447758100650040DD0F334FC1CA6970100008080312170224008D4F29CDE0EA7D9", "0791889653704434040C9188969366423600008090017134632302CA34", "0791889663000009040C918896631009910008809061510540238453485B89FF0167094EF681C97D055FC38DF376844E8B548C4F608AAA54E6FF016709500B59735B69525B52A0516590FD6703753759738AAA60F389818A8D8B584F60FF0C624B6A5F5FEB64A500350032003163090033628A63E16A5F67038A8D8B5859793002621664A50035003200316309003251FA73FE611B60C5597D904B5146FF01", "0791889663000019040C918896631030990008809071619483234E60A86709672A63A54F8696FB003A000A00300039002F00310037002000300034003A003400330050004D4F8681EA0030003900380038003500360033003900390036002000280032901A0029000A", "0791889663000009040C918896631009910008809071717374238A7E415FD951B76DE1768457CE5E02FF0C4F609858610F548C6211505A670B53CB55CEFF1F624B6A5F76F464A500350032003163090031518D630900338F3851650033003200320030003000390033621167037B495F854F60771F5FC376844F8696FB007E621664A5003500320031630900328AC75FC3804A59298DA330014EA453CB8D855BB96613007E", "0791889663000009040C91889671342752000080908171153223282073788E4EBFDD2B1CCE96C3E16AB6592E67D32944ECF7780D9A8FE5E5B25BA468B514", "0791889663000019040C918896138188020008809091907405238050B38A0A606F003F767E842C734E91D190017D664F6076846D3B52D590FD958B8DD15169500B67084E86FF0C4F605831540D4E8655CEFF0173FE572853EA898150B34E00500B7A7A767D7C218A0A52300030003900330031002D003100380031003900330030514D8CBB5831540DFF0C6A5F67035C31662F4F6076845594FF01", "07918896532430280406918816880000809042215024235FC3309B0D42BEDB6590380F22A7C3ECB4FB0CE2AD7C20DEF85D77D3E579D0F84D2E836839900FC403C1D16F7719E47E837CA01D681866B341ECF738CC06A9EB733A889C0EB341ECF738CC06C1D16F7719E47EBB00", "07914140279505F74404D011002000800190819234000704010200018000", "07914140279505F74404D011002000800190913285000704010200028000", "07914140279505F74404D011002000800190320243000704010200038000", "0791947106004034040C9194713900303341008011311265854059D6B75B076A86D36CF11BEF024DD365103A2C2EBB413390BB5C2F839CE1315A9E1EA3E96537C805D2D6DBA0A0585E3797DDA0FB1ECD2EBB41D37419244ED3E965906845CBC56EB9190C069BCD6622", "0791947106004034040C9194713900303341008011312270804059D6B75B076A86D36CF11BEF024DD365103A2C2EBB413490BB5C2F839CE1315A9E1EA3E96537C805D2D6DBA0A0585E3797DDA0FB1ECD2EBB41D37419244ED3E965906845CBC56EB9190C069BCD6622", ] self.pdus_sms_submit = [ "07910447946400F011000A9270042079330000AA0161", "079194710716000001310C919491103246570000061B1EBD3CA703", ] self.pdus_sms_submit_report = [ "010080110191146140", "010080112102618040", ] self.pdus_sms_status_report = [ "07919730071111F106BD0B919750673814F3902090127043219020901270142100", "079194710716000006B70C91943531946236903020308400409030203084004000" ] self.pdus_CB = [ "001000DD001133DAED46ABD56AB5186CD668341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D100", ] self.decodePDUs = [] def tearDown(self): pass def _recodepdu(self, pdu, dir): sms = SMS.decode(pdu, dir) genpdu = sms.pdu() self.assert_(pdu == genpdu, "Reencoded SMS doesn't match, PDUS:\n%s\n%s\n%s" % (repr(sms), pdu, genpdu)) def test_recode_sms_deliver(self): """Try to decode sms-deliver messages and reencode them to see if they match""" for pdu in self.pdus_sms_deliver: self._recodepdu(pdu, "sms-deliver") def test_recode_sms_submit(self): """Try to decode sms-submit messages and reencode them to see if they match""" for pdu in self.pdus_sms_submit: self._recodepdu(pdu, "sms-submit") def test_recode_sms_submit_report(self): """Try to decode sms-submit-report messages and reencode them to see if they match""" for pdu in self.pdus_sms_submit_report: self._recodepdu(pdu, "sms-submit-report") def test_recode_sms_status_report(self): """Try to decode sms-status-report messages and reencode them to see if they match""" for pdu in self.pdus_sms_status_report: self._recodepdu(pdu, "sms-status-report") def test_decode_sms_deliver(self): """Try to decode some sms-deliver messages""" for pdu in self.pdus_sms_deliver: SMS.decode(pdu, "sms-deliver") def test_decode_sms_submit(self): """Try to decode some sms-submit messages""" for pdu in self.pdus_sms_submit: SMS.decode(pdu, "sms-submit") def test_decode_sms_submit_report(self): """Try to decode some sms-submit-report messages""" for pdu in self.pdus_sms_submit_report: SMS.decode(pdu, "sms-submit-report") def test_decode_sms_status_report(self): """Try to decode some sms-status-report messages""" for pdu in self.pdus_sms_status_report: sms = SMS.decode(pdu, "sms-status-report") def test_decode_cb(self): """Try to decode CellBroadcast messages""" for pdu in self.pdus_CB: cb = CellBroadcast.decode(pdu) def test_default_sms_submit(self): """Check the default properties of an sms-submit message""" defprops = {'status-report-request': False, 'reject-duplicates': False, 'pid': 0, 'reply-path': False, 'message-reference': 0, 'alphabet': 'gsm_default', 'type': 'sms-submit'} sms = SMSSubmit() self.assert_(sms.properties == defprops, "Default sms-submit properties are wrong: %s" % sms.properties) def test_default_sms_deliver(self): """Check the default properties of an sms-deliver message""" defprops = {'status-report-indicator': False, 'more-messages-to-send': False, 'alphabet': 'gsm_default', 'pid': 0, 'reply-path': False, 'timestamp': 'Tue Jan 1 00:00:00 1980 +0000', 'type': 'sms-deliver'} sms = SMSDeliver() self.assert_(sms.properties == defprops, "Default sms-deliver properties are wrong: %s" % sms.properties) def test_default_sms_submit_report(self): """Check the default properties of an sms-submit-report message""" defprops = {'timestamp': 'Tue Jan 1 00:00:00 1980 +0000', 'type': 'sms-submit-report'} sms = SMSSubmitReport() self.assert_(sms.properties == defprops, "Default sms-submit-report properties are wrong: %s" % sms.properties) def test_default_sms_submit_report_err(self): """Check the default properties of an sms-submit-report message for RP-ERROR""" defprops = {'timestamp': 'Tue Jan 1 00:00:00 1980 +0000', 'type': 'sms-submit-report', 'fcs': 0xff, 'failure-cause': 'unspecified-error'} sms = SMSSubmitReport(False) self.assert_(sms.properties == defprops, "Default sms-submit-report properties are wrong: %s" % sms.properties) def test_default_sms_status_report(self): """Check the default properties of an sms-status-report message""" defprops = {'status': 0, 'status-report-qualifier': 'sms-submit', 'more-messages-to-send': False, 'timestamp': 'Tue Jan 1 00:00:00 1980 +0000', 'status-message': 'Completed: Delivered', 'discharge-time': 'Tue Jan 1 00:00:00 1980 +0000', 'message-reference': 0, 'type': 'sms-status-report'} sms = SMSStatusReport() self.assert_(sms.properties == defprops, "Default sms-status-report properties are wrong: %s" % sms.properties) def test_generate_sms(self): """Create an SMS object and try to encode it""" defprops = {'status-report-request': False, 'reject-duplicates': False, 'pid': 0, 'reply-path': False, 'message-reference': 0, 'alphabet': 'gsm_default', 'type': 'sms-submit'} sms = SMSSubmit() sms.addr = PDUAddress.guess("+491234") self.assert_(sms.pdu() == "0001000691942143000000", "SMS encoding incorrect, PDU is %s" %sms.pdu()) sms.ud = "Test" self.assert_(sms.pdu() == "0001000691942143000004D4F29C0E", "SMS encoding incorrect, PDU is %s" %sms.pdu()) sms.properties = { "pid": 10, "csm_id": 10, "csm_num": 2, "csm_seq" : 1} self.assert_(sms.pdu() == "00410006919421430A000B0500030A0201A8E5391D", "SMS encoding incorrect, PDU is %s" %sms.pdu()) expected_props = defprops expected_props.update( { "pid": 10, "csm_id": 10, "csm_num": 2, "csm_seq" : 1} ) self.assert_(sms.properties == expected_props, "SMS properties not as expected: %s" % sms.properties) # Extended plane sms.ud = "{}[]\\" self.assert_(sms.dcs_alphabet == "gsm_default", "SMS extended alphabet encoding failed, alphabet used is: %s" % sms.dcs_alphabet) self.assert_(sms.pdu() == "00410006919421430A00110500030A020136A84D6AC3DBF8362F", "SMS extended alphabet encoding failed, PDU:\n%s" % sms.pdu()) # UCS-2 sms.properties = { "alphabet": "ucs2" } sms.ud = u'Unicode\u2320' self.assert_(sms.dcs_alphabet == "utf_16_be", "SMS UCS2 alphabet encoding failed, alphabet used is: %s" % sms.dcs_alphabet) self.assert_(sms.pdu() == "00410006919421430A08160500030A02010055006E00690063006F006400652320", "SMS UCS2 alphabet encoding failed, PDU:\n%s" % sms.pdu()) def test_udh_port_8(self): """Test setting and getting ports via the userdata header (8-bit)""" sms = SMSSubmit() sms.properties = { "src_port": 10, "dst_port": 80, "port_size": 8 } self.assert_(4 in sms.udh, "UDH information element 4 not found") self.assert_(sms.udh[4] == [80, 10], "Data in UDH information element 4 is wrong: %s" % sms.udh[4]) def test_udh_port_16(self): """Test setting and getting ports via the userdata header (16-bit)""" sms = SMSSubmit() sms.properties = { "src_port": 1028, "dst_port": 80, "port_size": 16 } self.assert_(5 in sms.udh, "UDH information element 5 not found") self.assert_(sms.udh[5] == [0, 80, 1028/256, 1028%256], "Data in UDH information element 5 is wrong: %s" % sms.udh[5]) def test_udh_csm_short(self): """Test concatenated short message settings (8-bit ID) via userdata header""" sms = SMSSubmit() sms.properties = { "csm_id": 80, "csm_num": 2, "csm_seq":1 } self.assert_(0 in sms.udh, "UDH information element 0 not found") self.assert_(sms.udh[0] == [80, 2, 1], "Data in UDH information element 0 is wrong: %s" % sms.udh[0]) def test_udh_csm_long(self): """Test concatenated short message settings (16-bit ID) via userdata header""" sms = SMSSubmit() sms.properties = { "csm_id": 1080, "csm_num": 2, "csm_seq":1 } self.assert_(8 in sms.udh, "UDH information element 0 not found") self.assert_(sms.udh[8] == [1080/256, 1080%256, 2, 1], "Data in UDH information element 8 is wrong: %s" % sms.udh[8]) def test_invalid_scts_date_in_pdu(self): """Ensure invalid dates don't break SMS decoding""" invalid_date_pdu = "07914140279505F74404D011002000803190819234000704010200018000" sms = SMS.decode(invalid_date_pdu, "sms-deliver") self.assert_(sms.properties["timestamp"] == "Tue Jan 1 00:00:00 1980 +0000") self.assert_(sms.properties.has_key("error")) def test_profile_decoding(self): """See how long it takes for one sms-deliver to be decoded""" testpdu = "0791448720003023440C91449703529096000050016121855140A005000301060190F5F31C447F83C8E5327CEE0221EBE73988FE0691CB65F8DC05028190F5F31C447F83C8E5327CEE028140C8FA790EA2BF41E472193E7781402064FD3C07D1DF2072B90C9FBB402010B27E9E83E86F10B95C86CF5D2064FD3C07D1DF2072B90C9FBB40C8FA790EA2BF41E472193E7781402064FD3C07D1DF2072B90C9FBB402010B27E9E83E8" n = 200 start = datetime.now() for i in range(n): sms = SMS.decode(testpdu, "sms-deliver") end = datetime.now() diff = end-start diff = (diff.seconds*1000 + diff.microseconds/1000.0)/n print "%.3fms ... " % diff def test_profile_encoding(self): """See how long it takes for one sms-deliver to be reencoded""" testpdu = "0791448720003023440C91449703529096000050016121855140A005000301060190F5F31C447F83C8E5327CEE0221EBE73988FE0691CB65F8DC05028190F5F31C447F83C8E5327CEE028140C8FA790EA2BF41E472193E7781402064FD3C07D1DF2072B90C9FBB402010B27E9E83E86F10B95C86CF5D2064FD3C07D1DF2072B90C9FBB40C8FA790EA2BF41E472193E7781402064FD3C07D1DF2072B90C9FBB402010B27E9E83E8" n = 200 sms = SMS.decode(testpdu, "sms-deliver") start = datetime.now() for i in range(n): sms.pdu() end = datetime.now() diff = end-start diff = (diff.seconds*1000 + diff.microseconds/1000.0)/n print "%.3fms ... " % diff if __name__ == '__main__': suite = unittest.defaultTestLoader.loadTestsFromTestCase(SMSTests) result = unittest.TextTestRunner(verbosity=3).run(suite) # #============================================================================# # def readFromFile( path ): # #============================================================================# # try: # value = open( path, 'r' ).read().strip() # except IOError, e: # print( "(could not read from '%s': %s)" % ( path, e ) ) # return "N/A" # else: # return value # # # # Read PDUs from a file if passed as a parameter # if len(sys.argv) >= 2: # pdus_sms_deliver = readFromFile(sys.argv[1]).split("\n") # if len(sys.argv) == 3: # pdus_sms_submit = readFromFile(sys.argv[2]).split("\n") # vim: expandtab shiftwidth=4 tabstop=4 fso-frameworkd-0.10.1/tests/test.py000077500000000000000000000200031174525413000172120ustar00rootroot00000000000000#!/usr/bin/python -N """ Framework Test Framework (sic!) (C) 2008 Guillaume 'Charlie' Chereau (C) 2008 Michael 'Mickey' Lauer (C) 2008 Openmoko, Inc. GPLv2 or later """ # We are using python unittest for the framework testing. # One problem comes from the fact that we have to test the receiving of # DBus signals, wich mean that we have to handle callback from within the test # cases. It is not easily done using unittest. # We can use the framewok tasklet module to simplify things a little. import framework from framework.patterns.tasklet import Tasklet, Sleep import types import unittest import gobject import ConfigParser import dbus config = ConfigParser.ConfigParser() config.readfp(open('tests.conf')) #=========================================================================# def taskletTest(func): #=========================================================================# """decorator that turn a test into a tasklet The decorator will also take care of starting and stopping the mainloop """ def ret(*args): loop = gobject.MainLoop() done = False ret.error = None ret.done = False def on_done(*args): ret.done = True loop.quit() def on_error(type, err, traceback): ret.done = True ret.error = type, err, traceback loop.quit() Tasklet(generator=func(*args)).start(on_done, on_error) if not ret.done: # Because it could be we returned even before starting the main loop loop.run() if ret.error: raise ret.error[0], ret.error[1], ret.error[2] ret.__dict__ = func.__dict__ ret.__name__ = func.__name__ ret.__doc__ = func.__doc__ return ret #=========================================================================# def request(*conds): #=========================================================================# """This decorator can be used to skip some tests if a test condition if not satisfy It is useful for testing without sim card, or operator to answer questions, etc... You can call it with two arguments : option : string of the form
.