read_imu_log.php 29.5 KB
Newer Older
1
<?php
2 3 4 5 6 7 8 9
/*
 * FILE NAME  : read_imu_log.php
 * DESCRIPTION: parse imu log from Elphel 10393 (and 10353)
 * VERSION: 1.0
 * AUTHOR: Oleg K Dzhimiev <oleg@elphel.com>
 * LICENSE: AGPL, see http://www.gnu.org/licenses/agpl.txt
 * Copyright (C) 2016 Elphel, Inc.
 */
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
10 11
 
set_time_limit(0);
12

Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
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
$CLI = (php_sapi_name()=="cli");

if ($CLI){
  $_SERVER['REMOTE_ADDR'] = "localhost";
  $_SERVER['SERVER_ADDR'] = "localhost";
  $_GET['format'] = "csv";
  $_GET['nth'] = 1;
  $_GET['limit'] = -1;
  
  $_GET['nogui'] = true;
 
  if (isset($argv[1])) $_GET['file'] = $argv[1];
  else{
    die(<<<TEXT
    
\033[91mERROR: Filename is not set.\033[0m 

Command line usage examples:
* Minimal:
    \033[1;37m~$ php thisscript.php logfile > logfile.csv\033[0m
* With a filter, IMU records only:
    \033[1;37m~~$ php thisscript.php logfile 0x010 > logfile.csv\033[0m
* With a filter, GPS NMEA GPRMC records ony:
    \033[1;37m~~$ php thisscript.php logfile 0x001 > logfile.csv\033[0m
* Filter bits:
    External trigger source:
      0x200
    Image records:
      0x100 channel 3
      0x080 channel 2
      0x040 channel 1
      0x020 channel 0
    IMU:
      0x010
    GPS records:
      0x008 - NMEA GPVTG
      0x004 - NMEA GPGSA
      0x002 - NMEA GPGGA (have coordinates)
      0x001 - NMEA GPRMC (have coordinates) 

    
TEXT
    );
  }
57
 
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
58 59 60 61 62 63 64
  $_GET['record'] = 0;
}

$thisname = basename($_SERVER['SCRIPT_NAME']);
$remoteaccess = ($_SERVER['REMOTE_ADDR']!=$_SERVER['SERVER_ADDR']);
$hardcodeddir = "logs";

65 66 67 68 69 70
if (isset($_GET['format'])){
  $format = $_GET['format'];
}else{
  $format = "html";
}

Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
71 72 73 74 75 76
if (isset($_GET['nth'])){
  $nth = $_GET['nth']+0;
}else{
  $nth = 1;
}

77 78 79
if (isset($_GET['limit'])){
  $limit = $_GET['limit']+0;
}else{
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
80
  $limit = -1;
81 82 83 84
}

if (isset($_GET['nogui'])){
  $nogui = true;
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
85 86 87 88 89 90 91 92
  if ($format=="csv"){
    header("Content-Type: application/octet-stream");
    header('Content-Disposition: attachment; filename='.basename($_GET['file']).".".$format);
  }else{
    header("Content-Type: application/xml");
    echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
    echo "<Document>\n";
  }
93
}else{
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
94
  //header("Content-Type: text/xml");
95 96 97 98 99 100 101 102 103 104 105 106
  $nogui = false;
}

if (isset($_GET['source'])){
  show_source($_SERVER['SCRIPT_FILENAME']);
  die(0);
}

if (isset($_GET['list'])){
  echo help();
  die(0);
}
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
107 108

if (isset($_GET['file'])){
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
109
  if ($remoteaccess){
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
110
    $file = $hardcodeddir."/".basename($_GET['file']);
111
  }else{
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
112
    $file = $_GET['file'];
113 114 115
  }

  if (is_file($file)){
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
116 117
    $numRecordsInFile=filesize($file)/64;
  }else{
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
118
    //for local access also check $hardcodeddir
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
119 120
    if (is_file($hardcodeddir."/".$file)){
      $file = $hardcodeddir."/".$file;
121 122 123 124 125
      $numRecordsInFile=filesize($file)/64;
    }else{
      echo "File not found";
      die(0);
    }
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
126
  }
127
  $init = true;
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
128
}else{
129 130 131
  $init = false;
}

Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
132 133 134 135 136
if ($CLI){
  $_GET['nrecords'] = $numRecordsInFile;
  if (isset($argv[2])) $_GET['filter'] = $argv[2];
}

Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
137 138 139 140 141 142 143 144 145 146 147 148
if (isset($_GET['record']))   $record = $_GET['record']+0;
else                          $record=0;

if ($record>($numRecordsInFile-1)) $record=$numRecordsInFile-1;

if (isset($_GET['nrecords'])) $nRecords = $_GET['nrecords']+0;
else{
  if ($numRecordsInFile>5000)
    $nRecords=5000;
  else
    $nRecords=$numRecordsInFile;
}
149

Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
150 151
if ($nRecords>($numRecordsInFile-$record)) $nRecords= $numRecordsInFile-$record;

152
if ($limit>0) $nRecords= $numRecordsInFile;
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
153

Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
154 155 156 157 158
if (isset($_GET['showall'])){
  $limit = $numRecordsInFile;
  $nRecords= $numRecordsInFile;
}

Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
159
if (isset($_GET['filter'])) $filter= intval($_GET['filter'],0);
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
160 161
else                        $filter= 0x3ff;

162 163 164 165 166 167 168 169 170
/*
 1 - NMEA sentence 0 $GPRMC (log type 1)
 2 - NMEA sentence 1 $GPGGA (log type 1)
 4 - NMEA sentence 2 $GPGSA (log type 1)
 8 - NMEA sentence 3 $GPVTG (log type 1)
16 - IMU (type 0)
32 - SYNC (type 2)
64 - ODOMETER (type 3)
*/
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
171

Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
172 173
if ($limit<0) $limit = $nRecords;

Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
174 175 176 177 178 179
if(!$nogui){
  //list available files
  echo html();
  die(0);
}

Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
180 181
//$timeShift = 21600; // 6 hrs
$timeShift = 0; // 0 hrs
182 183 184 185

$type=-1;
$gpsType=-1;

186 187 188 189 190
$filterhex = dechex($filter);
$sindex = $record;
$eindex = $record+$nRecords;

if ($format=="csv"){
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
191
  echo "Filename,$file\n";
192 193
  echo "Found Records,$numRecordsInFile\n";
  echo "Record filter,0x$filterhex\n";
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
194
  echo "Show limit, $limit\n";
195 196 197 198 199 200 201 202
  echo "Start index,$sindex\n";
  echo "End index,$eindex\n\n";
}else{
  echo <<<TEXT
<table>
<tr><td>Filename</td><td>$file</td></tr>
<tr><td>Found records</td><td>$numRecordsInFile</td></tr>
<tr><td>Filter</td><td>$filterhex</td></tr>
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
203
<tr><td>Show limit</td><td>$limit</td></tr>
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
204 205
<tr><td>Start index</td><td>$sindex</td></tr>
<tr><td>End index</td><td>$eindex</td></tr>
206 207 208
</table>
TEXT;
}
209 210

$log_file=fopen($file,'r');
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
211

212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
$averageIMU=array(
    "number"=>0,
    "gyroX"=> 0,
    "gyroY"=> 0,
    "gyroZ"=> 0,
    "accelX"=>0,
    "accelY"=>0,
    "accelZ"=>0,
    "angleX"=>0,
    "angleY"=>0,
    "angleZ"=>0,
    "velocityX"=>0,
    "velocityY"=>0,
    "velocityZ"=>0,
    "temperature"=>0);

Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
$imuFieldOrder = array(
    "gyroX",
    "gyroY",
    "gyroZ",
    "accelX",
    "accelY",
    "accelZ",
    "angleX",
    "angleY",
    "angleZ",
    "velocityX",
    "velocityY",
    "velocityZ",
    "temperature"
);
    
244
imuLogParse($log_file,$record,$nRecords,$filter);
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
245 246

die(0);
247

Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
$gc = array(
  array(0,0,0,0),
  array(0,0,0,0),
  array(0,0,0,0),
  array(0,0,0,0)
);

function global_counters($i,$j){
  global $gc, $nth;
  
  if (($gc[$i][$j]%$nth)==0) {
    $gc[$i][$j] = 1;
    return true;
  }else{
    $gc[$i][$j]++;
    return false;
  }
}

267 268 269 270
function imuLogParse($handle,$record,$nSamples,$filter,$tryNumber=10000){

  global $timeShift;
  global $averageIMU;
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
271
  global $imuFieldOrder;
272
  global $format,$limit;
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
273 274
  
  global $gc;
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
275
    
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
276 277 278 279
  $gpsFilter = ($filter)&0xf;    //type=1
  $imuFilter = ($filter>>4)&0x1; //type=0
  $imgFilter = ($filter>>5)&0xf; //type=2
  $extFilter = ($filter>>9)&0x1; //type=3
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
280
  
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
281
  $typeFilter = (($gpsFilter!=0)?2:0) | (($imuFilter!=0)?1:0) | ((($imgFilter)!=0)?4:0) | ((($extFilter)!=0)?8:0);
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
282
  
283 284 285 286 287
  $skip_imu_cols = true;
  $imu_cols_csv = "";
  $imu_cols_html_header = "";
  $imu_cols_html = "";
  
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
288
  if ($imuFilter==0x1) {
289 290 291 292 293 294 295 296
    $skip_imu_cols = false;
    $imu_cols_csv_header = implode(",",$imuFieldOrder).",";
    for($i=0;$i<count($imuFieldOrder);$i++){
      $imu_cols_html .= "<td></td>\n";
      $imu_cols_html_header .= "<td align='left'><div style='width:170px'>{$imuFieldOrder[$i]}</div></td>\n";
      $imu_cols_csv .= ",";
    }
  }
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
297
  
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
  fseek($handle,64*$record,SEEK_SET);

  if ($format=="csv"){    
    echo "Index,Timestamp,Type,{$imu_cols_csv_header}GPS NMEA Type,GPS NMEA Sentence,Sensor Port,Master Timestamp,Master Timestamp,EXT\n";
  }else{
    echo <<<TEXT
<table>
<tr>
<td>Index</td>
<td align='center'>Timestamp</td>
<td align='center'><div style='width:50px;'>Type</div></td>
TEXT;
    echo $imu_cols_html_header;
    echo "<td align='center'>Sensor Port</td>\n";
    echo "<td align='center'><div style='width:180px;'>Master Timestamp</div></td>\n";
    echo "<td align='center'><div style='width:200px;'>Master Timestamp</div></td>\n";
    echo "<td align='center'><div style='width:100px;'>GPS NMEA Type</div></td>\n";
    echo "<td align='center'><div style='width:100px;'>GPS NMEA Sentence</div></td>\n";
    echo "<td>EXT</td>\n";
    echo "</tr>\n";
  }
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
319
  
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
320 321 322 323 324 325 326
  $gc = array(
    array(0,0,0,0),
    array(0,0,0,0),
    array(0,0,0,0),
    array(0,0,0,0)
  );
  
327
  for ($nSample=0;$nSample<$nSamples;$nSample++) {
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
328
  
329 330
    $sample=fread($handle,64);
    $arr32=unpack('L*',$sample);
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
331
    $time=(($arr32[1]&0xfffff)/1000000)+$arr32[2];
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
332
    
333
    $type=$arr32[1]>>24;
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
334
    
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
335 336 337 338
    $show_record = false;
    switch($type){
      case 0:
        if ($imuFilter!=0) $show_record = global_counters($type,0);
339
        break;
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
340 341 342 343 344 345 346 347 348 349 350 351 352
      case 1:
        if ((($gpsFilter>>($arr32[3]&0x3))&1)==1) $show_record = global_counters($type,$arr32[3]&0x3);
        break;
      case 2:
        $subchannel = ($arr32[3] >> 24);
        if ((($imgFilter>>$subchannel)&1)==1) $show_record = global_counters($type,$subchannel);
        break;
      case 3:
        if (($extFilter&1)==1) $show_record = global_counters($type,0);
        break;
    }
    
    if ($show_record) {
353
      
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
354 355 356
      $limit--;
      if ($limit==-1) break;
      //beginning
357 358 359 360 361 362
      if ($format=="csv"){
        printf("%d,%f,$type,",($record+$nSample),$time);
      }else{
        echo "<tr>\n";
        printf("<td>%d</td>\n<td>%f</td>\n<td align='center'>$type</td>\n",($record+$nSample),$time);
      }
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
363
      
364
      switch ($type) {
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
365
        // IMU record
366
        case 0:
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
367

368 369
          $imuSample=parseIMU($arr32);
          
370 371 372 373 374 375 376 377 378
          if ($format=="csv"){
            echo implode(",",$imuSample)."\n";
          }else{
            foreach($imuSample as $imus){
            //for($i=0;$i<count($imuSample);$i++){
              echo "<td>{$imus}</td>\n";
            }
            echo "</tr>\n";
          }
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
379 380 381
          
          /*
          echo " [angleX]=>".$imuSample["angleX"]."     [angleY]=>".$imuSample["angleY"]."     [angleZ]=>".$imuSample["angleZ"].
382 383 384 385
                             "     [gyroX] =>".$imuSample["gyroX"] ."      [gyroY]=>".$imuSample["gyroY"] ."      [gyroZ]=>".$imuSample["gyroZ"].
                             "     [accelX] =>".$imuSample["accelX"] ."      [accelY]=>".$imuSample["accelY"] ."      [accelZ]=>".$imuSample["accelZ"].
                             "     [velocityX] =>".$imuSample["velocityX"] ."      [velocityY]=>".$imuSample["velocityY"] ."      [velocityZ]=>".$imuSample["velocityZ"].
                             "     [temperature]=>".$imuSample["temperature"]."\n";
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
386
          */
387
          break;
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
388 389
          
        // GPS record
390
        case 1:
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
391
        
392
          $nmeaArray=parseGPS($sample);
393
          
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
394
          $nmeaString = '$'.implode(",",$nmeaArray);
395 396 397 398 399 400 401 402 403 404 405 406 407
          $nmeaType = $nmeaArray[0];
          
          if ($format=="csv"){
            echo "$imu_cols_csv,,,$nmeaType,\"".$nmeaString."\"\n";
          }else{
            echo "$imu_cols_html";
            //for img
            echo "<td></td>\n";
            echo "<td></td>\n";
            echo "<td></td>\n";
            echo "<td align='center'>$nmeaType</td>\n<td>$nmeaString</td>\n";
            echo "</tr>\n";
          }
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
408
          
409
          break;
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
410 411
          
        // Master (Sync) record
412
        case 2:
413
          $subchannel = ($arr32[3] >> 24);
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
414
          $masterTime=(($arr32[3]&0xfffff)/1000000)+$arr32[4];
415 416 417 418 419 420 421 422 423 424 425
          
          if ($format=="csv"){
            printf("$imu_cols_csv$subchannel,%f,".(gmdate(DATE_RFC850,$masterTime))."\n",$masterTime);
            //echo ",,,,,,,,,,,,,,,\"Subchannel: <b>".$subchannel."</b> MasterTimeStamp: <b>".($masterTime+$timeShift)."</b> TimeStamp: <b>".($time+$timeShift)."</b>   MasterTimeStamp(precise): 0x".dechex($arr32[4])."+".dechex($arr32[3])." TimeStamp(precise): 0x".dechex($arr32[2])."+".dechex($arr32[1])." $masterTime - ".gmdate(DATE_RFC850,$masterTime)." [local timestamp - ".gmdate(DATE_RFC850,$time)."]\"\n";
          }else{
            echo "$imu_cols_html";
            
            echo "<td align='center'>$subchannel</td>\n";
            printf("<td>%f</td>\n",$masterTime);
            echo "<td>".gmdate(DATE_RFC850,$masterTime)."</td>\n";
          }
426
          break;
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
427 428
          
        // Show hex data
429 430 431 432 433 434 435 436 437 438 439 440 441 442 443
        case 3: 
            $msg = "\"".implode(",",$arr32)."\"\n";
            
            if ($format=="csv"){
              echo "$imu_cols_csv,,,,,$msg";
            }else{
              echo "$imu_cols_html";
              echo "<td></td>\n";
              echo "<td></td>\n";
              echo "<td></td>\n";
              echo "<td></td>\n";
              echo "<td></td>\n";
              echo "<td>$msg</td>\n";
            }
            
444 445 446 447
          break;
      }
    }
  }
448 449
  if ($format!="csv"){
    echo "</table>\n";
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
450
    echo "</Document>\n";
451
  }
452 453 454 455 456 457 458 459 460 461 462 463 464 465 466
}

function parseGPS($sample){
  $sentences=array(
    0=>"GPRMC",
    1=>"GPGGA",
    2=>"GPGSA",
    3=>"GPVTG"
   );
  $formats=array(
    0=>str_split('nbnbnbnnnnb'),
    1=>str_split('nnbnbnnnnbnbbb'),
    2=>str_split('bnnnnnnnnnnnnnnnn'),
    3=>str_split('nbnbnbnb')
   );
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
467 468
  //print_r($formats);
  /*
469 470 471 472 473
    'R','M','C','n','b','n','b','n','b','n','n','n','n','b', 0,  0,  0,  0,  0,  0,  0,0,0,0,  0,0,0,0,  0,0,0,0,
    'G','G','A','n','n','b','n','b','n','n','n','n','b','n','b','b','b', 0,  0,  0,  0,0,0,0,  0,0,0,0,  0,0,0,0,
    'G','S','A','b','n','n','n','n','n','n','n','n','n','n','n','n','n','n','n','n', 0,0,0,0,  0,0,0,0,  0,0,0,0,
    'V','T','G','n','b','n','b','n','b','n','b', 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,0,0,0,  0,0,0,0,  0,0,0,0,

Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
474
  */
475 476 477 478
  $arr8= unpack('C*',$sample);
  $type=$arr8[4];
  $nibbleNumber=16; // starting with 0;
  $gps=getNibble($nibbleNumber++,$arr8);
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
479

480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498
  $rslt=array(0=>$sentences[$gps]);
  for ($i=0;$i<count($formats[$gps]);$i++){
    $rslt[$i+1]='';
    if ($formats[$gps][$i]=='n'){ //nibbles
      while (true) {
        $nibble=getNibble($nibbleNumber++,$arr8);
        if ($nibble==0xf) break;
        $rslt[$i+1].=chr($nibble+(($nibble>9)?0x20:0x30));
      }
    } else { // bytes
      do {
       $byte=getNibble($nibbleNumber++,$arr8);
       $byte+=(getNibble($nibbleNumber++,$arr8)<<4);
       if (($byte&0x7f)!=0x7f) $rslt[$i+1].=chr($byte&0x7f);
      } while (($byte&0x80)==0);
    }
  }
  return $rslt;
}
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
499

500 501 502
function getNibble($n,$arr8){
 return ($arr8[($n>>1)+1]>>(($n&1)?4:0))&0xf;
}
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533

function parseIMU ($arr32){
  $gyroScale=0.013108/65536;      // deg/sec
  $accelScale=0.8192/65536;       // mg
  $angleScale= 0.005493/65536;    // degrees
  $velocityScale=0.0030518/65536; // m/sec
  //$angleScale= 0.005493;      //degrees
  //$velocityScale=0.0030518;   //m/sec
  $temperatureScale=0.00565;      // C/LSB
  $t=($arr32[15] & 0xffff);
  if ($t>=32768) $t-=65536;
  
  for ($i=3;$i<15;$i++) if ((($arr32[$i] & 0x80000000)!=0) && ($arr32[$i] > 0)) $arr32[$i]-=0x100000000;
  
  return array(
    "gyroX"=> $arr32[ 3]*$gyroScale,
    "gyroY"=> $arr32[ 4]*$gyroScale,
    "gyroZ"=> $arr32[ 5]*$gyroScale,
    "accelX"=>$arr32[ 6]*$accelScale,
    "accelY"=>$arr32[ 7]*$accelScale,
    "accelZ"=>$arr32[ 8]*$accelScale,
    "angleX"=>$arr32[ 9]*$angleScale,
    "angleY"=>$arr32[10]*$angleScale,
    "angleZ"=>$arr32[11]*$angleScale,
    "velocityX"=>$arr32[12]*$velocityScale,
    "velocityY"=>$arr32[13]*$velocityScale,
    "velocityZ"=>$arr32[14]*$velocityScale,
    "temperature"=>$t*$temperatureScale+25
  );
}

534
/*
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
535 536
function parseNMEA($sent,$data){

537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635
eg4. $GPRMC,hhmmss.ss,A,llll.ll,a,yyyyy.yy,a,x.x,x.x,ddmmyy,x.x,a*hh
1    = UTC of position fix
2    = Data status (V=navigation receiver warning)
3    = Latitude of fix
4    = N or S
5    = Longitude of fix
6    = E or W
7    = Speed over ground in knots
8    = Track made good in degrees True
9    = UT date
10   = Magnetic variation degrees (Easterly var. subtracts from true course)
11   = E or W
12   = Checksum

eg3. $GPGGA,hhmmss.ss,llll.ll,a,yyyyy.yy,a,x,xx,x.x,x.x,M,x.x,M,x.x,xxxx*hh
1    = UTC of Position
2    = Latitude
3    = N or S
4    = Longitude
5    = E or W
6    = GPS quality indicator (0=invalid; 1=GPS fix; 2=Diff. GPS fix)
7    = Number of satellites in use [not those in view]
8    = Horizontal dilution of position
9    = Antenna altitude above/below mean sea level (geoid)
10   = Meters  (Antenna height unit)
11   = Geoidal separation (Diff. between WGS-84 earth ellipsoid and
       mean sea level.  -=geoid is below WGS-84 ellipsoid)
12   = Meters  (Units of geoidal separation)
13   = Age in seconds since last update from diff. reference station
14   = Diff. reference station ID#
15   = Checksum

GPS Satellites in view

eg. $GPGSV,3,1,11,03,03,111,00,04,15,270,00,06,01,010,00,13,06,292,00*74
    $GPGSV,3,2,11,14,25,170,00,16,57,208,39,18,67,296,40,19,40,246,00*74
    $GPGSV,3,3,11,22,42,067,42,24,14,311,43,27,05,244,00,,,,*4D


    $GPGSV,1,1,13,02,02,213,,03,-3,000,,11,00,121,,14,13,172,05*67


1    = Total number of messages of this type in this cycle
2    = Message number
3    = Total number of SVs in view
4    = SV PRN number
5    = Elevation in degrees, 90 maximum
6    = Azimuth, degrees from true north, 000 to 359
7    = SNR, 00-99 dB (null when not tracking)
8-11 = Information about second SV, same as field 4-7
12-15= Information about third SV, same as field 4-7
16-19= Information about fourth SV, same as field 4-7

eg3. $GPVTG,t,T,,,s.ss,N,s.ss,K*hh
1    = Track made good
2    = Fixed text 'T' indicates that track made good is relative to true north
3    = not used
4    = not used
5    = Speed over ground in knots
6    = Fixed text 'N' indicates that speed over ground in in knots
7    = Speed over ground in kilometers/hour
8    = Fixed text 'K' indicates that speed over ground is in kilometers/hour
9    = Checksum

// IMU logged data:
    0x10, // x gyro low
    0x12, // x gyro high 0.013108 deg/sec
    0x14, // y gyro low
    0x16, // y gyro high 0.013108 deg/sec
    0x18, // z gyro low
    0x1a, // z gyro high 0.013108 deg/sec

    0x1c, // x accel low
    0x1e, // x accel high 0.8192 mg
    0x20, // y accel low
    0x22, // y accel high
    0x24, // z accel low
    0x26, // z accel high

    0x40, // x delta angle low
    0x42, // x delta angle high +/-179.9891, LSB=0.005493 degree
    0x44, // y delta angle low
    0x46, // y delta angle high +/-179.9891, LSB=0.005493 degree
    0x48, // z delta angle low
    0x4a, // z delta angle high +/-179.9891, LSB=0.005493 degree

    0x4c, // x delta velocity low
    0x4e, // x delta velocity high +/-99.998 m/sec, LSB=3.0518mm/sec
    0x50, // y delta velocity low
    0x52, // y delta velocity high +/-99.998 m/sec, LSB=3.0518mm/sec
    0x54, // z delta velocity low
    0x56, // z delta velocity high +/-99.998 m/sec, LSB=3.0518mm/sec

    0x0e, // temperature 25C+ 0.00565C/LSB
    0x70, // time m/s [13:8] - minutes, [5:0] seconds
    0x72, // time d/h [12:8] - day [5:0] - hours
    0x74,// time y/m  [14:8] year (from 2000), [3:0] - month

}
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
636 637
*/

638 639 640
function showlist(){

  global $hardcodeddir;
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
641
  global $remoteaccess;
642
 
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
643 644
  $local = true;
 
645 646
  if (isset($_GET['file'])) $file = $_GET['file'];
   
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
647
  if ($remoteaccess){
648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664
    $dir = $hardcodeddir;
  }else{
    if (!isset($file)||($file=="")){
      $dir = ".";
    }else{
      if (is_dir($file)){
        $dir = $file;
      }else{
        $path = pathinfo($file);
        if (!isset($path['dirname'])){
          $dir = $hardcodeddir;
        }else{
          $dir = $path['dirname'];
        }
      }
    }
  }
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
665
  
666
  $files = scandir($dir);
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
667
  
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
668 669
  if ($dir=="."||($remoteaccess)) $dirlink="";
  else                            $dirlink="$dir/";
670 671 672 673 674
  
  $res = "";
  
  foreach($files as $f){
    if (($f[0]!=".")&&(!is_dir($dir.$f))) {
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
675
      $res .= "<li><a href='#' class='filenamechanger'>$dirlink$f</a></li>\n";
676 677 678 679 680 681
    }
  }

  $base = substr($_SERVER['SCRIPT_NAME'],0,strrpos($_SERVER['SCRIPT_NAME'],"/")+1);
  $base = $_SERVER['SERVER_NAME'].$base;
  
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
682
  $res = "Log files list (<b><i>$base$dir</i></b>, click to select):<ul>$res</ul>";
683 684 685 686 687 688 689
  
  return $res;
  
}

function html(){

Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
690
  global $file,$limit,$record,$nRecords,$filter,$nth;
691 692
  global $init;

Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
693 694
  $ins_filter = array();
  
695
  if ($init) {
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
696 697 698 699
    $ins_file = $file;
    $ins_limit = $limit;
    $ins_rec = $record;
    $ins_nrec = $_GET['nrecords']+$record;
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
700 701 702 703
    for($i=0;$i<10;$i++){
      if (($filter>>$i)&1==1) $ins_filter[$i] = "checked";
    }
    $ins_nth = $nth;
704
  }else{
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
705
    $ins_file = "imu.log";
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
706
    $ins_limit = 500;
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
707 708
    $ins_rec = 0;
    $ins_nrec = 5000;
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
709 710 711 712
    for($i=0;$i<10;$i++){
      $ins_filter[$i] = "checked";
    }
    $ins_nth = 1;
713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742
  }
  
  $js = js();
  return <<<TEXT
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8"/>
    <meta name="author" content="?"/>
    <style>
    #results td{
      padding:0px 10px;
    }
    #results{
      font-size:14px;
    }
    
    #controls{
      background: rgba(100,200,100,0.5);
      padding: 10px;
    }
    </style>
    <script>$js</script>
  </head>
  <body onload='init()'>
    <table id='controls'>
    <tr>
      <td>
        <table>
        <tr>
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
743
          <td>Filename: <input type='text' id='file' value='$ins_file' style='width:400px;' onchange='getRqStr()'>&nbsp;<button onclick='getList()'>List log files</button>&nbsp;&nbsp;</td>
744 745 746 747
        </tr>
        <tr>
          <td colspan=''>Record filter:&nbsp;
            <span title='External device'>EXT
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
748
              <input id='filter_9' type='checkbox' {$ins_filter[9]} onchange='getRqStr()' title='External source'>
749 750
            </span>&nbsp;&nbsp;
            <span title='Image trigger signal'>IMG
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
751 752 753 754
              <input id='filter_8' type='checkbox' {$ins_filter[8]} onchange='getRqStr()' title='Sensor port 3'>
              <input id='filter_7' type='checkbox' {$ins_filter[7]} onchange='getRqStr()' title='Sensor port 2'>
              <input id='filter_6' type='checkbox' {$ins_filter[6]} onchange='getRqStr()' title='Sensor port 1'>
              <input id='filter_5' type='checkbox' {$ins_filter[5]} onchange='getRqStr()' title='Sensor port 0'>
755 756
            </span>&nbsp;&nbsp;
            <span title='IMU'>IMU
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
757
              <input id='filter_4' type='checkbox' {$ins_filter[4]} onchange='getRqStr()'>
758 759
            </span>&nbsp;&nbsp;
            <span title='GPS'>GPS
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
760 761 762 763
              <input id='filter_3' type='checkbox' {$ins_filter[3]} onchange='getRqStr()' title='NMEA GPVTG'>
              <input id='filter_2' type='checkbox' {$ins_filter[2]} onchange='getRqStr()' title='NMEA GPGSA'>
              <input id='filter_1' type='checkbox' {$ins_filter[1]} onchange='getRqStr()' title='NMEA GPGGA'>
              <input id='filter_0' type='checkbox' {$ins_filter[0]} onchange='getRqStr()' title='NMEA GPRMC'>
764 765 766 767 768 769 770 771
            </span>
          </td>
        </tr>
        <tr>
          <td>
            <table>
            <tr>
              <td></td>
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
772
              <td>Begin</td><td><input id='start' type='text' value='$ins_rec' style='width:100px;text-align:right;' onchange='getRqStr()'></td>
773 774 775
            </tr>
            <tr>
              <td></td>
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
776
              <td>End</td><td><input id='end' type='text' value='$ins_nrec' style='width:100px;text-align:right;' onchange='getRqStr()'></td>
777
            </tr>
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
778
            <tr>
779
              <td><input type='checkbox' id='show_limit_toggle' checked onchange='getRqStr()'></td>
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
780
              <td>Show limit</td><td><input id='limit' type='text' value='$ins_limit' style='width:100px;text-align:right;' onchange='getRqStr()'></td>
781
            </tr>
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
782 783 784 785
            <tr>
              <td><input type='checkbox' id='show_nth_toggle' checked onchange='getRqStr()'></td>
              <td>Show every</td><td><input id='nth' type='text' value='$ins_nth' style='width:100px;text-align:right;' onchange='getRqStr()'> <sup>th</sup> record</td>
            </tr>
786 787 788 789
            </table>
          </td>
        </tr>
        <tr>
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
790
          <td><button onclick='show()'>Show records</button>&nbsp;&nbsp;<span id='csvlink'></span><span id='csvlink2'></span></td>
791 792 793 794 795 796 797 798 799 800 801 802
        </tr>
        </table>
      </td>
    </tr>
    </table>
    <div id='results'></div>
  </body>
</html>
TEXT;
}

function js(){
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
803
  global $thisname;
804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828
  global $init;

  if ($init) $insert = "show();";
  else       $insert = "";
  
  $help = help();
  
  return <<<TEXT
function init(){
  console.log("init");
  document.getElementById("results").innerHTML = $help;
  bindFilenameChangers();
  $insert
}

function show(){
  console.log("show");
  
  var rqstr = getRqStr();
  
  var request = new XMLHttpRequest();
  request.open('GET', rqstr+"&nogui", true);

  request.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {      
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
829
      var resp = this.responseText;  
830 831 832 833 834 835 836 837 838
      clearInterval(loading_interval);
      document.getElementById("results").innerHTML = "<br/>"+resp;
    }
  };
  
  request.onerror = function() {
    // There was a connection error of some sort
  };
  
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
839
  loading_interval = setInterval(loading,500);
840 841 842 843 844 845
  request.send();
}

function getList(){

  var filename = document.getElementById("file").value;
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
846
  var rqstr = "$thisname?list&nogui&file="+filename;
847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870
  
  var request = new XMLHttpRequest();
  request.open('GET', rqstr, true);
  
  request.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {      
      var resp = this.responseText;
      document.getElementById("results").innerHTML = "<br/><span style='font-size:1.0em'>"+resp+"</span>";
      bindFilenameChangers();
    }
  };
  
  request.onerror = function() {
    // There was a connection error of some sort
  };
  
  unbindFilenameChangers();
  request.send();
}

function getRqStr(){
  var filename = document.getElementById("file").value;
  
  var filter = 0;
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
871
  for(var i=0;i<10;i++){
872 873 874 875 876 877 878 879 880 881 882 883 884
    bit = (document.getElementById("filter_"+i).checked)?1:0;
    filter += (bit<<i);
  }
  
  var start = document.getElementById("start").value;
  var end = document.getElementById("end").value;
  
  var limit = "";
  
  if (document.getElementById("show_limit_toggle").checked){
    limit = "&limit="+document.getElementById("limit").value;
  }
  
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
885 886 887 888 889 890
  var nth = "";

  if (document.getElementById("show_nth_toggle").checked){
    nth = "&nth="+document.getElementById("nth").value;
  }
  
891 892 893 894 895 896 897
  var n = end - start;
  
  if (n<0) {
    console.log("Error: Begin > End");
    n = 1;
  }
  
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
898
  var rqstr = "$thisname?file="+filename+"&record="+start+"&nrecords="+n+"&filter="+filter+limit+nth;
899 900 901
  
  report("");
  setTimeout(function(){
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
902
    report("<a href='"+rqstr+"'>share</a>, Download: <a href='"+rqstr+"&nogui"+"&format=csv'>csv</a>, <a href='"+rqstr+"&nogui"+"&format=csv&showall'>full csv</a>");
903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943
  },100);
  
  return rqstr;
}

function bindFilenameChangers(){

  var elems = document.getElementsByClassName("filenamechanger");

  for(var i=0;i<elems.length;i++){
    elems[i].addEventListener("click", changeFilename);
  }  
}

function unbindFilenameChangers(){

  var elems = document.getElementsByClassName("filenamechanger");

  for(var i=0;i<elems.length;i++){
    elems[i].removeEventListener("click", changeFilename);
  }  
}

function changeFilename(){
  var file = this.innerHTML;
  var elem = document.getElementById("file");
  elem.value = file;

  ev = document.createEvent('Event');
  ev.initEvent('change', true, false);
  elem.dispatchEvent(ev);
}

function report(msg){
  document.getElementById("csvlink").innerHTML = msg;
}

var loading_interval;

function loading(){
  console.log("loading");
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
944
  var tmp = document.getElementById("csvlink2").innerHTML;
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
945 946
  if (tmp.length<2) tmp += ".";
  else              tmp = "";
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
947
  document.getElementById("csvlink2").innerHTML = tmp;
948 949 950 951 952 953
}

TEXT;
}

function help(){
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
954
  global $thisname;
955 956 957 958 959 960
  global $nogui;

  $logslist = showlist();
  
  $help = <<<TEXT
  <span style='font-size:1.2em;'>$logslist</span>
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
961 962 963 964 965
  <div style='font-size:1.2em;'>Source:
    <ul>
      <li><a href="?source" >This program source</a></li>
    </ul>
  </div>
966 967 968 969 970 971 972 973
  <div style='font-size:1.2em;'>Usage:
    <ul>
      <li>GUI:
        <ul>
          <li><b>Filename</b> - path to file:<br/>
          &nbsp;&nbsp;&nbsp;&nbsp;<i>remote != server address</i> - http://thisscriptrootpath/logs/filename<br/>
          &nbsp;&nbsp;&nbsp;&nbsp;<i>remote == server address</i> - any relative/absolute path
          </li>
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
974
          <li><b>Record filter checkboxes</b> - checked = show</li>
975 976 977
          <li><b>Begin</b> - offset, record index in log</li>
          <li><b>End</b> - offset, record index in log</li>
          <li><b>Show limit</b> - Number of filtered records to show, if enabled overrides <b>End</b></li>
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
978 979 980 981 982 983 984 985 986 987 988
          <li><button onclick='getList()'>List log files</button> :
            <ul>
              <li>remote access - list of files in <b>http://thisscriptrootpath/logs/</b></li>
              <li>local access - list of files in <b>http://thisscriptrootpath/</b></li>
            </ul>
            <button onclick='show()'>Show records</button> - after a file is selected.<br/>
            If a folder is selected - <button onclick='getList()'>List log files</button> again to scan inside.
          </li>
          <li><a href='#'>share</a> - open this page with the same parameters (auto <b>show records</b>)</li>
          <li><a href='#'>csv</a> - download csv: filter + limit</li>
          <li><a href='#'>csv full</a> - download csv: filter + no limit</li>
989 990 991
        </ul>
      </li>
      <br/>
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
992
      <li>URL (see <b>share</b>,<b>csv</b>,<b>csv full</b>):
993 994
        <ul>
          <li>
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
995
            <b>http://thisscriptrootpath/$thisname?file=..&record=..&nrecords=..&filter=..&limit=..&format=..</b><br/>
996 997 998 999 1000 1001
            &nbsp;&nbsp;<b>file</b> - with path<br/>
            &nbsp;&nbsp;<b>format</b> - accepts 'csv' or 'html'<br/>
            &nbsp;&nbsp;<b>limit</b> - limit the displayed records<br/>
            &nbsp;&nbsp;<b>record</b> - starting record index, default = 0<br/>
            &nbsp;&nbsp;<b>nrecords</b> - number of records to parse, default = 5000<br/>
            &nbsp;&nbsp;<b>filter</b> - filter out types of displayed records:<br/>
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
1002 1003 1004 1005 1006 1007
            &nbsp;&nbsp;&nbsp;&nbsp;0x200 - display external trigger records only<br/>
            &nbsp;&nbsp;&nbsp;&nbsp;0x100 - display image records, channel 3 only<br/>
            &nbsp;&nbsp;&nbsp;&nbsp;0x080 - display image records, channel 2 only<br/>
            &nbsp;&nbsp;&nbsp;&nbsp;0x040 - display image records, channel 1 only<br/>
            &nbsp;&nbsp;&nbsp;&nbsp;0x020 - display image records, channel 0 only<br/>
            &nbsp;&nbsp;&nbsp;&nbsp;0x010 - display imu records only<br/>
1008
            &nbsp;&nbsp;for gps records:<br/>
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
1009 1010 1011 1012
            &nbsp;&nbsp;&nbsp;&nbsp;0x008 - display NMEA GPVTG records,<br/>
            &nbsp;&nbsp;&nbsp;&nbsp;0x004 - display NMEA GPGSA records<br/>
            &nbsp;&nbsp;&nbsp;&nbsp;0x002 - display NMEA GPGGA records (have coordinates)<br/>
            &nbsp;&nbsp;&nbsp;&nbsp;0x001 - display NMEA GPRMC records (have coordinates)<br/>            
1013 1014 1015 1016
            &nbsp;&nbsp;default = 0x7f (display everything)<br/>
          </li>
        </ul>
      </li>
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
1017 1018
      <li>Command line:
        <ul>
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
1019 1020
          <li><i>~$ php $thisname logfile [filter] > output.csv</i></li>
          <li>cli help message: <i>~$ php $thisname</i></li>
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
1021 1022
        </ul>
      </li>
1023 1024
    </ul>
    </div>
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
1025
    <br/><br/><br/>
1026 1027 1028 1029 1030 1031 1032
TEXT;

  if (!$nogui) $help = "`<br/>$help`";
  
  return $help;
}

1033
?>