Бесплатное определение координат по GSM сотам

Сколько раз доводилось слышать, что криминальные элементы или правоохранительные органы могут определить местоположение телефона по его номеру. Это правда :) Изначально GSM стандарты не разрабатывались для этих целей, поэтому, безусловно, точность полученных координат конечно же уступает всем известным GPS / ГЛОНАСС. В густонаселенных местах, где плотность базовых станций (такие закрытые будки с антеннами) большая точность повышается, а в сельской местности, на трассе соответственно уменьшается. Не особо углубляясь в теорию предлагаю посмотреть как в любое время можно определить местоположение ручками с помощью обычных AT-команд.

Итак, у каждого GSM модуля, зарегистрированного в сети мобильного оператора, всегда можно вполне легально запросить следующие параметры:

Для экспериментов можно задействовать любой мобильный телефон (либо GSM модуль типа sim900, если Вы дружите с паяльником), который имеет возможность подключения к ПК. Поскольку особого желания возиться с проводами нету, самый простой способ соединить телефон и компьютер - использовать Bluetooth. Посмотрим какие устройства доступны для подключения:

~$ hcitool scan
Scanning ...
    00:11:22:33:44:55   Nokia 6300

Имея MAC адрес устройства, можем посмотреть список сервисов (вывод команды показан не весь):

~$ sdptool browse 00:11:22:33:44:55
Browsing 00:11:22:33:44:55 ...
......
Service Name: COM 1
Service RecHandle: 0x10002
Service Class ID List:
  "Serial Port" (0x1101)
Protocol Descriptor List:
  "L2CAP" (0x0100)
  "RFCOMM" (0x0003)
    Channel: 3
Language Base Attr List:
  code_ISO639: 0x656e
  encoding:    0x6a
  base_offset: 0x100
......

Цепляемся к Channel: 3:

~$ rfcomm connect 0 00:11:22:33:44:55 3
Connected /dev/rfcomm0 to 00:11:22:33:44:55 on channel 3
Press CTRL-C for hangup

Теперь мы можем подключиться к последовательному порту /dev/rfcomm0:

# cu -l /dev/rfcomm0
# screen /dev/rfcomm0
# minicom
~$ putty -serial /dev/rfcomm0

После запуска Putty открывается окошко, в котором можно писать AT-команды:

# ATE1
AT
OK

Отлично. Нас интересуют две AT-команды: AT+COPS и AT+CREG, которые имеют настраиваемый формат ответа - задаём тот, который устроит:

AT+COPS=0,2
OK
AT+CREG=2
OK

Всё готово - запрашиваем MCC, MNC, LAC, CID:

AT+COPS?
+COPS: 0,2,"25501",0

OK
AT+CREG?
+CREG: 2,1,"8174","EA45"

OK

В сети полно онлайн сервисов, которые позволяют преобразовать MCC=255, MNC=01, LAC=8174, CID=EA45 в координаты Latitude, Longitude, но, как всегда, Гуголь знает всё:

pygsm.py

#!/usr/bin/python

MMAP_URL = 'http://www.google.com/glm/mmap'
GOOGLE_MAPS_URL  = 'http://maps.google.com'

import shlex
import csv
import urllib2
import serial # pip install pyserial
import time

def fetch_cell_from_serial(comm='/dev/rfcomm0'):

    # open serial and setup GSM module

    ser = serial.Serial(comm)
    ser.write('AT+COPS=0,2\r')
    ser.write('AT+CREG=2\r')

    # Get MCC and MNC

    ser.write('AT+COPS?\r')
    time.sleep(1)
    out = ser.read(ser.inWaiting())
    # out >>> 'AT+COPS?\r\r\n+COPS: 0,2,"25501",0\r\n\r\nOK\r\n'

    tmp = shlex.split(out) 
    # tmp >>> ['AT+COPS?', '+COPS:', '0,2,25501,0', 'OK']
    tmp = tmp[tmp.index('+COPS:') + 1] # '0,2,25501,0'
    tmp = csv.reader([tmp])
    tmp = [row for row in tmp][-1] # ['0', '2', '25501', '0']
    mcc_mnc_dec = int(tmp[-2], 16) # 152833

    # Get LAC and CID

    ser.write('AT+CREG?\r')
    time.sleep(1)
    out = ser.read(ser.inWaiting())
    # out >>> 'AT+CREG?\r\r\n+CREG: 2,1,"8174","EA45"\r\n\r\nOK\r\n'

    tmp = shlex.split(out)
    # tmp >>> ['AT+CREG?', '+CREG:', '2,1,8174,EA45', 'OK']
    tmp = tmp[tmp.index('+CREG:') + 1] # '2,1,8174,E76A'
    tmp = csv.reader([tmp])
    tmp = [row for row in tmp][-1] # ['2', '1', '8174', 'E76A']

    lac_dec = int(tmp[-2], 16) # 33140
    cid_dec = int(tmp[-1], 16) # 59242

    return cid_dec, lac_dec, mcc_mnc_dec

