Vous êtes sur la page 1sur 26

PHP: un socket TCP pour capturer des

donnes partir du GPS Tracker GPS-102


compatible OpenGTS (Premire partie)
Post le 27 novembre 2011 par spadamar

Il y a quelques mois, on m'a donn ce tracker GPS pour tudier comment enregistrer des
donnes sur une base de donnes MySQL. L'appareil s'appelle GPS-102 est produit par
Coban.ch et est galement disponible en Italie un cot infrieur 100 sur ce site:
www.nonsoloprevenzione.it

tant donn que la documentation ci-jointe est, comme c'est souvent le cas, assez rare, j'ai
cherch sur le Web des spcifications techniques et des datagrammes pour la
communication de donnes fournie par le tracker (coordonnes, vitesses, etc.).
Heureusement, j'ai trouv cette feuille de calcul, qui se rfre TK102, TK103, mais le
protocole est identique et fournit donc les informations ncessaires au dveloppement du
logiciel.

Il convient de dire que l'appareil a besoin d'une carte GSM (comme indiqu sur l'image) et
utilise normalement des SMS pour les paramtres et pour la communication des donnes
de localisation. Si la carte est active pour le trafic GPRS , vous pouvez envoyer les donnes
sous forme de paquets TCP directement un serveur sur le rseau en configurant
simplement l'adresse IP du serveur et le port de communication TCP via SMS.

Bien que beaucoup de traceurs commerciaux utilisent des connexions UDP, ce priphrique
utilise le protocole TCP orient connexion , qui, au dtriment d'une plus grande bande
passante et d'une diminution modre des performances de vitesse, garantit une
transmission sans erreur.

Les donnes enregistrer sont principalement:

1. latitude
2. longitude
3. vitesse

Ce sont les donnes ncessaires pour crer la piste fournie par Google Maps sur Google
Maps ou tout autre systme de cartographie de golocalisation (p. Ex. OpenstreetMap ). Les
autres informations sont: le code IMEI (requis pour l'identification unique de l'appareil), les
codes d'alarme (batterie faible, etc.), la date et l'heure et la validit des donnes.

Voici le SQL pour crer les deux tables ncessaires dans une base de donnes MySQL:

-- DATABASE SCHEMA:
CREATE SCHEMA IF NOT EXISTS `gpsd` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci
;
USE `gpsd` ;
-- -----------------------------------------------------
-- Table `gpsd`.`devices`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `gpsd`.`devices` (
`id` INT NOT NULL AUTO_INCREMENT ,
`imeiNumber` VARCHAR(16) NOT NULL ,
PRIMARY KEY (`id`) )
ENGINE = MyISAM;
-- -----------------------------------------------------
-- Table `gpsd`.`positions`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `gpsd`.`positions` (
`IDpositions` INT NOT NULL AUTO_INCREMENT ,
`msg` VARCHAR(255) NULL ,
`timestamp` INT NOT NULL ,
`acq_time` DATETIME NULL ,
`track_time` TIME NULL ,
`is_valid` TINYINT NOT NULL DEFAULT 0 ,
`latitude` DOUBLE NULL ,
`longitude` DOUBLE NULL ,
`speedKPH` DOUBLE NULL ,
`course` DOUBLE NULL DEFAULT 0 ,
`device_id` INT NOT NULL ,
PRIMARY KEY (`IDpositions`) )
ENGINE = MyISAM;
Nous arrivons au code PHP pour la ralisation du socket. Les caractristiques dveloppes
sont:

Une prise de boucle infinie, en coutant le port rgl


Un socket de communication
Une fonction de dcodage de donnes
Une fonction pour la mise jour des tables DB
Une fonction pour contrler la distance du dernier point mmoris
Service de connexion DB, echo de messages, etc.

Plus prcisment, partir d'une analyse prliminaire du flux TCP via TCPDUMP, j'ai observ
que le traceur envoie les donnes de deux faons diffrentes:

1. Point unique: requiert que le serveur rpond avec un message spcifique, en envoyant des
paramtres spciaux tels que le temps d'interrogation.
2. Multi point: ne requiert aucune rponse, envoie le datagramme directement.

Pour cette raison, j'ai ralis le code pour comprendre automatiquement si le client
transmet dans l'un des deux modes (ce point multiple, je l'ai appel sale).

