298 lines
7.6 KiB
TypeScript
298 lines
7.6 KiB
TypeScript
import {Capture, Chip, Device as ProtoDevice, State} from './model.js';
|
|
|
|
// URL for netsim
|
|
const DEVICES_URL = './v1/devices';
|
|
const CAPTURES_URL = './v1/captures';
|
|
|
|
/**
|
|
* Interface for a method in notifying the subscribed observers.
|
|
* Subscribed observers must implement this interface.
|
|
*/
|
|
export interface Notifiable {
|
|
onNotify(data: {}): void;
|
|
}
|
|
|
|
/**
|
|
* Modularization of Device.
|
|
* Contains getters and setters for properties in Device interface.
|
|
*/
|
|
export class Device {
|
|
device: ProtoDevice;
|
|
|
|
constructor(device: ProtoDevice) {
|
|
this.device = device;
|
|
}
|
|
|
|
get name(): string {
|
|
return this.device.name;
|
|
}
|
|
|
|
set name(value: string) {
|
|
this.device.name = value;
|
|
}
|
|
|
|
get position(): {x: number; y: number; z: number} {
|
|
const result = {x: 0, y: 0, z: 0};
|
|
if ('position' in this.device && this.device.position &&
|
|
typeof this.device.position === 'object') {
|
|
if ('x' in this.device.position &&
|
|
typeof this.device.position.x === 'number') {
|
|
result.x = this.device.position.x;
|
|
}
|
|
if ('y' in this.device.position &&
|
|
typeof this.device.position.y === 'number') {
|
|
result.y = this.device.position.y;
|
|
}
|
|
if ('z' in this.device.position &&
|
|
typeof this.device.position.z === 'number') {
|
|
result.z = this.device.position.z;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
set position(pos: {x: number; y: number; z: number}) {
|
|
this.device.position = pos;
|
|
}
|
|
|
|
get orientation(): {yaw: number; pitch: number; roll: number} {
|
|
const result = {yaw: 0, pitch: 0, roll: 0};
|
|
if ('orientation' in this.device && this.device.orientation &&
|
|
typeof this.device.orientation === 'object') {
|
|
if ('yaw' in this.device.orientation &&
|
|
typeof this.device.orientation.yaw === 'number') {
|
|
result.yaw = this.device.orientation.yaw;
|
|
}
|
|
if ('pitch' in this.device.orientation &&
|
|
typeof this.device.orientation.pitch === 'number') {
|
|
result.pitch = this.device.orientation.pitch;
|
|
}
|
|
if ('roll' in this.device.orientation &&
|
|
typeof this.device.orientation.roll === 'number') {
|
|
result.roll = this.device.orientation.roll;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
set orientation(ori: {yaw: number; pitch: number; roll: number}) {
|
|
this.device.orientation = ori;
|
|
}
|
|
|
|
// TODO modularize getters and setters for Chip Interface
|
|
get chips(): Chip[] {
|
|
return this.device.chips ?? [];
|
|
}
|
|
|
|
// TODO modularize getters and setters for Chip Interface
|
|
set chips(value: Chip[]) {
|
|
this.device.chips = value;
|
|
}
|
|
|
|
get visible(): boolean {
|
|
return this.device.visible ?? true;
|
|
}
|
|
|
|
set visible(value: boolean) {
|
|
this.device.visible = value;
|
|
}
|
|
|
|
toggleChipState(chip: Chip, btType?: string) {
|
|
if ('bt' in chip && chip.bt) {
|
|
if (typeof (btType) === 'undefined') {
|
|
// eslint-disable-next-line
|
|
console.log(
|
|
'netsim-ui: must specify lowEnergy or classic for Bluetooth');
|
|
return;
|
|
}
|
|
if (btType === 'lowEnergy' && 'lowEnergy' in chip.bt &&
|
|
chip.bt.lowEnergy) {
|
|
if ('state' in chip.bt.lowEnergy) {
|
|
chip.bt.lowEnergy.state =
|
|
chip.bt.lowEnergy.state === State.ON ? State.OFF : State.ON;
|
|
}
|
|
}
|
|
if (btType === 'classic' && 'classic' in chip.bt && chip.bt.classic) {
|
|
if ('state' in chip.bt.classic) {
|
|
chip.bt.classic.state =
|
|
chip.bt.classic.state === State.ON ? State.OFF : State.ON;
|
|
}
|
|
}
|
|
}
|
|
if ('wifi' in chip && chip.wifi) {
|
|
if ('state' in chip.wifi) {
|
|
chip.wifi.state = chip.wifi.state === State.ON ? State.OFF : State.ON;
|
|
}
|
|
}
|
|
if ('uwb' in chip && chip.uwb) {
|
|
if ('state' in chip.uwb) {
|
|
chip.uwb.state = chip.uwb.state === State.ON ? State.OFF : State.ON;
|
|
}
|
|
}
|
|
}
|
|
|
|
toggleCapture(device: Device, chip: Chip) {
|
|
if ('capture' in chip && chip.capture) {
|
|
chip.capture = chip.capture === State.ON ? State.OFF : State.ON;
|
|
simulationState.patchDevice({
|
|
device: {
|
|
name: device.name,
|
|
chips: device.chips,
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The most recent state of the simulation.
|
|
* Subscribed observers must refer to this info and patch accordingly.
|
|
*/
|
|
export interface SimulationInfo {
|
|
devices: Device[];
|
|
captures: Capture[];
|
|
selectedId: string;
|
|
dimension: {x: number; y: number; z: number;};
|
|
}
|
|
|
|
interface Observable {
|
|
registerObserver(elem: Notifiable): void;
|
|
removeObserver(elem: Notifiable): void;
|
|
}
|
|
|
|
class SimulationState implements Observable {
|
|
private observers: Notifiable[] = [];
|
|
|
|
private simulationInfo: SimulationInfo = {
|
|
devices: [],
|
|
captures: [],
|
|
selectedId: '',
|
|
dimension: {x: 10, y: 10, z: 0},
|
|
};
|
|
|
|
constructor() {
|
|
// initial GET
|
|
this.invokeGetDevice();
|
|
this.invokeListCaptures();
|
|
}
|
|
|
|
invokeGetDevice() {
|
|
fetch(DEVICES_URL, {
|
|
method: 'GET',
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
this.fetchDevice(data.devices);
|
|
})
|
|
.catch(error => {
|
|
// eslint-disable-next-line
|
|
console.log('Cannot connect to netsim web server', error);
|
|
});
|
|
}
|
|
|
|
invokeListCaptures() {
|
|
fetch(CAPTURES_URL, {
|
|
method: 'GET',
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
this.simulationInfo.captures = data.captures;
|
|
})
|
|
.catch(error => {
|
|
console.log('Cannot connect to netsim web server', error);
|
|
})
|
|
}
|
|
|
|
fetchDevice(devices: ProtoDevice[]) {
|
|
this.simulationInfo.devices = [];
|
|
for (const device of devices) {
|
|
this.simulationInfo.devices.push(new Device(device));
|
|
}
|
|
this.notifyObservers();
|
|
}
|
|
|
|
patchSelected(id: string) {
|
|
this.simulationInfo.selectedId = id;
|
|
this.notifyObservers();
|
|
}
|
|
|
|
handleDrop(id: string, x: number, y: number) {
|
|
for (const device of this.simulationInfo.devices) {
|
|
if (id === device.name) {
|
|
device.position = {x, y, z: device.position.z};
|
|
this.patchDevice({
|
|
device: {
|
|
name: device.name,
|
|
position: device.position,
|
|
},
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
patchCapture(id: string, state: string) {
|
|
fetch(CAPTURES_URL + '/' + id, {
|
|
method: 'PATCH',
|
|
headers: {
|
|
'Content-Type': 'text/plain',
|
|
'Content-Length': state.length.toString(),
|
|
},
|
|
body: state,
|
|
});
|
|
this.notifyObservers();
|
|
}
|
|
|
|
patchDevice(obj: object) {
|
|
const jsonBody = JSON.stringify(obj);
|
|
fetch(DEVICES_URL, {
|
|
method: 'PATCH',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Content-Length': jsonBody.length.toString(),
|
|
},
|
|
body: jsonBody,
|
|
})
|
|
.then(response => response.json())
|
|
.catch(error => {
|
|
// eslint-disable-next-line
|
|
console.error('Error:', error);
|
|
});
|
|
this.notifyObservers();
|
|
}
|
|
|
|
registerObserver(elem: Notifiable) {
|
|
this.observers.push(elem);
|
|
elem.onNotify(this.simulationInfo);
|
|
}
|
|
|
|
removeObserver(elem: Notifiable) {
|
|
const index = this.observers.indexOf(elem);
|
|
this.observers.splice(index, 1);
|
|
}
|
|
|
|
notifyObservers() {
|
|
for (const observer of this.observers) {
|
|
observer.onNotify(this.simulationInfo);
|
|
}
|
|
}
|
|
|
|
getDeviceList() {
|
|
return this.simulationInfo.devices;
|
|
}
|
|
}
|
|
|
|
/** Subscribed observers must register itself to the simulationState */
|
|
export const simulationState = new SimulationState();
|
|
|
|
async function subscribe() {
|
|
const delay = (ms: number) => new Promise(res => setTimeout(res, ms));
|
|
while (true) {
|
|
simulationState.invokeGetDevice();
|
|
simulationState.invokeListCaptures();
|
|
await delay(1000);
|
|
}
|
|
}
|
|
|
|
subscribe();
|