PHP: un socket TCP per acquisire dati dal GPS Tracker GPS-102 compatibile OpenGTS (seconda parte)

Socket PHP
Socket PHP

Nella prima parte di questo articolo ho illustrato come creare le tabelle del DB dove registrare i dati provenienti dal tracker GPS, le principali funzionalità dello script e la possibilità di “agganciare” questo socket direttamente al software Open Source: OpenGTS.

Quest’ultima caratteristica, la ritengo particolarmente interessante in quanto permette di evitare di scriversi tutta la parte che riguarda la visualizzazione dei punti inviati dal tracker, sulle mappe.

Settaggi del socket
ChiaveValori possibiliDescrizione
VERBOSEtrue|falseSe impostato a true fornisce un output dettagliato degli errori
MOVING_THRESHOLD.05Soglia minima in Km per la registrazione del dato [.05 = 50 metri]
OPENGTStrue|falseSe impostato a true invia query per le tabelle OpenGTS
IP_ADDRxxx.xxx.xxx.xxxIndirizzo IP in ascolto [0 = tutti gli indirizzi]
TCP_PORT0..65535La porta TCP da utilizzare
DBHOSTlocalhostIndirizzo del DB MySQL
DBUSERdbuserutente del DB MySQL
DBPASSdbpasswordpassword dell’utente del DB MySQL
DBNAMEgpsdnome del DB MySQL
POLL_TIME20|30|60|300|600Tempo di polling del tracker in secondi
SPEED_CONV1.609344|1.852Conversione da Miglia (terrestri|marine) a Km
DFLT_MSG‘tracker’Messaggio di default del tracker
SOCK_RCV_TIMEOUT120Timeout in secondi per il socket in ricezione

Ed ecco il codice PHP del loop del socket (L’intero script è disponibile qui):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#!/usr/bin/env php
< ?php
/***********************************************************************
 *  SETTINGS
 * ********************************************************************/
define("VERBOSE", true);
define("MOVING_THRESHOLD",.04); //.05 = 50 metres
define("OPENGTS",false);
/* HOST                                                               */
define("IP_ADDR","0");	// "0" = listen all ip
define("TCP_PORT",5050);
/* DATABASE                                                           */
define("DBHOST","localhost");
define("DBUSER","dbuser");
define("DBPASS","dbpassword");
define("DBNAME","gpsd");
/* TRACKER                                                            */
define("POLL_TIME",60); // SET POLL TIME 20,30,60,300,600 default:60 secs
define("SPEED_CONV",1.852); //From NM to Km
define("DFLT_MSG","tracker");
/* SOCKET                                                             */
define("SOCK_RCV_TIMEOUT",120); // Socket receive timeout in seconds
/***********************************************************************
 * END SETTINGS
 * ********************************************************************/
// Do not edit here-----------------------------------------------------
error_reporting(E_ALL);
// Do not exit while waiting for connect...
set_time_limit(0);
// Turn implicit flush on
ob_implicit_flush();
 
$dblink = dbConnect();
$address =  IP_ADDR;
$port = TCP_PORT;
$allowedIMEI = getAllowedImei($dblink);
$sendPollTime = false;
 
// Create the socket and bind it to the host and port, with infinite loop.
 
if (($sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) < 0) {
   writeLog("socket_create() failed: ".socket_strerror($sock),true);
}
if (($ret = socket_bind($sock, $address, $port)) < 0) {
   writeLog("socket_bind() failed: ".socket_strerror($ret),true);
}
if (($ret = socket_listen($sock, 5)) < 0) {
   writeLog("socket_listen() failed: ".socket_strerror($ret),true);
}
do {
	if (!mysql_ping ($dblink)) { //check if mysql connection is active
		mysql_close($dblink);
		$dblink = dbConnect(); //if not, connect!
	}
	try {
		if (FALSE === ($msgsock = socket_accept($sock))) {
		  throw new Exception("socket_accept() failed: " . socket_strerror(socket_last_error($msgsock)));
		}
		socket_set_option($msgsock, SOL_SOCKET, SO_RCVTIMEO, array('sec' => SOCK_RCV_TIMEOUT,'usec' => 0));
		writeLog("CONNECT");
		if (FALSE === ($buf = socket_read($msgsock, 2048))) {
		  throw new Exception("socket_read() failed: " . socket_strerror(socket_last_error($msgsock)));
		}
		writeLog("RECEIVED: ".$buf);
		$actualIMEI = "";
		$outData = array();
		$buf = trim($buf); // clean up input string
		$dirtyMode = (substr($buf, 0, 2) == "##") ? false : true;
		$actualIMEI = (!$dirtyMode) ? substr($buf, 8, 15) : substr($buf, 5, 15);  // returns IMEI
 
		if (!in_array($actualIMEI, $allowedIMEI)){
		  throw new Exception("Received: $actualIMEI from $buf, IMEI not allowed");
		}
		if (!$dirtyMode){
			$output = "LOAD". "\n";
			writeLog("SEND: LOAD");
			// Send intructions
			socket_write($msgsock, $output, strlen($output));
			if (FALSE === ($buf = socket_read($msgsock, 2048))) {
		  		throw new Exception("socket_read() failed: " . socket_strerror(socket_last_error($msgsock)));
			}
			$buf = trim($buf);
			writeLog("RECEIVED: ".$buf);
			if (empty($buf)){
				 throw new Exception("Received: nothing, disconnect");
			}
 
			if (($sendPollTime === false)) {
				$output = "ON". "\n";
				writeLog("SEND: ON");
				socket_write($msgsock, $output, strlen($output));
				$output = "**,imei:".$actualIMEI."," . pollTimeString(POLL_TIME)."\n";
				socket_write($msgsock, $output, strlen($output));
				writeLog("SEND: ".$output);
				if (FALSE === ($buf = socket_read($msgsock, 2048))) {
					$sendPollTime = false;
					throw new Exception("socket_read() failed: " . socket_strerror(socket_last_error($msgsock)));
				}
				$buf = empty($buf) ? "NO DATA" : trim($buf);
				$sendPollTime = true;
			}
		}
		$outData = explode ( "," , $buf );
		writeLog("DATA: ".$buf);
		if(count($outData)>=5){
			$outDecodedData = decodeData($outData);
			if ($outDecodedData['DATA_FL'] == "F" || $outDecodedData['MSG'] !== DFLT_MSG){
				if (OPENGTS) {
					$res = updatePosOpenGTS($outDecodedData);
				}
				else{
					$res = updatePos($outDecodedData);
				}
			}
		}
	} catch (Exception $e) {
		writeLog(" ".$e->getMessage(),true);
	}
	socket_close($msgsock);
	writeLog("SOCKET CLOSE");
} while (true);
socket_close($sock);
dbClose($dblink);
// ... continua
?>