Une autre observation est que lorsque le traceur est stationnaire, en raison d'une
imprcision relative du systme GPS (les satellites ne sont pas ralistes et ne sont pas
toujours les mmes), les coordonnes diffrent lgrement. Ceci, tout en fournissant une
surcharge de donnes inutile la base de donnes, entrane un nuage indterminable de
points (mapers) en tant que sortie graphique sur la carte. La contre-mesure tait d'effectuer
une fonction qui calcule la distance entre les coordonnes du point juste transmis et celles
du dernier point enregistr. Si la distance est infrieure un certain seuil (dfinie dans les
paramtres initiaux), le point n'est pas enregistr dans le DB.

Une autre caractristique mettre en vidence est la possibilit d'utiliser la base de donnes
MySQL Open Source : OpenGTS , un systme intgr Java / Tomcat qui vous permet de
visualiser des cartes personnalisables, de coordonner les donnes fournies par un grand
nombre de traceurs commerciaux .

Dans la prochaine publication, je vais fournir la source complte du script et une explication
dtaille des paramtres initiaux.
PHP: un socket TCP pour capturer des
donnes partir du GPS Tracker GPS-102
compatible OpenGTS (deuxime partie)
Post le 30 novembre 2011 par spadamar

Socket PHP

Dans la premire partie de cet article, j'ai montr comment crer des tables DB pour
enregistrer des donnes partir du GPS tracker, les principales caractristiques du script et
la possibilit d'accrocher ce socket directement au logiciel Open Source: OpenGTS.

Cette dernire caractristique, je la trouve particulirement intressante, car elle vous permet
d'viter d'crire toute la partie qui pointe sur les points affichs par le traceur sur les cartes.

Paramtres de socket
mot-cl Valeurs possibles description
VERBOSE true | false Si dfini sur true, il fournit une sortie d'erreur dtaille
MOVING_THRESHOLD .05 Seuil minimum en km pour l'enregistrement des
donnes [0,05 = 50 mtres]
OpenGTS true | false Si dfini sur true, il envoie des requtes pour les tables
OpenGTS
IP_ADDR xxx.xxx.xxx.xxx Adresse de l'adresse IP [0 = toutes les adresses]
tcp_port 0..65535 Le port TCP utiliser
dbhost localhost Adresse DB MySQL
DBUSER dbuser Utilisateur DB MySQL
DBPASS dbpassword Mot de passe de MySQL DB
DBNAME gpsd Nom de base de donnes MySQL
POLL_TIME 20 | 30 | 60 | 300 | Temps d'interrogation du suivi en secondes
600
SPEED_CONV 1.609344 | 1852 Conversion de Miles (terrestres | marinas) Km
DFLT_MSG 'Tracker' Message par dfaut du Tracker
SOCK_RCV_TIMEOUT 120 Dlai en secondes pour recevoir le socket

Et voici le code PHP de la boucle de socket (Le script complet est disponible ici ):

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

Comme vous pouvez le voir, la douille $sock coute constamment, et lorsqu'une demande
de connexion arrive, la socket $msgsock est cre et prend toute la charge de
communication. Si le tracker transmet en "point unique", les deux premiers caractres sont:
## , dans ce cas, la commande de vote est envoye, forant l'unit transmettre "multi
points" avec la plage souhaite.

Examinons maintenant la fonction de dcryptage du message contenant les donnes relles.


Ceci est compos de 12 champs spars par des virgules. Les commentaires initiaux incluent
leur composition, leur format et leurs valeurs possibles.

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;
}
Il existe un doute dans le champ 3, qui devrait certainement tre le numro de tlphone
activ pour la communication SMS avec le tracker et l'unit utilise pour la vitesse. Les
Chinois de Cobanch disent que c'est Km, mais il est exprimentalement facile de les
discrditer. Toujours exprimentalement, j'ai pu vrifier avec une prcision acceptable que
c'est NM (mile nautique international)

Dans la troisime partie de cet article, nous verrons comment dmarrer le script sur un
serveur Linux qui l'excute en arrire-plan en tant que daemon.

PHP: un socket TCP pour capturer des


donnes du GPS Tracker GPS-102
compatible OpenGTS (tiers)
Publi le 11 dcembre 2011 par Spadamar

php daemon

Dans ce dernier article, je dcris comment le script PHP s'excute sur un systme
d'exploitation Linux Ubuntu ou Debian Linux. Comme vous le remarquerez, le script dcrit
dans la deuxime partie contient le code suivant dans la premire ligne:

