Untitled

 avatar
user_3839718
php
2 years ago
74 kB
3
Indexable
<?php
namespace App\Controller;

use App\Model\RobotManager;
use App\Model\RoundManager;
use App\Model\AlertManager;
use App\Model\CurvesManager;
use App\Model\ElasticSearchManager;
use App\Helper\SignatureHelper;
use Laminas\View\Model\JsonModel;
use App\Model\UserManager;
use Laminas\ServiceManager\ServiceManager;
use App\File\AwsS3;
use Spatie\Async\Pool;
use Throwable;

/**
 * This controller is responsible for robot management
 */
class RobotController extends \App\Controller\AbstractBaseController
{
    const LATEST = 'latest';
    const INTERVAL = 'interval';
    const HOURLY_OVERVIEW = 'hourly_overview';
    const HOURLY = 'hour';
    const DAILY = 'day';
    const WEEKLY = 'week';
    const ROUND = 'round';
    const NEW_DAY = 'new_day';
    const TIME_OF_DAY = 'time_of_day';
    const HEAT_MAP = 'heatmap';
    const GRAPH = 'graph';
    private $s3FileSystem;
    private $robotManager;
    private $userManager;
    private $curvesManager;
    private $alertManager;
    private $roundManager;
    private $elasticSearchManager;
    private $images;

    /**
     * Constructor.
     */
    public function __construct(ServiceManager $container) {
        parent::__construct($container);
        $this?->robotManager = $container?->get(RobotManager::class);
        $this?->roundManager = $container?->get(RoundManager::class);
        $this?->userManager = $container?->get(UserManager::class);
        $this?->curvesManager = $container?->get(CurvesManager::class);
        $this?->alertManager = $container?->get(AlertManager::class);
        $this?->elasticSearchManager = $container?->get(ElasticSearchManager::class);
        $this?->s3FileSystem = $container?->get(AwsS3::class);
    }


