Merge branch '299-cmb-realtime-map' into '270-final-cmb-fixes'

Resolve "CMB - realtime map"

See merge request jankstudio/resq!354
This commit is contained in:
Kendrick Chan 2020-01-27 20:36:26 +00:00
commit 676bf40004
24 changed files with 940 additions and 302 deletions

View file

@ -17,6 +17,7 @@
"predis/predis": "^1.1",
"sensio/framework-extra-bundle": "^5.1",
"setasign/fpdf": "^1.8",
"symfony/asset": "^4.0",
"symfony/console": "^4.0",
"symfony/debug": "^4.0",
"symfony/filesystem": "^4.0",

60
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"content-hash": "4873ae3fd18db755bc9bf395bbbfb141",
"content-hash": "b101ecfbc1f6f2270f0e8ad326035b7e",
"packages": [
{
"name": "catalyst/auth-bundle",
@ -2411,6 +2411,62 @@
],
"time": "2016-01-01T17:47:15+00:00"
},
{
"name": "symfony/asset",
"version": "v4.4.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/asset.git",
"reference": "2c67c89d064bfb689ea6bc41217c87100bb94c17"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/asset/zipball/2c67c89d064bfb689ea6bc41217c87100bb94c17",
"reference": "2c67c89d064bfb689ea6bc41217c87100bb94c17",
"shasum": ""
},
"require": {
"php": "^7.1.3"
},
"require-dev": {
"symfony/http-foundation": "^3.4|^4.0|^5.0",
"symfony/http-kernel": "^3.4|^4.0|^5.0"
},
"suggest": {
"symfony/http-foundation": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.4-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Asset\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Asset Component",
"homepage": "https://symfony.com",
"time": "2020-01-04T13:00:46+00:00"
},
{
"name": "symfony/cache",
"version": "v4.3.1",
@ -5344,6 +5400,7 @@
"code",
"zf2"
],
"abandoned": "laminas/laminas-code",
"time": "2018-08-13T20:36:59+00:00"
},
{
@ -5398,6 +5455,7 @@
"events",
"zf2"
],
"abandoned": "laminas/laminas-eventmanager",
"time": "2018-04-25T15:33:34+00:00"
}
],

View file

@ -75,6 +75,7 @@ services:
App\Service\MQTTClient:
arguments:
$redis_client: "@App\\Service\\RedisClientProvider"
$key: "mqtt_events"
App\Service\APNSClient:
arguments:
@ -86,7 +87,6 @@ services:
$host: "%env(REDIS_CLIENT_HOST)%"
$port: "%env(REDIS_CLIENT_PORT)%"
$password: "%env(REDIS_CLIENT_PASSWORD)%"
$env_flag: "dev"
App\Service\GeofenceTracker:
arguments:
@ -204,3 +204,28 @@ services:
App\Service\GISManagerInterface: "@App\\Service\\GISManager\\OpenStreet"
#App\Service\GISManagerInterface: "@App\\Service\\GISManager\\Google"
App\EventListener\JobOrderActiveCacheListener:
arguments:
$jo_cache: "@App\\Service\\JobOrderCache"
$mqtt: "@App\\Service\\MQTTClient"
tags:
- name: 'doctrine.orm.entity_listener'
event: 'postUpdate'
entity: 'App\Entity\JobOrder'
- name: 'doctrine.orm.entity_listener'
event: 'postRemove'
entity: 'App\Entity\JobOrder'
- name: 'doctrine.orm.entity_listener'
event: 'postPersist'
entity: 'App\Entity\JobOrder'
App\Service\JobOrderCache:
arguments:
$redis_prov: "@App\\Service\\RedisClientProvider"
$active_jo_key: "%env(LOCATION_JO_ACTIVE_KEY)%"
App\Service\RiderCache:
arguments:
$redis_prov: "@App\\Service\\RedisClientProvider"
$loc_key: "%env(LOCATION_RIDER_ACTIVE_KEY)%"
$status_key: "%env(STATUS_RIDER_KEY)%"

View file