#!/usr/bin/env php

En verbe, cette syntaxe s'appelle Shebang et est utilise pour lancer partir du shell
l'interprteur de script qui va tre excut. Dans ce cas, le PHP. Vous devrez alors vrifier
que PHP5-CLI (interprteur de ligne de commande) est install.

Si nous essayons de lancer, simplement en tapant gpsspck.php partir d'un terminal, ceci
est mis ds que le tracker est "accroch":
Pour le lancer en arrire-plan, il s'agit du script bash que j'ai prpar pour grer le
dmarrage et l'arrt du socket PHP:

#!/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
NAME=gpssock
PIDFILE="/var/run/${NAME}.pid"
WORKINGDIR="/path/to/gpssock/"
DAEMON="${WORKINGDIR}${NAME}.php"
DAEMONLOG="${WORKINGDIR}${NAME}.out"

case "$1" in
start)
start-stop-daemon --start --make-pidfile --pidfile=$PIDFILE --exec
$DAEMON>>$DAEMONLOG &
;;
restart|reload|force-reload)
echo "Error: argument '$1' not supported" >&2
exit 3
;;
stop)
start-stop-daemon --stop --pidfile=$PIDFILE
;;
*)
echo "Usage: $0 start|stop" >&2
exit 3
;;
esac

Le script excute start-stop-daemon qui est un programme frquemment utilis sur Ubuntu
pour dmarrer et mettre fin des processus qui fonctionnent comme des dmons. Il existe
plusieurs exemples dans le rpertoire /etc/init.d/ et en fait c'est juste l que nous placerons
notre script. Les deux seuls faire sont de modifier, le cas chant, la variable NAME avec le
nom du fichier PHP lancer (dans notre cas gpssock) et dfinir le WORKINGDIR travail
WORKINGDIR avec le chemin d'accs correct de gpssock.php
Notez que le dernier paramtre start-stop-daemon est & cela indique que le processus doit
tre excut en arrire-plan. Enfin, toutes les sorties sont rediriges vers un fichier journal
qui a le mme nom que le script PHP et l'extension .out
Pour lancer le script de la ligne de commande:

/etc/init.d/gpssock start

Pour terminer le script de ligne de commande:

/etc/init.d/gpssock stop

Si nous voulons que le script commence s'excuter automatiquement, vous devez


l'enregistrer avec cette instruction:

update-rc.d gpssock defaults

[Mise jour:] Antonino Celona souligne qu'il est conseill de dfinir une priorit de
dmarrage suffisamment leve pour empcher le dmarrage du serveur socket avant le
serveur MySQL, comme ceci:

update-rc.d gpssock defaults 99

rfrences:

OpenGTS
Ouvrir le serveur de suivi GPS
Socket Programming With PHP
PHP: Sockets - Manual

PHP: socket multi-processus TCP pour


acqurir des donnes du GPS Tracker GPS-
102 compatible avec OpenGTS
Publi le 5 novembre 2015 par Spadamar
socket php multi-processus

Dans les articles prcdents PHP: un socket TCP pour acqurir des donnes partir du GPS
Tracker GPS-102 compatible OpenGTS ( premire , deuxime et troisime partie ), j'ai dcrit
comment implmenter un socket TCP en PHP pour acqurir des donnes GPS partir d'un
suivi TK -102 (et compatible).

Le script, cependant, a t conu pour un processus unique, ce qui signifie qu'il ne pouvait
pas servir plus d'une connexion la fois et n'tait donc pas capable de grer les connexions
concurrentes.

Cependant, il est trs utile qu'un serveur socket de ce type puisse grer des connexions
simultanes afin de charger plus de traceurs GPS en mme temps. Heureusement, le
langage PHP prend en charge la programmation multiprocesseur en utilisant le fork() . De
cette faon, le processus parent impliqu dans une nouvelle connexion gnre un processus
enfant qui partage des variables et des ressources et reste libre d'accepter de nouvelles
connexions.

J'ai profit de l'opportunit de rcrire le code en mode multiprocesseur pour mettre jour
la connexion au serveur de base de donnes MySQL l'aide de l'extension mysqli au lieu de
l'original mysql maintenant obsolte dans PHP 5.5.x. Enfin, j'ai simplifi le code en prenant le
contrle du seuil de mouvement et en corrigeant certains bugs.