    // this one return the entity of one robot
    public function returnRobotAction() {
        try {
            // we check the user session
            $identity = $this?->identity();
            if (!isset($identity)) {
                return $this?->handleError(401, 'User not registered or not logged in');
            }
            // we check if all params are here
            $robotId = $this?->params()?->fromQuery('robotId', -1);

            // we check if the user has permision to do this
            $select = ['u.id' => $identity['id'], 'r.id' => $robotId];
            $role = $this?->robotManager?->getUserRoleInEntity($select, 'r.id');
            if (!$this?->isReadable($role)) {
                return $this?->handleError(403, 'You do not have sufficient permissions to access this functionality');
            }

            $isAdmin = $this?->isAdmin($identity);
            $robot = $this?->robotManager?->returnRobotView($robotId, $identity, $isAdmin);
            return new JsonModel([
                'data' => $robot
            ]);
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    // this one gives you all the robots you have related to you account
    public function getRobotsViewAction() {
        try {
            // we check the user session
            $identity = $this?->identity();
            if (!isset($identity)) {
                return $this?->handleError(401, 'User not registered or not logged in');
            }

            $offset = $this?->params()?->fromQuery('offset', 0);
            $limit = $this?->params()?->fromQuery('limit', 5);
            $search = $this?->params()?->fromQuery('search', null);
            $status = $this?->params()?->fromQuery('status', '1');
            $param = [
                'offset' => $offset,
                'limit' => $limit,
                'search' => $search,
                'status' => $status
            ];
            $isAdmin = $this?->isAdmin($identity);
            $robots = $this?->robotManager?->fetch($identity['id'], $param, $isAdmin);
            return new JsonModel([
                'robots' => $robots,
            ]);
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    // this endpoint return all the users that are related to a robot
    public function getRobotUsersAction() {
        try {
            // we check the user session
            $identity = $this?->identity();
            if (!isset($identity)) {
                return $this?->handleError(401, 'User not registered or not logged in');
            }
            // we check if all params are here
            $robotId = $this?->params()?->fromQuery('robotId', -1);
            if ($robotId == -1) {
                return $this?->handleError(400, 'You need to send robotId');
            }
            // we check if the user has permision to do this
            $select = ['u.id' => $identity['id'], 'r.id' => $robotId];
            $role = $this?->robotManager?->getUserRoleInEntity($select, 'r.id');
            if (!$this?->isReadable($role)) {
                return $this?->handleError(403, 'You do not have sufficient permissions to access this functionality');
            }

            return new JsonModel([
                'users' => $this?->robotManager?->usersAssociatedToMyRobots($robotId),
            ]);

        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    // This endpoint gives us the cameras, the values for frontend tables of the status page
    // and also the alerts we have on those tables and observables
    public function getRobotStatusPageDataAction() {
        try {
            // we check the user session
            $identity = $this?->identity();
            if (!isset($identity)) {
                return $this?->handleError(401, 'User not registered or not logged in');
            }

            // we check if all params are here
            $robotId = $this?->params()?->fromQuery('robotId', -1);
            $houseId = $this?->params()?->fromQuery('houseId', -1);
            if ($robotId == -1 && $houseId == -1) {
                return $this?->handleError(400, 'You need to send robotId or houseId');
            }

            $byTime = false;
            if ($houseId !== -1) {
                $robotId = $this?->robotManager?->getRobotsInHouse($houseId)?->current()?->robot_id;
            } else if ($this?->robotManager?->returnRobot('id', $robotId)?->robot_type == 'cd') {
                $byTime = true;
            }

            // we check if the user has permision to do this
            $select = ['u.id' => $identity['id'], 'r.id' => $robotId];
            $role = $this?->robotManager?->getUserRoleInEntity($select, 'r.id');
            if (!$this?->isReadable($role)) {
                return $this?->handleError(403, 'You do not have sufficient permissions to access this functionality');
            }

            $roundId = $this?->robotManager?->getRobotRound($robotId)?->id;
            $cameraDetails = $this?->getRobotCameraFromConfig($robotId);
            $cameras = [];
            if ($cameraDetails != null) {
                foreach ($cameraDetails?->{'camera-details'}?->children() as $cameraDetail) {
                    $camera = [];
                    $camera['name'] = $cameraDetail?->getName();
                    $camera['id'] = (string) $cameraDetail["id"];
                    array_push($cameras, $camera);
                }
            }
            $alerts = $this?->alertManager?->alertsAfterLastLoginbyRobot($identity, $robotId);
            $observableAlerts = [];
            foreach ($alerts as $alert) {
                isset($this?->config['alert_ids']['ambient_conditions'][$alert['alert_id']]) ? array_push($observableAlerts, $this?->config['alert_ids']['ambient_conditions'][$alert['alert_id']]) : null;
                isset($this?->config['alert_ids']['health_welfare'][$alert['alert_id']]) ? array_push($observableAlerts, $this?->config['alert_ids']['health_welfare'][$alert['alert_id']]) : null;
                isset($this?->config['alert_ids']['equipment'][$alert['alert_id']]) ? array_push($observableAlerts, $this?->config['alert_ids']['equipment'][$alert['alert_id']]) : null;
            }
            return new JsonModel([
                'cameras' => $cameras,
                'currentValues' => $this?->robotManager?->latestValue($roundId, $robotId, $byTime),
                'yesterdayValues' => $this?->robotManager?->yesterdayValues($roundId, $robotId, $byTime),
                'lastRoundValues' => $this?->robotManager?->lastRoundValues($roundId, $robotId),
                'observableAlerts' => array_count_values($observableAlerts),
            ]);

        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    // This endpoint gives us all the data from the robot and his status
    public function getRobotStatusLayoutDataAction() {
        try {
            // we check the user session
            $identity = $this?->identity();
            if (!isset($identity)) {
                return $this?->handleError(401, 'User not registered or not logged in');
            }
            // we check if all params are here
            $robotId = $this?->params()?->fromQuery('robotId', -1);
            if ($robotId == -1) {
                return $this?->handleError(400, 'You need to send robotId');
            }
            // we check if the user has permision to do this
            $select = ['u.id' => $identity['id'], 'r.id' => $robotId];
            $role = $this?->robotManager?->getUserRoleInEntity($select, 'r.id');
            if (!$this?->isReadable($role)) {
                return $this?->handleError(403, 'You do not have sufficient permissions to access this functionality');
            }
            // here is when we get all the data
            $params = $this?->getRobotStatusLayoutData($identity, $robotId);
            return new JsonModel($params);
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    // This endpoint gives us all the data from the robot and his status 
    // including laser, light or lift configuration
    public function getRobotStatusAction() {
        try {
            // we check the user session
            $identity = $this?->identity();
            if (!isset($identity)) {
                return $this?->handleError(401, 'User not registered or not logged in');
            }
            // we check if all params are here
            $robotId = $this?->params()?->fromQuery('robotId', -1);
            if ($robotId == -1) {
                return $this?->handleError(400, 'You need to send robotId');
            }
            // we check if the user has permision to do this
            $select = ['u.id' => $identity['id'], 'r.id' => $robotId];
            $role = $this?->robotManager?->getUserRoleInEntity($select, 'r.id');
            if (!$this?->isReadable($role)) {
                return $this?->handleError(403, 'You do not have sufficient permissions to access this functionality');
            }

            $getRobotStatus = $this?->robotManager?->returnRobotStatus($identity, $robotId);
            return new JsonModel([
                'robotType' => $getRobotStatus?->robot_type ?? 'cb',
                'lastUpdate' => $this?->robotManager?->getLastRobotPing($robotId) ?? null,
                'battery' => $getRobotStatus?->battery ?? 0,
                'location' => $getRobotStatus?->location ?? '[0,0,0]',
                'estimatedRecharge' => $getRobotStatus?->remaining_recharge_time ?? '0',
                'batteryTime' => $getRobotStatus?->battery_time ?? '0',
                'operationalState' => $getRobotStatus?->operational_state ?? null,
                'onCharger' => $getRobotStatus?->on_charger ?? null,
                'restartDate' => $getRobotStatus?->restart_date ?? null,
                'errorMsg' => $getRobotStatus?->error_msg ?? null,
                'status' => $getRobotStatus?->status ?? null,
                'action' => $getRobotStatus?->robot_action ?? null,
                'laser' => $getRobotStatus?->laser ?? 'off',
                'light' => $getRobotStatus?->light ?? 'off',
                'lift' => $getRobotStatus?->lift ?? 'manual',
            ]);
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    // this endpoint gives to us all observables settings from a robot, 
    // also information about the controls
    public function getRobotSettingsAction() {
        try {
            // we check the user session
            $identity = $this?->identity();
            if (!isset($identity)) {
                return $this?->handleError(401, 'User not registered or not logged in');
            }
            // we check if all params are here
            $robotId = $this?->params()?->fromQuery('robotId', -1);
            if ($robotId == -1) {
                return $this?->handleError(400, 'You need to send robotId');
            }
            // we check if the user has permision to do this
            $select = ['u.id' => $identity['id'], 'r.id' => $robotId];
            $role = $this?->robotManager?->getUserRoleInEntity($select, 'r.id');
            if (!$this?->isReadable($role)) {
                return $this?->handleError(403, 'You do not have sufficient permissions to access this functionality');
            }

            $robotSettings = json_decode($this?->robotManager?->getRobotSettings($robotId, $identity['id']));
            $robotObs = $this?->robotManager?->returnRobotAvailableObservables($robotId);
            $controlsConfig = $this?->robotManager?->getRobotControlsFromConfig($robotId);
            $controls = [];

            if (!empty($controlsConfig)) {
                foreach ($controlsConfig?->{'control-details'}?->children() as $controlsDetail) {
                    $controls[$controlsDetail?->getName()] = (string) $controlsDetail["enabled"];
                }
            }

            $roundId = $this?->robotManager?->getRobotRound($robotId)?->id;

            return new JsonModel([
                'settings' => $robotSettings,
                'availableObservables' => $robotObs,
                'availableControls' => $controls,
                'siUnit' => $this?->roundManager?->getSiUnit($roundId)
            ]);
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    // Return to the user the dimensions of the house stored on the config file of the robot
    public function getHouseDimensionsAction() {
        try {
            // we check the user session
            $identity = $this?->identity();
            if (!isset($identity)) {
                return $this?->handleError(401, 'User not registered or not logged in');
            }
            // we check if all params are here
            $robotId = $this?->params()?->fromQuery('robotId', -1);
            if ($robotId == -1) {
                return $this?->handleError(400, 'You need to send robotId');
            }
            // we check if the user has permision to do this
            $select = ['u.id' => $identity['id'], 'r.id' => $robotId];
            $role = $this?->robotManager?->getUserRoleInEntity($select, 'r.id');
            if (!$this?->isReadable($role)) {
                return $this?->handleError(403, 'You do not have sufficient permissions to access this functionality');
            }
            return new JsonModel(
                $this?->getHouseDimensions($robotId)
            );
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    // This endpoint returns the configuration file of the robot
    public function getConfigurationFileAction() {
        try {
            // we check the user session
            $identity = $this?->identity();
            if (!isset($identity)) {
                return $this?->handleError(401, 'User not registered or not logged in');
            }
            // we check if all params are here
            $robotId = $this?->params()?->fromQuery('robotId', -1);
            if ($robotId == -1) {
                return $this?->handleError(400, 'You need to send robotId');
            }
            // we check if the user has permision to do this
            $select = ['u.id' => $identity['id'], 'r.id' => $robotId];
            $role = $this?->robotManager?->getUserRoleInEntity($select, 'r.id');
            if (!$this?->isReadable($role)) {
                return $this?->handleError(403, 'You do not have sufficient permissions to access this functionality');
            }
            $configXml = $this?->robotManager?->getConfigurationFile($robotId);
            return new JsonModel([
                'configuration' => $configXml
            ]);
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    // this function gives tu us the rails of a robot configuration
    public function getRobotRailsAction() {
        try {
            // we check the user session
            $identity = $this?->identity();
            if (!isset($identity)) {
                return $this?->handleError(401, 'User not registered or not logged in');
            }
            // we check if all params are here
            $robotId = $this?->params()?->fromQuery('robotId', -1);
            if ($robotId == -1) {
                return $this?->handleError(400, 'You need to send robotId');
            }
            // we check if the user has permision to do this
            $select = ['u.id' => $identity['id'], 'r.id' => $robotId];
            $role = $this?->robotManager?->getUserRoleInEntity($select, 'r.id');
            if (!$this?->isReadable($role)) {
                return $this?->handleError(403, 'You do not have sufficient permissions to access this functionality');
            }
            $configFile = $this?->robotManager?->getConfigurationFile($robotId);
            $xml = simplexml_load_string($configFile);
            $result = [
                'offset' => $xml?->path?->{'offset'},
                'sections' => $xml?->path?->{'sections'},
                'path-configuration' => $xml?->path?->{'path-configuration'},
            ];
            return new JsonModel([
                'data' => $result
            ]);
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    // this endpoint is the one that connects to ES and aws S3 and return to you the images 
    // related to a robot and an observable type
    public function getGalleryImagesObservableAction() {
        try {
            // we check the user session
            $identity = $this?->identity();
            if (!isset($identity)) {
                return $this?->handleError(401, 'User not registered or not logged in');
            }
            // we check if all params are here
            $robotId = $this?->params()?->fromQuery('robotId', -1);
            $observableName = $this?->params()?->fromQuery('observableName', -1);
            $offset = $this?->params()?->fromQuery('offset', -1);
            $limit = $this?->params()?->fromQuery('limit', -1);
            if ($robotId == -1 || $observableName == -1 || $limit == -1 || $offset == -1) {
                return $this?->handleError(400, 'You need to send robotId, observable name, offset and limit');
            }
            $camera = $this?->params()?->fromQuery('camera', null);
            $from = $this?->params()?->fromQuery('from', null);
            $to = $this?->params()?->fromQuery('to', null);
            // we check if the user has permision to do this
            $select = ['u.id' => $identity['id'], 'r.id' => $robotId];
            $role = $this?->robotManager?->getUserRoleInEntity($select, 'r.id');
            if (!$this?->isReadable($role)) {
                return $this?->handleError(403, 'You do not have sufficient permissions to access this functionality');
            }

            $end = false;
            $anomalies = [
                'deadBirds' => 'dead_birds',
                'badDroppings' => 'bad_droppings',
                'brokenDrinkers' => 'wet_spots', # bad naming from the way the file is saved before correcting think of backward compatibility
            ];
            $roundId = $this?->robotManager?->getRobotRound($robotId)?->id;
            $param = $this?->getObjectsForObservablesElasticSearch($roundId, $anomalies[$observableName], $camera, $offset, $limit, $from, $to);

            if ($param['images'] !== null) {
                if (count($param['images']) == 0 || count($param['images']) < $limit) {
                    $end = true;
                }
            } else {
                $end = true;
            }
            return new JsonModel([
                'images' => $param['images'] ?? [],
                'key' => $param['key'],
                'end' => $end,
                'observableName' => $observableName
            ]);
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    // Endpoint that returns a heatmap with an hourly overview for each day of
    // prod for an observable and a robot
    public function getHourMapAction() {
        try {
            // we check the user session
            $identity = $this?->identity();
            if (!isset($identity)) {
                return $this?->handleError(401, 'User not registered or not logged in');
            }
            // we check if all params are here
            $observableName = $this?->params()?->fromQuery('observableName', -1);
            $roundId = $this?->params()?->fromQuery('roundId', -1);
            if ($roundId == -1 || $observableName == -1) {
                return $this?->handleError(400, 'You need to send roundId and observable name');
            }
            // we check if the user has permision to do this
            $select = ['u.id' => $identity['id'], 'round.id' => $roundId];
            $role = $this?->roundManager?->getUserRoleInEntity($select, 'round.id');
            if (!$this?->isReadable($role)) {
                return $this?->handleError(403, 'You do not have sufficient permissions to access this functionality');
            }

            $data = $this?->robotManager?->getFrontEndData($roundId, self::HEAT_MAP, self::HOURLY_OVERVIEW, $observableName);
            $data = $data?->data ?? null;
            return new JsonModel([
                'data' => $data,
                'siUnitType' => $this?->roundManager?->getSiUnit($roundId)
            ]);
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    // Endpoint that returns a lineplot of a 
    // prod for an observable, a robot, an event type and a time
    public function getGraphDataAction() {
        try {
            // we check the user session
            $identity = $this?->identity();
            if (!isset($identity)) {
                return $this?->handleError(401, 'User not registered or not logged in');
            }
            // we check if all params are here
            $observableName = $this?->params()?->fromQuery('observableName', -1);
            $roundId = $this?->params()?->fromQuery('roundId', -1);
            $eventType = $this?->params()?->fromQuery('type', -1);
            $time = $this?->params()?->fromQuery('time', -1);
            if ($roundId == -1 || $observableName == -1 || $time == -1 || $eventType == -1) {
                return $this?->handleError(400, 'You need to send roundId, observable name, event type and time');
            }
            // we check if the user has permision to do this
            $select = ['u.id' => $identity['id'], 'round.id' => $roundId];
            $role = $this?->roundManager?->getUserRoleInEntity($select, 'round.id');
            if (!$this?->isReadable($role)) {
                return $this?->handleError(403, 'You do not have sufficient permissions to access this functionality');
            }

            switch ($eventType) {
                case self::LATEST:
                    $data = $this?->robotManager?->getFrontEndData($roundId, self::GRAPH, self::LATEST, $observableName);
                    break;
                case self::ROUND:
                    $data = $this?->robotManager?->getFrontEndData($roundId, self::GRAPH, self::ROUND, $observableName);
                    break;
                case self::INTERVAL:
                    if ($time == self::HOURLY) {
                        $data = $this?->robotManager?->getFrontEndData($roundId, self::GRAPH, self::HOURLY, $observableName);
                    } elseif ($time == self::TIME_OF_DAY) {
                        $data = $this?->robotManager?->getFrontEndData($roundId, self::GRAPH, self::TIME_OF_DAY, $observableName);
                    } elseif ($time == self::DAILY) {
                        $data = $this?->robotManager?->getFrontEndData($roundId, self::GRAPH, self::NEW_DAY, $observableName);
                    } else {
                        $data = $this?->robotManager?->getFrontEndData($roundId, self::GRAPH, self::WEEKLY, $observableName);
                    }
                    break;
                case self::TIME_OF_DAY:
                    $data = $this?->robotManager?->getFrontEndData($roundId, self::GRAPH, self::TIME_OF_DAY, $observableName);
                    break;
                case self::NEW_DAY:
                    $data = $this?->robotManager?->getFrontEndData($roundId, self::GRAPH, self::NEW_DAY, $observableName);
                    break;
            }
            $data = $data?->data ?? null;
            $targets_raw = json_decode($this?->curvesManager?->getRoundTarget($roundId));

            return new JsonModel([
                'data' => $data,
                'target_raw' => $targets_raw,
                'siUnitType' => $this?->roundManager?->getSiUnit($roundId),
            ]);
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    // Endpoint that returns a heatmap of a 
    // prod for an observable, a robot, an event type 
    public function getHeatMapDataAction() {
        try {
            // we check the user session
            $identity = $this?->identity();
            if (!isset($identity)) {
                return $this?->handleError(401, 'User not registered or not logged in');
            }
            // we check if all params are here
            $roundId = $this?->params()?->fromQuery('roundId', -1);
            $observableName = $this?->params()?->fromQuery('observableName', -1);
            $eventType = $this?->params()?->fromQuery('type', -1);
            if ($roundId == -1 || $observableName == -1 || $eventType == -1) {
                return $this?->handleError(400, 'You need to send roundId, observable name, event type and time');
            }
            // we check if the user has permision to do this
            $select = ['u.id' => $identity['id'], 'round.id' => $roundId];
            $role = $this?->roundManager?->getUserRoleInEntity($select, 'round.id');
            if (!$this?->isReadable($role)) {
                return $this?->handleError(403, 'You do not have sufficient permissions to access this functionality');
            }

            switch ($eventType) {
                case self::LATEST:
                    $data = $this?->robotManager?->getFrontEndData($roundId, self::HEAT_MAP, self::LATEST, $observableName);
                    break;
                case self::ROUND:
                    $data = $this?->robotManager?->getFrontEndData($roundId, self::HEAT_MAP, self::ROUND, $observableName);
                    break;
                case self::TIME_OF_DAY:
                    $data = $this?->robotManager?->getFrontEndData($roundId, self::HEAT_MAP, self::TIME_OF_DAY, $observableName);
                    break;
                case self::NEW_DAY:
                    $data = $this?->robotManager?->getFrontEndData($roundId, self::HEAT_MAP, self::NEW_DAY, $observableName);
                    break;
            }

            $data = $data?->data ?? null;

            return new JsonModel([
                'z' => $data,
                'dayOfProduction' => $data?->date ?? null,
                'siUnitType' => $this?->roundManager?->getSiUnit($roundId)
            ]);
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    // this function validates a Mater token from a robot
    // you dont need to have a session to validate it
    public function validateMasterTokenAction() {
        try {
            $data = $this?->getRequest()?->getPost();
            // filter content from user
            $filter = new \Laminas\I18n\Filter\Alnum();
            $masterToken = $filter?->filter($data['masterToken']);
            $isMasterTokenValid = $this?->robotManager?->isMasterTokenValid($masterToken);

            return new JsonModel([
                'isMasterTokenValid' => $isMasterTokenValid,
            ]);
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    // this endpoint check if a user is able to associate a robot
    // if they exist or if they have the robot 
    public function validateUserBeforeAssociationAction() {
        try {
            // we check the user session
            $identity = $this?->identity();
            if (!isset($identity)) {
                return $this?->handleError(401, 'User not registered or not logged in');
            }
            // we check if all params are here
            $robotId = $this?->params()?->fromQuery('robotId', -1);
            if ($robotId == -1) {
                return $this?->handleError(400, 'You need to send robotId');
            }
            // we check if the user has permision to do this
            $select = ['u.id' => $identity['id'], 'r.id' => $robotId];
            $role = $this?->robotManager?->getUserRoleInEntity($select, 'r.id');
            if (!$this?->isReadable($role)) {
                return $this?->handleError(403, 'You do not have sufficient permissions to access this functionality');
            }
            $data = $this?->getRequest()?->getPost();
            // this input should be filtered
            $userId = $data['userId'];
            $id = $this?->userManager?->returnUser('id', $userId);
            $email = $this?->userManager?->returnUser('email', $userId);
            $user = $id || $email;
            if (!$user) {
                return new JsonModel([
                    'type' => 'error',
                    'text' => 'User does not exist'
                ]);
            }
            $checkUserExistsInRobot = $this?->robotManager?->getUserInRobot($data['userId'], $robotId);
            if (isset($checkUserExistsInRobot)) {
                return new JsonModel([
                    'type' => 'error',
                    'text' => 'User is already linked with this robot.'
                ]);
            }
            return new JsonModel([
                'type' => 'success',
                'user' => $user
            ]);
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    // this is the endpoint that confirm that you are associated 
    // to new robot
    public function confirmRobotAssociationApiAction() {
        try {
            // we check the user session
            $identity = $this?->identity();
            if (!isset($identity)) {
                return $this?->handleError(401, 'User not registered or not logged in');
            }
            $key = $this?->config['signatureHelper']['key'];
            $signedUrl = $this?->getRequest()?->getUriString();
            $request = $this?->params()?->fromQuery('request', null);
            $userId = substr($request, 0, 7);
            $roboId = substr($request, 7, 7);
            $fmId = substr($request, 14, 7);
            $role = substr($request, 21, 1);
            // if the user is not the same as the one we want to confirm
            if ($identity['id'] != $userId || $signedUrl == null) {
                return $this?->handleError(400, 'The link is not for this user');
            } elseif (!SignatureHelper::isValid($signedUrl, $key)) {
                return $this?->handleError(400, 'The link has expired');
            } else {
                if (!is_null($this?->robotManager?->getUserInRobot($userId, $roboId))) {
                    return $this?->handleError(400, 'You already have that robot');
                } else {
                    $this?->robotManager?->confirmUserAdded($userId, $roboId, $fmId, $role);
                    return new JsonModel([
                        'text' => 'You have successfully added the robot.',
                        'redirectUrl' => '/robot/view-robots',
                        'type' => 'success'
                    ]);
                }
            }
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }

    // here we the robot to a farm manager and send an email to the user
    public function addFarmManagerToRobotAction() {
        try {

            if ($this?->getRequest()?->isPost()) {
                // we check the method
                $data = $this?->getRequest()?->getPost();
                // we check the user session
                $identity = $this?->identity();
                if (!isset($identity)) {
                    return $this?->handleError(401, 'User not registered or not logged in');
                }
                // filter content from user
                $filter = new \Laminas\I18n\Filter\Alnum();
                $masterToken = $filter?->filter($data['masterToken']);
                // add farm manager to robot
                $this?->robotManager?->addFarmManagerToRobot($identity['id'], $masterToken);
                return new JsonModel([
                    'success' => True,
                ]);
            } else {
                return $this?->handleError(405, 'You need to send a POST request');
            }
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    // return all the robots status of the system
    public function getRobotsStatusAction() {
        try {
            // we check the user session
            $identity = $this?->identity();
            if (!isset($identity)) {
                return $this?->handleError(401, 'User not registered or not logged in');
            }
            // we check if the user has permision to do this
            if (!$this?->isAdmin($identity)) {
                return $this?->handleError(403, 'You do not have sufficient permissions to access this functionality');
            }
            $robotsStatus = $this?->robotManager?->returnRobotsStatus($identity);
            return new JsonModel([
                'data' => $robotsStatus,
            ]);
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    public function exportParamsAction() {
        try {
            // we check the user session
            $identity = $this?->identity();
            if (!isset($identity)) {
                return $this?->handleError(401, 'User not registered or not logged in');
            }
            // we check if all params are here
            $robotId = $this?->params()?->fromQuery('robotId', -1);
            if ($robotId == -1) {
                return $this?->handleError(400, 'You need to send robotId');
            }

            $isAdmin = $this?->isAdmin($identity);
            $select = ['u.id' => $identity['id'], 'r.id' => $robotId];
            $role = $this?->robotManager?->getUserRoleInEntity($select, 'r.id');
            $param = [
                'offset' => 0,
                'limit' => 99999,
                'robotId' => $robotId,
                'range' => 'false',
                'status' => 'notDeleted',
                'exportable' => (!$this?->isWritable($role)) ? '1' : null
            ];
            $rounds = $this?->roundManager?->fetch($identity['id'], $param, $isAdmin);

            // Pending to implement - Return observables that have permissions to be exported if farm manager 
            // want to restrict it like for the prod cycles or we have some "internal" observables that don't want to allow user to downlaod it but us, yes
            // For now we return ALL observable added to the robot, and filtering by exportable ones
            $allObs = $this?->robotManager?->returnRobotAvailableObservables($robotId);
            $filterObservableName = new \Laminas\Filter\Whitelist(['list' => $this?->config['exportable_observables']]);
            $observables = [];
            foreach ($allObs as $obs) {
                if ($filterObservableName($obs) !== null) {
                    array_push($observables, $obs);
                }
            }

            return new JsonModel([
                'rounds' => $rounds,
                'observables' => $observables
            ]);
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    public function addRobotAction() {
        try {
            if ($this?->getRequest()?->isPost()) {
                // we check the user session
                $identity = $this?->identity();
                if (!isset($identity)) {
                    return $this?->handleError(401, 'User not registered or not logged in');
                }
                // we check if the user has permision to do this
                if (!$this?->isAdmin($identity)) {
                    return $this?->handleError(403, 'You do not have sufficient permissions to access this functionality');
                }
                $robotId = '';
                $data = $this?->getRequest()?->getPost();
                $robotId = $this?->robotManager?->addRobot($data, $identity);
                return new JsonModel([
                    'text' => 'User successfully created a new robot.',
                    'type' => 'success',
                    'redirectUrl' => '/admin/assembly-order/' . $robotId
                ]);
            }
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    public function associateUserToRobotAction() {
        try {
            if ($this?->getRequest()?->isPost()) {
                // we check the user session
                $identity = $this?->identity();
                if (!isset($identity)) {
                    return $this?->handleError(401, 'User not registered or not logged in');
                }

                // we check if all params are here
                $robotId = $this?->params()?->fromQuery('robotId', -1);
                if ($robotId == -1) {
                    return $this?->handleError(400, 'You need to send robotId');
                }
                $select = ['u.id' => $identity['id'], 'r.id' => $robotId];
                $role = $this?->robotManager?->getUserRoleInEntity($select, 'r.id');
                if (!$this?->isReadable($role)) {
                    return $this?->handleError(403, 'You do not have sufficient permissions to access this functionality');
                }

                $data = $this?->getRequest()?->getPost();
                if ($data['type'] == 'email') {
                    $data['userId'] = $this?->userManager?->returnUser('email', $data['userId'])?->id;
                }
                $this?->robotManager?->addUserToRobot($identity, $robotId, $data);
                return new JsonModel([
                    'success' => True,
                ]);
            } else {
                return $this?->handleError(405, 'You need to send a POST request');
            }

        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    public function updateConfigurationFileAction() {
        try {
            // we check the method
            if ($this?->getRequest()?->isPost()) {
                // we check the user session
                $identity = $this?->identity();
                if (!isset($identity)) {
                    return $this?->handleError(401, 'User not registered or not logged in');
                }
                // we check if all params are here
                $robotId = $this?->params()?->fromQuery('robotId', -1);
                if ($robotId == -1) {
                    return $this?->handleError(400, 'You need to send robotId');
                }
                $select = ['u.id' => $identity['id'], 'r.id' => $robotId];
                $role = $this?->robotManager?->getUserRoleInEntity($select, 'r.id');
                if (!$this?->isWritable($role)) {
                    return $this?->handleError(403, 'You do not have sufficient permissions to access this functionality');
                }

                // update robot
                $data = $this?->getRequest()?->getPost();
                $this?->robotManager?->configuration($robotId, $data, $identity['id']);

                return new JsonModel([
                    'success' => True,
                ]);
            } else {
                return $this?->handleError(405, 'You need to send a POST request');
            }
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    public function editRobotSettingsAction() {
        try {
            // we check the method
            if ($this?->getRequest()?->isPost()) {
                // we check the user session
                $identity = $this?->identity();
                if (!isset($identity)) {
                    return $this?->handleError(401, 'User not registered or not logged in');
                }
                // we check if all params are here
                $robotId = $this?->params()?->fromQuery('robotId', -1);
                if ($robotId == -1) {
                    return $this?->handleError(400, 'You need to send robotId');
                }
                $select = ['u.id' => $identity['id'], 'r.id' => $robotId];
                $role = $this?->robotManager?->getUserRoleInEntity($select, 'r.id');
                if (!$this?->isReadable($role)) {
                    return $this?->handleError(403, 'You do not have sufficient permissions to access this functionality');
                }

                $data = $this?->getRequest()?->getPost();
                $this?->robotManager?->updateRobotSettings($robotId, $data, $identity);

                return new JsonModel([
                    'success' => true
                ]);

            } else {
                return $this?->handleError(405, 'You need to send a POST request');
            }
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    public function associateHouseToRobotAction() {
        try {
            // we check the method
            if ($this?->getRequest()?->isPost()) {
                // we check the user session
                $identity = $this?->identity();
                if (!isset($identity)) {
                    return $this?->handleError(401, 'User not registered or not logged in');
                }
                // we check if all params are here
                $robotId = $this?->params()?->fromQuery('robotId', -1);
                if ($robotId == -1) {
                    return $this?->handleError(400, 'You need to send robotId');
                }
                $select = ['u.id' => $identity['id'], 'r.id' => $robotId];
                $role = $this?->robotManager?->getUserRoleInEntity($select, 'r.id');
                if (!$this?->isReadable($role)) {
                    return $this?->handleError(403, 'You do not have sufficient permissions to access this functionality');
                }
                // update robot
                $data = $this?->getRequest()?->getPost();
                $this?->robotManager?->moveRobotToNewHouse($data, $robotId, $identity);

                return new JsonModel([
                    'success' => True,
                ]);
            } else {
                return $this?->handleError(405, 'You need to send a POST request');
            }
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    public function updateRobotAction() {
        try {
            // we check the method
            if ($this?->getRequest()?->isPost()) {
                // we check the user session
                $identity = $this?->identity();
                if (!isset($identity)) {
                    return $this?->handleError(401, 'User not registered or not logged in');
                }
                // we check if all params are here
                $robotId = $this?->params()?->fromQuery('robotId', -1);
                if ($robotId == -1) {
                    return $this?->handleError(400, 'You need to send robotId');
                }
                $select = ['u.id' => $identity['id'], 'r.id' => $robotId];
                $role = $this?->robotManager?->getUserRoleInEntity($select, 'r.id');
                if (!$this?->isWritable($role)) {
                    return $this?->handleError(403, 'You do not have sufficient permissions to access this functionality');
                }

                // update robot
                $data = $this?->getRequest()?->getPost();
                $this?->robotManager?->updateRobot($data, $robotId, $identity);

                return new JsonModel([
                    'success' => True,
                ]);
            } else {
                return $this?->handleError(405, 'You need to send a POST request');
            }
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    public function updateLaserStatusAction() {
        try {
            // we check the method
            if ($this?->getRequest()?->isPost()) {
                // we check the user session
                $identity = $this?->identity();
                if (!isset($identity)) {
                    return $this?->handleError(401, 'User not registered or not logged in');
                }
                // we check if all params are here
                $laser = $this?->params()?->fromQuery('laser', -1);
                $robotId = $this?->params()?->fromQuery('robotId', -1);
                if ($robotId == -1 || $laser == -1) {
                    return $this?->handleError(400, 'You need to send robotId and laser');
                }

                $select = ['u.id' => $identity['id'], 'r.id' => $robotId];
                $role = $this?->robotManager?->getUserRoleInEntity($select, 'r.id');
                if (!$this?->isWritable($role)) {
                    return $this?->handleError(403, 'You do not have sufficient permissions to access this functionality');
                }
                $filterAction = new \Laminas\Filter\Whitelist(['list' => ['off', 'auto']]);
                $laser = $filterAction($laser) ?? 'off';
                $response = $this?->robotManager?->updateLaserStatus($robotId, $laser);
                return new JsonModel([
                    'success' => $response
                ]);
            } else {
                return $this?->handleError(405, 'You need to send a POST request');
            }
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    public function updateActionStatusAction() {
        try {
            // we check the method
            if ($this?->getRequest()?->isPost()) {
                // we check the user session
                $identity = $this?->identity();
                if (!isset($identity)) {
                    return $this?->handleError(401, 'User not registered or not logged in');
                }
                // we check if all params are here
                $robotId = $this?->params()?->fromQuery('robotId', -1);
                $action = $this?->params()?->fromQuery('action', -1);
                if ($robotId == -1 || $action == -1) {
                    return $this?->handleError(400, 'You need to send robotId and action');
                }
                $select = ['u.id' => $identity['id'], 'r.id' => $robotId];
                $role = $this?->robotManager?->getUserRoleInEntity($select, 'r.id');
                if (!$this?->isWritable($role)) {
                    return $this?->handleError(403, 'You do not have sufficient permissions to access this functionality');
                }
                $filterAction = new \Laminas\Filter\Whitelist(['list' => ['start', 'stop', 'gohome']]);
                $action = $filterAction($action);
                $response = $this?->robotManager?->updateActionStatus($robotId, $action);
                return new JsonModel([
                    'success' => $response
                ]);
            } else {
                return $this?->handleError(405, 'You need to send a POST request');
            }
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    public function updateLightStatusAction() {
        try {
            // we check the method
            if ($this?->getRequest()?->isPost()) {
                // we check the user session
                $identity = $this?->identity();
                if (!isset($identity)) {
                    return $this?->handleError(401, 'User not registered or not logged in');
                }
                // we check if all params are here
                $light = $this?->params()?->fromQuery('light', -1);
                $robotId = $this?->params()?->fromQuery('robotId', -1);
                if ($robotId == -1 || $light == -1) {
                    return $this?->handleError(400, 'You need to send robotId and light');
                }
                $select = ['u.id' => $identity['id'], 'r.id' => $robotId];
                $role = $this?->robotManager?->getUserRoleInEntity($select, 'r.id');
                if (!$this?->isWritable($role)) {
                    return $this?->handleError(403, 'You do not have sufficient permissions to access this functionality');
                }
                $filterAction = new \Laminas\Filter\Whitelist(['list' => ['off', 'auto']]);
                $light = $filterAction($light) ?? 'off';
                $response = $this?->robotManager?->updateLightStatus($robotId, $light);
                return new JsonModel([
                    'success' => $response
                ]);
            } else {
                return $this?->handleError(405, 'You need to send a POST request');
            }
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    public function updateLiftStatusAction() {
        try {
            // we check the method
            if ($this?->getRequest()?->isPost()) {
                // we check the user session
                $identity = $this?->identity();
                if (!isset($identity)) {
                    return $this?->handleError(401, 'User not registered or not logged in');
                }
                // we check if all params are here
                $lift = $this?->params()?->fromQuery('lift', -1);
                $robotId = $this?->params()?->fromQuery('robotId', -1);
                if ($robotId == -1 || $lift == -1) {
                    return $this?->handleError(400, 'You need to send robotId and lift');
                }

                $select = ['u.id' => $identity['id'], 'r.id' => $robotId];
                $role = $this?->robotManager?->getUserRoleInEntity($select, 'r.id');
                if (!$this?->isWritable($role)) {
                    return $this?->handleError(403, 'You do not have sufficient permissions to access this functionality');
                }
                $filterAction = new \Laminas\Filter\Whitelist(['list' => ['manual', 'auto']]);
                $lift = $filterAction($lift) ?? 'manual';
                $response = $this?->robotManager?->updateLiftStatus($robotId, $lift);
                return new JsonModel([
                    'success' => $response
                ]);
            } else {
                return $this?->handleError(405, 'You need to send a POST request');
            }
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    public function deleteApiKeyAction() {
        try {
            // we check the user session
            $identity = $this?->identity();
            if (!isset($identity)) {
                return $this?->handleError(401, 'User not registered or not logged in');
            }
            // we check if all params are here
            $key = $this?->params()?->fromQuery('key', -1);
            if ($key == -1) {
                return $this?->handleError(400, 'You need to send key');
            }

            $owner = $this?->robotManager?->checkAPIKeyOwner($identity['id'], $key);
            if (!$owner) {
                return $this?->handleError(403, 'You do not have sufficient permissions to access this functionality');
            }
            $this?->robotManager?->deleteAPIKey($key);
            return new JsonModel([
                'text' => 'You have successfully deleted your API Key.',
                'type' => 'success',
                'redirectUrl' => '/export-data' //Keep user same page, name to be defined
            ]);
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    public function generateRobotApiKeyAction() {
        try {
            // we check the user session
            $identity = $this?->identity();
            if (!isset($identity)) {
                return $this?->handleError(401, 'User not registered or not logged in');
            }
            // we check if all params are here
            $robotId = $this?->params()?->fromQuery('robotId', -1);
            if ($robotId == -1) {
                return $this?->handleError(400, 'You need to send robotId');
            }

            $select = ['u.id' => $identity['id'], 'r.id' => $robotId];
            $role = $this?->robotManager?->getUserRoleInEntity($select, 'r.id');
            if (!$this?->isReadable($role)) {
                return $this?->handleError(403, 'You do not have sufficient permissions to access this functionality');
            }
            $APIKey = $this?->robotManager?->generateAPIKey($identity['id'], $robotId);
            return new JsonModel([
                'key' => $APIKey
            ]);
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    public function getRobotAvailableObservablesAction() {
        try {
            // we check the user session
            $identity = $this?->identity();
            if (!isset($identity)) {
                return $this?->handleError(401, 'User not registered or not logged in');
            }
            // we check if all params are here
            $robotId = $this?->params()?->fromQuery('robotId', -1);
            if ($robotId == -1) {
                return $this?->handleError(400, 'You need to send robotId');
            }
            // we check if the user has permision to do this
            $select = ['u.id' => $identity['id'], 'r.id' => $robotId];
            $role = $this?->robotManager?->getUserRoleInEntity($select, 'r.id');
            if (!$this?->isAdmin($identity) && !$this?->isReadable($role)) {
                return $this?->handleError(403, 'You do not have sufficient permissions to access this functionality');
            }

            $robotObs = $this?->robotManager?->returnRobotAvailableObservables($robotId);
            return new JsonModel([
                'robotObs' => $robotObs
            ]);
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    public function getAllAvailableObservablesAction() {
        try {
            // we check the user session
            $identity = $this?->identity();
            if (!isset($identity)) {
                return $this?->handleError(401, 'User not registered or not logged in');
            }
            $allObs = $this?->robotManager?->returnAllAvailableObservables();
            return new JsonModel([
                'data' => $allObs
            ]);
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    public function getRobotApiKeyAction() {
        try {
            // we check the user session
            $identity = $this?->identity();
            if (!isset($identity)) {
                return $this?->handleError(401, 'User not registered or not logged in');
            }
            // we check if all params are here
            $robotId = $this?->params()?->fromQuery('robotId', -1);
            if ($robotId == -1) {
                return $this?->handleError(400, 'You need to send robotId');
            }
            $APIKey = $this?->robotManager?->getRobotApiKey($identity['id'], $robotId);
            return new JsonModel([
                'key' => $APIKey
            ]);
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    public function removeUserFromRobotAction() {
        try {
            // we check the user session
            $identity = $this?->identity();
            if (!isset($identity)) {
                return $this?->handleError(401, 'User not registered or not logged in');
            }
            // we check if all params are here
            $robotId = $this?->params()?->fromQuery('id', -1);
            if ($robotId == -1) {
                return $this?->handleError(400, 'You need to send robotId');
            }

            $userId = $this?->params()?->fromQuery('userId', $identity['id']);
            $select = ['u.id' => $identity['id'], 'r.id' => $robotId];
            $role = $this?->robotManager?->getUserRoleInEntity($select, 'r.id');
            if (!$this?->isWritable($role)) {
                return $this?->handleError(403, 'You do not have sufficient permissions to access this functionality');
            }
            $this?->robotManager?->removeUserFromRobot($robotId, $userId);
            return new JsonModel([
                'text' => 'You have successfully removed a user from the robot.',
                'type' => 'success',
            ]);
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }



    public function showVideoAction() {
        try {
            // we check the user session
            $identity = $this?->identity();
            if (!isset($identity)) {
                return $this?->handleError(401, 'User not registered or not logged in');
            }
            // we check if all params are here
            $robotId = $this?->params()?->fromQuery('robotId', -1);
            $typeId = $this?->params()?->fromQuery('typeId', -1);
            $cameraId = $this?->params()?->fromQuery('cameraId', -1);
            if ($robotId == -1 || $typeId == -1 || $cameraId == -1) {
                return $this?->handleError(400, 'You need to send robotId, typeId and cameraId');
            }

            $select = ['u.id' => $identity['id'], 'r.id' => $robotId];
            $role = $this?->robotManager?->getUserRoleInEntity($select, 'r.id');
            if (!$this?->isReadable($role)) {
                return $this?->handleError(403, 'You do not have sufficient permissions to access this functionality');
            }

            $stream = $this?->robotManager?->getStreamStatus($robotId);
            $stream_stat = $stream != '0' || $stream == null;
            $streamDetails = "robotId: " . $robotId . ", typeId: " . $typeId . ", cameraId: " . $cameraId . ", is streaming: " . $stream . ", stream status: " . strval($stream_stat);
            error_log("Streaming about to start: $streamDetails", E_USER_NOTICE);
            if ($stream != '0' || $stream == null) {
                $streamDetails = $this?->robotManager?->getStreamingDetails($robotId);
                if ($streamDetails !== null) {
                    try {
                        $adapter = new \Laminas\Http\Client\Adapter\Curl();
                        $adapter?->setCurlOption(CURLOPT_FOLLOWLOCATION, true);
                        $adapter?->setCurlOption(CURLOPT_SSL_VERIFYPEER, false);

                        # I was getting errors about secured certificates, this is why I have tried to remove https and disabled verification this is a security risk.
                        $url_in = str_replace(("https", "http", $streamDetails?->url_in);
                        $url_details = "This is the url: " . $url_in;
                        error_log("Streaming about to start: $url_details", E_USER_NOTICE);
                        $uri = $streamDetails?->url_in . "/" . $streamDetails?->stream_id;
                        $client = new \Laminas\Http\Client($uri);
                        $client?->setAdapter($adapter);

                        $client?->setOptions([
                            "timeout" => 2
                        ]);

                        $response = $client?->send()?->getBody();

                    } catch (\Laminas\Http\Client\Adapter\Exception\TimeoutException $e) {
                        // when a client wants to initiate connection and there already exists an entry in robot stream table, verify if this uri is functional ie do I get a timeout 
                        // or do I get connection refused. React based on this response.
                        return new JsonModel([
                            'robotId' => $robotId,
                            'streamId' => $streamDetails?->stream_id,
                            'url' => $streamDetails?->url_out,
                        ]);

                    } catch (\Exception $e) {
                        $this?->robotManager?->updateRobotStreamStatus($robotId, 0);
                        $this?->robotManager?->deleteStream(['id' => $robotId]);
                        sleep(3);
                        $res = $this?->createStreamParameters($robotId, $typeId, $cameraId);

                        if ($res == False) {
                            return new JsonModel([
                                'error' => 'connection refused',
                            ]);
                        }
                        return new JsonModel([
                            'url' => $res['url_out'],
                            'streamId' => $res['streamId'],
                            'typeId' => $typeId,
                        ]);
                    }
                    return new JsonModel([
                        'robotId' => $robotId,
                        'streamId' => $streamDetails?->stream_id,
                        'url' => $streamDetails?->url_out,
                    ]);

                } else {
                    return new JsonModel([
                        'error' => 'streaming details not found',
                    ]);
                }
            } else {
                $res = $this?->createStreamParameters($robotId, $typeId, $cameraId);
                if ($res == False) {
                    return new JsonModel([
                        'error' => 'connection refused',
                    ]);
                }
                return new JsonModel([
                    'url' => $res['url_out'],
                    'streamId' => $res['streamId'],
                    'typeId' => $typeId,
                ]);
            }
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    public function closeVideoStreamAction() {
        try {
            $streamId = $this?->params()?->fromQuery('streamId', -1);
            $robotId = $this?->params()?->fromQuery('robotId', -1);
            if ($this?->robotManager?->checkIfStreamIdValid($robotId, $streamId)) {
                $this?->robotManager?->updateRobotStreamStatus($robotId, 0);
                $this?->robotManager?->deleteStream(['id' => $robotId]);
                return new JsonModel([
                    'result' => 'closed'
                ]);
            } else {
                return new JsonModel([
                    'result' => 'error not permitted'
                ]);
            }
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }


    public function verifyVideoStreamAction() {
        try {
            $streamId = $this?->params()?->fromQuery('streamId', -1);
            $robotId = $this?->params()?->fromQuery('robotId', -1);
            if ($streamId == null) {
                return new JsonModel([
                    'error' => 'error'
                ]);
            }
            return new JsonModel([
                $this?->robotManager?->checkIfStreamIdValid($robotId, $streamId)
            ]);
        } catch (Exception $e) {
            return $this?->handleError(500, 'Internal Server Error: ' . $e?->getMessage());
        }
    }

    /**
     * HELPERS
     */


    private function createStreamParameters($robotId, $typeId, $cameraId) {
        $streamId = bin2hex(random_bytes(20));
        $this?->robotManager?->insertStream(['id' => $robotId, 'stream_id' => $streamId]);
        $host = $_SERVER['SERVER_NAME'] == 'localhost' ? $this?->config['video_streaming']['localhost'] : $this?->config['video_streaming']['host'];
        $uri = 'http://' . $host . ':3000/api/start-stream?streamId=' . $streamId . '&robotId=' . $robotId;
        try {
            $adapter = new \Laminas\Http\Client\Adapter\Curl();
            $adapter?->setCurlOption(CURLOPT_NOBODY, true);
            $client = new \Laminas\Http\Client($uri);
            $response = $client?->send()?->getBody();
        } catch (\AdapterException\RuntimeException $e) {
            $this?->robotManager?->deleteStream(['id' => $robotId]);

            return False;
        } catch (\Exception $e) {
            $this?->robotManager?->deleteStream(['id' => $robotId]);
            $this?->robotManager?->updateRobotStreamStatus($robotId, 0);
            return False;
        }
        $response = \Laminas\Json\Json::decode($response);
        $url_in = 'https://' . $host . ':' . $response?->port; //https or http
        $url_out = 'wss://' . $host . ':' . $response?->port;
        if ($typeId == 'normal_video') {
            $this?->robotManager?->updateRobotStreamStatus($robotId, 1);
            $this?->robotManager?->updateStreamTable($robotId, ['url_in' => $url_in, 'url_out' => $url_out, 'normal_video' => 1, 'audio' => 1, 'camera_id' => $cameraId]);
        } elseif ($typeId == 'thermal_video') {
            $this?->robotManager?->updateRobotStreamStatus($robotId, 1);
            $this?->robotManager?->updateStreamTable($robotId, ['url_in' => $url_in, 'url_out' => $url_out, 'thermal_video' => 1, 'camera_id' => $cameraId]);
        } else {
            $this?->robotManager?->updateRobotStreamStatus($robotId, 1);
            $this?->robotManager?->updateStreamTable($robotId, ['url_in' => $url_in, 'url_out' => $url_out, 'audio' => 1, 'camera_id' => $cameraId]);
        }
        return [
            'url_out' => $url_out,
            'streamId' => $streamId
        ];
    }


    private function getHouseDimensions($robotId) {
        $configFile = $this?->robotManager?->getConfigurationFile($robotId);
        $xml = simplexml_load_string($configFile);
        return [
            'dimX' => (int) $xml?->house?->{'dimension-x'} ?? null,
            'dimY' => (int) $xml?->house?->{'dimension-y'} ?? null
        ];

    }


    // for some reason I couldn't move this is the robot manager. I was getting memory leakages when I tried to inject alertManager into the robotManager
    public function getRobotStatusLayoutData($identity, $robotId) {
        $identity = $this?->identity();
        $status = $this?->robotManager?->returnRobotStatus($identity, $robotId);
        $alerts = [
            'total' => count($this?->alertManager?->alertsAfterLastLogin($identity)) ?? 0,
            'ambientConditions' => $this?->alertManager?->ambientConditionAlertCount($identity['id'], $robotId),
            'healthAndWelfare' => $this?->alertManager?->healthandWelfareAlertCount($identity['id'], $robotId),
            'equipment' => $this?->alertManager?->equipmentAlertCount($identity['id'], $robotId),
        ];

        $lastping = $this?->robotManager?->getLastRobotPing($robotId);

        return [
            'robotType' => $status?->robot_type ?? 'cb',
            'location' => $status?->location ?? null,
            'battery' => $status?->battery ?? null,
            'estimatedRecharge' => $status?->remaining_recharge_time ?? '0',
            'batteryTime' => $status?->battery_time ?? '0',
            'restartDate' => $status?->restart_date ?? null,
            'operationalState' => $status?->operational_state ?? null,
            'onCharger' => $status?->on_charger ?? null,
            'errorMsg' => $status?->error_msg ?? null,
            'status' => $status?->status ?? null,
            'alert' => $alerts,
            'lastUpdate' => $lastping ? $lastping : null
        ];
    }


    public function getRobotCameraFromConfig($robotId) {
        $configFile = $this?->robotManager?->getConfigurationFile($robotId);
        if (isset($configFile)) {
            $xml = simplexml_load_string($configFile);
            return $xml?->camera;
        } else {
            return null;
        }
    }


    public function getObjectsForObservablesElasticSearch($roundId, $observable, $camera, $offset, $limit, $from, $to) {
        $this?->counter = 1;
        $objects = $this?->elasticSearchManager?->image_search($roundId, $observable, $camera, $offset, $limit, $from, $to)['hits']['hits'];
        $count = count($objects);
        // If object count is zero it means we reached the end of the objects in the location. The key is set to the last object once more.
        if ($count == 0) {
            return [
                'images' => [],
                //null
                'key' => $offset + $count,
            ];
        }
        $pool = Pool::create();
        foreach ($objects as $object) {
            $pool?->add(function () use ($object) {
                $this?->counter += 1;
                $request = $this?->s3FileSystem?->createUri($object['_source']['objectKey'], '+20 minutes');
                $metaData = $this?->s3FileSystem?->getHeader($object['_source']['objectKey']);

                return [
                    'request' => $request,
                    'metaData' => $metaData
                ];
            })?->then(function ($output) use ($object) {
                $request = $output['request'];
                $metaData = $output['metaData'];
                $time = strtotime($object['_source']['createdDate']);

                //Extract time from file name if have correct format, because time extracted from last modified is in GMT
                $nameTime = strtotime(str_replace(("_", ":", substr(explode("/", $object['_source']['objectKey'])[4], 14, 20)));

                $this?->images[] = [
                    'uri' => (string) $request?->getUri(),
                    'time' => $nameTime ? date("Y-m-d H:i:s", $nameTime) : date("Y-m-d H:i:s", $time),
                    'coordinates' => $metaData['@metadata']['headers']['x-amz-meta-coordinates'] ?? NULL,
                    'camera' => $metaData['@metadata']['headers']['x-amz-meta-camera'] ?? NULL
                ];

            })?->catch(function (Throwable $exception) {
                // Handle exception
            });
            // By default return 6 images max
            if ($this?->counter == $limit || $this?->counter == $count) {
                $this?->key = $object['_source']['objectKey'];
                break;
            }
            $this?->counter++;
        }
        $pool?->wait();
        return [
            'images' => $this?->images,
            'key' => $offset + $count,
        ];
    }
}
Editor is loading...