SMS server v Pythonu

10.08.2011

SMS server je software, který umožňuje v rámci lokální sítě odesílat SMS zprávy pomocí HW GSM modemu. Hlavním účelem jeho vzniku byla moje potřeba se trošku zorientovat v Pythonu a nejlépe to jde na nečem, co má smysl a funkční potenciál. Nejedná se o žádné veledílo, ale spiše o softwarový bastl, který ale funguje a plná svůj účel k maximální spokojenosti.

Intro

Pokud máte ve správě nějaké Windows servery a na nich naplánované úlohy, tak se vám zcela jistě už někdy stalo, že nějaká úloha selhala - tedy vyskytla se taková chyba, jejíž neřešení by mohlo vést závažným problémům (poškozená data, nefunkční aplikace, neprovedá záloha apod.). V takovém případě je jistě namístě, aby byla správci odeslána varovná SMSka.

Jenže jak na to? Vynechám-li použití webové SMS brány pro závislost na připojení do Internetu, je jediným řešením HW GSM modem. Pokud máte ve správě pouze jeden fyzický server, je situace poměrně snadná - stačí modem připojit k sériovému portu a pomocí několika AT příkazů SMS odeslat. V případě, že vaše "portfolio" čítá desitky virtuálních mašin a to v různých lokalitách, je jediným schůdným řešením vytvořit nějakou síťovou službu, která bude schopna SMS zprávy centralizovaně odesílat - a právě to řeší tento SMS server.

Jak to funguje?

Pro síťové a administrační věci preferuji Linux - je to tak trochu srdcovka :-) Čili základem je Debian a k němu na sériový port připojený GSM modem. V mém případě se jedná o fyzický stroj, neboť je mimo SMS serveru, využíván k několika dalším síťovým službám. Pokud by však měl řešit pouze SMS server, tak by byl realizován jako virtuální mašina s tím, že by k němu byl namapován sériový port fyzického host serveru. Každopádně, pokud si lze s modemem povídat pomocí AT příkazů je vše v naprostém pořádku.

Na uvedeném linuxovém stroji je spuštěn TCP server napsaný v Pythonu. Jedná se o primitivní implementaci práce se sockety, ale vzhledem k tomu, že síťová komunikace probíhá pouze v rámci lokální sítě a spojení je povoleno jen pro definovanou množinu klientů (firewall), tak toto řešení postačuje. Navíc četnost použití není příliš vysoké - SMS zpráva obvykle znamená problém a práci :-) TCP server poslouchá na porti 2021 a komunikuje v plaintextu - tedy nešifrovaně v lidsky čitelném textu.

Běžící server čeka na připojení od klientů. Pokud se tak stane, tak očekává, že mu klient předá požadavek na odeslání SMS - tedy svým způsobem formátovaný řetězec, který obsahuje telefonní číslo a zprávu. Přijatý požadavek server uloží do databáze - konkrétně MySQL. Jiný proces pak požadavek z databáze vyjme a pomocí připojeného GSM modemu zprávu odešle. Tento asynchronní způsob je zvolen proto, že požadavků na odeslání zpráv může TCP serveru přijít více, přičemž rychlost odesílání zpráv modemem je malá. Databáze je tedy něco jako buffer. V databázi pak zůstává i informace, že zpráva byla odeslána a kdy.

Komentář

Pro přenos požadavku od klienta k SMS serveru lze samozřejmě použít mnohem robustnější metody - xinetd, snmp trapy, http požadavky, zabezpečení SSL, ale jak jsem psal v úvodu, šlo o to se trochu naučit základům Pythonu. SMS server je tedy něco jako "by the way" :-)

Dokumentace

Celý SMS server je realizován na serveru takto:

GSM Modul