@ -0,0 +1,190 @@
class DashboardMap {
constructor(options, rider_markers, cust_markers) {
this.options = options;
this.rider_markers = rider_markers;
this.cust_markers = cust_markers;
// layer groups
this.layer_groups = {
'rider_available': L.layerGroup(),
'rider_active_jo': L.layerGroup(),
'customer': L.layerGroup()
};
}
initialize() {
// main map
this.map = L.map(this.options.div_id).setView(
[this.options.center_lat, this.options.center_lng],
this.options.zoom
);
// add tile layer
var streets = L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', {
attribution: 'Map data &copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>',
maxZoom: 18,
id: 'mapbox/streets-v11',
accessToken: this.options.access_token
}).addTo(this.map);
// layer groups
this.layer_groups.rider_available.addTo(this.map);
this.layer_groups.rider_active_jo.addTo(this.map);
this.layer_groups.customer.addTo(this.map);
// base layer
var baseMaps = {
'Streets': streets
};
// overlay layer
var overlayMaps = {
'Available Riders' : this.layer_groups.rider_available,
'JO Riders' : this.layer_groups.rider_active_jo,
'Customers' : this.layer_groups.customer
}
L.control.layers(baseMaps, overlayMaps).addTo(this.map);
return this.map;
}
putMarker(id, lat, lng, markers, icon, layer_group, popup_url) {
var my = this;
// existing marker
if (markers.hasOwnProperty(id)) {
markers[id].setLatLng(L.latLng(lat, lng));
return;
}
// new marker
markers[id] = L.marker(
[lat, lng],
{ icon: icon }
).bindPopup('Loading...')
.addTo(layer_group);
// bind ajax for popup
markers[id].on('click', function(e) {
var popup = e.target.getPopup();
var url = popup_url.replace('[id]', id);
console.log(url);
$.get(url).done(function(data) {
popup.setContent(data);
popup.update();
});
});
}
putCustomerMarker(id, lat, lng) {
this.putMarker(
id,
lat,
lng,
this.cust_markers,
this.options.icons.customer,
this.layer_groups.customer,
this.options.cust_popup_url
);
}
removeCustomerMarker(id) {
console.log('removing customer marker for ' + id);
var layer_group = this.layer_groups.customer;
var markers = this.cust_markers;
// no customer marker with that id
if (!markers.hasOwnProperty(id)) {
console.log('no such marker to remove');
return;
}
layer_group.removeLayer(markers[id]);
}
putRiderAvailableMarker(id, lat, lng) {
this.putMarker(
id,
lat,
lng,
this.rider_markers,
this.options.icons.rider_available,
this.layer_groups.rider_available,
this.options.rider_popup_url
);
}
putRiderActiveJOMarker(id, lat, lng) {
this.putMarker(
id,
lat,
lng,
this.rider_markers,
this.options.icons.rider_active_jo,
this.layer_groups.rider_active_jo,
this.options.rider_popup_url
);
}
loadLocations(location_url) {
console.log(this.rider_markers);
var my = this;
$.ajax({
url: location_url,
}).done(function(response) {
// clear all markers
my.layer_groups.rider_available.clearLayers();
my.layer_groups.rider_active_jo.clearLayers();
my.layer_groups.customer.clearLayers();
// get riders and job orders
var riders = response.riders;
var jos = response.jos;
// job orders
$.each(jos, function(id, data) {
var lat = data.latitude;
var lng = data.longitude;
my.putCustomerMarker(id, lat, lng);
});
// riders
$.each(riders, function(id, data) {
var lat = data.latitude;
var lng = data.longitude;
if (data.has_jo)
my.putRiderActiveJOMarker(id, lat, lng);
else
my.putRiderAvailableMarker(id, lat, lng);
});
/*
$.each(riders, function(rider_id, rider_data) {
// rider location
var point = rider_data['loc'];
var lat = point[0];
var lng = point[1];
// customer location
var cloc = rider_data['cust_loc'];
var clat = cloc[0];
var clng = cloc[1];
// create rider markers
if (rider_data['has_jo']) {
var jo_data = rider_data['jo'];
// my.putCustomerMarker(jo_data['id'], clat, clng);
my.putRiderActiveJOMarker(rider_id, lat, lng);
} else {
my.putRiderAvailableMarker(rider_id, lat, lng);
}
});
*/
// console.log(rider_markers);
});
}
}

View file

@ -0,0 +1,110 @@
class MapEventHandler {
constructor(options, dashmap) {
this.options = options;
this.dashmap = dashmap;
}
connect(user_id, host, port) {
var d = new Date();
var client_id = "dash-" + user_id + "-" + d.getMonth() + "-" + d.getDate() + "-" + d.getHours() + "-" + d.getMinutes() + "-" + d.getSeconds() + "-" + d.getMilliseconds();
console.log(client_id);
this.mqtt = new Paho.MQTT.Client(host, port, client_id);
var options = {
// useSSL: true,
timeout: 3,
invocationContext: this,
onSuccess: this.onConnect.bind(this),
};
this.mqtt.onMessageArrived = this.onMessage.bind(this);
console.log('connecting to mqtt server...');
this.mqtt.connect(options);
}
onConnect(icontext) {
console.log('mqtt connected!');
var my = icontext.invocationContext;
// subscribe to rider locations
console.log('subscribing to ' + my.options.channels.rider_location);
my.mqtt.subscribe(my.options.channels.rider_location);
// subscribe to jo locations
console.log('subscribing to ' + my.options.channels.jo_location);
my.mqtt.subscribe(my.options.channels.jo_location);
my.mqtt.subscribe(my.options.channels.jo_status);
}
onMessage(msg) {
// console.log(msg);
console.log('received message');
var channel = msg.destinationName;
var chan_split = channel.split('/');
var payload = msg.payloadString;
// handle different channels
switch (chan_split[0]) {
case "rider":
this.handleRider(chan_split, payload);
break;
case "jo":
this.handleJobOrder(chan_split, payload);
break;
}
}
handleRider(chan_split, payload) {
console.log("rider message");
switch (chan_split[2]) {
case "location":
console.log("got location for rider " + chan_split[1] + " - " + payload);
var pl_split = payload.split(':');
console.log(pl_split);
// check for correct format
if (pl_split.length != 2)
break;
var lat = parseFloat(pl_split[0]);
var lng = parseFloat(pl_split[1]);
this.dashmap.putRiderAvailableMarker(chan_split[1], lat, lng);
break;
}
}
handleJobOrder(chan_split, payload) {
console.log("jo message");
var id = chan_split[1];
switch (chan_split[2]) {
case "location":
// var my = this;
console.log("got location for jo " + id + " - " + payload);
var pl_split = payload.split(':');
// check for correct format
if (pl_split.length != 2)
break;
var lat = parseFloat(pl_split[0]);
var lng = parseFloat(pl_split[1]);
// move marker
console.log(lat + ' - ' + lng);
this.dashmap.putCustomerMarker(id, lat, lng);
break;
case "status":
switch (payload) {
case 'cancel':
case 'fulfill':
case 'delete':
this.dashmap.removeCustomerMarker(id);
break;
}
}
}
}

