MokaByte 66 - 7mbre 2002 
JavaComm e GSM
Inviare e ricevere messaggi SMS in Java
di
Paolo Mascia
La maggior parte dei cellulari attualmente in commercio è dotata di una interfaccia seriale che consente lo scambio di informazioni tra telefono e computer o attraverso un cavo, od una porta ad infrarossi. Utilizzando le Communication API ed un telefono GSM connesso ad una porta seriale, è possibile inviare e ricevere messaggi direttamente da una applicazione Java.


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

infatti

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 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 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:

  1. enumerazione delle porte disponibili su un sistema
  2. apertura e blocco di una porta
  3. comunicazione sincrona e asincrona
  4. 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/products/javacomm
[2] Etsi - http://www-etsi.org

 

MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it