Jedná se o GSM modul Cinterion TC35i (původní výrobce Siemens), připojený k serveru na fyzický sériový port /dev/ttyS0 (COM1). Alternativně lze použít i jiný modul, který lze ovládat pomocí AT příkazů, avšak tento je ověřen a určen i pro průmyslové použití. V modulu je vložena tarifní SIM karta.

Ovládací skripty

Jejich činnost je založena na vzájemné provázanosti následujících elementů:

Tabulky v MySQL

Databáze: sms

TCP Server

Principem činnosti je otevření portu 2021 pro konzolovou TCP komunikaci (telnet). Na tomto portu očekává připojení od klientů a předání požadavků na odeslání SMS zpráv. Pokud klient zašle požadavek ve správném formátu, server uloží jeho obsah do tabulky msg a následně zavolá skript check_and_send. Tímto dojde k okamžitému odeslání zprávy. Klient s TCP serverem komunikuje plain-textem.

Skript /usr/local/smss/smsspotřebuje ke svému běhu třídu daemon. Obsahuje metody na daemononizaci procesu, tedy to, že se tcp server spustí na pozadí a zůstane běžet.

Skript /usr/local/smss/smss potřebuje ke svému běhu třídu logger. Obsahuje jednoduch0 metody logování do souboru.

TCP se dá spustit nebo restartovat následujícím způsobem.

Pokud Python zakřičí, že nemá nějaký modul, stačí ho doinstalovat z debianích repozitářů.

PID file je implementován pro snadnou identifikaci procesu, neboť TCP server je po spuštění daemonizován. Obsahem PID file je číslo procesu. V případě problémů se spuštěním je třeba PID soubor smazat.

Formát požadavku na odeslání SMS zprávy: #phone1;text1#phone2;tex2...#phonen;textn

Př. #420608337726;AHOJ#420777123456;Kolotoc

Skript check_and_send

Skript přečte obsah tabulky msg a v případě, že nalezne přijaté požadavky na odeslání SMS zpráv, tyto zprávy odešle zapíše do tabulky sms jako odeslané. Poté otestuje existenci nějakých přijatých SMS zpráv a pokud takové jsou, zapíše je do tabulky rcv a z GSM modulu odstraní.

Skript je spouštěn periodicky cronem (každou minutu) a případně TCP serverem. PID file je zde implementován jako ochrana proti vícenásobnému spuštění (TCP server a následně cron).

Skript /usr/local/smss/check_and_send potřebuje ke svému běhu třídu sms a PDU. Třídy obsahují metody pro komunikaci s GSM modemem a vytváření zpráv.

Dále je potřeba třída daemon a logger - popsány jsou výše. Opět, pokud Python zakřičí, že nemá nějaký modul, stačí ho doinstalovat z debianích repozitářů.

Webové rozhraní

Slouží k získání přehledu o odeslaných a přijatých zprávách. Je rovněž možné pomocí jednoduchého formuláře SMS odeslat. Webové rozhraní využívá pouze obsah MySQL tabulek. Jinak klasika Apache + PHP.

Klienti

Popis formátu požadavku, který akceptuje TCP server

#phone1;text1#phone2;text2...#phonen;textn

Každý požadavek začíná vždy znakem # a při jednom připojení k serveru lze požadavky zřetězit a odeslat tak více SMS zpráv na různá telefonní čísla.

Příklad:
#420608337726;AHOJ odešle text AHOJ na číslo 420608337726
#420608337726;AHOJ#420777123456;Cau odešle text AHOJ na číslo 420608337726 a CAU na 420777123456

Klienti

Typické použití SMSSClienta:
SMSSClient.exe -a 192.168.1.1 #420608337726;Zprava

Vrací exitcode 0 v případě úspěšného odeslání požadavku a 1 v případě neúspěchu. Exitcode ovšem neříká nic o tom, zda SMS skutečně odešla nebo ne, pouze potvrzuje přijetí požadavku TCP serverem. V případě nedoručení je třeba nahlédnout do logu TCP serveru nebo skriptu check_and_send.