diff --git a/composer.json b/composer.json index e118abfc..7219d070 100644 --- a/composer.json +++ b/composer.json @@ -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", diff --git a/composer.lock b/composer.lock index b3630199..a1172379 100644 --- a/composer.lock +++ b/composer.lock @@ -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" } ], diff --git a/config/services.yaml b/config/services.yaml index 4249102e..0b7669fd 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -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)%" diff --git a/public/assets/js/dashboard_map.js b/public/assets/js/dashboard_map.js new file mode 100644 index 00000000..57d3c7a3 --- /dev/null +++ b/public/assets/js/dashboard_map.js @@ -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 © OpenStreetMap contributors, CC-BY-SA, Imagery © Mapbox', + 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); + }); + } +} diff --git a/public/assets/js/map_mqtt.js b/public/assets/js/map_mqtt.js new file mode 100644 index 00000000..ebd401bb --- /dev/null +++ b/public/assets/js/map_mqtt.js @@ -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; + } + } + } +} diff --git a/src/Command/RefreshJobOrderCacheCommand.php b/src/Command/RefreshJobOrderCacheCommand.php new file mode 100644 index 00000000..cdf904d2 --- /dev/null +++ b/src/Command/RefreshJobOrderCacheCommand.php @@ -0,0 +1,68 @@ +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); + } + } +} diff --git a/src/Controller/HomeController.php b/src/Controller/HomeController.php index 3a3e5173..ca5654b4 100644 --- a/src/Controller/HomeController.php +++ b/src/Controller/HomeController.php @@ -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, ]); } diff --git a/src/Controller/RAPIController.php b/src/Controller/RAPIController.php index c5d629c8..ce8631fd 100644 --- a/src/Controller/RAPIController.php +++ b/src/Controller/RAPIController.php @@ -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); diff --git a/src/EventListener/JobOrderActiveCacheListener.php b/src/EventListener/JobOrderActiveCacheListener.php new file mode 100644 index 00000000..541ce158 --- /dev/null +++ b/src/EventListener/JobOrderActiveCacheListener.php @@ -0,0 +1,105 @@ +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 + ); + } +} + diff --git a/src/Service/JobOrderCache.php b/src/Service/JobOrderCache.php new file mode 100644 index 00000000..488db29d --- /dev/null +++ b/src/Service/JobOrderCache.php @@ -0,0 +1,66 @@ +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() + ); + } +} diff --git a/src/Service/JobOrderHandler/CMBJobOrderHandler.php b/src/Service/JobOrderHandler/CMBJobOrderHandler.php index 4803e1ec..50c504fc 100644 --- a/src/Service/JobOrderHandler/CMBJobOrderHandler.php +++ b/src/Service/JobOrderHandler/CMBJobOrderHandler.php @@ -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(); diff --git a/src/Service/MQTTClient.php b/src/Service/MQTTClient.php index aa0932a3..c9c7a4f2 100644 --- a/src/Service/MQTTClient.php +++ b/src/Service/MQTTClient.php @@ -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) diff --git a/src/Service/RedisClientProvider.php b/src/Service/RedisClientProvider.php index 04ac1f80..80dd06fa 100644 --- a/src/Service/RedisClientProvider.php +++ b/src/Service/RedisClientProvider.php @@ -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; } } diff --git a/src/Service/RiderCache.php b/src/Service/RiderCache.php new file mode 100644 index 00000000..399a63f2 --- /dev/null +++ b/src/Service/RiderCache.php @@ -0,0 +1,78 @@ +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); + } +} diff --git a/symfony.lock b/symfony.lock index 88d10d33..576e49fd 100644 --- a/symfony.lock +++ b/symfony.lock @@ -152,6 +152,9 @@ "setasign/fpdf": { "version": "1.8.1" }, + "symfony/asset": { + "version": "v4.4.3" + }, "symfony/cache": { "version": "v4.0.2" }, diff --git a/templates/home.html.twig b/templates/home.html.twig index f55fe863..3bcb6c3b 100644 --- a/templates/home.html.twig +++ b/templates/home.html.twig @@ -13,111 +13,74 @@ {% block scripts %} + + +{{ include('map/' ~ map_js_file) }} - -{{ include('map/' ~ map_js_file) }} - - - {% endblock %} diff --git a/templates/map/initOpenStreetMap.js b/templates/map/initOpenStreetMap.js index 358b91d4..8eab44bd 100644 --- a/templates/map/initOpenStreetMap.js +++ b/templates/map/initOpenStreetMap.js @@ -2,142 +2,3 @@ integrity="sha512-gZwIG9x3wUXg2hdXF6+rVkLF/0Vi9U8D2Ntg4Ga5I5BZpVkVxlJWbSQtXPSiUTtC0TjtGOmxa1AJPuV0CPthew==" crossorigin=""> - - diff --git a/translations/messages.en.yaml b/translations/messages.en.yaml index 04ebdc42..992f8de9 100644 --- a/translations/messages.en.yaml +++ b/translations/messages.en.yaml @@ -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 diff --git a/utils/mqtt_rider/rider_location_cache.py b/utils/mqtt_rider/rider_location_cache.py new file mode 100644 index 00000000..b07d4acc --- /dev/null +++ b/utils/mqtt_rider/rider_location_cache.py @@ -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]) diff --git a/utils/mqtt_rider/rider_location_cache.pyc b/utils/mqtt_rider/rider_location_cache.pyc new file mode 100644 index 00000000..e8968436 Binary files /dev/null and b/utils/mqtt_rider/rider_location_cache.pyc differ diff --git a/utils/mqtt_rider/riderloc.py b/utils/mqtt_rider/riderloc.py new file mode 100644 index 00000000..2b810cca --- /dev/null +++ b/utils/mqtt_rider/riderloc.py @@ -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) diff --git a/utils/mqtt_rider/riderloc.service b/utils/mqtt_rider/riderloc.service new file mode 100644 index 00000000..f3d43264 --- /dev/null +++ b/utils/mqtt_rider/riderloc.service @@ -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 diff --git a/utils/mqtt_sender/mqtt_sender.py b/utils/mqtt_sender/mqtt_sender.py index 54349755..f610b3db 100644 --- a/utils/mqtt_sender/mqtt_sender.py +++ b/utils/mqtt_sender/mqtt_sender.py @@ -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() diff --git a/utils/rider_location_cache/riderloc.py b/utils/rider_location_cache/riderloc.py index 26a1a92f..ca7776b1 100644 --- a/utils/rider_location_cache/riderloc.py +++ b/utils/rider_location_cache/riderloc.py @@ -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)