Skip to content

Instantly share code, notes, and snippets.

@Dygear
Last active May 16, 2022 00:16
Show Gist options
  • Save Dygear/4eed582719cfe5ae10d4a751f362361e to your computer and use it in GitHub Desktop.
Save Dygear/4eed582719cfe5ae10d4a751f362361e to your computer and use it in GitHub Desktop.
MimoSDR Upload Files

config.json

Placed in the same directory as the Trunk-Recorder binary.

MimoUpload.sh

Placed in the same directory as the Trunk-Recorder binary.

upload.php

Placed in the receiving location on your HTTP web server.


autoload.php

This is the PSR autoload script. Should be in a well known location, with the other files blow it in the $SOMEWHERE/php/ dir.

Database.php

Database helper file. Should be SOMEWHERE/php/ and pointed to by your autoload script.

Audio.php

This is the actual meat of the MimoSDR audio system. Contains everything you need to setup an SQLite database. Place in $SOMEWHERE/php/MimoSDR.

Files.php

This is a helper class to make file upload easier. Place in the $SOMEWHERE/php/ dir.

DateTime.php

This is a helper class to make working with dates easier. Place in the $SOMEWHERE/php/ dir.

<?php
namespace MimoSDR;
use \DateTimeZone;
use \TTG\DateTime;
use \TTG\Files;
class Audio
{
const SQL_CREATE =<<<SQL
CREATE TABLE sdr_sites (
siteId INTEGER PRIMARY KEY,
lat REAL,
lng REAL,
name TEXT,
location TEXT
);
INSERT INTO sdr_sites (lat, lng, name, location) VALUES
(40.720000, -73.490000, 'CUSTOMER0', 'REDACTED'),
(40.740000, -73.630000, 'CUSTOMER1', 'REDACTED'),
(40.800000, -73.180000, 'CUSTOMER2', 'REDACTED'),
(40.720000, -73.550000, 'MimoSDR-NCPD0', 'REDACTED'),
(40.860000, -73.170000, 'MimoSDR-SCPD0', 'REDACTED'),
(40.860000, -73.170000, 'MimoSDR-SCPD1', 'REDACTED'),
(40.860000, -73.170000, 'MimoSDR-SCPD2', 'REDACTED'),
(40.860000, -73.170000, 'MimoSDR-SCPD3', 'REDACTED');
CREATE TABLE p25 (
p25Id INTEGER PRIMARY KEY,
WACN TEXT NOT NULL,
systemId TEXT NOT NULL,
nameShort TEXT NOT NULL,
nameLong TEXT NOT NULL
);
INSERT INTO p25 (WACN, systemId, nameShort, nameLong) VALUES
('BEE00', '1AE', 'NCPD', 'Nassau County Police Department'),
('BEE00', '3CE', 'SCPD', 'Suffolk County Police Department');
CREATE TABLE p25_talkgroups (
talkgroupId INTEGER PRIMARY KEY,
p25Id INTEGER NOT NULL,
tgId INTEGER NOT NULL,
mode TEXT NOT NULL,
alphaTag TEXT NOT NULL,
description TEXT NOT NULL,
tag TEXT NOT NULL,
'group' TEXT NOT NULL,
priority INTEGER,
FOREIGN KEY(p25Id) REFERENCES p25(p25Id)
);
CREATE TABLE p25_units (
unitId INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
p25Id INTEGER NOT NULL,
src INTEGER NOT NULL,
alias TEXT NOT NULL,
startTime DATETIME NOT NULL,
endTime DATETIME,
FOREIGN KEY(p25Id) REFERENCES p25(p25Id)
);
CREATE TABLE audio (
audioId INTEGER PRIMARY KEY,
siteId INTEGER,
p25Id INTEGER,
tgId INTEGER,
freq INTEGER,
timeStart DATETIME,
timeStop DATETIME,
emergency BOOL,
pathAudio TEXT,
sizeAudio INTEGER,
pathJSON TEXT,
sizeJSON INTEGER,
FOREIGN KEY(p25Id) REFERENCES p25(p25Id)
FOREIGN KEY(siteId) REFERENCES sdr_sites(siteId)
);
CREATE TABLE audio_sources (
sourceId INTEGER PRIMARY KEY,
audioId INTEGER NOT NULL,
src INTEGER,
time DATETIME,
pos REAL,
emergency BOOL,
signal_system TEXT,
tag TEXT,
FOREIGN KEY(audioId) REFERENCES audio(audioId)
);
CREATE TABLE audio_frequencies (
frequencyId INTEGER PRIMARY KEY,
audioId INTEGER NOT NULL,
freq INTEGER,
time DATETIME,
pos REAL,
len INTEGER,
error_count INTEGER,
spike_count INTEGER,
FOREIGN KEY(audioId) REFERENCES audio(audioId)
);
SQL;
const SQL_INSERT_AUDIO = 'INSERT INTO audio (siteId, p25Id, tgId, freq, timeStart, timeStop, emergency, pathAudio, sizeAudio, pathJSON, sizeJSON) VALUES (:siteId, :p25Id, :tgId, :freq, :timeStart, :timeStop, :emergency, :pathAudio, :sizeAudio, :pathJSON, :sizeJSON);';
const SQL_INSERT_SOURCE = 'INSERT INTO audio_sources (audioId, src, time, pos, emergency, signal_system, tag) VALUES (:audioId, :src, :time, :pos, :emergency, :signal_system, :tag);';
const SQL_INSERT_FREQUENCIES = 'INSERT INTO audio_frequencies (audioId, freq, time, pos, len, error_count, spike_count) VALUES (:audioId, :freq, :time, :pos, :len, :error_count, :spike_count);';
/**
* @property MimoCAD\Database $sql
*/
public $sql;
/**
* @property MimoCAD\Files $wav
*/
public $wav;
/**
* @property MimoCAD\Files $m4a
*/
public $m4a;
/**
* @property MimoCAD\Files $json
*/
public $json;
/**
* @property int $audioId
*/
public int $audioId;
/**
* Setups the SQLite Datbase into the $this->sql for use with the rest of the functions.
*
* @param MimoCAD\Database $sql
*/
public function __construct(Database $sql)
{
$this->sql = $sql;
}
/**
* Saves the Audio file from a POST request submitted by MimoSDR's MimoUpload.sh script.
*/
public function save()
{
$path = "/{$_POST['year']}/{$_POST['month']}/{$_POST['day']}/";
$this->wav = new Files();
if ($this->wav->isOk('wav', false)) {
if ($this->wav->moveSafeUpload('wav', $path)) {
$fileLocation = $this->wav->getFilePath();
} else {
echo "Failed to move file WAV; {$_FILES['wav']['error']}" . PHP_EOL;
}
$_FILES['wav']['path'] = $_SERVER['DOCUMENT_ROOT'] . $this->wav->getFilePath();
} else {
echo $this->wav->getError();
}
$this->m4a = new Files();
if ($this->m4a->isOk('m4a', false)) {
if ($this->m4a->moveSafeUpload('m4a', $path)) {
$fileLocation = $this->m4a->getFilePath();
} else {
echo "Failed to move file M4A; {$_FILES['m4a']['error']}" . PHP_EOL;
}
$_FILES['m4a']['path'] = $_SERVER['DOCUMENT_ROOT'] . $this->wav->getFilePath();
} else {
echo $this->m4a->getError();
}
$this->json = new Files();
if ($this->json->isOk('json', false)) {
if ($this->json->moveSafeUpload('json', $path)) {
$fileLocation = $this->json->getFilePath();
} else {
echo "Failed to move file JSON; {$_FILES['json']['error']}" . PHP_EOL;
}
$_FILES['json']['path'] = $_SERVER['DOCUMENT_ROOT'] . $this->json->getFilePath();
} else {
echo $this->json->getError();
}
}
/**
* Inserts the Audio meta data from the POST request of MimoUpload.sh script into the database.
* Takes generated JSON file and turns that into the refferental database tables.
*
* @param string $filePathAudio;
* @param int $fileSizeAudio;
* @param ?string $filePathJSON;
* @param ?int $fileSizeJSON;
* @param mixed $json;
*/
public function insert(string $filePathAudio, int $fileSizeAudio, ?string $filePathJSON, ?int $fileSizeJSON, mixed $json)
{
$insert = $this->sql->prepare(self::SQL_INSERT_AUDIO);
$insert->execute([
':siteId' => $_POST['siteId'],
':p25Id' => $_POST['p25Id'],
':tgId' => $json['talkgroup'] ?? null,
':freq' => $json['freq'] ?? null,
':timeStart' => $json['start_time'] ?? null,
':timeStop' => $json['stop_time'] ?? null,
':emergency' => $json['emergency'] ?? null,
':pathAudio' => $filePathAudio,
':sizeAudio' => $fileSizeAudio,
':pathJSON' => $filePathJSON,
':sizeJSON' => $fileSizeJSON,
]);
$this->audioId = $this->sql->lastInsertId();
foreach ($json['srcList'] ?? [] as $source)
{
$insert = $this->sql->prepare(self::SQL_INSERT_SOURCE);
$insert->execute([
':audioId' => $this->audioId,
':src' => $source['src'],
':time' => $source['time'],
':pos' => $source['pos'],
':emergency' => $source['emergency'],
':signal_system' => $source['signal_system'],
':tag' => $source['tag']
]);
}
foreach ($json['freqList'] ?? [] as $frequency)
{
$insert = $this->sql->prepare(self::SQL_INSERT_FREQUENCIES);
$insert->execute([
':audioId' => $this->audioId,
':freq' => $frequency['freq'],
':time' => $frequency['time'],
':pos' => $frequency['pos'],
':len' => $frequency['len'],
':error_count' => $frequency['error_count'],
':spike_count' => $frequency['spike_count'],
]);
}
}
/**
* Reads the JSON file and decodes it into an assocated array.
*
* @param string $path;
*/
public function readJSONFile(string $path)
{
if (is_dir($path))
return NULL;
return json_decode(file_get_contents($path), true);
}
}
<?php
/**
* Register given function as __autoload() implementation
* @param callable $autoload_function - The autoload function being registered.
* @param bool $throw = TRUE - Should this throw exceptions when the $autoload_function cannot be registered?
* @param bool $prepend = FALSE - Prepend the autoloader on the autoload queue instead of appending it.
* @return bool - Returns TRUE on success or FALSE on failure.
*/
spl_autoload_register(function (string $fullyQualifiedNamespace) {
static $namespaces = [
['TTG' ,__DIR__,'/php/'],
['MimoSDR' ,__DIR__.'/php/MimoSDR/'],
];
foreach ($namespaces as [$namespace, $fullyQualifiedPath])
{
# Just the length of the current namespace itself.
$len = strlen($namespace);
# Is the Fully Qualified Name Space Prefixed with the same Namespace?
if (0 !== strncmp($namespace, $fullyQualifiedNamespace, $len))
{
continue; # If they are not the same, contiune onto the next namespace.
}
# Get the class from the FQNS by removing the namespace prefix's length from the start.
$class = substr($fullyQualifiedNamespace, $len);
# We then get the fully quailified path to the class file.
# str_replace '\\' with '/' because of the call_user_func context.
$file = $fullyQualifiedPath . str_replace('\\', '/', $class) . '.php';
# str_replace '//' with '/' becaue of windows file systems.
$file = str_replace('//', '/', $file);
# If the file exists require it, otherwise try next $namespace.
if (file_exists($file)) {
require $file;
return TRUE;
}
}
return FALSE;
});
{
"ver": 2,
"sources": [{
"center": 852306250,
"rate": 3200000,
"gain": 36,
"error": 0,
"digitalRecorders": 4,
"modulation": "qpsk",
"driver": "osmosdr",
"device": "rtl=0"
}],
"systems": [{
"type": "p25",
"control_channels": [851162500, 852425000, 852675000, 852737500],
"shortName": "SCPD",
"talkgroupsFile": "SCPD.csv",
"callLog": true,
"recordUnknown": false,
"audioArchive": true,
"delayCreateOutput": true,
"uploadScript": "./MimoUpload.sh",
"talkgroupDisplayFormat": "id_tag",
"hideEncrypted": false,
"hideUnknownTalkgroups": false
}],
"defaultMode": "digital",
"captureDir": "./audio",
"callTimeout": 3,
"logFile": true,
"statusAsString": true,
"frequencyFormat": "mhz"
}
<?php
namespace MimoSDR;
class Database extends \PDO
{
/**
* @param string $file - The file path where the SQLite database is.
*/
public function __construct($file)
{
parent::__construct('sqlite:' . $file);
$this->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
$this->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC);
$this->query('PRAGMA foreign_keys = ON;');
}
/**
* @param string $statement - SQL Statement to prepare.
* @param array $options - Options to fill in.
* @return \PDOStatement - So it's compataible with the base PDO class.
*/
public function prepare($statement, $options = []): \PDOStatement
{
return parent::prepare($statement, $options);
}
/**
* MimoCAD Database Interactions
*/
const SQL_DEPARTMENTS = 'SELECT * FROM departments WHERE departmentEnabled = TRUE;';
const SQL_GET_MAX_ID = 'SELECT MAX(ID) AS ID FROM firecom WHERE FDID = :fdId';
const SQL_GET_COUNT = "SELECT substr(strftime('%Y', 'now', 'localtime'), 3) || '-' || printf('%.04d', COUNT(*)) FROM firecom WHERE FDID = :fdId AND TRANS1 BETWEEN strftime('%Y-01-01T00:00:00.000') AND strftime('%Y-12-31T23:59:59.999');";
const SQL_GET_FDID = 'SELECT FDID FROM departments_firecom WHERE departmentId = :departmentId';
const SQL_GET_DEPARTMENT = 'SELECT departmentId FROM departments_firecom WHERE FDID = :FDID';
/**
* @return array - An array of enabled departments.
*/
public static function getDepartments(): array
{
global $mimocad;
$statement = $mimocad->prepare(self::SQL_DEPARTMENTS);
$statement->execute();
return $statement->fetchAll();
}
/**
* This function takes the MimoCAD Department ID
* and returns the FireCom FDID for that department or
* if there is no FDID for that department, a NULL.
*
* @param int $departmentId - MimoCAD Department ID
* @return ?int - FDID for that department, or NULL.
*/
public static function departmentId2FDID(?int $departmentId): ?int
{
if (is_null($departmentId))
return NULL;
global $mimocad;
$select = $mimocad->prepare(self::SQL_GET_FDID);
$select->execute([':departmentId' => $departmentId]);
return $select->fetchColumn() ?? NULL;
}
/**
* This function takes the MimoCAD Department ID
* and returns the FireCom FDID for that department or
* if there is no FDID for that department, a NULL.
*
* @param int $departmentId - MimoCAD Department ID
* @return ?int - FDID for that department, or NULL.
*/
public static function FDID2departmentId(int $FDID): ?int
{
if (is_null($FDID))
return NULL;
global $mimocad;
$select = $mimocad->prepare(self::SQL_GET_DEPARTMENT);
$select->execute([':FDID' => $FDID]);
return $select->fetchColumn() ?? NULL;
}
/**
* @param int $departmentId - MimoCAD Department ID
* @return ?int - The MAX ID or NULL;
*/
public static function getMaxId(int $departmentId): ?int
{
global $mimocad;
// Turn DepartmentId into FDID.
if (NULL === ($fdId = self::departmentId2FDID($departmentId)))
{
return NULL;
}
// Get the MAX ID for this department.
$statement = $mimocad->prepare(self::SQL_GET_MAX_ID);
$statement->execute([':fdId' => $fdId]);
return $statement->fetchColumn();
}
/**
* @param int $departmentId - MimoCAD Department ID
* @return ?string yy-nnnn - Two digit year, zero padded number or NULL.
*/
public static function getYTDCount(int $departmentId): ?string
{
global $mimocad;
// Turn DepartmentId into FDID.
if (NULL === ($fdId = self::departmentId2FDID($departmentId)))
{
return NULL;
}
// Get the alarm count for this department.
$statement = $mimocad->prepare(self::SQL_GET_COUNT);
$statement->execute([':fdId' => $fdId]);
return $statement->fetchColumn();
}
/**
* @param ?string - Name of the sequence object from which the ID should be returned.
* @return string|false - Returns the ID of the last inserted row or sequence value.
*/
public static function getLastRowId(?string $name = null): string|false
{
global $mimocad;
return $mimocad->lastInsertId($name);
}
/**
* @param array $alarm Array of alarm data key => value pair.
* @return bool
*/
public static function setAlarmData(array $alarm): bool
{
global $mimocad;
$row = 'INSERT INTO firecom (' . implode(', ', array_keys($alarm)) . ') VALUES (\'' . implode("', '", array_values($alarm)) . '\');' . PHP_EOL;
try {
$mimocad->query($row);
} catch (\PDOException $e) {
echo 'Exception: ' . $e->getMessage() . ' (' . $e->getCode() . ')' . PHP_EOL;
debug_print_backtrace();
return false;
}
return true;
}
/**
* @return PDO
*/
public static function getHandle(): \PDO
{
global $mimocad;
return $mimocad;
}
}
<?php
namespace TTG;
use \DateTimeInterface as DateTimeInterface;
use \DateTimeZone as DateTimeZone;
class DateTime extends \DateTime
{
/**
* Return just the date component.
*/
const DATE = 'Y-m-d';
/**
* Return just the time component.
*/
const TIME = 'H:i:s';
/**
* Return Date in WEB format
*/
const WEB = 'Y-m-d\TH:i:s';
/**
* Return Date in SQL format
*/
const SQL = self::ISO8601;
/**
* Return an SQLite formatted Time String.
*/
const SQLite = 'Y-m-d\TH:i';
/**
* Return an SQLite formatted TIMESTAMP.
*/
const TIMESTAMP = 'Y-m-d H:i:s';
/**
* SQLTimeStamp = 'Y-m-d\TH:i:s'
*/
const SQLTimeStamp = 'Y-m-d\TH:i:s';
/**
* Return an SQLite formatted string suiteable for BETWEEN calls.
*/
const BETWEEN = 'Y-m-d H:i:s.u';
/**
* Return Date in FireComCad format
* Only works on PHP 7.0 or better.
*/
const FCC = 'Y-m-d\TH:i:s.vP';
/**
* Return Date in Google format
* Only works on PHP 7.0 or better.
*/
const Google = 'Y-m-d\TH:i:s.v\Z';
/**
* Return Date in emsCharts format.
*/
const emsCharts = self::SQL . '\Z';
/**
* Return Date in W3C Date Format.
*/
const W3C_DATE = 'Y-m-d';
/**
* Return Date in ISO8601 format
*
* @return String
*/
public function __toString()
{
return $this->format(self::SQLite);
}
/**
* Return difference between $this and $delta
*
* @param Datetime|String $delta
* @return DateInterval
*/
public function delta($delta = 'NOW', DateTimeZone $timezone = NULL)
{
if (!($delta instanceOf DateTime))
{
$delta = new DateTime($delta, $timezone);
}
return parent::diff($delta);
}
/**
* Return the delta in Years.
*
* @param Datetime|String $delta
* @return Integer
*/
public function getYears($delta = 'NOW'): int
{
return $this->delta($delta)->format('%y');
}
/**
* Is DateTime In Span
*
* Find out if $time is within the $start & $end DateTimes.
*
* @param DateTime $start
* @param DateTime $end
* @param DateTime $time
* @return bool TRUE if the time is within the specified time frame.
*/
public function isDateTimeInSpan(DateTimeInterface $start, DateTimeInterface $end, DateTimeInterface $time = NULL): bool
{
if ($time === NULL)
{
$time = $this;
}
if ($end < $start)
{
$end = $end->modify('+1 Day');
}
return ($time >= $start AND $time <= $end) ? TRUE : FALSE;
}
/**
* Is In Span
*
* Finds out if a time is in a given startTime and endTime.
*
* @param string $date ISO 8601 Date (2015-06-02).
* @param string $start In Military Time (1900).
* @param string $end In Military Time (0700).
* @param DateTime $time Optional. Time to check against.
* @return bool TRUE if the time is within the specified time frame.
*/
public function isInSpan(string $date, string $start, string $end, DateTimeInterface $time = NULL): bool
{
$startTime = new DateTime("$date $start");
$endTime = new DateTime("$date $end");
if ($endTime < $startTime)
{
$endTime = $endTime->modify("+1 Day");
}
if ($time === NULL)
{
$time = $this;
}
return self::isDateTimeInSpan($startTime, $endTime, $time);
}
/**
* String to Span
*
* Takes a date, start time and end time and turns it into a
* tuple of DateTime objects for the start and end of the crew.
*
* @param string $date ISO 8601 Date (2015-06-02).
* @param String $start In Military Time (1900)
* @param String $end In Military Time (0700)
* @return Array tuple, ordered pair of DateTime fields.
*/
public static function strToSpan(string $date, string $start, string $end): array
{
# Start
$startTime = new DateTime("$date $start");
# End
$endTime = new DateTime("$date $end");
# Make sure start is after end.
if ($startTime > $endTime)
{
$endTime = $endTime->modify("+1 Day");
}
return [$startTime, $endTime];
}
/**
* Is Span In Span
*
* Is time span in another time span.
*
* @param DateTime $crewStart In ISO 8601 Format
* @param DateTime $crewEnd In ISO 8601 Format
* @param DateTime $signStart In ISO 8601 Format
* @param DateTime $signEnd In ISO 8601 Format
* @return bool TRUE if $span is within the $crew.
*/
public static function isSpanInSpan(DateTimeInterface $crewStart, DateTimeInterface $crewEnd, DateTimeInterface $signStart, DateTimeInterface $signEnd)
{
return ($signStart <= $crewStart AND $signEnd >= $crewEnd) ? TRUE : FALSE;
}
/**
* Is Span Overlapping Span
*
* Is time overlapping another span.
*
* @param DateTime $crewStart In ISO 8601 Format
* @param DateTime $crewEnd In ISO 8601 Format
* @param DateTime $signStart In ISO 8601 Format
* @param DateTime $signEnd In ISO 8601 Format
* @return bool TRUE if $span is within the $crew.
*/
public static function isSpanOverlappingSpan(DateTimeInterface $crewStart, DateTimeInterface $crewEnd, DateTimeInterface $signStart, DateTimeInterface $signEnd)
{
return (
# if ends after start
$crewEnd > $signStart
AND
# if starts before end
$crewStart < $signEnd
) ? TRUE : FALSE;
}
/**
* Sunrise
*
* Returns time of sunrise for a given day and location.
*
* @param DateTime $timestamp If not set assumes this instance, otherwise must be a DateTime object.
* @param float $latitude Defaults to North, pass in a negative value for South. See also: date.default_latitude.
* @param float $longitude Defaults to East, pass in a negative value for West. See also: date.default_longitude.
* @param float $zenith Default: date.sunrise_zenith
* @param float $gmt_offset Specified in hours.
* @return Returns the sunrise time in a specified format on success or FALSE on failure.
*/
public function sunrise(DateTimeInterface $timestamp = null, float $latitude = null, float $longitude = null, float $zenith = null, float $gmt_offset = null)
{
$timestamp = ($timestamp) ? $timestamp->format('U') : $this->format('U');
$latitude = ($latitude) ?: ini_get("date.default_latitude");
$longitude = ($longitude) ?: ini_get("date.default_longitude");
$zenith = ($zenith) ?: ini_get("date.sunrise_zenith");
$return = date_sunrise($timestamp, SUNFUNCS_RET_TIMESTAMP, $latitude, $longitude, $zenith, $gmt_offset);
return ($return) ? (new DateTime())->setTimestamp($return) : false;
}
/**
* Sunset
*
* Returns time of sunset for a given day and location.
*
* @param DateTime $timestamp If not set assumes this instance, otherwise must be a DateTime object.
* @param float $latitude Defaults to North, pass in a negative value for South. See also: date.default_latitude.
* @param float $longitude Defaults to East, pass in a negative value for West. See also: date.default_longitude.
* @param float $zenith Default: date.sunrise_zenith
* @param float $gmt_offset Specified in hours.
* @return Returns the sunset time in a specified format on success or FALSE on failure.
*/
public function sunset(DateTimeInterface $timestamp = null, float $latitude = null, float $longitude = null, float $zenith = null, float $gmt_offset = null)
{
$timestamp = ($timestamp) ? $timestamp->format('U') : $this->format('U');
$latitude = ($latitude) ?: ini_get("date.default_latitude");
$longitude = ($longitude) ?: ini_get("date.default_longitude");
$zenith = ($zenith) ?: ini_get("date.sunrise_zenith");
$return = date_sunset($timestamp, SUNFUNCS_RET_TIMESTAMP, $latitude, $longitude, $zenith, $gmt_offset);
return ($return) ? (new DateTime())->setTimestamp($return) : false;
}
/**
* Sun Info
*
* Returns an array with information about sunset / sunrise and twilight begin / end
*
* @param DateTime $timestamp If not set assumes this instance, otherwise must be a DateTime object.
* @param float $latitude Defaults to North, pass in a negative value for South. See also: date.default_latitude.
* @param float $longitude Defaults to East, pass in a negative value for West. See also: date.default_longitude.
* @return Returns array on success or FALSE on failure.
*/
public function sunInfo(DateTimeInterface $timestamp = null, float $latitude = null, float $longitude = null)
{
$timestamp = ($timestamp) ? $timestamp->format('U') : $this->format('U');
$latitude = ($latitude) ?: ini_get("date.default_latitude");
$longitude = ($longitude) ?: ini_get("date.default_longitude");
return date_sun_info($timestamp, $latitude, $longitude);
}
public function resolveThisYear(DateTime &$timeStart = null, DateTime &$timeEnd = null)
{
if ($timeStart === null)
{
$timeStart = new DateTime(date('Y') . '-01-01T00:00:00');
}
if ($timeEnd === null)
{
$timeEnd = new DateTime(date('Y') . '-12-31T23:59:59');
}
}
public function resolveThisMonth(DateTime &$timeStart = null, DateTime &$timeEnd = null)
{
if ($timeStart === null)
{
$timeStart = new DateTime('first day of this month 00:00:00');
}
if ($timeEnd === null)
{
$timeEnd = new DateTime('first day of next month 00:00:00');
$timeEnd->modify('-1 Second');
}
}
}
<?php
namespace TTG;
class Files extends \finfo
{
const MAX_FILE_SIZE = 1024 * 1024 * 8;
const ERROR_CODES = [
UPLOAD_ERR_OK => 'There is no error, the file uploaded with success.',
UPLOAD_ERR_INI_SIZE => 'The uploaded file exceeds the upload_max_filesize directive in php.ini.',
UPLOAD_ERR_FORM_SIZE => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.',
UPLOAD_ERR_PARTIAL => 'The uploaded file was only partially uploaded.',
UPLOAD_ERR_NO_FILE => 'No file was uploaded.',
UPLOAD_ERR_NO_TMP_DIR => 'Missing a temporary folder.', # Introduced in PHP 5.0.3.
UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk.', # Introduced in PHP 5.1.0.
UPLOAD_ERR_EXTENSION => 'A PHP extension stopped the file upload.' # Introduced in PHP 5.2.0.
];
private int $errorNum;
private string $errorStr;
private ?string $filePath = NULL;
private ?int $fileSize = NULL;
/**
* File Upload with File Info
*
* @param int $options - One or disjunction of more Fileinfo constants
* @param string $magic_files - Location of the Magic Database File
*
*/
public function __construct(int $options = FILEINFO_MIME_TYPE, string $magic_file = NULL)
{
return parent::__construct($options, $magic_file);
}
public function isOk(string $name, string $type = 'image'): bool
{
if (empty($_FILES[$name]['name']))
{
$this->errorNum = 0;
$this->errorStr = "No file uploaded.";
return false;
}
if (empty($_FILES[$name]['tmp_name']))
{
$this->errorNum = -1;
$this->errorStr = "No file uploaded.";
return false;
}
if (!is_uploaded_file($_FILES[$name]['tmp_name']))
{
$this->errorNum = -2;
$this->errorStr = "This file was not uploaded by a POST / PUT request; Nice try.";
return false;
}
// Is is now OK to check the actual file for information;
if ($_FILES[$name]['error'] == UPLOAD_ERR_FORM_SIZE)
{
$this->errorNum = -3;
$this->errorStr = "The uploaded file exceeds the MAX_FILE_SIZE of the Files class.";
return false;
}
if ($type !== false AND substr($this->file($_FILES[$name]['tmp_name']), 0, strlen($type)) !== $type)
{
$this->errorNum = -4;
$this->errorStr = "The uploaded file is not of an {$type} type.";
return false;
}
if ($_FILES[$name]['error'] !== UPLOAD_ERR_OK)
{
$this->errorNum = $_FILES[$name]['error'];
$this->errorStr = self::ERROR_CODES[$_FILES[$name]['error']];
return false;
}
$this->fileSize = $_FILES[$name]['size'];
return true;
}
public function getError(): string
{
return $this->getErrorNum() . ': ' . $this->getErrorStr();
}
public function getErrorNum(): int
{
return $this->errorNum;
}
public function getErrorStr(): string
{
return $this->errorStr;
}
public function moveUpload(string $name, string $path = '/images/profile/'): bool
{
$ext = explode('/', $_FILES[$name]['type'])[1];
$hash = sha1_file($_FILES[$name]['tmp_name']);
$this->filePath = $path . $hash . '.' . $ext;
return move_uploaded_file(
$_FILES[$name]['tmp_name'],
$_SERVER['DOCUMENT_ROOT'] . $this->filePath
);
}
public function moveSafeUpload(string $name, string $path): bool
{
$this->filePath = $path . $_FILES[$name]['name'];
$DOCUMENT_ROOT = $_SERVER['DOCUMENT_ROOT'] . $this->filePath;
if (!is_numeric($_POST['year']))
{
echo 'Year is not an int.' . PHP_EOL;
return false;
}
if (!is_numeric($_POST['month']))
{
echo 'Month is not an int.' . PHP_EOL;
return false;
}
if (!is_numeric($_POST['day']))
{
echo 'Day is not an int.' . PHP_EOL;
return false;
}
// Make the dir tree.
$TREE = explode('/', $DOCUMENT_ROOT);
array_shift($TREE);
array_pop($TREE);
for ($PATH = '/', $i = 0, $j = count($TREE); $i < $j; ++$i)
{
$PATH .= "{$TREE[$i]}/";
if (!file_exists($PATH) OR !is_dir($PATH))
{
if (!mkdir($PATH, 0755, true))
{
echo "\n\nFailed to create directory {$PATH}\n\n";
}
}
}
return move_uploaded_file(
$_FILES[$name]['tmp_name'],
$_SERVER['DOCUMENT_ROOT'] . $this->filePath
);
}
public function getFilePath(): ?string
{
return $this->filePath;
}
public function getFileSize(): ?int
{
return $this->fileSize;
}
public function isJPEG(string $name, int $w = NULL, $h = NULL): bool
{
if ($_FILES[$name]['type'] != 'image/jpeg')
{
return false;
}
if (!function_exists('exif_imagetype'))
{
if (exif_imagetype($_FILES[$name]['tmp_name']) != IMAGETYPE_JPEG)
{
return false;
}
}
if (NULL !== $w AND NULL !== $h)
{
[$width, $height, $type, $attr] = getimagesize($_FILES[$name]['tmp_name']);
if ($width !== $w OR $height !== $h)
{
return false;
}
}
return true;
}
public function isPDF(string $name): bool
{
if ($_FILES[$name]['type'] !== 'application/pdf')
{
return false;
}
if ($this->file($_FILES[$name]['tmp_name'], FILEINFO_MIME_TYPE) !== 'application/pdf')
{
return false;
}
return true;
}
}
#!/usr/local/bin/php
<?php
// This is the SQLite sdr_sites.siteId assigned for this location. (MimoSDR-SCPD0)
define('SITE_ID', 5);
// This is the SQLite p25.p25Id assigned for this system. (SCPD)
define('P25_ID', 2);
[$me, $wav, $json, $m4a] = $argv;
[$path, $audio, $system, $year, $month, $day, $file] = explode('/', $wav);
[$tgid, $time, $hz, $call, $number, $ext] = preg_split('/[-_.]/', $file);
define('MULTIPART_BOUNDARY', '--------------------------'.microtime());
$header = 'Content-Type: multipart/form-data; boundary='.MULTIPART_BOUNDARY;
/**
* $_FILES
*/
// WAV File Upload
$content = "--".MULTIPART_BOUNDARY."\r\n".
"Content-Disposition: form-data; name=\"wav\"; filename=\"".basename($wav)."\"\r\n".
"Content-Type: audio/wav\r\n\r\n".
file_get_contents($wav)."\r\n";
// M4A File Upload
$content .= "--".MULTIPART_BOUNDARY."\r\n".
"Content-Disposition: form-data; name=\"m4a\"; filename=\"".basename($m4a)."\"\r\n".
"Content-Type: audio/m4a\r\n\r\n".
file_get_contents($m4a)."\r\n";
// JSON File Upload
$content .= "--".MULTIPART_BOUNDARY."\r\n".
"Content-Disposition: form-data; name=\"json\"; filename=\"".basename($json)."\"\r\n".
"Content-Type: application/json\r\n\r\n".
file_get_contents($json)."\r\n";
/**
* $_POST
*/
// SITE ID
$content .= "--".MULTIPART_BOUNDARY."\r\n".
"Content-Disposition: form-data; name=\"siteId\"\r\n\r\n".
SITE_ID."\r\n";
// P25 ID
$content .= "--".MULTIPART_BOUNDARY."\r\n".
"Content-Disposition: form-data; name=\"p25Id\"\r\n\r\n".
P25_ID."\r\n";
// Year
$content .= "--".MULTIPART_BOUNDARY."\r\n".
"Content-Disposition: form-data; name=\"year\"\r\n\r\n".
"{$year}\r\n";
// Month
$content .= "--".MULTIPART_BOUNDARY."\r\n".
"Content-Disposition: form-data; name=\"month\"\r\n\r\n".
"{$month}\r\n";
// Day
$content .= "--".MULTIPART_BOUNDARY."\r\n".
"Content-Disposition: form-data; name=\"day\"\r\n\r\n".
"{$day}\r\n";
// signal end of request (note the trailing "--")
$content .= "--".MULTIPART_BOUNDARY."--\r\n";
$context = stream_context_create(array(
'http' => array(
'method' => 'POST',
'header' => $header,
'content' => $content,
)
));
echo file_get_contents('https://radio.mimocad.io/upload.php', false, $context);
<?php
namespace MimoSDR;
### YOU MUST CHANGE THIS FILE PATH ###
### YOU MUST CHANGE THIS FILE PATH ###
### YOU MUST CHANGE THIS FILE PATH ###
require('$SOMEWHERE/autoload.php');
### YOU MUST CHANGE THIS FILE PATH ###
### YOU MUST CHANGE THIS FILE PATH ###
### YOU MUST CHANGE THIS FILE PATH ###
print_r($_POST);
print_r($_FILES);
// Read JSON file and Import to SQL Database.
$MimoSDRDB = new Database('/var/www/mimocad/MimoSDR.db');
$Audio = new Audio($MimoSDRDB);
$Audio->save();
$Audio->insert(
$Audio->wav->getFilePath(),
$Audio->wav->getFileSize(),
$Audio->json->getFilePath(),
$Audio->json->getFileSize(),
$Audio->readJSONFile($_SERVER['DOCUMENT_ROOT'] . $Audio->json->getFilePath())
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment