PHP: un socket TCP pour capturer des données à partir du GPS Tracker GPS-102 compatible OpenGTS OpenGTS (Première partie) par tie) Posté le 27 novembre 2011 par 2011 par spadamar Il y a quelques mois, on m'a donné ce tracker GPS pour étudier comment enregistrer des données sur une base de données MySQL. L'appareil s'appelle GPS-102 est produit par Coban.ch et est également disponible en Italie à un coût inférieur à 100 € sur ce site: Coban.ch 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 spécifications techniques et des datagrammes pour la communication de données fournie par le tracker (coordonnées, vitesses, etc.). Heureusement, j'ai trouvé cette cette feuille de calcul, qui se réfère à TK102, TK103, mais le protocole est identique et fournit donc les informations nécessaires au développement 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 paramètres et pour la communication des données de localisation. Si la carte est activée pour le trafic GPRS GPRS , , vous pouvez envoyer les données sous forme de paquets TCP directement à un serveur sur le réseau 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 périphérique utilise le protocole TCP TCP orienté connexion connexion , qui, au détriment d'une plus grande bande passante et d'une diminution modérée des performances de vitesse, garantit une transmission sans erreur. Les données à enregistrer sont principalement: 1. latitude 2. longitude
3. vitesse Ce sont les données nécessaires pour créer la piste fournie par Google Maps Maps sur Google Maps ou Maps ou tout autre système de cartographie de géolocalisation (p. Ex. OpenstreetMap OpenstreetMap ). ). Les autres informations sont: le code IMEI IMEI (requis (requis pour l'identification unique de l'appareil), les codes d'alarme (batterie faible, etc.), la date et l'heure et la validité des données. Voici le SQL pour créer les deux tables nécessaires dans une base de données MySQL: -- DATABASE SCHEMA: CREATE SCHEMA IF IF NOT NOT EXISTS `gpsd` DEFAULT CHARACTER CHARACTER SET SET utf8 COLLATE utf8_general_ci ;
USE `gpsd` `gpsd` ; ;
-- ------------------------------------------------------ Table `gpsd`.`devices` -- ----------------------------------------------------CREATE
TABLE IF IF NOT NOT EXISTS `gpsd` `gpsd`. .`devices` (
`id` INT INT NOT NOT NULL NULL AUTO_INCREME AUTO_INCREMENT NT , `imeiNumber` VARCHAR (16 16) ) NOT NOT NULL NULL ,
PRIMARY KEY (`id` `id`) ) ) ENGINE = MyISAM;
-- ------------------------------------------------------ Table `gpsd`.`positions` -- ----------------------------------------------------CREATE
TABLE IF IF NOT NOT EXISTS `gpsd` `gpsd`. .`positions` (
`IDpositions` INT INT NOT NOT NULL NULL AUTO_INCREM AUTO_INCREMENT ENT , `msg` VARCHAR (255 255) ) NULL , `timestamp` INT INT NOT NOT NULL NULL , `acq_time` DATETIME `acq_time` DATETIME NULL ,
TIME NULL NULL , `track_time` TIME `is_valid` TINYINT `is_valid` TINYINT NOT NOT NULL NULL DEFAULT 0 , `latitude` DOUBLE DOUBLE NULL NULL , `longitude` DOUBLE DOUBLE NULL NULL , `speedKPH` DOUBLE DOUBLE NULL NULL ,
DOUBLE NULL NULL DEFAULT 0 , `course` DOUBLE INT NOT NOT NULL NULL , `device_id` INT PRIMARY KEY (`IDpositions` `IDpositions`) ) ) ENGINE = MyISAM;
Nous arrivons au code PHP pour la réalisation du socket. Les caractéristiques développées sont: •
•
•
•
•
•
Une prise de boucle infinie, en écoutant le port réglé Un socket de communication Une fonction de décodage de données Une fonction pour la mise à jour des tables DB Une fonction pour contrôler la distance du dernier point mémorisé Service de connexion à DB, echo de messages, etc.
Plus précisément, à partir d'une analyse préliminaire du flux TCP via TCPDUMP, j'ai observé que le traceur envoie les données de deux façons différentes: 1. Point unique: requiert que le serveur répond avec un message spécifique, en envoyant des paramètres spéciaux tels que le temps d'interrogation. 2. Multi point: ne requiert aucune réponse, envoie le datagramme directement.
Pour cette raison, j'ai réalisé 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 imprécision relative du système GPS (les satellites ne sont pas réalistes et ne sont pas toujours les mêmes), les coordonnées diffèrent légèrement. Ceci, tout en fournissant une surcharge de données inutile à la base de données, entraîne un nuage indéterminable 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 coordonnées du point juste transmis et celles du dernier point enregistré. Si la distance est inférieure à un certain seuil (définie dans les paramètres initiaux), le point n'est pas enregistré dans le DB. Une autre caractéristique à mettre en évidence est la possibilité d'utiliser la base de données MySQL Open Source : OpenGTS , un système intégré Java / Tomcat qui vous permet de visualiser des cartes personnalisables, de coordonner les données fournies par un grand nombre de traceurs commerciaux . Dans la prochaine publication, je vais fournir la source complète du script et une explication détaillée des paramètres initiaux.
PHP: un socket TCP pour capturer des données à partir du GPS Tracker GPS-102 compatible OpenGTS (deuxième partie) Posté le 30 novembre 2011 par spadamar
Socket PHP Dans la première partie de cet article, j'ai montré comment créer des tables DB pour enregistrer des données à partir du GPS tracker, les principales caractéristiques du script et la possibilité d'accrocher ce socket directement au logiciel Open Source: OpenGTS. Cette dernière caractéristique, je la trouve particulièrement intéressante, car elle vous permet d'éviter d'écrire toute la partie qui pointe sur les points affichés par le traceur sur les cartes.
Paramètres de socket mot-clé
Valeurs possibles
VERBOSE true | false MOVING_THRESHOLD .05
description
OpenGTS
true | false
IP_ADDR tcp_port
xxx.xxx.xxx.xxx 0..65535
Si défini sur true, il fournit une sortie d'erreur détaillée Seuil minimum en km pour l'enregistrement des données [0,05 = 50 mètres] Si défini sur true, il envoie des requêtes pour les tables OpenGTS Adresse de l'adresse IP [0 = toutes les adresses] Le port TCP à utiliser
dbhost DBUSER DBPASS DBNAME
localhost dbuser dbpassword gpsd
Adresse DB MySQL Utilisateur DB MySQL Mot de passe de MySQL DB Nom de base de données MySQL
POLL_TIME
20 | 30 | 60 | 300 | Temps d'interrogation du suivi en secondes 600
SPEED_CONV DFLT_MSG SOCK_RCV_TIMEOUT
1.609344 | 1852 'Tracker' 120
Conversion de Miles (terrestres | marinas) à Km Message par défaut du Tracker Délai 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 créée et prend toute la charge de communication. Si le tracker transmet en "point unique", les deux premiers caractères sont: ## , dans ce cas, la commande de vote est envoyée, forçant l'unité à transmettre "multi points" avec la plage souhaitée. Examinons maintenant la fonction de décryptage du message contenant les données réelles. Ceci est composé de 12 champs séparés par des virgules. Les commentaires initiaux incluent leur composition, leur format et leurs valeurs possibles. function decodeData($arr){
/*
*********************************************************************
*
0 = imei:000000000000000
*
1 = tracker
[imei] [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 numéro de téléphone activé pour la communication SMS avec le tracker et l'unité utilisée pour la vitesse. Les Chinois de Cobanch disent que c'est Km, mais il est expérimentalement facile de les discréditer. Toujours expérimentalement, j'ai pu vérifier avec une précision acceptable que c'est NM (mile nautique international) Dans la troisième partie de cet article, nous verrons comment démarrer le script sur un serveur Linux qui l'exécute en arrière-plan en tant que «daemon».
PHP: un socket TCP pour capturer des données du GPS Tracker GPS-102 compatible OpenGTS (tiers) Publié le 11 décembre 2011 par Spadamar
php daemon Dans ce dernier article, je décris comment le script PHP s'exécute sur un système d'exploitation Linux Ubuntu ou Debian Linux. Comme vous le remarquerez, le script décrit dans la deuxième partie contient le code suivant dans la première ligne: #!/usr/bin/env php
En verbe, cette syntaxe s'appelle Shebang et est utilisée pour lancer à partir du shell l'interpréteur de script qui va être exécuté. Dans ce cas, le PHP. Vous devrez alors vérifier que PHP5-CLI (interpréteur de ligne de commande) est installé.
Si nous essayons de lancer, simplement en tapant gpsspck.php à partir d'un terminal, ceci est émis dès que le tracker est "accroché":
Pour le lancer en arrière-plan, il s'agit du script bash que j'ai préparé pour gérer le démarrage et l'arrêt 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 exécute start-stop-daemon qui est un programme fréquemment utilisé sur Ubuntu pour démarrer et mettre fin à des processus qui fonctionnent comme des démons. Il existe plusieurs exemples dans le répertoire /etc/init.d/ et en fait c'est juste là que nous placerons notre script. Les deux seuls à faire sont de modifier, le cas échéant, la variable NAME avec le nom du fichier PHP à lancer (dans notre cas gpssock) et définir le WORKINGDIR travail WORKINGDIR avec le chemin d'accès correct de gpssock.php Notez que le dernier paramètre start-stop-daemon est & cela indique que le processus doit être exécuté en arrière-plan. Enfin, toutes les sorties sont redirigées vers un fichier journal qui a le même 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'exécuter automatiquement, vous devez l'enregistrer avec cette instruction: update-rc.d gpssock defaults
[Mise à jour:] Antonino Celona souligne qu'il est conseillé de définir une priorité de démarrage suffisamment élevée pour empêcher le démarrage du serveur socket avant le serveur MySQL, comme ceci: update-rc.d gpssock defaults 99
références: •
•
•
•
OpenGTS Ouvrir le serveur de suivi GPS Socket Programming With PHP PHP: Sockets - Manual
PHP:
socket
multi-processus
TCP
pour
acquérir des données du GPS Tracker GPS102 compatible avec OpenGTS Publié le 5 novembre 2015 par Spadamar
socket php multi-processus Dans les articles précédents «PHP: un socket TCP pour acquérir des données à partir du GPS Tracker GPS-102 compatible OpenGTS» ( première , deuxième et troisième partie ), j'ai décrit comment implémenter un socket TCP en PHP pour acquérir des données GPS à partir d'un suivi TK -102 (et compatible). Le script, cependant, a été conçu 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 gérer les connexions concurrentes. Cependant, il est très utile qu'un serveur socket de ce type puisse gérer des connexions simultanées afin de charger plus de traceurs GPS en même temps. Heureusement, le langage PHP prend en charge la programmation multiprocesseur en utilisant le fork() . De cette façon, le processus parent impliqué dans une nouvelle connexion génère 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 réécrire le code en mode multiprocesseur pour mettre à jour la connexion au serveur de base de données MySQL à l'aide de l'extension mysqli au lieu de l'original mysql maintenant obsolète dans PHP 5.5.x. Enfin, j'ai simplifié le code en prenant le contrôle 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 développé avec GPL License 2.0 * * Licence GPL: http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt * * Développé par: Mario Spada à partir d'une idée 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 ultérieure. * * Ce programme est distribué dans l'espoir qu'il sera utile, * mais SANS AUCUNE GARANTIE;
sans même la garantie implicite de
* QUALITÉ MARCHANDE OU ADAPTATION À DES FINS PARTICULIERS. Voir le * GNU General Public License pour plus de détails. * * Pour information:
[email protected] ************************************************** ********************* / / ************************************************* ********************** * RÉGLAGES ************************************************** ******************* / // Constantes ----------------------------------------------- -----------/ * HOST * / // "0.0.0.0" = écouter tout ip
définir ( "IP_ADDR" , "0.0.0.0" ) ; définissez ( "TCP_PORT" , 5050 ) ;
// 5050
définissez ( "DEBUG" , true ) ;
/ * TRACKER * / définissez ( "POLL_TIME" , 60 ) ;
// SET POLL TIME 20,30,60,300,600 par défaut: 60
secs définissez ( "SPEED_CONV" , 1.852 ) ;
// De kilomètre de NM à Km
définir ( "DFLT_MSG" , "tracker" ) ;
/ * BASE DE DONNÉES * / définissez ( "DBHOST" , "127.0.0.1" ) ; définissez ( "DBUSER" , "your_db_user" ) ; définissez ( "DBPASS" , "votre_db_password" ) ; définir ( "DBNAME" , "gts" ) ;
/ * AUTRES RÉGLAGES * / définissez ( "OPENGTS" , true ) ; définissez ( "USE_SERVER_TIME" , true ) ;
// Variables ----------------------------------------------- -----------$ __ server_listening = true ; $ sendPollTime = false ;
// ------------------------------------------------ --------------------/ ************************************************* ********************** * INIT ************************************************** ******************* / error_reporting ( E_ALL ) ; set_time_limit ( 0 ) ; ob_implicit_flush ( ) ;
déclarer ( tiques = 1 ) ;
$ pid = become_daemon ( ) ; write_pid ( $ pid ) ;
/ * nobody / nogroup, modifiez le uid / gid de votre hôte 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' ) ;
/ * régler la fuseau horaire par défaut * / date_default_timezone_set ( 'Europe / Rome' ) ;
/ ************************************************* ********************** * Écoute des requêtes 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 définir" .
$ gid .
"!"
;
writeLog ( $ msg , true ) ; sortie ; } si ( ! posix_setuid ( $ uid ) ) { $ msg = "Impossible de configurer" . writeLog ( $ msg , true ) ;
$ uid .
"!"
;
sortie ; } }
/ ************************************************* ********************** * Crée 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 réussi à é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 numéro de signal ************************************************** ******************* / fonction sig_handler ( $ sig ) { commutateur ( $ sig ) { maisons SIGTERM : case SIGINT : sortie ; rupture ; Cas SIGCHLD : pcntl_waitpid ( - 1 , $ status ) ; rupture ; } }
/ ************************************************* ********************** * Gérer 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 ( " REÇU :" . $ buf ) ; $ outData = array ( ) ; $ rec = trim ( $ buf ) ;
// nettoyer la chaîne d'entrée
$ multiplePos = ( substr ( $ buf , 0 , 2 ) == "##" ) ?
faux : vrai
; $ realIMEI = ( ! $ multiplePos ) ? substr ( $ buf , 5 , 15 ) ;
substr ( $ buf , 8 , 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 ( " REÇU :" . $ buf ) ; si ( vide ( $ buf ) ) { lancer une nouvelle exception ( "Reçu: rien, déconnecter" ) ; } if ( ( $ sendPollTime === false ) ) { $ output = "ON" .
" \ n " ;
writeLog ( "ENVOYER: ON" ) ; socket_write ( $ socket , $ output , strlen ( $ output ) ) ; $ output = "**, imei:" . pollTimeString ( POLL_TIME ) .
$ actualIMEI .
"," .
" \ 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 démon 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 démon * /
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 ) ; }
/ ************************************************* ********************** * Vérifiez si une date valide * @param $ string string Chaîne 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 chaîne 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 défaut : $ res = "01m" ; }
retourner "C" .
$ res ;
}
/ ************************************************* ********************** * Décodage des données * @param $ arr array Ensemble de données * **************************************************** ****************** / 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 [Données: 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 ) .
( $ arr [ 5 ] , 2 , 2 ) .
":" .
substr ( $ arr [ 5 ] , 4 , 2 ) ;
":" .
substr
// 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 données Ensemble de données * **************************************************** ****************** / fonction updatePosOpenGTS ( $ data ) { $ res = false ; if ( false ! == ( $ cn = db_connect ( ) ) ) { essayez {
// données 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 ( "," , $ données ) ; 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 périphérique 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 données Ensemble de données
* **************************************************** ****************** / function updatePosGpsd ( $ data ) { $ res = false ; if ( false ! == ( $ cn = db_connect ( ) ) ) { essayez {
// données 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 données * **************************************************** ****************** / function clean_array ( $ cn , & $ data ) { foreach ( $ données 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 $ ; }
/ ************************************************* ********************** * Déconnexion de DB * @param $ link resource mysqli connection * **************************************************** ****************** / function db_close ( $ link ) { retournez mysqli_close ( $ link ) ; }
?>