#!/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; }