def get_location_by_cell(cid_dec, lac_dec, mcc_mnc_dec):

    # Use Google to get coordinates

    a = '000E00000000000000000000000000001B0000000000000000000000030000'
    h1, h2 = divmod(mcc_mnc_dec, 100) # 152833 >>> (1528, 33)
    b = hex(cid_dec)[2:].zfill(8) + hex(lac_dec)[2:].zfill(8)
    # b >>> '0000e76a00008174'
    c = hex(h1)[2:].zfill(8) + hex(h2)[2:].zfill(8)
    # c >>> '000005f800000021'

    data = (a + b + c + 'FFFFFFFF00000000').decode('hex')
    response = urllib2.urlopen(MMAP_URL, data)
    res_hex = response.read().encode('hex')
    # res_hex >>> '000e1b0000000002e5bfbd020f49a50000097b0000004b0000'

    latitude = float(int(res_hex[14:22], 16)) / 1000000 # 48.611261
    longitude = float(int(res_hex[22:30], 16)) / 1000000 # 34.556325

    return latitude, longitude

def format_googlemaps_link(latitude, longitude):
    return '%s?q=%f,%f' % (GOOGLE_MAPS_URL, latitude, longitude)


if __name__ == '__main__':
    cid_dec, lac_dec, mcc_mnc_dec = fetch_cell_from_serial()
    lat, lon = get_location_by_cell(cid_dec, lac_dec, mcc_mnc_dec)
    print format_googlemaps_link(lat, lon)

Скрипт написан на питоне и использует pyserial для коммуникации с последовательным портом.

~$ python pygsm.py 
http://maps.google.com?q=48.611261,34.556325

ПОЛЕЗНЫЕ ТРЮКИ

Данные обмена можно снифить:

#apt-get install socat
~$ socat -d -v -x \
PTY,link=/dev/ttyNONGREEDY,raw,perm=777,echo=0 \
/dev/rfcomm0,raw
#echo -e 'AT\r' > /dev/ttyNONGREEDY
> 2014/01/11 11:34:08.565031  length=4 from=0 to=3
 41 54 0d 0a                                      AT..
--
< 2014/01/11 11:34:08.743405  length=6 from=0 to=5
 0d 0a                                            ..
 4f 4b 0d 0a                                      OK..
--

Порой для корректной работы необходимо задать источнику скорость:

~$ socat -d -v -x \
PTY,link=/dev/ttyNONGREEDY,raw,perm=777 \
/dev/ttyUSB0,raw,b115200

Если нужно пробрасить последовательный порт по сети от одного компа к другому:

~$ wget -O tcp_serial_redirect.py \
http://sourceforge.net/p/pyserial/code/HEAD/tree/trunk/\
pyserial/examples/tcp_serial_redirect.py?format=raw
~$ python tcp_serial_redirect.py -b 115200 -p /dev/rfcomm0 --spy
#ifconfig | grep -o 'inet addr:[^ ]*'
#inet addr:192.168.1.246

# на другом компе
~$ socat PTY,link=/dev/rfcomm0,perm=777 TCP:192.168.1.246:7777

Эмулировать устройство можно так:

~$ socat -d -v -x \
PTY,link=/dev/rfcomm0,raw,perm=777,echo=0 \
PTY,link=/dev/ttyNONGREEDY,raw,perm=777,echo=0
# echo -e 'AT\r' > /dev/rfcomm0
> 2014/01/11 12:31:37.585420  length=4 from=0 to=3
 41 54 0d 0a                                      AT..
--
< 2014/01/11 12:31:37.586518  length=7 from=0 to=6
 41 54 0d 0a                                      AT..
 4f 4b 0a                                         OK.
--

~$ cat /dev/ttyNONGREEDY | while read val; \
do echo $val && \
echo $val > /dev/ttyNONGREEDY && \
echo 'OK' > /dev/ttyNONGREEDY; \
done
AT

P.S. Совсем необязательно иметь ПК чтобы получить MCC, MNC, LAC, CID с телефона - просто так удобнее для текущей задачи. Большинство производителей предоставляют API для доступа к подобного рода данным из приложений, запускаемых в их телефонах. В этом случае можно (и даже нужно) обойтись без AT команд. Вот например API для мидлетов Nokia и т.д.

links

social