python

Gammu with Samsung

A housemate of mine got a new Samsung phone on the weekend. Being a resident geek, I offered to transfer her contacts across rather than get her sister to manually retype 500-odd contacts.

Naturally, I thought this would be a simple problem, right? I mean, everyone updates their phones every 2 years, this must be a pretty common use case. All my Sony Ericsson phones have had a “send all contacts by Bluetooth” option since the inception of Bluetooth. Naturally, it didn’t have such a feature, it only supports sending one contact at a time. (Although, to Samsung’s credit, the new phone will be able to do for the next upgrade)

Next option: I’ll sync old phone to laptop to new phone.

The Samsung website has a helpful Windows utility that you can download to do this, however you need the cable to link the phone to the computer. The phones needed different cables, and I had neither. My laptop with a Windows partition has had broken Bluetooth ever since its motherboard got replaced. So that wasn’t an option. The phones don’t have IRDA, so there was no way to connect them with the Windows laptop.

Time to do it properly.

I tried wammu, a python-based gammu GUI. It supported the phones via the “blueat” driver, and could browse their SIM cards fine, but not their internal Phonebooks. It couldn’t back them up either. A bit of poking around with gammu on the command line showed that the internal phone books are not 0-indexed (normal computer counting, 0 to n-1) or 1-indexed (normal human counting, 1 to n), but 2-indexed. Dijkstra would turn in his grave!

At this point, I could see that I was going to have to write my own, backup utility. The output of gammu was awkable, but seeing as there are good gammu-python bindings, I decided to do it in pure Python.

Reading the address book went something like this:

import gammu, pickle
sm = gammu.StateMachine()
sm.ReadConfig(3, 0)
sm.Init()
old = []
for i in range(2, 587):
        old.append(sm.GetMemory("ME", i))

pickle.dump(old, file("phonebook.dump", "w"))

The 3 signifies gammu configuration number 3, read into position 0. 587 is the number of address book entries. “ME” means internal memory. I then pickled “old” in preparation for the next stage. Here is an example of an item in old:

{‘Entries’: [{‘AddError’: 7517792,
              ‘Type’: ‘Text_FirstName’,
              ‘Value’: u‘Foo’},
             {‘AddError’: 796160623,
              ‘Type’: ‘Text_LastName’,
              ‘Value’: u‘Bar’},
             {‘AddError’: 796160623,
              ‘SMSList’: [],
              ‘Type’: ‘Number_Other’,
              ‘Value’: u‘0211234567’,
              ‘VoiceTag’: 0},
             {‘Type’: ‘Category’, ‘Value’: 0}],
 ‘Location’: 2,
 ‘MemoryType’: ME}

Pretty icky, but at least all the information is there. At this point, one should be able to feed it into the new phone:

sm.Terminate()
sm = gammu.StateMachine()
sm.ReadConfig(4, 0)
sm.Init()
for i in old:
        sm.AddMemory(i)

However nothing I tried worked, I always got an “Invalid Location” error. I think the 2-indexing is trumping gammu again.

Next idea, lets munge the data into vCard format and use wammu / gammu’s “import from vCard” function. (Code coming up soon) Turns out this doesn’t work either. The phone only received the First name, first phone number, and various other things that I didn’t send it (i.e. custom ring tones that it made up). Hmph!

Aha, but cellphones can normally Bluetooth vCards to each other. So I pushed it the vCard collection via obexftp. Starts transmitting, but then the phone reboots. I played around a bit, and found that if you send it more than one vCard in a vCard file, it reboots. Lovely.

So my final solution was: Extract address book with python-gammu. Transform into vCards. Send each one individually. At least the phone had a “trust this device” option so that it wouldn’t prompt the user for every vCard I sent, but just automatically import them - the first sensible feature I’ve found on it.

Here goes:

#!/usr/bin/env python
import os, pickle, time

def normalise_num(n):
        "Neaten up the phone number, internationalise, etc."
        if n.startswith("+"):
                return n
        if n.startswith("00"):
                return "+" + n[2:]
        if len(n) == 10 and n[0] == "0":
                return "+27" + n[1:]
        return n

d = pickle.load(file("phonebook.dump", "r"))

# Normalise into a sensible format:
o = []
for i in d:
        t = {}
        for j in i["Entries"]:
                if j["Type"] == "Text_FirstName":
                        t["First"] = j["Value"]
                if j["Type"] == "Text_LastName":
                        t["Last"] = j["Value"]
                if j["Type"] == "Number_Other":
                        n = normalise_num(j["Value"])
                        type = "Home"
                        if n[3] in ("7", "8"):
                                type = "Cell"
                        if type not in t:
                                t[type] = []
                        t[type].append(n)
        o.append(t)

