Introduzione Un messaggio SMS, così come specificato dall'organizzazione Etsi ( www.etsi.org ) nei documenti GSM 03.40 e GSM 03.38, può essere lungo sino a 160 caratteri. Ogni carattere è codificato con 7 bit e fa parte di un set ristretto di 127 caratteri detto alfabeto di default a 7 bit. I messaggi ad otto bit (per un massimo di 140 caratteri) generalmente non sono visualizzabili dai telefoni come messaggi di testo, ma sono usati per la trasmissione di dati quali immagini o suonerie. I messaggi a 16 bit (per un massimo di 70 caratteri) sono invece utilizzati per la trasmissione di messaggi di testo in formato Unicode (UCS2).
Il formato PDU
Esistono principalmente due modi per ricevere ed inviare messaggi SMS: il formato testo (non disponibile su alcuni telefoni) ed il formato PDU (protocol description unit). In definitiva, una applicazione in grado di leggere dei messaggi SMS può usare sia la modalità testo sia la modalità PDU, tuttavia utilizzando la prima modalità, l'applicazione è limitata ad un set di opzioni preimpostate mentre utilizzando il formato PDU si ha una maggiore flessibilità.
Formato di un messaggio PDU in ricezione
La stringa PDU di un messaggio in ricezione contiene non soltanto il testo del messaggio vero e proprio, ma anche un insieme di meta-informazioni riguardo il mittente, il centro servizi SMS, il time stamp d’invio, ecc. Tutte queste informazioni sono in forma di ottetti esa-decimali o semi-ottetti decimali. Un ottetto esadecimale è la rappresentazione, sotto forma di stringa esadecimale, del valore di un byte, ottenuto invertendo l’ordine dei nibble o più semplicemente l’ordine delle due cifre esadecimali. Ad esempio il valore decimale 207 (esadecimale CF) è rappresentato dalla stringa ‘FC’. I semi ottetti decimali rappresentano un numero in formato decimale ottenuto invertendo a due a due l’ordine di apparizione delle cifre. Ad esempio il numero del centro servizi 39 34 92 00 02 00 è codificato in semi-ottetti decimali come 93 43 29 00 20 00. Se il numero di cifre non è pari viene aggiunta una F in coda al numero prima dell'inversione.
Il seguente messaggio, in formato PDU, è quanto ricevuto con il messaggio ‘Ciao’
0791934329006060040C9193434774422600002050922242300804C374F80D
07 - la lunghezza del numero del centro servizi (SMSC) espressa in numero di ottetti 91 - tipo del numero del centro servizi SMSC (91 indica il formato internazionale dei numeri di telefono) 93 43 29 00 60 60 - il numero del centro servizi (espresso in semi ottetti decimali) 04 - il primo ottetto del messaggio SMS-DELIVER 0C - lunghezza del numero del mittente espressa in numero di caratteri 91 - tipo del numero del mittente 93 43 47 74 42 26 - il numero del mittente (espresso in semi ottetti decimali) 00 - TP-PID 00 - TP-DCS 20 50 92 22 42 30 08 - TP-SCTS Time Stamp 04 - TP-UDL Lunghezza del messaggio. Se il TP_DCS specifica il formato a 7-bit, la lunghezza è il numero di blocchi di 7 bit. C3 74 F8 0D - il messaggio ‘Ciao’ codificato
Il numero del centro servizi e il numero del mittente sono espressi in semi ottetti-decimali.
Il TP-Data Coding Scheme fornisce informazioni riguardo la codifica, la compressione e l'alfabeto utilizzato. Il valore 00 specifica un testo non compresso, che utilizza l'alfabeto di default , di classe 0 (Immediate display).
Il TP-Service Center Time Stamp specifica l'istante di invio del messaggio. I primi sei caratteri (3 ottetti) rappresentano la data nel formato anno-mese-giorno, i successivi sei l'orario nel formato ore-minuti-secondi. L'ultimo ottetto indica, invece, la time zone riferita a GMT. In particolare ogni unità rappresenta 15 minuti e se il bit più significativo è a 1 il valore indicato è negativo. Anche il time stamp è espresso in semi ottetti decimali, quindi la data
0x20 0x50 0x92 0x22 0x42 0x30 0x08
indica il 20 Maggio 2002 22:24:03 GMT+2
Formato di un messaggio PDU in trasmissione
Al messaggio precedentemente analizzato in ricezione corrisponde il seguente in trasmissione:
079193432900200011000C919343477442260000AA04C374F80D
07 - La lunghezza del numero del centro servizi. Se la lunghezza è 0, viene utilizzato il numero memorizzato nel telefono. Questo valore è opzionale, e su alcuni telefoni l'utilizzo del numero interno è implicito. 91 - Tipo del numero del centro servizi 93 43 29 00 20 00 – il numero del centro servizi 11 - Il primo ottetto del messaggio SMS-SUBMIT 00 - TP - Message-Reference. Il valore 0 fa si che il telefono imposti da solo questo numero 0C - Lunghezza del numero di telefono del destinatario 91 -Tipo del numero di telefono del destinatario 93 43 47 74 42 26 - il numero di telefono del destinatario codificato in semi ottetti decimali 00 - il TP-PID Protocol Identifier 00 - il TP-DCS AA - il TP-VP Validity Period 04 - la lunghezza del messaggio C3 74 F8 0D - il messaggio codificato
Il periodo di validità specifica entro quanto il messaggio può essere consegnato. Superato questo periodo il messaggio viene scartato dal centro servizi. Il periodo può essere espresso in forma relativa, assoluta e accresciuta. Nella forma relativa la validità è espressa da un solo ottetto che viene interpretato in base allo schema di figura 1.
<figura tpvp.gif> Figura 1 – Significato del TP-VP
Codifica a 7 bit
Questo tipo di codifica consiste nel trasformare una sequenza di caratteri a 7 bit (septet), appartenenti all'alfabeto GSM di default, in una sequenza di byte (octet). L'algoritmo compone i singoli byte del messaggio operando nel seguente modo: il carattere di sette bit è trasformato in un byte aggiungendo come bit più significativo il bit più a destra del secondo carattere. Il bit più a destra del secondo carattere viene così consumato. Per otterenere un nuovo byte necessitano, quindi, i due bit meno significativi del terzo carattere. Iterando il procedimento otteniamo una codifica come quella dell’esempio in figura 2
<figura 7bit.gif> Figura 2 - Esempio di codifica a 7 bit
Gli algoritmi per la codifica e la decodifica, possono essere implementati in Java nel modo seguente:
/**
* Questo metodo ritorna una stringa che rappresenta
* in forma esadecimale
* il messaggio passato come parametro codificato nel formato 7-bit
* encoded data
*
* @param message il messaggio da codificare
* @return il messaggio codificato
*/
public static String encode(String message) {
StringBuffer encodedMessage = new StringBuffer();
// Conversione dei caratteri ISO8859-1 nei corrispettivi
// caratteri dell'alfabeto di default
// message = isoTogsm(message) ;
int bits = 0 ;
int bitsLength = 0 ;
for(int i = 0; i < message.length() ; i++) {
char c = message.charAt(i);
bits |= (c << bitsLength);
bitsLength += 7;
while(bitsLength >= 8) {
char octet = (char)(bits & 255);
encodedMessage.append(JSmsUtils.toHexString(octet));
bits >>>= 8;
bitsLength -= 8;
}
}
if((bitsLength > 0))
encodedMessage.append(JSmsUtils.toHexString(bits));
return encodedMessage.toString();
}
/**
* Questo metodo decodifica un messaggio codificato nel
* formato 7-bit encoded data
*
* @param message la stringa esadecimale del messaggio codificato
* @return il messaggio decodificato
*/
public static String decode(String message) throws NumberFormatException {
StringBuffer decodedMessage = new StringBuffer() ;
int bits = 0 ;
int bitsLength = 0 ;
for(int i = 0; i < message.length() ; i += 2) {
char c = (char)Integer.parseInt(message.substring(i, i + 2), 16) ;
bits = (c << bitsLength) | bits ;
bitsLength += 8 ;
while(bitsLength >= 7) {
char septet = (char)(bits & 127);
decodedMessage.append(septet);
bits >>>= 7;
bitsLength -= 7;
}
}
// Conversione dei caratteri dell'alfabeto di default
// nei corrispettivi caratteri ISO8859- 1
return gsmToIso(decodedMessage.toString()) ;
}
Le due funzioni isoToGsm e gsmToIso si occupano di trasformare una stringa di caratteri ISO8859 in una stringa di caratteri dell’alfabeto Gsm di default, in base alla seguente tabella di conversione:
Hex
Dec Character name Character ISO-8859-1 0x00 0 COMMERCIAL AT @ 64 0x01 1 POUND SIGN £ 163 0x02 2 DOLLAR SIGN $ 36 0x03 3 YEN SIGN ¥ 165 0x04 4 LATIN SMALL LETTER E WITH GRAVE È 232 0x05 5 LATIN SMALL LETTER E WITH ACUTE È 233 0x06 6 LATIN SMALL LETTER U WITH GRAVE Ú 250 0x07 7 LATIN SMALL LETTER I WITH GRAVE Ì 236 0x08 8 LATIN SMALL LETTER O WITH GRAVE Ò 242 0x09 9 LATIN CAPITAL LETTER C WITH CEDILLA Ç 199 0x0A 10 LINE FEED
10 0x0B 11 LATIN CAPITAL LETTER O WITH STROKE Ø 216 0x0C 12 LATIN SMALL LETTER O WITH STROKE Ø 248 0x0D 13 CARRIAGE RETURN
13 0x0E 14 LATIN CAPITAL LETTER A WITH RING ABOVE Å 197 0x0F 15 LATIN SMALL LETTER A WITH RING ABOVE Å 229 0x10 16 GREEK CAPITAL LETTER DELTA
0x11 17 LOW LINE _ 95 0x12 18 GREEK CAPITAL LETTER PHI
0x13 19 GREEK CAPITAL LETTER GAMMA
0x14 20 GREEK CAPITAL LETTER LAMBDA
0x15 21 GREEK CAPITAL LETTER OMEGA
0x16 22 GREEK CAPITAL LETTER PI
0x17 23 GREEK CAPITAL LETTER PSI
0x18 24 GREEK CAPITAL LETTER SIGMA
0x19 25 GREEK CAPITAL LETTER THETA
0x1A 26 GREEK CAPITAL LETTER XI
0x1B 27 ESCAPE TO EXTENSION TABLE
0x1B0A 27 10 FORM FEED
12
0x1B14
27 20
CIRCUMFLEX ACCENT
^
94
0x1B28
27 40
LEFT CURLY BRACKET {
123
0x1B29
27 41
RIGHT CURLY BRACKET
}
125
0x1B2F
27 47
REVERSE SOLIDUS(BACKSLASH)
\
92
0x1B3C
27 60
LEFT SQUARE BRACKET
[
91
0x1B3D
27 61
TILDE
~
126
0x1B3E
27 62
RIGHT SQUARE BRACKET
]
93
0x1B40
27 64
VERTICAL BAR
|
124
0x1B65
27 101
EURO SIGN
€
164(ISO - 8859 - 15)
0x1C
28
LATIN CAPITAL LETTER AE
Æ
198
0x1D
29
LATIN SMALL LETTER AE
Æ
230
0x1E
30
LATIN SMALL LETTER SHARP S(German)
ß
223
0x1F
31
LATIN CAPITAL LETTER E WITH ACUTE
Ê
202
0x20
32
SPACE
32 0x21 33 EXCLAMATION MARK ! 33 0x22 34 QUOTATION MARK " 34 0x23 35 NUMBER SIGN # 35 0x24 36 CURRENCY SIGN ¤ 164 (ISO-8859-1) 0x25 37 PERCENT SIGN % 37 0x26 38 AMPERSAND & 38 0x27 39 APOSTROPHE ' 39 0x28 40 LEFT PARENTHESIS ( 40 0x29 41 RIGHT PARENTHESIS ) 41 0x2A 42 ASTERISK * 42 0x2B 43 PLUS SIGN + 43 0x2C 44 COMMA , 44 0x2D 45 HYPHEN-MINUS - 45 0x2E 46 FULL STOP . 46 0x2F 47 SOLIDUS (SLASH) / 47 0x30 48 DIGIT ZERO 0 48 0x31 49 DIGIT ONE 1 49 0x32 50 DIGIT TWO 2 50 0x33 51 DIGIT THREE 3 51 0x34 52 DIGIT FOUR 4 52 0x35 53 DIGIT FIVE 5 53 0x36 54 DIGIT SIX 6 54 0x37 55 DIGIT SEVEN 7 55 0x38 56 DIGIT EIGHT 8 56 0x39 57 DIGIT NINE 9 57 0x3A 58 COLON : 58 0x3B 59 SEMICOLON ; 59 0x3C 60 LESS-THAN SIGN < 60 0x3D 61 EQUALS SIGN = 61 0x3E 62 GREATER-THAN SIGN > 62 0x3F 63 QUESTION MARK ? 63 0x40 64 INVERTED EXCLAMATION MARK ¡ 161 0x41 65 LATIN CAPITAL LETTER A A 65 ….. ….
…. ….. 0x5A 90 LATIN CAPITAL LETTER Z Z 90 0x5B 91 LATIN CAPITAL LETTER A WITH DIAERESIS Ä 196 0x5C 92 LATIN CAPITAL LETTER O WITH DIAERESIS Ö 214 0x5D 93 LATIN CAPITAL LETTER N WITH TILDE Ñ 209 0x5E 94 LATIN CAPITAL LETTER U WITH DIAERESIS Ü 220 0x5F 95 SECTION SIGN § 167 0x60 96 INVERTED QUESTION MARK ¿ 191 0x61 97 LATIN SMALL LETTER A A 97 ….. ….
…. …. 0x7A 122 LATIN SMALL LETTER Z Z 122 0x7B 123 LATIN SMALL LETTER A WITH DIAERESIS Ä 228 0x7C 124 LATIN SMALL LETTER O WITH DIAERESIS Ö 246 0x7D 125 LATIN SMALL LETTER N WITH TILDE Ñ 241 0x7E 126 LATIN SMALL LETTER U WITH DIAERESIS Ü 252 0x7F 127 LATIN SMALL LETTER A WITH GRAVE À 224
Tabella 1 - conversione GSM - ISO8859/1
Esecuzione di comandi AT per l'invio di un messaggio
La lettura o la spedizione di un messaggio SMS avviene inviando una serie di comanti AT, attraverso la porta seriale, dal computer al telefonino.
L’utilizzo delle porte seriali e parallele in Java è reso possibile mediante l’utilizzo delle API Java Communication. Queste API possono essere utilizzate per scrivere delle applicazioni, indipendenti dalla piattaforma, che accedono alle porte di comunicazione disponibili. Le funzionalità messe a disposizione dalle javacomm possono essere così riassunte: enumerazione delle porte disponibili su un sistema apertura e blocco di una porta comunicazione sincrona e asincrona gestione degli eventi di cambio stato
Di seguito è riportato il codice per l’apertura di una porta seriale
CommPortIdentifier portId = null;
Enumeration portList = CommPortIdentifier.getPortIdentifiers();
while(portList.hasMoreElements()) {
portId = (CommPortIdentifier) portList.nextElement();
if(portId.getPortType() == CommPortIdentifier.PORT_SERIAL) {
if(portId.getName().equals(portName)) {
serialPort = (SerialPort)portId.open(JSms.class.getName(), 2000);
break ;
}
}
}
if(serialPort == null) throw new Exception("Serial port " + portName +
" not found.") ;
outStream = serialPort.getOutputStream();
inStream = serialPort.getInputStream();
serialPort.setSerialPortParams(bitRate, SerialPort.DATABITS_8,
SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
Il ciclo while scorre le porte disponibili sulla macchina sino ad individuare quella con il nome indicato. Se la porta non viene trovata, viene rilanciata una eccezione, altrimenti vengono creati gli stream per l’input e l’output. L’ultima istruzione, infine, imposta i parametri di comunicazione della porta seriale.
Una volta ottenuti gli stream di input ed output, possiamo inviare al telefonino i comandi AT necessari per gestire i messaggi SMS. I comandi AT di nostro interesse sono:
Inizializzazione:
ATE0 – disabilita l’eco dei comandi ATQ0 – abilita le risposte del DTE
Invio: AT+CMGF=0 – imposta il formato PDF AT+CMGS=lunghezza_messaggio – invia un messaggio
Ricezione:
AT+CMGL – Legge tutti i messaggi AT+CMGR=numero_messaggio - Legge un messaggio
Il comando AT+CMGS deve essere seguito dal messaggio in formato PDF terminato dal caratte ‘\032’
Volendo inviare il messaggio PDU codificato in precedenza, dobbiamo quindi inviare, in successione, le seguenti stringhe:
AT + CMGF = 0\015
AT + CMGS = 52\015
079193432900200011000C919343477442260000AA04C374F80D\032
Ogni stringa AT per poter essere eseguita deve essere terminata dal carattere ‘\015’, quindi, occorre aggiungerlo in coda ad ogni comando inviato al telefonino. Se l’esecuzione di un comando AT fallisce, il telefono risponde con un codice di errore del tipo:
+CMS ERROR: <err>
I codice numerici di errore principali hanno il seguente significato:
300 Phone failure
301 SMS service of phone 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 necessary 312 PH-SIM PIN necessary 313 SIM failure 314 SIM busy 315 SIM wrong 320 Memory failure 321 Invalid memory index 322 Memory full 330 SMSC (message service center) address unknown 331 No network service 332 Network timeout 500 Unknown error
Per verificare se l’esecuzione di ogni comando è andata a buon fine, occorre leggere i messaggi di risposta del telefonino. Per leggere occorre registrare un listener degli eventi generati dalla porta seriale. Questo viene effettuato con le istruzioni
serialPort.addEventListener(serialPortEventListener);
serialPort.notifyOnDataAvailable(true);
La classe listener deve implementare l’interfaccia SerialPortEventListener definendone il metodo serialEvent. Il codice seguente mostra una implementazione del metodo serialEvent che legge i dati dalla porta e li elabora, una riga per volta, ogni volta che incontra la sequenza di caratteri cr e lf.
public void serialEvent(SerialPortEvent event) {
if(event.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
int n;
try {
while((n = inStream.available()) > 0) {
n = inStream.read(readBuffer, bufferOffset, n);
bufferOffset += n;
if((readBuffer[bufferOffset - 1] == 10) &&
(readBuffer[bufferOffset - 2] == 13)) {
String newLine = new String(readBuffer, 0, bufferOffset - 2);
// Elaborazione della riga ricevuta
// lineRecived( newLine );
bufferOffset = 0;
}
}
}
catch(IOException e) {
………….
}
}
}
Conclusioni
Gli SMS sono ormai un nuovo modo di comunicare che si è diffuso di pari passo con i telefonini, grazie alla immediatezza e alla economicità. Una applicazione può sfruttare gli SMS tutte le volte che la criticità dei messaggi non consente l’invio di una semplice mail (ad esempio la segnalazione di malfunzioni di sistemi che richiedono un intervento umano tempestivo). Java, con le API Javacomm, consente di sfruttare le potenzialità degli SMS dimostrando, ancora una volta, di essere un linguaggio flessibile e completo.
Bibliografia
[1] Javacomm - http://java.sun.com
[2] Etsi - http://www-etsi.org