Un bon exemple d'criture de socket multi-processus est ici , et de cet exemple, j'ai adopt
le cadre de code.

Voici le code complet:

#! / usr / bin / php -q


<? Php
/ ************************************************* **********************
* Mstracker est dvelopp avec GPL License 2.0
*
* Licence GPL: http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
*
* Dvelopp par: Mario Spada partir d'une ide de Cyril Feraudet
* : https://github.com/feraudet/tracker/blob/master/trackerTK102
*
* Web: http://www.spadamar.com
*
* Ce programme est un logiciel gratuit; vous pouvez le redistribuer et / ou
* modifiez-le selon les termes de la licence GNU General Public License
* publi par Free Software Foundation; soit la version 2
* de la licence, ou ( votre choix) toute version ultrieure.
*
* Ce programme est distribu dans l'espoir qu'il sera utile,
* mais SANS AUCUNE GARANTIE; sans mme la garantie implicite de
* QUALIT MARCHANDE OU ADAPTATION DES FINS PARTICULIERS. Voir le
* GNU General Public License pour plus de dtails.
*
* Pour information: spadamar@spadamar.com
************************************************** ********************* /

/ ************************************************* **********************
* RGLAGES
************************************************** ******************* /
// Constantes ----------------------------------------------- ------------
/ * HOST * /
dfinir ( "IP_ADDR" , "0.0.0.0" ) ; // "0.0.0.0" = couter tout ip
dfinissez ( "TCP_PORT" , 5050 ) ; // 5050
dfinissez ( "DEBUG" , true ) ;
/ * TRACKER * /
dfinissez ( "POLL_TIME" , 60 ) ; // SET POLL TIME 20,30,60,300,600 par dfaut: 60
secs
dfinissez ( "SPEED_CONV" , 1.852 ) ; // De kilomtre de NM Km
dfinir ( "DFLT_MSG" , "tracker" ) ;
/ * BASE DE DONNES * /
dfinissez ( "DBHOST" , "127.0.0.1" ) ;
dfinissez ( "DBUSER" , "your_db_user" ) ;
dfinissez ( "DBPASS" , "votre_db_password" ) ;
dfinir ( "DBNAME" , "gts" ) ;
/ * AUTRES RGLAGES * /
dfinissez ( "OPENGTS" , true ) ;
dfinissez ( "USE_SERVER_TIME" , true ) ;
// Variables ----------------------------------------------- ------------
$ __ server_listening = true ;
$ sendPollTime = false ;
// ------------------------------------------------ ---------------------
/ ************************************************* **********************
* INIT
************************************************** ******************* /
error_reporting ( E_ALL ) ;
set_time_limit ( 0 ) ;
ob_implicit_flush ( ) ;
dclarer ( tiques = 1 ) ;

$ pid = become_daemon ( ) ;
write_pid ( $ pid ) ;

/ * nobody / nogroup, modifiez le uid / gid de votre hte de l'utilisateur non priv
* /
change_identity ( 65534 , 65534 ) ;

/ * signal de commande * /
pcntl_signal ( SIGTERM , 'sig_handler' ) ;
pcntl_signal ( SIGINT , 'sig_handler' ) ;
pcntl_signal ( SIGCHLD , 'sig_handler' ) ;

/ * rgler la fuseau horaire par dfaut * /


