UNIVERSITÀ POLITECNICA DELLE MARCHE FACOLTÀ DI INGEGNERIA CORSO DI LAUREA IN INGEGNERIA INFORMATICA E DELL’AUTOMAZIONE Studio ed implementazione di un servizio di prenotazioni “on-line” su piattaforma MHP utilizzando Set Top Box da sviluppo ADB X-75 Relatore : Prof. Aldo Franco DRAGONI Correlatore: Prof. Paolo PULITI Tesi di Laurea di: Bracalente Fabio Anno Accademico 2005-2006 Dietro ogni problema c’è un’opportunità… Galileo Galilei Ringraziamenti Sarò in controtendenza ma vorrei iniziare dai ringraziamenti “tecnici” per arrivare poi a quelli affettivi. Ringrazio il Prof. Aldo Franco Dragoni per avermi spinto verso una nuova tecnologia in fase di sviluppo, che speriamo apra nuovi sbocchi, grazie anche per avermi dato fiducia e costante assistenza in questo progetto. Al Dott. Fabio Tonucci per la sua disponibilità durante il periodo di tirocinio. Al Prof. Paolo Puliti per avermi fatto scoprire un nuovo linguaggio di programmazione, e per avermi insegnato a vedere il Mondo ad “Oggetti”. Vorrei ricordare il Prof. Maurizio Panti e ringraziarlo perché devo a lui le conoscenze in campo di analisi dei dati e Database. Vorrei ringraziare anche tutti gli altri professori che ho incontrato nel cammino scolastico e soprattutto universitario perché ognuno di loro ha contribuito a formarmi sia culturalmente che caratterialmente. Ai colleghi tirocinanti con cui ho condiviso questa nuova esperienza. Un ringraziamento veramente di cuore va in particolare ai miei genitori perché mi hanno sempre sostenuto e lasciato libero in tutte le mie scelte e soprattutto perché senza di loro tutto questo non sarebbe stato possibile. A mio fratello, spirito guida ed esempio di diligenza sicuramente da seguire. A tutti i miei nonni che mi hanno trasmesso, una in particolare che continua tuttora a farlo, un po’ della loro saggezza. Ai tutti i miei zii che fin da piccolo mi hanno allietato tante giornate e qualcuno in particolare mi ha anche aiutato, in tutti i campi, in fasi particolari della mia vita. Ai miei cugini più o meno piccoli perché, anche se li trascuro un po’, li porto sempre nel cuore. Un grazie anche a tutti i diversi coinquilini con cui ho convissuto in tutti questi anni. Un grazie a tutti gli amici e amiche che mi sono stati vicino e spero che continuino a farlo. Un ringraziamento doveroso all’Hotstaff che è stato sinonimo di sostentamento nel periodo degli studi, ma soprattutto: di grandi amicizie, di innumerevoli conoscenze, di momenti di divertimento allo stato puro, una vera e propria medicina nei periodi tristi di questi ultimi anni; forse anche la causa dell’allungamento della mia carriera universitaria, ma ne è valsa la pena, grazie a tutti di cuore. Ultimo, ma sicuramente non per importanza, un grazie tutto per lei lo devo alla mia ragazza; grazie semplicemente di esistere e di avermi sopportato e supportato in questi ultimi periodi. Sommario Sommario GLOSSARIO................................................................................................................... 1 INTRODUZIONE........................................................................................................... 2 CAPITOLO 1 IL DIGITALE TERRESTRE ........................................................................................ 3 1.1 INTRODUZIONE ......................................................................................................... 3 1.2 NOVITÀ TECNOLOGICHE ........................................................................................... 3 1.3 LO STANDARD MHP................................................................................................. 7 1.4 BROADCASTING DI APPLICAZIONI INTERATTIVE ..................................................... 10 1.5 IL DECODER INTERATTIVO ...................................................................................... 11 CAPITOLO 2 APPLICAZIONI MHP................................................................................................. 13 2.1 MODELLO DI BUSINESS E INTERFACCE ................................................................... 13 2.2 XLET ...................................................................................................................... 15 2.3 GESTIONE DELLE RISORSE SCARSE ......................................................................... 18 2.4 MODELLO GRAFICO ................................................................................................ 25 2.4.1 Background layer........................................................................................... 26 2.4.2 Video layer ..................................................................................................... 32 2.4.3 Graphics layer ............................................................................................... 35 2.5 GESTIONE DEGLI EVENTI DEL TELECOMANDO ........................................................ 38 CAPITOLO 3 CANALE DI RITORNO .............................................................................................. 41 3.1 LO SCENARIO ......................................................................................................... 41 3.2 I PROTOCOLLI SUPPORTATI ..................................................................................... 45 3.2.1 Livello 1: Fisico ............................................................................................. 45 3.2.2 Livello 2: PPP................................................................................................ 46 3.2.3 Protocollo di rete: IP ..................................................................................... 48 3.2.4 Protocolli di trasporto: TCP / UDP .............................................................. 48 3.2.5 Hypertext Transfer Protocol (HTTP)............................................................. 49 3.2.6 Secure Hypertext Transfer Protocol (HTTPS)............................................... 51 3.2.7 Servizi Specifici.............................................................................................. 51 3.3 GESTIONE DELLA CONNESSIONE ............................................................................. 52 3.3.1 Utilizzare il canale di ritorno: il package org.dvb.net.rc .............................. 52 3.3.2 Interfacce per il Canale di Ritorno ................................................................ 53 3.3.3 Acquisire l’interfaccia corretta...................................................................... 55 3.3.4 Stabilire la connessione ................................................................................. 57 3.3.5 I package java.net e java.io ........................................................................... 61 I Sommario CAPITOLO 4 AMBIENTE DI SVILUPPO ........................................................................................ 62 4.1 INTRODUZIONE....................................................................................................... 62 4.2 IL SIMULATORE XLETVIEW ................................................................................... 63 4.3 SET TOP BOX DA SVILUPPO ADB X-75.................................................................. 66 4.3.1 Caratteristiche Tecniche................................................................................ 67 4.3.2 Pannello frontale ........................................................................................... 69 4.3.3 Pannello posteriore........................................................................................ 70 4.3.4 Telecomando .................................................................................................. 71 4.3.5 Settaggio connessione di default.................................................................... 72 4.3.6 STB Firmware upgrade.................................................................................. 73 4.3.7 UpLoad e debug di un’applicazione ............................................................. 74 4.3.8 Xlet description file ........................................................................................ 76 CAPITOLO 5 IMPLEMENTAZIONE DI UN SERVIZIO DI PRENOTAZIONI ......................... 78 5.1 LE PROBLEMATICHE ............................................................................................... 79 5.2 INTERFACCIA GRAFICA ........................................................................................... 80 5.3 ACCESSO AI DATI ................................................................................................... 84 5.3.1 Socket ............................................................................................................. 85 5.3.2 JDBC.............................................................................................................. 91 5.3.3 Database ........................................................................................................ 94 5.4 SVILUPPI FUTURI .................................................................................................... 95 CONCLUSIONI............................................................................................................ 96 APPENDICE A CODICE XLET............................................................................................................. 97 APPENDICE B CODICE PROXY ....................................................................................................... 124 SITI E BIBLIOGRAFIA............................................................................................ 131 II Glossario Glossario DTT: Digital Terrestrial Television DVB: Digital Video Broadcasting DTv: Digital Television EPG: Electronic Program Guide ETSI: European Telecommunications Standards Institute FTV: Free To View HDTV: High Definition Television Defined MHP: Multimedia Home Platform MPEG: Moving Picture Expert Group PAL: Phase Alternative Line QAM: Quadrature Amplitude Modulation SCART: Syndicat Francais des Constructeurs d’Appareils Radio et Television IP: Internet Protocol ISP: Internet Service Provider TCP: Transmission Control Protocol UDP: User Data Protocol HTTP: Hypertext Transfer Protocol TLS: Transport Layer Security LAN: Local Area Network 1 Introduzione Introduzione La digitalizzazione della televisione terrestre è forse l'ultimo anello importante che manca alla catena che in poco più di 10 anni ha trasformato in digitale i principali flussi informativi che viaggiano sul nostro pianeta. Da qualche tempo dopo aver tratto insegnamento dalle prime esperienze effettuate in UK, Spagna e Finlandia, anche l'Italia ha intrapreso il cammino che, nel corso dei prossimi due anni, porterà a trasformare le centinaia di stazioni televisive che popolano l'etere nazionale in altrettanti canali digitali. Con la televisione Digitale Terrestre (DTT: Digital Terrestrial Television) arriveranno non solo trasmissioni televisive di miglior qualità e in maggior quantità, ma anche nuove possibilità di servizi interattivi, che da un lato potranno rendere più ricca ed interessante l'esperienza del telespettatore, e dall'altro apriranno un canale di comunicazione bidirezionale, e quindi di natura del tutto nuova, che potrà raggiungere qualsiasi famiglia del nostro paese. Proprio la bidirezionalità della comunicazione è un fattore che suscita molte aspettative in diversi settori: dalle Pubbliche Amministrazioni, che vedono nel sistema un’opportunità per avvicinare i cittadini e rendere loro più facilmente disponibili tutti i propri servizi, ai vari privati che sfrutteranno al massimo un nuovo strumento sia pubblicitario che informativo. Tra i vari servizi che oggi sono già realizzati per internet spiccano sicuramente: ecommerce, e-health, e-banking e servizi di prenotazioni. In questo elaborato si cercare di spiegare innanzitutto le tecnologie sia hardware che software, che interessano il digitale terrestre, compresi i vari standard che entrano in gioco, continuando poi con la spiegazione di come realizzare un ambiente di sviluppo per applicazioni MHP e infine descrivendo l’implementazione di una piattaforma per prenotazioni “on-line” con particolare riguardo all’uso del canale di ritorno e l’accesso ai dati e le varie problematiche che ne conseguono. 2 Capitolo 1: Il Digitale Terrestre Capitolo 1 Il Digitale Terrestre 1.1 Introduzione La televisione digitale terrestre (DTT: Digital Terrestrial Television) è l'ultima tecnologia di comunicazione digitale che sta bussando alla porta delle case degli italiani, e certamente risulta essere piuttosto apprezzata, a giudicare dai numeri. Nel volgere di poco più di due anni e mezzo, dall'inizio del 2004, circa 2,5 milioni di decoder DTT (i cosiddetti Set-Top-Box) hanno trovato posto nei nostri salotti, aiutati indubbiamente anche dall’iniziale contributo per l'acquisto stanziato dal governo, di 70 euro per famiglia. La trasformazione della TV da analogica a digitale nel nostro Paese coinvolgerà progressivamente gli oltre 50 milioni di apparecchi televisivi presenti sul territorio. La fine delle trasmissioni TV terrestri in tecnica analogica (switch-off) è prevista normativamente entro il 31 dicembre 2008: in base alla legge n. 66/2001 a partire da tale data tutte le trasmissioni TV terrestri dovranno essere solo digitali. In questa fase quindi tutti i broadcaster, nazionali e regionali, stanno affrontando i problemi collegati alla transizione. 1.2 Novità tecnologiche La novità tecnologica della DTT risiede principalmente in due elementi: l'adozione di un sistema di trasmissione numerico COFDM, che presenta caratteristiche di robustezza rispetto alle interferenze, migliore qualità del segnale trasportato e, soprattutto, minor banda occupata nello spettro elettromagnetico, da cui l’attesa moltiplicazione dei canali disponibili; la contemporanea presenza di un canale di ritorno (CdR) che, collegando il ricevitore ad una rete di telecomunicazioni, fornisce al telespettatore uno strumento per interagire a ritroso con l’emittente televisiva o con il fornitore del servizio. La modulazione usata dalla DTT a parte l'aggiunta di specifici codici per la correzione dell'errore, è affine a quella usata per le connessioni ADSL. Essa lavora sul principio di 3 Capitolo 1: Il Digitale Terrestre utilizzare diverse frequenze portanti fra loro ortogonali, fino ad un massimo di 8k frequenze, e poi suddividere l'informazione in altrettanti flussi di bit indipendenti; ogni flusso di bit viene assegnato ad una diversa portante e poi alla ricezione i diversi flussi vengono opportunamente ricombinati. Tale modulazione, quando opportunamente utilizzata, presenta una caratteristica interessante: permette di sfruttare in modo costruttivo gli echi isofrequenziali che giungono all'antenna ricevente del televisore. Ciò porta almeno un paio di vantaggi rispetto al mondo analogico. Da un lato facilita una migliore ricezione all'interno delle aree urbane, dove il segnale elettromagnetico può facilmente arrivare all'antenna in modo multiplo, per via delle riflessioni che subisce incontrando i vari edifici (e che nel mondo della TV analogica produrrebbero immagini ghost), dall'altro abilita una più semplice costruzione dell'intera rete broadcast, permettendone una organizzazione in isole isofrequenziali, fino ad una dimensione massima che dipende dal rapporto fra tempo di trasmissione e tempo di silenzio ("di guardia" in termini tecnici) che è stato scelto da ogni broadcaster per i trasmettitori della propria rete. La ricezione del segnale televisivo può avvenire attraverso lo stesso impianto d’antenna utilizzato già oggi per la TV analogica, a patto che l'impianto stesso sia stato effettuato a regola d'arte, rispettando i requisiti tecnici di base, com’è normale se ci si è affidati a professionisti del settore per la sua realizzazione. Il canale di ritorno utilizzerà un collegamento fornito dalla rete di telecomunicazioni, e potrà operare sia con tecnologia tradizionale (RTG: Rete Telefonica Generale) sia con tecnologie a larga banda (ADSL) o wireless (GPRS/UMTS). Grazie al canale di ritorno ed alle funzioni che permettono interattività locale sarà possibile progettare e costruire una grande varietà di nuovi servizi, che vanno dal settore dell'intrattenimento a quello informativo, per arrivare a servizi di utilità forniti da soggetti pubblici (es. il Comune di residenza) o privati (es: la propria banca). Il segnale audiovisivo della DTT è codificato in digitale secondo lo standard MPEG-2, lo stesso adottato da oltre un decennio per la TV digitale satellitare. La modulazione di tale segnale sulla specifica frequenza portante, fatto secondo lo standard DVB-T, permette di trasportare un flusso numerico di circa 24 Mbit/s su ogni frequenza, consentendo così di trasmettere fino a 4 o 5 canali TV, con qualità audiovisiva paragonabile a quella di un DVD, occupando la stessa banda di frequenza attualmente 4 Capitolo 1: Il Digitale Terrestre utilizzata da un singolo canale analogico. In questa prima fase di diffusione del servizio, e nell'attesa che si diffondano sul mercato televisori già naturalmente predisposti per vedere le trasmissioni digitali, per ricevere il segnale digitale sarà necessario collegare il normale apparecchio televisivo che popola i salotti degli italiani con un ricevitore digitale, il cosiddetto Set-Top Box (STB), che realizza le funzioni di ricevitore/decoder per il segnale della TV digitale terrestre, ed utilizza la televisione per visualizzare le immagini e diffondere i suoni. Il collegamento fra Set-Top Box e televisore si può realizzare tramite la consueta presa SCART. Come già accennato sopra, in aggiunta al consueto contenuto audiovisivo, il segnale digitale può agevolmente trasportare, sempre all'interno dello stesso flusso digitale, altri tipi di dati, associati o meno al canale che si sta trasmettendo. Il Set-Top Box diventa così un vero e proprio ambiente di esecuzione per applicazioni, in grado di fornire i diversi servizi aggiuntivi, grazie al dialogo realizzato con telecomando e Canale di Ritorno. La componente interattiva della TV digitale è stata standardizzata a livello europeo attraverso la specifica DVB-MHP (Digital Video Broadcasting - Multimedia Home Platform), che definisce la piattaforma standard per la distribuzione ed esecuzione di applicazioni televisive su STB, nonché le specifiche per il loro sviluppo. Un STB MHP è un sistema basato su una Java Virtual Machine che integra le funzionalità di controllo dei flussi audio/video necessarie per operare nell'ambiente televisivo, mentre le applicazioni non sono altro che dei programmi scritti in Java che utilizzano le risorse hardware e software presenti nel STB. La combinazione di STB, piattaforma MHP e canale di ritorno, che sarà presente in prospettiva in tutte le case degli italiani, è il fattore abilitante che favorirà lo sviluppo di una molteplicità di servizi informativi, di pubblica utilità e commerciali. In questo contesto la DTT si va ad inserire come essenziale tecnologia abilitante nel processo di ammodernamento delle infrastrutture telematiche del paese, con l'obiettivo dichiarato di diventare uno degli elementi chiave nel processo di riduzione dell'analfabetismo digitale della popolazione italiana. E’ importante capire cosa possa essere e come si possa costruire il nuovo fenomeno dell'interattività televisiva, i ruoli dei diversi soggetti che opereranno nel ciclo di vita del prodotto digitale e come si struttureranno i rapporti tra i vari attori che si inseriscono in tale ciclo; attori che possono essere tradizionali, come le TV che conosciamo, oppure del tutto nuovi, come i fornitori di servizi interattivi, la loro natura, il loro ciclo di sviluppo, messa in onda ed esercizio. 5 Capitolo 1: Il Digitale Terrestre L'evoluzione prossima della DTT sarà quella dell'arrivo di STB con hard disk integrato e collegata capacità di registrazione digitale, il cosiddetto PVR (Personal Video Recording). Alcuni produttori hanno già pronto un tale oggetto, ma appaiono ancora cauti sull’opportunità di passare alla fase commerciale vera e propria. Passare dalla registrazione su cassetta a quella su hard-disk, oltre a costituire un vero grande salto nelle abitudini quotidiane dei telespettatori, introduce, infatti, alcuni effetti collaterali rispetto ai quali il mondo della televisione si sta ancora interrogando. Primo fra tutti l'insorgere di un’abitudine diffusa nel fruire della cosiddetta "time-shifted TV". Quanti di noi normalmente non riescono ad accendere la televisione nel momento giusto per vedere un film, un telegiornale, uno show dall'inizio della programmazione? Un videoregistratore digitale è l'oggetto ideale per registrare la trasmissione dal suo inizio, e renderla visibile in un qualunque momento, anche mentre è ancora in corso. Ed a quel punto posso anche "accelerare" la fruizione della parte già registrata, usando i comandi di "avanti veloce" così come avviene su un registratore VHS. Immaginate cosa potrebbe capitare agli spot pubblicitari? Negli USA, dove i PVR cominciano ad essere diffusi sulle TV via cavo e via satellite, i pubblicitari hanno dichiarato che quando tali dispositivi avranno conquistato la soglia di 30 milioni di case, gli investimenti pubblicitari fatti nelle TV si ridurranno di almeno il 20%. Conciliare evoluzione tecnologica e modelli di business è il prossimo importante passo che la TV digitale deve affrontare, e non solo in Italia. Fig. 1.2.1 – Digital Video Broadcasting Terrestre nel mondo 6 Capitolo 1: Il Digitale Terrestre 1.3 Lo standard MHP MHP è uno standard molto giovane, la prima release è stata creata dal DVB Project e standardizzata dall'ETSI Institute nell'anno 2000. Multimedia Home Platform definisce l'interfaccia tra le applicazioni interattive e i terminali sulle quali queste possono essere eseguite (Set-Top Box, digital TV e PC multimediali). L'architettura (come mostrato in figura 2) è definita dai seguenti 3 layers: Resources: MPEG processing, I/O devices,CPU, memoria e graphics system. System Software: JVM, APIs, transport protocols e soprattutto l'Application Manager (o navigatore) che permette di gestire il running delle applicazioni Java. Applications: le applicazioni interattive come ad esempio Electronic Program Guide, servizi informativi, giochi, TV-Commerce etc. Fig. 1.3.1 - Architettura dell'MHP Come già preannunciato il "core" di MHP si basa su Java e in particolare sull'adozione di una Personal JVM e di una serie di API, tra cui spiccano le Java TV di Sun. All'interno dello standard troviamo anche la definizione di 3 profili, utili ad evidenziare i servizi possibili con le relative tecnologie. Esiste inoltre un legame univoco tra i profili (Figura 1.3.2) e le release dello standard: 7 Capitolo 1: Il Digitale Terrestre Enhanced Broadcast Profile è definito nelle specifiche MHP 1.0, è il profilo base e permette solamente l'arricchimento del contenuto audio-video con informazioni e immagini visualizzabili e navigabili sullo schermo tv. Per questo profilo non sono richieste performance particolari dei set-top box. Interactive TV Profile è definito sempre nelle specifiche MHP 1.0 ed è il profilo intermedio che permette di utilizzare il canale di ritorno (sia di tipo modem, adsl, gprs, ethernet, etc) per fornire servizi con interattività superiore rispetto al profilo base. Questo profilo, infatti, supporta anche il caricamento di applicazioni MHP tramite il canale di ritorno e non solo dal canale di broadcast. Internet Access Profile è definito nelle specifiche MHP 1.1 e permette tramite il canale di ritorno di accedere ai contenuti di Internet. Questo profilo necessita di performance di alto livello essendo obbligatoria l'adozione di un browser internet e di un client di email embedded nel set-top box. Fig. 1.3.2 - Profili dell'MHP 8 Capitolo 1: Il Digitale Terrestre Alcuni esempi di applicazioni interattive possibili in MHP sono: Electronic Program Guide(EPG). Servizi di news come ad esempio un Telegiornale Interattivo. Un gioco tv. Giochi stand-alone. Servizi interattivi legati ad eventi sportivi. TV Commerce e TV Banking. Servizi Meteo, Traffico e di pubblica utilità. Pubblicità interattive. Fig. 1.3.3 - Esempi di applicazioni MHP 9 Capitolo 1: Il Digitale Terrestre 1.4 Broadcasting di applicazioni interattive Dopo aver analizzato lo standard, le tecnologie coinvolte e fornito qualche esempio di cosa è possibile realizzare, vediamo in che modo e con quali strumenti le applicazioni Java-MHP possono essere trasmesse e ricevute dai decoder interattivi. Iniziamo evidenziando uno dei già citati vantaggi della trasmissione in digitale, la maggior disponibilità di canali televisivi, analizzando il caso del digitale terrestre. Nel sistema di trasmissione analogico(PAL) il segnale occupa una banda di 8MHz, modulando il segnale in digitale, nella stessa banda è possibile avere un flusso dati di 24Mbit/sec. Considerando che un canale tv digitale grazie alla compressione A/V MPEG2 mediamente occupa 4Mbit/sec risulta evidente come sia possibile trasmettere un maggior numero di canali televisivi. Ovviamente se volessimo trasmettere anche applicazioni interattive dovremmo riservare parte della banda disponibile anche per questo tipo di dati; mediamente il peso di un applicazione comprensiva di grafica si aggira sui 500Kbyte. Lo schema generale di figura 1.4.1 ci mostra un esempio relativo all'emissione e alla ricezione del segnale digitale comprensivo delle applicazioni interattive. Nello standard MHP tali applicazioni sono chiamate Xlet e possono essere composte solo da tipologie precise di dati: classi java, file xml o txt, immagini png o jpg o gif, audio mp2 e video mpg. Le Xlet per essere broadcastate necessitano di un preventivo impacchettamento in moduli di dimensione massima di 64Kbyte. La sequenza di moduli che compone ogni applicazione viene tramessa in loop costantemente e di solito viene associata ad un particolare canale tv. Tale associazione, come evidenziato nello schema, avviene nel Multiplexer dove è possibile creare tali associazioni per ogni Xlet broadcastata. A questo punto il flusso in uscita dal Multiplexer (Trasport Stream) viene modulato in una delle 3 tipologie possibili definite da Digital Video Broadcasting: Terrestre (specifiche DVB-T), Satellite (specifiche DVB-S) e Cavo (specifiche DVB-C). Dopo la modulazione il segnale viene trasmesso e quindi ricevuto sul decoder interattivo (set-top box) che come vedremo offre la possibilità di connessioni a server dedicati o ad internet tramite modem o tecnologie più avanzate come l'ADSL. 10 Capitolo 1: Il Digitale Terrestre Fig. 1.4.1 - Emissione e ricezione del segnale digitale 1.5 Il decoder interattivo Il decoder interattivo(Set-Top Box) è l'apparecchio indispensabile per ricevere i canali digitali e utilizzare i software interattivi. Questo dispositivo permette innanzitutto la decodifica del segnale da digitale ad analogico per gli apparecchi televisivi analogici che attualmente nel nostro paese rappresentano la totalità del mercato. Infine consente di eseguire applicazioni Java grazie alla presenza di una Personal JVM embedded. Come mostra la Figura 1.5.1 un decoder è formato da una parte hardware(CPU, memoria, modem, etc), da un sistema operativo real-time e da un middleware MHP. Fig. 1.5.1 - Schema HW/SW di un Set-Top Box Un attore importante che troviamo all'interno del decoder è il cosiddetto navigatore(o application manager). Il suo compito è fondamentale, infatti esso si occupa di segnalare 11 Capitolo 1: Il Digitale Terrestre all'utente la lista delle applicazioni MHP disponibili sul canale televisivo. Inoltre se l'utente effettua una selezione da tale lista, il navigatore inizia la fase di download in memoria dei moduli relativi all'applicazione scelta. Importante sottolineare che non tutti i moduli che compongono l'applicazione verranno scaricati, infatti il download sarà relativo solo a quelli neccessari alla partenza della Xlet. Successivamente e solo se realmente necessari verranno scaricati anche quelli rimanenti. Tale meccanismo ricorda il modello di caricamento delle pagine del televideo ed è pensato allo scopo di ridurre i tempi di attesa del telespettatore. Un dispositivo tecnologico presente sul decoder che rende operativo il concetto di canale di ritorno definito nello standard è il modem nei decoder di fascia bassa e l'ADSL, Gprs, Ethernet, etc. sui decoder di fascia alta. Il canale di ritorno permette di interagire con servizi esterni, su server dedicati o internet e scaricare contenuti su richiesta del telespettatore. Nel caso di modem V90 i tempi di connessione e recupero dati sono abbastanza lunghi(30-40 sec solo per la chiamata telefonica e l'autenticazione presso un ISP), ma nel caso di connessioni "always on" come con ADSL allora gli scenari possibili sono molto più interessanti, come la possibilità di scaricare musica e video su richiesta dell'utente. 12 Capitolo 2: Applicazioni MHP Capitolo 2 Applicazioni MHP 2.1 Modello di Business e interfacce Un’applicazione mhp viene sviluppata seguendo un modello di business che si basa su più fattori: - MSO (Multiple Service Operator) i broadcaster, cioè coloro che forniscono la possibilità di scaricare le applicazioni e i contenuti per la iTV - MSV (Middleware Software Vendor) gli sviluppatori java/DVB-HTML - I fornitori dei contenuti ISP (Internet Service Provider) - Set-top-box Manufacturers - Users: gli utenti dotati di set-top-box, televisore e telecomando Date l’innumerevoli combinazioni che possono venire dall’incrocio dei diversi fattori elencati, non poche possono sembrare le difficoltà nel produrre un’applicazione MHP. Il trucco si trova nelle interfacce che lo standard MHP mette a disposizione sia a livello software (figura 2.1.1) che hardware (figura 2.1.2). MHP è basato su java, (platform indipendent), il set-top-box ha preinstallato una VM java corredata da API proprietarie e TV API di SUN, che definiscono le modalità di comunicazione tra l'utente, il set-top-box e il broadcaster. MHP ha definito anche un sotto linguaggio HTML (DVB-HTML per la precisione) con tag supplementari relativi alla cattura dell'input dell'utente (telecomanado). Il set-top-box ha preinstallato un'applicazione che si chiama Application Manager (di solito java) che gestisce il caricamento del software creando anche i menu dei canali forniti dal broadcaster. Oltre a ciò il set-top-box possiede anche un alloggiamento per smartcard (nonché un lettore per le stesse), che offre servizi sicuri come login utente, servizi a pagamento ecc.. 13 Capitolo 2: Applicazioni MHP Fig. 2.1.1 – Software layer Fig. 2.1.2 - Interfaccia tra mhp application e mhp system Per ogni tipo di interfaccia si avrà una suite di classi che permetterà di far dialogare l’applicazione con i dispositivi a disposizione: schermo, telecomando, smart card, canale di ritorno. 14 Capitolo 2: Applicazioni MHP 2.2 Xlet Una Xlet è una particolare applicazione Java concepita per essere scaricata ed eseguita su decoder interattivi nei quali un software specifico, l'Application Manager, è in grado di controllarne il ciclo di vita. Come vedremo le similitudini con le Applet, dove l'application manager è rappresentato dal browser sono molteplici. Per iniziare a costruire una Xlet occorre innanzitutto capire cosa viene fornito da Sun nelle API Java TV. Infatti è proprio in queste librerie che vengono definite le due interfacce fondamentali da utilizzare: • javax.tv.xlet.Xlet che definisce i seguenti metodi: public void initXlet(XletContext ctx); public void startXlet(); public void pauseXlet(); public void destroyXlet(boolean unconditional); • javax.tv.xlet.XletContext che definisce i seguenti metodi: public void notifyDestroyed(); public void notifyPaused(); public java.lang.Object getXletProperty(java.lang.String key); public void resumeRequest(); Entrambe le interfacce servono per interagire con l'application manager in merito al ciclo di vita e al contesto in cui la Xlet viene eseguita. La classe principale di ogni Xlet deve sempre implementare javax.tv.xlet.Xlet per poter essere eseguita sui decoder interattivi MHP. Come si mostra in Figura 2.2.1 il ciclo di vita di una Xlet è caratterizzato da 4 stati: Loaded: in questo stato la Xlet è stata creata ma non inizializzata, se durante questa fase viene rilevata un eccezione allora si passa direttamente nello stato Destroyed. Da notare che la Xlet si può trovare in questo stato solo una volta durante tutto il suo ciclo di vita. Paused: la Xlet è inizializzata e può trovarsi in questo stato sia dopo che il metodo initXlet(XletContext) è ritornato con successo dallo stato Loaded oppure dopo che il metodo pauseXlet() è ritornato con successo dallo stato Active. In entrambi i casi in questo stato la Xlet dovrebbe limitare al massimo l'utilizzo delle risorse condivise e soprattutto non impegnare la GUI televisiva. Active: in questo stato la Xlet è attiva e utilizza le risorse necessarie per fornire i suoi servizi, se dotata di GUI allora dovrebbe essere l'unica registrata per ricevere gli eventi 15 Capitolo 2: Applicazioni MHP del telecomando. Destroyed: in questo stato la Xlet deve rilasciare tutte le risorse in uso per predisporsi alla terminazione. Fig. 2.2.1 - Ciclo di vita di una Xlet Una sequenza tipica di questo ciclo potrebbe essere: L' application manager crea una nuova istanza di una Xlet chiamando il suo costruttore (stato LOADED). La Xlet usa il context passatogli dall'application manager nel metodo initXlet(XletContext) e si inizializza (stato PAUSED). L'application manager chiama il metodo startXlet() e la Xlet inizia ad utilizzare le risorse necessarie per fornire i servizi per cui è stata costruita(stato ACTIVE). L'application manager chiama il metodo destroyXlet(true) e la Xlet rilascia le risorse, dopodiché viene terminata (stato DESTROYED). Il primo esempio: HelloXlet.java import javax.tv.xlet.Xlet; import javax.tv.xlet.XletContext; import javax.tv.xlet.XletStateChangeException; public class HelloXlet implements Xlet{ // L'XletContext è un oggetto usato dalla Xlet per accedere // alle properties del suo environment e per comunicare con // l'application manager che glielo ha passato nella fase // di inizializzazione. private XletContext context; private boolean alreadyActive = false; 16 Capitolo 2: Applicazioni MHP // Il costruttore tipicamente viene lasciato vuoto visto che tutte // le inizializzazioni dovrebbero stare nel metodo initXlet() public HelloXlet(){} // In questo metodo vengono fatte tutte le inizializzazioni, // se in questa fase qualcosa fallisse verrebbe lanciata un // eccezione. public void initXlet(XletContext context) throws XletStateChangeException{ // si memorizza il context passato dall'Application Manager this.context = context; System.out.println(this.getClass().getName()+" : Inizializzazione avvenuta!"); } // Con questo metodo la Xlet è avviata e vengono richieste // le risorse necessarie come ad esempio lo schermo TV o // gli eventi del telecomando public void startXlet() throws XletStateChangeException{ // controlliamo se la Xlet era già stata messa in stato Active // precedentemente if (alreadyActive){ System.out.println(this.getClass().getName()+" : Hello Again TV World! "); }else { System.out.println(this.getClass().getName()+" : Hello TV World"); alreadyActive = true; } } // Con questo metodo la Xlet viene messa in pausa e dovrebbe // rilasciare tutte le risorse utilizzate public void pauseXlet(){ System.out.println(this.getClass().getName()+" : Xlet in pausa!"); // notifichiamo al Context l'avvenuta pausa della Xlet context.notifyPaused(); } // Con questo metodo la Xlet viene terminata, tramite il // parametro boolean la Xlet ha la facoltà di accettare // tale richiesta; nel caso non accettasse di // terminare dovrebbe lanciare un eccezione. public void destroyXlet(boolean unconditional) throws XletStateChangeException { if (unconditional) { System.out.println(this.getClass().getName()+" : la Xlet è stata terminata!"); }else { System.out.println(this.getClass().getName()+ " : la Xlet ha accettato di essere terminata!"); } // notifichiamo al Context l'avvenuta terminazione della Xlet context.notifyDestroyed(); } } 17 Capitolo 2: Applicazioni MHP 2.3 Gestione delle risorse scarse I Set Top Box sono sistemi che presentano risorse limitate a livello hardware come poca memoria e processori “lenti”, rispetto agli attuali PC in commercio, quindi per poter utilizzare le varie periferiche: input (telecomando), output (decoder Mpeg2 per visualizzare le immagini su video) e modem si devono usare delle accortezze, le precedenti periferiche vengono chiamate “risorse scarse”. Se le risorse non vengono gestite in maniera ottimale potrebbe succedere che un'applicazione non sia in grado di funzionare correttamente o non sia in grado di sfruttare tutte le potenzialità del STB. Le API che andremo a considerare sono le resource notification API che sono state definite da DAVIC nel package org.davic.resource . Una cosa da tenere in considerazione è che queste non sono API di gestione delle risorse, in quanto questa viene lasciata al middleware del ricevitore ma ben si di notifica. Servono quindi a notificare all'applicazione se una risorsa è stata tolta, oppure se è richiesta da un'altra applicazione concorrente. Le resource notification API sono sostanzialmente composte da tre classi: • ResourceServer • ResourceClient • ResourceProxy Tali classi non hanno funzionalità autonoma ma forniscono dei concetti comuni ai quali le API che definiremo di “ utilizzo” dovranno attenersi per svolgere i loro compiti. Questo significa che essendo queste delle “interface” saranno le classi che le implementano a definirne nello specifico i loro compiti. Queste API si basano sul modello client-server con piccole variazioni. L'interfaccia ResurceServer è implementata da quelle API di utilizzo responsabili di effettuare la richiesta di risorse scarse, e di gestirne la loro allocazione. Tali risorse scarse potrebbero essere di tipo software, ad esempio il section filter, oppure di tipo hardware come per esempio un modem oppure un decoder MPEG, le API non fanno distinzione e le trattano tutte in maniera simile. Il codice sorgente dell'interfaccia ResurceServer è il seguente: 18 Capitolo 2: Applicazioni MHP public interface ResourceServer { public abstract void addResourceStatusEventListener( ResourceStatusListener listener); public void removeResourceStatusEventListener( ResourceStatusListener listener); } Come si può vedere questa interfaccia non definisce dei metodi specifici per attuare una richiesta di accesso esclusivo ad una risorsa scarsa oppure per rilasciarla, questa è una scelta voluta perché sarà compito delle API di utilizzo fare questo nella maniera più consona. Gli unici metodi che questa interfaccia definisce permettono di registrare un ascoltatore degli eventi che riporterà i cambiamenti di stato della nostra risorsa. Il modo con cui noi riserviamo una risorsa come il modem è infatti completamente differente da quello con cui gestiamo il un decodificatore MPEG per esempio. Vedremo in seguito come la classe ConnectionRCInterface attraverso il metodo reserve() andrà a richiedere l'accesso esclusivo sul modem. L'interfaccia ResurceClient è come si può capire dal nome il lato client delle API. Questa interfaccia viene implementata da quelle classi che richiedono l'accesso alla risorsa scarsa, e viene utilizzata dal middleware per richiedere il rilascio della risorsa oppure per informarla che il rilascio è già avvenuto. Il codice sorgente è il seguente: public interface ResourceClient { public abstract boolean requestRelease( ResourceProxy proxy, Object requestData); public abstract void release( ResourceProxy proxy); public abstract void notifyRelease( ResourceProxy proxy); } 19 Capitolo 2: Applicazioni MHP Come possiamo vedere questa interfaccia mette a disposizione del middleware tre metodi per comunicare all'applicazione di rilasciare la risorsa. Il metodo requestRelease() è il meno incisivo dei tre perché lascia al client di scegliere se soddisfare la richiesta oppure rifiutare. Nel caso in cui il client abbia ancora bisogno di trattenere la risorsa ritornerà false e il middleware dovrà se necessario attuare un sistema di rilascio forzato. Altrimenti nel caso in cui la risorsa non è più necessaria per il client esso riporterà true ed il middleware potrà procedere con il rilascio. Il metodo release() è il più aggressivo perché obbliga al nostro client di rilasciare la risorsa senza scelta. Quando il metodo ritorna, il middleware sarà certo di poter fornire la risorsa ad un altro client che ne fa richiesta. Se il metodo release() non ritorna in tempo accettabile il middleware può ritenere che l'applicazione sia andata in crash e metterà comunque a disposizione la risorsa rimuovendone i diritti di utilizzo. Se succede questo il middleware chiamerà il metodo notifyRelease() dell'interfaccia ResourceClient, andando a informare l'applicazione che è stata privata della risorsa scarsa e che dovrà quindi agire terminando i processi che la impiegavano. Questa è una operazione brutale per il client ma viene utilizzata quando questo non collabora con il middleware. L’interfaccia ResourceProxy mette a disposizione solo un metodo di notifica utilizzato per restituire il client della risorsa a cui esso fa riferimento ed è composta come di seguito: public interface ResourceProxy { public ResourceClient getClient(); } La classe che implementa ResourceProxy si pone nel mezzo tra il client e la risorsa da utilizzare questo porta ad un grande livello di sicurezza perché l’applicazione ottiene un accesso mediato alla risorsa e ciò per permettere al middleware di controllarla. Le classi che implementano il proxy mettono a disposizione dei metodi che possono essere utilizzati per fare il set-up delle risorse. In questo modo quando si attuano più cicli di request/release delle risorse non si dovrà tutte le volte settare i parametri ma 20 Capitolo 2: Applicazioni MHP questi verranno forniti dal proxy precedentemente impostato. Con una risorsa come il modem, dove ogni volta, per effettuare una connessione, bisogna fornire numero telefonico, username e password questo comporta un elevato risparmio di tempo. Un altro beneficio portato è che le classi che implementano il client possono allacciarsi a più proxy differenti, ognuno dei quali capace di fornire un setting differente per i parametri della risorsa, sarà poi l’applicazione a decidere quali proxy utilizzare nel momento in cui ne fa richiesta. Vediamo di capire meglio cosa succede quando un’applicazione richiede l’accesso a una risorsa seguendo i passi di seguito illustrati: 1. Innanzi tutto l’applicazione, o meglio le classi che implementano il client creano un’istanza del ResourceProxy appropriato e settano tutti i parametri del caso in base alla risorsa che andranno a utilizzare: 2. Il client va a chiamare un metodo appropriato sulle API che implementano il ResourceServer e richiedono accesso alla risorsa passando il ResourceProxy come parametro: 3. Se l’accesso alla risorsa è garantito il server chiama un metodo privato sul proxy per convalidarlo: 21 Capitolo 2: Applicazioni MHP 4. Si stabilisce un collegamento diretto tra il proxy e la risorsa, collegamento che fino a questo momento non esisteva per ragioni di sicurezza: 5. A questo punto la nostra applicazione può cominciare ad utilizzare la risorsa utilizzando i metodi del proxy,sarà poi questo a riportare le richieste alla risorsa: Il processo di rilascio delle risorse è sostanzialmente l’inverso di quello precedentemente illustrato: 1. Quando il client ha terminato di utilizzare una risorsa chiama un metodo appropriato sul server per rilasciare la risorsa passandogli il proxy come parametro: 22 Capitolo 2: Applicazioni MHP 2. Ora il server chiamerà un metodo appropriato sul proxy per invitarlo a rilasciare la risorsa a cui fa riferimento invalidandolo: 3. Nel caso in cui l’applicazione è cooperante il proxy procede con il rilascio della risorsa: Vediamo cosa succede quando un’applicazione “maliziosa” non vuole rilasciare la risorsa, ed interviene il middleware: 1. il server chiama il metodo release() sul client; 2. il client fallisce nel ritorno da questa chiamata e continua a trattenere la risorsa; 3. dopo un certo periodo di tempo il middleware notifica al server che la richiesta non ha avuto un responso; 23 Capitolo 2: Applicazioni MHP 4. il server chiama un metodo privato sul proxy per informarlo che non e più valido e che non può più trattenere la risorsa; 5. il proxy rompe il collegamento con la risorsa; 6. il middleware chiama allora il metodo notifyRelease() su quella classe che implementa l’interfaccia ResourceClient. In questo modo l’applicazione sa che non ha più accesso alla risorsa e che si dovrà comportare di conseguenza; 7. tutti i futuri tentativi di accesso alla risorsa da parte dell’applicazione non avranno alcun effetto. L’aver descritto in maniera cosi approfondita, come avviene la gestione delle risorse e quali sono le API Java che ci permettono di fornire supporto per la notifica di gestione da parte del middleware, ci da la possibilità in futuro di introdurre i package che contengono le classi da utilizzare per stabilire una connessione, impostarne i parametri, settare un background, spostare e/o ridimensionare il video che come vedremo implementano le classi del package org.davic.resource. 24 Capitolo 2: Applicazioni MHP 2.4 Modello grafico Il modello grafico definito dallo standard MHP è basato su tre differenti piani (o layers) come mostrato in Figura 2.4.1. Fig. 2.4.1 - Layer grafici dell'MHP Partendo da dietro, rispetto a chi guarda la TV, troviamo il Background layer che può contenere un solid color o una still image (rappresentata da un particolare frame MPEG2, quello di tipo I), avanti troviamo il Video layer rappresentato dal flusso A/V del canale TV o di una qualsiasi altra fonte in formato MPEG-2. L’ultimo livello è il Graphics layer che può contenere la grafica creata nella Xlet, che a sua volta può essere strutturata su n-livelli sovrapposti. Tutti e tre i livelli lavorano ad una risoluzione standard di 720x576 o 768x576 se viene impostato il modo di visualizzazione su schermo 4:3. Per chi è abituato a fare applicazioni grafiche su PC non ci sono molte differenze, ma è il caso di tenere in considerazione alcuni aspetti: • i pixel non sono perfettamente quadrati; • il file che costituisce il background deve essere un’immagine video in formato mpeg2 (.mpg) in loop e non un immagine statica (.jpg), dato che il STB usa per la sua visualizzazione lo stesso decoder del video layer. (Programma di conversione consigliato .jpg -> .mpg “ImageMagic”); 25 Capitolo 2: Applicazioni MHP • le linee spesse 1 pixel non vengono visualizzate perfettamente o addirittura non sono visibili (consigliato spessore >=2); • le coordinate assolute (x,y) partono da 0,0 angolo superiore sinistro; • possono essere usate coordinate indipendenti dalla risoluzione chiamate “normalizzate” e si esprimo con numeri decimali da 0.0 a 1.0 supportate nelle API Havi; • nella gestione del livello grafico è assicurata la visualizzazione completa in un area ridotta rispetto alle coordinate totali, detta SAFE AREA di ampiezza 562x460. 0,0 79,58 Safe Area 641,518 720,576 Dopo aver visto le accortezze da usare andiamo ad analizzare in dettaglio, con porzione di codice commentato, i tre livelli di grafica. 2.4.1 Background layer Per settare uno sfondo sulla nostra applicazione bisognerà innanzitutto tener conto che la periferica che andremo ad utilizzare, il decoder mpeg2 è una risorsa scarsa, quindi per effettuare qualsiasi tipo di settaggio bisognerà prima acquisirla usando le resource notification API introdotte nei paragrafi precedenti. Il package più importante da importare per la gestione di questo layer è org.havi.ui.* , riportiamo la classe più rilevante “HScreen”: 26 Capitolo 2: Applicazioni MHP Method Summary HBackgroundConfiguration getBestConfiguration(HBackgroundConfigTempla te[] hbcta) Returns an HBackgroundConfiguration from an HBackgroundDevice which is present on this HScreen that best matches at least one of the specified HBackgroundConfigTemplates, or null if this is not possible. HGraphicsConfiguration getBestConfiguration(HGraphicsConfigTemplate [] hgcta) Returns an HGraphicsConfiguration from an HGraphicsDevice which is present on this HScreen that best matches at least one of the specified HGraphicsConfigTemplates. HVideoConfiguration getBestConfiguration(HVideoConfigTemplate[]) Returns an HVideoConfiguration from an HVideoDevice which is present on this HScreen that best matches at least one of the specified HVideoConfigTemplates. HScreenConfiguration[] getCoherentScreenConfigurations(HScreenConfi gTemplate[] hscta) Return a coherent set of HScreenConfigurations matching a set of templates. HBackgroundDevice getDefaultHBackgroundDevice() Return the default background device for this screen. HGraphicsDevice getDefaultHGraphicsDevice() Return the default graphics device for this screen. static HScreen getDefaultHScreen() Returns the default HScreen for this application. HVideoDevice getDefaultHVideoDevice() Return the default video device for this screen. HBackgroundDevice[] getHBackgroundDevices() Returns a list of background devices for this screen. HGraphicsDevice[] getHGraphicsDevices() Returns a list of graphics devices for this screen. static HScreen[] getHScreens() Returns all HScreens in this system. HVideoDevice[] getHVideoDevices() Returns a list of video device for this screen. 27 Capitolo 2: Applicazioni MHP Vediamo in breve le operazioni da eseguire: per prima cosa si sceglie lo schermo da usare mediante l’oggetto HScreen, a questo oggetto bisogna associare una HGraphicsDevice che conterrà il set d’impostazioni assegnate per mezzo di HGraphicsConfigTemplate. Il set di ogni singola impostazione è eseguito dal metodo setPreference al quale viene passato il parametro e la priorità; sarà poi il midlleware a decidere se il settaggio richiesto è attuabile in base alla priorità data e alle esigenze delle altre eventuali applicazioni che sono in running. Conviene creare una classe che implementa obbligatoriamente org.davic.resources.ResourceClient dove inserire tutti i metodi per l’acquisizione, il settaggio e il rilascio della risorsa. Di seguito viene riportata la classe “BackgroundController”, creata da Steve Morris, che mostra in dettaglio oltre alle fasi sopra elencate la gestione delle eccezioni . /********************************************************************************** * THIS SOFTWARE IS COPYRIGHT STEVEN MORRIS 2002. ALL RIGHTS RESERVED * THIS SOFTWARE IS FREE FOR NON COMMERCIAL USE FOR THE PURPOSE OF LEARNING *MHP. ANY USE FOR OTHER PURPOSES IS PROHIBITED UNLESS WRITTEN AGREEMENT * IS OBTAINED. * DISTRIBUTION OF THIS CODE IS PROHIBITED */ // Import the HAVi UI classes that we need for this import org.havi.ui.HScreen; import org.havi.ui.HBackgroundDevice; import org.havi.ui.HBackgroundConfiguration; import org.havi.ui.HBackgroundImage; import org.havi.ui.HStillImageBackgroundConfiguration; import org.havi.ui.HConfigurationException; import org.havi.ui.HPermissionDeniedException; import org.havi.ui.HBackgroundConfigTemplate; // Since some of the graphics configuration requires scarce resources, we // need to use the DAVIC resource notification API import org.davic.resources.*; // We need these (under some circumstances) to let our background be seen import javax.tv.service.selection.*; import javax.tv.media.AWTVideoSizeControl; import javax.tv.media.AWTVideoSize; import java.awt.Rectangle; import javax.tv.xlet.XletContext; 28 Capitolo 2: Applicazioni MHP /** * This class handles the display of background images in an easy-to * use way. Setting this up can be quite complex, and so in the case * we've relegated it to a separate class to make it a little easier to * understand. */ class BackgroundController implements org.davic.resources.ResourceClient{ // The background device that we will use for displaying the image private HBackgroundDevice backdevice; // The configuration for that background device private HStillImageBackgroundConfiguration backconfig; /** * This method will initialise the video and graphics devices to allow * them to display the background image. */ public boolean init(){ // We first need to get the background device that we will use for displaying the image. To do //this, we need to first decide which // HScreen we will use. In this case (and most others), we will use // the default one. HScreen screen = HScreen.getDefaultHScreen(); // Once we have that, we get the default background device for that HScreen HBackgroundDevice backdevice = screen.getDefaultHBackgroundDevice(); // Once we have a reference to the device, we can get a configuration for it. // Get a configuration that we can use HBackgroundConfigTemplate backgroundConfigurationTemplate = new HBackgroundConfigTemplate(); backgroundConfigurationTemplate.setPreference(HBackgroundConfigTemplate.FLICKER_FILTERING ,HBackgroundConfigTemplate.REQUIRED); HBackgroundConfiguration backconfig; backconfig = backdevice.getBestConfiguration(backgroundConfigurationTemplate); // Can we reserve the device so that we can change the settings on it? if (backdevice.reserveDevice(this)){ // If we can, we set the configuration of the background device to // the one we got above - this is the default configuration for this device. try{ backdevice.setBackgroundConfiguration(backconfig); }catch (Exception ex){ System.out.println("Can't initialise the background device"); // Release the device so that other applications can use it, if necessary. backdevice.releaseDevice(); return false; } // We need to check if we can put an image in the background in // this configuration, since we can't do this in every configuration. if(backconfig instanceof HStillImageBackgroundConfiguration){ // We can use this this.backconfig = (HStillImageBackgroundConfiguration)backconfig; this.backdevice = backdevice; return true; }else{ // If we can't, we again release the device since it's no use to us. backdevice.releaseDevice(); } } return false; } 29 Capitolo 2: Applicazioni MHP /** * Free the resources we needed to display background images. * Some implementations leave the image there, but there is an explicit * warning in the MHP specification that this may not happen on all * implementations. If you want to be sure that your image is still * visible, don't do this. */ public void dispose(){ // Check if we have something to dispose of if (backdevice != null){ // RZlease the device and clear any references backdevice.releaseDevice(); backdevice = null; backconfig = null; } } /** * Display a background image */ public void display(String filename){ // Check we have the resources we need to display a background image if(backconfig != null) { // Create a new background image. The image is loaded from the // filename that we pass in. HBackgroundImage backimage = new HBackgroundImage(filename); // Now display the image. This can throw several exceptions, so we // enclose it in a 'try' block try { backconfig.displayImage(backimage); } catch (java.io.IOException ioe) { // Ignore it, but print a message to tell the user what's happened. System.out.println("Can't display background image - IO exception"); ioe.printStackTrace(); } catch (HPermissionDeniedException hpde) { // We don't have permission to displayan image. We just ignore it. System.out.println("Can't display background image - permission denied"); } catch (HConfigurationException hce) { // We don't have permission to displayan image. We just ignore it. System.out.println("Can't display background image - configuration exception"); hce.printStackTrace(); } } } 30 Capitolo 2: Applicazioni MHP Implementazione metodi di org.davic.resource: /************************************************************************** * * These methods are inherited from the ResourceClient interface and are used * to tell the application when it has lost access to its resources (or * when it is about to lose access to them). This gives the application a * chance to clean up when it loses access to a resource, and gives it a * chance to handle things gracefully. */ /** * This method gets called when the resource manager requests that we give * up a resource. We can refuse to do so, and that's what we do in this * case (even though we shouldn't). */ public boolean requestRelease(ResourceProxy proxy, Object requestData) { return false; } /** * This method gets called when the resource manager informs us that we must * release a resource */ public void release(ResourceProxy proxy) { // release control of the background device backdevice.releaseDevice(); } /** * This method gets called when the resource manager tells us we've * lost access to a resource and we should clean up after ourselves. */ public void notifyRelease(ResourceProxy proxy) { // release control of the background device. Even though we don't // have control of it, this makes sure everything is synchronized. backdevice.releaseDevice(); } L’ultima cosa da aggiungere per quanto riguarda la gestione di questo livello è che, per rendere visibile il background, bisognerà ridimensionare o nascondere del tutto il livello superiore, ovvero il video layer. Trascurando la gestione del primo livello si possono implementare interfacce usando il livello grafico e come sfondo avranno la trasmissione corrente del canale dove si è sintonizzati. 31 Capitolo 2: Applicazioni MHP 2.4.2 Video layer La gestione del video layer è molto importante non solo per l’aspetto estetico della nostra interfaccia grafica. Non dobbiamo dimenticare, che il broadcaster ha si interessi a fornire l’applicazione MHP, ma si deve preoccupare anche della trasmissione che sta andando in onda. Mentre in una fase iniziale, nelle prime applicazioni, si preferiva ridimensionare il video e spostarlo in modo da non sovrapporsi a menù, pulsanti etc. ora la tendenza e di lasciarlo invariato il più possibile, usando trasparenze o sovrapposizioni di grafica temporanee, in modo da distogliere il meno possibile l’attenzione del telespettatore dalla trasmissione. Comunque ci sono casi in cui l’applicazione può pretendere e merita tutta l’attenzione dell’utente, basta pensare all’inserimento dati o alla conferma di un movimento in una applicazione e-banking per esempio. Quindi è bene alternare fasi, come la navigazioni di menù, dove il video sia in primo piano e altre in cui venga eliminato o ridotto. Per realizzare tutto ciò ci vengono in aiuto le classi javaTV della Sun, ecco un esempio di due metodi per nascondere o ridimensionare e posizionare il video tratti dalla classe “BackgroundController”, creata da Steve Morris. import javax.tv.service.selection.*; import javax.tv.media.AWTVideoSizeControl; import javax.tv.media.AWTVideoSize; import java.awt.Rectangle; import javax.tv.xlet.XletContext; /** * Hide the video that may be obscuring our background */ public void hideVideo(XletContext context) { // The background may be hidden by the video when the Xlet starts up. To // solve this, we have to do two things: // 1) stop the video // 2) resize the video so that we can see the app underneath it. This is // not always necessary, but some emulators (xletview, I'm looking at you) // don't hide the video when you stop it // Get a reference to the JavaTV ServiceContextFactory ServiceContextFactory factory; factory = ServiceContextFactory.getInstance(); 32 Capitolo 2: Applicazioni MHP // From this, we can get a reference to the parent // service context of our Xlet. To do this, we need a // reference to our Xlet context. It's times like this // that show why your application should always keep a // reference to its Xlet context ServiceContext myContext = null; try { myContext = factory.getServiceContext(context); } catch (Exception e) { e.printStackTrace(); } if (myContext != null) { // ServiceContentHandler objects are responsible for // presenting the different parts of the service. This // includes the media components ServiceContentHandler[] handlers; handlers = myContext.getServiceContentHandlers(); for(int i=0; i < handlers.length ; i++) { if (handlers[i] instanceof ServiceMediaHandler) { // This is a Player for part of the service, since // ServiceMediaHandler objects are instances of JMF // Player objects javax.media.Player p = (javax.media.Player) handlers[i]; // this may be all we need to do in order to see our background... p.stop(); p.deallocate(); // ...but some emulator require more AWTVideoSizeControl awtVideoSizeControl; awtVideoSizeControl = (AWTVideoSizeControl) p.getControl("javax.tv.media.AWTVideoSizeControl"); // in this case, we set the size of the video to be 0, but we could set it // to display on part of the screen awtVideoSizeControl.setSize(new AWTVideoSize(new Rectangle(0, 0, 720, 576), new Rectangle(0,0,0,0))); } } }else { System.out.println("Can't get our service context. May not be able to " + "resize the video, so our background may not be visible"); } } public void resizeVideo(XletContext context,int x,int y,int l,int h) { // The background may be hidden by the video when the Xlet starts up. To // solve this, we have to do two things: // 1) stop the video // 2) resize the video so that we can see the app underneath it. This is // not always necessary, but some emulators (xletview, I'm looking at you) // don't hide the video when you stop it // Get a reference to the JavaTV ServiceContextFactory ServiceContextFactory factory; factory = ServiceContextFactory.getInstance(); 33 Capitolo 2: Applicazioni MHP // From this, we can get a reference to the parent // service context of our Xlet. To do this, we need a // reference to our Xlet context. It's times like this // that show why your application should always keep a // reference to its Xlet context ServiceContext myContext = null; try { myContext = factory.getServiceContext(context); } catch (Exception e) { e.printStackTrace(); } if (myContext != null) { // ServiceContentHandler objects are responsible for // presenting the different parts of the service. This // includes the media components ServiceContentHandler[] handlers; handlers = myContext.getServiceContentHandlers(); for(int i=0; i < handlers.length ; i++) { if (handlers[i] instanceof ServiceMediaHandler) { // This is a Player for part of the service, since // ServiceMediaHandler objects are instances of JMF // Player objects javax.media.Player p = (javax.media.Player) handlers[i]; // this may be all we need to do in order to see our background... // ...but some emulator require more AWTVideoSizeControl awtVideoSizeControl; awtVideoSizeControl = (AWTVideoSizeControl) p.getControl("javax.tv.media.AWTVideoSizeControl"); // in this case, we set the size of the video to be 0, but we could set it // to display on part of the screen awtVideoSizeControl.setSize(new AWTVideoSize(new Rectangle(0, 0, 720, 576), new Rectangle(x,y,l,h))); } } }else { System.out.println("Can't get our service context. May not be able to " + "resize the video, so our background may not be visible"); } } Prima di ridimensionare o eliminare addirittura il video layer è bene aver impostato una immagine nel background layer come descritto nel paragrafo precedente, onde intercorrere a videate nere o addirittura alla non visualizzazione del livello grafico che ora andremo a trattare. 34 Capitolo 2: Applicazioni MHP 2.4.3 Graphics layer Il livello grafico è supportato da diversi package messi a disposizione dallo standard; uno dei fondamentali è Havi 1.1. In tale package il container di più alto livello in una Xlet è rappresentato dalla classe org.havi.ui.HScene, che concettualmente è equivalente a java.awt.Window o java.awt.Frame ma con caratteristiche specifiche per i decoder digitali, ne riportiamo i metodi principali: Method Summary void dispose() Disposes of this HScene. java.awt.Image getBackgroundImage() Retrieve any image used as a background for this HScene. int getBackgroundMode() Get the background mode of this HScene. boolean isOpaque() Returns true if the entire HScene area, as given by the java.awt.Component#getBounds method, is fully opaque, i.e. boolean isVisible() Determines if the HScene (or more properly its added child components) is Visible. void paint(java.awt.Graphics g) HScene objects override the paint method (defined in java.awt.Component) to paint the added "child" components on top of an optional background color or image. void setActive(boolean focus) Set whether the HScene is prepared to accept focus. void setBackgroundImage(java.awt.Image image) Set an image which shall be painted in the background of the HScene, after the background has been drawn according to the current mode set with setBackgroundMode, but before any children are drawn. void setBackgroundMode(int mode) Set the background mode of this HScene. void setVisible(boolean visible) Shows or hides this HScene depending on the value of the input parameter visible. void show() Shows this HScene, and brings it to the front. 35 Capitolo 2: Applicazioni MHP La classe factory che ci permette di richiedere l'unica istanza della HScene è fornita dallo stesso package e si chiama org.havi.ui.HSceneFactory. MHP supporta anche le classi AWT contenute nella versione 1.1.8 del JDK Sun ma spesso è possibile trovare un equivalente nel package Havi e che quindi dovrebbe essere preferita (es: org.havi.ui.HContainer è più specifica per MHP di java.awt.Container). Oltre ad Havi e AWT è possibile utilizzare anche un package specifico DVB org.dvb.ui dove è possibile trovare classi di utilità come la DvbColor che permette di gestire anche le trasparenze tramite il parametro alpha non presente nella classe java.awt.Color. Data l’importanza vediamone un tipo di costruttore: DVBColor (int r, int g, int b, int a) dove r,g,b sono i colori primari red, green, blue e “a”- alpha è la trasparenza, tutti e quattro i parametri hanno un range da 0 a 255. Non tutti i tipi di decoder MHP gestiscono le trasparenze in questo modo, ma lo standard garantisce almeno 3 livelli: 0 % (opaco), 100 % (completamente trasparente), 30 % di trasparenza. Il font di sistema supportato in MHP è il “Tiresias”, che deve essere disponibile almeno nelle seguenti dimensioni: • 36 punti (Title) • 31 punti (Subtitle) • 26 punti (Body) • 24 punti (Footnote) I formati grafici supportati sono: png, jpg, gif e mpg (I-Frame). Vediamo un esempio di codice che recupera l'istanza di HScene e ci posiziona un HContainer e un HText. HSceneFactory hsf = HSceneFactory.getInstance(); HScene scene = hsf.getFullScreenScene( HScreen.getDefaultHScreen().getDefaultHGraphicsDevice());// risoluzione a schermo pieno scene.setSize(720,576); scene.setLayout(null); HContainer cont = new HContainer(50,50,650,500); HText text = new HText("Casella di testo!",160, 200, 300, 60, new Font("Tiresias", Font.PLAIN, 26),Color.black,Color.white, new HDefaultTextLayoutManager()); cont.add(text); scene.add(cont); scene.setVisible(true); scene.repaint(); 36 Capitolo 2: Applicazioni MHP Un altro oggetto molto utile nella creazione di un’interfaccia è HIcon che permette di caricare un immagine e visualizzarla, per esempio per creare un bottone, in dettaglio: HIcon (java.awt.Image image, int x, int y, int width, int height) utilizzando java.awt.Toolkit ecco il nostro bottone: bottone = new HIcon(Toolkit.getDefaultToolkit().getImage("bottone.jpg"), 50, 100, 140, 30); Per dare facilmente l’effetto che il pulsante venga premuto si possono creare due oggetti HIcon con le rispettive immagini, pulsante rilasciato/pulsante premuto, posizionati sulle stesse coordinate e agire con il metodo setVisible(true/false) per nascondere l’uno e visualizzare l’altro. 37 Capitolo 2: Applicazioni MHP 2.5 Gestione degli eventi del telecomando La gestione del telecomando è banale per chi conosce Java, si basa sugli ascoltatore di eventi; in MHP si possono usare i package org.havi.ui.event che necessita del focus di un’ oggetto grafico oppure org.dvb.event. Iniziamo dal primo caso, nel package sono definite le costanti che corrispondono al codice restituito dal metodo getKeyCode di java.awt.event.KeyEvent ad ogni pressione di un tasto. L’ascoltatore degli eventi va scene.addKeyListener((KeyListener)this) aggiunto e rimosso nel nel metodo metodo initXlet: destroyXlet: scene.removeKeyListener((KeyListener)this). Come mostrato in Figura 2.5.1 gli eventi associati alla pressione dei un tasto del telecomando definiti nell'MHP sono solamente quelli numerici, le frecce di navigazione, il tasto “OK”, il tasto teletext e i quattro tasti colorati (rosso,verde,giallo,blu). Fig. 2.5.1 - Input event in MHP Da notare che lo standard MHP non definisce eventi per i tasti “EXIT” e “BACK” che sono presenti sulla quasi totalità dei decoder e comunque gestiti da org.havi.ui.event.HRcEvent ma potrebbero generare eventi non-standard e non sempre coerenti sui diversi apparecchi. Vediamo ora un esempio di codice necessario per l’implementazione del primo metodo, il codice seguente deve risiedere nella classe principale della Xlet. 38 Capitolo 2: Applicazioni MHP public void keyPressed (KeyEvent key) { int pulsantePremuto = key.getKeyCode(); switch(pulsantePremuto){ //Tastierino numerico case KeyEvent.VK_0: case KeyEvent.VK_1: case KeyEvent.VK_2: case KeyEvent.VK_3: case KeyEvent.VK_4: case KeyEvent.VK_5: case KeyEvent.VK_6: case KeyEvent.VK_7: case KeyEvent.VK_8: case KeyEvent.VK_9: //Tasti colorati case HRcEvent.VK_COLORED_KEY_0: //rosso case HRcEvent.VK_COLORED_KEY_1: //verde case HRcEvent.VK_COLORED_KEY_2: //giallo case HRcEvent.VK_COLORED_KEY_3: //blu //Tasti direzionali case HRcEvent.VK_UP: case HRcEvent.VK_DOWN: case HRcEvent.VK_RIGHT: case HRcEvent.VK_LEFT: case HRcEvent.VK_ENTER: //OK //Vengono passati gli eventi. interfaccia.keyPressed(key); break; //Alla pressione del tasto EXIT del telecomando si //richiama il metodo destroyXlet, per fermare la xlet. case HRcEvent.VK_ESCAPE: try{ destroyXlet(true); } catch (XletStateChangeException xsce){ System.out.println("Premuto tasto EXIT "+xsce.toString()); } break; default: break; } } public void keyTyped(KeyEvent ignored) { } public void keyReleased(KeyEvent ignored) { } 39 Capitolo 2: Applicazioni MHP Essendo il primo metodo ampiamente documentato e conosciuto vediamo nel dettaglio quello specifico del DVB. Tale meccanismo è consigliabile rispetto ad AWT perché permette un uso più oculato e collaborativo nella gestione degli eventi. Infatti rispetto ad AWT dove la registrazione degli eventi è esclusiva e comprende tutti gli eventi possibili, nel modello dvb è possibile registrarsi per la notifica dei soli eventi a cui si è realmente interessati. Le classi principali di questo package sono la EventManger e la UserEventRepository che gestiscono rispettivamente la registrazione degli eventi e il repository degli eventi a cui si è interessati. UserEventRepository repository = new userEventRepository("UserRepository"); repository.addAllColourKeys(); EventManager manager = EventManager.getInstance(); // this rappresenta la classe listener manager.addUserEventListener(this, repository); Questo approccio ha l'ulteriore vantaggio di non imporre alla Xlet di richiedere il focus tramite la HScene (come prevede il modello awt sui Component) risulta quindi essere l'unico approccio da seguire in tutte quelle applicazioni in cui si vogliono ricevere eventi del telecomando ma non si dispone una GUI. 40 Capitolo 3: Canale di ritorno Capitolo 3 Canale di ritorno 3.1 Lo scenario L’infrastruttura trasmissiva per il digitale terrestre consente l’invio broadcast di contenuti televisivi, radio ed informatici (es. dati) a ricevitori DTT quali: televisori associati ad un “decoder” supplementare o Set Top Box, televisori digitali corredati di decoder interno, altri apparecchi (sintoamplificatori, videoregistratori, sistemi AV compatti, etc.) che incorporano un sintonizzatore DTT. A ciascun “programma” (televisivo, radio, informatico) il Content Provider può associare un servizio informazioni sul programma, un servizio Teletext tradizionale, un servizio “super Teletext” (“Enhanced Broadcasting”) supportato dallo standard “mhp” (Multimedia Home Platform); quest’ultimo, infatti, realizza e presenta sullo schermo del televisore “pagine” contenenti testo e grafica al cui interno è possibile “navigare” in modo molto simile ad Internet. Se poi il ricevitore DTT è corredato da un modem (analogico, ISDN o xDSL), connettendosi ad Internet o ad altre reti di telecomunicazioni, la DTT diventa lo strumento attraverso il quale sviluppare servizi interattivi più o meno estesi (da forme minimali di “televoto” realizzato collegandosi a numeri telefonici o a indirizzi Internet corrispondenti al voto che si vuole esprimere, fino all’impiego di sofisticate applicazioni di commercio elettronico, e-government, fornitura di servizi Video On Demand, etc). Più in generale, l’interattività estesa è destinata all’impiego di servizi Internet. 41 Capitolo 3: Canale di ritorno Fig. 3.1.1 - Scenario canale di ritorno Tutte tali possibilità (che si comprendono meglio utilizzando un ricevitore) sono ampiamente chiarite già nell’introduzione dallo Standard ETSI ES 201 812 V1.1.1 (2003-12) relativo e Digital Video Broadcasting (DVB) e Multimedia Home Platform (MHP) Specification 1.0.3 , capitolo 0, paragrafo 0.2: “… At the beginning the following application areas are considered - Enhanced Broadcasting, Interactive Broadcasting and Internet Access. Enhanced Broadcasting combines digital broadcast of audio/video services with downloaded applications which can enable local interactivity. It does not need an interaction channel. The application area Interactive Broadcasting enables a range of interactive services associated or independent from broadcast services. This application area requires an interaction channel. The application area of Internet Access is intended for the provisioning of Internet services. It also includes links between those Internet services and broadcast services. …” In pratica, il protocollo mhp è costituito da una raccolta di “metodi java” (programmi di base) che consentono di accedere alle funzioni di base del ricevitore DTT e di far partire automaticamente o manualmente, utilizzando il telecomando, le applicazioni java trasmesse dal Content Provider Broadcaster. Queste “applicazioni Java” sono a tutti gli 42 Capitolo 3: Canale di ritorno effetti dei programmi informatici che utilizzano, in luogo di un personal computer o di un PDA, le capacita di elaborazione e lo schermo del ricevitore DTT. Pertanto, le applicazioni di “Enhanced Broadcasting” costituiscono il presupposto per il lancio di applicazioni di “Interactive Broadcating”, che consentono il lancio delle applicazioni di “Internet Access” realizzate attraverso l’uso di reti PSTN, ISDN o xDSL (si prevede che la prossima generazione di Set Top Boxes incorpori anche tecnologia WiFi). Si noti che una volta attivato il “canale di interattività”, il ricevitore DTT può diventare autonomo dalle applicazioni broadcast e, per esempio, accedere, puramente via Internet. ad un servizio “pay per view” o “pay to download” via xDSL. Sempre attraverso il “canale di interazione”, il ricevitore può continuare a ricevere nuovi programmi java che sviluppano nuove funzionalità. Di fatto, nel corso della attivazione, l’utente può configurare nel ricevitore DTT il numero di telefono, il codice utente e la password fornitagli dal proprio fornitore di accesso ad Internet, ma i servizi di “Enhanced Broadcasting” e “Interactive Broadcating” possono imporre al ricevitore DTT interattivo di utilizzare per l’“Internet Access” numero di telefono, codice utente e password diversi da quelli predefiniti dall’utente. In altre parole, l’accesso alle applicazioni di “Enhanced Broadcasting” rappresenta una insuperabile barriera alla realizzazione e alla fornitura di servizi della società dell’informazione diretti a quel 60% della popolazione che non usa il computer, ma che dopo lo switch off previsto per il 31 dicembre 2008, userà un televisore DTT. Accordi tra content provider su piattaforma DTT (che hanno il potere di associare al programma televisivo o radiofonico opportune applicazioni) e alcuni operatori di rete fissa, potrebbero avere l’effetto di una totale esclusione da tale mercato dei restanti operatori di rete fissa. Il pericolo è particolarmente elevato nel caso di Telecom Italia che, attraverso Telecom Italia Media, dispone già oggi di un accesso ai servizi di “Enhanced Broadcasting” e “Interactive Broadcating” su piattaforma DTT, ma non è da sottovalutare l’ipotesi che anche gli altri operatori di rete DTT possano seguire politiche discriminatorie. In assenza di adeguate misure volte a garantire l’accesso alle applicazioni di “Enhanced Broadcasting” (che, si è visto, costituiscono il presupposto per il lancio di applicazioni di “Interactive Broadcating”), l’accesso al “canale di ritorno” del decoder a condizioni 43 Capitolo 3: Canale di ritorno oggettive e non discriminatorie tra i diversi operatori di telecomunicazioni e fornitori di accesso ad Internet e ad assicurare l’ interoperabilità dei servizi, si potrebbe assistere ad inquietanti fenomeni di “walled garden”, consistenti in un’offerta integrata di contenuti Internet (es. Video On Demand, e-commerce, etc…), promossi per il tramite delle applicazioni di Enhanced ed Interactive Broadcasting, accessibili mediante la DTT (sfruttando il canale di ritorno del decoder) esclusivamente per il tramite di uno specifico fornitore di accesso e contenuti Internet predeterminato dal fornitore della rete e/o del servizio DTT, così impedendo l’accesso degli utenti del servizio DTT ai servizi/contenuti/siti dei content providers non predeterminati dal gestore della rete e/o del servizio DTT, che verrebbero ad essere relegati in una sorta di “universo recintato”, a scapito della concorrenza, del pluralismo e, da ultimo, a danno degli utenti finali. 44 Capitolo 3: Canale di ritorno 3.2 I protocolli supportati Per comprendere appieno le potenzialità del canale di ritorno, che si possono utilizzare, partiremo dall’analisi dei protocolli supportati da MHP che sono mostrati in figura 3.2.1. Fig. 3.2.1 - Protocolli canale di ritorno 3.2.1 Livello 1: Fisico A livello fisico (Livello 1), si definiscono le caratteristiche del mezzo trasmissivo, del tasso di trasmissione e gli schemi di codifica su cui avverrà la comunicazione. I ricevitori DTT attualmente in commercio almeno per quanto riguarda quelli interattivi, sono dotati al loro interno di un modem 56K che permette di aprire una connessione su canale bidirezionale sfruttando la linea telefonica commutata. Tuttavia il canale di ritorno via modem analogico (PSTN) presenta i seguenti problemi: • banda disponibile ridotta ; • impossibilità di uso contemporaneo del servizio di fonia sulla stessa linea telefonica; • canale non always-on, con conseguente necessità di effettuare eventualmente diverse aperture e chiusure di connessione durante una sessione di utilizzo del servizio interattivo; 45 Capitolo 3: Canale di ritorno • tempi di attesa dovuti alla procedura di connessione; • costi di utilizzo basati sulla durata della connessione a circuito; • canale con flusso di dati bilanciato, proprietà non utile in molte applicazioni. Lo standard PSTN (definito nelle specifiche ETSI ETS 300 801) è sicuramente uno standard affermato e ancora di largo consumo, ma che limita enormemente quelle che sono le possibilità di creare applicativi di alto livello. Uno dei vantaggi di questa scelta è però la garanzia che vi sia un più vasto numero di utenti che può, data la ormai affermata tecnologia e la vasta disponibilità sul territorio, sfruttare i servizi interattivi messi a disposizione sul digitale terrestre. Il fatto che questa tecnologia sia accessibile da quasi la totalità della popolazione è un fattore da non sottovalutare per poter rendere il DTT una tecnologia aperta a tutti. In futuro con una prevista evoluzione dei servizi messi a disposizione, si inizieranno a vedere sul mercato STB di fascia media con supporto per linea ISDN oppure di fascia alta con connessione a banda larga ADSL, anche se ci sarà ancora molto da aspettare dati i costi attualmente più elevati rispetto a quelli di fasci bassa. L’ ultima ipotesi è certamente la più interessante, perché da la possibilità di avere una connessione sempre attiva, cosa non permessa dai normali ricevitori con modem PSTN che chiudono la chiamata al Centro Servizi dopo un tempo prestabilito di inattività, ma soprattutto darà la possibilità di portare sul digitale terrestre applicazioni che ora sono riservate solo ai PC su Internet. 3.2.2 Livello 2: PPP A livello di collegamento (Livello 2) si trova il protocollo PPP (Point-to-Point Protocol) che è ad oggi il protocollo più utilizzato per il collegamento a Internet degli host residenziali tramite doppino telefonico. Standardizzato in RFC 1661 e RFC 2152 . Il PPP gestisce il riconoscimento degli errori, supporta molti protocolli, permette che gli indirizzi IP siano stabiliti al momento della connessione e supporta l’autenticazione. I vantaggi del PPP sono: • i frame vengono suddivisi in maniera tale da garantire una non ambigua separazione tra un frame e quello successivo; 46 Capitolo 3: Canale di ritorno • possiede un protocollo di controllo di collegamento per la gestione delle linee che prende il nome di LCP (Link Control Protocol); • le opzioni del livello di rete sono negoziate in maniera indipendente dal protocollo che si utilizzerà a livello di rete. Si avrà quindi un diverso NCP (Network Control Protocol) per ogni livello rete supportato. Nel nostro caso vediamo come il STB riesce ad acquisire un indirizzo IP dal Centro Servizi per poter poi comunicare con esso ed accedere ai servizi interattivi messi a disposizione. Innanzi tutto si esegue la chiamata verso il CS ad un numero che può essere univoco per tutto il territorio impostato di default dall’utente oppure settato dalla Xlet. Dopo che il modem del CS ha risposto e stabilito una connessione a livello fisico nel nostro caso su linea commutata, il STB manda al router una serie di pacchetti LCP inseriti nel campo Payload di uno o più frame PPP. I pacchetti inviati andranno a selezionare i parametri PPP da utilizzare nella comunicazione. Una volta selezionati i parametri, si passa all’invio dei pacchetti NCP per configurare il livello rete. Nel nostro caso il ricevitore vuole eseguire un insieme di protocolli TCP/IP e quindi necessita di ottenere un indirizzo IP. Ogni CS avrà a disposizione un set di indirizzi IP da assegnare e ne verrà assegnato dinamicamente uno a chi ne fa richiesta, una volta eseguita la connessione e per tutta la sua durata. A questo punto il nostro STB e formalmente un host Internet e può inviare e ricevere pacchetti IP proprio come se fosse connesso fisicamente con CS. La disconnessone avviene quando NCP sgancia la connessione a livello di rete e va cosi a liberare l’indirizzo IP. Quindi LCP sgancia la connessione del livello di collegamento e quindi la PPP ed infine il STB ordina al modem interno di liberare la linea telefonica rilasciano la connessione a livelli fisico. 47 Capitolo 3: Canale di ritorno 3.2.3 Protocollo di rete: IP Il livello di rete, è occupato dal protocollo IP (Internet Protocol), che nel suo datagram incapsula tra i numerosi campi previsti, l’indirizzo IP del server di destinazione, cioè quello del Centro servizi e quello del client, quindi quello assegnato dal CS stesso al momento dell’autenticazione in maniera dinamica. Il suo compito è quello di garantire che i pacchetti arrivino dalla sorgente, nel nostro caso il STB, alla destinazione il CS, indipendentemente dalla presenza di reti intermedie durante il suo percorso e indipendentemente dal fatto che più utenti stiano utilizzando nello stesso momento l’applicazione per mettersi in contatto con il CS. In pratica sarà il livello di trasporto che riceverà il flusso di dati ed andrà a frammentarlo in datagram. Ogni datagram viene poi trasmesso e frammentato ulteriormente durante il percorso, una volta che tutti i pezzi hanno raggiunto la destinazione, vengono riassemblati dal livello di rete nel datagram di partenza. Il datagram riassemblato viene poi passato al livello trasporto, che lo inserisce nel flusso di input del processo ricevente. Il protocollo IP supporta sopra di se i protocolli TCP e UDP entrambi previsti dalle specifiche MHP. 3.2.4 Protocolli di trasporto: TCP / UDP I protocolli di trasporto supportati sono sia il TCP che l’ UDP questi si differenziano principalmente per il fatto che il primo e orientato alla connessione mentre il secondo e sostanzialmente fondato sull’IP e non orientato alla connessione. Il TCP (Transmission Control Protocol) progettato per garantire connessioni affidabili, necessita di una entità di trasporto TCP, che va a gestire i flussi di dati TCP e si interfaccia con il livello IP. I datagram IP ricevuti che contengono dati TCP vengono passati all’entità TCP che ricostruisce il flusso originale di byte, precedentemente spezzettato dall’entità TCP del trasmettitore. Il livello IP non da garanzia sulla avvenuta consegna dei datagram; e quindi compito del TCP ritrasmetterli quando necessario. Un altro compito del TCP è riassemblare i messaggi nella sequenza corretta tutte cose che da solo IP non può fare. 48 Capitolo 3: Canale di ritorno Il TCP si basa su punti accesso chiamati socket, sia il client (STB) che il server (CS) ne devono possedere uno univoco. Ogni socket e caratterizzato da un indirizzo IP che identifica l’host e da un numero di 16 bit detto “porta”, per esempio l'HTTP comunica sulla porta 80. Il protocollo UDP (User Data Protocol) è un protocollo non orientato alla connessione, è usato per spedire datagram IP, senza dover stabilire una connessione, in maniera più rapida ma senza garantire il controllo degli errori e del flusso dei pacchetti. 3.2.5 Hypertext Transfer Protocol (HTTP) Lo standard HTPP (Hyper Text Tranfer Protocol) è il protocollo che sta alla base del WWW. Le specifiche MHP incentivano l'uso di questo protocollo come possiamo vedere dalla figura 3.2.1, questo perché il suo utilizzo da la possibilità di trasferire informazioni di varia natura, come file di testo, immagini, ecc.. L'HTTP è un protocollo orientato alla comunicazione tra un client ed un server. Come si può vedere anche in figura esso è supportato dal TCP, ma nonostante questo resta comunque un protocollo “senza stato”, quindi ogni transazione viene trattata in maniera indipendente dalle altre almeno per quanto riguarda l'HTTP 1.0. Viene quindi creata una connessione TCP ogni volta che il STB dovrà interagire con il Centro Servizi per lo scambio dei dati. I terminali MHP di nuova generazione sono nati per supportare sia HTTP 1.0 (IETF RFC 1945[92]) che HTTP1.1 (IETF RFC 2616[40]) con alcune considerazioni del caso soprattutto per quanto riguarda le connessioni di tipo keep-alive. I terminali MHP e quindi le applicazioni che implementano i profili Interactive TV Profile e Internet Access Profile, che sfruttano il protocolli HTTP 1.0/1.1 dovranno supportare connessioni di tipo persistente. L' HTTP 1.1 è stato introdotto per colmare le lacune del vecchio HTTP1.0 in quanto una delle caratteristiche fondamentali è quella di stabilire una sola connessione TCP per tutta la durata della comunicazione. Il fatto che nella maggior parte dei casi il terminale MHP comunicherà per tutta la durata della connessione con un unico ISP, il nostro Centro Servizi, è opportuno stabilire una connessione sempre attiva per tutta la durata della comunicazione evitando problemi di congestione della rete. Per stabilire una connessione di tipo persistente i terminali MHP che sfruttano il 49 Capitolo 3: Canale di ritorno protocollo HTTP1.0 dovranno inserire all'interno del messaggio HTTP nel campo Connection del General-Header il token Keep-Alive: Connection: keep-alive Il server HTTP che si vede arrivare un messaggio di richiesta di connessione KeepAlive risponderà instaurando una connessione persistente. Se si fa questo,il campo Keep-Alive del General-Header, del messaggio HTTP, conterrà il tempo durante il quale il trasmettitore mantiene la connessione aperta in attesa della richiesta successiva, oppure il numero massimo di richieste aggiuntive che sono consentite sulla connessione in corso. Il campo Keep-Alive sarà composto come sotto: Keep-Alive-header="Keep-Alive" ":" 0# keepalive-param keepalive-param = param-name "=" value dove la notazione ''0#'' significa che i parametri potranno essere ripetuti 0 o più volte e saranno separati da '','' se il campo ne contiene più di uno. Se i terminali MHP dovranno comunicare con un server proxy HTTP1.0 non sarà necessario inviare il token Keep-Alive, questo perché un proxy HTTP1.0 non ubbidisce alle regole dell' HTTP1.1 per fare il parsing del campo Connection. Questo significa che si dovrà impedire ai terminali MHP di richiedere connessioni sempre attive quando questi devono comunicare con server proxy che non ne potrà supportare le funzionalità. I server di tipo HTTP1.1 che si basano su connessioni persistenti dovranno supportare anche l' HTTP1.0 per richieste da parte di terminali MHP che lavorano con protocollo HTTP1.0 e che cercheranno comunque di instaurare connessioni sempre attive utilizzando il token Keep-Alive. 50 Capitolo 3: Canale di ritorno 3.2.6 Secure Hypertext Transfer Protocol (HTTPS) Nei casi in cui si richiede un canale di comunicazione protetto con il CS, le specifiche MHP consigliano l'uso del protocollo HTTPS. Questo protocollo è sostanzialmente basato sull'HTTP ma affianca ad esso il protocollo TLS (Transport Layer Security). TLS 1.0 è molto simile a SSL 3.0 anche se non è compatibile con questo. Lo scopo di TLS è quello di permettere una comunicazione sicura tra due applicazioni, fornendo al flusso di dati servizi di autenticazione, integrità e riservatezza. Il profilo MHP non implementa l'intero TLS ma solamente una parte degli algoritmi di cifratura previsti tra cui in particolare : • RSA () • MD5 • SHA-1 • DES 3.2.7 Servizi Specifici Qualora si trovasse la necessita di supportare da parte dei ricevitori protocolli proprietari per il canale di ritorno lo standard MHP lascia ai costruttori la possibilità di definirne alcuni e di portarli sulle piattaforme. Ad oggi tutti i ricevitori STB in commercio utilizzano protocolli di uso comune che garantiscono più robustezza, integrità e maggiore possibilità di integrazione alla rete Internet per una prospettiva futura di accesso a contenuti Web con l'introduzione del profilo “Internet Access Profile”. 51 Capitolo 3: Canale di ritorno 3.3 Gestione della connessione Indipendentemente dal tipo di modem di cui è provvisto il set top box, le connessioni sono gestite dal package Org.dvb.net.rc in questo paragrafo andremo a vedere nel dettaglio le classi di questo package e cercheremo di capire come queste interagiscono tra di loro per poter poi essere utilizzate per stabilire una connessione con un ISP remoto. Successivamente daremo uno sguardo agli altri package, java.net e java.io, che verranno utilizzati una volta che la connessione è stabilita. 3.3.1 Utilizzare il canale di ritorno: il package org.dvb.net.rc Tra i tre profili che MHP introduce sia “Interactive Broadcast” che “Internet Access”, includono il supporto per utilizzate il canale di ritorno sui STB. In base al tipo di ricevitore che stiamo utilizzando il canale di ritorno può assumere diverse forme in base alla fascia di appartenenza, anche se ad oggi quella più comune è il modem PSTN, che garantisce: una maggiore reperibilità sul territorio, quindi un bacino di utenza più ampio e semplicità di installazione, anche se porta dei limiti che come abbiamo gia visto sono molto “pesanti”. I STB quando stabiliscono una connessione con canale di ritorno non fanno altro che appoggiarsi al protocollo IP del livello di rete e come molte applicazioni Java che fanno uso di una connessione remota, anche le nostre Xlet si appoggeranno alle classi del package java.net, con alcune restrizioni del caso. Le specifiche MHP 1.0.x richiedono supporto per HTTP1.0 e DNS su canale di ritorno al di sopra del TCP, ma ogni altro protocollo è opzionale, questo lascia un po’ di liberta anche se può portare a dei mal funzionamenti, sarà quindi compito del programmatore fare le opportune verifiche. Con MHP 1.1 le specifiche aggiungono il supporto per HTTPS un protocollo per connessioni sicure che unisce l’ HTTP con i certificati TLS. Le specifiche non aggiungono niente per quanto riguarda il supporto ad altri protocolli come FTP o SMTP. Anche se l’ultimo è sicuramente necessario per la creazione di un client e-mail. Una considerazione va fatta per quanto riguarda la gestione della connessione, in quanto le API java.net assumono che una connessione permanente sia eseguibile, è questo 52 Capitolo 3: Canale di ritorno può diventare un problema da non sottovalutare specialmente quando il nostro STB usa un modem PSTN. In certi casi non ci dovremo curare di gestire la connessione, sarà il ricevitore a connettersi automaticamente all’ISP di default, dovremmo solo preoccuparci di utilizzare correttamente le classi java.net.socket e java.net.URLConnection per fare riferimento ad un file remoto. In maniera simile il ricevitore dopo un periodo di inattività andrà a disconnettere automaticamente il canale di ritorno, questo porta ad una sorta di sistema di sicurezza da xlet maligne che però è facilmente aggirabile. 3.3.2 Interfacce per il Canale di Ritorno Ogni tipo di interfaccia supportata dai STB è rappresentata da un’istanza della classe RCInterface : public class RCInterface extends java.lang.Object { public static final int TYPE_PSTN = 1; public static final int TYPE_ISDN = 2; public static final int TYPE_DECT = 3; public static final int TYPE_CATV = 4; public static final int TYPE_LMDS = 5; public static final int TYPE_MATV = 6; public static final int TYPE_RCS = 7; public static final int TYPE_UNKNOWN = 8; public static final int TYPE_OTHER = 9; private int type; private int dataRate; protected RCInterface() { } public int getType() { return this.type; } public int getDataRate() { return this.dataRate; } } Con questa classe è possibile modellare nome, velocità in Kbps e tipo di interfaccia da utilizzare per ottenere una connessione IP. Come possiamo vedere questa classe definisce un insieme di costanti dove ognuna rappresenta un tipo di canale di ritorno. 53 Capitolo 3: Canale di ritorno COSTANTI TYPE_PSTN TYPE_ISDN TYPE_DECT TYPE_CATV TYPE_LMDS TYPE_MATV TYPE_RSC TYPE_UNKNOWN TYPE_OTHER TIPO DI CANALE PSTN ISDN DECT Cable modem Local Multipoint Distribution System. Canale di Ritorno Wireless Master antenna TV DVB-RCS (Canale di ritorno via satellite) Utilizzato quando è presente una interfaccia hardware tra il ricevitore e il dispositivo senza conoscerne i dettagli Usato per standard futuri di cui DVB non intende aggiungere costanti proprie Tabella 3.3.2.1 – Costanti canale di ritorno Il metodo getType()ritorna un intero corrispondente al tipo di interfaccia a cui la classe ConnectionRCInterface fa riferimento. Mentre il metodo getDataRate() ritorna il massimo “data rate” dell’interfaccia a cui siamo connessi in questo momento, se nessuna interfaccia è connessa il ricevitore ci restituirà il valore dell’ultima interfaccia utilizzata oppure il valore –1 se non reperibile. 54 Capitolo 3: Canale di ritorno 3.3.3 Acquisire l’interfaccia corretta Come abbiamo visto nelle specifiche MHP esiste più di un tipo differente di interfaccia, in base al supporto che implementa il canale di ritorno, questo rende necessaria la presenza di una classe che sia in grado di ottenere l’accesso a quella corretta e ne controlli l’utilizzo in base al quello che dovrà fare la nostra xlet. A tal proposito e stata creata la classe RCInterfaceManager che ci fornisce alcuni metodi per controllare l’accesso a questo tipo di risorsa scarsa. public class RCInterfaceManager implements org.davic.resources.ResourceServer { public static RCInterfaceManager getInstance(); public RCInterface[] getInterfaces(); public RCInterface getInterface(java.net.InetAddress addr); public RCInterface getInterface(java.net.Socket s); public RCInterface getInterface(java.net.URLConnection u); public void addResourceStatusEventListener( org.davic.resources.ResourceStatusListener listener); public void removeResourceStatusEventListener( org.davic.resources.ResourceStatusListener listener); } La classe RCInterfaceManager mette a disposizione un metodo il getInstance()che viene innanzitutto utilizzato per ottenere un’istanza di se stessa da utilizzare nei passi successivi. Una volta che abbiamo ottenuto il riferimento a questa classe possiamo procedere con l’acquisire l’interfaccia appropriata, cosi con il metodo getInterfaces() ci viene restituito un array di RCInterface in base ai permessi associati alla nostra applicazione e alla disponibilità di interfacce che il nostro ricevitore supporta. Questo array potrebbe risultare anche vuoto se la nostra applicazione non ha i permessi necessari per l’uso della risorsa magari perché viene identificata come maligna. Il metodo getInterfaces() ha tre varianti legate alla classe del package java.net che andremo ad utilizzare; java.net.URLConnection, java.net.Socket, java.net.InetAdress. Nei primi due casi il middleware del ricevitore assume che la connessione sia già attiva, questo perché sarà esso stesso che non appena nota la richiesta di connessione ad una 55 Capitolo 3: Canale di ritorno URL o ad una Socket cerca di creare una connessione su linea di ritorno, e ritornerà l’interfaccia utilizzata per questa connessione. Nell’ultimo caso invece il middleware ritornerà l’interfaccia che dovremmo poi utilizzare per stabilire la connessione. Come ho gia detto i STB in commercio sono forniti solamente di modem PSTN e quindi i metodi appena citati riporteranno se ben formati sempre l’interfaccia ‘1’ o nulla se non presente, bisognerà tenere conto di questo per non incorrere in mal funzionamenti. Essendo il canale di ritorno una risorsa scarsa, questa classe come possiamo vedere dal codice, implementa l’interfaccia ResourceServer permettendo di associare un ResourceStatusListener al nostro oggetto per informarlo sugli eventi che accadono alla risorsa. Gli eventi generati del tipo ResurceStatusEvents possono essere: • RCInterfaceReservedEvent: Questo evento viene generato quando un’applicazione ha correttamente riservato una RCInterface, può essere generato anche quando un’altra entità del sistema ha riservato la risorsa rendendola cosi non più utilizzabile. • RCInterfaceReleasedEvent: Viene generato quando un’applicazione che precedentemente aveva riservato una RCInterface ha poi deciso di rilasciare la risorsa chiamando il metodo release() della classe ConnectionRCInterface su quella interfaccia. Può essere generato anche da un’altra entità del sistema informando cosi l’applicazione che la risorsa è ora disponibile. 56 Capitolo 3: Canale di ritorno 3.3.4 Stabilire la connessione Tra le interfacce per il canale di ritorno dichiarate nella classe RCInterface alcune rappresentano una connessione sempre attiva. Il middleware non tratta quindi tutti i tipi di interfacce in maniera uguale questo perché le connessioni sempre attive come possono essere TYPE_CATV o ADSL non vengono trattate come se fossero risorse scarse, semplicemente perché l’applicazione non ha bisogno di sapere a quale provider il canale di ritorno è connesso. La gestione della connessione e quindi completamente lasciata nelle mani del middleware che si occupa di tenerla sempre disponibile nel momento in cui l’applicazione ne fa richiesta, senza preoccuparsi di troppi dettagli. Questo però non accade con altri tipi di interfacce, come per esempio con un modem PSTN, in questo caso dovremo conoscere il numero di telefono dell’ISP a cui connettersi, prima di poter utilizzare la risorsa, oltre a dati come user e password. In base al service provider al quale ci si vuole connettere potrebbe succedere che un’applicazione sia abilitata a stabilire una comunicazione, mentre altre no per problemi di sicurezza, e ciò pone dei limiti ma anche dei vantaggi. La classe ConnectionRCInterface estende la classe RCInterface ed implementa l’interfaccia ResourceProxy , di seguito possiamo vedere i metodi di cui e composta. public class ConnectionRCInterface extends RCInterface implements org.davic.resources.ResourceProxy { public boolean isConnected(); public float getSetupTimeEstimate(); public void reserve( org.davic.resources.ResourceClient c, java.lang.Object requestData) throws PermissionDeniedException; public void release(); public void connect() throws java.io.IOException, PermissionDeniedException; public void disconnect() throws PermissionDeniedException; public ConnectionParameters getCurrentTarget() throws IncompleteTargetException; public void setTarget(ConnectionParameters target) throws IncompleteTargetException, PermissionDeniedException; 57 Capitolo 3: Canale di ritorno public void setTargetToDefault() throws PermissionDeniedException; public int getConnectedTime(); public org.davic.resources.ResourceClient getClient(); public void addConnectionListener( ConnectionListener l); public void removeConnectionListener( ConnectionListener l); } Questa classe mette a disposizione i metodi necessari per riservare la risorsa, settarne i parametri ed eseguire connessione e disconnessione dall’ISP impostato. Prima di eseguire la connessione all’ISP utilizzando l’interfaccia specificata è necessario riservare la risorsa per la nostra applicazione, per fare questo si utilizza il metodo riserve() per evitare che altri cerchino di utilizzarla generando dei conflitti. Questo metodo è utilizzato per associare all’interfaccia un ResurceClient utilizzato per i messaggi di notifica sullo stato della risorsa e per quando questa viene rimossa, questo metodo può generare un’eccezione del tipo PermissionDeniedException quando la nostra applicazione non ha i permessi necessari per riservare la risorsa. L’applicazione che utilizza il canale di ritorno deve per forza essere a conoscenza dei dati che ci permettono di chiamare ad autenticarci presso un ISP e quindi prima di chiamare il metodo connect() sarà necessario che questi vengano impostati nell’interfaccia selezionata, per fare questo si utilizza il metodo setTarget(). Ogni interfaccia ha in genere un ISP associato al quale ci si connette quando la si usa, solitamente questo è strettamente legato al broadcaster che trasmette l’applicazione interattiva e che quindi mette a disposizione anche un Centro Servizi con cui interagire. Il metodo setTargetToDefault() rimuove tutti i target impostati e setta quello che il produttore del middleware ha definito come standard. Se la nostra applicazione non ha i permessi per reimpostare il target o se quello di default non è valido viene lanciata una SecurityException. Ogni target è rappresentato da un oggetto della classe ConnectionParameters, questa classe è utilizzata per creare un oggetto in cui sono definiti i parametri necessari per stabilire una connessione. 58 Capitolo 3: Canale di ritorno La classe e strutturata come segue: public class ConnectionParameters { public ConnectionParameters( java.lang.String number, java.lang.String username, java.lang.String password); public ConnectionParameters( java.lang.String number, java.lang.String username, java.lang.String password, java.net.InetAddress[] dns); public java.lang.String getTarget(); public java.lang.String getUsername(); public java.lang.String getPassword(); public java.net.InetAddress[] getDNSServer(); } Come possiamo vedere i parametri di cui una interfaccia ha bisogno sono un numero telefonico un “username” e una password che il middleware utilizza per autenticare la connessione. E’ possibile passare se necessario un array con gli indirizzi dei server DNS (Domain Name System) da utilizzare, prima di riportare un messaggio di fallimento, che vengono utilizzati per trasformare nomi di host e indirizzi di posta elettronica in indirizzi IP, da utilizzare quindi per stabilire connessioni TCP/UDP sulle socket. Chiamando il metodo setTarget() è possibile che venga lanciata una IncompleteTargetException nel caso in cui l’oggetto ConnectionParameters fosse stato creato con qualche parametro a null. Ogni cambiamento sull’oggetto ConnectionParameters avrà effetto nel momento in cui noi stabiliamo la connessione, se pero delle modifiche vengono fatte durante una connessione gia attiva per rendere effettivi i nuovi parametri sarà necessario disconnettere l’interfaccia e poi riconnetterci per puntare al nuovo ISP. Una volta impostato un target possiamo chiamare il metodo connect() sulla nostra interfaccia, ed una volta ottenuto l’IP utilizzare le classi del package java.net per connetterci all’host desiderato. Nel caso dell’interfaccia PSTN il middleware andrà a chiedere il consenso dell’utente prima che l’applicazione effettui la chiamata, se l’utente acconsente il numero telefonico viene messo in una “white list” per evitare che si richieda il consenso al prossimo accesso. 59 Capitolo 3: Canale di ritorno Se l’utente blocca la chiamata viene generato un ConnectionFailedEvent, mentre se si acconsente ed il middleware riesce a creare la connessione viene generato un ConnectionEstablischedEvent. Nel caso in cui la risorsa sia riservata ma non sia possibile stabilire una connessione con il target specificato il middleware lancerà una IOException per notificare all’ascoltare che ci sono stati dei problemi non direttamente dovuti all’hardware del ricevitore. Una volta che l’applicazione ha terminato di utilizzare la risorsa si chiama il metodo disconnect() e quindi il metodo release() per rilasciare la risorsa e permettere ad altre applicazioni di utilizzarla, lanciando l’evento ConnectionTerminatedEvent. Ottenere la connessione ad un ISP può richiedere del tempo ed avere delle complicanze, specialmente nel caso di un modem PSTN, per questo la nostra applicazione dovrà registrare come ascoltatori quelle classi che intendono sfruttare la connessione. Queste classi dovranno implementare l’interfaccia ConnectionListener e quindi definire come il metodo astratto connectionChanged dovrà comportarsi quando si vede arrivare un evento del tipo ConnectionRCEvent. La classe ConnectionListener è strutturata come segue: public interface ConnectionListener { public abstract void connectionChanged(ConnectionRCEvent e); } Le classi che implementano ConnectionListener vengono associate all’oggetto ConnectionRCInterface utilizzando il metodo addConnectionListener in modo che il middleware saprà a chi inviare gli eventi che si generano da quell’ interfaccia. Tali eventi sono di tre tipi: • ConnectionEstabliscedEvent: Generato nel caso in cui la connessione ha avuto successo. • ConnectionFailedEvent: Se non è stato possibile stabilire la connessione per motivi tecnici, se per esempio qualcuno sta utilizzando in quel momento il telefono. • ConnectionTerminateEvent: Generato nel caso in cui si rompe la connessone oppure se l’applicazione stessa ha deciso di disconnettersi. 60 Capitolo 3: Canale di ritorno Gli eventi appena descritti possono essere generati anche dall’applicazione stessa nel caso in cui si verifichino più sequenze di request sulla stessa connessione per notificare alla classe in ascolto che la sessione è ancora attiva e si possono trasferire file senza stabilirne una nuova. 3.3.5 I package java.net e java.io Ottenuta la connessione con il Centro Servizi tramite i metodi messi a disposizione dal package org.dvb.net.rc le applicazioni si vanno ad appoggiare alle API java.net per accedere alle URL. Lo standard MHP pone alcune restrizioni sulle classi che si possono utilizzare di questo package, in particolare le classi messe a disposizione da DVB-J che è la versione ridotta di Java per MHP basata oltre che sui package specifici per il DVB sulla Personal Java 1.1.8 sono: • java.net.URL • java.net.URLConnection • java.net.Socket • java.net.InetAdress Tutte queste classi possono essere utilizzate per ottenere un collegamento con il Centro Servizi tramite protocolli HTTP e HTTPS. La particolarità delle classi del package java.net è quella di essere in grado di interfacciarsi tramite l’uso della JVM con il sistema operativo della macchina su cui si sta eseguendo l’applicazione sarà poi questa a gestire l’accesso al modem e a far transitare le richieste attivando una connessione su protocollo TCP. Ottenuto il link con la risorsa desiderata ci si appoggia al package java.io per i metodi di gestione dei file veri e propri. Anche altri metodi possono essere utilizzati per gestire i file in particolare per quanto riguarda immagini ci si appoggia alla classe java.awt.image.ImageProducer e per file audio e video al package javax.media. E’ sempre opportuno controllare nelle specifiche MHP quali classi possono essere utilizzate per evitare mal funzionamenti nelle applicazioni dato che non tutti i tipi MIME sono supportati dai STB. 61 Capitolo 4: Ambiente di sviluppo Capitolo 4 Ambiente di sviluppo 4.1 Introduzione Dopo aver visto nei capitoli precedenti le tecnologie usate nel digitale terrestre, le caratteristiche principali di un’applicazione MHP, con particolare riguardo all’uso del canale di ritorno, andremo a proporre come è possibile realizzare l’ambiente di sviluppo per l’implementazione, debug e test di una Xlet. Per l’implementazione, come prima cosa, si avrà bisogno di un PC con installata una Java Virtual Machine e magari un programma di sviluppo Java, come Eclipse, per evitare errori di sintassi. Per la fase di debug e una prima fase di test è molto utile utilizzare un simulatore per applicazioni MHP, per vedere errori in fase di compilazione. Per la fase di test vera e propria ci sono due modi di procedere: • il primo consiste nel simulare un broadcaster; necessità di un modulatore COFDM al quale in ingresso verrà mandata l’applicazione insieme ad un eventuale flusso video, mentre in uscita vi sarà collegato uno (o più) Set Top Box commerciali; • il secondo, che è quello che poi andremo ad analizzare in dettaglio, consiste nell’acquisto di un Set Top Box da sviluppo, che a differenza di quelli commerciali, permette l’ upload di applicazioni non solo dall’etere, ma anche in locale, tramite porta seriale (RS-232). 62 Capitolo 4: Ambiente di sviluppo 4.2 Il Simulatore XletView Tra i vari software freeware di simulazione per applicazioni MHP, che si trovano in rete, spicca XletView, distribuito da GNU General Public License (GPL) , scaricabile all’indirizzo www.sourceforge.net. Per l’installazione si richiede che sul PC sia presente una Java Virtual Machine a partire da JRE 1.4, JSDK1.4 o versione superiore, della Sun; mentre all’interno del pacchetto troviamo già le API JMF 2.1.1 (Java Media Framework) che servono per incorporare media-data come audio e video nelle applicazioni Java, applets e Xlet. Dopo aver installato il simulatore bisogna copiare nella cartella base, le API Javatv scaricabili dal sito della Sun, praticamente le directory presenti devono essere: Fig. 4.2.1 - Directory XletView A questo punto non ci dovrebbero essere problemi per l’avvio del programma; è importante che insieme alla finestra principale ne compaia anche un’altra, dove verranno scritti gli eventuali messaggi d’errore, insieme ai messaggi di debug voluti dal programmatore, ovvero tutti i System.out.println presenti nella Xlet. 63 Capitolo 4: Ambiente di sviluppo Per fare ciò è necessario scrivere la seguente riga di comando, magari mettendola in un file batch: cd xletview java -cp %CLASSPATH%;javatv_fcs/javatv.jar;xletview.jar;jars/metouia.jar;jars/javassist.jar; jars/nanoxml-2.2.3.jar net.beiker.xletview.Main cd .. Ora le finestre visibili dovrebbero essere le seguenti come mostrato in figura 4.2.2 e 4.2.3. Fig. 4.2.2 - Finestra principale XletView Nella schermata principale vengono visualizzati: • a destra il telecomando con tutti i tasti per simulare, mediante mouse, la periferica d’ingresso; • nel riquadro giallo l’eventuale background layer, video layer e il graphics layer; • i menù per poter eseguire la nostra applicazione. Dal menu Applications, sotto ManageApplication, bisognerà inserire il classpath e il nome della classe principale della Xlet. 64 Capitolo 4: Ambiente di sviluppo Una volta che l’applicazione è stata mandata in “run”, dalla schermata di debug mostrata in figura 4.2.3, sarà possibile vedere gli eventuali errori e messaggi. Fig. 4.2.3 - Finestra di debug XletView Mentre si programma bisogna tener conto che a differenza del PC, dove abbiamo a disposizione tutte le API fornite dal JR1.4, sul Set Top Box vi è una versione ridotta della JVM , quindi bisogna fare attenzione alle specifiche sulle API che si andranno ad usare messe a disposizione su www.mhp.org e www.dvb.org, altrimenti si rischia di sviluppare applicazioni che “girano” solo su simulatore. 65 Capitolo 4: Ambiente di sviluppo 4.3 Set Top Box da sviluppo ADB X-75 Per completare il ciclo di sviluppo di una applicazione MHP, dopo che è stata sviluppata con Eclipse e mandata in esecuzione su Xletview, manca solo la fase finale di test, che come abbiamo detto consiste nel caricare l’applicazione su un Set Top Box da sviluppo. Riassumendo il nostro ambiente di sviluppo quindi sarà costituito da un Personal Computer, da una normale TV munita di presa SCART e da il Set Top Box da sviluppo ADB X-75. Le interconnessioni tra gli apparati appena elencati, come mostrato in figura 4.3.1, sono: • il STB riceve in ingresso il segnale televisivo (da una comune antenna), è collegato tramite SCART alla TV, e tramite seriale RS-232 al PC; • sia il PC che il STB sono connessi in rete (LAN) successivamente ne chiariremo il motivo. Fig. 4.3.1 - Ambiente di sviluppo 66 Capitolo 4: Ambiente di sviluppo 4.3.1 Caratteristiche Tecniche Una delle più prestigiose ditte che forniscono strumenti per lo sviluppo in campo broadcast è l’ADB, acronimo di Advanced Digital Broadcast. La serie X-75 comprende Decoder da sviluppo per tre tecnologie: Cable, Satellite, Terrestrial, in conformità con lo standard DVB, quindi è più corretto chiamare il nostro strumento ADB T.75. Andiamo ad elencare innanzitutto i componenti e le caratteristiche Hardware mostrate nella seguente tabella 4.3.1.1: ARCHITETTURA DESCRIZIONE CPU STi5517 166MHz Tuner/Front end DVB-T Flash Memory 16 MB RAM Memory 72 MB EEPROM 32 kB Power Supply 90-264 VAC, 47-63Hz Casing 440x260x50mm OUTPUTS RF input/output RF in & RF out (loop trought) Audio/Video outputs 2xRCA (Stereo Audio), 2xSCART, S/PDIF optical Return channel Ethernet 10BaseT, PSTN modem v.92 Data port Debug serial port (RS-232 up to 115.2 kbps) Front panel display 4x7-segment LED display, 2 LEDs DVB-CI slot Located on front panel Smart card slot Located on front panel ACCESSORI Power Cable 1.8 m SCART Cable 1.5 m RS 232 Cable 2.0 m 67 Capitolo 4: Ambiente di sviluppo CARATTERISTICHE DESCRIZIONE MPEG VIDEO DECODING Standards MPEG-2 MP@ML, CD ISO/IEC 13818-1, CD ISO/IEC13818-2 Video Data Rate 0.5 – 15 Mbps Format Conversion 4:3 > 16:9 with Pan & Scan and Letterbox Graphics Planes 4 planes(Background, Still-plane, Video, OSD) AUDIO DECODING Standards MPEG-1 Layer 1&2; 16 bit precision, CD ISO/IEC 13818-3 Sampling Rate 32 kHz, 44.1 kHz, 48 kHz Variable Output Level 16 steps @ 2dB per step DOLBY Digital AC3 Pass throught to S/PDIF TERRESTRIAL FRONT END – T.75 COFDM(DVB-T) ETSI EN 300 744 Modulation QPSK, QAM16, QAM64 Code rate ½, 2/3, ¾, 5/6, 7/8 Guard Interval ¼, 1/8, 1/16, 1/32 Transmission modes 2k, 8k Tabella 4.3.1.1 - Aspetti tecnici Un STB di buon livello, reperibile attualmente in commercio, presenta sicuramente le seguenti caratteristiche: • modem V.90; • lettore Smart Card; • doppia presa SCART; • uscita audio RCA; • uscita audio ottica; • connettore seriale RS-232 per eventuali periferiche di ingresso; Il nostro Set Top Box da sviluppo, oltre ad avere tutte le caratteristiche di un normale decoder, presenta: 68 Capitolo 4: Ambiente di sviluppo • una maggior capacità di elaborazione (frequenza processore più elevata) • una maggiore capacità di memorizzazione; • interfaccia Ethernet; • CI Common Interface; • presa seriale RS-232 bidirezionale (permette l’upload dell’applicazione e il debug). 4.3.2 Pannello frontale Mostriamo ora nei dettagli il pannello frontale e le funzionalità dei suoi componenti, rispettivamente in figura 4.3.2.1 e nella tabella 4.3.2.1. I componenti sono: 7 pulsanti, 2 indicatori LEDs, un display, una CI Common Interface e una OCF Smart Card slot. Fig. 4.3.2.1 - Pannello frontale ADB T.75 69 Capitolo 4: Ambiente di sviluppo Tabella 4.3.2.1 - Funzionalità pannello frontale 4.3.3 Pannello posteriore In figura 4.3.3.1 si mostra come è composto il pannello posteriore. Fig. 4.3.3.1 - Pannello posteriore ADB T.75 Vediamo in dettaglio i suoi componenti: 1. interruttore alimentazione; 2. presa alimentazione 220V ~ 50Hz; 3. connettore RS-232; 4. ingresso antenna; 5. uscita antenna (alla TV); 6. RJ11 jack modem; 7. RJ45 jack Ethernet; 8. Uscita AC3 ottica (Audio digitale); 9. SCART (connessione verso TV); 10. SCART (connessione verso eventuale VCR); 11. uscita audio RCA. 70 Capitolo 4: Ambiente di sviluppo 4.3.4 Telecomando Il telecomando e le funzionalità dei sui tasti sono mostrate in figura 4.3.4.1; non ci sono molte differenze rispetto a quello dei comuni decoder, da notare il tasto “APP” che apre e chiude la finestra delle applicazioni. Fig. 4.3.4.1 - Telecomando ADB T.75 71 Capitolo 4: Ambiente di sviluppo 4.3.5 Settaggio connessione di default Diamo uno sguardo al menù principale del decoder ADB: Tra i vari sottomenù della voce “Setting”, in fondo troviamo “Internet Connection”, una volta entrati apparirà una schermata dove è possibile scegliere se usare come predefinita la connessione tramite modem PSTN o tramite Ethernet. Internet Connection Ethernet Setting La connessione Ethernet permette di far comunicare Il STB con gli altri dispositivi della rete, a patto che al decoder sia associato un indirizzo IP; per fare questo ci sono due modi Auto e Manual . Se si imposta “Auto” dal menù, verrà assegnato un Indirizzo IP dinamico (si suppone che il server della Ethernet (Auto) rete abbia abilitato il DHCP). In caso contrario si dovrà impostare su “Manual” e Si dovranno inserire i valori: IP (statico), Mask, Default Gateway e il DNS primario e secondario. Ethrnet (Manual) 72 Capitolo 4: Ambiente di sviluppo Modem Setting Se si vuole far connettere in rete il STB, usando il Modem, bisogna impostare in questa schermata: N telefonico ISP, eventuale prefisso, tipo composizione (impulsi/tone), username, password ed eventualmente l’opzione “aspetta segnale di libero”. 4.3.6 STB Firmware upgrade Prima di passare all’upload di un’applicazione è necessario aggiornare il firmware fornito dalla casa madre. Il software che risiede nel Set Top Box (STB), consiste in due parti principali: il decoder code e il loader. Il decoder code, anche chiamato codice di alto livello, è responsabile della ricezione, codifica e visualizzazione di audio/video, e altri componenti come teletext, sottotitoli, etc. Il loader non mostra ne video ne audio, ma visualizza all’utente alcune informazioni riguardanti le fasi del processo o errori di download. L’ADB fornisce insieme al STB un software chiamato ADB FastTerm, che interagisce con il loader per aggiornare il firmware tramite la porta RS-232. L’applicazione di cui sopra, si mostra come in figura 4.3.6.1; prima di utilizzarla per prima cosa bisogna impostare il numero della porta COM (dal menù a tendina), sulla quale è connesso il STB, assicurandosi che questa sia settata con i seguenti parametri: • Baud rate • N° bit di dati • Parità • N° bit di stop 115200 8 NO 1 A questo punto, dal menù File - Open download file, si va ad aprire il file contenente l’aggiornamento (*.enc), si spegne il STB per almeno 2 secondi, si tiene premuto il tasto freccia sinistro situato sul pannello frontale e contemporaneamente si riaccende il STB. Il tasto deve continuare ad essere premuto fino a che non si accendono i LED frontali, a questo punto si lascia il pulsante e inizia il download del firmware sul STB; in questa fase la barra sulla destra dell’applicazione inizia a colorarsi fino ad arrivare al 100%. 73 Capitolo 4: Ambiente di sviluppo Fig. 4.3.6.1 - Finestra FastTerm 4.3.7 UpLoad e debug di un’applicazione La ditta produttrice del STB oltre al software “FastTerm”, fornisce anche l’ADB APPLOADER che permette di trasferire la nostra applicazione, dal PC alla memoria del decoder, sempre tramite porta seriale. In realtà la comunicazione tra i due dispositivi deve passare attraverso un secondo programma, che fa da Proxy, il quale può risiedere localmente o trovarsi in un’altra macchina, l’importante è che abbia un indirizzo IP valido e che sia connessa al STB. Quando si fa partire il Proxy, bisogna specificare il numero di porta COM (con gli eventuali settagli) dove è connesso il STB e il nome del file di log comprensivo di classpath; questo programma lavora usando il protocollo TCP sulla porta standard 4444, a meno che non specificato diversamente. Il file di log è un file di testo che viene generato dal STB ed è proprio questo che viene usato, in maniera simile al simulatore, come strumento di debug. 74 Capitolo 4: Ambiente di sviluppo La riga di comando con le varie opzioni e un’ esempio per inizializzare il Proxy sono: stbproxy.exe -com <port_number>[-port <TCP port number>] [-rate<115200,8,N,1>][-log <logfile>] stbproxy.exe –com 3 –log c:\stb.log Lavorando sotto Windows apparirà un’icona sulla barra degli strumenti, dove è possibile accedere, tra le altre cose, all’opzione “svuotare” il file di log. L’ultima cosa da fare prima di passare all’ upload, è configurare il STB, a questo ci pensa un terzo tool chiamato “stbconfig” al quale bisogna specificare l’indirizzo IP dell’host dove si trova il Proxy e l’eventuale numero di porta, se non si usa quella di default; il suo scopo è quello di abilitare/disabilitare l’opzione di debug output e security manager. La riga di comando con le varie opzioni e un’ esempio per configurare il STB ipotizzando che il Proxy sia in locale, si usi il numero di porta TCP di default e si attivi solo l’opzione di debug sono: stbconfig.exe proxy_host_IP[:port] [-debug] [-security] stbconfig.exe localhost -debug Dopo aver effettuato la configurazione del STB si richiede il riavvio dello stesso. Finalmente siamo arrivati alla fase finale, il Proxy è in running, il STB è configurato, ora manca di avviare il trasferimento dell’applicazione. Il tool stbupload richiede l’IP del proxy, il nome del description file (vedremo in seguito cosa sia) completo di classpath, e il classpath della directory base dov’è contenuta l’applicazione (*.class e file accessori). La riga di comando con le varie opzioni e un’ esempio per fare l’upload di una applicazione contenuta nella directory c:\xlet\class, supponendo il Proxy in locale con porta di default sono: stbupload <proxy_host[:port]> <xlets_descr_file> <pc_base_dir> stbupload localhost c:\xlet\xlet_desciption_file c:\xlet\class Ora l’applicazione è copiata nella directory /home del file system del STB. 75 Capitolo 4: Ambiente di sviluppo 4.3.8 Xlet description file L’Xlet description file riflette il contenuto della tabella AIT (Application Identification Table); contiene tutte le informazioni e i parametri utili per l’identificazione e l’esecuzione dell’applicazione da parte del Set Top Box. Vediamone un’ esempio: Parametri obbligatori: #app <Application ID> <Organisation ID> app 0xhex_number 0xhex_number #Application control flag: 1=autostart 2=present 3=destroy 4=kill control 1 #service bound flag (0 or 1) bound 0 #Basedir of application (must be relative to /home directory) basedir "/home" #Initial class name (fully qualified name) class "your.company.Test" Parametri opzionali: #Name of application preceded by language code name eng "Test" #Parameter of service on which the application should be visible to application manager tsid 0x7 onid 0x46 svid 0x2bd #other flags priority 137 visibility 3 #Classpath extension classpath "" #String params passed to Xlet param = "value 0" param = "value 1" 76 Capitolo 4: Ambiente di sviluppo Ora cercheremo di spiegare in dettaglio i campi principali: app Ogni organizzazione che produce Xlet, deve essere riconosciuta, ed avere un codice identificativo univoco, in più ogni applicazione prodotta da quell’ organizzazione deve anch’essa essere riconoscibile univocamente. Questo per permettere al STB di non eseguire applicazioni maligne non riconosciute. control Questo flag dice al STB come si deve comportare con l’applicazione che ha ricevuto: se = 1 la manda in esecuzione appena caricata (per avvenire effettivamente l’esecuzione, nelle impostazioni base del STB deve essere abilitata l’opzione autostart); se = 2 il STB riceve l’applicazione è la mette disponibile nell’application manager, sarà l’utente poi a decidere quando farla avviare (premendo app oppure OK, in base al modello di decoder, si visualizzano le applicazioni disponibili); se =3 viene distrutta (si usa se caricata con autostart); se =4 viene killata (si usa se caricata con present); bound Questo flag dice se si tratta di una applicazione bound (1) legata al canale e alla trasmissione o unbound (0) non legata alla trasmissione o addirittura non legata al canale (quindi disponibile indifferentemente dal canale su cui si è sintonizzati); basedir campo contenente il classpath di destinazione dell’applicazione del file system nel STB (deve essere relativo a /home); class deve contenere il nome della classe principale dell’applicazione (per es.:“main.class” ) name specifica il codice della lingua usata e il nome, relativo all’applicazione, che comparirà sull’Application Manager; priority nel caso in cui l’opzione “autostart” sia settata (nel menu di base del STB) e nell’Application Manager, vi sono più di una Xlet caricata con il control flag=1, il STB manda in esecuzione l’applicazione con la priorità più alta. 77 Capitolo 5: Implementazione di un servizio di prenotazioni Capitolo 5 Implementazione di un servizio di prenotazioni A questo punto avendo a disposizione un ambiente di sviluppo ed essendo a conoscenza delle potenzialità, dei limiti e degli standard MHP, possiamo pensare di mettere in pratica i concetti acquisiti, per implementare la nostra applicazione. Essendo il STB un dispositivo che per alcuni aspetti può essere paragonato ad un PC connesso in rete e volendo sfruttare il canale di ritorno, abbiamo dato uno sguardo alle innumerevoli applicazione che sono sul web, che svariano da semplici portali informativi fino ad arrivare a complesse applicazioni e-commerce/e-banking/e-healt. La scelta infine è stata quella di realizzare un servizio di prenotazioni “on-line” rivolto agli utenti del digitale terrestre, ovvero implementare una Xlet che si in grado di effettuare una generica prenotazione. Lo scopo di questo elaborato si basa sull’analizzare tutte le problematiche tecniche comuni che possono produrre un insieme di servizi del genere, trovarne le soluzioni ottimali e ovviamente verificarne le funzionalità. Non si è data molto importanza al tipo di prenotazione, anche se per motivi realizzativi è stato scelto un caso reale riguardante la prenotazione di “tavoli” in un generico esercizio pubblico ad esempio. Anticipiamo che l’applicazione gestirà una semplice anagrafica clienti, una fase di autenticazione (log-in) per mezzo di username e password, la visualizzazione della disponibilità del servizio, e la possibilità di prenotazione. La scelta per quanto riguarda la gestione dei dati (anagrafica e disponibilità) a favore di robustezza, sicurezza e velocità nel recupero delle informazioni è stata convergente verso l’uso di un database. 78 Capitolo 5: Implementazione di un servizio di prenotazioni 5.1 Le problematiche Le prime problematiche riscontrate riguardano l’interfaccia grafica, alcuni programmatori MHP ne paragonano la gestione a un ambiente simile a Windows “vecchio stampo” senza l’uso del mouse. La risoluzione video disponibile, infatti, per ora è ancora limitata (720x576) e non permette l’implementazione di sofisticati metodi di accesso ai menù, visto e considerato anche che per la navigazione, abbiamo solamente a disposizione il telecomando del decoder. Un altro problema si è riscontrato nell’inserimento di testo nei campi delle varie form, essendo abituati all’uso di tastiera e mouse non è stata così immediata la soluzione. Le scelte potevano essere due: creare una tastiera, simile al PC, su video, navigabile con le quattro frecce, con lo svantaggio di grosso ingombro a livello grafico e scarsa velocità d’inserimento dei caratteri; oppure quella per cui si è optato, ovvero l’implementazione di un ridotto tastierino a dodici pulsanti simile a quelli usati nei telefoni cellulari,con la possibilità di sviluppare in futuro un sistema d’inserimento simile al T9. Per quanto riguarda la gestione del canale di ritorno, ampiamente trattata nel capitolo 3, l’unico accorgimento è stato quello di differenziare i controlli (connesso/disconnesso) in base al tipo di connessione utilizzata; tramite Modem o tramite Ethernet. L’ultimo problema, e anche il più rilevante, è stato risolvere la fase di accesso a Database tramite Xlet. Considerando un database e alle modalità di accesso ai dati, tramite una piattaforma Java, viene immediatamente da pensare al potente strumento offerto da Sun, il JDBC (Java Database Connectivity). Il fatto è però, che nelle specifiche MHP e DVB, questo strumento non è nemmeno menzionato. Dopo diversi tentativi di forzare l’uso del JDBC, facendo l’upload su STB di driver meno moderni e addirittura intere API compilate con JVM simile a quella residente nel STB, si è deciso di cambiare strada. L’idea è stata quella di realizzare una applicazione Java che funge da Proxy tra il STB, quindi l’applicazione MHP, e il Database. 79 Capitolo 5: Implementazione di un servizio di prenotazioni 5.2 Interfaccia grafica L’interfaccia grafica è costituita principalmente dai seguenti elementi: uno sfondo, un menù principale costituito da tre pulsanti navigabili ciclicamente con i tasti direzionali (su/giù), una finestra orizzontale posizionata in basso dove compare una descrizione del sottomenù evidenziato come mostrato in figura 5.2.1. La gestione dello sfondo è affidata ad una specifica classe GestoreSfondi, la quale viene usata sia nella classe principale Main dal metodo startXlet(), sia ogni qualvolta servirà di ridimensionare o eliminare il video; infatti usa i metodi di BackGroundController per effettuare tutte le operazioni possibili sui primi due layer grafici. La creazione della schermata principale è affidata alla classe InterfacciaGrafica che si preoccupa di gestire per mezzo di variabili si stato, i vari componenti descritti sopra e inoltre implementa il metodo keyPressed il quale riceve l’evento tasto premuto, ne legge il codice corrispondente e in base allo stato chiama i metodi appropriati. Fig. 5.2.1 - Schermata principale 80 Capitolo 5: Implementazione di un servizio di prenotazioni Le tre scelte possibili sono: Servizi, Registrati, Prenota. Servizi serve per dare informazioni utili al cliente sull’attività dell’esercizio pubblico, registrati permette di registrarsi al servizio inserendo nome, cognome, numero di telefono, username, password; prenota permette di effettuare il log-in, visualizzare la disponibilità dei tavoli (verde libero, rosso occupato) e l’eventuale prenotazione. In questa fase si usa un delle caratteristiche principali di Java, il polimorfismo, infatti ogni opzione delle tre è implementata da una classe omonima Servizi, Registra, Prenota e tutte estendono la classe Strumenti. Nella classe Strumenti infatti sono contenuti i metodi comuni a tutte le opzioni come init, stop, mentre il metodo start e implementato diversamente per ogni strumento. Strumenti Init() Stop() Servizi Registra Prenota Start() Start() Start() Il metodo init() provvede ad attivare il tastierino, stop() lo rimuove, start() in base al servizio crea i campi per l’inserimento dati della corrispettiva form. L’implementazione della tastiera è contenuta nelle classi KeyPad e KeypadButton mentre i campi di testo da EditableTextBox. Sia entrando nel menù “Registrati” (figura 5.2.2) che “Prenotazioni” è possibile scorrere ciclicamente i campi di testo con le frecce (su/giù), premendo “ok” si inizia la compilazione del campo evidenziato, per confermare tutti i campi inseriti bisogna premere il tasto rosso, mentre per annullare l’inserimento e tornare al menù principale quello blu. In caso di username o password errati (figura 5.2.3) o già esistenti (nel caso di prima registrazione) comparirà un messaggio informativo per l’utente, realizzato dal metodo messFlash; per continuare si dovrà premere il tasto verde. 81 Capitolo 5: Implementazione di un servizio di prenotazioni Fig. 5.2.2 - Form Registrati Fig. 5.2.3 - Form Prenotazioni 82 Capitolo 5: Implementazione di un servizio di prenotazioni Una volta effettuato correttamente il log-in viene visualizzata la disponibilità dei tavoli, (usando il metodo visualizzaDisp nella classe Prenota) come mostrato nella figura 5.2.4; ora con le frecce direzionali (destra/sinistra) è possibile selezionare il tavolo desiderato, e premendo “ok” prenotarlo, sempre se non sia già occupato (colore rosso). Fig. 5.2.4 Disponibilità tavoli Per quanto riguarda la parte grafica c’è da aggiungere che si è cercato di mantenere sempre presente il video layer opportunamente ridimensionato e posizionato, ad esclusione delle fasi di inserimento dati. Il codice di questa parte di applicazione è disponibile nell’appendice A suddivisa per classi. 83 Capitolo 5: Implementazione di un servizio di prenotazioni 5.3 Accesso ai dati Come accennato all’inizio del capitolo per la gestione dei dati si usa un DBMS Mysql scaricabile dal sito www.mysql.com. Nel nostro ambiente di sviluppo sia il Database che l’applicazione Proxy risiedevano sullo stesso PC (con Java 2 Platform Standard Edition ver. 1.4.2) connesso in rete LAN; sono state anche effettuate prove lavorando con DB e Proxy remoti. La comunicazione tra Xlet e Proxy avviene tramite Socket utilizzando le API java.net e java.io, mentre tra Proxy e Database si sfruttano appieno le potenzialità del JDBC. Xlet Proxy Socket DBMS JDBC Le potenzialità di adottare questo meccanismo sono molteplici: • la comunicazione tra xlet e proxy è più sicura in quanto le socket supportano il TSL e SSL; • la Xlet fungerà solo da interfaccia grafica e penserà a gestire la connessione con il Proxy; • la Xlet in questo modo viene alleggerita dalle funzioni d’interrogazione database, può richiedere complicate query con pochi byte comunicando con il Proxy, al quale viene lasciato tutto il lavoro (mediante messaggi standard o codici proprietari comuni a entrambi); • il Proxy risiederà su un server con adeguate caratteristiche tecniche è avrà a disposizione tutte le API comprese java.sql e com.java.jdbc; • il Proxy se implementato con un sistema multithread sarà capace di gestire più richieste contemporanee e l’accesso dei dati in concorrenza; • pensando a un centro servizi dove lo stesso Database viene usato da diverse utenze:operatori che accedono da terminali remoti, utenti via web, utenti del digitale terrestre; il Proxy può fungere da interfaccia per garantire la coerenza dei dati. 84 Capitolo 5: Implementazione di un servizio di prenotazioni 5.3.1 Socket Una socket rappresenta il terminale (end-point) di un canale di comunicazione bidirezionale. Permette a due processi, residenti sulla stessa macchina o anche molto distanti, di comunicare fra loro nello stesso modo. Si basano tipicamente sul modello client/server: • il servitore deve stare in attesa di possibili comunicazioni in arrivo (ente passivo), nel nostro caso è il Proxy • i clienti (anche più di uno), nel nostro caso le Xlet, quando vogliono, comunicano con il servitore (sono enti attivi). Il protocollo usato è il TCP, il servitore crea la sua ServerSocket con un numero noto di porta e si mette in attesa. Un cliente, quando vuole comunicare col servitore, crea la sua Socket specificando con chi vuole parlare: • nome dell’host • numero di porta Questo fa scattare il protocollo di 3-way handshaking per la connessione. Il servitore accetta la richiesta dal cliente: con ciò si stabilisce una connessione e il server crea una nuova Socket già collegata al cliente, tramite la quale i due comunicano. A questo punto i processi possono parlare senza più bisogno di specificare ogni volta con chi si vuol parlare (come nelle telefonate: una volta presa la linea, tutti i segnali transitano automaticamente tra le due parti) come mostrato in figura 5.3.1.1. Fig. 5.3.1.1 - Comunicazione via Socket stream 85 Capitolo 5: Implementazione di un servizio di prenotazioni Vediamo la classe Socket nel package java.net. Alla Socket sono associati due stream: 1. uno dal cliente verso il servitore 2. uno dal servitore verso il cliente. La comunicazione cliente/servitore è bidirezionale. I ruoli “cliente” e “servitore” sono tali solo nella fase iniziale, quando si instaura la connessione. Una volta connessi, i due processi si parlano reciprocamente “alla pari”. Costruire una Socket significa aprire la comunicazione verso l’altra parte; “public Socket(String remoteHost, int remotePort)” crea una socket stream e la collega alla porta specificata della macchina remota corrispondente al nome dato. ESEMPIO Socket s = new Socket(“univpm.it”,13); oppure String str = “prova.com”; int port = 14; Socket s = new Socket(ss,port); Non bisogna specificare il nome dell’host locale, è implicito. Per il client, non bisogna specificare il numero della porta (non deve essere noto a priori). Il sistema assegna automaticamente un numero di porta tra quelli liberi. Il servizio di DNS è invocato automaticamente. Si può fare anche: “public Socket(InetAddress remoteAddr, int remotePort)” Crea una socket stream e la collega alla porta specificata della macchina remota corrispondente all’indirizzo IP dato. Vediamo ora la classe InitAddress del package java.net. Gli oggetti della classe InetAddress rappresentano indirizzi internet (IP). La classe InetAddress fornisce dei metodi per gestire facilmente gli indirizzi Internet. • public static InetAddress getByName(String host) - determina l’indirizzo IP di host. 86 Capitolo 5: Implementazione di un servizio di prenotazioni ESEMPI InetAddress.getByName(null); InetAddress.getByName(“localhost”); InetAddress.getByName(“127.0.0.1”); Una volta creata la socket per comunicare: si recuperano dalla socket i due stream (di ingresso e di uscita) tramite i metodi della classe Socket: • public InputStream getInputStream() - restituisce lo stream di input da cui leggere i dati (byte) che giungono dall’altra parte. • public OutputStream getOutputStream() - restituisce lo stream di output su cui scrivere i dati (byte) da inviare all’altra parte. Quindi su questi stream si può leggere / scrivere come su qualunque altro stream di byte. Per chiudere la comunicazione: si chiude la socket col metodo: • public synchronized void close() - chiude la connessione e libera la risorsa. Di seguito riportiamo un esempio di un semplice client e di un server multithread: 87 Capitolo 5: Implementazione di un servizio di prenotazioni //Simple Client import java.net.*; import java.io.*; public class SimpleClient { public static void main(String args[]) { try { // Apre una connessione verso un server in ascolto sulla porta 7777. In questo caso utilizziamo localhost // che corrisponde all'indirizzo IP 127.0.0.1 System.out.println("Apertura connessione..."); Socket s1 = new Socket ("127.0.0.1", 7777); // Ricava lo stream di input dal socket s1 ed utilizza un oggetto wrapper di classe BufferedReader // per semplificare le operazioni di lettura InputStream is = s1.getInputStream(); BufferedReader dis = new BufferedReader(new InputStreamReader(is)); // Legge l'input e lo visualizza sullo schermo System.out.println("Risposta del server: " + dis.readLine()); // Al termine, chiude lo stream di comunicazione e il socket. dis.close(); s1.close(); System.out.println("Chiusura connessione effettuata"); }catch (ConnectException connExc){ System.err.println("Errore nella connessione "); }catch (IOException ex){ ex.printStackTrace(); } } } //Server multithread import java.net.*; import java.io.*; public class SimpleServer { private int port; private ServerSocket server; private Socket client; public SimpleServer (int port) { this.port = port; if(!startServer()) System.err.println("Errore durate la creazione del Server"); } private boolean startServer(){ try{ server = new ServerSocket(port); }catch (IOException ex){ ex.printStackTrace(); return false; } System.out.println("Server creato con successo!"); return true; } 88 Capitolo 5: Implementazione di un servizio di prenotazioni public void runServer() { while (true){ try{ // Il server resta in attesa di una richiesta System.out.println("Server in attesa di richieste..."); client = server.accept(); System.out.println("Un client si e' connesso..."); ParallelServer pServer = new ParallelServer(client); Thread t = new Thread (pServer); t.start(); }catch (IOException ex){ ex.printStackTrace(); } } } public class ParallelServer implements Runnable { private Socket client; public ParallelServer (Socket client) { this.client = client; } public void run(){ try{ // Ricava lo stream di output associate al socket e definisce una classe wrapper di tipo // BufferedWriter per semplificare le operazioni di scrittura OutputStream s1out = client.getOutputStream(); BufferedWriter bw = new BufferedWriter( new OutputStreamWriter(s1out)); // Il server invia la risposta al client bw.write("Benvenuto sul server!\n"); // Chiude lo stream di output e la connessione bw.close(); client.close(); System.out.println("Chiusura connessione effettuata"); }catch (IOException ex){ ex.printStackTrace(); }catch (Exception e){ e.printStackTrace(); } } } public static void main (String args[]) { SimpleServer ss = new SimpleServer(7777); ss.runServer(); } } 89 Capitolo 5: Implementazione di un servizio di prenotazioni La Xlet con il Proxy si scambiano oggetti usando gli input e output stream. Gli oggetti sono di tre tipi, implementati dalle classi: ObjectPrenotation, ObjectRegistration, ObjectTavoli. Ogni oggetto contiene la struttura dati che rappresenta, coerenti con le relative tabelle del database, dove sono realmente memorizzati, e i metodi per renderli disponibili all’applicazione. Il codice della Xlet (lato client) è contenuto nell’appendice A le classi di interesse sono Comunica che ha come parametri IP del Proxy e numero della porta, PrenotaSingolo. Il codice dell’applicazione Proxy (lato server), nel nostro caso si usa un singolo thread, è contenuto nell’appendice B, dove si vede che oltre alla gestione della socket gestisce anche il recupero dati dal DB e l’invio degli stessi alla Xlet. La classe di interesse è SimpleServer; in più vi sono i .java dei tre oggetti di cui sopra. 90 Capitolo 5: Implementazione di un servizio di prenotazioni 5.3.2 JDBC Per effettuare l’interrogazione del Database da parte del nostro Proxy si usa il JDBC (Java Database Connectivity), è la parte delle API di J2SE che fornisce le primitive per la connessione a basi di dati relazionali e si basa su due semplici concetti: • si inviano comandi SQL; • si recuperano i risultati dei comandi. Il package a cui faremo riferimento è java.sql. Ogni DB ha una sua API e un suo protocollo (implementato dal driver) particolare. JDBC astrae dalle particolarità a basso livello delle API e dei protocolli, fornendo un’interfaccia comune. I driver sottostanti si preoccupano poi di effettuare la traduzione dei comandi SQL nelle interfacce native dei database supportati come mostrato in figura 5.3.2.1. L’accesso al database può essere locale o remoto. Fig. 5.3.2.1 - Schema JDBC 91 Capitolo 5: Implementazione di un servizio di prenotazioni I driver JDBC sono divisi in 4 Classi: • Classe 1: sono i driver JDBC-ODBC. • Classe 2: sono i driver che si aspettano di trovare sulla macchina su cui sono utilizzati uno strato di software, scritto in linguaggio nativo per quella Macchina/Piattaforma, i quali si preoccupano di connettersi e di operare sul Database. (nel caso di Oracle i driver in questione vengono chiamati OCI, Oracle callback Interface, e vogliono che sulla macchina su cui girano ci sia installato SQL-Net) • Classe 3: sono i driver che si aspettano di trovare un Gateway, o in generale un server a cui connettersi il quale provvederà a ritornare una connessione dal Database. (i JDBC-RMI driver sono di classe 3) • Classe 4: sono i driver totalmente scritti in Java ed autonomi. (Nel caso di Oracle vengono chiamati THIN). Vediamo le procedure principali e i metodi che le implementano per effettuare la connessione ed utilizzare un database: •Caricare il gestore di driver DriverManager •Aprire la connessione Connection •Creare un oggetto istruzione Statement •Eseguire l’interrogazione (query) executeQuery •Elaborare i risultati ResultSet •Chiudere la connessione close Drive manager implementa un gestore di driver. Il metodo (statico) per ottenere una connessione al DB è:staticConnectiongetConnection(String,String,String) che prende tre argomenti: URL, username, password. Sintassi dell’URL:–jdbc:mysql://<host>[:<port>]/<dbname> Siccome le connessioni occupano risorse, in un ambiente multiutente e multitasking è opportuno adottare la seguente politica: aprire una connessione solo quando necessario, assicurarsi di chiuderla, non aprire/chiudere connessioni inutilmente. 92 Capitolo 5: Implementazione di un servizio di prenotazioni Connection rappresenta una connessione al DB. Prima di fare qualunque cosa con un db, devo stabilire una connessione. Ad esempio, per creare un comando, devo disporre di una connessione (c): Statement s = c.createStatement(). Per chiudere la connessione, si utilizza il metodo close(). Statement rappresenta un’istruzione/comando. Ha i metodi sia per eseguire un’interrogazione (query) SQL che restituisca un insieme di dati che per eseguire una query di aggiornamento/modifica del DB: ResultSet executeQuery(String) int executeUpdate(String). Quando non serve più va chiuso con il metodo: close ResultSet rappresenta il risultato di un’interrogazione. Tramite il metodo next() possiamo spostarci da un record al successivo: boolean next(). Abbiamo a disposizione una serie di metodi per recuperare i dati memorizzati nei vari campi di ogni record, in base al tipo di dato originario del DB:String getString(int)[gli indici partono da 1!], getXxx()[Byte,Boolean, Blob, …]. E’ buona norma chiudere il ResultSet quando non serve più con il metodo: close. I Driver usati nel Proxy sono “mysql-connector-java-3.1.12”, di clase 4, da ricordare di importare l’omonimo .jar nel progetto. Il codice riguardante l’uso dei JDBC nel nostro Proxy è riportato nell’appendice B; le classi d’interesse sono: ServerPrenotaSingolo, ServerPrenotation, ServerRegistration. 93 Capitolo 5: Implementazione di un servizio di prenotazioni 5.3.3 Database Il DBMS usato in questo progetto per la precisione è MySQL Server versione 5.0. Le operazioni svolte direttamente su DB sono solamente la creazione di due tabelle: una chiamata “cliente” contenente una ridotta anagrafica dell’utente che si andrà a registrare e l’altra “tavoli” che serve per memorizzare la disponibilità corrente del servizio; contenente un codice identificativo del tavolo, lo username dell’utente che lo andrà a prenotare e un campo flag (libero/occupato). Per completezza riportiamo il DLL: CREATE TABLE `cliente` ( `user` char(20) NOT NULL default '', `password` char(20) NOT NULL default '', `nome` char(20) default NULL, `cognome` char(20) default NULL, `telefono` char(15) default NULL, PRIMARY KEY (`user`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; CREATE TABLE `tavoli` ( `num_tavolo` int(3) NOT NULL default '0', `utente` char(20) default NULL, `libero` int(1) default NULL, PRIMARY KEY (`num_tavolo`), UNIQUE KEY `IDtav` USING BTREE (`num_tavolo`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 94 Capitolo 5: Implementazione di un servizio di prenotazioni 5.4 Sviluppi futuri All’inizio del capitolo si è anticipato che il lavoro svolto è stato basato più sull’ ingegnerizzazione del software per permettere di effettuare una prenotazione “on-line” generica da Xlet, che sul servizio in sé. Il progetto, così come è descritto nei paragrafi precedenti, è stato implementato e testato su Set Top Box da sviluppo ed è risultato essere funzionante. Gli sviluppi possibili, partendo da questa piattaforma, sono innanzitutto basare l’attenzione su un servizio specifico e quindi curare di più la parte grafica e fare un’analisi dei dati ad hoc; dal lato tecnico si potrà sviluppare una procedura per velocizzare l’inserimento dati nei campi di testo, implementare il Proxy in multithread per gestire l’accesso in concorrenza di più utenti e magari implementare l’uso di lettore smart card da usare per l’identificazione dell’utente tramite carta d’identità elettronica per esempio. 95 Conclusioni Conclusioni Attualmente l'Italia, come raramente accade per il nostro paese si trova in una posizione di avanguardia e per molti aspetti sta guidando a livello europeo questa rivoluzione, soprattutto in ambito Televisione Digitale Terrestre. In commercio possiamo trovare diverse marche di decoder interattivi MHP e tutti i principali broadcaster sono impegnati a scoprire le potenzialità, i limiti e le possibili fonti di guadagno di questa nuova tecnologia. Difficile fare pronostici soprattutto in merito alla legge del nostro parlamento che sancisce entro dicembre 2008 il cosiddetto "switch-off" delle trasmissioni analogiche per passare esclusivamente al sistema digitale. Difficile anche capire se un maggior numero di canali implicherà una maggior scelta e auspicabilmente maggior qualità per noi telespettatori. Le certezze maggiori rimangono quindi quelle tecnologiche legate ad uno standard europeo aperto che mira alla convergenza del mondo terrestre, cavo e satellite, dove invece oggi regnano sovrane troppe tecnologie e standard proprietari. Siamo di fronte ad una tecnologia in grado di abilitare una delle più profonde trasformazioni che si possa pensare in una società fortemente fondata sull'immagine e sulla comunicazione come è la nostra. Un trasformazione che con ogni probabilità non si limiterà ad influenzare l'ambiente casalingo, ma si estenderà rapidamente anche ai terminali mobili e tascabili, il DVB-H e le sue applicazioni. 96 APPENDICE A: Codice Xlet APPENDICE A Codice Xlet Main.java import javax.tv.xlet.Xlet; import javax.tv.xlet.XletContext; import javax.tv.xlet.XletStateChangeException; import org.havi.ui.*; import org.havi.ui.event.*; import java.awt.event.*; //Classe iniziale è questa che implementa l'interfaccia Xlet e KeyListener e che quindi sarà //utilizzata dall'application manager per gestire il //ciclo di vita della xlet. Si occupa di inizializzare //i vari componenti, ascoltare gli eventi dal telecomando, //creare l'oggetto scene fondamentale per la parte grafica. public class Main implements Xlet, KeyListener { public XletContext context; public static GestoreSfondi gestoreSfondi; public HScene scene; public int tastoPremuto = 0; private InterfacciaGrafica interfaccia; //Costruttore vuoto. public Main() { } // Definisco un metodo che inizializza la Xlet, e i suoi componenti. public void initXlet(XletContext xletContext) throws XletStateChangeException { //xletContext utilizzato per la gestione del ciclo di vita della xlet context = xletContext; HSceneFactory hsceneFactory = HSceneFactory.getInstance(); //estraggo l'oggetto scene su cui si appoggiano i componenti grafici. scene = hsceneFactory.getFullScreenScene(HScreen.getDefaultHScreen().getDefaultHGraphicsDevice()); //le dimensioni sono quelle standard di risoluzione di un televisore analogico. scene.setSize(720, 576); scene.setVisible(false); scene.setLayout(null); //aggiungo l'ascoltatore degli eventi in questo caso //la classe stessa. scene.addKeyListener((KeyListener)this); } // Definisco il metodo che permette di mandare in esecuzione la Xlet public void startXlet() throws XletStateChangeException { System.out.println("Eseguo startXlet"); gestoreSfondi = new GestoreSfondi(context); interfaccia = new InterfacciaGrafica(scene); 97 APPENDICE A: Codice Xlet // Imposto lo sfondo e ridimensiono il video gestoreSfondi.displayBackground("background.mpg",240,100,420,320); // Inizializzo l'interfaccia grafica interfaccia.disegnaInit(); // Richiedo il Focus per la scene corrente scene.requestFocus(); scene.setVisible(true); } // Definisco il metodo per mettere in pausa la Xlet public void pauseXlet() { System.out.println("Xlet in pausa"); context.notifyPaused(); } // Definisco il metodo che distrugge la Xlet e rilascia le risorse. public void destroyXlet(boolean flag) throws XletStateChangeException { if(flag){ System.out.println("Distruggi Xlet"); /* try{ RCconnector.Disconnect(); }catch (IOException e){ e.printStackTrace(); } */ interfaccia.distruggi(); //rimuovo l'ascoltatore degli eventi. scene.removeKeyListener(this); scene.removeKeyListener((KeyListener)this); scene.setVisible(false); HSceneFactory.getInstance().dispose(scene); scene = null; gestoreSfondi.displayBackgroundExit(); context.notifyDestroyed(); } } // Ascoltatore degli eventi legati al telecomando. Gli eventi vengono //passati alla classe InterfacciaGrafica che poi li passerà alle //funzioni attive. public void keyPressed (KeyEvent key) { int pulsantePremuto = key.getKeyCode(); switch(pulsantePremuto){ //Tastierino numerico case KeyEvent.VK_0: case KeyEvent.VK_1: case KeyEvent.VK_2: case KeyEvent.VK_3: case KeyEvent.VK_4: case KeyEvent.VK_5: case KeyEvent.VK_6: case KeyEvent.VK_7: case KeyEvent.VK_8: case KeyEvent.VK_9: 98 APPENDICE A: Codice Xlet //Tasti colorati case HRcEvent.VK_COLORED_KEY_0: //rosso case HRcEvent.VK_COLORED_KEY_1: //verde case HRcEvent.VK_COLORED_KEY_2: //giallo case HRcEvent.VK_COLORED_KEY_3: //blu //Tasti direzionali case HRcEvent.VK_UP: case HRcEvent.VK_DOWN: case HRcEvent.VK_RIGHT: case HRcEvent.VK_LEFT: case HRcEvent.VK_ENTER: //OK //Vengono passati gli eventi. interfaccia.keyPressed(key); break; //Alla pressione del tasto EXIT del telecomando si //richiama il metodo destroyXlet, per fermare la xlet. case HRcEvent.VK_ESCAPE: try{ destroyXlet(true); } catch (XletStateChangeException xsce){ System.out.println("Premuto tasto EXIT "+xsce.toString()); } break; default: break; } } public void keyTyped(KeyEvent ignored) { } public void keyReleased(KeyEvent ignored) { } } InterfacciaGrafica.java import java.awt.*; import java.awt.event.*; //import java.io.IOException; //import javax.tv.xlet.XletStateChangeException; import org.havi.ui.*; import org.havi.ui.event.HRcEvent; //import javax.tv.xlet.XletContext; //Questa classe amministra la parte grafica del frame //principale gestendo l'accesso alle varie funzionalità e //passando gli eventi ai processi attivi. public class InterfacciaGrafica { public static final int MENU=0; public static final int PRIMO=1; public static final int DISP=2; public static final int POPUP=3; public static final int INIT=4; 99 APPENDICE A: Codice Xlet public static int STATO; private HScene scene; private HIcon [] vetON,vetOFF; private HText [] mess; private String como; private String [] str; private HIcon tastor; private GestoreSfondi gestoreSfondi; private Registra registra; private Servizi servizi; private Prenota prenota; private int tasto=0; //Utilizzata per il polimorfismo. private Strumenti strumento=null; //Costruttore. public InterfacciaGrafica(HScene scenemain){ //viene passato l'oggetto scene su cui verranno //aggiunti i vari componenti grafici. scene=scenemain; //STATO=INIT; } //Crea i componenti grafici e li visualizza. public void disegnaInit(){ STATO=MENU; Font font1=new Font("Arial",Font.BOLD,30); str =new String[3]; str[0]="Questo è un servizio di prenotazioni MHP"; str[1]="Inserisci tutti i campi richiesti"; str[2]="Fai il login ed effettua la prenotazione"; como=str[1]; mess= new HText[3]; for(int i=0;i<3;i++){ mess[i]=new HText(str[i]); mess[i].setFont(font1); mess[i].setForeground(Color.white); mess[i].setBackgroundMode(HVisible.BACKGROUND_FILL); mess[i].setBackground(new Color(100,100,100,120)); mess[i].setBounds(0,456,720,100); mess[i].setBordersEnabled(true); scene.add(mess[i]); mess[i].setVisible(false); } System.out.println("Entrato in disegnainit:mess ok"); vetON = new HIcon[3]; vetOFF = new HIcon[3]; vetOFF[0]=new HIcon(Toolkit.getDefaultToolkit().getImage("servizi_off.jpg"), 50, 100, 140, 30); vetOFF[1] = new HIcon(Toolkit.getDefaultToolkit().getImage("registra_off.jpg"), 50, 135, 140, 30); vetOFF[2] = new HIcon(Toolkit.getDefaultToolkit().getImage("prenota_off.jpg"), 50, 170, 140, 30); vetON[0] = new HIcon(Toolkit.getDefaultToolkit().getImage("servizi_on.jpg"), 50, 100, 140, 30); vetON[1] = new HIcon(Toolkit.getDefaultToolkit().getImage("registra_on.jpg"), 50, 135, 140, 30); vetON[2] = new HIcon(Toolkit.getDefaultToolkit().getImage("prenota_on.jpg"), 50, 170, 140, 30); for(int i=0;i<3;i++){ scene.add(vetON[i]); scene.add(vetOFF[i]); 100 APPENDICE A: Codice Xlet vetOFF[i].setVisible(true); vetON[i].setVisible(false); } System.out.println("Entrato in disegnainit:icone ok"); //Istanzio gli oggetti che implementano le funzioni //della xlet. prenota=new Prenota(scene); System.out.println("pre ok"); servizi=new Servizi(scene); System.out.println("serv ok"); registra=new Registra(scene); System.out.println("Entrato in disegnainit:strumenti ok"); accendi(tasto); scene.setVisible(true); scene.requestFocus(); System.out.println("fine disegnainit"); } //Gestione grafica dei pulsanti public void accendi (int numero){ vetOFF[numero].setVisible(false); vetON[numero].setVisible(true); mess[numero].setVisible(true); scene.repaint(); } public void spegni (int numero){ vetOFF[numero].setVisible(true); vetON[numero].setVisible(false); mess[numero].setVisible(false); scene.repaint(); } //Viene oscurata la parte grafica e //si ferma il processo attivo. //Richiamato del metodo destroyXlet nel momento di //distruzione della xlet. public void distruggi(){ this.scene.setVisible(false); //strumento.stop(); } public static void premutoEnter(){ } //Ascoltatore degli eventi del telecomando. //La gestione delle funzionalità è stata fatta utilizzando le //interfacce Java questo permette di gestire i processi in maniera dinamica, //passando gli eventi solo al processo attivo. public void keyPressed (KeyEvent key) { int pulsantePremuto = key.getKeyCode(); switch(pulsantePremuto){ case KeyEvent.VK_0: 101 APPENDICE A: Codice Xlet case KeyEvent.VK_1: case KeyEvent.VK_2: case KeyEvent.VK_3: case KeyEvent.VK_4: case KeyEvent.VK_5: case KeyEvent.VK_6: case KeyEvent.VK_7: case KeyEvent.VK_8: case KeyEvent.VK_9: case HRcEvent.VK_COLORED_KEY_0: System.out.println("Rosso"); if(STATO==POPUP) break; if(STATO==INIT){ System.out.println("Passa a menu"); STATO=MENU; disegnaInit(); break; } case HRcEvent.VK_COLORED_KEY_1: if(STATO==POPUP){ Strumenti.messFlashOff(); break; } case HRcEvent.VK_COLORED_KEY_2: if(STATO==POPUP) break; if(STATO==PRIMO){ strumento.passaEvento(key); break; } case HRcEvent.VK_COLORED_KEY_3: if(STATO==POPUP) break; if(STATO==PRIMO){ strumento.Stop(); mess[tasto].setVisible(true); //Main.gestoreSfondi.displayBackgroundExit(); Main.gestoreSfondi.displayBackground("background.mpg",240,100,420,320); scene.repaint(); STATO=MENU; break; } if(STATO==DISP){ Prenota.stopVisu(); mess[tasto].setVisible(true); //Main.gestoreSfondi.displayBackgroundExit(); Main.gestoreSfondi.displayBackground("background.mpg",240,100,420,320); scene.repaint(); STATO=MENU; break; } case HRcEvent.VK_RIGHT: if(STATO==POPUP) break; System.out.println("stato init"+InterfacciaGrafica.STATO); System.out.println("Init:Destro"); 102 APPENDICE A: Codice Xlet if(STATO==DISP){ strumento.passaEvento(key); break; } case HRcEvent.VK_LEFT: if(STATO==POPUP) break; if(STATO==DISP){ strumento.passaEvento(key); break; } if(STATO==MENU)break; case HRcEvent.VK_UP: if(STATO==POPUP) break; if(STATO==MENU){ spegni(tasto); if(tasto==0) tasto=2; else tasto--; accendi(tasto); break; } case HRcEvent.VK_DOWN: if(STATO==POPUP) break; if(STATO==MENU){ spegni(tasto); if(tasto==2)tasto=0; else tasto++; accendi(tasto); break; } case HRcEvent.VK_ENTER: if(STATO==POPUP) break; if(STATO==MENU){ if(tasto==0)break; mess[tasto].setVisible(false); Main.gestoreSfondi.displayBackgroundExit(); Main.gestoreSfondi.displayBackground("background.mpg",0,0,0,0); scene.repaint(); /*if(tasto==0){ strumento=servizi; }*/ if(tasto==1){ strumento=registra; } if(tasto==2){ strumento=prenota; } strumento.init(); strumento.Start(); break; } strumento.passaEvento(key); default: break; } } 103 APPENDICE A: Codice Xlet } GestoreSfondi.java import javax.tv.xlet.XletContext; //Gestisce gli sfondi della xlet. //Utilizzato dalla classe principale "Main". I metodi si appoggiano alla //classe BackgroundController per modificare l'immagine in background. public class GestoreSfondi { //Context della xlet in esecuzione. private XletContext xContext; private BackgroundController backgroundManager; public GestoreSfondi(XletContext context){ backgroundManager = new BackgroundController(); xContext=context; } // Carica l'immagine di sfondo della xlet, oscurando il video. public void displayBackground(String back) { if (backgroundManager.init()) { backgroundManager.hideVideo(xContext); backgroundManager.display(back); } } // Carica l'immagine di sfondo ridimensionando il video. public void displayBackground(String back,int x,int y,int l,int h) { if (backgroundManager.init()) { backgroundManager.resizeVideo(xContext,x,y,l,h); backgroundManager.display(back); } } // Rilascia la risorsa Background. public void displayBackgroundExit() { backgroundManager.dispose(); } } Keypad.java import java.awt.*; import java.awt.event.*; public class Keypad extends Container implements Runnable { private class NewKey { private int nk = -1; public NewKey() { } public void set(int i) { nk = i; } public int get() { return nk; } } 104 APPENDICE A: Codice Xlet // Keypad size constants, don't change. private static final int width = 500; private static final int height = 237; private String editedText = null; private char pendingChar = 0; // Used for keypad output, set by setOutput method private EditableTextBox textBox = null; // Images of buttons private Image redButtonImage = null; private Image blueButtonImage = null; private Image okButtonImage = null; public Keypad() { setSize(width,height); // Load button images redButtonImage = Toolkit.getDefaultToolkit().getImage("Rosso_Off.gif"); blueButtonImage = Toolkit.getDefaultToolkit().getImage("Blu_Off.gif"); // Create numeric buttons and layout buttons = new KeypadButton[10]; buttons[0] = new KeypadButton(0,"0_-"); buttons[1] = new KeypadButton(1,"1@,;."); buttons[2] = new KeypadButton(2,"abc2äå"); buttons[3] = new KeypadButton(3,"def3"); buttons[4] = new KeypadButton(4,"ghi4"); buttons[5] = new KeypadButton(5,"jkl5"); buttons[6] = new KeypadButton(6,"mno6ö"); buttons[7] = new KeypadButton(7,"pqrs7"); buttons[8] = new KeypadButton(8,"tuv8"); buttons[9] = new KeypadButton(9,"wxyz9"); buttons[1].setLocation(259,5); buttons[2].setLocation(339,5); buttons[3].setLocation(419,5); buttons[4].setLocation(259,62); buttons[5].setLocation(339,62); buttons[6].setLocation(419,62); buttons[7].setLocation(259,119); buttons[8].setLocation(339,119); buttons[9].setLocation(419,119); buttons[0].setLocation(339,176); for (int i=0;i<10;i++) { buttons[i].setVisible(true); add(buttons[i]); } newKey = new NewKey(); } public void paint(Graphics g) { // Paint keypad border //g.setColor(Color.black); //g.fillRect(254,0,245,height); // Paint keypad background g.setColor(new Color(100,100,100,120)); g.fillRect(0,0,width,height); //g.fillRect(256,2,241,height-4); // Paint backspace button 105 APPENDICE A: Codice Xlet //g.drawImage(redButtonImage,10,40,this); g.setColor(Color.white); g.setFont(new Font("Tireasias",Font.PLAIN,23)); g.drawString("Su/Giù :Seleziona campo",10,20); g.drawString("OK :Inserimento",10,50); g.drawString("< :Backspace",10,80); g.drawString("> :Space",10,110); g.setFont(new Font("Tireasias",Font.BOLD,23)); g.setColor(Color.red); g.drawString("CONFERMA DATI",10,140); g.setColor(Color.blue); g.drawString("ANNULLA",10,170); // Paint space button //g.drawImage(blueButtonImage,10,90,this); //g.drawString("= space",90,121); // Paint components (keys) super.paint(g); } // activeChar is the character in editText which is edited at the moment int activeChar = 0; private int numKeyActive = -1; // -1 = no key private char chosenChar = 0; // 0 = no character private int timer = 0; boolean backSpaceSet = false; // backspace has been pressed? boolean spaceSet = false; // space has been pressed? private NewKey newKey = null; private KeypadButton[] buttons; // Main loop of the thread. public void run() { int i=0; while (true) { // Sleep for a while so that the CPU won't be too busy. try { Thread.sleep(100); } catch (Exception e) { System.out.println(e); } // newKey is shared by two thread, allow only one access at a time synchronized (newKey) { if (newKey.get() != -1) { if (numKeyActive == -1) { // New key pressed numKeyActive = newKey.get(); buttons[numKeyActive].press(); textBox.setPending(buttons[numKeyActive].getChar()); newKey.set(-1); } else { if (numKeyActive == newKey.get()) { // Old key re-pressed. buttons[numKeyActive].press(); textBox.setPending(buttons[numKeyActive].getChar()); newKey.set(-1); } else { // New key pressed while old still active 106 APPENDICE A: Codice Xlet System.out.println("Chosen: "+buttons[numKeyActive].getChar()); textBox.addPending(); buttons[numKeyActive].unpress(); numKeyActive = newKey.get(); buttons[numKeyActive].press(); textBox.setPending(buttons[numKeyActive].getChar()); newKey.set(-1); } } // Every key press sets the timer to 10 timer = 10; } else { // If no key press, the timer is decremented. timer--; } if (timer==0) { // If timer reaches 0 it means that it has gone 10x100ms=1 second since // the active key was pressed. The active key is now added to the edited // string (in the text box). System.out.println("Chosen: "+buttons[numKeyActive].getChar()); textBox.addPending(); buttons[numKeyActive].unpress(); numKeyActive = -1; } // If a key is active, backspace always inactivates it. if (backSpaceSet) { textBox.backSpace(); if (numKeyActive != -1) { buttons[numKeyActive].unpress(); numKeyActive = -1; } timer = -1; backSpaceSet = false; } // A space is always added to the edited string. If a key is active // when space is pressed, it get inactivated. if (spaceSet) { textBox.space(); if (numKeyActive != -1) { buttons[numKeyActive].unpress(); numKeyActive = -1; } timer = -1; spaceSet = false; } } } } // Acknowledge key press. public void keyPressed (KeyEvent key) { int code = key.getKeyCode(); if ((code >= KeyEvent.VK_0) && (code <= KeyEvent.VK_9)) { // newKey can also be changed by the run method. System.out.println("keyPad.keyevent"+code); synchronized (newKey) { 107 APPENDICE A: Codice Xlet newKey.set(code-KeyEvent.VK_0); } } } public void backSpace() { backSpaceSet = true; } public void space() { spaceSet = true; } // Assign the key pad to a EditableTextBox. public void setOutput (EditableTextBox e) { textBox = e; } } KeypadButton.java import java.awt.*; public class KeypadButton extends Component { // Constants private static final int KB_WIDTH = 75; private static final int KB_HEIGHT = 52; private int keyNumber = 0; private char[] subChars = null; private int activeSubChar = -1; private int subCharCount = 0; private boolean focused = false; private static Font mainCharFont = null; private static Font subCharFont = null; // Create KeypadButton. public KeypadButton(int keyNum, String keyChars) { setSize(KB_WIDTH,KB_HEIGHT); keyNumber = keyNum; subChars = keyChars.toCharArray(); subCharCount = keyChars.length(); mainCharFont = new Font("Tirealias",Font.PLAIN,27); subCharFont = new Font("Tirealias", Font.PLAIN,20); } public void paint(Graphics g) { super.paint(g); FontMetrics fm = null; String s = null; // If the button if focused it is painted with a white border. if (focused) { 108 APPENDICE A: Codice Xlet g.setColor(Color.white); } else { g.setColor(new Color(0x2a,0x2a,0x2a)); } g.fillRect(0,0,this.getSize().width,this.getSize().height); g.setColor(Color.black); g.fillRect(2,2,this.getSize().width-4,this.getSize().height-4); // Draw subcharacters. FontMetrics is used for getting character // widths, so that the text can be centered. g.setFont(subCharFont); fm = g.getFontMetrics(); int textWidth = fm.stringWidth(String.valueOf(subChars)); int charX = (KB_WIDTH-textWidth)/2; for (int i=0;i<subCharCount;i++) { if (i==activeSubChar) { g.setColor(Color.white); } else { g.setColor(new Color(0x3f,0x3f,0xff)); } s = String.valueOf(subChars[i]); g.drawString(s,charX,46); charX += fm.charWidth(subChars[i]); } // Draw key number. g.setFont(mainCharFont); fm = g.getFontMetrics(); g.setColor(Color.white); s = String.valueOf(keyNumber); g.drawString(s,(KB_WIDTH-fm.stringWidth(s))/2,26); } public void press() { if (activeSubChar == -1) { focused = true; activeSubChar = 0; } else { activeSubChar = (activeSubChar+1) % subCharCount; } repaint(); } public void unpress() { activeSubChar = -1; focused = false; repaint(); } public char getChar() { if (subChars != null) return subChars[activeSubChar]; return 0; } } 109 APPENDICE A: Codice Xlet EditableTextBox.java import java.awt.*; public class EditableTextBox extends Component { private int width = 0; private int height = 0; private String editStr = null; // The currently edited text private String nullStr = null; // The text shown if there's no editStr present private char pendingChar = 0; // An character which has not yet been added to editStr private boolean focused = false; private boolean editActive = false; // Create the component. public EditableTextBox(String nullStr, int width,int height) { setSize(width,height); this.nullStr = nullStr; this.width = width; this.height = height; } // Paint the component. public void paint(Graphics g) { if (focused) { if(editActive)g.setColor(Color.red);//colore riquadro focused e edit active else g.setColor(Color.darkGray); //colore riquadro focused g.fillRect(0,0,width,height); } g.setColor(Color.white); g.fillRect(2,2,width-4,height-4); if ((editStr == null) || ((editStr.length() == 0) && !editActive)) { g.setColor(Color.black); g.setFont(new Font("Tireasias",Font.PLAIN,height-10)); g.drawString(nullStr,10,height-10); } else { g.setColor(Color.black); g.setFont(new Font("Tireasias",Font.PLAIN,height-10)); g.drawString(editStr,10,height-10); if (pendingChar != 0) { g.setColor(Color.red); FontMetrics fm = g.getFontMetrics(); g.drawString(String.valueOf(pendingChar),10+fm.stringWidth(editStr),height-10); } } } public void setFocused(boolean b) { focused = b; } // Set the pending character (shown after editStr) public void setPending(char c) { pendingChar = c; repaint(); } 110 APPENDICE A: Codice Xlet // Add the pending character to editStr public void addPending() { editStr += pendingChar; pendingChar = 0; repaint(); } // Remove the last character (pending or last of editStr) public void backSpace() { if (pendingChar != 0) { pendingChar = 0; } else { if (editStr.length()>0) { editStr = editStr.substring(0,editStr.length()-1); } } repaint(); } // Add space. public void space() { if (pendingChar != 0) { addPending(); } editStr = editStr + ' '; } // Return the edited string. public String getString() { return editStr; } // Set edit mode on/off public void setEdit(boolean b) { editActive = b; if (editStr == null) { editStr = new String(); } } } 111 APPENDICE A: Codice Xlet Strumenti.java import java.awt.Color; import java.awt.Font; import java.awt.event.KeyEvent; import java.io.IOException; import org.havi.ui.*; //import java.net.*; //import java.sql.*; import org.dvb.net.rc.*; public class Strumenti { //0:non esistono connessioni 1:conn persistenete 2:conn non persistenete public static final String IPPROXY="localhost"; //"192.168.1.1"; deit //"localhost"; //"10.6.160.143"; Asur public static int connesso=0; public String []strmask; public static HScene scene; private static int StatoOld; EditableTextBox []etb; public Keypad keyPad; private static HText mess,mess2; public Strumenti(HScene sceneint){ scene=sceneint; } public void init(){ InterfacciaGrafica.STATO=InterfacciaGrafica.PRIMO; keyPad = new Keypad(); new Thread(keyPad).start(); keyPad.setLocation (100,310); scene.add(keyPad); keyPad.setVisible(false); } public void Start(){} public void Stop(){ //new Thread(keyPad).stop(); scene.remove(keyPad); for(int i=0;i<etb.length;i++){ scene.remove(etb[i]); } scene.repaint(); } public boolean popUp(String str,int x,int y){ boolean flag=false; return flag; 112 APPENDICE A: Codice Xlet } public static void Connetti()throws IOException { //prefisso centralino 73372504 final String phone="733725047027020000"; final String user="[email protected]"; final String pass="979116"; Rsl listener; ConnectionRCInterface cRCi=null; RCResourceClient resource_client; resource_client=new RCResourceClient(); //Per prima cosa acquisiamo un'istanza di RCInterfaceManager RCInterfaceManager rcim=RCInterfaceManager.getInstance(); //rcim.addResourceStatusEventListener(listener); //Estraiamo l'array con tutte le possibili interfacce di connessione //che la nostra applicazione potrà avviare. //RCInterface rci[] = rcim.getInterfaces(); RCInterface rci[] = rcim.getInterfaces(); int il=0; if (rci!= null) { il = rci.length; System.out.println("Numero delle interfacce: " + il); } else { il = 0; connesso=0; System.out.println("Nessuna interfaccia supportata (rci =null)"); return; } // // // Tra tutte le interfacce riportate nell'array si verifica quale è una istanza di ConnectionRCInterface e si seleziona quella appropriata. // Elenco l'interfacce System.out.println("Interfacce trovate:"); for(int i = 0; i<il; i++) { System.out.println(rci[i].getType()); if(rci[i] instanceof ConnectionRCInterface) { cRCi = (ConnectionRCInterface) rci[i]; System.out.println("Interfaccia "+i+ ":" + cRCi.getType()+" "+cRCi.toString()+" DataRate:"+cRCi.getDataRate()); } } // Scelgo l'interfaccia più veloce /*for(int i = il-1; i>=0; i--) { if(rci[i] instanceof ConnectionRCInterface) { 113 APPENDICE A: Codice Xlet cRCi = (ConnectionRCInterface) rci[i]; System.out.println("L'interfaccia usata è" + cRCi.getType()); break; } }*/ if(rci[0].getType()==4) connesso=1; //cRCi = (ConnectionRCInterface) rci[0]; // Forzo a modem // cRCi=(ConnectionRCInterface) rci[0]; // Forzo a LAN // cRCi=(ConnectionRCInterface) rci[0]; // Ora abbiamo ottenuto il riferimento alla nostra interfaccia, // se non è permanente andiamo a stabilire la connessione. if (cRCi!= null && cRCi.getType() == RCInterface.TYPE_PSTN) { L'interfaccia viene riservata per la nostra applicazione associandogli un client da noi definito. try{ cRCi.reserve(resource_client, null ); }catch (PermissionDeniedException e){ System.out.println("Errore nel riservare la risorsa: "+e.getMessage()); System.out.println("L' interfaccia non puo essere riservata:Permission Denied!"); return; } Setto i parametri per effetture la chiamata e l'autenticazione. try{ cRCi.setTarget(new ConnectionParameters(phone,user,pass)); }catch (IncompleteTargetException e){ System.out.println("Errore nel setting del target(incomplete):"+e.getMessage()); return; }catch (PermissionDeniedException e){ System.out.println("Errore nel setting del target(permission denied:"+e.getMessage()); return; } // // // //cRCi.addConnectionListener(listener); Crea la connesione con i parametri precedentemente impostati. Se la connesione a gia attiva il metodo non ha effetto. try{ cRCi.connect(); System.out.println("La connessione e stata effettuata!"); }catch (IOException e){ System.out.println("Errore di connessione(io): "+e.getMessage()); return; }catch (PermissionDeniedException e){ System.out.println("Errore di connessione(permission denied): "+e.getMessage()); return; } connesso=2; } // // } public boolean getMask(int nc){ strmask=new String[nc]; boolean flag=true; 114 APPENDICE A: Codice Xlet for(int i=0;i<nc;i++){ strmask[i]=etb[i].getString(); System.out.println(strmask[i]); if(strmask[i]==null || strmask[i].length()==0) flag=false; } return flag; } // Messaggio di commento di durata indicata public static void messFlash(String str,int x,int y){ StatoOld=InterfacciaGrafica.STATO; InterfacciaGrafica.STATO=InterfacciaGrafica.POPUP; Font font=new Font("Arial",Font.BOLD,22); mess= new HText(str); mess2=new HText("PREMI VERDE per continuare"); mess.setFont(font); mess2.setFont(font); mess.setForeground(Color.white); mess2.setForeground(Color.green); mess.setBackgroundMode(HVisible.BACKGROUND_FILL); mess2.setBackgroundMode(HVisible.BACKGROUND_FILL); mess.setBackground(new Color(100,100,100,120)); mess2.setBackground(new Color(100,100,100,120)); mess.setBounds(x,y,580,25); mess2.setBounds(x,y+25,580,20); mess.setBordersEnabled(true); scene.add(mess); scene.add(mess2); mess.setVisible(true); mess2.setVisible(true); scene.repaint(); } public static void messFlashOff(){ InterfacciaGrafica.STATO=StatoOld; mess.setVisible(false); scene.remove(mess); mess2.setVisible(false); scene.remove(mess2); scene.repaint(); } public void passaEvento (KeyEvent key) { } } 115 APPENDICE A: Codice Xlet Registra.java import java.awt.event.KeyEvent; import java.io.IOException; import org.havi.ui.*; import org.havi.ui.event.HRcEvent; public class Registra extends Strumenti{ private int ic=0; //indice del campo corrente private static final int dist=40; boolean edita=false; Registra(HScene sceneint){ super(sceneint); } public void Start(){ etb=new EditableTextBox[5]; etb[0]=new EditableTextBox("Nome",300,30); etb[0].setLocation(300,70); etb[1]=new EditableTextBox("Cognome",300,30); etb[1].setLocation(300,70+dist); etb[2]=new EditableTextBox("Telefono",300,30); etb[2].setLocation(300,70+2*dist); etb[3]=new EditableTextBox("Username",300,30); etb[3].setLocation(300,70+3*dist); etb[4]=new EditableTextBox("Password",300,30); etb[4].setLocation(300,70+4*dist); for(int i=0;i<5;i++){ etb[i].setVisible(true); scene.add(etb[i]); } etb[ic].setFocused(true); keyPad.setVisible(true); scene.repaint(); } public void passaEvento (KeyEvent key) { int pulsantePremuto = key.getKeyCode(); switch(pulsantePremuto){ case KeyEvent.VK_0: case KeyEvent.VK_1: case KeyEvent.VK_2: case KeyEvent.VK_3: case KeyEvent.VK_4: case KeyEvent.VK_5: case KeyEvent.VK_6: case KeyEvent.VK_7: case KeyEvent.VK_8: case KeyEvent.VK_9: 116 APPENDICE A: Codice Xlet if(edita)keyPad.keyPressed(key); break; case HRcEvent.VK_COLORED_KEY_0: System.out.println("Premuto rosso sotto registra"); if (getMask(5)){ //Controlla se esiste user e aggiorna anag try{ Connetti(); }catch (IOException e){ System.out.println("Errore di connessione(io): "+e.getMessage()); } // Registra un nuovo utente Comunica com=new Comunica(IPPROXY,8000); ObjectRegistration reg=new ObjectRegistration(strmask[3],strmask[4],strmask[0],strmask[1],strmask[2]); String ris=(String)(com.Connetti(reg)); System.out.println("ris="+ris); messFlash(ris,60,265); //Ritorna registra effettuata con successo o Username già esistente }else{ // finestra errore:riempi tutti i campi messFlash("Compila tutti i campi",60,265); } break; case HRcEvent.VK_COLORED_KEY_1: break; case HRcEvent.VK_COLORED_KEY_2: break; case HRcEvent.VK_COLORED_KEY_3: Stop(); break; case HRcEvent.VK_UP: edita=false; etb[ic].setFocused(false); etb[ic].setEdit(false); if(ic==0){ ic=4; }else ic--; etb[ic].setFocused(true); scene.repaint(); break; case HRcEvent.VK_DOWN: edita=false; etb[ic].setEdit(false); etb[ic].setFocused(false); if(ic==4){ ic=0; }else ic++; etb[ic].setFocused(true); scene.repaint(); break; case HRcEvent.VK_RIGHT: if(edita)keyPad.space(); break; 117 APPENDICE A: Codice Xlet case HRcEvent.VK_LEFT: if(edita)keyPad.backSpace(); break; case HRcEvent.VK_ENTER: edita=true; keyPad.setOutput(etb[ic]); etb[ic].setEdit(true); scene.repaint(); default: break; } } } Prenota.java import java.awt.*; import java.awt.event.*; import java.io.IOException; import java.net.*; import org.havi.ui.*; import org.havi.ui.event.HRcEvent; public class Prenota extends Strumenti{ private int ic=0; //indice del campo corrente private static final int dist=50; boolean edita=false; private boolean riprenota=false; private int disp []; private static int ntav=5; //N tot tavoli private int itav=0; //indice corrente vettore tavoli private Image tavv,tavr; //icone:tavolo libero,occupato public static HIcon []vetTav; private static HText messblu; Prenota(HScene sceneint){ super(sceneint); tavv=Toolkit.getDefaultToolkit().getImage("tavolo_v.gif"); tavr=Toolkit.getDefaultToolkit().getImage("tavolo_r.gif"); } public void Start(){ etb=new EditableTextBox[2]; etb[0]=new EditableTextBox("Username",300,35); etb[0].setLocation(300,100); etb[1]=new EditableTextBox("Password",300,35); etb[1].setLocation(300,100+dist); for(int i=0;i<2;i++){ etb[i].setVisible(true); scene.add(etb[i]); } etb[ic].setFocused(true); keyPad.setVisible(true); scene.repaint(); } public void visualizzaDisp(){ if(!riprenota){ 118 APPENDICE A: Codice Xlet Main.gestoreSfondi.displayBackground("background.mpg",260,100,225,180); Font font=new Font("Arial",Font.BOLD,20); messblu= new HText("PREMI TASTO BLU PER USCIRE"); messblu.setFont(font); messblu.setForeground(Color.blue); messblu.setBackgroundMode(HVisible.BACKGROUND_FILL); messblu.setBackground(new Color(100,100,100,120)); messblu.setBounds(60,460,580,20); messblu.setBordersEnabled(true); scene.add(messblu); } if(riprenota){ for(int i=0;i<ntav;i++){ scene.remove(vetTav[i]); } System.out.println("RIPRENOTA"); scene.repaint(); } messblu.setVisible(true); InterfacciaGrafica.STATO=InterfacciaGrafica.DISP; System.out.println("stato visu disp"+InterfacciaGrafica.STATO); vetTav=new HIcon[ntav]; for(int i=0;i<ntav;i++){ if(disp[i]==0)vetTav[i]= new HIcon(tavv,110+100*i,300,100,80); else vetTav[i]= new HIcon(tavr,110+100*i,300,100,80); vetTav[i].setBackground(new Color(100,100,100,120)); scene.add(vetTav[i]); vetTav[i].setVisible(true); } vetTav[itav].setBackgroundMode(HVisible.BACKGROUND_FILL); scene.repaint(); } public static void stopVisu(){ for(int i=0;i<ntav;i++){ scene.remove(vetTav[i]); } messblu.setVisible(false); //scene.remove(messblu); scene.repaint(); InterfacciaGrafica.STATO=InterfacciaGrafica.MENU; } public void passaEvento (KeyEvent key) { int pulsantePremuto = key.getKeyCode(); switch(pulsantePremuto){ case KeyEvent.VK_0: case KeyEvent.VK_1: case KeyEvent.VK_2: case KeyEvent.VK_3: case KeyEvent.VK_4: case KeyEvent.VK_5: case KeyEvent.VK_6: case KeyEvent.VK_7: 119 APPENDICE A: Codice Xlet case KeyEvent.VK_8: case KeyEvent.VK_9: if(edita)keyPad.keyPressed(key); break; case HRcEvent.VK_COLORED_KEY_0: if(getMask(2)){ try{ Connetti(); }catch (IOException e){ System.out.println("Errore di connessione(io): "+e.getMessage()); } System.out.println("connesso:"+connesso); //prova(); Comunica com=new Comunica(IPPROXY,8000); ObjectPrenotation pren=new ObjectPrenotation(strmask[0],strmask[1]); System.out.println("creato ObjectPrenotation"); Object tavoli=com.Connetti(pren); System.out.println("ritornato tavoli"); if(tavoli instanceof ObjectTavoli) { String ris=""; disp=new int[((ObjectTavoli)tavoli).getLength()]; for (int i=0;i<((ObjectTavoli)tavoli).getLength();i++) { String u=(((ObjectTavoli)tavoli).getUtente(i)); String =String.valueOf(((ObjectTavoli)tavoli).getNumero(i)); String l=String.valueOf(((ObjectTavoli)tavoli).getLibero(i)); disp[i]=((ObjectTavoli)tavoli).getLibero(i); ris=ris+"\nUtente: "+u+" Numero: "+n+" Libero: "+l; } System.out.println(ris); Stop(); visualizzaDisp(); messFlash("Login corretto,prenota il tuo tavolo!",60,415); riprenota=true; break; } if(tavoli instanceof String) { System.out.println(tavoli); messFlash((String)tavoli,60,265); } //Ritorna Username o password errati }else{ System.out.println("compila tutto"); messFlash("Compila tutti i campi",60,265); } break; case HRcEvent.VK_COLORED_KEY_1: break; case HRcEvent.VK_COLORED_KEY_2: break; case HRcEvent.VK_COLORED_KEY_3: if(InterfacciaGrafica.STATO==InterfacciaGrafica.DISP){ stopVisu(); 120 APPENDICE A: Codice Xlet } break; case HRcEvent.VK_UP: edita=false; etb[ic].setFocused(false); etb[ic].setEdit(false); if(ic==0){ ic=1; }else ic--; etb[ic].setFocused(true); scene.repaint(); break; case HRcEvent.VK_DOWN: edita=false; etb[ic].setEdit(false); etb[ic].setFocused(false); if(ic==1){ ic=0; }else ic++; etb[ic].setFocused(true); scene.repaint(); break; case HRcEvent.VK_RIGHT: System.out.println("Destro"); if(edita)keyPad.space(); if(InterfacciaGrafica.STATO==InterfacciaGrafica.DISP){ vetTav[itav].setBackgroundMode(HVisible.NO_BACKGROUND_FILL); if(itav>=ntav-1){ itav=0; }else itav++; vetTav[itav].setBackgroundMode(HVisible.BACKGROUND_FILL); scene.repaint(); } break; case HRcEvent.VK_LEFT: if(edita)keyPad.backSpace(); if(InterfacciaGrafica.STATO==InterfacciaGrafica.DISP){ vetTav[itav].setBackgroundMode(HVisible.NO_BACKGROUND_FILL); if(itav<=0){ itav=ntav-1; }else itav--; vetTav[itav].setBackgroundMode(HVisible.BACKGROUND_FILL); scene.repaint(); } break; case HRcEvent.VK_ENTER: if(InterfacciaGrafica.STATO==InterfacciaGrafica.DISP){ if(disp[itav]==0){ Comunica com=new Comunica(IPPROXY,8000); PrenotaSingolo pren=new PrenotaSingolo(strmask[0],itav+1);//user e n tavolo 1a5 Object p=com.Connetti(pren); disp[itav]=1; 121 APPENDICE A: Codice Xlet if (p instanceof String) { System.out.println(p); //Prenotazione eseguita con successo visualizzaDisp(); messFlash((String)p,60,415); } }else{ messFlash("Tavolo già riservato",60,415); } break; } edita=true; keyPad.setOutput(etb[ic]); etb[ic].setEdit(true); scene.repaint(); default: break; } } } Comunica.java import java.net.*; import java.io.*; //import java.util.*; public class Comunica { private String IP; private int port; private String str; private Object obj; private Object objW; public Comunica(String sIP,int p){ this.port=p; this.IP=sIP; } public Object Connetti(Object object){ obj=null; objW=object; try{ // Apre una connessione verso un server in ascolto // sulla porta "port", all'indirizzo "IP" System.out.println("Apertura Socket..."); Socket sock = new Socket(IP,port); System.out.println("creato socket"); OutputStream s1out = sock.getOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(s1out); System.out.println("impostato outpustream"); oos.writeObject(objW); // Ricava lo stream di input dal socket sock // ed utilizza un oggetto wrapper di classe BufferedReader // per semplificare le operazioni di lettura InputStream is = sock.getInputStream(); ObjectInputStream ois = new ObjectInputStream(is); //BufferedReader dis = new BufferedReader(new InputStreamReader(is)); // Legge l'input e lo visualizza sullo schermo 122 APPENDICE A: Codice Xlet //str=dis.readLine(); try{ obj=ois.readObject();} catch (ClassNotFoundException e) { System.out.println("Error: "+e.getMessage()); } ois.close(); sock.close(); System.out.println("Chiusura connessione effettuata"); }catch (ConnectException connExc){ System.out.println("Errore nella connessione "); } catch (IOException ex){ ex.printStackTrace(); } return obj; } PrenotaSingolo pren=new PrenotaSingolo("jack",1); Object p=com.Connetti(pren); if (p instanceof String) { System.out.println(p); } } } PrenotaSingolo.java import java.io.*; public class PrenotaSingolo implements Serializable { private String user; private int numero; public PrenotaSingolo (String user, int numero) { this.user=user; this.numero=numero; } public String getUser() { return user; } public int getNumero() { return numero; } } 123 APPENDICE B: Codice Proxy APPENDICE B Codice Proxy SimpleServer.java import java.net.*; import java.io.*; //import java.util.*; public class SimpleServer { public static final String IPDB="localhost"; public static final String USERDB="fabiob"; public static final String PASSDB="979116"; private int port; private ServerSocket server; private Object obj; private Object prenot; private String ris; private Object tavoli; private Object prenotaS; public SimpleServer (int port){ ris=""; this.port = port; if(!startServer()) System.err.println("Errore durante la creazione del Server"); } private boolean startServer(){ try{ server = new ServerSocket(port); }catch (IOException ex){ ex.printStackTrace(); return false; } System.out.println("Server creato con successo!"); return true; } public void runServer(){ while (true){ try{ // Il server resta in attesa di una richiesta System.out.println("Server in attesa di richieste..."); Socket s1 = server.accept(); System.out.println("Un client si e' connesso..."); InputStream is = s1.getInputStream(); ObjectInputStream ois = new ObjectInputStream(is); try{ obj=(Object)(ois.readObject()); System.out.println("oggetto letto"); } 124 APPENDICE B: Codice Proxy catch (ClassNotFoundException e) { System.out.println("Error: "+e.getMessage()); } if (obj instanceof ObjectRegistration) { ris=(new ServerRegistration((ObjectRegistration)obj)).start(); // Ricava lo stream di output associate al socket // e definisce una classe wrapper di tipo // BufferedWriter per semplificare le operazioni // di scrittura OutputStream s1out = s1.getOutputStream(); // BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s1out)); ObjectOutputStream oos = new ObjectOutputStream(s1out); // Il server invia la risposta al client // oos.writeObject("Benvenuto sul server!\n"); oos.writeObject(ris); // Chiude lo strema di output e la connessione oos.close(); } if(obj instanceof ObjectPrenotation) { tavoli=(new ServerPrenotation((ObjectPrenotation)obj)).start(); OutputStream s1out = s1.getOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(s1out); oos.writeObject(tavoli); oos.close(); } if (obj instanceof PrenotaSingolo) { prenotaS=(new ServerPrenotaSingolo((PrenotaSingolo)obj)).start(); OutputStream s1out = s1.getOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(s1out); oos.writeObject(prenotaS); oos.close(); } s1.close(); System.out.println("Chiusura connessione effettuata\n"); }catch (IOException ex){ ex.printStackTrace(); } } } public static void main(String[] args) { SimpleServer ss = new SimpleServer(8000); ss.runServer(); } } 125 APPENDICE B: Codice Proxy ServerRegistration.java import java.sql.*; public class ServerRegistration { private ObjectRegistration obj; private String ris; public ServerRegistration(ObjectRegistration object) { this.obj=object; ris=""; } public String start() { try { Class.forName("com.mysql.jdbc.Driver"); System.out.println("eseguito class.forname"); } catch (ClassNotFoundException e1){} try{ String user=SimpleServer.USERDB; String pass=SimpleServer.PASSDB; String url="jdbc:mysql://"+SimpleServer.IPDB+"/prenotazioni"; Connection con = DriverManager.getConnection(url,user,pass); System.out.println("Connessione DB: "+url+user+pass); Statement stmt=con.createStatement(); System.out.println(((ObjectRegistration)obj).getUser()); String query= "SELECT user FROM cliente WHERE (user='"+((ObjectRegistration)obj).getUser()+"');"; ResultSet rm=stmt.executeQuery(query); while (rm.next()) { ris=rm.getString(1); } if (ris=="") { String query2= "INSERT INTO cliente (user,password,nome,cognome,telefono) VALUES ('"+((ObjectRegistration)obj).getUser()+"','"+((ObjectRegistration)obj).getPassword()+"','"+((ObjectRegi stration)obj).getNome()+"','"+((ObjectRegistration)obj).getCognome()+"','"+((ObjectRegistration)obj).get Telefono()+"');"; stmt.executeUpdate(query2); ris="Registrazione effettuata con successo!"; System.out.println("query effettuata"); } else { ris="Username esistente."; } rm.close(); stmt.close(); con.close(); }catch(SQLException exc) { System.out.println("SQL error: "+exc.getMessage()); } return ris; } } 126 APPENDICE B: Codice Proxy ServerPrenotation.java import java.sql.*; public class ServerPrenotation { private ObjectPrenotation obj; private Object tavoli; public ServerPrenotation(ObjectPrenotation obj) { this.obj=obj; } public Object start() { try { Class.forName("com.mysql.jdbc.Driver"); System.out.println("eseguito class.forname"); } catch (ClassNotFoundException e1){} try{ String user=SimpleServer.USERDB; String pass=SimpleServer.PASSDB; String url="jdbc:mysql://"+SimpleServer.IPDB+"/prenotazioni"; Connection con = DriverManager.getConnection(url,user,pass); System.out.println("Connessione DB: "+url+user+pass); Statement stmt=con.createStatement(); String query="SELECT user FROM cliente WHERE user='"+obj.getUser()+"' AND password='"+obj.getPassword()+"';"; ResultSet rm=stmt.executeQuery(query); String ris1=""; while(rm.next()) { ris1=ris1+rm.getString(1); } rm.close(); if (ris1=="") { System.out.println("user pwd errati"); tavoli="Username o password errati."; } else { System.out.println("user pwd esatti"); String query2= "SELECT count(*) FROM tavoli;"; ResultSet rm2=stmt.executeQuery(query2); System.out.println("eseguita query2"); int length=0; while (rm2.next()) { length=Integer.parseInt(rm2.getString(1)); System.out.println("length: "+length); } rm2.close(); String query3= "SELECT * FROM tavoli;"; ResultSet rm3=stmt.executeQuery(query3); int[] num=new int[length]; String[] utente=new String[length]; int[] libero=new int[length]; int j=0; while (rm3.next()) { num[j]=Integer.parseInt(rm3.getString(1)); System.out.println(num[j]); utente[j]=rm3.getString(2); 127 APPENDICE B: Codice Proxy System.out.println(utente[j]); libero[j]=Integer.parseInt(rm3.getString(3)); System.out.println(libero[j]); j++; } tavoli=new ObjectTavoli(utente,num,libero); rm3.close(); } stmt.close(); con.close(); } catch(SQLException exc) { System.out.println("SQLException: "+exc.getMessage()); } return tavoli; } } ServerPrenotaSingolo.java import java.sql.*; public class ServerPrenotaSingolo { private PrenotaSingolo obj; private String ris; public ServerPrenotaSingolo(PrenotaSingolo obj) { this.obj=obj; } public Object start() { ris=""; try { Class.forName("com.mysql.jdbc.Driver"); System.out.println("eseguito class.forname"); } catch (ClassNotFoundException e1){} try{ String user=SimpleServer.USERDB; String pass=SimpleServer.PASSDB; String url="jdbc:mysql://"+SimpleServer.IPDB+"/prenotazioni"; Connection con = DriverManager.getConnection(url,user,pass); System.out.println("Connessione DB: "+url+user+pass); Statement stmt=con.createStatement(); String query= "UPDATE tavoli SET libero=1 ,utente='"+obj.getUser()+"' WHERE num_tavolo="+obj.getNumero()+";"; stmt.executeUpdate(query); ris="Prenotazione eseguita con successo!"; stmt.close(); con.close(); } catch (SQLException e) { 128 APPENDICE B: Codice Proxy System.out.println("Eccezione: "+e.getMessage()); ris="Errore prenotazione"; } return ris; } } PrenotaSingolo.java import java.io.*; public class PrenotaSingolo implements Serializable { private String user; private int numero; public PrenotaSingolo (String user, int numero) { this.user=user; this.numero=numero; } public String getUser() { return user; } public int getNumero() { return numero; } } ObjectRegistration.java import java.io.*; public class ObjectRegistration implements Serializable{ private String user; private String password; private String nome; private String cognome; private String telefono; public ObjectRegistration(String u, String pwd, String nom, String cog, String tel) { this.user=u; this.password=pwd; this.nome=nom; this.cognome=cog; this.telefono=tel; System.out.println("creato objectRegistration"); } public String getUser() { return this.user; } public String getPassword() { return this.password; } public String getNome() { 129 APPENDICE B: Codice Proxy return this.nome; } public String getCognome() { return this.cognome; } public String getTelefono() { return this.telefono; } } ObjectPrenotation.java import java.io.*; public class ObjectPrenotation implements Serializable { private String user; private String password; public ObjectPrenotation (String user, String password){ this.user=user; this.password=password; } public String getUser() { return this.user; } public String getPassword() { return this.password; } } ObjectTavoli.java import java.io.*; public class ObjectTavoli implements Serializable{ private String[] utente; private int[] num_tavolo; private int[] libero; public ObjectTavoli(String[] utente, int[] num, int[] libero) { this.num_tavolo=num; this.libero=libero; this.utente=utente; } public String getUtente(int i) { return utente[i]; } public int getNumero(int i) { return num_tavolo[i]; } public int getLibero(int i) { return libero[i]; } public int getLength() { return utente.length; } } 130 Siti e Bibliografia SITI E BIBLIOGRAFIA www.mhp.org www.openmhp.org www.dvb.org www.etsi.org www.cineca.it www.sun.it www.myslq.com www.java.sun.com www.javastaff.com www.sourceforge.net www.eclipse.org www.coremedia.com www.televisionedigitaleterrestre.it www.interactivetvweb.org www.digisoft.it www.digitaleterrestre.it Steven Morris, Anthony Smith-Chaigneau: “Interactive TV Standard – A Guide to MHP, OCAP and Java TV”. George Reese: “O'Reilly-Database Programming with JDBC and Java, Second Edition”. V.Mignone, A.Morello, M.Visintin: “Lo standard DVB-T per la televisione digitale terrestre”. E.Fleischner,B.Somalvico: "La TV diventa digitale", Franco Angeli, 2002. 131 UNIVERSITÀ POLITECNICA DELLE MARCHE FACOLTÀ DI INGEGNERIA TESI DI LAUREA IN INGEGNERIA INFORMATICA E DELL’AUTOMAZIONE Studio ed implementazione di un servizio di prenotazioni “on-line” su piattaforma MHP utilizzando Set Top Box da sviluppo ADB X-75 RELATORE : Prof. Aldo Franco DRAGONI CORRELATORE: Prof. Paolo PULITI CANDIDATO: Bracalente Fabio Anno Accademico 2005-2006 UNIVERSITÀ POLITECNICA DELLE MARCHE FACOLTÀ DI INGEGNERIA CORSO DI LAUREA IN INGEGNERIA INFORMATICA E DELL’AUTOMAZIONE Studio ed implementazione di un servizio di prenotazioni “on-line” su piattaforma MHP utilizzando Set Top Box da sviluppo ADB X-75 Relatore : Prof. Aldo Franco DRAGONI Correlatore: Prof. Paolo PULITI Tesi di Laurea di: Bracalente Fabio Anno Accademico 2005-2006 Dietro ogni problema c’è un’opportunità… Galileo Galilei Ringraziamenti Sarò in controtendenza ma vorrei iniziare dai ringraziamenti “tecnici” per arrivare poi a quelli affettivi. Ringrazio il Prof. Aldo Franco Dragoni per avermi spinto verso una nuova tecnologia in fase di sviluppo, che speriamo apra nuovi sbocchi, grazie anche per avermi dato fiducia e costante assistenza in questo progetto. Al Dott. Fabio Tonucci per la sua disponibilità durante il periodo di tirocinio. Al Prof. Paolo Puliti per avermi fatto scoprire un nuovo linguaggio di programmazione, e per avermi insegnato a vedere il Mondo ad “Oggetti”. Vorrei ricordare il Prof. Maurizio Panti e ringraziarlo perché devo a lui le conoscenze in campo di analisi dei dati e Database. Vorrei ringraziare anche tutti gli altri professori che ho incontrato nel cammino scolastico e soprattutto universitario perché ognuno di loro ha contribuito a formarmi sia culturalmente che caratterialmente. Ai colleghi tirocinanti con cui ho condiviso questa nuova esperienza. Un ringraziamento veramente di cuore va in particolare ai miei genitori perché mi hanno sempre sostenuto e lasciato libero in tutte le mie scelte e soprattutto perché senza di loro tutto questo non sarebbe stato possibile. A mio fratello, spirito guida ed esempio di diligenza sicuramente da seguire. A tutti i miei nonni che mi hanno trasmesso, una in particolare che continua tuttora a farlo, un po’ della loro saggezza. Ai tutti i miei zii che fin da piccolo mi hanno allietato tante giornate e qualcuno in particolare mi ha anche aiutato, in tutti i campi, in fasi particolari della mia vita. Ai miei cugini più o meno piccoli perché, anche se li trascuro un po’, li porto sempre nel cuore. Un grazie anche a tutti i diversi coinquilini con cui ho convissuto in tutti questi anni. Un grazie a tutti gli amici e amiche che mi sono stati vicino e spero che continuino a farlo. Un ringraziamento doveroso all’Hotstaff che è stato sinonimo di sostentamento nel periodo degli studi, ma soprattutto: di grandi amicizie, di innumerevoli conoscenze, di momenti di divertimento allo stato puro, una vera e propria medicina nei periodi tristi di questi ultimi anni; forse anche la causa dell’allungamento della mia carriera universitaria, ma ne è valsa la pena, grazie a tutti di cuore. Ultimo, ma sicuramente non per importanza, un grazie tutto per lei lo devo alla mia ragazza; grazie semplicemente di esistere e di avermi sopportato e supportato in questi ultimi periodi. Sommario Sommario GLOSSARIO................................................................................................................... 1 INTRODUZIONE........................................................................................................... 2 CAPITOLO 1 IL DIGITALE TERRESTRE ........................................................................................ 3 1.1 INTRODUZIONE ......................................................................................................... 3 1.2 NOVITÀ TECNOLOGICHE ........................................................................................... 3 1.3 LO STANDARD MHP................................................................................................. 7 1.4 BROADCASTING DI APPLICAZIONI INTERATTIVE ..................................................... 10 1.5 IL DECODER INTERATTIVO ...................................................................................... 11 CAPITOLO 2 APPLICAZIONI MHP................................................................................................. 13 2.1 MODELLO DI BUSINESS E INTERFACCE ................................................................... 13 2.2 XLET ...................................................................................................................... 15 2.3 GESTIONE DELLE RISORSE SCARSE ......................................................................... 18 2.4 MODELLO GRAFICO ................................................................................................ 25 2.4.1 Background layer........................................................................................... 26 2.4.2 Video layer ..................................................................................................... 32 2.4.3 Graphics layer ............................................................................................... 35 2.5 GESTIONE DEGLI EVENTI DEL TELECOMANDO ........................................................ 38 CAPITOLO 3 CANALE DI RITORNO .............................................................................................. 41 3.1 LO SCENARIO ......................................................................................................... 41 3.2 I PROTOCOLLI SUPPORTATI ..................................................................................... 45 3.2.1 Livello 1: Fisico ............................................................................................. 45 3.2.2 Livello 2: PPP................................................................................................ 46 3.2.3 Protocollo di rete: IP ..................................................................................... 48 3.2.4 Protocolli di trasporto: TCP / UDP .............................................................. 48 3.2.5 Hypertext Transfer Protocol (HTTP)............................................................. 49 3.2.6 Secure Hypertext Transfer Protocol (HTTPS)............................................... 51 3.2.7 Servizi Specifici.............................................................................................. 51 3.3 GESTIONE DELLA CONNESSIONE ............................................................................. 52 3.3.1 Utilizzare il canale di ritorno: il package org.dvb.net.rc .............................. 52 3.3.2 Interfacce per il Canale di Ritorno ................................................................ 53 3.3.3 Acquisire l’interfaccia corretta...................................................................... 55 3.3.4 Stabilire la connessione ................................................................................. 57 3.3.5 I package java.net e java.io ........................................................................... 61 I Sommario CAPITOLO 4 AMBIENTE DI SVILUPPO ........................................................................................ 62 4.1 INTRODUZIONE....................................................................................................... 62 4.2 IL SIMULATORE XLETVIEW ................................................................................... 63 4.3 SET TOP BOX DA SVILUPPO ADB X-75.................................................................. 66 4.3.1 Caratteristiche Tecniche................................................................................ 67 4.3.2 Pannello frontale ........................................................................................... 69 4.3.3 Pannello posteriore........................................................................................ 70 4.3.4 Telecomando .................................................................................................. 71 4.3.5 Settaggio connessione di default.................................................................... 72 4.3.6 STB Firmware upgrade.................................................................................. 73 4.3.7 UpLoad e debug di un’applicazione ............................................................. 74 4.3.8 Xlet description file ........................................................................................ 76 CAPITOLO 5 IMPLEMENTAZIONE DI UN SERVIZIO DI PRENOTAZIONI ......................... 78 5.1 LE PROBLEMATICHE ............................................................................................... 79 5.2 INTERFACCIA GRAFICA ........................................................................................... 80 5.3 ACCESSO AI DATI ................................................................................................... 84 5.3.1 Socket ............................................................................................................. 85 5.3.2 JDBC.............................................................................................................. 91 5.3.3 Database ........................................................................................................ 94 5.4 SVILUPPI FUTURI .................................................................................................... 95 CONCLUSIONI............................................................................................................ 96 APPENDICE A CODICE XLET............................................................................................................. 97 APPENDICE B CODICE PROXY ....................................................................................................... 124 SITI E BIBLIOGRAFIA............................................................................................ 131 II Glossario Glossario DTT: Digital Terrestrial Television DVB: Digital Video Broadcasting DTv: Digital Television EPG: Electronic Program Guide ETSI: European Telecommunications Standards Institute FTV: Free To View HDTV: High Definition Television Defined MHP: Multimedia Home Platform MPEG: Moving Picture Expert Group PAL: Phase Alternative Line QAM: Quadrature Amplitude Modulation SCART: Syndicat Francais des Constructeurs d’Appareils Radio et Television IP: Internet Protocol ISP: Internet Service Provider TCP: Transmission Control Protocol UDP: User Data Protocol HTTP: Hypertext Transfer Protocol TLS: Transport Layer Security LAN: Local Area Network 1 Introduzione Introduzione La digitalizzazione della televisione terrestre è forse l'ultimo anello importante che manca alla catena che in poco più di 10 anni ha trasformato in digitale i principali flussi informativi che viaggiano sul nostro pianeta. Da qualche tempo dopo aver tratto insegnamento dalle prime esperienze effettuate in UK, Spagna e Finlandia, anche l'Italia ha intrapreso il cammino che, nel corso dei prossimi due anni, porterà a trasformare le centinaia di stazioni televisive che popolano l'etere nazionale in altrettanti canali digitali. Con la televisione Digitale Terrestre (DTT: Digital Terrestrial Television) arriveranno non solo trasmissioni televisive di miglior qualità e in maggior quantità, ma anche nuove possibilità di servizi interattivi, che da un lato potranno rendere più ricca ed interessante l'esperienza del telespettatore, e dall'altro apriranno un canale di comunicazione bidirezionale, e quindi di natura del tutto nuova, che potrà raggiungere qualsiasi famiglia del nostro paese. Proprio la bidirezionalità della comunicazione è un fattore che suscita molte aspettative in diversi settori: dalle Pubbliche Amministrazioni, che vedono nel sistema un’opportunità per avvicinare i cittadini e rendere loro più facilmente disponibili tutti i propri servizi, ai vari privati che sfrutteranno al massimo un nuovo strumento sia pubblicitario che informativo. Tra i vari servizi che oggi sono già realizzati per internet spiccano sicuramente: ecommerce, e-health, e-banking e servizi di prenotazioni. In questo elaborato si cercare di spiegare innanzitutto le tecnologie sia hardware che software, che interessano il digitale terrestre, compresi i vari standard che entrano in gioco, continuando poi con la spiegazione di come realizzare un ambiente di sviluppo per applicazioni MHP e infine descrivendo l’implementazione di una piattaforma per prenotazioni “on-line” con particolare riguardo all’uso del canale di ritorno e l’accesso ai dati e le varie problematiche che ne conseguono. 2 Capitolo 1: Il Digitale Terrestre Capitolo 1 Il Digitale Terrestre 1.1 Introduzione La televisione digitale terrestre (DTT: Digital Terrestrial Television) è l'ultima tecnologia di comunicazione digitale che sta bussando alla porta delle case degli italiani, e certamente risulta essere piuttosto apprezzata, a giudicare dai numeri. Nel volgere di poco più di due anni e mezzo, dall'inizio del 2004, circa 2,5 milioni di decoder DTT (i cosiddetti Set-Top-Box) hanno trovato posto nei nostri salotti, aiutati indubbiamente anche dall’iniziale contributo per l'acquisto stanziato dal governo, di 70 euro per famiglia. La trasformazione della TV da analogica a digitale nel nostro Paese coinvolgerà progressivamente gli oltre 50 milioni di apparecchi televisivi presenti sul territorio. La fine delle trasmissioni TV terrestri in tecnica analogica (switch-off) è prevista normativamente entro il 31 dicembre 2008: in base alla legge n. 66/2001 a partire da tale data tutte le trasmissioni TV terrestri dovranno essere solo digitali. In questa fase quindi tutti i broadcaster, nazionali e regionali, stanno affrontando i problemi collegati alla transizione. 1.2 Novità tecnologiche La novità tecnologica della DTT risiede principalmente in due elementi: l'adozione di un sistema di trasmissione numerico COFDM, che presenta caratteristiche di robustezza rispetto alle interferenze, migliore qualità del segnale trasportato e, soprattutto, minor banda occupata nello spettro elettromagnetico, da cui l’attesa moltiplicazione dei canali disponibili; la contemporanea presenza di un canale di ritorno (CdR) che, collegando il ricevitore ad una rete di telecomunicazioni, fornisce al telespettatore uno strumento per interagire a ritroso con l’emittente televisiva o con il fornitore del servizio. La modulazione usata dalla DTT a parte l'aggiunta di specifici codici per la correzione dell'errore, è affine a quella usata per le connessioni ADSL. Essa lavora sul principio di 3 Capitolo 1: Il Digitale Terrestre utilizzare diverse frequenze portanti fra loro ortogonali, fino ad un massimo di 8k frequenze, e poi suddividere l'informazione in altrettanti flussi di bit indipendenti; ogni flusso di bit viene assegnato ad una diversa portante e poi alla ricezione i diversi flussi vengono opportunamente ricombinati. Tale modulazione, quando opportunamente utilizzata, presenta una caratteristica interessante: permette di sfruttare in modo costruttivo gli echi isofrequenziali che giungono all'antenna ricevente del televisore. Ciò porta almeno un paio di vantaggi rispetto al mondo analogico. Da un lato facilita una migliore ricezione all'interno delle aree urbane, dove il segnale elettromagnetico può facilmente arrivare all'antenna in modo multiplo, per via delle riflessioni che subisce incontrando i vari edifici (e che nel mondo della TV analogica produrrebbero immagini ghost), dall'altro abilita una più semplice costruzione dell'intera rete broadcast, permettendone una organizzazione in isole isofrequenziali, fino ad una dimensione massima che dipende dal rapporto fra tempo di trasmissione e tempo di silenzio ("di guardia" in termini tecnici) che è stato scelto da ogni broadcaster per i trasmettitori della propria rete. La ricezione del segnale televisivo può avvenire attraverso lo stesso impianto d’antenna utilizzato già oggi per la TV analogica, a patto che l'impianto stesso sia stato effettuato a regola d'arte, rispettando i requisiti tecnici di base, com’è normale se ci si è affidati a professionisti del settore per la sua realizzazione. Il canale di ritorno utilizzerà un collegamento fornito dalla rete di telecomunicazioni, e potrà operare sia con tecnologia tradizionale (RTG: Rete Telefonica Generale) sia con tecnologie a larga banda (ADSL) o wireless (GPRS/UMTS). Grazie al canale di ritorno ed alle funzioni che permettono interattività locale sarà possibile progettare e costruire una grande varietà di nuovi servizi, che vanno dal settore dell'intrattenimento a quello informativo, per arrivare a servizi di utilità forniti da soggetti pubblici (es. il Comune di residenza) o privati (es: la propria banca). Il segnale audiovisivo della DTT è codificato in digitale secondo lo standard MPEG-2, lo stesso adottato da oltre un decennio per la TV digitale satellitare. La modulazione di tale segnale sulla specifica frequenza portante, fatto secondo lo standard DVB-T, permette di trasportare un flusso numerico di circa 24 Mbit/s su ogni frequenza, consentendo così di trasmettere fino a 4 o 5 canali TV, con qualità audiovisiva paragonabile a quella di un DVD, occupando la stessa banda di frequenza attualmente 4 Capitolo 1: Il Digitale Terrestre utilizzata da un singolo canale analogico. In questa prima fase di diffusione del servizio, e nell'attesa che si diffondano sul mercato televisori già naturalmente predisposti per vedere le trasmissioni digitali, per ricevere il segnale digitale sarà necessario collegare il normale apparecchio televisivo che popola i salotti degli italiani con un ricevitore digitale, il cosiddetto Set-Top Box (STB), che realizza le funzioni di ricevitore/decoder per il segnale della TV digitale terrestre, ed utilizza la televisione per visualizzare le immagini e diffondere i suoni. Il collegamento fra Set-Top Box e televisore si può realizzare tramite la consueta presa SCART. Come già accennato sopra, in aggiunta al consueto contenuto audiovisivo, il segnale digitale può agevolmente trasportare, sempre all'interno dello stesso flusso digitale, altri tipi di dati, associati o meno al canale che si sta trasmettendo. Il Set-Top Box diventa così un vero e proprio ambiente di esecuzione per applicazioni, in grado di fornire i diversi servizi aggiuntivi, grazie al dialogo realizzato con telecomando e Canale di Ritorno. La componente interattiva della TV digitale è stata standardizzata a livello europeo attraverso la specifica DVB-MHP (Digital Video Broadcasting - Multimedia Home Platform), che definisce la piattaforma standard per la distribuzione ed esecuzione di applicazioni televisive su STB, nonché le specifiche per il loro sviluppo. Un STB MHP è un sistema basato su una Java Virtual Machine che integra le funzionalità di controllo dei flussi audio/video necessarie per operare nell'ambiente televisivo, mentre le applicazioni non sono altro che dei programmi scritti in Java che utilizzano le risorse hardware e software presenti nel STB. La combinazione di STB, piattaforma MHP e canale di ritorno, che sarà presente in prospettiva in tutte le case degli italiani, è il fattore abilitante che favorirà lo sviluppo di una molteplicità di servizi informativi, di pubblica utilità e commerciali. In questo contesto la DTT si va ad inserire come essenziale tecnologia abilitante nel processo di ammodernamento delle infrastrutture telematiche del paese, con l'obiettivo dichiarato di diventare uno degli elementi chiave nel processo di riduzione dell'analfabetismo digitale della popolazione italiana. E’ importante capire cosa possa essere e come si possa costruire il nuovo fenomeno dell'interattività televisiva, i ruoli dei diversi soggetti che opereranno nel ciclo di vita del prodotto digitale e come si struttureranno i rapporti tra i vari attori che si inseriscono in tale ciclo; attori che possono essere tradizionali, come le TV che conosciamo, oppure del tutto nuovi, come i fornitori di servizi interattivi, la loro natura, il loro ciclo di sviluppo, messa in onda ed esercizio. 5 Capitolo 1: Il Digitale Terrestre L'evoluzione prossima della DTT sarà quella dell'arrivo di STB con hard disk integrato e collegata capacità di registrazione digitale, il cosiddetto PVR (Personal Video Recording). Alcuni produttori hanno già pronto un tale oggetto, ma appaiono ancora cauti sull’opportunità di passare alla fase commerciale vera e propria. Passare dalla registrazione su cassetta a quella su hard-disk, oltre a costituire un vero grande salto nelle abitudini quotidiane dei telespettatori, introduce, infatti, alcuni effetti collaterali rispetto ai quali il mondo della televisione si sta ancora interrogando. Primo fra tutti l'insorgere di un’abitudine diffusa nel fruire della cosiddetta "time-shifted TV". Quanti di noi normalmente non riescono ad accendere la televisione nel momento giusto per vedere un film, un telegiornale, uno show dall'inizio della programmazione? Un videoregistratore digitale è l'oggetto ideale per registrare la trasmissione dal suo inizio, e renderla visibile in un qualunque momento, anche mentre è ancora in corso. Ed a quel punto posso anche "accelerare" la fruizione della parte già registrata, usando i comandi di "avanti veloce" così come avviene su un registratore VHS. Immaginate cosa potrebbe capitare agli spot pubblicitari? Negli USA, dove i PVR cominciano ad essere diffusi sulle TV via cavo e via satellite, i pubblicitari hanno dichiarato che quando tali dispositivi avranno conquistato la soglia di 30 milioni di case, gli investimenti pubblicitari fatti nelle TV si ridurranno di almeno il 20%. Conciliare evoluzione tecnologica e modelli di business è il prossimo importante passo che la TV digitale deve affrontare, e non solo in Italia. Fig. 1.2.1 – Digital Video Broadcasting Terrestre nel mondo 6 Capitolo 1: Il Digitale Terrestre 1.3 Lo standard MHP MHP è uno standard molto giovane, la prima release è stata creata dal DVB Project e standardizzata dall'ETSI Institute nell'anno 2000. Multimedia Home Platform definisce l'interfaccia tra le applicazioni interattive e i terminali sulle quali queste possono essere eseguite (Set-Top Box, digital TV e PC multimediali). L'architettura (come mostrato in figura 2) è definita dai seguenti 3 layers: Resources: MPEG processing, I/O devices,CPU, memoria e graphics system. System Software: JVM, APIs, transport protocols e soprattutto l'Application Manager (o navigatore) che permette di gestire il running delle applicazioni Java. Applications: le applicazioni interattive come ad esempio Electronic Program Guide, servizi informativi, giochi, TV-Commerce etc. Fig. 1.3.1 - Architettura dell'MHP Come già preannunciato il "core" di MHP si basa su Java e in particolare sull'adozione di una Personal JVM e di una serie di API, tra cui spiccano le Java TV di Sun. All'interno dello standard troviamo anche la definizione di 3 profili, utili ad evidenziare i servizi possibili con le relative tecnologie. Esiste inoltre un legame univoco tra i profili (Figura 1.3.2) e le release dello standard: 7 Capitolo 1: Il Digitale Terrestre Enhanced Broadcast Profile è definito nelle specifiche MHP 1.0, è il profilo base e permette solamente l'arricchimento del contenuto audio-video con informazioni e immagini visualizzabili e navigabili sullo schermo tv. Per questo profilo non sono richieste performance particolari dei set-top box. Interactive TV Profile è definito sempre nelle specifiche MHP 1.0 ed è il profilo intermedio che permette di utilizzare il canale di ritorno (sia di tipo modem, adsl, gprs, ethernet, etc) per fornire servizi con interattività superiore rispetto al profilo base. Questo profilo, infatti, supporta anche il caricamento di applicazioni MHP tramite il canale di ritorno e non solo dal canale di broadcast. Internet Access Profile è definito nelle specifiche MHP 1.1 e permette tramite il canale di ritorno di accedere ai contenuti di Internet. Questo profilo necessita di performance di alto livello essendo obbligatoria l'adozione di un browser internet e di un client di email embedded nel set-top box. Fig. 1.3.2 - Profili dell'MHP 8 Capitolo 1: Il Digitale Terrestre Alcuni esempi di applicazioni interattive possibili in MHP sono: Electronic Program Guide(EPG). Servizi di news come ad esempio un Telegiornale Interattivo. Un gioco tv. Giochi stand-alone. Servizi interattivi legati ad eventi sportivi. TV Commerce e TV Banking. Servizi Meteo, Traffico e di pubblica utilità. Pubblicità interattive. Fig. 1.3.3 - Esempi di applicazioni MHP 9 Capitolo 1: Il Digitale Terrestre 1.4 Broadcasting di applicazioni interattive Dopo aver analizzato lo standard, le tecnologie coinvolte e fornito qualche esempio di cosa è possibile realizzare, vediamo in che modo e con quali strumenti le applicazioni Java-MHP possono essere trasmesse e ricevute dai decoder interattivi. Iniziamo evidenziando uno dei già citati vantaggi della trasmissione in digitale, la maggior disponibilità di canali televisivi, analizzando il caso del digitale terrestre. Nel sistema di trasmissione analogico(PAL) il segnale occupa una banda di 8MHz, modulando il segnale in digitale, nella stessa banda è possibile avere un flusso dati di 24Mbit/sec. Considerando che un canale tv digitale grazie alla compressione A/V MPEG2 mediamente occupa 4Mbit/sec risulta evidente come sia possibile trasmettere un maggior numero di canali televisivi. Ovviamente se volessimo trasmettere anche applicazioni interattive dovremmo riservare parte della banda disponibile anche per questo tipo di dati; mediamente il peso di un applicazione comprensiva di grafica si aggira sui 500Kbyte. Lo schema generale di figura 1.4.1 ci mostra un esempio relativo all'emissione e alla ricezione del segnale digitale comprensivo delle applicazioni interattive. Nello standard MHP tali applicazioni sono chiamate Xlet e possono essere composte solo da tipologie precise di dati: classi java, file xml o txt, immagini png o jpg o gif, audio mp2 e video mpg. Le Xlet per essere broadcastate necessitano di un preventivo impacchettamento in moduli di dimensione massima di 64Kbyte. La sequenza di moduli che compone ogni applicazione viene tramessa in loop costantemente e di solito viene associata ad un particolare canale tv. Tale associazione, come evidenziato nello schema, avviene nel Multiplexer dove è possibile creare tali associazioni per ogni Xlet broadcastata. A questo punto il flusso in uscita dal Multiplexer (Trasport Stream) viene modulato in una delle 3 tipologie possibili definite da Digital Video Broadcasting: Terrestre (specifiche DVB-T), Satellite (specifiche DVB-S) e Cavo (specifiche DVB-C). Dopo la modulazione il segnale viene trasmesso e quindi ricevuto sul decoder interattivo (set-top box) che come vedremo offre la possibilità di connessioni a server dedicati o ad internet tramite modem o tecnologie più avanzate come l'ADSL. 10 Capitolo 1: Il Digitale Terrestre Fig. 1.4.1 - Emissione e ricezione del segnale digitale 1.5 Il decoder interattivo Il decoder interattivo(Set-Top Box) è l'apparecchio indispensabile per ricevere i canali digitali e utilizzare i software interattivi. Questo dispositivo permette innanzitutto la decodifica del segnale da digitale ad analogico per gli apparecchi televisivi analogici che attualmente nel nostro paese rappresentano la totalità del mercato. Infine consente di eseguire applicazioni Java grazie alla presenza di una Personal JVM embedded. Come mostra la Figura 1.5.1 un decoder è formato da una parte hardware(CPU, memoria, modem, etc), da un sistema operativo real-time e da un middleware MHP. Fig. 1.5.1 - Schema HW/SW di un Set-Top Box Un attore importante che troviamo all'interno del decoder è il cosiddetto navigatore(o application manager). Il suo compito è fondamentale, infatti esso si occupa di segnalare 11 Capitolo 1: Il Digitale Terrestre all'utente la lista delle applicazioni MHP disponibili sul canale televisivo. Inoltre se l'utente effettua una selezione da tale lista, il navigatore inizia la fase di download in memoria dei moduli relativi all'applicazione scelta. Importante sottolineare che non tutti i moduli che compongono l'applicazione verranno scaricati, infatti il download sarà relativo solo a quelli neccessari alla partenza della Xlet. Successivamente e solo se realmente necessari verranno scaricati anche quelli rimanenti. Tale meccanismo ricorda il modello di caricamento delle pagine del televideo ed è pensato allo scopo di ridurre i tempi di attesa del telespettatore. Un dispositivo tecnologico presente sul decoder che rende operativo il concetto di canale di ritorno definito nello standard è il modem nei decoder di fascia bassa e l'ADSL, Gprs, Ethernet, etc. sui decoder di fascia alta. Il canale di ritorno permette di interagire con servizi esterni, su server dedicati o internet e scaricare contenuti su richiesta del telespettatore. Nel caso di modem V90 i tempi di connessione e recupero dati sono abbastanza lunghi(30-40 sec solo per la chiamata telefonica e l'autenticazione presso un ISP), ma nel caso di connessioni "always on" come con ADSL allora gli scenari possibili sono molto più interessanti, come la possibilità di scaricare musica e video su richiesta dell'utente. 12 Capitolo 2: Applicazioni MHP Capitolo 2 Applicazioni MHP 2.1 Modello di Business e interfacce Un’applicazione mhp viene sviluppata seguendo un modello di business che si basa su più fattori: - MSO (Multiple Service Operator) i broadcaster, cioè coloro che forniscono la possibilità di scaricare le applicazioni e i contenuti per la iTV - MSV (Middleware Software Vendor) gli sviluppatori java/DVB-HTML - I fornitori dei contenuti ISP (Internet Service Provider) - Set-top-box Manufacturers - Users: gli utenti dotati di set-top-box, televisore e telecomando Date l’innumerevoli combinazioni che possono venire dall’incrocio dei diversi fattori elencati, non poche possono sembrare le difficoltà nel produrre un’applicazione MHP. Il trucco si trova nelle interfacce che lo standard MHP mette a disposizione sia a livello software (figura 2.1.1) che hardware (figura 2.1.2). MHP è basato su java, (platform indipendent), il set-top-box ha preinstallato una VM java corredata da API proprietarie e TV API di SUN, che definiscono le modalità di comunicazione tra l'utente, il set-top-box e il broadcaster. MHP ha definito anche un sotto linguaggio HTML (DVB-HTML per la precisione) con tag supplementari relativi alla cattura dell'input dell'utente (telecomanado). Il set-top-box ha preinstallato un'applicazione che si chiama Application Manager (di solito java) che gestisce il caricamento del software creando anche i menu dei canali forniti dal broadcaster. Oltre a ciò il set-top-box possiede anche un alloggiamento per smartcard (nonché un lettore per le stesse), che offre servizi sicuri come login utente, servizi a pagamento ecc.. 13 Capitolo 2: Applicazioni MHP Fig. 2.1.1 – Software layer Fig. 2.1.2 - Interfaccia tra mhp application e mhp system Per ogni tipo di interfaccia si avrà una suite di classi che permetterà di far dialogare l’applicazione con i dispositivi a disposizione: schermo, telecomando, smart card, canale di ritorno. 14 Capitolo 2: Applicazioni MHP 2.2 Xlet Una Xlet è una particolare applicazione Java concepita per essere scaricata ed eseguita su decoder interattivi nei quali un software specifico, l'Application Manager, è in grado di controllarne il ciclo di vita. Come vedremo le similitudini con le Applet, dove l'application manager è rappresentato dal browser sono molteplici. Per iniziare a costruire una Xlet occorre innanzitutto capire cosa viene fornito da Sun nelle API Java TV. Infatti è proprio in queste librerie che vengono definite le due interfacce fondamentali da utilizzare: • javax.tv.xlet.Xlet che definisce i seguenti metodi: public void initXlet(XletContext ctx); public void startXlet(); public void pauseXlet(); public void destroyXlet(boolean unconditional); • javax.tv.xlet.XletContext che definisce i seguenti metodi: public void notifyDestroyed(); public void notifyPaused(); public java.lang.Object getXletProperty(java.lang.String key); public void resumeRequest(); Entrambe le interfacce servono per interagire con l'application manager in merito al ciclo di vita e al contesto in cui la Xlet viene eseguita. La classe principale di ogni Xlet deve sempre implementare javax.tv.xlet.Xlet per poter essere eseguita sui decoder interattivi MHP. Come si mostra in Figura 2.2.1 il ciclo di vita di una Xlet è caratterizzato da 4 stati: Loaded: in questo stato la Xlet è stata creata ma non inizializzata, se durante questa fase viene rilevata un eccezione allora si passa direttamente nello stato Destroyed. Da notare che la Xlet si può trovare in questo stato solo una volta durante tutto il suo ciclo di vita. Paused: la Xlet è inizializzata e può trovarsi in questo stato sia dopo che il metodo initXlet(XletContext) è ritornato con successo dallo stato Loaded oppure dopo che il metodo pauseXlet() è ritornato con successo dallo stato Active. In entrambi i casi in questo stato la Xlet dovrebbe limitare al massimo l'utilizzo delle risorse condivise e soprattutto non impegnare la GUI televisiva. Active: in questo stato la Xlet è attiva e utilizza le risorse necessarie per fornire i suoi servizi, se dotata di GUI allora dovrebbe essere l'unica registrata per ricevere gli eventi 15 Capitolo 2: Applicazioni MHP del telecomando. Destroyed: in questo stato la Xlet deve rilasciare tutte le risorse in uso per predisporsi alla terminazione. Fig. 2.2.1 - Ciclo di vita di una Xlet Una sequenza tipica di questo ciclo potrebbe essere: L' application manager crea una nuova istanza di una Xlet chiamando il suo costruttore (stato LOADED). La Xlet usa il context passatogli dall'application manager nel metodo initXlet(XletContext) e si inizializza (stato PAUSED). L'application manager chiama il metodo startXlet() e la Xlet inizia ad utilizzare le risorse necessarie per fornire i servizi per cui è stata costruita(stato ACTIVE). L'application manager chiama il metodo destroyXlet(true) e la Xlet rilascia le risorse, dopodiché viene terminata (stato DESTROYED). Il primo esempio: HelloXlet.java import javax.tv.xlet.Xlet; import javax.tv.xlet.XletContext; import javax.tv.xlet.XletStateChangeException; public class HelloXlet implements Xlet{ // L'XletContext è un oggetto usato dalla Xlet per accedere // alle properties del suo environment e per comunicare con // l'application manager che glielo ha passato nella fase // di inizializzazione. private XletContext context; private boolean alreadyActive = false; 16 Capitolo 2: Applicazioni MHP // Il costruttore tipicamente viene lasciato vuoto visto che tutte // le inizializzazioni dovrebbero stare nel metodo initXlet() public HelloXlet(){} // In questo metodo vengono fatte tutte le inizializzazioni, // se in questa fase qualcosa fallisse verrebbe lanciata un // eccezione. public void initXlet(XletContext context) throws XletStateChangeException{ // si memorizza il context passato dall'Application Manager this.context = context; System.out.println(this.getClass().getName()+" : Inizializzazione avvenuta!"); } // Con questo metodo la Xlet è avviata e vengono richieste // le risorse necessarie come ad esempio lo schermo TV o // gli eventi del telecomando public void startXlet() throws XletStateChangeException{ // controlliamo se la Xlet era già stata messa in stato Active // precedentemente if (alreadyActive){ System.out.println(this.getClass().getName()+" : Hello Again TV World! "); }else { System.out.println(this.getClass().getName()+" : Hello TV World"); alreadyActive = true; } } // Con questo metodo la Xlet viene messa in pausa e dovrebbe // rilasciare tutte le risorse utilizzate public void pauseXlet(){ System.out.println(this.getClass().getName()+" : Xlet in pausa!"); // notifichiamo al Context l'avvenuta pausa della Xlet context.notifyPaused(); } // Con questo metodo la Xlet viene terminata, tramite il // parametro boolean la Xlet ha la facoltà di accettare // tale richiesta; nel caso non accettasse di // terminare dovrebbe lanciare un eccezione. public void destroyXlet(boolean unconditional) throws XletStateChangeException { if (unconditional) { System.out.println(this.getClass().getName()+" : la Xlet è stata terminata!"); }else { System.out.println(this.getClass().getName()+ " : la Xlet ha accettato di essere terminata!"); } // notifichiamo al Context l'avvenuta terminazione della Xlet context.notifyDestroyed(); } } 17 Capitolo 2: Applicazioni MHP 2.3 Gestione delle risorse scarse I Set Top Box sono sistemi che presentano risorse limitate a livello hardware come poca memoria e processori “lenti”, rispetto agli attuali PC in commercio, quindi per poter utilizzare le varie periferiche: input (telecomando), output (decoder Mpeg2 per visualizzare le immagini su video) e modem si devono usare delle accortezze, le precedenti periferiche vengono chiamate “risorse scarse”. Se le risorse non vengono gestite in maniera ottimale potrebbe succedere che un'applicazione non sia in grado di funzionare correttamente o non sia in grado di sfruttare tutte le potenzialità del STB. Le API che andremo a considerare sono le resource notification API che sono state definite da DAVIC nel package org.davic.resource . Una cosa da tenere in considerazione è che queste non sono API di gestione delle risorse, in quanto questa viene lasciata al middleware del ricevitore ma ben si di notifica. Servono quindi a notificare all'applicazione se una risorsa è stata tolta, oppure se è richiesta da un'altra applicazione concorrente. Le resource notification API sono sostanzialmente composte da tre classi: • ResourceServer • ResourceClient • ResourceProxy Tali classi non hanno funzionalità autonoma ma forniscono dei concetti comuni ai quali le API che definiremo di “ utilizzo” dovranno attenersi per svolgere i loro compiti. Questo significa che essendo queste delle “interface” saranno le classi che le implementano a definirne nello specifico i loro compiti. Queste API si basano sul modello client-server con piccole variazioni. L'interfaccia ResurceServer è implementata da quelle API di utilizzo responsabili di effettuare la richiesta di risorse scarse, e di gestirne la loro allocazione. Tali risorse scarse potrebbero essere di tipo software, ad esempio il section filter, oppure di tipo hardware come per esempio un modem oppure un decoder MPEG, le API non fanno distinzione e le trattano tutte in maniera simile. Il codice sorgente dell'interfaccia ResurceServer è il seguente: 18 Capitolo 2: Applicazioni MHP public interface ResourceServer { public abstract void addResourceStatusEventListener( ResourceStatusListener listener); public void removeResourceStatusEventListener( ResourceStatusListener listener); } Come si può vedere questa interfaccia non definisce dei metodi specifici per attuare una richiesta di accesso esclusivo ad una risorsa scarsa oppure per rilasciarla, questa è una scelta voluta perché sarà compito delle API di utilizzo fare questo nella maniera più consona. Gli unici metodi che questa interfaccia definisce permettono di registrare un ascoltatore degli eventi che riporterà i cambiamenti di stato della nostra risorsa. Il modo con cui noi riserviamo una risorsa come il modem è infatti completamente differente da quello con cui gestiamo il un decodificatore MPEG per esempio. Vedremo in seguito come la classe ConnectionRCInterface attraverso il metodo reserve() andrà a richiedere l'accesso esclusivo sul modem. L'interfaccia ResurceClient è come si può capire dal nome il lato client delle API. Questa interfaccia viene implementata da quelle classi che richiedono l'accesso alla risorsa scarsa, e viene utilizzata dal middleware per richiedere il rilascio della risorsa oppure per informarla che il rilascio è già avvenuto. Il codice sorgente è il seguente: public interface ResourceClient { public abstract boolean requestRelease( ResourceProxy proxy, Object requestData); public abstract void release( ResourceProxy proxy); public abstract void notifyRelease( ResourceProxy proxy); } 19 Capitolo 2: Applicazioni MHP Come possiamo vedere questa interfaccia mette a disposizione del middleware tre metodi per comunicare all'applicazione di rilasciare la risorsa. Il metodo requestRelease() è il meno incisivo dei tre perché lascia al client di scegliere se soddisfare la richiesta oppure rifiutare. Nel caso in cui il client abbia ancora bisogno di trattenere la risorsa ritornerà false e il middleware dovrà se necessario attuare un sistema di rilascio forzato. Altrimenti nel caso in cui la risorsa non è più necessaria per il client esso riporterà true ed il middleware potrà procedere con il rilascio. Il metodo release() è il più aggressivo perché obbliga al nostro client di rilasciare la risorsa senza scelta. Quando il metodo ritorna, il middleware sarà certo di poter fornire la risorsa ad un altro client che ne fa richiesta. Se il metodo release() non ritorna in tempo accettabile il middleware può ritenere che l'applicazione sia andata in crash e metterà comunque a disposizione la risorsa rimuovendone i diritti di utilizzo. Se succede questo il middleware chiamerà il metodo notifyRelease() dell'interfaccia ResourceClient, andando a informare l'applicazione che è stata privata della risorsa scarsa e che dovrà quindi agire terminando i processi che la impiegavano. Questa è una operazione brutale per il client ma viene utilizzata quando questo non collabora con il middleware. L’interfaccia ResourceProxy mette a disposizione solo un metodo di notifica utilizzato per restituire il client della risorsa a cui esso fa riferimento ed è composta come di seguito: public interface ResourceProxy { public ResourceClient getClient(); } La classe che implementa ResourceProxy si pone nel mezzo tra il client e la risorsa da utilizzare questo porta ad un grande livello di sicurezza perché l’applicazione ottiene un accesso mediato alla risorsa e ciò per permettere al middleware di controllarla. Le classi che implementano il proxy mettono a disposizione dei metodi che possono essere utilizzati per fare il set-up delle risorse. In questo modo quando si attuano più cicli di request/release delle risorse non si dovrà tutte le volte settare i parametri ma 20 Capitolo 2: Applicazioni MHP questi verranno forniti dal proxy precedentemente impostato. Con una risorsa come il modem, dove ogni volta, per effettuare una connessione, bisogna fornire numero telefonico, username e password questo comporta un elevato risparmio di tempo. Un altro beneficio portato è che le classi che implementano il client possono allacciarsi a più proxy differenti, ognuno dei quali capace di fornire un setting differente per i parametri della risorsa, sarà poi l’applicazione a decidere quali proxy utilizzare nel momento in cui ne fa richiesta. Vediamo di capire meglio cosa succede quando un’applicazione richiede l’accesso a una risorsa seguendo i passi di seguito illustrati: 1. Innanzi tutto l’applicazione, o meglio le classi che implementano il client creano un’istanza del ResourceProxy appropriato e settano tutti i parametri del caso in base alla risorsa che andranno a utilizzare: 2. Il client va a chiamare un metodo appropriato sulle API che implementano il ResourceServer e richiedono accesso alla risorsa passando il ResourceProxy come parametro: 3. Se l’accesso alla risorsa è garantito il server chiama un metodo privato sul proxy per convalidarlo: 21 Capitolo 2: Applicazioni MHP 4. Si stabilisce un collegamento diretto tra il proxy e la risorsa, collegamento che fino a questo momento non esisteva per ragioni di sicurezza: 5. A questo punto la nostra applicazione può cominciare ad utilizzare la risorsa utilizzando i metodi del proxy,sarà poi questo a riportare le richieste alla risorsa: Il processo di rilascio delle risorse è sostanzialmente l’inverso di quello precedentemente illustrato: 1. Quando il client ha terminato di utilizzare una risorsa chiama un metodo appropriato sul server per rilasciare la risorsa passandogli il proxy come parametro: 22 Capitolo 2: Applicazioni MHP 2. Ora il server chiamerà un metodo appropriato sul proxy per invitarlo a rilasciare la risorsa a cui fa riferimento invalidandolo: 3. Nel caso in cui l’applicazione è cooperante il proxy procede con il rilascio della risorsa: Vediamo cosa succede quando un’applicazione “maliziosa” non vuole rilasciare la risorsa, ed interviene il middleware: 1. il server chiama il metodo release() sul client; 2. il client fallisce nel ritorno da questa chiamata e continua a trattenere la risorsa; 3. dopo un certo periodo di tempo il middleware notifica al server che la richiesta non ha avuto un responso; 23 Capitolo 2: Applicazioni MHP 4. il server chiama un metodo privato sul proxy per informarlo che non e più valido e che non può più trattenere la risorsa; 5. il proxy rompe il collegamento con la risorsa; 6. il middleware chiama allora il metodo notifyRelease() su quella classe che implementa l’interfaccia ResourceClient. In questo modo l’applicazione sa che non ha più accesso alla risorsa e che si dovrà comportare di conseguenza; 7. tutti i futuri tentativi di accesso alla risorsa da parte dell’applicazione non avranno alcun effetto. L’aver descritto in maniera cosi approfondita, come avviene la gestione delle risorse e quali sono le API Java che ci permettono di fornire supporto per la notifica di gestione da parte del middleware, ci da la possibilità in futuro di introdurre i package che contengono le classi da utilizzare per stabilire una connessione, impostarne i parametri, settare un background, spostare e/o ridimensionare il video che come vedremo implementano le classi del package org.davic.resource. 24 Capitolo 2: Applicazioni MHP 2.4 Modello grafico Il modello grafico definito dallo standard MHP è basato su tre differenti piani (o layers) come mostrato in Figura 2.4.1. Fig. 2.4.1 - Layer grafici dell'MHP Partendo da dietro, rispetto a chi guarda la TV, troviamo il Background layer che può contenere un solid color o una still image (rappresentata da un particolare frame MPEG2, quello di tipo I), avanti troviamo il Video layer rappresentato dal flusso A/V del canale TV o di una qualsiasi altra fonte in formato MPEG-2. L’ultimo livello è il Graphics layer che può contenere la grafica creata nella Xlet, che a sua volta può essere strutturata su n-livelli sovrapposti. Tutti e tre i livelli lavorano ad una risoluzione standard di 720x576 o 768x576 se viene impostato il modo di visualizzazione su schermo 4:3. Per chi è abituato a fare applicazioni grafiche su PC non ci sono molte differenze, ma è il caso di tenere in considerazione alcuni aspetti: • i pixel non sono perfettamente quadrati; • il file che costituisce il background deve essere un’immagine video in formato mpeg2 (.mpg) in loop e non un immagine statica (.jpg), dato che il STB usa per la sua visualizzazione lo stesso decoder del video layer. (Programma di conversione consigliato .jpg -> .mpg “ImageMagic”); 25 Capitolo 2: Applicazioni MHP • le linee spesse 1 pixel non vengono visualizzate perfettamente o addirittura non sono visibili (consigliato spessore >=2); • le coordinate assolute (x,y) partono da 0,0 angolo superiore sinistro; • possono essere usate coordinate indipendenti dalla risoluzione chiamate “normalizzate” e si esprimo con numeri decimali da 0.0 a 1.0 supportate nelle API Havi; • nella gestione del livello grafico è assicurata la visualizzazione completa in un area ridotta rispetto alle coordinate totali, detta SAFE AREA di ampiezza 562x460. 0,0 79,58 Safe Area 641,518 720,576 Dopo aver visto le accortezze da usare andiamo ad analizzare in dettaglio, con porzione di codice commentato, i tre livelli di grafica. 2.4.1 Background layer Per settare uno sfondo sulla nostra applicazione bisognerà innanzitutto tener conto che la periferica che andremo ad utilizzare, il decoder mpeg2 è una risorsa scarsa, quindi per effettuare qualsiasi tipo di settaggio bisognerà prima acquisirla usando le resource notification API introdotte nei paragrafi precedenti. Il package più importante da importare per la gestione di questo layer è org.havi.ui.* , riportiamo la classe più rilevante “HScreen”: 26 Capitolo 2: Applicazioni MHP Method Summary HBackgroundConfiguration getBestConfiguration(HBackgroundConfigTempla te[] hbcta) Returns an HBackgroundConfiguration from an HBackgroundDevice which is present on this HScreen that best matches at least one of the specified HBackgroundConfigTemplates, or null if this is not possible. HGraphicsConfiguration getBestConfiguration(HGraphicsConfigTemplate [] hgcta) Returns an HGraphicsConfiguration from an HGraphicsDevice which is present on this HScreen that best matches at least one of the specified HGraphicsConfigTemplates. HVideoConfiguration getBestConfiguration(HVideoConfigTemplate[]) Returns an HVideoConfiguration from an HVideoDevice which is present on this HScreen that best matches at least one of the specified HVideoConfigTemplates. HScreenConfiguration[] getCoherentScreenConfigurations(HScreenConfi gTemplate[] hscta) Return a coherent set of HScreenConfigurations matching a set of templates. HBackgroundDevice getDefaultHBackgroundDevice() Return the default background device for this screen. HGraphicsDevice getDefaultHGraphicsDevice() Return the default graphics device for this screen. static HScreen getDefaultHScreen() Returns the default HScreen for this application. HVideoDevice getDefaultHVideoDevice() Return the default video device for this screen. HBackgroundDevice[] getHBackgroundDevices() Returns a list of background devices for this screen. HGraphicsDevice[] getHGraphicsDevices() Returns a list of graphics devices for this screen. static HScreen[] getHScreens() Returns all HScreens in this system. HVideoDevice[] getHVideoDevices() Returns a list of video device for this screen. 27 Capitolo 2: Applicazioni MHP Vediamo in breve le operazioni da eseguire: per prima cosa si sceglie lo schermo da usare mediante l’oggetto HScreen, a questo oggetto bisogna associare una HGraphicsDevice che conterrà il set d’impostazioni assegnate per mezzo di HGraphicsConfigTemplate. Il set di ogni singola impostazione è eseguito dal metodo setPreference al quale viene passato il parametro e la priorità; sarà poi il midlleware a decidere se il settaggio richiesto è attuabile in base alla priorità data e alle esigenze delle altre eventuali applicazioni che sono in running. Conviene creare una classe che implementa obbligatoriamente org.davic.resources.ResourceClient dove inserire tutti i metodi per l’acquisizione, il settaggio e il rilascio della risorsa. Di seguito viene riportata la classe “BackgroundController”, creata da Steve Morris, che mostra in dettaglio oltre alle fasi sopra elencate la gestione delle eccezioni . /********************************************************************************** * THIS SOFTWARE IS COPYRIGHT STEVEN MORRIS 2002. ALL RIGHTS RESERVED * THIS SOFTWARE IS FREE FOR NON COMMERCIAL USE FOR THE PURPOSE OF LEARNING *MHP. ANY USE FOR OTHER PURPOSES IS PROHIBITED UNLESS WRITTEN AGREEMENT * IS OBTAINED. * DISTRIBUTION OF THIS CODE IS PROHIBITED */ // Import the HAVi UI classes that we need for this import org.havi.ui.HScreen; import org.havi.ui.HBackgroundDevice; import org.havi.ui.HBackgroundConfiguration; import org.havi.ui.HBackgroundImage; import org.havi.ui.HStillImageBackgroundConfiguration; import org.havi.ui.HConfigurationException; import org.havi.ui.HPermissionDeniedException; import org.havi.ui.HBackgroundConfigTemplate; // Since some of the graphics configuration requires scarce resources, we // need to use the DAVIC resource notification API import org.davic.resources.*; // We need these (under some circumstances) to let our background be seen import javax.tv.service.selection.*; import javax.tv.media.AWTVideoSizeControl; import javax.tv.media.AWTVideoSize; import java.awt.Rectangle; import javax.tv.xlet.XletContext; 28 Capitolo 2: Applicazioni MHP /** * This class handles the display of background images in an easy-to * use way. Setting this up can be quite complex, and so in the case * we've relegated it to a separate class to make it a little easier to * understand. */ class BackgroundController implements org.davic.resources.ResourceClient{ // The background device that we will use for displaying the image private HBackgroundDevice backdevice; // The configuration for that background device private HStillImageBackgroundConfiguration backconfig; /** * This method will initialise the video and graphics devices to allow * them to display the background image. */ public boolean init(){ // We first need to get the background device that we will use for displaying the image. To do //this, we need to first decide which // HScreen we will use. In this case (and most others), we will use // the default one. HScreen screen = HScreen.getDefaultHScreen(); // Once we have that, we get the default background device for that HScreen HBackgroundDevice backdevice = screen.getDefaultHBackgroundDevice(); // Once we have a reference to the device, we can get a configuration for it. // Get a configuration that we can use HBackgroundConfigTemplate backgroundConfigurationTemplate = new HBackgroundConfigTemplate(); backgroundConfigurationTemplate.setPreference(HBackgroundConfigTemplate.FLICKER_FILTERING ,HBackgroundConfigTemplate.REQUIRED); HBackgroundConfiguration backconfig; backconfig = backdevice.getBestConfiguration(backgroundConfigurationTemplate); // Can we reserve the device so that we can change the settings on it? if (backdevice.reserveDevice(this)){ // If we can, we set the configuration of the background device to // the one we got above - this is the default configuration for this device. try{ backdevice.setBackgroundConfiguration(backconfig); }catch (Exception ex){ System.out.println("Can't initialise the background device"); // Release the device so that other applications can use it, if necessary. backdevice.releaseDevice(); return false; } // We need to check if we can put an image in the background in // this configuration, since we can't do this in every configuration. if(backconfig instanceof HStillImageBackgroundConfiguration){ // We can use this this.backconfig = (HStillImageBackgroundConfiguration)backconfig; this.backdevice = backdevice; return true; }else{ // If we can't, we again release the device since it's no use to us. backdevice.releaseDevice(); } } return false; } 29 Capitolo 2: Applicazioni MHP /** * Free the resources we needed to display background images. * Some implementations leave the image there, but there is an explicit * warning in the MHP specification that this may not happen on all * implementations. If you want to be sure that your image is still * visible, don't do this. */ public void dispose(){ // Check if we have something to dispose of if (backdevice != null){ // RZlease the device and clear any references backdevice.releaseDevice(); backdevice = null; backconfig = null; } } /** * Display a background image */ public void display(String filename){ // Check we have the resources we need to display a background image if(backconfig != null) { // Create a new background image. The image is loaded from the // filename that we pass in. HBackgroundImage backimage = new HBackgroundImage(filename); // Now display the image. This can throw several exceptions, so we // enclose it in a 'try' block try { backconfig.displayImage(backimage); } catch (java.io.IOException ioe) { // Ignore it, but print a message to tell the user what's happened. System.out.println("Can't display background image - IO exception"); ioe.printStackTrace(); } catch (HPermissionDeniedException hpde) { // We don't have permission to displayan image. We just ignore it. System.out.println("Can't display background image - permission denied"); } catch (HConfigurationException hce) { // We don't have permission to displayan image. We just ignore it. System.out.println("Can't display background image - configuration exception"); hce.printStackTrace(); } } } 30 Capitolo 2: Applicazioni MHP Implementazione metodi di org.davic.resource: /************************************************************************** * * These methods are inherited from the ResourceClient interface and are used * to tell the application when it has lost access to its resources (or * when it is about to lose access to them). This gives the application a * chance to clean up when it loses access to a resource, and gives it a * chance to handle things gracefully. */ /** * This method gets called when the resource manager requests that we give * up a resource. We can refuse to do so, and that's what we do in this * case (even though we shouldn't). */ public boolean requestRelease(ResourceProxy proxy, Object requestData) { return false; } /** * This method gets called when the resource manager informs us that we must * release a resource */ public void release(ResourceProxy proxy) { // release control of the background device backdevice.releaseDevice(); } /** * This method gets called when the resource manager tells us we've * lost access to a resource and we should clean up after ourselves. */ public void notifyRelease(ResourceProxy proxy) { // release control of the background device. Even though we don't // have control of it, this makes sure everything is synchronized. backdevice.releaseDevice(); } L’ultima cosa da aggiungere per quanto riguarda la gestione di questo livello è che, per rendere visibile il background, bisognerà ridimensionare o nascondere del tutto il livello superiore, ovvero il video layer. Trascurando la gestione del primo livello si possono implementare interfacce usando il livello grafico e come sfondo avranno la trasmissione corrente del canale dove si è sintonizzati. 31 Capitolo 2: Applicazioni MHP 2.4.2 Video layer La gestione del video layer è molto importante non solo per l’aspetto estetico della nostra interfaccia grafica. Non dobbiamo dimenticare, che il broadcaster ha si interessi a fornire l’applicazione MHP, ma si deve preoccupare anche della trasmissione che sta andando in onda. Mentre in una fase iniziale, nelle prime applicazioni, si preferiva ridimensionare il video e spostarlo in modo da non sovrapporsi a menù, pulsanti etc. ora la tendenza e di lasciarlo invariato il più possibile, usando trasparenze o sovrapposizioni di grafica temporanee, in modo da distogliere il meno possibile l’attenzione del telespettatore dalla trasmissione. Comunque ci sono casi in cui l’applicazione può pretendere e merita tutta l’attenzione dell’utente, basta pensare all’inserimento dati o alla conferma di un movimento in una applicazione e-banking per esempio. Quindi è bene alternare fasi, come la navigazioni di menù, dove il video sia in primo piano e altre in cui venga eliminato o ridotto. Per realizzare tutto ciò ci vengono in aiuto le classi javaTV della Sun, ecco un esempio di due metodi per nascondere o ridimensionare e posizionare il video tratti dalla classe “BackgroundController”, creata da Steve Morris. import javax.tv.service.selection.*; import javax.tv.media.AWTVideoSizeControl; import javax.tv.media.AWTVideoSize; import java.awt.Rectangle; import javax.tv.xlet.XletContext; /** * Hide the video that may be obscuring our background */ public void hideVideo(XletContext context) { // The background may be hidden by the video when the Xlet starts up. To // solve this, we have to do two things: // 1) stop the video // 2) resize the video so that we can see the app underneath it. This is // not always necessary, but some emulators (xletview, I'm looking at you) // don't hide the video when you stop it // Get a reference to the JavaTV ServiceContextFactory ServiceContextFactory factory; factory = ServiceContextFactory.getInstance(); 32 Capitolo 2: Applicazioni MHP // From this, we can get a reference to the parent // service context of our Xlet. To do this, we need a // reference to our Xlet context. It's times like this // that show why your application should always keep a // reference to its Xlet context ServiceContext myContext = null; try { myContext = factory.getServiceContext(context); } catch (Exception e) { e.printStackTrace(); } if (myContext != null) { // ServiceContentHandler objects are responsible for // presenting the different parts of the service. This // includes the media components ServiceContentHandler[] handlers; handlers = myContext.getServiceContentHandlers(); for(int i=0; i < handlers.length ; i++) { if (handlers[i] instanceof ServiceMediaHandler) { // This is a Player for part of the service, since // ServiceMediaHandler objects are instances of JMF // Player objects javax.media.Player p = (javax.media.Player) handlers[i]; // this may be all we need to do in order to see our background... p.stop(); p.deallocate(); // ...but some emulator require more AWTVideoSizeControl awtVideoSizeControl; awtVideoSizeControl = (AWTVideoSizeControl) p.getControl("javax.tv.media.AWTVideoSizeControl"); // in this case, we set the size of the video to be 0, but we could set it // to display on part of the screen awtVideoSizeControl.setSize(new AWTVideoSize(new Rectangle(0, 0, 720, 576), new Rectangle(0,0,0,0))); } } }else { System.out.println("Can't get our service context. May not be able to " + "resize the video, so our background may not be visible"); } } public void resizeVideo(XletContext context,int x,int y,int l,int h) { // The background may be hidden by the video when the Xlet starts up. To // solve this, we have to do two things: // 1) stop the video // 2) resize the video so that we can see the app underneath it. This is // not always necessary, but some emulators (xletview, I'm looking at you) // don't hide the video when you stop it // Get a reference to the JavaTV ServiceContextFactory ServiceContextFactory factory; factory = ServiceContextFactory.getInstance(); 33 Capitolo 2: Applicazioni MHP // From this, we can get a reference to the parent // service context of our Xlet. To do this, we need a // reference to our Xlet context. It's times like this // that show why your application should always keep a // reference to its Xlet context ServiceContext myContext = null; try { myContext = factory.getServiceContext(context); } catch (Exception e) { e.printStackTrace(); } if (myContext != null) { // ServiceContentHandler objects are responsible for // presenting the different parts of the service. This // includes the media components ServiceContentHandler[] handlers; handlers = myContext.getServiceContentHandlers(); for(int i=0; i < handlers.length ; i++) { if (handlers[i] instanceof ServiceMediaHandler) { // This is a Player for part of the service, since // ServiceMediaHandler objects are instances of JMF // Player objects javax.media.Player p = (javax.media.Player) handlers[i]; // this may be all we need to do in order to see our background... // ...but some emulator require more AWTVideoSizeControl awtVideoSizeControl; awtVideoSizeControl = (AWTVideoSizeControl) p.getControl("javax.tv.media.AWTVideoSizeControl"); // in this case, we set the size of the video to be 0, but we could set it // to display on part of the screen awtVideoSizeControl.setSize(new AWTVideoSize(new Rectangle(0, 0, 720, 576), new Rectangle(x,y,l,h))); } } }else { System.out.println("Can't get our service context. May not be able to " + "resize the video, so our background may not be visible"); } } Prima di ridimensionare o eliminare addirittura il video layer è bene aver impostato una immagine nel background layer come descritto nel paragrafo precedente, onde intercorrere a videate nere o addirittura alla non visualizzazione del livello grafico che ora andremo a trattare. 34 Capitolo 2: Applicazioni MHP 2.4.3 Graphics layer Il livello grafico è supportato da diversi package messi a disposizione dallo standard; uno dei fondamentali è Havi 1.1. In tale package il container di più alto livello in una Xlet è rappresentato dalla classe org.havi.ui.HScene, che concettualmente è equivalente a java.awt.Window o java.awt.Frame ma con caratteristiche specifiche per i decoder digitali, ne riportiamo i metodi principali: Method Summary void dispose() Disposes of this HScene. java.awt.Image getBackgroundImage() Retrieve any image used as a background for this HScene. int getBackgroundMode() Get the background mode of this HScene. boolean isOpaque() Returns true if the entire HScene area, as given by the java.awt.Component#getBounds method, is fully opaque, i.e. boolean isVisible() Determines if the HScene (or more properly its added child components) is Visible. void paint(java.awt.Graphics g) HScene objects override the paint method (defined in java.awt.Component) to paint the added "child" components on top of an optional background color or image. void setActive(boolean focus) Set whether the HScene is prepared to accept focus. void setBackgroundImage(java.awt.Image image) Set an image which shall be painted in the background of the HScene, after the background has been drawn according to the current mode set with setBackgroundMode, but before any children are drawn. void setBackgroundMode(int mode) Set the background mode of this HScene. void setVisible(boolean visible) Shows or hides this HScene depending on the value of the input parameter visible. void show() Shows this HScene, and brings it to the front. 35 Capitolo 2: Applicazioni MHP La classe factory che ci permette di richiedere l'unica istanza della HScene è fornita dallo stesso package e si chiama org.havi.ui.HSceneFactory. MHP supporta anche le classi AWT contenute nella versione 1.1.8 del JDK Sun ma spesso è possibile trovare un equivalente nel package Havi e che quindi dovrebbe essere preferita (es: org.havi.ui.HContainer è più specifica per MHP di java.awt.Container). Oltre ad Havi e AWT è possibile utilizzare anche un package specifico DVB org.dvb.ui dove è possibile trovare classi di utilità come la DvbColor che permette di gestire anche le trasparenze tramite il parametro alpha non presente nella classe java.awt.Color. Data l’importanza vediamone un tipo di costruttore: DVBColor (int r, int g, int b, int a) dove r,g,b sono i colori primari red, green, blue e “a”- alpha è la trasparenza, tutti e quattro i parametri hanno un range da 0 a 255. Non tutti i tipi di decoder MHP gestiscono le trasparenze in questo modo, ma lo standard garantisce almeno 3 livelli: 0 % (opaco), 100 % (completamente trasparente), 30 % di trasparenza. Il font di sistema supportato in MHP è il “Tiresias”, che deve essere disponibile almeno nelle seguenti dimensioni: • 36 punti (Title) • 31 punti (Subtitle) • 26 punti (Body) • 24 punti (Footnote) I formati grafici supportati sono: png, jpg, gif e mpg (I-Frame). Vediamo un esempio di codice che recupera l'istanza di HScene e ci posiziona un HContainer e un HText. HSceneFactory hsf = HSceneFactory.getInstance(); HScene scene = hsf.getFullScreenScene( HScreen.getDefaultHScreen().getDefaultHGraphicsDevice());// risoluzione a schermo pieno scene.setSize(720,576); scene.setLayout(null); HContainer cont = new HContainer(50,50,650,500); HText text = new HText("Casella di testo!",160, 200, 300, 60, new Font("Tiresias", Font.PLAIN, 26),Color.black,Color.white, new HDefaultTextLayoutManager()); cont.add(text); scene.add(cont); scene.setVisible(true); scene.repaint(); 36 Capitolo 2: Applicazioni MHP Un altro oggetto molto utile nella creazione di un’interfaccia è HIcon che permette di caricare un immagine e visualizzarla, per esempio per creare un bottone, in dettaglio: HIcon (java.awt.Image image, int x, int y, int width, int height) utilizzando java.awt.Toolkit ecco il nostro bottone: bottone = new HIcon(Toolkit.getDefaultToolkit().getImage("bottone.jpg"), 50, 100, 140, 30); Per dare facilmente l’effetto che il pulsante venga premuto si possono creare due oggetti HIcon con le rispettive immagini, pulsante rilasciato/pulsante premuto, posizionati sulle stesse coordinate e agire con il metodo setVisible(true/false) per nascondere l’uno e visualizzare l’altro. 37 Capitolo 2: Applicazioni MHP 2.5 Gestione degli eventi del telecomando La gestione del telecomando è banale per chi conosce Java, si basa sugli ascoltatore di eventi; in MHP si possono usare i package org.havi.ui.event che necessita del focus di un’ oggetto grafico oppure org.dvb.event. Iniziamo dal primo caso, nel package sono definite le costanti che corrispondono al codice restituito dal metodo getKeyCode di java.awt.event.KeyEvent ad ogni pressione di un tasto. L’ascoltatore degli eventi va scene.addKeyListener((KeyListener)this) aggiunto e rimosso nel nel metodo metodo initXlet: destroyXlet: scene.removeKeyListener((KeyListener)this). Come mostrato in Figura 2.5.1 gli eventi associati alla pressione dei un tasto del telecomando definiti nell'MHP sono solamente quelli numerici, le frecce di navigazione, il tasto “OK”, il tasto teletext e i quattro tasti colorati (rosso,verde,giallo,blu). Fig. 2.5.1 - Input event in MHP Da notare che lo standard MHP non definisce eventi per i tasti “EXIT” e “BACK” che sono presenti sulla quasi totalità dei decoder e comunque gestiti da org.havi.ui.event.HRcEvent ma potrebbero generare eventi non-standard e non sempre coerenti sui diversi apparecchi. Vediamo ora un esempio di codice necessario per l’implementazione del primo metodo, il codice seguente deve risiedere nella classe principale della Xlet. 38 Capitolo 2: Applicazioni MHP public void keyPressed (KeyEvent key) { int pulsantePremuto = key.getKeyCode(); switch(pulsantePremuto){ //Tastierino numerico case KeyEvent.VK_0: case KeyEvent.VK_1: case KeyEvent.VK_2: case KeyEvent.VK_3: case KeyEvent.VK_4: case KeyEvent.VK_5: case KeyEvent.VK_6: case KeyEvent.VK_7: case KeyEvent.VK_8: case KeyEvent.VK_9: //Tasti colorati case HRcEvent.VK_COLORED_KEY_0: //rosso case HRcEvent.VK_COLORED_KEY_1: //verde case HRcEvent.VK_COLORED_KEY_2: //giallo case HRcEvent.VK_COLORED_KEY_3: //blu //Tasti direzionali case HRcEvent.VK_UP: case HRcEvent.VK_DOWN: case HRcEvent.VK_RIGHT: case HRcEvent.VK_LEFT: case HRcEvent.VK_ENTER: //OK //Vengono passati gli eventi. interfaccia.keyPressed(key); break; //Alla pressione del tasto EXIT del telecomando si //richiama il metodo destroyXlet, per fermare la xlet. case HRcEvent.VK_ESCAPE: try{ destroyXlet(true); } catch (XletStateChangeException xsce){ System.out.println("Premuto tasto EXIT "+xsce.toString()); } break; default: break; } } public void keyTyped(KeyEvent ignored) { } public void keyReleased(KeyEvent ignored) { } 39 Capitolo 2: Applicazioni MHP Essendo il primo metodo ampiamente documentato e conosciuto vediamo nel dettaglio quello specifico del DVB. Tale meccanismo è consigliabile rispetto ad AWT perché permette un uso più oculato e collaborativo nella gestione degli eventi. Infatti rispetto ad AWT dove la registrazione degli eventi è esclusiva e comprende tutti gli eventi possibili, nel modello dvb è possibile registrarsi per la notifica dei soli eventi a cui si è realmente interessati. Le classi principali di questo package sono la EventManger e la UserEventRepository che gestiscono rispettivamente la registrazione degli eventi e il repository degli eventi a cui si è interessati. UserEventRepository repository = new userEventRepository("UserRepository"); repository.addAllColourKeys(); EventManager manager = EventManager.getInstance(); // this rappresenta la classe listener manager.addUserEventListener(this, repository); Questo approccio ha l'ulteriore vantaggio di non imporre alla Xlet di richiedere il focus tramite la HScene (come prevede il modello awt sui Component) risulta quindi essere l'unico approccio da seguire in tutte quelle applicazioni in cui si vogliono ricevere eventi del telecomando ma non si dispone una GUI. 40 Capitolo 3: Canale di ritorno Capitolo 3 Canale di ritorno 3.1 Lo scenario L’infrastruttura trasmissiva per il digitale terrestre consente l’invio broadcast di contenuti televisivi, radio ed informatici (es. dati) a ricevitori DTT quali: televisori associati ad un “decoder” supplementare o Set Top Box, televisori digitali corredati di decoder interno, altri apparecchi (sintoamplificatori, videoregistratori, sistemi AV compatti, etc.) che incorporano un sintonizzatore DTT. A ciascun “programma” (televisivo, radio, informatico) il Content Provider può associare un servizio informazioni sul programma, un servizio Teletext tradizionale, un servizio “super Teletext” (“Enhanced Broadcasting”) supportato dallo standard “mhp” (Multimedia Home Platform); quest’ultimo, infatti, realizza e presenta sullo schermo del televisore “pagine” contenenti testo e grafica al cui interno è possibile “navigare” in modo molto simile ad Internet. Se poi il ricevitore DTT è corredato da un modem (analogico, ISDN o xDSL), connettendosi ad Internet o ad altre reti di telecomunicazioni, la DTT diventa lo strumento attraverso il quale sviluppare servizi interattivi più o meno estesi (da forme minimali di “televoto” realizzato collegandosi a numeri telefonici o a indirizzi Internet corrispondenti al voto che si vuole esprimere, fino all’impiego di sofisticate applicazioni di commercio elettronico, e-government, fornitura di servizi Video On Demand, etc). Più in generale, l’interattività estesa è destinata all’impiego di servizi Internet. 41 Capitolo 3: Canale di ritorno Fig. 3.1.1 - Scenario canale di ritorno Tutte tali possibilità (che si comprendono meglio utilizzando un ricevitore) sono ampiamente chiarite già nell’introduzione dallo Standard ETSI ES 201 812 V1.1.1 (2003-12) relativo e Digital Video Broadcasting (DVB) e Multimedia Home Platform (MHP) Specification 1.0.3 , capitolo 0, paragrafo 0.2: “… At the beginning the following application areas are considered - Enhanced Broadcasting, Interactive Broadcasting and Internet Access. Enhanced Broadcasting combines digital broadcast of audio/video services with downloaded applications which can enable local interactivity. It does not need an interaction channel. The application area Interactive Broadcasting enables a range of interactive services associated or independent from broadcast services. This application area requires an interaction channel. The application area of Internet Access is intended for the provisioning of Internet services. It also includes links between those Internet services and broadcast services. …” In pratica, il protocollo mhp è costituito da una raccolta di “metodi java” (programmi di base) che consentono di accedere alle funzioni di base del ricevitore DTT e di far partire automaticamente o manualmente, utilizzando il telecomando, le applicazioni java trasmesse dal Content Provider Broadcaster. Queste “applicazioni Java” sono a tutti gli 42 Capitolo 3: Canale di ritorno effetti dei programmi informatici che utilizzano, in luogo di un personal computer o di un PDA, le capacita di elaborazione e lo schermo del ricevitore DTT. Pertanto, le applicazioni di “Enhanced Broadcasting” costituiscono il presupposto per il lancio di applicazioni di “Interactive Broadcating”, che consentono il lancio delle applicazioni di “Internet Access” realizzate attraverso l’uso di reti PSTN, ISDN o xDSL (si prevede che la prossima generazione di Set Top Boxes incorpori anche tecnologia WiFi). Si noti che una volta attivato il “canale di interattività”, il ricevitore DTT può diventare autonomo dalle applicazioni broadcast e, per esempio, accedere, puramente via Internet. ad un servizio “pay per view” o “pay to download” via xDSL. Sempre attraverso il “canale di interazione”, il ricevitore può continuare a ricevere nuovi programmi java che sviluppano nuove funzionalità. Di fatto, nel corso della attivazione, l’utente può configurare nel ricevitore DTT il numero di telefono, il codice utente e la password fornitagli dal proprio fornitore di accesso ad Internet, ma i servizi di “Enhanced Broadcasting” e “Interactive Broadcating” possono imporre al ricevitore DTT interattivo di utilizzare per l’“Internet Access” numero di telefono, codice utente e password diversi da quelli predefiniti dall’utente. In altre parole, l’accesso alle applicazioni di “Enhanced Broadcasting” rappresenta una insuperabile barriera alla realizzazione e alla fornitura di servizi della società dell’informazione diretti a quel 60% della popolazione che non usa il computer, ma che dopo lo switch off previsto per il 31 dicembre 2008, userà un televisore DTT. Accordi tra content provider su piattaforma DTT (che hanno il potere di associare al programma televisivo o radiofonico opportune applicazioni) e alcuni operatori di rete fissa, potrebbero avere l’effetto di una totale esclusione da tale mercato dei restanti operatori di rete fissa. Il pericolo è particolarmente elevato nel caso di Telecom Italia che, attraverso Telecom Italia Media, dispone già oggi di un accesso ai servizi di “Enhanced Broadcasting” e “Interactive Broadcating” su piattaforma DTT, ma non è da sottovalutare l’ipotesi che anche gli altri operatori di rete DTT possano seguire politiche discriminatorie. In assenza di adeguate misure volte a garantire l’accesso alle applicazioni di “Enhanced Broadcasting” (che, si è visto, costituiscono il presupposto per il lancio di applicazioni di “Interactive Broadcating”), l’accesso al “canale di ritorno” del decoder a condizioni 43 Capitolo 3: Canale di ritorno oggettive e non discriminatorie tra i diversi operatori di telecomunicazioni e fornitori di accesso ad Internet e ad assicurare l’ interoperabilità dei servizi, si potrebbe assistere ad inquietanti fenomeni di “walled garden”, consistenti in un’offerta integrata di contenuti Internet (es. Video On Demand, e-commerce, etc…), promossi per il tramite delle applicazioni di Enhanced ed Interactive Broadcasting, accessibili mediante la DTT (sfruttando il canale di ritorno del decoder) esclusivamente per il tramite di uno specifico fornitore di accesso e contenuti Internet predeterminato dal fornitore della rete e/o del servizio DTT, così impedendo l’accesso degli utenti del servizio DTT ai servizi/contenuti/siti dei content providers non predeterminati dal gestore della rete e/o del servizio DTT, che verrebbero ad essere relegati in una sorta di “universo recintato”, a scapito della concorrenza, del pluralismo e, da ultimo, a danno degli utenti finali. 44 Capitolo 3: Canale di ritorno 3.2 I protocolli supportati Per comprendere appieno le potenzialità del canale di ritorno, che si possono utilizzare, partiremo dall’analisi dei protocolli supportati da MHP che sono mostrati in figura 3.2.1. Fig. 3.2.1 - Protocolli canale di ritorno 3.2.1 Livello 1: Fisico A livello fisico (Livello 1), si definiscono le caratteristiche del mezzo trasmissivo, del tasso di trasmissione e gli schemi di codifica su cui avverrà la comunicazione. I ricevitori DTT attualmente in commercio almeno per quanto riguarda quelli interattivi, sono dotati al loro interno di un modem 56K che permette di aprire una connessione su canale bidirezionale sfruttando la linea telefonica commutata. Tuttavia il canale di ritorno via modem analogico (PSTN) presenta i seguenti problemi: • banda disponibile ridotta ; • impossibilità di uso contemporaneo del servizio di fonia sulla stessa linea telefonica; • canale non always-on, con conseguente necessità di effettuare eventualmente diverse aperture e chiusure di connessione durante una sessione di utilizzo del servizio interattivo; 45 Capitolo 3: Canale di ritorno • tempi di attesa dovuti alla procedura di connessione; • costi di utilizzo basati sulla durata della connessione a circuito; • canale con flusso di dati bilanciato, proprietà non utile in molte applicazioni. Lo standard PSTN (definito nelle specifiche ETSI ETS 300 801) è sicuramente uno standard affermato e ancora di largo consumo, ma che limita enormemente quelle che sono le possibilità di creare applicativi di alto livello. Uno dei vantaggi di questa scelta è però la garanzia che vi sia un più vasto numero di utenti che può, data la ormai affermata tecnologia e la vasta disponibilità sul territorio, sfruttare i servizi interattivi messi a disposizione sul digitale terrestre. Il fatto che questa tecnologia sia accessibile da quasi la totalità della popolazione è un fattore da non sottovalutare per poter rendere il DTT una tecnologia aperta a tutti. In futuro con una prevista evoluzione dei servizi messi a disposizione, si inizieranno a vedere sul mercato STB di fascia media con supporto per linea ISDN oppure di fascia alta con connessione a banda larga ADSL, anche se ci sarà ancora molto da aspettare dati i costi attualmente più elevati rispetto a quelli di fasci bassa. L’ ultima ipotesi è certamente la più interessante, perché da la possibilità di avere una connessione sempre attiva, cosa non permessa dai normali ricevitori con modem PSTN che chiudono la chiamata al Centro Servizi dopo un tempo prestabilito di inattività, ma soprattutto darà la possibilità di portare sul digitale terrestre applicazioni che ora sono riservate solo ai PC su Internet. 3.2.2 Livello 2: PPP A livello di collegamento (Livello 2) si trova il protocollo PPP (Point-to-Point Protocol) che è ad oggi il protocollo più utilizzato per il collegamento a Internet degli host residenziali tramite doppino telefonico. Standardizzato in RFC 1661 e RFC 2152 . Il PPP gestisce il riconoscimento degli errori, supporta molti protocolli, permette che gli indirizzi IP siano stabiliti al momento della connessione e supporta l’autenticazione. I vantaggi del PPP sono: • i frame vengono suddivisi in maniera tale da garantire una non ambigua separazione tra un frame e quello successivo; 46 Capitolo 3: Canale di ritorno • possiede un protocollo di controllo di collegamento per la gestione delle linee che prende il nome di LCP (Link Control Protocol); • le opzioni del livello di rete sono negoziate in maniera indipendente dal protocollo che si utilizzerà a livello di rete. Si avrà quindi un diverso NCP (Network Control Protocol) per ogni livello rete supportato. Nel nostro caso vediamo come il STB riesce ad acquisire un indirizzo IP dal Centro Servizi per poter poi comunicare con esso ed accedere ai servizi interattivi messi a disposizione. Innanzi tutto si esegue la chiamata verso il CS ad un numero che può essere univoco per tutto il territorio impostato di default dall’utente oppure settato dalla Xlet. Dopo che il modem del CS ha risposto e stabilito una connessione a livello fisico nel nostro caso su linea commutata, il STB manda al router una serie di pacchetti LCP inseriti nel campo Payload di uno o più frame PPP. I pacchetti inviati andranno a selezionare i parametri PPP da utilizzare nella comunicazione. Una volta selezionati i parametri, si passa all’invio dei pacchetti NCP per configurare il livello rete. Nel nostro caso il ricevitore vuole eseguire un insieme di protocolli TCP/IP e quindi necessita di ottenere un indirizzo IP. Ogni CS avrà a disposizione un set di indirizzi IP da assegnare e ne verrà assegnato dinamicamente uno a chi ne fa richiesta, una volta eseguita la connessione e per tutta la sua durata. A questo punto il nostro STB e formalmente un host Internet e può inviare e ricevere pacchetti IP proprio come se fosse connesso fisicamente con CS. La disconnessone avviene quando NCP sgancia la connessione a livello di rete e va cosi a liberare l’indirizzo IP. Quindi LCP sgancia la connessione del livello di collegamento e quindi la PPP ed infine il STB ordina al modem interno di liberare la linea telefonica rilasciano la connessione a livelli fisico. 47 Capitolo 3: Canale di ritorno 3.2.3 Protocollo di rete: IP Il livello di rete, è occupato dal protocollo IP (Internet Protocol), che nel suo datagram incapsula tra i numerosi campi previsti, l’indirizzo IP del server di destinazione, cioè quello del Centro servizi e quello del client, quindi quello assegnato dal CS stesso al momento dell’autenticazione in maniera dinamica. Il suo compito è quello di garantire che i pacchetti arrivino dalla sorgente, nel nostro caso il STB, alla destinazione il CS, indipendentemente dalla presenza di reti intermedie durante il suo percorso e indipendentemente dal fatto che più utenti stiano utilizzando nello stesso momento l’applicazione per mettersi in contatto con il CS. In pratica sarà il livello di trasporto che riceverà il flusso di dati ed andrà a frammentarlo in datagram. Ogni datagram viene poi trasmesso e frammentato ulteriormente durante il percorso, una volta che tutti i pezzi hanno raggiunto la destinazione, vengono riassemblati dal livello di rete nel datagram di partenza. Il datagram riassemblato viene poi passato al livello trasporto, che lo inserisce nel flusso di input del processo ricevente. Il protocollo IP supporta sopra di se i protocolli TCP e UDP entrambi previsti dalle specifiche MHP. 3.2.4 Protocolli di trasporto: TCP / UDP I protocolli di trasporto supportati sono sia il TCP che l’ UDP questi si differenziano principalmente per il fatto che il primo e orientato alla connessione mentre il secondo e sostanzialmente fondato sull’IP e non orientato alla connessione. Il TCP (Transmission Control Protocol) progettato per garantire connessioni affidabili, necessita di una entità di trasporto TCP, che va a gestire i flussi di dati TCP e si interfaccia con il livello IP. I datagram IP ricevuti che contengono dati TCP vengono passati all’entità TCP che ricostruisce il flusso originale di byte, precedentemente spezzettato dall’entità TCP del trasmettitore. Il livello IP non da garanzia sulla avvenuta consegna dei datagram; e quindi compito del TCP ritrasmetterli quando necessario. Un altro compito del TCP è riassemblare i messaggi nella sequenza corretta tutte cose che da solo IP non può fare. 48 Capitolo 3: Canale di ritorno Il TCP si basa su punti accesso chiamati socket, sia il client (STB) che il server (CS) ne devono possedere uno univoco. Ogni socket e caratterizzato da un indirizzo IP che identifica l’host e da un numero di 16 bit detto “porta”, per esempio l'HTTP comunica sulla porta 80. Il protocollo UDP (User Data Protocol) è un protocollo non orientato alla connessione, è usato per spedire datagram IP, senza dover stabilire una connessione, in maniera più rapida ma senza garantire il controllo degli errori e del flusso dei pacchetti. 3.2.5 Hypertext Transfer Protocol (HTTP) Lo standard HTPP (Hyper Text Tranfer Protocol) è il protocollo che sta alla base del WWW. Le specifiche MHP incentivano l'uso di questo protocollo come possiamo vedere dalla figura 3.2.1, questo perché il suo utilizzo da la possibilità di trasferire informazioni di varia natura, come file di testo, immagini, ecc.. L'HTTP è un protocollo orientato alla comunicazione tra un client ed un server. Come si può vedere anche in figura esso è supportato dal TCP, ma nonostante questo resta comunque un protocollo “senza stato”, quindi ogni transazione viene trattata in maniera indipendente dalle altre almeno per quanto riguarda l'HTTP 1.0. Viene quindi creata una connessione TCP ogni volta che il STB dovrà interagire con il Centro Servizi per lo scambio dei dati. I terminali MHP di nuova generazione sono nati per supportare sia HTTP 1.0 (IETF RFC 1945[92]) che HTTP1.1 (IETF RFC 2616[40]) con alcune considerazioni del caso soprattutto per quanto riguarda le connessioni di tipo keep-alive. I terminali MHP e quindi le applicazioni che implementano i profili Interactive TV Profile e Internet Access Profile, che sfruttano il protocolli HTTP 1.0/1.1 dovranno supportare connessioni di tipo persistente. L' HTTP 1.1 è stato introdotto per colmare le lacune del vecchio HTTP1.0 in quanto una delle caratteristiche fondamentali è quella di stabilire una sola connessione TCP per tutta la durata della comunicazione. Il fatto che nella maggior parte dei casi il terminale MHP comunicherà per tutta la durata della connessione con un unico ISP, il nostro Centro Servizi, è opportuno stabilire una connessione sempre attiva per tutta la durata della comunicazione evitando problemi di congestione della rete. Per stabilire una connessione di tipo persistente i terminali MHP che sfruttano il 49 Capitolo 3: Canale di ritorno protocollo HTTP1.0 dovranno inserire all'interno del messaggio HTTP nel campo Connection del General-Header il token Keep-Alive: Connection: keep-alive Il server HTTP che si vede arrivare un messaggio di richiesta di connessione KeepAlive risponderà instaurando una connessione persistente. Se si fa questo,il campo Keep-Alive del General-Header, del messaggio HTTP, conterrà il tempo durante il quale il trasmettitore mantiene la connessione aperta in attesa della richiesta successiva, oppure il numero massimo di richieste aggiuntive che sono consentite sulla connessione in corso. Il campo Keep-Alive sarà composto come sotto: Keep-Alive-header="Keep-Alive" ":" 0# keepalive-param keepalive-param = param-name "=" value dove la notazione ''0#'' significa che i parametri potranno essere ripetuti 0 o più volte e saranno separati da '','' se il campo ne contiene più di uno. Se i terminali MHP dovranno comunicare con un server proxy HTTP1.0 non sarà necessario inviare il token Keep-Alive, questo perché un proxy HTTP1.0 non ubbidisce alle regole dell' HTTP1.1 per fare il parsing del campo Connection. Questo significa che si dovrà impedire ai terminali MHP di richiedere connessioni sempre attive quando questi devono comunicare con server proxy che non ne potrà supportare le funzionalità. I server di tipo HTTP1.1 che si basano su connessioni persistenti dovranno supportare anche l' HTTP1.0 per richieste da parte di terminali MHP che lavorano con protocollo HTTP1.0 e che cercheranno comunque di instaurare connessioni sempre attive utilizzando il token Keep-Alive. 50 Capitolo 3: Canale di ritorno 3.2.6 Secure Hypertext Transfer Protocol (HTTPS) Nei casi in cui si richiede un canale di comunicazione protetto con il CS, le specifiche MHP consigliano l'uso del protocollo HTTPS. Questo protocollo è sostanzialmente basato sull'HTTP ma affianca ad esso il protocollo TLS (Transport Layer Security). TLS 1.0 è molto simile a SSL 3.0 anche se non è compatibile con questo. Lo scopo di TLS è quello di permettere una comunicazione sicura tra due applicazioni, fornendo al flusso di dati servizi di autenticazione, integrità e riservatezza. Il profilo MHP non implementa l'intero TLS ma solamente una parte degli algoritmi di cifratura previsti tra cui in particolare : • RSA () • MD5 • SHA-1 • DES 3.2.7 Servizi Specifici Qualora si trovasse la necessita di supportare da parte dei ricevitori protocolli proprietari per il canale di ritorno lo standard MHP lascia ai costruttori la possibilità di definirne alcuni e di portarli sulle piattaforme. Ad oggi tutti i ricevitori STB in commercio utilizzano protocolli di uso comune che garantiscono più robustezza, integrità e maggiore possibilità di integrazione alla rete Internet per una prospettiva futura di accesso a contenuti Web con l'introduzione del profilo “Internet Access Profile”. 51 Capitolo 3: Canale di ritorno 3.3 Gestione della connessione Indipendentemente dal tipo di modem di cui è provvisto il set top box, le connessioni sono gestite dal package Org.dvb.net.rc in questo paragrafo andremo a vedere nel dettaglio le classi di questo package e cercheremo di capire come queste interagiscono tra di loro per poter poi essere utilizzate per stabilire una connessione con un ISP remoto. Successivamente daremo uno sguardo agli altri package, java.net e java.io, che verranno utilizzati una volta che la connessione è stabilita. 3.3.1 Utilizzare il canale di ritorno: il package org.dvb.net.rc Tra i tre profili che MHP introduce sia “Interactive Broadcast” che “Internet Access”, includono il supporto per utilizzate il canale di ritorno sui STB. In base al tipo di ricevitore che stiamo utilizzando il canale di ritorno può assumere diverse forme in base alla fascia di appartenenza, anche se ad oggi quella più comune è il modem PSTN, che garantisce: una maggiore reperibilità sul territorio, quindi un bacino di utenza più ampio e semplicità di installazione, anche se porta dei limiti che come abbiamo gia visto sono molto “pesanti”. I STB quando stabiliscono una connessione con canale di ritorno non fanno altro che appoggiarsi al protocollo IP del livello di rete e come molte applicazioni Java che fanno uso di una connessione remota, anche le nostre Xlet si appoggeranno alle classi del package java.net, con alcune restrizioni del caso. Le specifiche MHP 1.0.x richiedono supporto per HTTP1.0 e DNS su canale di ritorno al di sopra del TCP, ma ogni altro protocollo è opzionale, questo lascia un po’ di liberta anche se può portare a dei mal funzionamenti, sarà quindi compito del programmatore fare le opportune verifiche. Con MHP 1.1 le specifiche aggiungono il supporto per HTTPS un protocollo per connessioni sicure che unisce l’ HTTP con i certificati TLS. Le specifiche non aggiungono niente per quanto riguarda il supporto ad altri protocolli come FTP o SMTP. Anche se l’ultimo è sicuramente necessario per la creazione di un client e-mail. Una considerazione va fatta per quanto riguarda la gestione della connessione, in quanto le API java.net assumono che una connessione permanente sia eseguibile, è questo 52 Capitolo 3: Canale di ritorno può diventare un problema da non sottovalutare specialmente quando il nostro STB usa un modem PSTN. In certi casi non ci dovremo curare di gestire la connessione, sarà il ricevitore a connettersi automaticamente all’ISP di default, dovremmo solo preoccuparci di utilizzare correttamente le classi java.net.socket e java.net.URLConnection per fare riferimento ad un file remoto. In maniera simile il ricevitore dopo un periodo di inattività andrà a disconnettere automaticamente il canale di ritorno, questo porta ad una sorta di sistema di sicurezza da xlet maligne che però è facilmente aggirabile. 3.3.2 Interfacce per il Canale di Ritorno Ogni tipo di interfaccia supportata dai STB è rappresentata da un’istanza della classe RCInterface : public class RCInterface extends java.lang.Object { public static final int TYPE_PSTN = 1; public static final int TYPE_ISDN = 2; public static final int TYPE_DECT = 3; public static final int TYPE_CATV = 4; public static final int TYPE_LMDS = 5; public static final int TYPE_MATV = 6; public static final int TYPE_RCS = 7; public static final int TYPE_UNKNOWN = 8; public static final int TYPE_OTHER = 9; private int type; private int dataRate; protected RCInterface() { } public int getType() { return this.type; } public int getDataRate() { return this.dataRate; } } Con questa classe è possibile modellare nome, velocità in Kbps e tipo di interfaccia da utilizzare per ottenere una connessione IP. Come possiamo vedere questa classe definisce un insieme di costanti dove ognuna rappresenta un tipo di canale di ritorno. 53 Capitolo 3: Canale di ritorno COSTANTI TYPE_PSTN TYPE_ISDN TYPE_DECT TYPE_CATV TYPE_LMDS TYPE_MATV TYPE_RSC TYPE_UNKNOWN TYPE_OTHER TIPO DI CANALE PSTN ISDN DECT Cable modem Local Multipoint Distribution System. Canale di Ritorno Wireless Master antenna TV DVB-RCS (Canale di ritorno via satellite) Utilizzato quando è presente una interfaccia hardware tra il ricevitore e il dispositivo senza conoscerne i dettagli Usato per standard futuri di cui DVB non intende aggiungere costanti proprie Tabella 3.3.2.1 – Costanti canale di ritorno Il metodo getType()ritorna un intero corrispondente al tipo di interfaccia a cui la classe ConnectionRCInterface fa riferimento. Mentre il metodo getDataRate() ritorna il massimo “data rate” dell’interfaccia a cui siamo connessi in questo momento, se nessuna interfaccia è connessa il ricevitore ci restituirà il valore dell’ultima interfaccia utilizzata oppure il valore –1 se non reperibile. 54 Capitolo 3: Canale di ritorno 3.3.3 Acquisire l’interfaccia corretta Come abbiamo visto nelle specifiche MHP esiste più di un tipo differente di interfaccia, in base al supporto che implementa il canale di ritorno, questo rende necessaria la presenza di una classe che sia in grado di ottenere l’accesso a quella corretta e ne controlli l’utilizzo in base al quello che dovrà fare la nostra xlet. A tal proposito e stata creata la classe RCInterfaceManager che ci fornisce alcuni metodi per controllare l’accesso a questo tipo di risorsa scarsa. public class RCInterfaceManager implements org.davic.resources.ResourceServer { public static RCInterfaceManager getInstance(); public RCInterface[] getInterfaces(); public RCInterface getInterface(java.net.InetAddress addr); public RCInterface getInterface(java.net.Socket s); public RCInterface getInterface(java.net.URLConnection u); public void addResourceStatusEventListener( org.davic.resources.ResourceStatusListener listener); public void removeResourceStatusEventListener( org.davic.resources.ResourceStatusListener listener); } La classe RCInterfaceManager mette a disposizione un metodo il getInstance()che viene innanzitutto utilizzato per ottenere un’istanza di se stessa da utilizzare nei passi successivi. Una volta che abbiamo ottenuto il riferimento a questa classe possiamo procedere con l’acquisire l’interfaccia appropriata, cosi con il metodo getInterfaces() ci viene restituito un array di RCInterface in base ai permessi associati alla nostra applicazione e alla disponibilità di interfacce che il nostro ricevitore supporta. Questo array potrebbe risultare anche vuoto se la nostra applicazione non ha i permessi necessari per l’uso della risorsa magari perché viene identificata come maligna. Il metodo getInterfaces() ha tre varianti legate alla classe del package java.net che andremo ad utilizzare; java.net.URLConnection, java.net.Socket, java.net.InetAdress. Nei primi due casi il middleware del ricevitore assume che la connessione sia già attiva, questo perché sarà esso stesso che non appena nota la richiesta di connessione ad una 55 Capitolo 3: Canale di ritorno URL o ad una Socket cerca di creare una connessione su linea di ritorno, e ritornerà l’interfaccia utilizzata per questa connessione. Nell’ultimo caso invece il middleware ritornerà l’interfaccia che dovremmo poi utilizzare per stabilire la connessione. Come ho gia detto i STB in commercio sono forniti solamente di modem PSTN e quindi i metodi appena citati riporteranno se ben formati sempre l’interfaccia ‘1’ o nulla se non presente, bisognerà tenere conto di questo per non incorrere in mal funzionamenti. Essendo il canale di ritorno una risorsa scarsa, questa classe come possiamo vedere dal codice, implementa l’interfaccia ResourceServer permettendo di associare un ResourceStatusListener al nostro oggetto per informarlo sugli eventi che accadono alla risorsa. Gli eventi generati del tipo ResurceStatusEvents possono essere: • RCInterfaceReservedEvent: Questo evento viene generato quando un’applicazione ha correttamente riservato una RCInterface, può essere generato anche quando un’altra entità del sistema ha riservato la risorsa rendendola cosi non più utilizzabile. • RCInterfaceReleasedEvent: Viene generato quando un’applicazione che precedentemente aveva riservato una RCInterface ha poi deciso di rilasciare la risorsa chiamando il metodo release() della classe ConnectionRCInterface su quella interfaccia. Può essere generato anche da un’altra entità del sistema informando cosi l’applicazione che la risorsa è ora disponibile. 56 Capitolo 3: Canale di ritorno 3.3.4 Stabilire la connessione Tra le interfacce per il canale di ritorno dichiarate nella classe RCInterface alcune rappresentano una connessione sempre attiva. Il middleware non tratta quindi tutti i tipi di interfacce in maniera uguale questo perché le connessioni sempre attive come possono essere TYPE_CATV o ADSL non vengono trattate come se fossero risorse scarse, semplicemente perché l’applicazione non ha bisogno di sapere a quale provider il canale di ritorno è connesso. La gestione della connessione e quindi completamente lasciata nelle mani del middleware che si occupa di tenerla sempre disponibile nel momento in cui l’applicazione ne fa richiesta, senza preoccuparsi di troppi dettagli. Questo però non accade con altri tipi di interfacce, come per esempio con un modem PSTN, in questo caso dovremo conoscere il numero di telefono dell’ISP a cui connettersi, prima di poter utilizzare la risorsa, oltre a dati come user e password. In base al service provider al quale ci si vuole connettere potrebbe succedere che un’applicazione sia abilitata a stabilire una comunicazione, mentre altre no per problemi di sicurezza, e ciò pone dei limiti ma anche dei vantaggi. La classe ConnectionRCInterface estende la classe RCInterface ed implementa l’interfaccia ResourceProxy , di seguito possiamo vedere i metodi di cui e composta. public class ConnectionRCInterface extends RCInterface implements org.davic.resources.ResourceProxy { public boolean isConnected(); public float getSetupTimeEstimate(); public void reserve( org.davic.resources.ResourceClient c, java.lang.Object requestData) throws PermissionDeniedException; public void release(); public void connect() throws java.io.IOException, PermissionDeniedException; public void disconnect() throws PermissionDeniedException; public ConnectionParameters getCurrentTarget() throws IncompleteTargetException; public void setTarget(ConnectionParameters target) throws IncompleteTargetException, PermissionDeniedException; 57 Capitolo 3: Canale di ritorno public void setTargetToDefault() throws PermissionDeniedException; public int getConnectedTime(); public org.davic.resources.ResourceClient getClient(); public void addConnectionListener( ConnectionListener l); public void removeConnectionListener( ConnectionListener l); } Questa classe mette a disposizione i metodi necessari per riservare la risorsa, settarne i parametri ed eseguire connessione e disconnessione dall’ISP impostato. Prima di eseguire la connessione all’ISP utilizzando l’interfaccia specificata è necessario riservare la risorsa per la nostra applicazione, per fare questo si utilizza il metodo riserve() per evitare che altri cerchino di utilizzarla generando dei conflitti. Questo metodo è utilizzato per associare all’interfaccia un ResurceClient utilizzato per i messaggi di notifica sullo stato della risorsa e per quando questa viene rimossa, questo metodo può generare un’eccezione del tipo PermissionDeniedException quando la nostra applicazione non ha i permessi necessari per riservare la risorsa. L’applicazione che utilizza il canale di ritorno deve per forza essere a conoscenza dei dati che ci permettono di chiamare ad autenticarci presso un ISP e quindi prima di chiamare il metodo connect() sarà necessario che questi vengano impostati nell’interfaccia selezionata, per fare questo si utilizza il metodo setTarget(). Ogni interfaccia ha in genere un ISP associato al quale ci si connette quando la si usa, solitamente questo è strettamente legato al broadcaster che trasmette l’applicazione interattiva e che quindi mette a disposizione anche un Centro Servizi con cui interagire. Il metodo setTargetToDefault() rimuove tutti i target impostati e setta quello che il produttore del middleware ha definito come standard. Se la nostra applicazione non ha i permessi per reimpostare il target o se quello di default non è valido viene lanciata una SecurityException. Ogni target è rappresentato da un oggetto della classe ConnectionParameters, questa classe è utilizzata per creare un oggetto in cui sono definiti i parametri necessari per stabilire una connessione. 58 Capitolo 3: Canale di ritorno La classe e strutturata come segue: public class ConnectionParameters { public ConnectionParameters( java.lang.String number, java.lang.String username, java.lang.String password); public ConnectionParameters( java.lang.String number, java.lang.String username, java.lang.String password, java.net.InetAddress[] dns); public java.lang.String getTarget(); public java.lang.String getUsername(); public java.lang.String getPassword(); public java.net.InetAddress[] getDNSServer(); } Come possiamo vedere i parametri di cui una interfaccia ha bisogno sono un numero telefonico un “username” e una password che il middleware utilizza per autenticare la connessione. E’ possibile passare se necessario un array con gli indirizzi dei server DNS (Domain Name System) da utilizzare, prima di riportare un messaggio di fallimento, che vengono utilizzati per trasformare nomi di host e indirizzi di posta elettronica in indirizzi IP, da utilizzare quindi per stabilire connessioni TCP/UDP sulle socket. Chiamando il metodo setTarget() è possibile che venga lanciata una IncompleteTargetException nel caso in cui l’oggetto ConnectionParameters fosse stato creato con qualche parametro a null. Ogni cambiamento sull’oggetto ConnectionParameters avrà effetto nel momento in cui noi stabiliamo la connessione, se pero delle modifiche vengono fatte durante una connessione gia attiva per rendere effettivi i nuovi parametri sarà necessario disconnettere l’interfaccia e poi riconnetterci per puntare al nuovo ISP. Una volta impostato un target possiamo chiamare il metodo connect() sulla nostra interfaccia, ed una volta ottenuto l’IP utilizzare le classi del package java.net per connetterci all’host desiderato. Nel caso dell’interfaccia PSTN il middleware andrà a chiedere il consenso dell’utente prima che l’applicazione effettui la chiamata, se l’utente acconsente il numero telefonico viene messo in una “white list” per evitare che si richieda il consenso al prossimo accesso. 59 Capitolo 3: Canale di ritorno Se l’utente blocca la chiamata viene generato un ConnectionFailedEvent, mentre se si acconsente ed il middleware riesce a creare la connessione viene generato un ConnectionEstablischedEvent. Nel caso in cui la risorsa sia riservata ma non sia possibile stabilire una connessione con il target specificato il middleware lancerà una IOException per notificare all’ascoltare che ci sono stati dei problemi non direttamente dovuti all’hardware del ricevitore. Una volta che l’applicazione ha terminato di utilizzare la risorsa si chiama il metodo disconnect() e quindi il metodo release() per rilasciare la risorsa e permettere ad altre applicazioni di utilizzarla, lanciando l’evento ConnectionTerminatedEvent. Ottenere la connessione ad un ISP può richiedere del tempo ed avere delle complicanze, specialmente nel caso di un modem PSTN, per questo la nostra applicazione dovrà registrare come ascoltatori quelle classi che intendono sfruttare la connessione. Queste classi dovranno implementare l’interfaccia ConnectionListener e quindi definire come il metodo astratto connectionChanged dovrà comportarsi quando si vede arrivare un evento del tipo ConnectionRCEvent. La classe ConnectionListener è strutturata come segue: public interface ConnectionListener { public abstract void connectionChanged(ConnectionRCEvent e); } Le classi che implementano ConnectionListener vengono associate all’oggetto ConnectionRCInterface utilizzando il metodo addConnectionListener in modo che il middleware saprà a chi inviare gli eventi che si generano da quell’ interfaccia. Tali eventi sono di tre tipi: • ConnectionEstabliscedEvent: Generato nel caso in cui la connessione ha avuto successo. • ConnectionFailedEvent: Se non è stato possibile stabilire la connessione per motivi tecnici, se per esempio qualcuno sta utilizzando in quel momento il telefono. • ConnectionTerminateEvent: Generato nel caso in cui si rompe la connessone oppure se l’applicazione stessa ha deciso di disconnettersi. 60 Capitolo 3: Canale di ritorno Gli eventi appena descritti possono essere generati anche dall’applicazione stessa nel caso in cui si verifichino più sequenze di request sulla stessa connessione per notificare alla classe in ascolto che la sessione è ancora attiva e si possono trasferire file senza stabilirne una nuova. 3.3.5 I package java.net e java.io Ottenuta la connessione con il Centro Servizi tramite i metodi messi a disposizione dal package org.dvb.net.rc le applicazioni si vanno ad appoggiare alle API java.net per accedere alle URL. Lo standard MHP pone alcune restrizioni sulle classi che si possono utilizzare di questo package, in particolare le classi messe a disposizione da DVB-J che è la versione ridotta di Java per MHP basata oltre che sui package specifici per il DVB sulla Personal Java 1.1.8 sono: • java.net.URL • java.net.URLConnection • java.net.Socket • java.net.InetAdress Tutte queste classi possono essere utilizzate per ottenere un collegamento con il Centro Servizi tramite protocolli HTTP e HTTPS. La particolarità delle classi del package java.net è quella di essere in grado di interfacciarsi tramite l’uso della JVM con il sistema operativo della macchina su cui si sta eseguendo l’applicazione sarà poi questa a gestire l’accesso al modem e a far transitare le richieste attivando una connessione su protocollo TCP. Ottenuto il link con la risorsa desiderata ci si appoggia al package java.io per i metodi di gestione dei file veri e propri. Anche altri metodi possono essere utilizzati per gestire i file in particolare per quanto riguarda immagini ci si appoggia alla classe java.awt.image.ImageProducer e per file audio e video al package javax.media. E’ sempre opportuno controllare nelle specifiche MHP quali classi possono essere utilizzate per evitare mal funzionamenti nelle applicazioni dato che non tutti i tipi MIME sono supportati dai STB. 61 Capitolo 4: Ambiente di sviluppo Capitolo 4 Ambiente di sviluppo 4.1 Introduzione Dopo aver visto nei capitoli precedenti le tecnologie usate nel digitale terrestre, le caratteristiche principali di un’applicazione MHP, con particolare riguardo all’uso del canale di ritorno, andremo a proporre come è possibile realizzare l’ambiente di sviluppo per l’implementazione, debug e test di una Xlet. Per l’implementazione, come prima cosa, si avrà bisogno di un PC con installata una Java Virtual Machine e magari un programma di sviluppo Java, come Eclipse, per evitare errori di sintassi. Per la fase di debug e una prima fase di test è molto utile utilizzare un simulatore per applicazioni MHP, per vedere errori in fase di compilazione. Per la fase di test vera e propria ci sono due modi di procedere: • il primo consiste nel simulare un broadcaster; necessità di un modulatore COFDM al quale in ingresso verrà mandata l’applicazione insieme ad un eventuale flusso video, mentre in uscita vi sarà collegato uno (o più) Set Top Box commerciali; • il secondo, che è quello che poi andremo ad analizzare in dettaglio, consiste nell’acquisto di un Set Top Box da sviluppo, che a differenza di quelli commerciali, permette l’ upload di applicazioni non solo dall’etere, ma anche in locale, tramite porta seriale (RS-232). 62 Capitolo 4: Ambiente di sviluppo 4.2 Il Simulatore XletView Tra i vari software freeware di simulazione per applicazioni MHP, che si trovano in rete, spicca XletView, distribuito da GNU General Public License (GPL) , scaricabile all’indirizzo www.sourceforge.net. Per l’installazione si richiede che sul PC sia presente una Java Virtual Machine a partire da JRE 1.4, JSDK1.4 o versione superiore, della Sun; mentre all’interno del pacchetto troviamo già le API JMF 2.1.1 (Java Media Framework) che servono per incorporare media-data come audio e video nelle applicazioni Java, applets e Xlet. Dopo aver installato il simulatore bisogna copiare nella cartella base, le API Javatv scaricabili dal sito della Sun, praticamente le directory presenti devono essere: Fig. 4.2.1 - Directory XletView A questo punto non ci dovrebbero essere problemi per l’avvio del programma; è importante che insieme alla finestra principale ne compaia anche un’altra, dove verranno scritti gli eventuali messaggi d’errore, insieme ai messaggi di debug voluti dal programmatore, ovvero tutti i System.out.println presenti nella Xlet. 63 Capitolo 4: Ambiente di sviluppo Per fare ciò è necessario scrivere la seguente riga di comando, magari mettendola in un file batch: cd xletview java -cp %CLASSPATH%;javatv_fcs/javatv.jar;xletview.jar;jars/metouia.jar;jars/javassist.jar; jars/nanoxml-2.2.3.jar net.beiker.xletview.Main cd .. Ora le finestre visibili dovrebbero essere le seguenti come mostrato in figura 4.2.2 e 4.2.3. Fig. 4.2.2 - Finestra principale XletView Nella schermata principale vengono visualizzati: • a destra il telecomando con tutti i tasti per simulare, mediante mouse, la periferica d’ingresso; • nel riquadro giallo l’eventuale background layer, video layer e il graphics layer; • i menù per poter eseguire la nostra applicazione. Dal menu Applications, sotto ManageApplication, bisognerà inserire il classpath e il nome della classe principale della Xlet. 64 Capitolo 4: Ambiente di sviluppo Una volta che l’applicazione è stata mandata in “run”, dalla schermata di debug mostrata in figura 4.2.3, sarà possibile vedere gli eventuali errori e messaggi. Fig. 4.2.3 - Finestra di debug XletView Mentre si programma bisogna tener conto che a differenza del PC, dove abbiamo a disposizione tutte le API fornite dal JR1.4, sul Set Top Box vi è una versione ridotta della JVM , quindi bisogna fare attenzione alle specifiche sulle API che si andranno ad usare messe a disposizione su www.mhp.org e www.dvb.org, altrimenti si rischia di sviluppare applicazioni che “girano” solo su simulatore. 65 Capitolo 4: Ambiente di sviluppo 4.3 Set Top Box da sviluppo ADB X-75 Per completare il ciclo di sviluppo di una applicazione MHP, dopo che è stata sviluppata con Eclipse e mandata in esecuzione su Xletview, manca solo la fase finale di test, che come abbiamo detto consiste nel caricare l’applicazione su un Set Top Box da sviluppo. Riassumendo il nostro ambiente di sviluppo quindi sarà costituito da un Personal Computer, da una normale TV munita di presa SCART e da il Set Top Box da sviluppo ADB X-75. Le interconnessioni tra gli apparati appena elencati, come mostrato in figura 4.3.1, sono: • il STB riceve in ingresso il segnale televisivo (da una comune antenna), è collegato tramite SCART alla TV, e tramite seriale RS-232 al PC; • sia il PC che il STB sono connessi in rete (LAN) successivamente ne chiariremo il motivo. Fig. 4.3.1 - Ambiente di sviluppo 66 Capitolo 4: Ambiente di sviluppo 4.3.1 Caratteristiche Tecniche Una delle più prestigiose ditte che forniscono strumenti per lo sviluppo in campo broadcast è l’ADB, acronimo di Advanced Digital Broadcast. La serie X-75 comprende Decoder da sviluppo per tre tecnologie: Cable, Satellite, Terrestrial, in conformità con lo standard DVB, quindi è più corretto chiamare il nostro strumento ADB T.75. Andiamo ad elencare innanzitutto i componenti e le caratteristiche Hardware mostrate nella seguente tabella 4.3.1.1: ARCHITETTURA DESCRIZIONE CPU STi5517 166MHz Tuner/Front end DVB-T Flash Memory 16 MB RAM Memory 72 MB EEPROM 32 kB Power Supply 90-264 VAC, 47-63Hz Casing 440x260x50mm OUTPUTS RF input/output RF in & RF out (loop trought) Audio/Video outputs 2xRCA (Stereo Audio), 2xSCART, S/PDIF optical Return channel Ethernet 10BaseT, PSTN modem v.92 Data port Debug serial port (RS-232 up to 115.2 kbps) Front panel display 4x7-segment LED display, 2 LEDs DVB-CI slot Located on front panel Smart card slot Located on front panel ACCESSORI Power Cable 1.8 m SCART Cable 1.5 m RS 232 Cable 2.0 m 67 Capitolo 4: Ambiente di sviluppo CARATTERISTICHE DESCRIZIONE MPEG VIDEO DECODING Standards MPEG-2 MP@ML, CD ISO/IEC 13818-1, CD ISO/IEC13818-2 Video Data Rate 0.5 – 15 Mbps Format Conversion 4:3 > 16:9 with Pan & Scan and Letterbox Graphics Planes 4 planes(Background, Still-plane, Video, OSD) AUDIO DECODING Standards MPEG-1 Layer 1&2; 16 bit precision, CD ISO/IEC 13818-3 Sampling Rate 32 kHz, 44.1 kHz, 48 kHz Variable Output Level 16 steps @ 2dB per step DOLBY Digital AC3 Pass throught to S/PDIF TERRESTRIAL FRONT END – T.75 COFDM(DVB-T) ETSI EN 300 744 Modulation QPSK, QAM16, QAM64 Code rate ½, 2/3, ¾, 5/6, 7/8 Guard Interval ¼, 1/8, 1/16, 1/32 Transmission modes 2k, 8k Tabella 4.3.1.1 - Aspetti tecnici Un STB di buon livello, reperibile attualmente in commercio, presenta sicuramente le seguenti caratteristiche: • modem V.90; • lettore Smart Card; • doppia presa SCART; • uscita audio RCA; • uscita audio ottica; • connettore seriale RS-232 per eventuali periferiche di ingresso; Il nostro Set Top Box da sviluppo, oltre ad avere tutte le caratteristiche di un normale decoder, presenta: 68 Capitolo 4: Ambiente di sviluppo • una maggior capacità di elaborazione (frequenza processore più elevata) • una maggiore capacità di memorizzazione; • interfaccia Ethernet; • CI Common Interface; • presa seriale RS-232 bidirezionale (permette l’upload dell’applicazione e il debug). 4.3.2 Pannello frontale Mostriamo ora nei dettagli il pannello frontale e le funzionalità dei suoi componenti, rispettivamente in figura 4.3.2.1 e nella tabella 4.3.2.1. I componenti sono: 7 pulsanti, 2 indicatori LEDs, un display, una CI Common Interface e una OCF Smart Card slot. Fig. 4.3.2.1 - Pannello frontale ADB T.75 69 Capitolo 4: Ambiente di sviluppo Tabella 4.3.2.1 - Funzionalità pannello frontale 4.3.3 Pannello posteriore In figura 4.3.3.1 si mostra come è composto il pannello posteriore. Fig. 4.3.3.1 - Pannello posteriore ADB T.75 Vediamo in dettaglio i suoi componenti: 1. interruttore alimentazione; 2. presa alimentazione 220V ~ 50Hz; 3. connettore RS-232; 4. ingresso antenna; 5. uscita antenna (alla TV); 6. RJ11 jack modem; 7. RJ45 jack Ethernet; 8. Uscita AC3 ottica (Audio digitale); 9. SCART (connessione verso TV); 10. SCART (connessione verso eventuale VCR); 11. uscita audio RCA. 70 Capitolo 4: Ambiente di sviluppo 4.3.4 Telecomando Il telecomando e le funzionalità dei sui tasti sono mostrate in figura 4.3.4.1; non ci sono molte differenze rispetto a quello dei comuni decoder, da notare il tasto “APP” che apre e chiude la finestra delle applicazioni. Fig. 4.3.4.1 - Telecomando ADB T.75 71 Capitolo 4: Ambiente di sviluppo 4.3.5 Settaggio connessione di default Diamo uno sguardo al menù principale del decoder ADB: Tra i vari sottomenù della voce “Setting”, in fondo troviamo “Internet Connection”, una volta entrati apparirà una schermata dove è possibile scegliere se usare come predefinita la connessione tramite modem PSTN o tramite Ethernet. Internet Connection Ethernet Setting La connessione Ethernet permette di far comunicare Il STB con gli altri dispositivi della rete, a patto che al decoder sia associato un indirizzo IP; per fare questo ci sono due modi Auto e Manual . Se si imposta “Auto” dal menù, verrà assegnato un Indirizzo IP dinamico (si suppone che il server della Ethernet (Auto) rete abbia abilitato il DHCP). In caso contrario si dovrà impostare su “Manual” e Si dovranno inserire i valori: IP (statico), Mask, Default Gateway e il DNS primario e secondario. Ethrnet (Manual) 72 Capitolo 4: Ambiente di sviluppo Modem Setting Se si vuole far connettere in rete il STB, usando il Modem, bisogna impostare in questa schermata: N telefonico ISP, eventuale prefisso, tipo composizione (impulsi/tone), username, password ed eventualmente l’opzione “aspetta segnale di libero”. 4.3.6 STB Firmware upgrade Prima di passare all’upload di un’applicazione è necessario aggiornare il firmware fornito dalla casa madre. Il software che risiede nel Set Top Box (STB), consiste in due parti principali: il decoder code e il loader. Il decoder code, anche chiamato codice di alto livello, è responsabile della ricezione, codifica e visualizzazione di audio/video, e altri componenti come teletext, sottotitoli, etc. Il loader non mostra ne video ne audio, ma visualizza all’utente alcune informazioni riguardanti le fasi del processo o errori di download. L’ADB fornisce insieme al STB un software chiamato ADB FastTerm, che interagisce con il loader per aggiornare il firmware tramite la porta RS-232. L’applicazione di cui sopra, si mostra come in figura 4.3.6.1; prima di utilizzarla per prima cosa bisogna impostare il numero della porta COM (dal menù a tendina), sulla quale è connesso il STB, assicurandosi che questa sia settata con i seguenti parametri: • Baud rate • N° bit di dati • Parità • N° bit di stop 115200 8 NO 1 A questo punto, dal menù File - Open download file, si va ad aprire il file contenente l’aggiornamento (*.enc), si spegne il STB per almeno 2 secondi, si tiene premuto il tasto freccia sinistro situato sul pannello frontale e contemporaneamente si riaccende il STB. Il tasto deve continuare ad essere premuto fino a che non si accendono i LED frontali, a questo punto si lascia il pulsante e inizia il download del firmware sul STB; in questa fase la barra sulla destra dell’applicazione inizia a colorarsi fino ad arrivare al 100%. 73 Capitolo 4: Ambiente di sviluppo Fig. 4.3.6.1 - Finestra FastTerm 4.3.7 UpLoad e debug di un’applicazione La ditta produttrice del STB oltre al software “FastTerm”, fornisce anche l’ADB APPLOADER che permette di trasferire la nostra applicazione, dal PC alla memoria del decoder, sempre tramite porta seriale. In realtà la comunicazione tra i due dispositivi deve passare attraverso un secondo programma, che fa da Proxy, il quale può risiedere localmente o trovarsi in un’altra macchina, l’importante è che abbia un indirizzo IP valido e che sia connessa al STB. Quando si fa partire il Proxy, bisogna specificare il numero di porta COM (con gli eventuali settagli) dove è connesso il STB e il nome del file di log comprensivo di classpath; questo programma lavora usando il protocollo TCP sulla porta standard 4444, a meno che non specificato diversamente. Il file di log è un file di testo che viene generato dal STB ed è proprio questo che viene usato, in maniera simile al simulatore, come strumento di debug. 74 Capitolo 4: Ambiente di sviluppo La riga di comando con le varie opzioni e un’ esempio per inizializzare il Proxy sono: stbproxy.exe -com <port_number>[-port <TCP port number>] [-rate<115200,8,N,1>][-log <logfile>] stbproxy.exe –com 3 –log c:\stb.log Lavorando sotto Windows apparirà un’icona sulla barra degli strumenti, dove è possibile accedere, tra le altre cose, all’opzione “svuotare” il file di log. L’ultima cosa da fare prima di passare all’ upload, è configurare il STB, a questo ci pensa un terzo tool chiamato “stbconfig” al quale bisogna specificare l’indirizzo IP dell’host dove si trova il Proxy e l’eventuale numero di porta, se non si usa quella di default; il suo scopo è quello di abilitare/disabilitare l’opzione di debug output e security manager. La riga di comando con le varie opzioni e un’ esempio per configurare il STB ipotizzando che il Proxy sia in locale, si usi il numero di porta TCP di default e si attivi solo l’opzione di debug sono: stbconfig.exe proxy_host_IP[:port] [-debug] [-security] stbconfig.exe localhost -debug Dopo aver effettuato la configurazione del STB si richiede il riavvio dello stesso. Finalmente siamo arrivati alla fase finale, il Proxy è in running, il STB è configurato, ora manca di avviare il trasferimento dell’applicazione. Il tool stbupload richiede l’IP del proxy, il nome del description file (vedremo in seguito cosa sia) completo di classpath, e il classpath della directory base dov’è contenuta l’applicazione (*.class e file accessori). La riga di comando con le varie opzioni e un’ esempio per fare l’upload di una applicazione contenuta nella directory c:\xlet\class, supponendo il Proxy in locale con porta di default sono: stbupload <proxy_host[:port]> <xlets_descr_file> <pc_base_dir> stbupload localhost c:\xlet\xlet_desciption_file c:\xlet\class Ora l’applicazione è copiata nella directory /home del file system del STB. 75 Capitolo 4: Ambiente di sviluppo 4.3.8 Xlet description file L’Xlet description file riflette il contenuto della tabella AIT (Application Identification Table); contiene tutte le informazioni e i parametri utili per l’identificazione e l’esecuzione dell’applicazione da parte del Set Top Box. Vediamone un’ esempio: Parametri obbligatori: #app <Application ID> <Organisation ID> app 0xhex_number 0xhex_number #Application control flag: 1=autostart 2=present 3=destroy 4=kill control 1 #service bound flag (0 or 1) bound 0 #Basedir of application (must be relative to /home directory) basedir "/home" #Initial class name (fully qualified name) class "your.company.Test" Parametri opzionali: #Name of application preceded by language code name eng "Test" #Parameter of service on which the application should be visible to application manager tsid 0x7 onid 0x46 svid 0x2bd #other flags priority 137 visibility 3 #Classpath extension classpath "" #String params passed to Xlet param = "value 0" param = "value 1" 76 Capitolo 4: Ambiente di sviluppo Ora cercheremo di spiegare in dettaglio i campi principali: app Ogni organizzazione che produce Xlet, deve essere riconosciuta, ed avere un codice identificativo univoco, in più ogni applicazione prodotta da quell’ organizzazione deve anch’essa essere riconoscibile univocamente. Questo per permettere al STB di non eseguire applicazioni maligne non riconosciute. control Questo flag dice al STB come si deve comportare con l’applicazione che ha ricevuto: se = 1 la manda in esecuzione appena caricata (per avvenire effettivamente l’esecuzione, nelle impostazioni base del STB deve essere abilitata l’opzione autostart); se = 2 il STB riceve l’applicazione è la mette disponibile nell’application manager, sarà l’utente poi a decidere quando farla avviare (premendo app oppure OK, in base al modello di decoder, si visualizzano le applicazioni disponibili); se =3 viene distrutta (si usa se caricata con autostart); se =4 viene killata (si usa se caricata con present); bound Questo flag dice se si tratta di una applicazione bound (1) legata al canale e alla trasmissione o unbound (0) non legata alla trasmissione o addirittura non legata al canale (quindi disponibile indifferentemente dal canale su cui si è sintonizzati); basedir campo contenente il classpath di destinazione dell’applicazione del file system nel STB (deve essere relativo a /home); class deve contenere il nome della classe principale dell’applicazione (per es.:“main.class” ) name specifica il codice della lingua usata e il nome, relativo all’applicazione, che comparirà sull’Application Manager; priority nel caso in cui l’opzione “autostart” sia settata (nel menu di base del STB) e nell’Application Manager, vi sono più di una Xlet caricata con il control flag=1, il STB manda in esecuzione l’applicazione con la priorità più alta. 77 Capitolo 5: Implementazione di un servizio di prenotazioni Capitolo 5 Implementazione di un servizio di prenotazioni A questo punto avendo a disposizione un ambiente di sviluppo ed essendo a conoscenza delle potenzialità, dei limiti e degli standard MHP, possiamo pensare di mettere in pratica i concetti acquisiti, per implementare la nostra applicazione. Essendo il STB un dispositivo che per alcuni aspetti può essere paragonato ad un PC connesso in rete e volendo sfruttare il canale di ritorno, abbiamo dato uno sguardo alle innumerevoli applicazione che sono sul web, che svariano da semplici portali informativi fino ad arrivare a complesse applicazioni e-commerce/e-banking/e-healt. La scelta infine è stata quella di realizzare un servizio di prenotazioni “on-line” rivolto agli utenti del digitale terrestre, ovvero implementare una Xlet che si in grado di effettuare una generica prenotazione. Lo scopo di questo elaborato si basa sull’analizzare tutte le problematiche tecniche comuni che possono produrre un insieme di servizi del genere, trovarne le soluzioni ottimali e ovviamente verificarne le funzionalità. Non si è data molto importanza al tipo di prenotazione, anche se per motivi realizzativi è stato scelto un caso reale riguardante la prenotazione di “tavoli” in un generico esercizio pubblico ad esempio. Anticipiamo che l’applicazione gestirà una semplice anagrafica clienti, una fase di autenticazione (log-in) per mezzo di username e password, la visualizzazione della disponibilità del servizio, e la possibilità di prenotazione. La scelta per quanto riguarda la gestione dei dati (anagrafica e disponibilità) a favore di robustezza, sicurezza e velocità nel recupero delle informazioni è stata convergente verso l’uso di un database. 78 Capitolo 5: Implementazione di un servizio di prenotazioni 5.1 Le problematiche Le prime problematiche riscontrate riguardano l’interfaccia grafica, alcuni programmatori MHP ne paragonano la gestione a un ambiente simile a Windows “vecchio stampo” senza l’uso del mouse. La risoluzione video disponibile, infatti, per ora è ancora limitata (720x576) e non permette l’implementazione di sofisticati metodi di accesso ai menù, visto e considerato anche che per la navigazione, abbiamo solamente a disposizione il telecomando del decoder. Un altro problema si è riscontrato nell’inserimento di testo nei campi delle varie form, essendo abituati all’uso di tastiera e mouse non è stata così immediata la soluzione. Le scelte potevano essere due: creare una tastiera, simile al PC, su video, navigabile con le quattro frecce, con lo svantaggio di grosso ingombro a livello grafico e scarsa velocità d’inserimento dei caratteri; oppure quella per cui si è optato, ovvero l’implementazione di un ridotto tastierino a dodici pulsanti simile a quelli usati nei telefoni cellulari,con la possibilità di sviluppare in futuro un sistema d’inserimento simile al T9. Per quanto riguarda la gestione del canale di ritorno, ampiamente trattata nel capitolo 3, l’unico accorgimento è stato quello di differenziare i controlli (connesso/disconnesso) in base al tipo di connessione utilizzata; tramite Modem o tramite Ethernet. L’ultimo problema, e anche il più rilevante, è stato risolvere la fase di accesso a Database tramite Xlet. Considerando un database e alle modalità di accesso ai dati, tramite una piattaforma Java, viene immediatamente da pensare al potente strumento offerto da Sun, il JDBC (Java Database Connectivity). Il fatto è però, che nelle specifiche MHP e DVB, questo strumento non è nemmeno menzionato. Dopo diversi tentativi di forzare l’uso del JDBC, facendo l’upload su STB di driver meno moderni e addirittura intere API compilate con JVM simile a quella residente nel STB, si è deciso di cambiare strada. L’idea è stata quella di realizzare una applicazione Java che funge da Proxy tra il STB, quindi l’applicazione MHP, e il Database. 79 Capitolo 5: Implementazione di un servizio di prenotazioni 5.2 Interfaccia grafica L’interfaccia grafica è costituita principalmente dai seguenti elementi: uno sfondo, un menù principale costituito da tre pulsanti navigabili ciclicamente con i tasti direzionali (su/giù), una finestra orizzontale posizionata in basso dove compare una descrizione del sottomenù evidenziato come mostrato in figura 5.2.1. La gestione dello sfondo è affidata ad una specifica classe GestoreSfondi, la quale viene usata sia nella classe principale Main dal metodo startXlet(), sia ogni qualvolta servirà di ridimensionare o eliminare il video; infatti usa i metodi di BackGroundController per effettuare tutte le operazioni possibili sui primi due layer grafici. La creazione della schermata principale è affidata alla classe InterfacciaGrafica che si preoccupa di gestire per mezzo di variabili si stato, i vari componenti descritti sopra e inoltre implementa il metodo keyPressed il quale riceve l’evento tasto premuto, ne legge il codice corrispondente e in base allo stato chiama i metodi appropriati. Fig. 5.2.1 - Schermata principale 80 Capitolo 5: Implementazione di un servizio di prenotazioni Le tre scelte possibili sono: Servizi, Registrati, Prenota. Servizi serve per dare informazioni utili al cliente sull’attività dell’esercizio pubblico, registrati permette di registrarsi al servizio inserendo nome, cognome, numero di telefono, username, password; prenota permette di effettuare il log-in, visualizzare la disponibilità dei tavoli (verde libero, rosso occupato) e l’eventuale prenotazione. In questa fase si usa un delle caratteristiche principali di Java, il polimorfismo, infatti ogni opzione delle tre è implementata da una classe omonima Servizi, Registra, Prenota e tutte estendono la classe Strumenti. Nella classe Strumenti infatti sono contenuti i metodi comuni a tutte le opzioni come init, stop, mentre il metodo start e implementato diversamente per ogni strumento. Strumenti Init() Stop() Servizi Registra Prenota Start() Start() Start() Il metodo init() provvede ad attivare il tastierino, stop() lo rimuove, start() in base al servizio crea i campi per l’inserimento dati della corrispettiva form. L’implementazione della tastiera è contenuta nelle classi KeyPad e KeypadButton mentre i campi di testo da EditableTextBox. Sia entrando nel menù “Registrati” (figura 5.2.2) che “Prenotazioni” è possibile scorrere ciclicamente i campi di testo con le frecce (su/giù), premendo “ok” si inizia la compilazione del campo evidenziato, per confermare tutti i campi inseriti bisogna premere il tasto rosso, mentre per annullare l’inserimento e tornare al menù principale quello blu. In caso di username o password errati (figura 5.2.3) o già esistenti (nel caso di prima registrazione) comparirà un messaggio informativo per l’utente, realizzato dal metodo messFlash; per continuare si dovrà premere il tasto verde. 81 Capitolo 5: Implementazione di un servizio di prenotazioni Fig. 5.2.2 - Form Registrati Fig. 5.2.3 - Form Prenotazioni 82 Capitolo 5: Implementazione di un servizio di prenotazioni Una volta effettuato correttamente il log-in viene visualizzata la disponibilità dei tavoli, (usando il metodo visualizzaDisp nella classe Prenota) come mostrato nella figura 5.2.4; ora con le frecce direzionali (destra/sinistra) è possibile selezionare il tavolo desiderato, e premendo “ok” prenotarlo, sempre se non sia già occupato (colore rosso). Fig. 5.2.4 Disponibilità tavoli Per quanto riguarda la parte grafica c’è da aggiungere che si è cercato di mantenere sempre presente il video layer opportunamente ridimensionato e posizionato, ad esclusione delle fasi di inserimento dati. Il codice di questa parte di applicazione è disponibile nell’appendice A suddivisa per classi. 83 Capitolo 5: Implementazione di un servizio di prenotazioni 5.3 Accesso ai dati Come accennato all’inizio del capitolo per la gestione dei dati si usa un DBMS Mysql scaricabile dal sito www.mysql.com. Nel nostro ambiente di sviluppo sia il Database che l’applicazione Proxy risiedevano sullo stesso PC (con Java 2 Platform Standard Edition ver. 1.4.2) connesso in rete LAN; sono state anche effettuate prove lavorando con DB e Proxy remoti. La comunicazione tra Xlet e Proxy avviene tramite Socket utilizzando le API java.net e java.io, mentre tra Proxy e Database si sfruttano appieno le potenzialità del JDBC. Xlet Proxy Socket DBMS JDBC Le potenzialità di adottare questo meccanismo sono molteplici: • la comunicazione tra xlet e proxy è più sicura in quanto le socket supportano il TSL e SSL; • la Xlet fungerà solo da interfaccia grafica e penserà a gestire la connessione con il Proxy; • la Xlet in questo modo viene alleggerita dalle funzioni d’interrogazione database, può richiedere complicate query con pochi byte comunicando con il Proxy, al quale viene lasciato tutto il lavoro (mediante messaggi standard o codici proprietari comuni a entrambi); • il Proxy risiederà su un server con adeguate caratteristiche tecniche è avrà a disposizione tutte le API comprese java.sql e com.java.jdbc; • il Proxy se implementato con un sistema multithread sarà capace di gestire più richieste contemporanee e l’accesso dei dati in concorrenza; • pensando a un centro servizi dove lo stesso Database viene usato da diverse utenze:operatori che accedono da terminali remoti, utenti via web, utenti del digitale terrestre; il Proxy può fungere da interfaccia per garantire la coerenza dei dati. 84 Capitolo 5: Implementazione di un servizio di prenotazioni 5.3.1 Socket Una socket rappresenta il terminale (end-point) di un canale di comunicazione bidirezionale. Permette a due processi, residenti sulla stessa macchina o anche molto distanti, di comunicare fra loro nello stesso modo. Si basano tipicamente sul modello client/server: • il servitore deve stare in attesa di possibili comunicazioni in arrivo (ente passivo), nel nostro caso è il Proxy • i clienti (anche più di uno), nel nostro caso le Xlet, quando vogliono, comunicano con il servitore (sono enti attivi). Il protocollo usato è il TCP, il servitore crea la sua ServerSocket con un numero noto di porta e si mette in attesa. Un cliente, quando vuole comunicare col servitore, crea la sua Socket specificando con chi vuole parlare: • nome dell’host • numero di porta Questo fa scattare il protocollo di 3-way handshaking per la connessione. Il servitore accetta la richiesta dal cliente: con ciò si stabilisce una connessione e il server crea una nuova Socket già collegata al cliente, tramite la quale i due comunicano. A questo punto i processi possono parlare senza più bisogno di specificare ogni volta con chi si vuol parlare (come nelle telefonate: una volta presa la linea, tutti i segnali transitano automaticamente tra le due parti) come mostrato in figura 5.3.1.1. Fig. 5.3.1.1 - Comunicazione via Socket stream 85 Capitolo 5: Implementazione di un servizio di prenotazioni Vediamo la classe Socket nel package java.net. Alla Socket sono associati due stream: 1. uno dal cliente verso il servitore 2. uno dal servitore verso il cliente. La comunicazione cliente/servitore è bidirezionale. I ruoli “cliente” e “servitore” sono tali solo nella fase iniziale, quando si instaura la connessione. Una volta connessi, i due processi si parlano reciprocamente “alla pari”. Costruire una Socket significa aprire la comunicazione verso l’altra parte; “public Socket(String remoteHost, int remotePort)” crea una socket stream e la collega alla porta specificata della macchina remota corrispondente al nome dato. ESEMPIO Socket s = new Socket(“univpm.it”,13); oppure String str = “prova.com”; int port = 14; Socket s = new Socket(ss,port); Non bisogna specificare il nome dell’host locale, è implicito. Per il client, non bisogna specificare il numero della porta (non deve essere noto a priori). Il sistema assegna automaticamente un numero di porta tra quelli liberi. Il servizio di DNS è invocato automaticamente. Si può fare anche: “public Socket(InetAddress remoteAddr, int remotePort)” Crea una socket stream e la collega alla porta specificata della macchina remota corrispondente all’indirizzo IP dato. Vediamo ora la classe InitAddress del package java.net. Gli oggetti della classe InetAddress rappresentano indirizzi internet (IP). La classe InetAddress fornisce dei metodi per gestire facilmente gli indirizzi Internet. • public static InetAddress getByName(String host) - determina l’indirizzo IP di host. 86 Capitolo 5: Implementazione di un servizio di prenotazioni ESEMPI InetAddress.getByName(null); InetAddress.getByName(“localhost”); InetAddress.getByName(“127.0.0.1”); Una volta creata la socket per comunicare: si recuperano dalla socket i due stream (di ingresso e di uscita) tramite i metodi della classe Socket: • public InputStream getInputStream() - restituisce lo stream di input da cui leggere i dati (byte) che giungono dall’altra parte. • public OutputStream getOutputStream() - restituisce lo stream di output su cui scrivere i dati (byte) da inviare all’altra parte. Quindi su questi stream si può leggere / scrivere come su qualunque altro stream di byte. Per chiudere la comunicazione: si chiude la socket col metodo: • public synchronized void close() - chiude la connessione e libera la risorsa. Di seguito riportiamo un esempio di un semplice client e di un server multithread: 87 Capitolo 5: Implementazione di un servizio di prenotazioni //Simple Client import java.net.*; import java.io.*; public class SimpleClient { public static void main(String args[]) { try { // Apre una connessione verso un server in ascolto sulla porta 7777. In questo caso utilizziamo localhost // che corrisponde all'indirizzo IP 127.0.0.1 System.out.println("Apertura connessione..."); Socket s1 = new Socket ("127.0.0.1", 7777); // Ricava lo stream di input dal socket s1 ed utilizza un oggetto wrapper di classe BufferedReader // per semplificare le operazioni di lettura InputStream is = s1.getInputStream(); BufferedReader dis = new BufferedReader(new InputStreamReader(is)); // Legge l'input e lo visualizza sullo schermo System.out.println("Risposta del server: " + dis.readLine()); // Al termine, chiude lo stream di comunicazione e il socket. dis.close(); s1.close(); System.out.println("Chiusura connessione effettuata"); }catch (ConnectException connExc){ System.err.println("Errore nella connessione "); }catch (IOException ex){ ex.printStackTrace(); } } } //Server multithread import java.net.*; import java.io.*; public class SimpleServer { private int port; private ServerSocket server; private Socket client; public SimpleServer (int port) { this.port = port; if(!startServer()) System.err.println("Errore durate la creazione del Server"); } private boolean startServer(){ try{ server = new ServerSocket(port); }catch (IOException ex){ ex.printStackTrace(); return false; } System.out.println("Server creato con successo!"); return true; } 88 Capitolo 5: Implementazione di un servizio di prenotazioni public void runServer() { while (true){ try{ // Il server resta in attesa di una richiesta System.out.println("Server in attesa di richieste..."); client = server.accept(); System.out.println("Un client si e' connesso..."); ParallelServer pServer = new ParallelServer(client); Thread t = new Thread (pServer); t.start(); }catch (IOException ex){ ex.printStackTrace(); } } } public class ParallelServer implements Runnable { private Socket client; public ParallelServer (Socket client) { this.client = client; } public void run(){ try{ // Ricava lo stream di output associate al socket e definisce una classe wrapper di tipo // BufferedWriter per semplificare le operazioni di scrittura OutputStream s1out = client.getOutputStream(); BufferedWriter bw = new BufferedWriter( new OutputStreamWriter(s1out)); // Il server invia la risposta al client bw.write("Benvenuto sul server!\n"); // Chiude lo stream di output e la connessione bw.close(); client.close(); System.out.println("Chiusura connessione effettuata"); }catch (IOException ex){ ex.printStackTrace(); }catch (Exception e){ e.printStackTrace(); } } } public static void main (String args[]) { SimpleServer ss = new SimpleServer(7777); ss.runServer(); } } 89 Capitolo 5: Implementazione di un servizio di prenotazioni La Xlet con il Proxy si scambiano oggetti usando gli input e output stream. Gli oggetti sono di tre tipi, implementati dalle classi: ObjectPrenotation, ObjectRegistration, ObjectTavoli. Ogni oggetto contiene la struttura dati che rappresenta, coerenti con le relative tabelle del database, dove sono realmente memorizzati, e i metodi per renderli disponibili all’applicazione. Il codice della Xlet (lato client) è contenuto nell’appendice A le classi di interesse sono Comunica che ha come parametri IP del Proxy e numero della porta, PrenotaSingolo. Il codice dell’applicazione Proxy (lato server), nel nostro caso si usa un singolo thread, è contenuto nell’appendice B, dove si vede che oltre alla gestione della socket gestisce anche il recupero dati dal DB e l’invio degli stessi alla Xlet. La classe di interesse è SimpleServer; in più vi sono i .java dei tre oggetti di cui sopra. 90 Capitolo 5: Implementazione di un servizio di prenotazioni 5.3.2 JDBC Per effettuare l’interrogazione del Database da parte del nostro Proxy si usa il JDBC (Java Database Connectivity), è la parte delle API di J2SE che fornisce le primitive per la connessione a basi di dati relazionali e si basa su due semplici concetti: • si inviano comandi SQL; • si recuperano i risultati dei comandi. Il package a cui faremo riferimento è java.sql. Ogni DB ha una sua API e un suo protocollo (implementato dal driver) particolare. JDBC astrae dalle particolarità a basso livello delle API e dei protocolli, fornendo un’interfaccia comune. I driver sottostanti si preoccupano poi di effettuare la traduzione dei comandi SQL nelle interfacce native dei database supportati come mostrato in figura 5.3.2.1. L’accesso al database può essere locale o remoto. Fig. 5.3.2.1 - Schema JDBC 91 Capitolo 5: Implementazione di un servizio di prenotazioni I driver JDBC sono divisi in 4 Classi: • Classe 1: sono i driver JDBC-ODBC. • Classe 2: sono i driver che si aspettano di trovare sulla macchina su cui sono utilizzati uno strato di software, scritto in linguaggio nativo per quella Macchina/Piattaforma, i quali si preoccupano di connettersi e di operare sul Database. (nel caso di Oracle i driver in questione vengono chiamati OCI, Oracle callback Interface, e vogliono che sulla macchina su cui girano ci sia installato SQL-Net) • Classe 3: sono i driver che si aspettano di trovare un Gateway, o in generale un server a cui connettersi il quale provvederà a ritornare una connessione dal Database. (i JDBC-RMI driver sono di classe 3) • Classe 4: sono i driver totalmente scritti in Java ed autonomi. (Nel caso di Oracle vengono chiamati THIN). Vediamo le procedure principali e i metodi che le implementano per effettuare la connessione ed utilizzare un database: •Caricare il gestore di driver DriverManager •Aprire la connessione Connection •Creare un oggetto istruzione Statement •Eseguire l’interrogazione (query) executeQuery •Elaborare i risultati ResultSet •Chiudere la connessione close Drive manager implementa un gestore di driver. Il metodo (statico) per ottenere una connessione al DB è:staticConnectiongetConnection(String,String,String) che prende tre argomenti: URL, username, password. Sintassi dell’URL:–jdbc:mysql://<host>[:<port>]/<dbname> Siccome le connessioni occupano risorse, in un ambiente multiutente e multitasking è opportuno adottare la seguente politica: aprire una connessione solo quando necessario, assicurarsi di chiuderla, non aprire/chiudere connessioni inutilmente. 92 Capitolo 5: Implementazione di un servizio di prenotazioni Connection rappresenta una connessione al DB. Prima di fare qualunque cosa con un db, devo stabilire una connessione. Ad esempio, per creare un comando, devo disporre di una connessione (c): Statement s = c.createStatement(). Per chiudere la connessione, si utilizza il metodo close(). Statement rappresenta un’istruzione/comando. Ha i metodi sia per eseguire un’interrogazione (query) SQL che restituisca un insieme di dati che per eseguire una query di aggiornamento/modifica del DB: ResultSet executeQuery(String) int executeUpdate(String). Quando non serve più va chiuso con il metodo: close ResultSet rappresenta il risultato di un’interrogazione. Tramite il metodo next() possiamo spostarci da un record al successivo: boolean next(). Abbiamo a disposizione una serie di metodi per recuperare i dati memorizzati nei vari campi di ogni record, in base al tipo di dato originario del DB:String getString(int)[gli indici partono da 1!], getXxx()[Byte,Boolean, Blob, …]. E’ buona norma chiudere il ResultSet quando non serve più con il metodo: close. I Driver usati nel Proxy sono “mysql-connector-java-3.1.12”, di clase 4, da ricordare di importare l’omonimo .jar nel progetto. Il codice riguardante l’uso dei JDBC nel nostro Proxy è riportato nell’appendice B; le classi d’interesse sono: ServerPrenotaSingolo, ServerPrenotation, ServerRegistration. 93 Capitolo 5: Implementazione di un servizio di prenotazioni 5.3.3 Database Il DBMS usato in questo progetto per la precisione è MySQL Server versione 5.0. Le operazioni svolte direttamente su DB sono solamente la creazione di due tabelle: una chiamata “cliente” contenente una ridotta anagrafica dell’utente che si andrà a registrare e l’altra “tavoli” che serve per memorizzare la disponibilità corrente del servizio; contenente un codice identificativo del tavolo, lo username dell’utente che lo andrà a prenotare e un campo flag (libero/occupato). Per completezza riportiamo il DLL: CREATE TABLE `cliente` ( `user` char(20) NOT NULL default '', `password` char(20) NOT NULL default '', `nome` char(20) default NULL, `cognome` char(20) default NULL, `telefono` char(15) default NULL, PRIMARY KEY (`user`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; CREATE TABLE `tavoli` ( `num_tavolo` int(3) NOT NULL default '0', `utente` char(20) default NULL, `libero` int(1) default NULL, PRIMARY KEY (`num_tavolo`), UNIQUE KEY `IDtav` USING BTREE (`num_tavolo`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 94 Capitolo 5: Implementazione di un servizio di prenotazioni 5.4 Sviluppi futuri All’inizio del capitolo si è anticipato che il lavoro svolto è stato basato più sull’ ingegnerizzazione del software per permettere di effettuare una prenotazione “on-line” generica da Xlet, che sul servizio in sé. Il progetto, così come è descritto nei paragrafi precedenti, è stato implementato e testato su Set Top Box da sviluppo ed è risultato essere funzionante. Gli sviluppi possibili, partendo da questa piattaforma, sono innanzitutto basare l’attenzione su un servizio specifico e quindi curare di più la parte grafica e fare un’analisi dei dati ad hoc; dal lato tecnico si potrà sviluppare una procedura per velocizzare l’inserimento dati nei campi di testo, implementare il Proxy in multithread per gestire l’accesso in concorrenza di più utenti e magari implementare l’uso di lettore smart card da usare per l’identificazione dell’utente tramite carta d’identità elettronica per esempio. 95 Conclusioni Conclusioni Attualmente l'Italia, come raramente accade per il nostro paese si trova in una posizione di avanguardia e per molti aspetti sta guidando a livello europeo questa rivoluzione, soprattutto in ambito Televisione Digitale Terrestre. In commercio possiamo trovare diverse marche di decoder interattivi MHP e tutti i principali broadcaster sono impegnati a scoprire le potenzialità, i limiti e le possibili fonti di guadagno di questa nuova tecnologia. Difficile fare pronostici soprattutto in merito alla legge del nostro parlamento che sancisce entro dicembre 2008 il cosiddetto "switch-off" delle trasmissioni analogiche per passare esclusivamente al sistema digitale. Difficile anche capire se un maggior numero di canali implicherà una maggior scelta e auspicabilmente maggior qualità per noi telespettatori. Le certezze maggiori rimangono quindi quelle tecnologiche legate ad uno standard europeo aperto che mira alla convergenza del mondo terrestre, cavo e satellite, dove invece oggi regnano sovrane troppe tecnologie e standard proprietari. Siamo di fronte ad una tecnologia in grado di abilitare una delle più profonde trasformazioni che si possa pensare in una società fortemente fondata sull'immagine e sulla comunicazione come è la nostra. Un trasformazione che con ogni probabilità non si limiterà ad influenzare l'ambiente casalingo, ma si estenderà rapidamente anche ai terminali mobili e tascabili, il DVB-H e le sue applicazioni. 96 APPENDICE A: Codice Xlet APPENDICE A Codice Xlet Main.java import javax.tv.xlet.Xlet; import javax.tv.xlet.XletContext; import javax.tv.xlet.XletStateChangeException; import org.havi.ui.*; import org.havi.ui.event.*; import java.awt.event.*; //Classe iniziale è questa che implementa l'interfaccia Xlet e KeyListener e che quindi sarà //utilizzata dall'application manager per gestire il //ciclo di vita della xlet. Si occupa di inizializzare //i vari componenti, ascoltare gli eventi dal telecomando, //creare l'oggetto scene fondamentale per la parte grafica. public class Main implements Xlet, KeyListener { public XletContext context; public static GestoreSfondi gestoreSfondi; public HScene scene; public int tastoPremuto = 0; private InterfacciaGrafica interfaccia; //Costruttore vuoto. public Main() { } // Definisco un metodo che inizializza la Xlet, e i suoi componenti. public void initXlet(XletContext xletContext) throws XletStateChangeException { //xletContext utilizzato per la gestione del ciclo di vita della xlet context = xletContext; HSceneFactory hsceneFactory = HSceneFactory.getInstance(); //estraggo l'oggetto scene su cui si appoggiano i componenti grafici. scene = hsceneFactory.getFullScreenScene(HScreen.getDefaultHScreen().getDefaultHGraphicsDevice()); //le dimensioni sono quelle standard di risoluzione di un televisore analogico. scene.setSize(720, 576); scene.setVisible(false); scene.setLayout(null); //aggiungo l'ascoltatore degli eventi in questo caso //la classe stessa. scene.addKeyListener((KeyListener)this); } // Definisco il metodo che permette di mandare in esecuzione la Xlet public void startXlet() throws XletStateChangeException { System.out.println("Eseguo startXlet"); gestoreSfondi = new GestoreSfondi(context); interfaccia = new InterfacciaGrafica(scene); 97 APPENDICE A: Codice Xlet // Imposto lo sfondo e ridimensiono il video gestoreSfondi.displayBackground("background.mpg",240,100,420,320); // Inizializzo l'interfaccia grafica interfaccia.disegnaInit(); // Richiedo il Focus per la scene corrente scene.requestFocus(); scene.setVisible(true); } // Definisco il metodo per mettere in pausa la Xlet public void pauseXlet() { System.out.println("Xlet in pausa"); context.notifyPaused(); } // Definisco il metodo che distrugge la Xlet e rilascia le risorse. public void destroyXlet(boolean flag) throws XletStateChangeException { if(flag){ System.out.println("Distruggi Xlet"); /* try{ RCconnector.Disconnect(); }catch (IOException e){ e.printStackTrace(); } */ interfaccia.distruggi(); //rimuovo l'ascoltatore degli eventi. scene.removeKeyListener(this); scene.removeKeyListener((KeyListener)this); scene.setVisible(false); HSceneFactory.getInstance().dispose(scene); scene = null; gestoreSfondi.displayBackgroundExit(); context.notifyDestroyed(); } } // Ascoltatore degli eventi legati al telecomando. Gli eventi vengono //passati alla classe InterfacciaGrafica che poi li passerà alle //funzioni attive. public void keyPressed (KeyEvent key) { int pulsantePremuto = key.getKeyCode(); switch(pulsantePremuto){ //Tastierino numerico case KeyEvent.VK_0: case KeyEvent.VK_1: case KeyEvent.VK_2: case KeyEvent.VK_3: case KeyEvent.VK_4: case KeyEvent.VK_5: case KeyEvent.VK_6: case KeyEvent.VK_7: case KeyEvent.VK_8: case KeyEvent.VK_9: 98 APPENDICE A: Codice Xlet //Tasti colorati case HRcEvent.VK_COLORED_KEY_0: //rosso case HRcEvent.VK_COLORED_KEY_1: //verde case HRcEvent.VK_COLORED_KEY_2: //giallo case HRcEvent.VK_COLORED_KEY_3: //blu //Tasti direzionali case HRcEvent.VK_UP: case HRcEvent.VK_DOWN: case HRcEvent.VK_RIGHT: case HRcEvent.VK_LEFT: case HRcEvent.VK_ENTER: //OK //Vengono passati gli eventi. interfaccia.keyPressed(key); break; //Alla pressione del tasto EXIT del telecomando si //richiama il metodo destroyXlet, per fermare la xlet. case HRcEvent.VK_ESCAPE: try{ destroyXlet(true); } catch (XletStateChangeException xsce){ System.out.println("Premuto tasto EXIT "+xsce.toString()); } break; default: break; } } public void keyTyped(KeyEvent ignored) { } public void keyReleased(KeyEvent ignored) { } } InterfacciaGrafica.java import java.awt.*; import java.awt.event.*; //import java.io.IOException; //import javax.tv.xlet.XletStateChangeException; import org.havi.ui.*; import org.havi.ui.event.HRcEvent; //import javax.tv.xlet.XletContext; //Questa classe amministra la parte grafica del frame //principale gestendo l'accesso alle varie funzionalità e //passando gli eventi ai processi attivi. public class InterfacciaGrafica { public static final int MENU=0; public static final int PRIMO=1; public static final int DISP=2; public static final int POPUP=3; public static final int INIT=4; 99 APPENDICE A: Codice Xlet public static int STATO; private HScene scene; private HIcon [] vetON,vetOFF; private HText [] mess; private String como; private String [] str; private HIcon tastor; private GestoreSfondi gestoreSfondi; private Registra registra; private Servizi servizi; private Prenota prenota; private int tasto=0; //Utilizzata per il polimorfismo. private Strumenti strumento=null; //Costruttore. public InterfacciaGrafica(HScene scenemain){ //viene passato l'oggetto scene su cui verranno //aggiunti i vari componenti grafici. scene=scenemain; //STATO=INIT; } //Crea i componenti grafici e li visualizza. public void disegnaInit(){ STATO=MENU; Font font1=new Font("Arial",Font.BOLD,30); str =new String[3]; str[0]="Questo è un servizio di prenotazioni MHP"; str[1]="Inserisci tutti i campi richiesti"; str[2]="Fai il login ed effettua la prenotazione"; como=str[1]; mess= new HText[3]; for(int i=0;i<3;i++){ mess[i]=new HText(str[i]); mess[i].setFont(font1); mess[i].setForeground(Color.white); mess[i].setBackgroundMode(HVisible.BACKGROUND_FILL); mess[i].setBackground(new Color(100,100,100,120)); mess[i].setBounds(0,456,720,100); mess[i].setBordersEnabled(true); scene.add(mess[i]); mess[i].setVisible(false); } System.out.println("Entrato in disegnainit:mess ok"); vetON = new HIcon[3]; vetOFF = new HIcon[3]; vetOFF[0]=new HIcon(Toolkit.getDefaultToolkit().getImage("servizi_off.jpg"), 50, 100, 140, 30); vetOFF[1] = new HIcon(Toolkit.getDefaultToolkit().getImage("registra_off.jpg"), 50, 135, 140, 30); vetOFF[2] = new HIcon(Toolkit.getDefaultToolkit().getImage("prenota_off.jpg"), 50, 170, 140, 30); vetON[0] = new HIcon(Toolkit.getDefaultToolkit().getImage("servizi_on.jpg"), 50, 100, 140, 30); vetON[1] = new HIcon(Toolkit.getDefaultToolkit().getImage("registra_on.jpg"), 50, 135, 140, 30); vetON[2] = new HIcon(Toolkit.getDefaultToolkit().getImage("prenota_on.jpg"), 50, 170, 140, 30); for(int i=0;i<3;i++){ scene.add(vetON[i]); scene.add(vetOFF[i]); 100 APPENDICE A: Codice Xlet vetOFF[i].setVisible(true); vetON[i].setVisible(false); } System.out.println("Entrato in disegnainit:icone ok"); //Istanzio gli oggetti che implementano le funzioni //della xlet. prenota=new Prenota(scene); System.out.println("pre ok"); servizi=new Servizi(scene); System.out.println("serv ok"); registra=new Registra(scene); System.out.println("Entrato in disegnainit:strumenti ok"); accendi(tasto); scene.setVisible(true); scene.requestFocus(); System.out.println("fine disegnainit"); } //Gestione grafica dei pulsanti public void accendi (int numero){ vetOFF[numero].setVisible(false); vetON[numero].setVisible(true); mess[numero].setVisible(true); scene.repaint(); } public void spegni (int numero){ vetOFF[numero].setVisible(true); vetON[numero].setVisible(false); mess[numero].setVisible(false); scene.repaint(); } //Viene oscurata la parte grafica e //si ferma il processo attivo. //Richiamato del metodo destroyXlet nel momento di //distruzione della xlet. public void distruggi(){ this.scene.setVisible(false); //strumento.stop(); } public static void premutoEnter(){ } //Ascoltatore degli eventi del telecomando. //La gestione delle funzionalità è stata fatta utilizzando le //interfacce Java questo permette di gestire i processi in maniera dinamica, //passando gli eventi solo al processo attivo. public void keyPressed (KeyEvent key) { int pulsantePremuto = key.getKeyCode(); switch(pulsantePremuto){ case KeyEvent.VK_0: 101 APPENDICE A: Codice Xlet case KeyEvent.VK_1: case KeyEvent.VK_2: case KeyEvent.VK_3: case KeyEvent.VK_4: case KeyEvent.VK_5: case KeyEvent.VK_6: case KeyEvent.VK_7: case KeyEvent.VK_8: case KeyEvent.VK_9: case HRcEvent.VK_COLORED_KEY_0: System.out.println("Rosso"); if(STATO==POPUP) break; if(STATO==INIT){ System.out.println("Passa a menu"); STATO=MENU; disegnaInit(); break; } case HRcEvent.VK_COLORED_KEY_1: if(STATO==POPUP){ Strumenti.messFlashOff(); break; } case HRcEvent.VK_COLORED_KEY_2: if(STATO==POPUP) break; if(STATO==PRIMO){ strumento.passaEvento(key); break; } case HRcEvent.VK_COLORED_KEY_3: if(STATO==POPUP) break; if(STATO==PRIMO){ strumento.Stop(); mess[tasto].setVisible(true); //Main.gestoreSfondi.displayBackgroundExit(); Main.gestoreSfondi.displayBackground("background.mpg",240,100,420,320); scene.repaint(); STATO=MENU; break; } if(STATO==DISP){ Prenota.stopVisu(); mess[tasto].setVisible(true); //Main.gestoreSfondi.displayBackgroundExit(); Main.gestoreSfondi.displayBackground("background.mpg",240,100,420,320); scene.repaint(); STATO=MENU; break; } case HRcEvent.VK_RIGHT: if(STATO==POPUP) break; System.out.println("stato init"+InterfacciaGrafica.STATO); System.out.println("Init:Destro"); 102 APPENDICE A: Codice Xlet if(STATO==DISP){ strumento.passaEvento(key); break; } case HRcEvent.VK_LEFT: if(STATO==POPUP) break; if(STATO==DISP){ strumento.passaEvento(key); break; } if(STATO==MENU)break; case HRcEvent.VK_UP: if(STATO==POPUP) break; if(STATO==MENU){ spegni(tasto); if(tasto==0) tasto=2; else tasto--; accendi(tasto); break; } case HRcEvent.VK_DOWN: if(STATO==POPUP) break; if(STATO==MENU){ spegni(tasto); if(tasto==2)tasto=0; else tasto++; accendi(tasto); break; } case HRcEvent.VK_ENTER: if(STATO==POPUP) break; if(STATO==MENU){ if(tasto==0)break; mess[tasto].setVisible(false); Main.gestoreSfondi.displayBackgroundExit(); Main.gestoreSfondi.displayBackground("background.mpg",0,0,0,0); scene.repaint(); /*if(tasto==0){ strumento=servizi; }*/ if(tasto==1){ strumento=registra; } if(tasto==2){ strumento=prenota; } strumento.init(); strumento.Start(); break; } strumento.passaEvento(key); default: break; } } 103 APPENDICE A: Codice Xlet } GestoreSfondi.java import javax.tv.xlet.XletContext; //Gestisce gli sfondi della xlet. //Utilizzato dalla classe principale "Main". I metodi si appoggiano alla //classe BackgroundController per modificare l'immagine in background. public class GestoreSfondi { //Context della xlet in esecuzione. private XletContext xContext; private BackgroundController backgroundManager; public GestoreSfondi(XletContext context){ backgroundManager = new BackgroundController(); xContext=context; } // Carica l'immagine di sfondo della xlet, oscurando il video. public void displayBackground(String back) { if (backgroundManager.init()) { backgroundManager.hideVideo(xContext); backgroundManager.display(back); } } // Carica l'immagine di sfondo ridimensionando il video. public void displayBackground(String back,int x,int y,int l,int h) { if (backgroundManager.init()) { backgroundManager.resizeVideo(xContext,x,y,l,h); backgroundManager.display(back); } } // Rilascia la risorsa Background. public void displayBackgroundExit() { backgroundManager.dispose(); } } Keypad.java import java.awt.*; import java.awt.event.*; public class Keypad extends Container implements Runnable { private class NewKey { private int nk = -1; public NewKey() { } public void set(int i) { nk = i; } public int get() { return nk; } } 104 APPENDICE A: Codice Xlet // Keypad size constants, don't change. private static final int width = 500; private static final int height = 237; private String editedText = null; private char pendingChar = 0; // Used for keypad output, set by setOutput method private EditableTextBox textBox = null; // Images of buttons private Image redButtonImage = null; private Image blueButtonImage = null; private Image okButtonImage = null; public Keypad() { setSize(width,height); // Load button images redButtonImage = Toolkit.getDefaultToolkit().getImage("Rosso_Off.gif"); blueButtonImage = Toolkit.getDefaultToolkit().getImage("Blu_Off.gif"); // Create numeric buttons and layout buttons = new KeypadButton[10]; buttons[0] = new KeypadButton(0,"0_-"); buttons[1] = new KeypadButton(1,"1@,;."); buttons[2] = new KeypadButton(2,"abc2äå"); buttons[3] = new KeypadButton(3,"def3"); buttons[4] = new KeypadButton(4,"ghi4"); buttons[5] = new KeypadButton(5,"jkl5"); buttons[6] = new KeypadButton(6,"mno6ö"); buttons[7] = new KeypadButton(7,"pqrs7"); buttons[8] = new KeypadButton(8,"tuv8"); buttons[9] = new KeypadButton(9,"wxyz9"); buttons[1].setLocation(259,5); buttons[2].setLocation(339,5); buttons[3].setLocation(419,5); buttons[4].setLocation(259,62); buttons[5].setLocation(339,62); buttons[6].setLocation(419,62); buttons[7].setLocation(259,119); buttons[8].setLocation(339,119); buttons[9].setLocation(419,119); buttons[0].setLocation(339,176); for (int i=0;i<10;i++) { buttons[i].setVisible(true); add(buttons[i]); } newKey = new NewKey(); } public void paint(Graphics g) { // Paint keypad border //g.setColor(Color.black); //g.fillRect(254,0,245,height); // Paint keypad background g.setColor(new Color(100,100,100,120)); g.fillRect(0,0,width,height); //g.fillRect(256,2,241,height-4); // Paint backspace button 105 APPENDICE A: Codice Xlet //g.drawImage(redButtonImage,10,40,this); g.setColor(Color.white); g.setFont(new Font("Tireasias",Font.PLAIN,23)); g.drawString("Su/Giù :Seleziona campo",10,20); g.drawString("OK :Inserimento",10,50); g.drawString("< :Backspace",10,80); g.drawString("> :Space",10,110); g.setFont(new Font("Tireasias",Font.BOLD,23)); g.setColor(Color.red); g.drawString("CONFERMA DATI",10,140); g.setColor(Color.blue); g.drawString("ANNULLA",10,170); // Paint space button //g.drawImage(blueButtonImage,10,90,this); //g.drawString("= space",90,121); // Paint components (keys) super.paint(g); } // activeChar is the character in editText which is edited at the moment int activeChar = 0; private int numKeyActive = -1; // -1 = no key private char chosenChar = 0; // 0 = no character private int timer = 0; boolean backSpaceSet = false; // backspace has been pressed? boolean spaceSet = false; // space has been pressed? private NewKey newKey = null; private KeypadButton[] buttons; // Main loop of the thread. public void run() { int i=0; while (true) { // Sleep for a while so that the CPU won't be too busy. try { Thread.sleep(100); } catch (Exception e) { System.out.println(e); } // newKey is shared by two thread, allow only one access at a time synchronized (newKey) { if (newKey.get() != -1) { if (numKeyActive == -1) { // New key pressed numKeyActive = newKey.get(); buttons[numKeyActive].press(); textBox.setPending(buttons[numKeyActive].getChar()); newKey.set(-1); } else { if (numKeyActive == newKey.get()) { // Old key re-pressed. buttons[numKeyActive].press(); textBox.setPending(buttons[numKeyActive].getChar()); newKey.set(-1); } else { // New key pressed while old still active 106 APPENDICE A: Codice Xlet System.out.println("Chosen: "+buttons[numKeyActive].getChar()); textBox.addPending(); buttons[numKeyActive].unpress(); numKeyActive = newKey.get(); buttons[numKeyActive].press(); textBox.setPending(buttons[numKeyActive].getChar()); newKey.set(-1); } } // Every key press sets the timer to 10 timer = 10; } else { // If no key press, the timer is decremented. timer--; } if (timer==0) { // If timer reaches 0 it means that it has gone 10x100ms=1 second since // the active key was pressed. The active key is now added to the edited // string (in the text box). System.out.println("Chosen: "+buttons[numKeyActive].getChar()); textBox.addPending(); buttons[numKeyActive].unpress(); numKeyActive = -1; } // If a key is active, backspace always inactivates it. if (backSpaceSet) { textBox.backSpace(); if (numKeyActive != -1) { buttons[numKeyActive].unpress(); numKeyActive = -1; } timer = -1; backSpaceSet = false; } // A space is always added to the edited string. If a key is active // when space is pressed, it get inactivated. if (spaceSet) { textBox.space(); if (numKeyActive != -1) { buttons[numKeyActive].unpress(); numKeyActive = -1; } timer = -1; spaceSet = false; } } } } // Acknowledge key press. public void keyPressed (KeyEvent key) { int code = key.getKeyCode(); if ((code >= KeyEvent.VK_0) && (code <= KeyEvent.VK_9)) { // newKey can also be changed by the run method. System.out.println("keyPad.keyevent"+code); synchronized (newKey) { 107 APPENDICE A: Codice Xlet newKey.set(code-KeyEvent.VK_0); } } } public void backSpace() { backSpaceSet = true; } public void space() { spaceSet = true; } // Assign the key pad to a EditableTextBox. public void setOutput (EditableTextBox e) { textBox = e; } } KeypadButton.java import java.awt.*; public class KeypadButton extends Component { // Constants private static final int KB_WIDTH = 75; private static final int KB_HEIGHT = 52; private int keyNumber = 0; private char[] subChars = null; private int activeSubChar = -1; private int subCharCount = 0; private boolean focused = false; private static Font mainCharFont = null; private static Font subCharFont = null; // Create KeypadButton. public KeypadButton(int keyNum, String keyChars) { setSize(KB_WIDTH,KB_HEIGHT); keyNumber = keyNum; subChars = keyChars.toCharArray(); subCharCount = keyChars.length(); mainCharFont = new Font("Tirealias",Font.PLAIN,27); subCharFont = new Font("Tirealias", Font.PLAIN,20); } public void paint(Graphics g) { super.paint(g); FontMetrics fm = null; String s = null; // If the button if focused it is painted with a white border. if (focused) { 108 APPENDICE A: Codice Xlet g.setColor(Color.white); } else { g.setColor(new Color(0x2a,0x2a,0x2a)); } g.fillRect(0,0,this.getSize().width,this.getSize().height); g.setColor(Color.black); g.fillRect(2,2,this.getSize().width-4,this.getSize().height-4); // Draw subcharacters. FontMetrics is used for getting character // widths, so that the text can be centered. g.setFont(subCharFont); fm = g.getFontMetrics(); int textWidth = fm.stringWidth(String.valueOf(subChars)); int charX = (KB_WIDTH-textWidth)/2; for (int i=0;i<subCharCount;i++) { if (i==activeSubChar) { g.setColor(Color.white); } else { g.setColor(new Color(0x3f,0x3f,0xff)); } s = String.valueOf(subChars[i]); g.drawString(s,charX,46); charX += fm.charWidth(subChars[i]); } // Draw key number. g.setFont(mainCharFont); fm = g.getFontMetrics(); g.setColor(Color.white); s = String.valueOf(keyNumber); g.drawString(s,(KB_WIDTH-fm.stringWidth(s))/2,26); } public void press() { if (activeSubChar == -1) { focused = true; activeSubChar = 0; } else { activeSubChar = (activeSubChar+1) % subCharCount; } repaint(); } public void unpress() { activeSubChar = -1; focused = false; repaint(); } public char getChar() { if (subChars != null) return subChars[activeSubChar]; return 0; } } 109 APPENDICE A: Codice Xlet EditableTextBox.java import java.awt.*; public class EditableTextBox extends Component { private int width = 0; private int height = 0; private String editStr = null; // The currently edited text private String nullStr = null; // The text shown if there's no editStr present private char pendingChar = 0; // An character which has not yet been added to editStr private boolean focused = false; private boolean editActive = false; // Create the component. public EditableTextBox(String nullStr, int width,int height) { setSize(width,height); this.nullStr = nullStr; this.width = width; this.height = height; } // Paint the component. public void paint(Graphics g) { if (focused) { if(editActive)g.setColor(Color.red);//colore riquadro focused e edit active else g.setColor(Color.darkGray); //colore riquadro focused g.fillRect(0,0,width,height); } g.setColor(Color.white); g.fillRect(2,2,width-4,height-4); if ((editStr == null) || ((editStr.length() == 0) && !editActive)) { g.setColor(Color.black); g.setFont(new Font("Tireasias",Font.PLAIN,height-10)); g.drawString(nullStr,10,height-10); } else { g.setColor(Color.black); g.setFont(new Font("Tireasias",Font.PLAIN,height-10)); g.drawString(editStr,10,height-10); if (pendingChar != 0) { g.setColor(Color.red); FontMetrics fm = g.getFontMetrics(); g.drawString(String.valueOf(pendingChar),10+fm.stringWidth(editStr),height-10); } } } public void setFocused(boolean b) { focused = b; } // Set the pending character (shown after editStr) public void setPending(char c) { pendingChar = c; repaint(); } 110 APPENDICE A: Codice Xlet // Add the pending character to editStr public void addPending() { editStr += pendingChar; pendingChar = 0; repaint(); } // Remove the last character (pending or last of editStr) public void backSpace() { if (pendingChar != 0) { pendingChar = 0; } else { if (editStr.length()>0) { editStr = editStr.substring(0,editStr.length()-1); } } repaint(); } // Add space. public void space() { if (pendingChar != 0) { addPending(); } editStr = editStr + ' '; } // Return the edited string. public String getString() { return editStr; } // Set edit mode on/off public void setEdit(boolean b) { editActive = b; if (editStr == null) { editStr = new String(); } } } 111 APPENDICE A: Codice Xlet Strumenti.java import java.awt.Color; import java.awt.Font; import java.awt.event.KeyEvent; import java.io.IOException; import org.havi.ui.*; //import java.net.*; //import java.sql.*; import org.dvb.net.rc.*; public class Strumenti { //0:non esistono connessioni 1:conn persistenete 2:conn non persistenete public static final String IPPROXY="localhost"; //"192.168.1.1"; deit //"localhost"; //"10.6.160.143"; Asur public static int connesso=0; public String []strmask; public static HScene scene; private static int StatoOld; EditableTextBox []etb; public Keypad keyPad; private static HText mess,mess2; public Strumenti(HScene sceneint){ scene=sceneint; } public void init(){ InterfacciaGrafica.STATO=InterfacciaGrafica.PRIMO; keyPad = new Keypad(); new Thread(keyPad).start(); keyPad.setLocation (100,310); scene.add(keyPad); keyPad.setVisible(false); } public void Start(){} public void Stop(){ //new Thread(keyPad).stop(); scene.remove(keyPad); for(int i=0;i<etb.length;i++){ scene.remove(etb[i]); } scene.repaint(); } public boolean popUp(String str,int x,int y){ boolean flag=false; return flag; 112 APPENDICE A: Codice Xlet } public static void Connetti()throws IOException { //prefisso centralino 73372504 final String phone="733725047027020000"; final String user="[email protected]"; final String pass="979116"; Rsl listener; ConnectionRCInterface cRCi=null; RCResourceClient resource_client; resource_client=new RCResourceClient(); //Per prima cosa acquisiamo un'istanza di RCInterfaceManager RCInterfaceManager rcim=RCInterfaceManager.getInstance(); //rcim.addResourceStatusEventListener(listener); //Estraiamo l'array con tutte le possibili interfacce di connessione //che la nostra applicazione potrà avviare. //RCInterface rci[] = rcim.getInterfaces(); RCInterface rci[] = rcim.getInterfaces(); int il=0; if (rci!= null) { il = rci.length; System.out.println("Numero delle interfacce: " + il); } else { il = 0; connesso=0; System.out.println("Nessuna interfaccia supportata (rci =null)"); return; } // // // Tra tutte le interfacce riportate nell'array si verifica quale è una istanza di ConnectionRCInterface e si seleziona quella appropriata. // Elenco l'interfacce System.out.println("Interfacce trovate:"); for(int i = 0; i<il; i++) { System.out.println(rci[i].getType()); if(rci[i] instanceof ConnectionRCInterface) { cRCi = (ConnectionRCInterface) rci[i]; System.out.println("Interfaccia "+i+ ":" + cRCi.getType()+" "+cRCi.toString()+" DataRate:"+cRCi.getDataRate()); } } // Scelgo l'interfaccia più veloce /*for(int i = il-1; i>=0; i--) { if(rci[i] instanceof ConnectionRCInterface) { 113 APPENDICE A: Codice Xlet cRCi = (ConnectionRCInterface) rci[i]; System.out.println("L'interfaccia usata è" + cRCi.getType()); break; } }*/ if(rci[0].getType()==4) connesso=1; //cRCi = (ConnectionRCInterface) rci[0]; // Forzo a modem // cRCi=(ConnectionRCInterface) rci[0]; // Forzo a LAN // cRCi=(ConnectionRCInterface) rci[0]; // Ora abbiamo ottenuto il riferimento alla nostra interfaccia, // se non è permanente andiamo a stabilire la connessione. if (cRCi!= null && cRCi.getType() == RCInterface.TYPE_PSTN) { L'interfaccia viene riservata per la nostra applicazione associandogli un client da noi definito. try{ cRCi.reserve(resource_client, null ); }catch (PermissionDeniedException e){ System.out.println("Errore nel riservare la risorsa: "+e.getMessage()); System.out.println("L' interfaccia non puo essere riservata:Permission Denied!"); return; } Setto i parametri per effetture la chiamata e l'autenticazione. try{ cRCi.setTarget(new ConnectionParameters(phone,user,pass)); }catch (IncompleteTargetException e){ System.out.println("Errore nel setting del target(incomplete):"+e.getMessage()); return; }catch (PermissionDeniedException e){ System.out.println("Errore nel setting del target(permission denied:"+e.getMessage()); return; } // // // //cRCi.addConnectionListener(listener); Crea la connesione con i parametri precedentemente impostati. Se la connesione a gia attiva il metodo non ha effetto. try{ cRCi.connect(); System.out.println("La connessione e stata effettuata!"); }catch (IOException e){ System.out.println("Errore di connessione(io): "+e.getMessage()); return; }catch (PermissionDeniedException e){ System.out.println("Errore di connessione(permission denied): "+e.getMessage()); return; } connesso=2; } // // } public boolean getMask(int nc){ strmask=new String[nc]; boolean flag=true; 114 APPENDICE A: Codice Xlet for(int i=0;i<nc;i++){ strmask[i]=etb[i].getString(); System.out.println(strmask[i]); if(strmask[i]==null || strmask[i].length()==0) flag=false; } return flag; } // Messaggio di commento di durata indicata public static void messFlash(String str,int x,int y){ StatoOld=InterfacciaGrafica.STATO; InterfacciaGrafica.STATO=InterfacciaGrafica.POPUP; Font font=new Font("Arial",Font.BOLD,22); mess= new HText(str); mess2=new HText("PREMI VERDE per continuare"); mess.setFont(font); mess2.setFont(font); mess.setForeground(Color.white); mess2.setForeground(Color.green); mess.setBackgroundMode(HVisible.BACKGROUND_FILL); mess2.setBackgroundMode(HVisible.BACKGROUND_FILL); mess.setBackground(new Color(100,100,100,120)); mess2.setBackground(new Color(100,100,100,120)); mess.setBounds(x,y,580,25); mess2.setBounds(x,y+25,580,20); mess.setBordersEnabled(true); scene.add(mess); scene.add(mess2); mess.setVisible(true); mess2.setVisible(true); scene.repaint(); } public static void messFlashOff(){ InterfacciaGrafica.STATO=StatoOld; mess.setVisible(false); scene.remove(mess); mess2.setVisible(false); scene.remove(mess2); scene.repaint(); } public void passaEvento (KeyEvent key) { } } 115 APPENDICE A: Codice Xlet Registra.java import java.awt.event.KeyEvent; import java.io.IOException; import org.havi.ui.*; import org.havi.ui.event.HRcEvent; public class Registra extends Strumenti{ private int ic=0; //indice del campo corrente private static final int dist=40; boolean edita=false; Registra(HScene sceneint){ super(sceneint); } public void Start(){ etb=new EditableTextBox[5]; etb[0]=new EditableTextBox("Nome",300,30); etb[0].setLocation(300,70); etb[1]=new EditableTextBox("Cognome",300,30); etb[1].setLocation(300,70+dist); etb[2]=new EditableTextBox("Telefono",300,30); etb[2].setLocation(300,70+2*dist); etb[3]=new EditableTextBox("Username",300,30); etb[3].setLocation(300,70+3*dist); etb[4]=new EditableTextBox("Password",300,30); etb[4].setLocation(300,70+4*dist); for(int i=0;i<5;i++){ etb[i].setVisible(true); scene.add(etb[i]); } etb[ic].setFocused(true); keyPad.setVisible(true); scene.repaint(); } public void passaEvento (KeyEvent key) { int pulsantePremuto = key.getKeyCode(); switch(pulsantePremuto){ case KeyEvent.VK_0: case KeyEvent.VK_1: case KeyEvent.VK_2: case KeyEvent.VK_3: case KeyEvent.VK_4: case KeyEvent.VK_5: case KeyEvent.VK_6: case KeyEvent.VK_7: case KeyEvent.VK_8: case KeyEvent.VK_9: 116 APPENDICE A: Codice Xlet if(edita)keyPad.keyPressed(key); break; case HRcEvent.VK_COLORED_KEY_0: System.out.println("Premuto rosso sotto registra"); if (getMask(5)){ //Controlla se esiste user e aggiorna anag try{ Connetti(); }catch (IOException e){ System.out.println("Errore di connessione(io): "+e.getMessage()); } // Registra un nuovo utente Comunica com=new Comunica(IPPROXY,8000); ObjectRegistration reg=new ObjectRegistration(strmask[3],strmask[4],strmask[0],strmask[1],strmask[2]); String ris=(String)(com.Connetti(reg)); System.out.println("ris="+ris); messFlash(ris,60,265); //Ritorna registra effettuata con successo o Username già esistente }else{ // finestra errore:riempi tutti i campi messFlash("Compila tutti i campi",60,265); } break; case HRcEvent.VK_COLORED_KEY_1: break; case HRcEvent.VK_COLORED_KEY_2: break; case HRcEvent.VK_COLORED_KEY_3: Stop(); break; case HRcEvent.VK_UP: edita=false; etb[ic].setFocused(false); etb[ic].setEdit(false); if(ic==0){ ic=4; }else ic--; etb[ic].setFocused(true); scene.repaint(); break; case HRcEvent.VK_DOWN: edita=false; etb[ic].setEdit(false); etb[ic].setFocused(false); if(ic==4){ ic=0; }else ic++; etb[ic].setFocused(true); scene.repaint(); break; case HRcEvent.VK_RIGHT: if(edita)keyPad.space(); break; 117 APPENDICE A: Codice Xlet case HRcEvent.VK_LEFT: if(edita)keyPad.backSpace(); break; case HRcEvent.VK_ENTER: edita=true; keyPad.setOutput(etb[ic]); etb[ic].setEdit(true); scene.repaint(); default: break; } } } Prenota.java import java.awt.*; import java.awt.event.*; import java.io.IOException; import java.net.*; import org.havi.ui.*; import org.havi.ui.event.HRcEvent; public class Prenota extends Strumenti{ private int ic=0; //indice del campo corrente private static final int dist=50; boolean edita=false; private boolean riprenota=false; private int disp []; private static int ntav=5; //N tot tavoli private int itav=0; //indice corrente vettore tavoli private Image tavv,tavr; //icone:tavolo libero,occupato public static HIcon []vetTav; private static HText messblu; Prenota(HScene sceneint){ super(sceneint); tavv=Toolkit.getDefaultToolkit().getImage("tavolo_v.gif"); tavr=Toolkit.getDefaultToolkit().getImage("tavolo_r.gif"); } public void Start(){ etb=new EditableTextBox[2]; etb[0]=new EditableTextBox("Username",300,35); etb[0].setLocation(300,100); etb[1]=new EditableTextBox("Password",300,35); etb[1].setLocation(300,100+dist); for(int i=0;i<2;i++){ etb[i].setVisible(true); scene.add(etb[i]); } etb[ic].setFocused(true); keyPad.setVisible(true); scene.repaint(); } public void visualizzaDisp(){ if(!riprenota){ 118 APPENDICE A: Codice Xlet Main.gestoreSfondi.displayBackground("background.mpg",260,100,225,180); Font font=new Font("Arial",Font.BOLD,20); messblu= new HText("PREMI TASTO BLU PER USCIRE"); messblu.setFont(font); messblu.setForeground(Color.blue); messblu.setBackgroundMode(HVisible.BACKGROUND_FILL); messblu.setBackground(new Color(100,100,100,120)); messblu.setBounds(60,460,580,20); messblu.setBordersEnabled(true); scene.add(messblu); } if(riprenota){ for(int i=0;i<ntav;i++){ scene.remove(vetTav[i]); } System.out.println("RIPRENOTA"); scene.repaint(); } messblu.setVisible(true); InterfacciaGrafica.STATO=InterfacciaGrafica.DISP; System.out.println("stato visu disp"+InterfacciaGrafica.STATO); vetTav=new HIcon[ntav]; for(int i=0;i<ntav;i++){ if(disp[i]==0)vetTav[i]= new HIcon(tavv,110+100*i,300,100,80); else vetTav[i]= new HIcon(tavr,110+100*i,300,100,80); vetTav[i].setBackground(new Color(100,100,100,120)); scene.add(vetTav[i]); vetTav[i].setVisible(true); } vetTav[itav].setBackgroundMode(HVisible.BACKGROUND_FILL); scene.repaint(); } public static void stopVisu(){ for(int i=0;i<ntav;i++){ scene.remove(vetTav[i]); } messblu.setVisible(false); //scene.remove(messblu); scene.repaint(); InterfacciaGrafica.STATO=InterfacciaGrafica.MENU; } public void passaEvento (KeyEvent key) { int pulsantePremuto = key.getKeyCode(); switch(pulsantePremuto){ case KeyEvent.VK_0: case KeyEvent.VK_1: case KeyEvent.VK_2: case KeyEvent.VK_3: case KeyEvent.VK_4: case KeyEvent.VK_5: case KeyEvent.VK_6: case KeyEvent.VK_7: 119 APPENDICE A: Codice Xlet case KeyEvent.VK_8: case KeyEvent.VK_9: if(edita)keyPad.keyPressed(key); break; case HRcEvent.VK_COLORED_KEY_0: if(getMask(2)){ try{ Connetti(); }catch (IOException e){ System.out.println("Errore di connessione(io): "+e.getMessage()); } System.out.println("connesso:"+connesso); //prova(); Comunica com=new Comunica(IPPROXY,8000); ObjectPrenotation pren=new ObjectPrenotation(strmask[0],strmask[1]); System.out.println("creato ObjectPrenotation"); Object tavoli=com.Connetti(pren); System.out.println("ritornato tavoli"); if(tavoli instanceof ObjectTavoli) { String ris=""; disp=new int[((ObjectTavoli)tavoli).getLength()]; for (int i=0;i<((ObjectTavoli)tavoli).getLength();i++) { String u=(((ObjectTavoli)tavoli).getUtente(i)); String =String.valueOf(((ObjectTavoli)tavoli).getNumero(i)); String l=String.valueOf(((ObjectTavoli)tavoli).getLibero(i)); disp[i]=((ObjectTavoli)tavoli).getLibero(i); ris=ris+"\nUtente: "+u+" Numero: "+n+" Libero: "+l; } System.out.println(ris); Stop(); visualizzaDisp(); messFlash("Login corretto,prenota il tuo tavolo!",60,415); riprenota=true; break; } if(tavoli instanceof String) { System.out.println(tavoli); messFlash((String)tavoli,60,265); } //Ritorna Username o password errati }else{ System.out.println("compila tutto"); messFlash("Compila tutti i campi",60,265); } break; case HRcEvent.VK_COLORED_KEY_1: break; case HRcEvent.VK_COLORED_KEY_2: break; case HRcEvent.VK_COLORED_KEY_3: if(InterfacciaGrafica.STATO==InterfacciaGrafica.DISP){ stopVisu(); 120 APPENDICE A: Codice Xlet } break; case HRcEvent.VK_UP: edita=false; etb[ic].setFocused(false); etb[ic].setEdit(false); if(ic==0){ ic=1; }else ic--; etb[ic].setFocused(true); scene.repaint(); break; case HRcEvent.VK_DOWN: edita=false; etb[ic].setEdit(false); etb[ic].setFocused(false); if(ic==1){ ic=0; }else ic++; etb[ic].setFocused(true); scene.repaint(); break; case HRcEvent.VK_RIGHT: System.out.println("Destro"); if(edita)keyPad.space(); if(InterfacciaGrafica.STATO==InterfacciaGrafica.DISP){ vetTav[itav].setBackgroundMode(HVisible.NO_BACKGROUND_FILL); if(itav>=ntav-1){ itav=0; }else itav++; vetTav[itav].setBackgroundMode(HVisible.BACKGROUND_FILL); scene.repaint(); } break; case HRcEvent.VK_LEFT: if(edita)keyPad.backSpace(); if(InterfacciaGrafica.STATO==InterfacciaGrafica.DISP){ vetTav[itav].setBackgroundMode(HVisible.NO_BACKGROUND_FILL); if(itav<=0){ itav=ntav-1; }else itav--; vetTav[itav].setBackgroundMode(HVisible.BACKGROUND_FILL); scene.repaint(); } break; case HRcEvent.VK_ENTER: if(InterfacciaGrafica.STATO==InterfacciaGrafica.DISP){ if(disp[itav]==0){ Comunica com=new Comunica(IPPROXY,8000); PrenotaSingolo pren=new PrenotaSingolo(strmask[0],itav+1);//user e n tavolo 1a5 Object p=com.Connetti(pren); disp[itav]=1; 121 APPENDICE A: Codice Xlet if (p instanceof String) { System.out.println(p); //Prenotazione eseguita con successo visualizzaDisp(); messFlash((String)p,60,415); } }else{ messFlash("Tavolo già riservato",60,415); } break; } edita=true; keyPad.setOutput(etb[ic]); etb[ic].setEdit(true); scene.repaint(); default: break; } } } Comunica.java import java.net.*; import java.io.*; //import java.util.*; public class Comunica { private String IP; private int port; private String str; private Object obj; private Object objW; public Comunica(String sIP,int p){ this.port=p; this.IP=sIP; } public Object Connetti(Object object){ obj=null; objW=object; try{ // Apre una connessione verso un server in ascolto // sulla porta "port", all'indirizzo "IP" System.out.println("Apertura Socket..."); Socket sock = new Socket(IP,port); System.out.println("creato socket"); OutputStream s1out = sock.getOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(s1out); System.out.println("impostato outpustream"); oos.writeObject(objW); // Ricava lo stream di input dal socket sock // ed utilizza un oggetto wrapper di classe BufferedReader // per semplificare le operazioni di lettura InputStream is = sock.getInputStream(); ObjectInputStream ois = new ObjectInputStream(is); //BufferedReader dis = new BufferedReader(new InputStreamReader(is)); // Legge l'input e lo visualizza sullo schermo 122 APPENDICE A: Codice Xlet //str=dis.readLine(); try{ obj=ois.readObject();} catch (ClassNotFoundException e) { System.out.println("Error: "+e.getMessage()); } ois.close(); sock.close(); System.out.println("Chiusura connessione effettuata"); }catch (ConnectException connExc){ System.out.println("Errore nella connessione "); } catch (IOException ex){ ex.printStackTrace(); } return obj; } PrenotaSingolo pren=new PrenotaSingolo("jack",1); Object p=com.Connetti(pren); if (p instanceof String) { System.out.println(p); } } } PrenotaSingolo.java import java.io.*; public class PrenotaSingolo implements Serializable { private String user; private int numero; public PrenotaSingolo (String user, int numero) { this.user=user; this.numero=numero; } public String getUser() { return user; } public int getNumero() { return numero; } } 123 APPENDICE B: Codice Proxy APPENDICE B Codice Proxy SimpleServer.java import java.net.*; import java.io.*; //import java.util.*; public class SimpleServer { public static final String IPDB="localhost"; public static final String USERDB="fabiob"; public static final String PASSDB="979116"; private int port; private ServerSocket server; private Object obj; private Object prenot; private String ris; private Object tavoli; private Object prenotaS; public SimpleServer (int port){ ris=""; this.port = port; if(!startServer()) System.err.println("Errore durante la creazione del Server"); } private boolean startServer(){ try{ server = new ServerSocket(port); }catch (IOException ex){ ex.printStackTrace(); return false; } System.out.println("Server creato con successo!"); return true; } public void runServer(){ while (true){ try{ // Il server resta in attesa di una richiesta System.out.println("Server in attesa di richieste..."); Socket s1 = server.accept(); System.out.println("Un client si e' connesso..."); InputStream is = s1.getInputStream(); ObjectInputStream ois = new ObjectInputStream(is); try{ obj=(Object)(ois.readObject()); System.out.println("oggetto letto"); } 124 APPENDICE B: Codice Proxy catch (ClassNotFoundException e) { System.out.println("Error: "+e.getMessage()); } if (obj instanceof ObjectRegistration) { ris=(new ServerRegistration((ObjectRegistration)obj)).start(); // Ricava lo stream di output associate al socket // e definisce una classe wrapper di tipo // BufferedWriter per semplificare le operazioni // di scrittura OutputStream s1out = s1.getOutputStream(); // BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s1out)); ObjectOutputStream oos = new ObjectOutputStream(s1out); // Il server invia la risposta al client // oos.writeObject("Benvenuto sul server!\n"); oos.writeObject(ris); // Chiude lo strema di output e la connessione oos.close(); } if(obj instanceof ObjectPrenotation) { tavoli=(new ServerPrenotation((ObjectPrenotation)obj)).start(); OutputStream s1out = s1.getOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(s1out); oos.writeObject(tavoli); oos.close(); } if (obj instanceof PrenotaSingolo) { prenotaS=(new ServerPrenotaSingolo((PrenotaSingolo)obj)).start(); OutputStream s1out = s1.getOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(s1out); oos.writeObject(prenotaS); oos.close(); } s1.close(); System.out.println("Chiusura connessione effettuata\n"); }catch (IOException ex){ ex.printStackTrace(); } } } public static void main(String[] args) { SimpleServer ss = new SimpleServer(8000); ss.runServer(); } } 125 APPENDICE B: Codice Proxy ServerRegistration.java import java.sql.*; public class ServerRegistration { private ObjectRegistration obj; private String ris; public ServerRegistration(ObjectRegistration object) { this.obj=object; ris=""; } public String start() { try { Class.forName("com.mysql.jdbc.Driver"); System.out.println("eseguito class.forname"); } catch (ClassNotFoundException e1){} try{ String user=SimpleServer.USERDB; String pass=SimpleServer.PASSDB; String url="jdbc:mysql://"+SimpleServer.IPDB+"/prenotazioni"; Connection con = DriverManager.getConnection(url,user,pass); System.out.println("Connessione DB: "+url+user+pass); Statement stmt=con.createStatement(); System.out.println(((ObjectRegistration)obj).getUser()); String query= "SELECT user FROM cliente WHERE (user='"+((ObjectRegistration)obj).getUser()+"');"; ResultSet rm=stmt.executeQuery(query); while (rm.next()) { ris=rm.getString(1); } if (ris=="") { String query2= "INSERT INTO cliente (user,password,nome,cognome,telefono) VALUES ('"+((ObjectRegistration)obj).getUser()+"','"+((ObjectRegistration)obj).getPassword()+"','"+((ObjectRegi stration)obj).getNome()+"','"+((ObjectRegistration)obj).getCognome()+"','"+((ObjectRegistration)obj).get Telefono()+"');"; stmt.executeUpdate(query2); ris="Registrazione effettuata con successo!"; System.out.println("query effettuata"); } else { ris="Username esistente."; } rm.close(); stmt.close(); con.close(); }catch(SQLException exc) { System.out.println("SQL error: "+exc.getMessage()); } return ris; } } 126 APPENDICE B: Codice Proxy ServerPrenotation.java import java.sql.*; public class ServerPrenotation { private ObjectPrenotation obj; private Object tavoli; public ServerPrenotation(ObjectPrenotation obj) { this.obj=obj; } public Object start() { try { Class.forName("com.mysql.jdbc.Driver"); System.out.println("eseguito class.forname"); } catch (ClassNotFoundException e1){} try{ String user=SimpleServer.USERDB; String pass=SimpleServer.PASSDB; String url="jdbc:mysql://"+SimpleServer.IPDB+"/prenotazioni"; Connection con = DriverManager.getConnection(url,user,pass); System.out.println("Connessione DB: "+url+user+pass); Statement stmt=con.createStatement(); String query="SELECT user FROM cliente WHERE user='"+obj.getUser()+"' AND password='"+obj.getPassword()+"';"; ResultSet rm=stmt.executeQuery(query); String ris1=""; while(rm.next()) { ris1=ris1+rm.getString(1); } rm.close(); if (ris1=="") { System.out.println("user pwd errati"); tavoli="Username o password errati."; } else { System.out.println("user pwd esatti"); String query2= "SELECT count(*) FROM tavoli;"; ResultSet rm2=stmt.executeQuery(query2); System.out.println("eseguita query2"); int length=0; while (rm2.next()) { length=Integer.parseInt(rm2.getString(1)); System.out.println("length: "+length); } rm2.close(); String query3= "SELECT * FROM tavoli;"; ResultSet rm3=stmt.executeQuery(query3); int[] num=new int[length]; String[] utente=new String[length]; int[] libero=new int[length]; int j=0; while (rm3.next()) { num[j]=Integer.parseInt(rm3.getString(1)); System.out.println(num[j]); utente[j]=rm3.getString(2); 127 APPENDICE B: Codice Proxy System.out.println(utente[j]); libero[j]=Integer.parseInt(rm3.getString(3)); System.out.println(libero[j]); j++; } tavoli=new ObjectTavoli(utente,num,libero); rm3.close(); } stmt.close(); con.close(); } catch(SQLException exc) { System.out.println("SQLException: "+exc.getMessage()); } return tavoli; } } ServerPrenotaSingolo.java import java.sql.*; public class ServerPrenotaSingolo { private PrenotaSingolo obj; private String ris; public ServerPrenotaSingolo(PrenotaSingolo obj) { this.obj=obj; } public Object start() { ris=""; try { Class.forName("com.mysql.jdbc.Driver"); System.out.println("eseguito class.forname"); } catch (ClassNotFoundException e1){} try{ String user=SimpleServer.USERDB; String pass=SimpleServer.PASSDB; String url="jdbc:mysql://"+SimpleServer.IPDB+"/prenotazioni"; Connection con = DriverManager.getConnection(url,user,pass); System.out.println("Connessione DB: "+url+user+pass); Statement stmt=con.createStatement(); String query= "UPDATE tavoli SET libero=1 ,utente='"+obj.getUser()+"' WHERE num_tavolo="+obj.getNumero()+";"; stmt.executeUpdate(query); ris="Prenotazione eseguita con successo!"; stmt.close(); con.close(); } catch (SQLException e) { 128 APPENDICE B: Codice Proxy System.out.println("Eccezione: "+e.getMessage()); ris="Errore prenotazione"; } return ris; } } PrenotaSingolo.java import java.io.*; public class PrenotaSingolo implements Serializable { private String user; private int numero; public PrenotaSingolo (String user, int numero) { this.user=user; this.numero=numero; } public String getUser() { return user; } public int getNumero() { return numero; } } ObjectRegistration.java import java.io.*; public class ObjectRegistration implements Serializable{ private String user; private String password; private String nome; private String cognome; private String telefono; public ObjectRegistration(String u, String pwd, String nom, String cog, String tel) { this.user=u; this.password=pwd; this.nome=nom; this.cognome=cog; this.telefono=tel; System.out.println("creato objectRegistration"); } public String getUser() { return this.user; } public String getPassword() { return this.password; } public String getNome() { 129 APPENDICE B: Codice Proxy return this.nome; } public String getCognome() { return this.cognome; } public String getTelefono() { return this.telefono; } } ObjectPrenotation.java import java.io.*; public class ObjectPrenotation implements Serializable { private String user; private String password; public ObjectPrenotation (String user, String password){ this.user=user; this.password=password; } public String getUser() { return this.user; } public String getPassword() { return this.password; } } ObjectTavoli.java import java.io.*; public class ObjectTavoli implements Serializable{ private String[] utente; private int[] num_tavolo; private int[] libero; public ObjectTavoli(String[] utente, int[] num, int[] libero) { this.num_tavolo=num; this.libero=libero; this.utente=utente; } public String getUtente(int i) { return utente[i]; } public int getNumero(int i) { return num_tavolo[i]; } public int getLibero(int i) { return libero[i]; } public int getLength() { return utente.length; } } 130 Siti e Bibliografia SITI E BIBLIOGRAFIA www.mhp.org www.openmhp.org www.dvb.org www.etsi.org www.cineca.it www.sun.it www.myslq.com www.java.sun.com www.javastaff.com www.sourceforge.net www.eclipse.org www.coremedia.com www.televisionedigitaleterrestre.it www.interactivetvweb.org www.digisoft.it www.digitaleterrestre.it Steven Morris, Anthony Smith-Chaigneau: “Interactive TV Standard – A Guide to MHP, OCAP and Java TV”. George Reese: “O'Reilly-Database Programming with JDBC and Java, Second Edition”. V.Mignone, A.Morello, M.Visintin: “Lo standard DVB-T per la televisione digitale terrestre”. E.Fleischner,B.Somalvico: "La TV diventa digitale", Franco Angeli, 2002. 131