View file

@ -0,0 +1,68 @@
<?php
namespace App\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Doctrine\DBAL\Connection;
use Doctrine\Common\Persistence\ObjectManager;
use App\Service\JobOrderCache;
use App\Entity\JobOrder;
use App\Ramcar\JOStatus;
use DateTime;
class RefreshJobOrderCacheCommand extends Command
{
protected $em;
protected $jo_cache;
public function __construct(ObjectManager $om, JobOrderCache $jo_cache)
{
$this->em = $om;
$this->jo_cache = $jo_cache;
parent::__construct();
}
protected function configure()
{
$this->setName('joborder:refresh_cache')
->setDescription('Refresh active job order cache from database.')
->setHelp('Refresh active job order cache from database.');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$date = new DateTime();
$date->modify('-3 day');
$status_list = [
JOStatus::PENDING,
JOStatus::RIDER_ASSIGN,
JOStatus::ASSIGNED,
JOStatus::IN_TRANSIT,
JOStatus::IN_PROGRESS,
];
$qb = $this->em->getRepository(JobOrder::class)
->createQueryBuilder('jo');
$res = $qb->select('jo')
->where('jo.status IN (:statuses)')
->andWhere('jo.date_schedule >= :date')
->setParameter('statuses', $status_list, Connection::PARAM_STR_ARRAY)
->setParameter('date', $date)
->getQuery()
->execute();
// fulfill each
foreach ($res as $jo)
{
$this->jo_cache->addActiveJobOrder($jo);
}
}
}

View file

@ -9,6 +9,8 @@ use Doctrine\ORM\EntityManagerInterface;
use App\Service\RiderTracker;
use App\Service\GISManagerInterface;
use App\Service\JobOrderCache;
use App\Service\RiderCache;
use App\Entity\Rider;
@ -18,8 +20,11 @@ class HomeController extends Controller
/**
* @Menu(selected="home")
*/
public function index(EntityManagerInterface $em, RiderTracker $rider_tracker,
GISManagerInterface $gis_manager)
public function index(
EntityManagerInterface $em,
RiderTracker $rider_tracker,
GISManagerInterface $gis_manager
)
{
// get map
$params['map_js_file'] = $gis_manager->getJSInitFile();
@ -27,11 +32,34 @@ class HomeController extends Controller
return $this->render('home.html.twig', $params);
}
public function getRiderLocations(EntityManagerInterface $em, RiderTracker $rider_tracker)
public function getMapLocations(JobOrderCache $jo_cache)
{
// TODO: get active riders from cache
// TODO: get active JOs from cache
$active_jos = $jo_cache->getAllActiveJobOrders();
// get active JOs from cache
}
public function getRiderLocations(JobOrderCache $jo_cache, RiderCache $rider_cache, EntityManagerInterface $em, RiderTracker $rider_tracker)
{
// get active JOs from cache
$active_jos = $jo_cache->getAllActiveJobOrders();
$riders = $rider_cache->getAllActiveRiders();
// TODO: optimize this
// get all riders and figure out if they have active jos
foreach ($riders as $rider_id => $rider_data)
{
$rider = $em->getRepository(Rider::class)->find($rider_id);
$jo = $rider->getActiveJobOrder();
if ($jo == null)
$riders[$rider_id]['has_jo'] = false;
else
$riders[$rider_id]['has_jo'] = true;
}
// get active riders from cache
// get all riders
/*
$riders = $em->getRepository(Rider::class)->findAll();
$locations = [];
@ -81,9 +109,11 @@ class HomeController extends Controller
];
}
*/
return $this->json([
'riders' => $locations,
'jos' => $active_jos,
'riders' => $riders,
]);
}

View file