date_default_timezone_set ( 'Europe / Rome' ) ;
/ ************************************************* **********************
* coute des requtes et des fourchettes sur chaque connexion
************************************************** ******************* /
server_loop ( ) ;
/ ************************************************* **********************
* Modifier l'identit vers un utilisateur non priv
* @param int $ uid identifiant utilisateur linux
* @param int $ gid linux group id
************************************************** ******************* /
function change_identity ( $ uid , $ gid ) {
si ( ! posix_setgid ( $ gid ) ) {
$ msg = "Impossible de dfinir" . $ gid . "!" ;
writeLog ( $ msg , true ) ;
sortie ;
}
si ( ! posix_setuid ( $ uid ) ) {
$ msg = "Impossible de configurer" . $ uid . "!" ;
writeLog ( $ msg , true ) ;
sortie ;
}
}
/ ************************************************* **********************
* Cre un socket serveur et coute les connexions client entrantes
* @param string $ address L'adresse couter sur
* @param int $ port Le port couter
************************************************** ******************* /
function server_loop ( ) {
GLOBAL $ __ server_listening ;
si ( ( $ sock = socket_create ( AF_INET , SOCK_STREAM , 0 ) ) < 0 ) {
$ msg = "failed to create socket:" . socket_strerror ( $ sock ) ;
writeLog ( $ msg , true ) ;
sortie ;
}
si ( ( $ ret = socket_bind ( $ sock , IP_ADDR , TCP_PORT ) ) <
$ msg = "failed to bind socket:" . socket_strerror ( $ ret ) ;
writeLog ( $ msg , true ) ;
sortie ;
}
si ( ( $ ret = socket_listen ( $ sock , 0 ) ) < 0 ) {
$ msg = "n'a pas russi couter le socket:" . socket_strerror ( $ ret ) ;
writeLog ( $ msg , true ) ;
sortie ;
}
socket_set_nonblock ( $ sock ) ;
writeLog ( "attente de connexion des clients" ) ;
while ( $ __ server_listening ) {
$ connection = @ socket_accept ( $ sock ) ;
si ( $ connection === false ) {
usleep ( 100 ) ;
} elseif ( $ connection > 0 ) {
handle_client ( $ sock , $ connection ) ;
} else {
$ msg = "erreur:" . socket_strerror ( connexion $ ) ;
writeLog ( $ msg , true ) ;
}
}
}
/ ************************************************* **********************
* Gestionnaire de signal
* @param int $ sig Le numro de signal
************************************************** ******************* /
fonction sig_handler ( $ sig ) {
commutateur ( $ sig ) {
maisons SIGTERM :
case SIGINT :
sortie ;
rupture ;
Cas SIGCHLD :
pcntl_waitpid ( - 1 , $ status ) ;
rupture ;
}
}
/ ************************************************* **********************
* Grer une nouvelle connexion client
* @param $ ssock resource
* @param $ csock resource
************************************************** ******************* /
function handle_client ( $ ssock , $ csock ) {
GLOBAL $ __ server_listening ;
$ pid = pcntl_fork ( ) ;
si ( $ pid == - 1 ) {
writeLog ( "chec de la fourchette!" , vrai ) ;
sortie ;
} elseif ( $ pid == 0 ) {
/ * processus enfant * /
writeLog ( "CONNECT" ) ;
$ __ server_listening = false ;
socket_close ( $ ssock ) ;
interagir ( $ csock ) ;
socket_close ( $ csock ) ;
writeLog ( "SOCKET CLOSE" ) ;
} else {
socket_close ( $ csock ) ;
writeLog ( "SOCKET CLOSE" ) ;
}
}
/ ************************************************* **********************
* Parler au client
* @param $ ressource socket
************************************************** ******************* /
fonction interagir ( $ socket ) {
Global $ sendPollTime ;
essayez {
$ buf = "" ;
socket_recv ( $ socket , $ buf , 2048 , 0 ) ;
writeLog ( " REU :" . $ buf ) ;
$ outData = array ( ) ;
$ rec = trim ( $ buf ) ; // nettoyer la chane d'entre
$ multiplePos = ( substr ( $ buf , 0 , 2 ) == "##" ) ? faux : vrai
;
$ realIMEI = ( ! $ multiplePos ) ? substr ( $ buf , 8 , 15 ) :
substr ( $ buf , 5 , 15 ) ; // renvoie IMEI
si ( ! $ multiplePos ) {
$ output = "LOAD" . " \ n " ;
writeLog ( "ENVOYER: CHARGER" ) ;
// Envoyer des instructions
socket_write ( $ socket , $ output , strlen ( $ output ) ) ;
if ( FALSE === ( $ buf = socket_read ( $ socket , 2048 ) ) )
lancer une nouvelle exception ( "socket_read () a
chou:" . socket_strerror ( socket_last_error ( $ socket ) ) ) ;
}
$ buf = trim ( $ buf ) ;
writeLog ( " REU :" . $ buf ) ;
si ( vide ( $ buf ) ) {
lancer une nouvelle exception ( "Reu: rien,
dconnecter" ) ;
}
if ( ( $ sendPollTime === false ) ) {
$ output = "ON" . " \ n " ;
writeLog ( "ENVOYER: ON" ) ;
socket_write ( $ socket , $ output , strlen ( $
output ) ) ;
$ output = "**, imei:" . $ actualIMEI . "," .
pollTimeString ( POLL_TIME ) . " \ n " ;
socket_write ( $ socket , $ output , strlen ( $
output ) ) ;
writeLog ( "SEND:" . $ output ) ;
if ( FALSE === ( $ buf = socket_read ( $ socket ,
2048 ) ) )
$ sendPollTime = false ;
lancer une nouvelle exception ( "socket_read
() a chou:" . socket_strerror ( socket_last_error ( $ msgsock ) ) ) ;
}
$ buf = vide ( $ buf ) ? "NO DATA" : trim ( $ buf )
;
$ sendPollTime = true ;
}
}
$ outData = explose ( "," , $ buf ) ;
if ( count ( $ outData ) > = 5 ) {
$ outDecodedData = decodeData ( $ outData ) ;
si {$ outDecodedData [ 'DATA_FL' ] == "F" || $ outDecodedData
[ 'MSG' ] ! == DFLT_MSG ) {
writeLog ( print_r ( $ outDecodedData , true ) ) ;
si ( OPENGTS ) {
$ res = updatePosOpenGTS ( $ outDecodedData )
;
} else {
$ res = updatePosGpsd ( $ outDecodedData ) ;
}
}
}
} capture ( Exception $ e ) {
writeLog ( "" . $ e -> getMessage ( ) , true ) ;
}

}
/ ************************************************* **********************
* Devenez un dmon en bifurquant et en fermant le parent
************************************************** ******************* /
function become_daemon ( ) {
$ pid = pcntl_fork ( ) ;
si ( $ pid == - 1 ) {
/ * fork failed * /
echo "chec de la fourchette! \ n " ;
exit ( ) ;
} elseif ( $ pid ) {
/ * ferme le parent * /
exit ( ) ;
} else {
/ * l'enfant devient notre dmon * /
posix_setsid ( ) ;
chdir ( '/' ) ;
umask ( 0 ) ;
retour posix_getpid ( ) ;
}
}
/ ************************************************* **********************
* Write pid
* @param int $ pid Process ID
************************************************** ******************* /
function write_pid ( $ pid ) {
$ fn = preg_replace ( '/\.php$/' , '' , __FILE__ ) ;
$ fn . = "_pid" ;
$ fp = fopen ( $ fn , 'w' ) ;
fwrite ( $ fp , $ pid ) ;
fclose ( $ fp ) ;
}
/ ************************************************* **********************
* Ecrire le journal
* @param string $ msg Texte crire
* @param bool $ is_error Si l'erreur est toujours crite
************************************************** ******************* /
function writeLog ( $ msg , $ is_error = false ) {
$ msg = now ( ) . "" . $ msg . " \ n " ;
if ( $ is_error ) {
echo $ msg ;
} else {
si ( DEBUG )
echo $ msg ;
}
retourner vrai ;
}
/ ************************************************* **********************
* Date et heure de retour
* **************************************************** ****************** /
activez maintenant ( $ secs = 0 ) {
date de retour ( "Ymd H: i: s" , heure ( ) + $ secs ) ;
}
/ ************************************************* **********************
* Vrifiez si une date valide
* @param $ string string Chane de date
* **************************************************** ****************** /
function is_valid_date ( $ str ) {
$ str = str_replace ( "-" , "" , $ str ) ;
$ str = str_replace ( ":" , "" , $ str ) ;
$ arrDate = explose ( "" , $ str ) ;
foreach ( $ arrDate en $ val ) {
si ( ! is_numeric ( $ val ) ) {
renvoyer faux ;
}
}
$ res = checkdate ( intval ( $ arrDate [ 1 ] ) , intval ( $ arrDate [ 2 ] )
, intval ( $ arrDate [ 0 ] ) ) ;
$ res = $ res && ( $ arrDate [ 3 ] > = 0 && $ arrDate [ 3 ] <= 24 ) ;
$ res = $ res && ( $ arrDate [ 4 ] > = 0 && $ arrDate [ 4 ] <= 59 ) ;
retourner $ res ;
}
/ ************************************************* **********************
* Composez une chane d'intervalle de temps
* @param $ sec int secondes
* **************************************************** ****************** /
function pollTimeString ( $ secs ) {
$ secs = intval ( $ secs ) ;
commutateur ( $ secs ) {
cas 20 :
$ res = "20s" ;
rupture ;
cas 30 :
$ res = "30s" ;
rupture ;
cas 60 :
$ res = "01m" ;
rupture ;
cas 300 :
$ res = "05m" ;
rupture ;
cas 600 :
$ res = "10m" ;
rupture ;
par dfaut :
$ res = "01m" ;
}
retourner "C" . $ res ;
}
/ ************************************************* **********************
* Dcodage des donnes
* @param $ arr array Ensemble de donnes
* **************************************************** ****************** /
function decodeData ( $ arr ) {
/ * **************************************************** *********************
* 0 = imei: 000000000000000 [imei]
* 1 = tracker [Msg: help me / low battery / stockade /
* dt / move / speed / tracker]
* 2 = 0809231929 [temps d'acquisition: YYMMDDhhmm + 8GMT cn]
* 3 = 13554900601 [adminphone?]
* 4 = F [Donnes: F - plein / L - bas]
* 5 = 112909.397 [Time (HHMMSS.SSS)]
* 6 = Est-ce que [A = est disponible?]
* 7 = 2234.4669 [Latitude (DDMM.MMMM)]
* 8 = N [Lat direction: N / A]
* 9 = 11354.3287 [Longitude (DDDMM.MMMM)]
* 10 = E [direction Lon: E / O]
* 11 = 0.11 [vitesse 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 ] ) ;
si ( $ out [ 'DATA_FL' ] === "F" ) {
$ out [ 'TIME' ] = substr ( $ arr [ 5 ] , 0 , 2 ) . ":" . substr
( $ arr [ 5 ] , 2 , 2 ) . ":" . substr ( $ arr [ 5 ] , 4 , 2 ) ; // sautez des
millisecondes ...
$ out [ 'DISPONIBLE' ] = $ arr [ 6 ] === "A" ? 1 : 0 ;
$ out [ 'LAT' ] = float ( substr ( $ arr [ 7 ] , 0 , 2 ) ) +
floatval ( substr ( $ arr [ 7 ] , 2 , 7 )
) / 60 ;
$ out [ 'LAT' ] = $ arr [ 8 ] === "N" ? $ out [ 'LAT' ] : - $ out
[ 'LAT' ] ;
$ out [ 'LON' ] = float ( 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 [ 'DISPONIBLE' ] = 0 ;
$ out [ 'LAT' ] = ( float ) 0 ;
$ out [ 'LON' ] = ( float ) 0 ;
$ out [ 'SPEED' ] = ( float ) 0 ;
}
retourner $ out ;
}
/ ************************************************* **********************
* Position de mise jour sur OpenGTS DB
* @param $ array de donnes Ensemble de donnes
* **************************************************** ****************** /
fonction updatePosOpenGTS ( $ data ) {
$ res = false ;
if ( false ! == ( $ cn = db_connect ( ) ) ) {
essayez {
// donnes d'chappement
clean_array ( $ cn , $ data ) ;
$ sql = "SELECT imeiNumber From Device WHERE imeiNumber = '
{$ data [' IMEI ']} ' AND isActive;" ;
writeLog ( $ sql ) ;
if ( $ result = $ cn -> query ( $ sql ) ) {
if ( $ result -> num_rows == 1 ) {
$ result -> close ( ) ;
$ rawData = implosion ( "," , $ donnes ) ;
si ( USE_SERVER_TIME ) {
$ acq_time = maintenant ( - POLL_TIME
) ;
} else {
$ date = new DateTime ( $ data [
'ACQUISITION_TIME' ] ) ;
$ data [ 'ACQUISITION_TIME' ] = $ date
-> format ( 'Ymd H: i: s' ) ;
$ exactDate = substr ( $ data [
'ACQUISITION_TIME' ] , 0 , 10 ) . "" . $ data [ 'TIME' ] ;
$ acq_time = is_valid_date ( $
exactDate ) ? $ exactDate : maintenant ( - POLL_TIME ) ;
}
$ sql = <<< STR
COMMENCER LA TRANSACTION;
UPDATE Device SET lastValidLatitude = {$ data ['LAT']}, lastValidLongitude
= {$ data ['LON']}, lastGPSTimestamp = UNIX_TIMESTAMP ('{$ acq_time}'),
lastUpdateTime = UNIX_TIMESTAMP (NOW ()) WHERE imeiNumber = DROIT (CONCAT
('000000000000000', '{$ data [' IMEI ']}'), 15);
SELECT @accountID: = accountID, @deviceID: = deviceID FROM Device WHERE
imeiNumber = DROIT (CONCAT ('000000000000000', '{$ data [' IMEI ']}'), 15);
INSERT INTO EventData (accountID, deviceID, timestamp, statusCode,
latitude, longitude, speedKPH, cap, altitude, rawData, creationTime, adresse)
VALEURS (@accountID, @deviceID, UNIX_TIMESTAMP ('{$ acq_time}') 0, 0, '{$
rawData}', UNIX_TIMESTAMP (NOW ()), '');
COMMIT;
STR ;
writeLog ( $ sql ) ;
$ res = $ cn -> multi_query ( $ sql ) ;
while ( $ cn -> next_result ( ) ) { ; } //
flush multi_queries
} else {
writeLog ( "IMEI ou priphrique invalide
n'est pas actif!" ) ;
$ result -> close ( ) ;
}
}
} capture ( Exception $ e ) {
writeLog ( "" . $ e -> getMessage ( ) , true ) ;
}
db_close ( $ cn ) ;
}
retourner $ res ;
}
/ ************************************************* **********************
* Position de mise jour sur Gpsd DB
* @param $ array de donnes Ensemble de donnes
* **************************************************** ****************** /
function updatePosGpsd ( $ data ) {
$ res = false ;
if ( false ! == ( $ cn = db_connect ( ) ) ) {
essayez {
// donnes d'chappement
clean_array ( $ cn , $ data ) ;
$ sql = "SELECT imeiNumber from devices WHERE imeiNumber = '
{$ data [' IMEI ']} ';" ;
writeLog ( $ sql ) ;
if ( $ result = $ cn -> query ( $ sql ) ) {
if ( $ result -> num_rows == 1 ) {
si ( USE_SERVER_TIME ) {
$ acq_time = maintenant ( - POLL_TIME
) ;
} else {
$ date = new DateTime ( $ data [
'ACQUISITION_TIME' ] ) ;
$ data [ 'ACQUISITION_TIME' ] = $ date
-> format ( 'Ymd H: i: s' ) ;
$ exactDate = substr ( $ data [
'ACQUISITION_TIME' ] , 0 , 10 ) . "" . $ data [ 'TIME' ] ;
$ acq_time = is_valid_date ( $
exactDate ) ? $ exactDate : maintenant ( - POLL_TIME ) ;
}
$ sql = <<< STR
COMMENCER LA TRANSACTION;
SELECT @deviceID: = devices.id FROM devices WHERE imeiNumber = '{$ data ['
IMEI ']}';
INSERT INTO positions (msg, timestamp, acq_time, track_time, is_valid,
latitude, longitude, speedKPH, cours, device_id)
{$ Data ['TIME']} ', {$ data [' DISPONIBLE ']},' {$ acq_time} ',' VALUES
('{$ data [' MSG ']}', UNIX_TIMESTAMP {$ data [ 'LAT']}, {$ data [ 'LON']}, {$ data [
'SPEED']}, 0, @ deviceID);
COMMIT;
STR ;
writeLog ( $ sql ) ;
$ res = $ cn -> multi_query ( $ sql ) ;
while ( $ cn -> next_result ( ) ) { ; } //
flush multi_queries
} else {
writeLog ( "IMEI invalide!" ) ;
$ result -> close ( ) ;
}
}
} capture ( Exception $ e ) {
writeLog ( "" . $ e -> getMessage ( ) , true ) ;
}
db_close ( $ cn ) ;
}
retourner $ res ;
}
/ ************************************************* **********************
* Nettoyer le tableau de donnes
* **************************************************** ****************** /
function clean_array ( $ cn , & $ data ) {
foreach ( $ donnes comme $ key => $ val ) {
$ data [ $ key ] = mysqli_real_escape_string ( $ cn , $ val ) ;
}
}
/ ************************************************* **********************
* Se connecter DB
* **************************************************** ****************** /
function db_connect ( ) {
$ link = mysqli_connect ( DBHOST , DBUSER , DBPASS , DBNAME ) ;
si ( ! $ link ) {
writeLog ( "Erreur: Impossible de se connecter MySQL." , true ) ;
renvoyer faux ;
}
retourner le lien $ ;
}
/ ************************************************* **********************
* Dconnexion de DB
* @param $ link resource mysqli connection
* **************************************************** ****************** /
function db_close ( $ link ) {
retournez mysqli_close ( $ link ) ;
}
?>