Come si può notare il socket $sock è in costante ascolto, e quando arriva una richiesta di connessione viene creato il socket $msgsock che si prende il carico dell’intera comunicazione. Se il tracker sta trasmettendo in “single point”, i primi due caratteri sono: ##, in questo caso viene inviato il comando di polling forzando l’apparecchio a trasmettere in “multi point” con l’intervallo desiderato.

Vediamo adesso la funzione che si occupa della decodifica del messaggio che contiene i dati veri e propri. Questo è formato da 12 campi separati da virgola. Nei commenti iniziali è riportata la loro composizione, il formato e i possibili valori.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
function decodeData($arr){
/* *********************************************************************
*   0 = imei:000000000000000	[imei]
*   1 = tracker					[Msg: help me / low battery / stockade /
* 								dt /move / speed / tracker]
*   2 = 0809231929				[acquisition time: YYMMDDhhmm +8GMT cn]
*   3 = 13554900601				[adminphone?]
*   4 = F					[Data: F - full / L - low]
*   5 = 112909.397				[Time (HHMMSS.SSS)]
*   6 = A					[A = available?]
*   7 = 2234.4669				[Latitude (DDMM.MMMM)]
*   8 = N					[Lat direction: N / S]
*   9 = 11354.3287				[Longitude (DDDMM.MMMM)]
*  10 = E					[Lon direction: E / O]
*  11 = 0.11					[speed Mph]
***********************************************************************/
	$out = array();
	$out['IMEI'] = substr($arr[0], 5, 15);
	$out['MSG'] = trim($arr[1]);
	$out['ACQUISITION_TIME'] = substr($arr[2], 0, 2)."-".
						substr($arr[2], 2, 2)."-".substr($arr[2], 4, 2).
						" ".substr($arr[2],6,2).":".substr($arr[2],8,2);
	$out['ADMINPHONE'] = trim($arr[3]);
	$out['DATA_FL'] = trim($arr[4]);
	 if ($out['DATA_FL'] === "F"){
		 $out['TIME'] = substr($arr[5], 0, 2).":" . substr($arr[5], 2, 2).":" . sprintf("%2d",round(floatval(substr($arr[5], 4, 6))));
		 $out['AVAILABLE'] = $arr[6]==="A" ? 1 : 0;
		 $out['LAT'] = floatval(substr($arr[7], 0, 2)) +
					   floatval(substr($arr[7], 2, 7)) / 60;
		 $out['LAT'] = $arr[8]==="N" ? $out['LAT'] : -$out['LAT'];
		 $out['LON'] = floatval(substr($arr[9], 0, 3))  +
					   floatval(substr($arr[9], 3, 7)) / 60;
		 $out['LON'] = $arr[10]==="E" ? $out['LON'] : -$out['LON'];
		 $out['SPEED'] = floatval($arr[11]) * SPEED_CONV;
	}
	else {
		 $out['TIME'] = "00:00:00";
		 $out['AVAILABLE'] = 0;
		 $out['LAT'] = (float) 0;
		 $out['LON'] = (float) 0;
		 $out['SPEED'] = (float) 0;
	}
	return $out;
}

Rimane un dubbio sul campo 3, che quasi sicuramente dovrebbe essere il numero di telefono abilitato alla comunicazione SMS con il tracker e sull’unità di misura utilizzata per la velocità. I cinesi della Cobanch dicono che è Km, ma sperimentalmente è facile smentirli. Sempre sperimentalmente, ho potuto verificare con accettabile precisione che si tratta di NM (miglia nautico internazionale)

Nella terza parte di questo articolo vedremo come avviare lo script su un server Linux facendolo funzionare in background come “demone”.