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
Chiave Valori possibili Descrizione
VERBOSE true|false Se impostato a true fornisce un output dettagliato degli errori
MOVING_THRESHOLD .05 Soglia minima in Km per la registrazione del dato [.05 = 50 metri]
OPENGTS true|false Se impostato a true invia query per le tabelle OpenGTS
IP_ADDR xxx.xxx.xxx.xxx Indirizzo IP in ascolto [0 = tutti gli indirizzi]
TCP_PORT 0..65535 La porta TCP da utilizzare
DBHOST localhost Indirizzo del DB MySQL
DBUSER dbuser utente del DB MySQL
DBPASS dbpassword password dell’utente del DB MySQL
DBNAME gpsd nome del DB MySQL
POLL_TIME 20|30|60|300|600 Tempo di polling del tracker in secondi
SPEED_CONV 1.609344|1.852 Conversione da Miglia (terrestri|marine) a Km
DFLT_MSG ‘tracker’ Messaggio di default del tracker
SOCK_RCV_TIMEOUT 120 Timeout in secondi per il socket in ricezione

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

#!/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.

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”.

Questa voce è stata pubblicata in Informatica e contrassegnata con , , , , . Contrassegna il permalink.

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

  1. Pingback: PHP: un socket TCP per acquisire dati dal GPS Tracker GPS-102 compatibile OpenGTS (terza parte) | Il blog di Mario Spada