<template>
<!-- Layout -->
<div
id="operator-layout"
class="vh-100">
<!-- Navbar container-->
<div
id="nav-bar"
class="position-relative">
<!-- Navbar -->
<b-navbar
toggleable="md"
class="navbar-dark mb-0 bg-green-sb">
<!-- Brand -->
<b-navbar-brand class="p-0">
<div
class="h-100"
style="width:240px">
<img
src="../assets/SecurBotLogo.png"
alt="SecurBot"
class="logo mh-100 mw-100 align-middle">
</div>
</b-navbar-brand>
<!-- Collapse Option -->
<b-navbar-toggle target="nav_collapse" />
<!-- Collapsable Navbar -->
<b-collapse
id="nav_collapse"
is-nav>
<!-- Navbar right side content -->
<b-navbar-nav>
<!-- Teleop -->
<b-nav-item to="teleop">
Teleoperation
</b-nav-item>
<!-- Patrol -->
<b-nav-item to="patrol">
Patrol Planner
</b-nav-item>
<!-- Event -->
<b-nav-item to="logs">
Logs
</b-nav-item>
</b-navbar-nav>
<!-- Navbar left side content -->
<b-navbar-nav class="ml-auto">
<!-- Dropdown with connection widget -->
<b-nav-item-dropdown
text="Connect to Robot"
right>
<!-- Connection container -->
<div class="px-2 py-1">
<!-- Connection -->
<connection
:self-id="selfEasyrtcid"
:peers-table="peerTable"
:bus="teleopBus" />
</div>
</b-nav-item-dropdown>
</b-navbar-nav>
</b-collapse>
</b-navbar>
</div>
<!-- Main content (page) -->
<div style="height:calc(100% - 64px)">
<!-- Router to pages -->
<router-view
:bus="teleopBus"
:router="routeBus" />
</div>
</div>
</template>
<script>
/**
* Vue SFC that is the main routing point. All the routing is done by
* this component (layout = parent, other pages = children). It has a
* navigation bar for the routing that also contains the connection
* component in a drop-down menu (simplify the connection process for
* the user) and set the page height (currently the viewport height
* minus the height the navbar). This component is the one managing
* all the easyRTC necessary for the application. It communicates with
* the children component with a bus. The layout has the following
* dependencies : Connection component and Bootstrap-vue for styling
* and HTML element.
*
* @module Layout
* @vue-data {String} peerId - Id of the connected peer.
* @vue-data {Boolean} isDataChannelAvailable - Keep track if the data channel is open.
* @vue-data {String} selfEasyrtcid - self easyrtc id.
* @vue-data {HTMLVideoElement} cameraStreamElement - HTML video element to host camera.
* @vue-data {HTMLVideoElement} mapStreamElement - HTML video element to host map.
* @vue-data {HTMLVideoElement} patrolMapStreamElement - HTML video element to host map.
* @vue-data {VideoTrack} cameraStream - VideoTrack given by easyrtc of the camera.
* @vue-data {VideoTrack} mapStream - VideoTrack given by easyrtc of the camera.
* @vue-data {Vue} teleopBus - General communication bus between component. Change name.
* @vue-data {Vue} routeBus - Communication bus for the routing event.
* @vue-data {String[]} peerTable - List of the robot in the room.
* @vue-data {String} joystickState - Indicates if the joystick should be activable or not.
* @vue-event {String} connection-changed - Use to communicate the state of connection
* has changed to the other components.
* @vue-event {String} on-joystick-state-changed - Use to communicate the joystick can
* be used to the teleop page.
*/
/**
* @author Edouard Legare <edouard.legare@usherbrooke.ca>
*/
/* global easyrtc */
import Vue from 'vue';
import Connection from './widget/Connection';
export default {
name: 'layout',
components: {
Connection,
},
data() {
return {
peerId: null,
isDataChannelAvailable: false,
selfEasyrtcid: null,
cameraStreamElement: null,
mapStreamElement: null,
patrolMapStreamElement: null,
cameraStream: null,
mapStream: null,
teleopBus: new Vue(),
routeBus: new Vue(),
peerTable: [],
joystickState: 'disable',
};
},
/**
* On component mounted, use to get and initialise
* @method
*/
mounted() {
this.teleopBus.$on('peer-connection', this.connectTo);
this.teleopBus.$on('joystick-position-change', this.onJoystickPositionChange);
this.teleopBus.$on('send-patrol', this.sendPatrol);
this.routeBus.$on('mounted', this.setHTMLVideoStream);
this.routeBus.$on('destroyed', this.clearHTMLVideoStream);
this.connect();
},
/**
* On component destroy, hangup and disconnect
* @method
*/
destroyed() {
if (this.selfEasyrtcid !== null) {
easyrtc.hangupAll();
easyrtc.disconnect();
}
},
methods: {
/**
* Function managing initialisation and connection to the easyrtc server
* @method
*/
connect() {
easyrtc.enableDebug(false);
console.log('Initializing...');
easyrtc.enableVideo(false);
easyrtc.enableAudio(false);
easyrtc.enableVideoReceive(true);
easyrtc.enableAudioReceive(false);
easyrtc.enableDataChannels(true);
easyrtc.setDataChannelOpenListener(this.dataOpenListenerCB);
easyrtc.setDataChannelCloseListener(this.dataCloseListenerCB);
easyrtc.setPeerListener(this.handleData);
easyrtc.setRoomOccupantListener(this.handleRoomOccupantChange);
easyrtc.setStreamAcceptor(this.acceptPeerVideo);
easyrtc.setOnStreamClosed(this.closePeerVideo);
easyrtc.setAcceptChecker(this.acceptCall);
easyrtc.setRoomApiField('default', 'type', 'operator');
// Uncomment next line to use the dev server
easyrtc.setSocketUrl('http://securbot.gel.usherbrooke.ca:8080');
// Uncomment initialisation to use local stream for map (debugging only)
// easyrtc.initMediaSource(() => {
// this.mapStream = easyrtc.getLocalStream();
// easyrtc.connect('easyrtc.securbot', this.loginSuccess, this.loginFailure);
// }, this.loginFailure);
// This is the production line, only comment if necessary for debugging
easyrtc.connect('easyrtc.securbot', this.loginSuccess, this.loginFailure);
console.log('You are connected...');
this.setHTMLVideoStream();
},
/**
* Callback for the handle of occupant in room.
* @method
* @param {String} roomName - Name of the room.
* @param {Array} occupants - List of occupant in the room.
* @param {Boolean} isPrimary
*/
handleRoomOccupantChange(roomName, occupants, isPrimary) {
this.peerTable = [];
if (occupants !== null) {
for (const occupant in occupants) {
if (occupants[occupant].apiField.type.fieldValue.includes('robot')) {
const peer = {
peerName: occupants[occupant].apiField.type.fieldValue,
peerId: occupant,
};
this.peerTable.push(peer);
}
}
}
},
/**
* Use to call someone in the room with its id.
* @method
* @param {String} occupantId - Id to call.
*/
performCall(occupantId) {
easyrtc.hangupAll();
console.log(`Calling the chosen occupant : ${occupantId}`);
easyrtc.call(occupantId, this.callSuccessful, this.callFailure, this.callAccepted);
},
/**
* Callback for a successful call.
* @method
* @param {String} occupantId - Id of the occupant that was called.
* @param {String} mediaType - Type of media received (ex: AudioVideo)
*/
callSuccessful(occupantId, mediaType) {
console.warn(`Call to ${occupantId} was successful, here's the media: ${mediaType}`);
if (mediaType === 'connection') {
this.teleopBus.$emit('connection-changed', 'connected');
}
},
/**
* Callback for call failure.
* @method
* @param {Number} errCode - Error Code.
* @param {String} errMessage - Error Message.
*/
callFailure(errCode, errMessage) {
console.warn(`Call failed : ${errCode} | ${errMessage}`);
this.teleopBus.$emit('connection-changed', 'failed');
this.peerId = null;
},
/**
* Callback for call accepted.
* @method
* @param {Boolean} accepted - If the call was accepted.
* @param {String} easyrtcid - Id of the occupant that accepted the call.
*/
callAccepted(accepted, easyrtcid) {
console.warn(`Call was ${accepted} from ${easyrtcid}`);
if (!accepted) {
this.teleopBus.$emit('connection-changed', 'failed');
this.peerId = null;
} else {
this.peerId = easyrtcid;
}
},
/**
* Callback for successfully connecting to the room.
* @method
* @param {String} easyrtcid - Self id give by the server.
*/
loginSuccess(easyrtcid) {
console.warn(`I am ${easyrtc.idToName(easyrtcid)}`);
this.selfEasyrtcid = easyrtcid;
},
/**
* Callback for failure to connect to the room.
* @method
* @param {Number} errorCode - Error code.
* @param {String} message - Error message.
*/
loginFailure(errorCode, message) {
easyrtc.showError(errorCode, message);
},
/**
* Callback for the setStreamAcceptor function.
* @method
* @param {String} easyrtcid - Id of the occupant giving the video stream.
* @param {VideoTrack} stream - Track of the stream coming from the server.
* @param {String} streamName - Name of the stream coming from the server.
*/
acceptPeerVideo(easyrtcid, stream, streamName) {
console.log(`Stream received info, id : ${easyrtcid}, streamName : ${streamName}`);
if (streamName === 'camera') {
this.cameraStream = stream;
} else if (streamName === 'map') {
this.mapStream = stream;
} else {
console.warn('Unknown stream obtained...');
}
this.setHTMLVideoStream();
},
/**
* Callback for the setOnStreamClosed function.
* @method
* @param {String} easyrtcid - Id of the occupant that disabled or lost its stream.
*/
closePeerVideo(easyrtcid) {
this.clearHTMLVideoStream();
},
/**
* Callback for the setAcceptChecker function.
* @method
* @param {String} easyrtcid - Id of the occupant calling.
* @param {Callback} acceptor - Callback to accept the call.
*/
acceptCall(easyrtcid, acceptor) {
console.log(`This id called : ${easyrtcid}, i'll only answer to ${this.peerId}`);
if (easyrtcid === this.peerId) {
acceptor(true);
} else {
acceptor(false);
}
},
/**
* Callback of the "peer-connection"event.
* @method
* @param {String} easyrtcid - Id of the occupant to connect to.
*/
connectTo(easyrtcid) {
console.log(`A connection change with ${easyrtcid} was asked...`);
if (this.peerId === easyrtcid) {
easyrtc.hangupAll();
this.teleopBus.$emit('connection-changed', 'disconnected');
this.peerId = null;
} else if (this.peerId === null) {
this.performCall(easyrtcid);
} else {
console.warn("The is an issue in the connection state handling... This shouldn't happen...");
}
},
/**
* Callback for the setDataChannelOpenListener function.
* @method
* @param {String} easyrtc - Id of the occupant that a channel was opened with.
*/
dataOpenListenerCB(easyrtcid) {
console.warn(`Data channel open with ${easyrtcid}`);
this.isDataChannelAvailable = true;
this.joystickState = 'enable';
this.teleopBus.$emit('on-joystick-state-changed', this.joystickState);
// This request the stream from the robot so the operator doesn't have to have
// a local stream to get the feed from the robot. It also allows to get both stream
// from robot, which might have been a problem previously. They can be somewhere else.
if (!this.mapStream) {
console.log('Requesting the map stream from peer...');
this.requestFeedFromPeer('map');
}
if (!this.cameraStream) {
console.log('Requesting the camera stream from peer...');
this.requestFeedFromPeer('camera');
}
},
/**
* Callback for the setDataChannelCloseListener function.
* @method
* @param {String} easyrtc - Id of the occupant that a channel was closed with.
*/
dataCloseListenerCB(easyrtcid) {
this.isDataChannelAvailable = false;
if (easyrtcid === this.peerId || !this.peerId) {
this.peerId = null;
this.teleopBus.$emit('connection-changed', 'disconnected');
this.clearHTMLVideoStream();
}
this.joystickState = 'disable';
this.teleopBus.$emit('on-joystick-state-changed', this.joystickState);
},
/**
* Callback of the "joystick-position-change" event.
* @method
* @param {Object} data - Teleop data.
*/
onJoystickPositionChange(data) {
this.sendData(this.peerId, 'joystick-position', JSON.stringify(data));
},
/**
* Callback of the "send-patrol" event.
* @method
* @param {String} goalJsonString - Stringigify JSON of the patrol.
*/
sendPatrol(goalJsonString) {
this.sendData(this.peerId, 'patrol-plan', goalJsonString);
},
/**
* Use to request a stream from the peer.
* @method
* @param {String} feed - Feed Name to request.
*/
requestFeedFromPeer(feed) {
this.sendData(this.peerId, 'request-feed', feed);
},
/**
* Sends data to the robot using the data channel.
* @method
* @param {String} goalJsonString - Stringigify JSON of the patrol.
* @param {String} type - Channel to send the data on.
* @param {String} data - Stringify data to send.
*/
sendData(peer, type, data) {
if (this.isDataChannelAvailable && peer) {
easyrtc.sendDataP2P(peer, type, data);
} else {
console.warn('No data channel or peer available to send data...');
}
},
/**
* Callback of the setPeerListener function. Not currently used.
* @method
* @param {String} easyrtcid - Peer id the datas are are coming from.
* @param {String} type - Channel to data was received on.
* @param {String} data - Data received.
*/
handleData(easyrtcid, type, data) {
if (easyrtcid === this.peerId) {
console.log(`Received ${data} of type ${type}...`);
} else {
console.log('Received data from someone else than the peer, ignoring it...');
}
},
/**
* Verify if the joystick should be enable.
* @method
*/
verifyJoystickState() {
if (this.isDataChannelAvailable) {
this.joystickState = 'enable';
this.teleopBus.$emit('on-joystick-state-changed', this.joystickState);
}
},
/**
* Sets the available video feed(s) to available html element(s).
* @method
*/
setHTMLVideoStream() {
this.verifyJoystickState();
this.getHTMLElements();
if (this.cameraStreamElement && this.cameraStream) {
console.log('Setting camera stream...');
easyrtc.setVideoObjectSrc(this.cameraStreamElement, this.cameraStream);
}
if (this.mapStreamElement && this.mapStream) {
console.log('Setting map stream...');
easyrtc.setVideoObjectSrc(this.mapStreamElement, this.mapStream);
}
if (this.patrolMapStreamElement && this.mapStream) {
console.log('Setting patrol stream...');
easyrtc.setVideoObjectSrc(this.patrolMapStreamElement, this.mapStream);
}
},
/**
* Clears all feeds in html and reset the feed variables.
* @method
*/
clearHTMLVideoStream() {
if (this.cameraStreamElement) {
easyrtc.setVideoObjectSrc(this.cameraStreamElement, '');
}
if (this.mapStreamElement) {
easyrtc.setVideoObjectSrc(this.mapStreamElement, '');
}
if (this.patrolMapStreamElement) {
easyrtc.setVideoObjectSrc(this.patrolMapStreamElement, '');
}
this.cameraStreamElement = null;
this.mapStreamElement = null;
this.patrolMapStreamElement = null;
},
/**
* Get HTML element(s) of VideoBox component in the current page.
*/
getHTMLElements() {
this.cameraStreamElement = document.getElementById('camera-stream');
this.mapStreamElement = document.getElementById('map-stream');
this.patrolMapStreamElement = document.getElementById('patrol-map-stream');
},
},
};
</script>
<style>
/* Changes to the bootstrap CSS */
.jumbotron{
margin-bottom: 0;
}
.container-fluid{
height: 100%
}
.navbar{
min-height:64px;
}
/*Custom CSS element (SecurBot)*/
.shadow-sb{
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.5), 0 6px 20px 0 rgba(0, 54, 5, 0.19);
}
.bg-black-sb{
background-color: black;
}
.bg-green-sb{
background-color:#00A759
}
.b-collapse-sb{
border-collapse: collapse;
}
</style>