<?php
namespace App\Controller\Api;
use App\Entity\ApiCallLog;
use App\Entity\IbeaconUpdateApiCallLog;
use App\Entity\IbeaconLastSeen;
use App\Entity\IbeaconLastSeenHistory;
use App\Entity\IbeaconLowBatteryAlert;
use App\Entity\Patient;
use App\Entity\PatientIbeacon;
use App\Entity\Ibeacon;
use App\Entity\User;
use App\LocationReader;
use App\SocketConnection;
use Doctrine\ORM\Query\Expr\Join;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Component\HttpClient\HttpClient;
/**
* @Route("/ibeacons")
*/
class IbeaconLastSeenController extends Controller
{
/**
* @var SerializerInterface
*/
private $serializer;
private $socketConnection;
private $locationReader;
private $ibeaconUpdateDistanceThreshold;
private $stopwatch;
public function __construct(
SerializerInterface $serializer,
SocketConnection $socketConnection,
LocationReader $locationReader,
$ibeaconUpdateDistanceThreshold
) {
$this->serializer = $serializer;
$this->socketConnection = $socketConnection;
$this->locationReader = $locationReader;
$this->ibeaconUpdateDistanceThreshold = $ibeaconUpdateDistanceThreshold;
$this->stopwatch = new Stopwatch();
}
/**
* @Route("/package", methods={"POST"})
*/
public function package(Request $request)
{
$packages = json_decode($request->getContent(), true);
$ibeaconResults = [];
$lost = false;
foreach ($packages as $key => $value) {
$ibeaconResults[$key] = $this->getDoctrine()->getRepository(PatientIbeacon::class)->findOneBy(['mac' => $value['tmac']]);
}
foreach ($ibeaconResults as $key => $ibeacon) {
$packages[$key]['lost'] = 0;
if (!$ibeacon) {
unset($packages[$key]);
} else {
if ($ibeacon->getPatient() && $ibeacon->getPatient()->getPatientStatus() == Patient::STATUS_LOST) {
$lost = true;
$packages[$key]['lost'] = 1;
}
if (!$ibeacon->getLastSeen()) {
$lastSeen = new IbeaconLastSeen();
$lastSeen->setIbeacon($ibeacon);
$this->getDoctrine()->getManager()->persist($lastSeen);
$this->getDoctrine()->getManager()->flush();
}
if (isset($packages[$key]['battery']) && $packages[$key]['battery']) {
$ibeacon->setBattery($packages[$key]['battery']);
$ibeacon->setUpdatedAt(new \DateTime('now'));
$this->getDoctrine()->getManager()->persist($ibeacon);
$this->getDoctrine()->getManager()->flush();
unset($packages[$key]['battery']);
}
}
}
$packages = array_values($packages);
if (count($packages)) {
$dataInJson = json_encode($packages);
$dataToSend = strlen($dataInJson) . $dataInJson;
$this->stopwatch->start('uploadData');
$this->socketConnection->write($dataToSend);
$uploadDataEvent = $this->stopwatch->stop('uploadData');
$uploadDataEvent->ensureStopped();
if ($uploadDataEvent->getDuration() >= 2100) {
$log = new ApiCallLog();
$log->setDuration($uploadDataEvent->getDuration() / 1000.0);
$log->setType("POST");
$log->setSessionId(uniqid());
$this->getDoctrine()->getManager()->persist($log);
$this->getDoctrine()->getManager()->flush();
}
}
return new JsonResponse(array("lost" => $lost), 200);
}
/**
* @Route("/uptime", methods={"POST"})
*/
public function uptime(Request $request)
{
$uptime = json_decode($request->getContent(), true);
if (isset($uptime[0]) && (isset($uptime[0]['type']) && $uptime[0]['type'] == 3) && isset($uptime[0]['uptime'])) {
$dataInJson = json_encode($uptime);
$dataToSend = strlen($dataInJson) . $dataInJson;
$this->stopwatch->start('uploadUptime');
$this->socketConnection->write($dataToSend);
$uploadDataEvent = $this->stopwatch->stop('uploadUptime');
$uploadDataEvent->ensureStopped();
if ($uploadDataEvent->getDuration() >= 2100) {
$log = new ApiCallLog();
$log->setDuration($uploadDataEvent->getDuration() / 1000.0);
$log->setType("POST");
$log->setSessionId(uniqid());
$this->getDoctrine()->getManager()->persist($log);
$this->getDoctrine()->getManager()->flush();
}
return new JsonResponse(array(), 200);
} else {
return new JsonResponse(array("error" => "Incorrect data format"), 402);
}
}
/**
*
* @Route("/match", methods={"POST"})
*/
public function match(Request $request)
{
$packages = json_decode($request->getContent(), true);
$ibeaconResults = [];
$data = "[";
foreach ($packages as $key => $value) {
$ibeaconResults[$key] = $this->getDoctrine()->getRepository(Ibeacon::class)->findOneBy(['major' => $value['major'], 'minor' => $value['minor']]);
}
foreach ($ibeaconResults as $key => $value) {
if (!$value) {
unset($packages[$key]);
} else {
$mac = $value->getMajor() . $value->getMinor();
$patientIbeaconResults = $this->getDoctrine()->getRepository(PatientIbeacon::class)->findOneBy(['mac' => $mac]);
$data .= ($data == "[" ? "" : ",") . $this->serializer->serialize([
'mac' => $mac,
'ibeaconName' => $value->getName(),
'patientIbeaconId' => $patientIbeaconResults ? $patientIbeaconResults->getId() : "",
'patientId' => $patientIbeaconResults ? $patientIbeaconResults->getPatient()->getId() : "",
'patientName' => $patientIbeaconResults ? $patientIbeaconResults->getPatient()->getName() : "",
], 'json');
}
}
$data .= "]";
return new JsonResponse($data, 200, [], true);
}
/**
*
* @Route("/update", methods={"POST"})
*/
public function update(Request $request)
{
$decoded = base64_decode($request->getContent());
$packages = json_decode($decoded, true);
$sessionId = uniqid();
$data = "";
for ($i = 0; $i < count($packages); $i++) {
$ibeacons[$i] = $this->getDoctrine()->getRepository(PatientIbeacon::class)->findOneBy(['mac' => $packages[$i]['tmac']]);
if ($ibeacons[$i]) {
$executionTime = $this->updateAPILastSeen($ibeacons[$i], $packages[$i], $sessionId);
foreach ($executionTime as $key => $value) {
if ($value >= 2.1) {
$logGet = new ApiCallLog();
$logGet->setDuration($value);
$logGet->setType($key);
$logGet->setSessionId($sessionId);
$this->getDoctrine()->getManager()->persist($logGet);
$this->getDoctrine()->getManager()->flush();
}
}
$updatedIbeacon = $this->getDoctrine()->getRepository(PatientIbeacon::class)->findOneBy(['mac' => $packages[$i]['tmac']]);
$this->updateRoute($updatedIbeacon);
$data .= ($data == "" ? "" : ",") . $this->serializer->serialize([
'id' => $ibeacons[$i]->getId(),
], 'json');
}
}
return new JsonResponse("success", 200, [], true);
}
public function updateAPILastSeen($ibeacon, $data, $sessionId)
{
$executionTime = [];
$lastSeen = null;
if ($data) {
$ibeaconUpdateLog = new IbeaconUpdateApiCallLog();
$ibeaconUpdateLog->setSessionId($sessionId);
$ibeaconUpdateLog->setIbeacon($ibeacon);
$lat = $data['lat'];
$long = $data['long'];
$resultLastSeen = round($data['ts']);
$gpsacc = (isset($data['gpsacc']) ? (int)$data['gpsacc'] : 0);
$signal = ($data['signal'] ?? 'BLE');
$lastLocation = [
'lat' => $lat,
'long' => $long,
'address' => '',
'gpsacc' => $gpsacc,
'signal' => $signal,
];
$ibeaconUpdateLog->setLat($lat);
$ibeaconUpdateLog->setLong($long);
$ibeaconUpdateLog->setGpsacc($gpsacc);
$ibeaconUpdateLog->setSignal($signal);
if (!$ibeacon->getLastSeen()) {
$lastSeen = new IbeaconLastSeen();
$lastSeen->setIbeacon($ibeacon);
$this->getDoctrine()->getManager()->persist($lastSeen);
$this->getDoctrine()->getManager()->flush();
} else {
$lastSeen = $ibeacon->getLastSeen();
}
$distance = (float)$ibeaconUpdateLog->getDistance();
if (!$ibeaconUpdateLog->getPrevHistory()['address'] || ($distance && $distance > (float)$this->ibeaconUpdateDistanceThreshold)) {
$this->stopwatch->start('getAddress');
$googleResponse = $this->locationReader->addressTranslation($lat, $long);
$getAddressEvent = $this->stopwatch->stop('getAddress');
$getAddressEvent->ensureStopped();
$executionTime['GOOGLE'] = $getAddressEvent->getDuration() / 1000.0;
if ($googleResponse) {
$googleResponseData = json_decode($googleResponse->getContent(), true);
$ibeaconUpdateLog->setStatus($googleResponseData['status']);
$ibeaconUpdateLog->setError($googleResponseData['errorMessage']);
if ($googleResponseData['status'] == 'OK') {
$ibeaconUpdateLog->setCached($googleResponseData['cached']);
if (isset($googleResponseData['addresses']) && isset($googleResponseData['addresses'][0])) {
$ibeaconUpdateLog->setAddress($googleResponseData['addresses'][0]);
$lastLocation['address'] = $googleResponseData['addresses'][0];
$lastSeen->setLastAPILocation($lastLocation);
} else {
$ibeaconUpdateLog->setAddress("[NO ADDRESS CONVERTED FROM API]");
}
}
}
} else {
$address = $ibeaconUpdateLog->getPrevHistory()['address'] ? : "";
$ibeaconUpdateLog->setStatus('SKIPPED');
$ibeaconUpdateLog->setError('');
$ibeaconUpdateLog->setCached(true);
$ibeaconUpdateLog->setAddress($address);
$lastLocation['address'] = $address;
$lastSeen->setLastAPILocation($lastLocation);
}
$lastSeen->setLastAPISeenTimestamp($resultLastSeen);
$this->getDoctrine()->getManager()->persist($ibeaconUpdateLog);
$this->getDoctrine()->getManager()->persist($ibeacon);
$this->getDoctrine()->getManager()->flush();
}
return $executionTime;
}
public function updateRoute($ibeacon) {
$lastSeen = $ibeacon->getLastSeen();
if ($lastSeen && $ibeacon) {
$lastAPILocation = $ibeacon->getLastAPILocation();
$timestamp = $lastSeen->getLastAPISeenTimestamp();
$lastSeenHistory = new IbeaconLastSeenHistory();
$lastSeenHistory->setLastSeen($lastSeen);
$lastSeenHistory->setAddress($lastAPILocation['address']);
$lastSeenHistory->setTimestamp($timestamp);
$lastSeenHistory->setLat($lastAPILocation['lat']);
$lastSeenHistory->setLong($lastAPILocation['long']);
$lastSeenHistory->setGpsacc($lastAPILocation['gpsacc']);
$lastSeenHistory->setSignal($lastAPILocation['signal']);
$this->getDoctrine()->getManager()->persist($lastSeenHistory);
$this->getDoctrine()->getManager()->persist($lastSeen);
}
$this->getDoctrine()->getManager()->persist($ibeacon);
$this->getDoctrine()->getManager()->flush();
}
/**
* @Route("/losts/{version}", methods={"GET"}, requirements={"version"="\d{10}"}, defaults={"version"=null})
*/
public function loadLosts($version)
{
$result = [];
$lastupdate = new \DateTime();
if ($version) {
$result['ver'] = $version;
$lastupdate = (new \DateTime())->setTimestamp($version);
} else {
$result['ver'] = 0;
}
$result['patientlost'] = $this->getDoctrine()->getManager()->createQueryBuilder()
->from('App\Entity\PatientLost', 'l')
->join('App\Entity\PatientIbeacon', 'i', Join::WITH, 'i.patient = l.patient')
->select('l.id AS lostid, i.mac AS beaconid, l.updatedAt AS date, l.found')
->where('l.updatedAt > :lastUpdate')
->andWhere('i.enabled = true')
->orderBy('l.updatedAt', 'DESC')
->addOrderBy('i.updatedAt', 'DESC')
->getQuery()
->execute([
'lastUpdate' => $lastupdate
]);
for ($i = 0; $i < sizeof($result['patientlost']); $i++) {
$timestamp = $result['patientlost'][$i]['date']->getTimestamp();
if ($result['ver'] < $timestamp) {
$result['ver'] = $timestamp;
}
$result['patientlost'][$i]['date'] = date_format($result['patientlost'][$i]['date'], 'Y-m-d H:i:s');
}
return new JsonResponse($result, 200);
}
/**
* @Route("/search", methods={"GET"})
*/
public function loadSearch(Request $request)
{
$jwt = $request->headers->get('Authorization');
if (!isset(explode('.', $jwt)[1])) {
return new JsonResponse(array('code'=>'400','message'=>'Invalid JWT Token'));
}
$caregiverId = json_decode(base64_decode(str_replace('_', '/', str_replace('-','+',explode('.', $jwt)[1]))))->id;
$result = [];
$result['tags'] = $this->getDoctrine()->getManager()->createQueryBuilder()
->from('App\Entity\Patient', 'p')
->join('App\Entity\PatientIbeacon', 'i', Join::WITH, 'i.patient = p.id')
->join('App\Entity\IbeaconLastSeen', 's', Join::WITH, 's.ibeacon = i.id')
->select("i.mac, s.lastAPILocation, s.lastAPISeenTimestamp")
->where('p.caregiver = :caregiver')
->andWhere('i.enabled = true')
->orderBy('s.updatedAt', 'DESC')
->addOrderBy('p.updatedAt', 'DESC')
->getQuery()
->execute([
'caregiver' => $caregiverId
]);
foreach ($result['tags'] as $key => $tag) {
$result['tags'][$key]['lastAPISeenTimestamp'] = (new \DateTime())->setTimestamp((int)$tag['lastAPISeenTimestamp'])->format('Y年m月d日 H:i:s');
}
return new JsonResponse($result, 200);
}
}