# Write & Send vCards:
for i in o:
        f = file("temp.vcf", "w")
        f.write("BEGIN:VCARD\n")
        f.write("VERSION:2.1\n")
        f.write("N:%s;%s;;;\n" % (i.get("Last", ""), i.get("First", "")))
        pref = ";PREF"
        for j in i["Cell"]:
                f.write("TEL;CELL%s:%s\n" % (pref, j))
                pref=""
        for j in i["Home"]:
                f.write("TEL;HOME%s:%s\n" % (pref, j))
                pref=""
        f.write("END:VCARD\n")
        f.close()
        os.system("obexftp -b 00:DE:AD:00:BE:EF -p temp.vcf")
        # Give the thing a chance to recover:
        time.sleep(0.1)

Yes, the normalisation could be done with list comprehensions, but it would be horrible to read. And there might by Python Obex bindings, but I couldn’t be bothered.

I got to spend an afternoon messing with dodgy Cellphones, rather than having a teenager do the job for free. I think I chose the wrong option, but at least it was fun.

Footnote: Samsung, your phones User Interface is awful. Why on earth is Bluetooth under “Applications” rather than “Settings”? I searched everywhere but there, and finally googled before I found it…

SugarCRM to Mozilla LDAP contact slurper

Seeing as SugarCRM is now truly Open Source, I decided to support them buy using SugarCRM as a contact database for a client.

This script extracts contact data from Sugar, and imports it into an LDAP tree, so that Thunderbird clients can use it as an address book.

It’s written for Sugar 5.0, and a suitable LDAP installation with the Mozilla Schema

A suitable VIEW for printing out a contact directory would look like:

51&q=CREATE&lr=lang_en">CREATE 5.1/en/non-typed-operators.html">OR 51&q=REPLACE&lr=lang_en">REPLACE 51&q=VIEW&lr=lang_en">VIEW sugarab 51&q=AS&lr=lang_en">AS
51&q=SELECT&lr=lang_en">SELECT c.id, c.description, salutation, first_name, last_name, title, department,
do_not_call, phone_home, phone_mobile, phone_work, phone_other, c.phone_fax,
primary_address_street, primary_address_city, primary_address_state,
primary_address_postalcode, primary_address_country, alt_address_street,
alt_address_city, alt_address_state, alt_address_postalcode, alt_address_country,
assistant, assistant_phone, lead_source, birthdate,
a.name 51&q=AS&lr=lang_en">AS account_name,
e1.email_address 51&q=AS&lr=lang_en">AS primary_email, e2.email_address 51&q=AS&lr=lang_en">AS secondary_email
51&q=FROM&lr=lang_en">FROM contacts 51&q=AS&lr=lang_en">AS c
5.1/en/string-functions.html">LEFT 51&q=OUTER&lr=lang_en">OUTER 51&q=JOIN&lr=lang_en">JOIN accounts_contacts 51&q=AS&lr=lang_en">AS j 51&q=ON&lr=lang_en">ON (c.id = j.contact_id 5.1/en/non-typed-operators.html">AND j.deleted = 0)
5.1/en/string-functions.html">LEFT 51&q=OUTER&lr=lang_en">OUTER 51&q=JOIN&lr=lang_en">JOIN accounts 51&q=AS&lr=lang_en">AS a 51&q=ON&lr=lang_en">ON (j.account_id = a.id 5.1/en/non-typed-operators.html">AND a.deleted = 0)
5.1/en/string-functions.html">LEFT 51&q=OUTER&lr=lang_en">OUTER 51&q=JOIN&lr=lang_en">JOIN email_addr_bean_rel 51&q=AS&lr=lang_en">as eb 51&q=ON&lr=lang_en">ON (eb.bean_id = c.id 5.1/en/non-typed-operators.html">AND eb.deleted = 0)
5.1/en/string-functions.html">LEFT 51&q=OUTER&lr=lang_en">OUTER 51&q=JOIN&lr=lang_en">JOIN email_addresses 51&q=AS&lr=lang_en">as e1 51&q=ON&lr=lang_en">ON (eb.email_address_id = e1.id 5.1/en/non-typed-operators.html">AND eb.primary_address = 1 5.1/en/non-typed-operators.html">AND e1.deleted = 0)
5.1/en/string-functions.html">LEFT 51&q=OUTER&lr=lang_en">OUTER 51&q=JOIN&lr=lang_en">JOIN email_addresses 51&q=AS&lr=lang_en">as e2 51&q=ON&lr=lang_en">ON (eb.email_address_id = e2.id 5.1/en/non-typed-operators.html">AND e1.id != e2.id 5.1/en/non-typed-operators.html">AND e2.deleted = 0)
51&q=WHERE&lr=lang_en">WHERE c.deleted = 0;

Firefly

Back in the days of using dial-up, I wrote this firewall & QoS script. All in one and relatively easy to use for my specific use case. However it only really works for single machines or small networks routers with no DMZ and only one router.

Mozilla LDIF fixer

Mozilla Address Book exports pretty terrible LDIF. To add these LDIF files to an LDAP directory, you must first run it through this utility. It will turn the LDIF into schematically correct LDIF. If will (hopefully) not maul data that is already in the correct format.

DF Warn

This script is for putting in cron on remote servers. It does a df and mails root if a filesystem is over 80% full. (Naturally configurable, too).

Never let a server in your control fill up a file-system slowly over time again.

Syndicate content