#!/usr/bin/perl
use warnings;
use strict;
use DBI;
use LWP::UserAgent;
use Image::Magick;
use GPSD::Parse;
use File::Copy qw(move);
use Algorithm::GooglePolylineEncoding;
$| = 1; # auto-flush socket
my $sessionID = shift;
my ($QUAD, $ZERO, $ZERO2, $PI3, $PI4, $PI5) = (0, 1, 2, 3, 4, 5); # deviceType
my ($NORMAL, $TEST, $SYNC) = (0, 1, 2); # mode
my ($deviceType, $mapID, $pinID, $isOnline, $mode, $rotation) = ($QUAD, 0, 0, 0, 0, 180);
my ($SUNNY, $NIGHT, $DARK) = (30000, 800, 400);
my ($checkDark, $mpv, $camName, $onward) = (12, ($DARK - 1), 'CENTER', 1);
my ($locFreq, $picFreq, $clock, $asleep, $sleepFreq, $sleepLimit) = (10, 30, 0, 0, 2, 300);
my ($path, $lat, $lon, $timestamp, $ele, $speed, $tmpt) = ('', '', '', '', '', '', '');
my ($mhdQuality, $mhdWidth, $mhdHeight, $mldQuality, $mldWidth, $mldHeight) = (0, 0, 0, 0, 0, 0);
my $url = '--URL for server--'; # removed for code sample
my %picsToSend = ();
my ($trailDir, $sessionDir) = ('/home/pi/trail', '');
my $configFile = "$trailDir/raw/camconfig.txt";
unlink $configFile;
my $gps = GPSD::Parse->new;
my $ua = LWP::UserAgent->new;
$ua->timeout(10);
my ($dsn, $dbUser, $dbPass) = ('DBI:SQLite:dbname=mapsDB', 'user', 'pw');
my $dbh = DBI->connect($dsn, $dbUser, $dbPass, { RaiseError => 1 })
or die $DBI::errstr;
&loadConfiguration();
# wait for the camera cover to be removed
while (($checkDark > 0) && ($mpv < $DARK)) { &takePicture(0, 500); sleep(5); }
do
{
print "[clock: $clock] [locFreq: $locFreq] [picFreq: $picFreq]\n";
&pollGPS();
while ($onward && (($lat eq '') || ($speed < 0.5))) # we're not moving
{
print "We're not moving.\n";
$asleep += 5; sleep(5);
if ($asleep >= $sleepLimit) { $onward = 0; }
elsif ($checkDark && ($asleep > $checkDark))
{
&takePicture(0, 500); # is the camera shuttered?
if ($mpv < $DARK) { print "UH-OH DARK! ...exiting.\n"; $onward = 0; }
}
&pollGPS();
}
if ($onward)
{
$timestamp = time();
&saveSpot();
my $picID = 0;
if ($clock % $picFreq == 0) # round up available pictures
{
if ($deviceType != $QUAD) { &writeCamConfig(); sleep(4); }
&takePicture(0, 2000);
&pollGPS();
if ($deviceType == $PI5) { &takePicture(1, 2000); }
if ($deviceType != $QUAD) { sleep(6); } # wait for pics from other cameras
opendir(DIR, "${trailDir}/raw");
my @picFiles = grep(/\.jpg$/, readdir(DIR));
closedir(DIR);
foreach my $cFile (@picFiles)
{
my ($cam, $ending) = split('\.', $cFile);
my $dFile = $sessionDir . '/' . $cam . '_lr_' . $timestamp . '.jpg';
$cFile = $trailDir . '/raw/' . $cam . '.jpg';
move($cFile, $dFile);
$picsToSend{$cam} = $dFile;
}
if (scalar(@picFiles) > 0) { $picID = &savePhotos(); }
}
# at the path interval, send coords and available pics
if ($isOnline && ($clock % $locFreq == 0))
{
my $markerID = &sendPackage();
if ($markerID < 0) { $onward = 0; }
elsif (($picID > 0) && ($markerID > 0))
{ $dbh->do("UPDATE pics SET markerID = $markerID WHERE ID = $picID"); }
%picsToSend = ();
}
$clock += $sleepFreq; sleep($sleepFreq);
}
} while ($onward);
$dbh->disconnect;
###################################################################################
sub pollGPS
{
$gps->poll; # Sample the GPS
($lat, $lon, $ele, $speed) = ($mode == $TEST) ? (rand(2) + 43, rand(2) - 101, 10, 1.0)
: ($gps->lat, $gps->lon, $gps->alt, $gps->speed);
$tmpt = `cat /sys/class/thermal/thermal_zone0/temp` / 1000; # in degrees C
print "($lat, $lon) [speed $speed] [temperature $tmpt]\n";
}
sub sendPackage
{
my ($markerID, $newPinID) = (0, 0);
my $coords = &buildCoords();
my $fileCount = keys %picsToSend;
print "Sending package [$lat, $lon] with $fileCount pictures... ";
my $resp = $ua->post($url,
['mapID' => $mapID, 'ts' => $timestamp, 'lat' => $lat, 'lon' => $lon,
(map { "file_".($_) => [ $picsToSend{$_} ] } keys %picsToSend),
'count' => $fileCount, 'coords' => "$coords"],
Content_Type => 'form-data');
if ($resp->is_success)
{
my @values = split("\n", $resp->decoded_content);
if ($#values < 2) { $markerID = $values[0]; }
else
{
($markerID, $newPinID, $locFreq, $picFreq) = @values;
if ($newPinID != $pinID) { &changeSession($newPinID); $pinID = $newPinID; }
&saveSession();
}
$dbh->do("UPDATE spots SET delivered = 1 WHERE sessionID = $sessionID");
}
print "done [markerID: $markerID]\n";
return $markerID;
}
# --info-text arg (=#%frame (%fps fps) exp %exp ag %ag dg %dg)
# -awb mode can be auto, incandescent, tungsten, fluorescent, indoor, daylight, cloudy
sub takePicture
{
my ($camNum, $expTime) = (shift, shift);
my $lightString = ($mpv >= $SUNNY) ? '--awb daylight' : '--awb cloudy';
my $picFile = ($expTime < 1000) ? $trailDir . '/' . 'discard.jpg'
: $sessionDir . '/' . $camName . $camNum . '_hr_' . $timestamp . '.jpg';
print "Taking picture [${expTime}ms] [$picFile]... ";
my $exitCode = system("/usr/bin/libcamera-still --verbose=0 --autofocus-mode auto --quality ${mhdQuality} --width ${mhdWidth} --height ${mhdHeight} --rotation $rotation ${lightString} --nopreview -t $expTime --camera $camNum -e jpg -o $picFile");
if ($exitCode <= 0) # picture taken successfully
{
my $image = Image::Magick->new;
$image->Read("$picFile");
$mpv = $image->Get("%[mean]");
undef $image;
print "[MPV: $mpv]... done.\n";
if (($expTime > 1000) && ($mpv > $DARK))
{
my $compressedFile = $trailDir . '/raw/' . $camName . $camNum . '.jpg';
if ($deviceType == $QUAD) { &splitPicture($picFile); }
else { &shrinkPicture($picFile, $compressedFile); }
}
}
else { print "PHOTO FAILED.\n"; }
}
sub shrinkPicture
{
my ($origFile, $compressedFile) = (shift, shift);
print "Shrinking picture [$origFile].\n";
my $image = Image::Magick->new;
$image->Read("$origFile");
$image->Resize(geometry=>"${mldWidth}x${mldHeight}");
$image->Write(filename=> "$compressedFile", quality=> $mldQuality);
undef $image;
}
sub splitPicture
{
my $origFile = shift;
my @camNames = ('REAR', 'CENTER', 'LEFT', 'RIGHT');
my @x = (0, ($mhdWidth / 2), 0, ($mhdWidth / 2));
my @y = (0, 0, ($mhdHeight / 2), ($mhdHeight / 2));
print "Splitting picture [$origFile].\n";
for (my $i = 1; $i <= $#camNames; $i++) # choose cameras
{
my $compressedFile = $trailDir . '/raw/' . $camNames[$i] . '.jpg';
my $image = Image::Magick->new;
$image->Read("$origFile");
$image->Crop(width => ($mhdWidth / 2), height => ($mhdHeight / 2), x=> $x[$i], y=> $y[$i]);
$image->Resize(geometry=>"${mldWidth}x${mldHeight}");
$image->Write(filename=> "$compressedFile", quality=> $mldQuality);
undef $image;
}
}
sub buildCoords() # create string like: 76.541,104.223,48.2|76.111,103.8387,53.9
{
my ($coords, $count) = ('', 0);
my $sth = $dbh->prepare("SELECT lat, lon, elevation FROM spots WHERE sessionID = $sessionID AND delivered = 0 ORDER BY timestamp");
$sth->execute();
while (my $ref = $sth->fetchrow_hashref())
{
if ($count > 0) { $coords .= '|'; }
$coords .= $ref->{'lat'} . ',' . $ref->{'lon'} . ',' . $ref->{'elevation'};
$count++;
}
$sth->finish();
print "Built coords [session $sessionID] with $count points.\n";
return $coords;
}
sub saveSpot
{
$dbh->do('INSERT INTO spots (sessionID, lat, lon, elevation, speed, temperature) VALUES (?, ?, ?, ?, ?, ?)', undef, $sessionID, $lat, $lon, $ele, $speed, $tmpt);
}
sub savePhotos
{
$dbh->do('INSERT INTO pics (sessionID, timestamp, lat, lon, mpv) VALUES (?, ?, ?, ?, ?)', undef, $sessionID, $timestamp, $lat, $lon, $mpv);
return $dbh->selectrow_array("SELECT last_insert_rowid()");
}
sub changeSession
{
my $newPinID = shift;
$dbh->do("UPDATE sessions SET pinID = $newPinID WHERE ID = $sessionID");
}
sub loadConfiguration
{
my $sth = $dbh->prepare("SELECT * FROM device WHERE ID = 1");
$sth->execute();
if (my $ref = $sth->fetchrow_hashref())
{
$deviceType = $ref->{'deviceType'}; $isOnline = $ref->{'isOnline'};
$mode = $ref->{'isTest'}; $checkDark = $ref->{'checkDark'};
$locFreq = $ref->{'locFreq'}; $picFreq = $ref->{'picFreq'};
$mhdQuality = $ref->{'mhdQuality'}; $mhdWidth = $ref->{'mhdWidth'};
$mhdHeight = $ref->{'mhdHeight'}; $mldQuality = $ref->{'mldQuality'};
$mldWidth = $ref->{'mldWidth'}; $mldHeight = $ref->{'mldHeight'};
}
$sth->finish();
$sth = $dbh->prepare("SELECT * FROM sessions WHERE ID = $sessionID");
$sth->execute();
if (my $ref = $sth->fetchrow_hashref())
{
$mapID = $ref->{'mapID'}; $pinID = $ref->{'pinID'};
$locFreq = $ref->{'locFreq'}; $picFreq = $ref->{'picFreq'};
}
$sth->finish();
$sessionDir = '/home/pi/trail/raw/' . $sessionID;
mkdir($sessionDir);
}
sub saveSession
{
$dbh->do("UPDATE sessions SET pinID = $pinID, locFreq = $locFreq, picFreq = $picFreq WHERE ID = $sessionID");
}
sub writeCamConfig
{
print "Writing camconfig.txt (ts $timestamp]...\n";
open(my $fh, '>', $configFile) or do { print "CamConfig write FAILED\n"; return; };
$fh->autoflush;
print $fh "$sessionID\n";
print $fh "$timestamp\n";
close $fh;
}