@ -28,6 +28,7 @@ use App\Ramcar\JOEventType;
use App\Service\InvoiceGeneratorInterface;
use App\Service\MQTTClient;
use App\Service\RedisClientProvider;
use App\Service\RiderCache;
use App\Entity\RiderSession;
use App\Entity\Customer;
@ -209,7 +210,7 @@ class RAPIController extends Controller
return $res->getReturnResponse();
}
public function login(Request $req, EncoderFactoryInterface $ef, RedisClientProvider $redis)
public function login(Request $req, EncoderFactoryInterface $ef, RedisClientProvider $redis, RiderCache $rcache)
{
$required_params = [
'user',
@ -251,6 +252,11 @@ class RAPIController extends Controller
$rider->setAvailable(true);
$rider_id = $rider->getID();
// cache rider location (default to hub)
// TODO: figure out longitude / latitude default
$rcache->addActiveRider($rider_id, 0, 0);
// TODO: log rider logging in
$em->flush();
@ -289,7 +295,7 @@ class RAPIController extends Controller
return $res->getReturnResponse();
}
public function logout(Request $req)
public function logout(Request $req, RiderCache $rcache)
{
$required_params = [];
$em = $this->getDoctrine()->getManager();
@ -301,6 +307,9 @@ class RAPIController extends Controller
$rider = $this->session->getRider();
$rider->setAvailable(false);
// remove from cache
$rcache->removeActiveRider($rider->getID());
// remove rider from session
$this->session->setRider(null);

View file

@ -0,0 +1,105 @@
<?php
namespace App\EventListener;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use App\Service\JobOrderCache;
use App\Ramcar\JOStatus;
use App\Entity\JobOrder;
class JobOrderActiveCacheListener
{
protected $key;
protected $mqtt;
public function __construct(JobOrderCache $jo_cache, $mqtt)
{
$this->jo_cache = $jo_cache;
$this->mqtt = $mqtt;
}
// when a new job order comes in
public function postPersist(JobOrder $jo, LifecycleEventArgs $args)
{
$status = $jo->getStatus();
switch ($status)
{
// active
case JOStatus::PENDING:
case JOStatus::RIDER_ASSIGN:
case JOStatus::ASSIGNED:
case JOStatus::IN_TRANSIT:
case JOStatus::IN_PROGRESS:
$this->processActiveJO($jo);
break;
// inactive
case JOStatus::CANCELLED:
$this->processInactiveJO($jo, 'cancel');
break;
case JOStatus::FULFILLED:
$this->processInactiveJO($jo, 'fulfill');
break;
}
}
// when a job order is updated
public function postUpdate(JobOrder $jo, LifecycleEventArgs $args)
{
$status = $jo->getStatus();
switch ($status)
{
// active
case JOStatus::PENDING:
case JOStatus::RIDER_ASSIGN:
case JOStatus::ASSIGNED:
case JOStatus::IN_TRANSIT:
case JOStatus::IN_PROGRESS:
$this->processActiveJO($jo);
break;
// inactive
case JOStatus::CANCELLED:
$this->processInactiveJO($jo, 'cancel');
break;
case JOStatus::FULFILLED:
$this->processInactiveJO($jo, 'fulfill');
break;
}
}
// when a job order is deleted
public function postRemove(JobOrder $jo, LifecycleEventArgs $args)
{
$this->processInactiveJO($jo, 'delete');
}
protected function processActiveJO($jo)
{
// save in cache
$this->jo_cache->addActiveJobOrder($jo);
// publish to mqtt
$coords = $jo->getCoordinates();
// TODO: do we put the key in config?
$this->mqtt->publish(
'jo/' . $jo->getID() . '/location',
$coords->getLatitude() . ':' . $coords->getLongitude()
);
}
protected function processInactiveJO($jo, $status = 'cancel')
{
// remove from redis cache
$this->jo_cache->removeActiveJobOrder($jo);
// TODO: publich to mqtt
$this->mqtt->publish(
'jo/' . $jo->getID() . '/status',
$status
);
}
}

View file

@ -0,0 +1,66 @@
<?php
namespace App\Service;
use App\Service\RedisClientProvider;
use App\Entity\JobOrder;
class JobOrderCache
{
protected $redis;
protected $active_jo_key;
public function __construct(RedisClientProvider $redis_prov, $active_jo_key)
{
$this->redis = $redis_prov->getRedisClient();
$this->active_jo_key = $active_jo_key;
}
public function addActiveJobOrder(JobOrder $jo)
{
$coords = $jo->getCoordinates();
$this->redis->geoadd(
$this->active_jo_key,
$coords->getLongitude(),
$coords->getLatitude(),
$jo->getID()
);
}
public function getAllActiveJobOrders()
{
$all_jo = $this->redis->georadius(
$this->active_jo_key,
0,
0,
41000,
'km',
['WITHCOORD' => true]
);
$jo_locs = [];
foreach ($all_jo as $jo_data)
{
$id = $jo_data[0];
$lng = $jo_data[1][0];
$lat = $jo_data[1][1];
$jo_locs[$id] = [
'longitude' => $lng,
'latitude' => $lat,
];
}
// error_log(print_r($all_jo, true));
return $jo_locs;
}
public function removeActiveJobOrder(JobOrder $jo)
{
$this->redis->zrem(
$this->active_jo_key,
$jo->getID()
);
}
}

View file

@ -164,7 +164,7 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
$row['delivery_address'] = $orow->getDeliveryAddress();
$row['date_schedule'] = $orow->getDateSchedule()->format("d M Y g:i A");
$row['type'] = $orow->isAdvanceOrder() ? 'Advanced Order' : 'Immediate';
$row['service_type'] = $service_types[$orow->getServiceType()];
$row['service_type'] = $service_types[$orow->getServiceType()] ?? 'Unknown';
$row['status'] = $statuses[$orow->getStatus()];
$row['flag_advance'] = $orow->isAdvanceOrder();
$row['plate_number'] = $orow->getCustomerVehicle()->getPlateNumber();

View file

@ -9,14 +9,15 @@ class MQTTClient
{
const PREFIX = 'motolite.control.';
const RIDER_PREFIX = 'motorider_';
const REDIS_KEY = 'events';
// protected $mclient;
protected $redis;
protected $key;
public function __construct(RedisClientProvider $redis_client)
public function __construct(RedisClientProvider $redis_client, $key)
{
$this->redis = $redis_client->getRedisClient();
$this->key = $key;
}
public function __destruct()
@ -29,7 +30,7 @@ class MQTTClient
// $this->mclient->publish($channel, $message);
$data = $channel . '|' . $message;
$this->redis->lpush(self::REDIS_KEY, $data);
$this->redis->lpush($this->key, $data);
}
public function sendEvent(JobOrder $job_order, $payload)

View file

@ -11,34 +11,46 @@ class RedisClientProvider
protected $host;
protected $port;
protected $password;
protected $env_flag;
public function __construct($scheme, $host, $port, $password, $env_flag)
public function __construct($scheme, $host, $port, $password)
{
$this->scheme = $scheme;
$this->host = $host;
$this->port = $port;
$this->password = $password;
$this->env_flag = $env_flag;
$this->redis = null;
$this->connect();
}
protected function connect()
{
// already connected
if ($this->redis != null)
return $this->redis;
// if password is specified attempt connection
if (strlen($this->password) > 0)
{
$this->redis = new PredisClient([
"scheme" => $this->scheme,
"host" => $this->host,
"port" => $this->port,
"password" => $this->password]);
return $this->redis;
}
$this->redis = new PredisClient([
"scheme" => $this->scheme,
"host" => $this->host,
"port" => $this->port]);
return $this->redis;
}
public function getRedisClient()
{
if ($this->env_flag == 'dev')
{
$this->redis = new PredisClient([
"scheme"=>$this->scheme,
"host"=>$this->host,
"port"=>$this->port]);
}
else
{
$this->redis = new PredisClient([
"scheme"=>$this->scheme,
"host"=>$this->host,
"port"=>$this->port,
"password"=>$this->password]);
}
return $this->redis;
}
}

View file

@ -0,0 +1,78 @@
<?php
namespace App\Service;
use App\Service\RedisClientProvider;
use App\Entity\Rider;
class RiderCache
{
protected $redis;
protected $loc_key;
protected $status_key;
public function __construct(RedisClientProvider $redis_prov, $loc_key, $status_key)
{
$this->redis = $redis_prov->getRedisClient();
$this->loc_key = $loc_key;
$this->status_key = $status_key;
}
public function addActiveRider($id, $lat, $lng)
{
$coords = $jo->getCoordinates();
$this->redis->geoadd(
$this->loc_key,
$lng,
$lat,
$id
);
}
public function getAllActiveRiders()
{
$all_riders = $this->redis->georadius(
$this->loc_key,
0,
0,
41000,
'km',
['WITHCOORD' => true]
);
$locs = [];
foreach ($all_riders as $data)
{
$id = $data[0];
$lng = $data[1][0];
$lat = $data[1][1];
$locs[$id] = [
'longitude' => $lng,
'latitude' => $lat,
];
}
// error_log(print_r($all_riders, true));
return $locs;
}
public function removeActiveRider($id)
{
$this->redis->zrem(
$this->loc_key,
$id
);
}
public function incJobOrderCount($id, $status)
{
$this->redis->hincrby($this->status_key, $id, 1);
}
public function decJobOrderCount($id, $status)
{
$this->redis->hincrby($this->status_key, $id, -1);
}
}

View file

@ -152,6 +152,9 @@
"setasign/fpdf": {
"version": "1.8.1"
},
"symfony/asset": {
"version": "v4.4.3"
},
"symfony/cache": {
"version": "v4.0.2"
},

View file

@ -13,111 +13,74 @@
{% block scripts %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.min.js" type="text/javascript"></script>
<script src="{{ asset('assets/js/dashboard_map.js') }}"></script>
<script src="{{ asset('assets/js/map_mqtt.js') }}"></script>
{{ include('map/' ~ map_js_file) }}
<script>
var map;
function initMap() {
function initMap(r_markers, c_markers, icons) {
var default_lat = {% trans %}default_lat{% endtrans %};
var default_lng = {% trans %}default_long{% endtrans %};
var rider_popup_url = '/riders/[id]/popup';
var cust_popup_url = '/job-order/[id]/popup';
map = mapCreate('dashboard_map', default_lat, default_lng, 'road', 13, rider_popup_url, cust_popup_url);
}
</script>
{{ include('map/' ~ map_js_file) }}
<script>
// TODO: put this in .env
var mqtt;
var timeout = 2000;
var host = '{{ mqtt_host }}';
var port = {{ mqtt_port }};
var icon_rider_available = L.divIcon({
className: 'map-div-icon',
html: "<div style='background-color:#00FF00;' class='marker-pin'></div><i class='fa fa-bolt awesome'>",
iconSize: [39, 42],
iconAnchor: [15, 42]
});
function onConnect() {
console.log('connected!');
mqtt.subscribe('rider/+/location');
}
function onMessage(msg) {
// console.log(msg);
console.log('received message');
var channel = msg.destinationName;
var chan_split = channel.split('/');
var payload = msg.payloadString;
// handle different channels
switch (chan_split[0]) {
case "rider":
handleRider(chan_split, payload);
break;
}
}
function handleRider(chan_split, payload) {
console.log("rider message");
switch (chan_split[2]) {
case "location":
console.log("got location for rider " + chan_split[1] + " - " + payload);
pl_split = payload.split(':');
console.log(pl_split);
// check for correct format
if (pl_split.length != 2)
break;
var lat = parseFloat(pl_split[0]);
var lng = parseFloat(pl_split[1]);
// move marker
console.log(rider_markers[chan_split[1]]);
// check if marker exists
if (rider_markers.hasOwnProperty(chan_split[1])) {
// marker's there, move it
rider_markers[chan_split[1]].setLatLng(L.latLng(lat, lng));
} else {
// no marker, make one
console.log('creating marker');
// TODO: make it add to the correct map layer
rider_markers[chan_split[1]]= L.marker([lat, lng], { icon: icon_rider_available }).addTo(map);;
}
break;
}
}
function mqttConnect() {
var d = new Date();
var client_id = "dash-{{ app.user.getID }}-" + d.getMonth() + "-" + d.getDate() + "-" + d.getHours() + "-" + d.getMinutes() + "-" + d.getSeconds() + "-" + d.getMilliseconds();
console.log(client_id);
mqtt = new Paho.MQTT.Client(host, port, client_id);
var options = {
useSSL: true,
timeout: 3,
onSuccess: onConnect,
'access_token': 'pk.eyJ1Ijoia2NvcmRlcm8iLCJhIjoiY2szbzA3ZHdsMDZxdTNsbGl4ZGNnN2VxaSJ9.LRzAe3RlV8sIP1N1x0chdw',
'div_id': 'dashboard_map',
'center_lat': default_lat,
'center_lng': default_lng,
'map_type': 'road',
'zoom': 13,
'rider_popup_url': '/riders/[id]/popup',
'cust_popup_url': '/job-order/[id]/popup',
'icons': icons
};
mqtt.onMessageArrived = onMessage;
var dashmap = new DashboardMap(options, r_markers, c_markers);
dashmap.initialize();
dashmap.loadLocations('{{ path('rider_locations') }}');
console.log('connecting to mqtt server...');
mqtt.connect(options);
return dashmap;
}
mqttConnect();
function initEventHandler(dashmap) {
var options = {
'channels': {
'rider_location': 'rider/+/location',
'jo_location': 'jo/+/location',
'jo_status': 'jo/+/status'
},
};
var event_handler = new MapEventHandler(options, dashmap);
event_handler.connect('{{ app.user.getID }}', '{{ mqtt_host }}', {{ mqtt_port }});
}
// create icons
var icons = {
'rider_active_jo': L.divIcon({
className: 'map-div-icon',
html: "<div style='background-color:#FF0000;' class='marker-pin'></div><i class='fa fa-bolt awesome'>",
iconSize: [39, 42],
iconAnchor: [15, 42]
}),
'rider_available': L.divIcon({
className: 'map-div-icon',
html: "<div style='background-color:#00FF00;' class='marker-pin'></div><i class='fa fa-bolt awesome'>",
iconSize: [39, 42],
iconAnchor: [15, 42]
}),
'customer': L.divIcon({
className: 'map-div-icon',
html: "<div style='background-color:#0055FF;' class='marker-pin'></div><i class='fa fa-user awesome'>",
iconSize: [39, 42],
iconAnchor: [15, 42]
})
};
var r_markers = {};
var c_markers = {};
var dashmap = initMap(r_markers, c_markers, icons);
initEventHandler(dashmap, icons);
</script>
{% endblock %}

View file

@ -2,142 +2,3 @@
integrity="sha512-gZwIG9x3wUXg2hdXF6+rVkLF/0Vi9U8D2Ntg4Ga5I5BZpVkVxlJWbSQtXPSiUTtC0TjtGOmxa1AJPuV0CPthew=="
crossorigin="">
</script>
<script>
var rider_markers = {};
var cust_markers = {};
function mapCreate(div_id, center_lat, center_lng, map_type, zoom, rider_popup_url, cust_popup_url) {
var map = L.map(div_id).setView(
[center_lat, center_lng],
zoom
);
// add tile layer
// TODO: put access token in .env
var streets = L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', {
attribution: 'Map data &copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>',
maxZoom: 18,
id: 'mapbox/streets-v11',
accessToken: 'pk.eyJ1Ijoia2NvcmRlcm8iLCJhIjoiY2szbzA3ZHdsMDZxdTNsbGl4ZGNnN2VxaSJ9.LRzAe3RlV8sIP1N1x0chdw'
}).addTo(map);
// layer groups
// .addTo(map) --> this will display your riders by default
var lg_avail_rider = L.layerGroup().addTo(map);
var lg_jo_rider = L.layerGroup().addTo(map);
var lg_cust = L.layerGroup().addTo(map);
// this little snippet will not display your riders by default.
// Instead, a toggle button will display in the map, with a checkbox with text Riders.
// Check that to display the riders
//var ridersLayerGroup = L.layerGroup();
// create icons
var icon_rider_active_jo = L.divIcon({
className: 'map-div-icon',
html: "<div style='background-color:#FF0000;' class='marker-pin'></div><i class='fa fa-bolt awesome'>",
iconSize: [39, 42],
iconAnchor: [15, 42]
});
var icon_rider_available = L.divIcon({
className: 'map-div-icon',
html: "<div style='background-color:#00FF00;' class='marker-pin'></div><i class='fa fa-bolt awesome'>",
iconSize: [39, 42],
iconAnchor: [15, 42]
});
var icon_customer = L.divIcon({
className: 'map-div-icon',
html: "<div style='background-color:#0055FF;' class='marker-pin'></div><i class='fa fa-user awesome'>",
iconSize: [39, 42],
iconAnchor: [15, 42]
});
$.ajax({
url: '{{ path('rider_locations') }}',
}).done(function(response) {
// clear all markers
lg_avail_rider.clearLayers();
lg_jo_rider.clearLayers();
lg_cust.clearLayers();
// get riders and mark
var riders = response.riders;
$.each(riders, function(rider_id, rider_data) {
// rider location
var point = rider_data['loc'];
var lat = point[0];
var long = point[1];
// customer location
var cloc = rider_data['cust_loc'];
var clat = cloc[0];
var clong = cloc[1];
// rider popup content
rider_popup = '<strong>' + rider_data['label'] + '</strong>';
// create rider markers
if (rider_data['has_jo']) {
var jo_data = rider_data['jo'];
// rider_markers[rider_id] = L.marker([lat, long], { icon: icon_rider_active_jo }).bindPopup(rider_popup);
rider_markers[rider_id] = L.marker([lat, long], { icon: icon_rider_active_jo }).bindPopup('Loading...');
// var cust_marker = L.marker([clat, clong], { icon: icon_customer }).bindPopup('Loading...');
cust_markers[jo_data['id']] = L.marker([clat, clong], { icon: icon_customer }).bindPopup('Loading...');
lg_cust.addLayer(cust_markers[jo_data['id']]);
lg_jo_rider.addLayer(rider_markers[rider_id]);
// customer popup ajax
cust_markers[jo_data['id']].on('click', function(e) {
var popup = e.target.getPopup();
var url = cust_popup_url.replace('[id]', jo_data['id']);
console.log(url);
$.get(url).done(function(data) {
popup.setContent(data);
popup.update();
});
});
} else {
// rider_markers[rider_id]= L.marker([lat, long], { icon: icon_rider_available }).bindPopup(rider_popup);
rider_markers[rider_id]= L.marker([lat, long], { icon: icon_rider_available }).bindPopup('Loading...');
lg_avail_rider.addLayer(rider_markers[rider_id]);
}
// ajax loading of rider popup
rider_markers[rider_id].on('click', function(e) {
var popup = e.target.getPopup();
var url = rider_popup_url.replace('[id]', rider_id);
console.log(url);
$.get(url).done(function(data) {
popup.setContent(data);
popup.update();
});
});
});
// console.log(rider_markers);
});
// base layer
var baseMaps = {
'Streets': streets
};
// overlay layer
var overlayMaps = {
'Available Riders' : lg_avail_rider,
'JO Riders' : lg_jo_rider,
'Customers' : lg_cust
}
L.control.layers(baseMaps, overlayMaps).addTo(map);
return map;
}
initMap();
</script>

View file

@ -26,4 +26,4 @@ default_lat: 14.6091
default_long: 121.0223
#default_lat: 3.084216
#default_long: 101.6129996
default_region: my
default_region: ph

View file

@ -0,0 +1,41 @@
import paho.mqtt.client as mqtt
import ssl
import redis
import time
import signal
import sys
import os
import json
class RiderLocationCache(object):
def run(self, client):
print "running loop..."
client.loop_forever()
# TODO: fix this and put these guys back under the class
def init_subscriptions(client):
print "subscribing to rider/+/location"
client.subscribe('rider/+/location')
def on_connect(client, userdata, flags, rc):
init_subscriptions(client)
#print("Connected with result code "+str(rc))
# client.subscribe("$SYS/#")
def on_publish(client, userdata, mid):
pass
def on_message(client, userdata, message):
redis_conn = userdata['redis']
topic_split = message.topic.split('/')
if topic_split[0] != 'rider':
return;
payload_split = message.payload.split(':')
rider_long = str(payload_split[1])
rider_lat = str(payload_split[0])
# set the location
redis_conn.geoadd('loc_rider_active', rider_long, rider_lat, topic_split[1])

Binary file not shown.

View file

@ -0,0 +1,26 @@
import paho.mqtt.client as mqtt
import rider_location_cache as rlc
import ssl
import redis
import logging
redis_client = redis.StrictRedis(host='localhost', port=6379, db=0)
userdata = {'redis': redis_client}
client = mqtt.Client("", True, userdata)
client.on_connect = rlc.on_connect
# client.on_publish = on_publish
client.on_message = rlc.on_message
#client.tls_set(
# "/etc/letsencrypt/live/resqaws.jankstudio.com/fullchain.pem", cert_reqs=ssl.CERT_NONE,
# tls_version=ssl.PROTOCOL_TLSv1)
#client.tls_set(
# "/root/aws_ssl_keys/fullchain.pem", cert_reqs=ssl.CERT_NONE,
# tls_version=ssl.PROTOCOL_TLSv1)
#client.connect("resqaws.jankstudio.com", 8883, 60)
client.connect("localhost", 1883, 60)
rider_location = rlc.RiderLocationCache()
rider_location.run(client)

View file

@ -0,0 +1,12 @@
[Unit]
Description=Rider Location Cache Service
After=mosquitto.service redis.service
[Service]
Type=simple
ExecStart=/usr/bin/python /root/www/resq/utils/rider_location_cache/riderloc.py
StandardInput=tty-force
Restart=always
[Install]
WantedBy=multi-user.target

View file

@ -1,7 +1,6 @@
import paho.mqtt.client as mqtt
import yaml
import ssl
from threading import Thread
from daemonize import Daemonize
import redis
import time
import signal
@ -10,29 +9,23 @@ import os
import logging
def sigint_handler(signal, frame):
#logging.warning('Interrupted')
sys.exit(0)
os._exit(0)
# TODO: yaml configuration file for redis and mqtt settings
def on_connect(client, userdata, flags, rc):
#logging.info("Connected with result code "+str(rc))
client.subscribe("$SYS/#")
logging.info("Connected with result code "+str(rc))
#client.subscribe("$SYS/#")
def on_publish(client, userdata, mid):
pass
def getRedis(i, client, logger):
def redis_listen(client, logger):
logger.info("Listening in redis events")
r = redis.StrictRedis(host='localhost', port=6379, db=0)
while 1:
time.sleep(0)
data = r.brpop("events", 10)
data = r.brpop("mqtt_events", 10)
if data:
info = data[1].split('|')
logger.info("Channel: " + info[0] + " message: " + info[1])
@ -40,9 +33,6 @@ def getRedis(i, client, logger):
def sigint_handler(signal, frame):
sys.exit(0)
def get_logger():
logger = logging.getLogger("mqtt_logger")
logger.setLevel(logging.INFO)
@ -64,25 +54,14 @@ def main():
client.on_connect = on_connect
client.on_publish = on_publish
client.tls_set(
"/etc/letsencrypt/live/resqaws.jankstudio.com/fullchain.pem", cert_reqs=ssl.CERT_NONE,
tls_version=ssl.PROTOCOL_TLSv1)
# configure mqtt broker to accept localhost
client.connect("localhost", 1883, 60)
client.connect("resqaws.jankstudio.com", 8883, 60)
client.loop_start()
redis_listen(client, logger)
client.loop_end()
logger.info("Starting redis thread")
t = Thread(target=getRedis, args=(1, client, logger))
t.start()
signal.signal(signal.SIGINT, sigint_handler)
client.loop_forever()
#client.loop_forever()
#logging.basicConfig(filename='/tmp/mqtt_sender.log', level=logging.INFO)
#logging.info('Started mqtt_sender')
#pid = "/tmp/mqtt_sender.pid"
#daemon = Daemonize(app="mqtt_sender", pid=pid, action=main)
#daemon.start()
main()

View file

@ -12,9 +12,9 @@ client.on_message = rlc.on_message
#client.tls_set(
# "/etc/letsencrypt/live/resqaws.jankstudio.com/fullchain.pem", cert_reqs=ssl.CERT_NONE,
# tls_version=ssl.PROTOCOL_TLSv1)
client.tls_set(
"/root/aws_ssl_keys/fullchain.pem", cert_reqs=ssl.CERT_NONE,
tls_version=ssl.PROTOCOL_TLSv1)
#client.tls_set(
# "/root/aws_ssl_keys/fullchain.pem", cert_reqs=ssl.CERT_NONE,
# tls_version=ssl.PROTOCOL_TLSv1)
#client.connect("resqaws.jankstudio.com", 8883, 60)
client.connect("localhost", 8883, 60)