• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import {Capture, Chip, Device as ProtoDevice, State} from './model.js';
2
3// URL for netsim
4const DEVICES_URL = './v1/devices';
5const CAPTURES_URL = './v1/captures';
6
7/**
8 * Interface for a method in notifying the subscribed observers.
9 * Subscribed observers must implement this interface.
10 */
11export interface Notifiable {
12  onNotify(data: {}): void;
13}
14
15/**
16 * Modularization of Device.
17 * Contains getters and setters for properties in Device interface.
18 */
19export class Device {
20  device: ProtoDevice;
21
22  constructor(device: ProtoDevice) {
23    this.device = device;
24  }
25
26  get name(): string {
27    return this.device.name;
28  }
29
30  set name(value: string) {
31    this.device.name = value;
32  }
33
34  get position(): {x: number; y: number; z: number} {
35    const result = {x: 0, y: 0, z: 0};
36    if ('position' in this.device && this.device.position &&
37        typeof this.device.position === 'object') {
38      if ('x' in this.device.position &&
39          typeof this.device.position.x === 'number') {
40        result.x = this.device.position.x;
41      }
42      if ('y' in this.device.position &&
43          typeof this.device.position.y === 'number') {
44        result.y = this.device.position.y;
45      }
46      if ('z' in this.device.position &&
47          typeof this.device.position.z === 'number') {
48        result.z = this.device.position.z;
49      }
50    }
51    return result;
52  }
53
54  set position(pos: {x: number; y: number; z: number}) {
55    this.device.position = pos;
56  }
57
58  get orientation(): {yaw: number; pitch: number; roll: number} {
59    const result = {yaw: 0, pitch: 0, roll: 0};
60    if ('orientation' in this.device && this.device.orientation &&
61        typeof this.device.orientation === 'object') {
62      if ('yaw' in this.device.orientation &&
63          typeof this.device.orientation.yaw === 'number') {
64        result.yaw = this.device.orientation.yaw;
65      }
66      if ('pitch' in this.device.orientation &&
67          typeof this.device.orientation.pitch === 'number') {
68        result.pitch = this.device.orientation.pitch;
69      }
70      if ('roll' in this.device.orientation &&
71          typeof this.device.orientation.roll === 'number') {
72        result.roll = this.device.orientation.roll;
73      }
74    }
75    return result;
76  }
77
78  set orientation(ori: {yaw: number; pitch: number; roll: number}) {
79    this.device.orientation = ori;
80  }
81
82  // TODO modularize getters and setters for Chip Interface
83  get chips(): Chip[] {
84    return this.device.chips ?? [];
85  }
86
87  // TODO modularize getters and setters for Chip Interface
88  set chips(value: Chip[]) {
89    this.device.chips = value;
90  }
91
92  get visible(): boolean {
93    return this.device.visible ?? true;
94  }
95
96  set visible(value: boolean) {
97    this.device.visible = value;
98  }
99
100  toggleChipState(chip: Chip, btType?: string) {
101    if ('bt' in chip && chip.bt) {
102      if (typeof (btType) === 'undefined') {
103        // eslint-disable-next-line
104        console.log(
105            'netsim-ui: must specify lowEnergy or classic for Bluetooth');
106        return;
107      }
108      if (btType === 'lowEnergy' && 'lowEnergy' in chip.bt &&
109          chip.bt.lowEnergy) {
110        if ('state' in chip.bt.lowEnergy) {
111          chip.bt.lowEnergy.state =
112              chip.bt.lowEnergy.state === State.ON ? State.OFF : State.ON;
113        }
114      }
115      if (btType === 'classic' && 'classic' in chip.bt && chip.bt.classic) {
116        if ('state' in chip.bt.classic) {
117          chip.bt.classic.state =
118              chip.bt.classic.state === State.ON ? State.OFF : State.ON;
119        }
120      }
121    }
122    if ('wifi' in chip && chip.wifi) {
123      if ('state' in chip.wifi) {
124        chip.wifi.state = chip.wifi.state === State.ON ? State.OFF : State.ON;
125      }
126    }
127    if ('uwb' in chip && chip.uwb) {
128      if ('state' in chip.uwb) {
129        chip.uwb.state = chip.uwb.state === State.ON ? State.OFF : State.ON;
130      }
131    }
132  }
133
134  toggleCapture(device: Device, chip: Chip) {
135    if ('capture' in chip && chip.capture) {
136      chip.capture = chip.capture === State.ON ? State.OFF : State.ON;
137      simulationState.patchDevice({
138        device: {
139          name: device.name,
140          chips: device.chips,
141        }
142      });
143    }
144  }
145}
146
147/**
148 * The most recent state of the simulation.
149 * Subscribed observers must refer to this info and patch accordingly.
150 */
151export interface SimulationInfo {
152  devices: Device[];
153  captures: Capture[];
154  selectedId: string;
155  dimension: {x: number; y: number; z: number;};
156}
157
158interface Observable {
159  registerObserver(elem: Notifiable): void;
160  removeObserver(elem: Notifiable): void;
161}
162
163class SimulationState implements Observable {
164  private observers: Notifiable[] = [];
165
166  private simulationInfo: SimulationInfo = {
167    devices: [],
168    captures: [],
169    selectedId: '',
170    dimension: {x: 10, y: 10, z: 0},
171  };
172
173  constructor() {
174    // initial GET
175    this.invokeGetDevice();
176    this.invokeListCaptures();
177  }
178
179  invokeGetDevice() {
180    fetch(DEVICES_URL, {
181      method: 'GET',
182    })
183        .then(response => response.json())
184        .then(data => {
185          this.fetchDevice(data.devices);
186        })
187        .catch(error => {
188          // eslint-disable-next-line
189          console.log('Cannot connect to netsim web server', error);
190        });
191  }
192
193  invokeListCaptures() {
194    fetch(CAPTURES_URL, {
195      method: 'GET',
196    })
197        .then(response => response.json())
198        .then(data => {
199          this.simulationInfo.captures = data.captures;
200        })
201        .catch(error => {
202          console.log('Cannot connect to netsim web server', error);
203        })
204  }
205
206  fetchDevice(devices: ProtoDevice[]) {
207    this.simulationInfo.devices = [];
208    for (const device of devices) {
209      this.simulationInfo.devices.push(new Device(device));
210    }
211    this.notifyObservers();
212  }
213
214  patchSelected(id: string) {
215    this.simulationInfo.selectedId = id;
216    this.notifyObservers();
217  }
218
219  handleDrop(id: string, x: number, y: number) {
220    for (const device of this.simulationInfo.devices) {
221      if (id === device.name) {
222        device.position = {x, y, z: device.position.z};
223        this.patchDevice({
224          device: {
225            name: device.name,
226            position: device.position,
227          },
228        });
229        break;
230      }
231    }
232  }
233
234  patchCapture(id: string, state: string) {
235    fetch(CAPTURES_URL + '/' + id, {
236      method: 'PATCH',
237      headers: {
238        'Content-Type': 'text/plain',
239        'Content-Length': state.length.toString(),
240      },
241      body: state,
242    });
243    this.notifyObservers();
244  }
245
246  patchDevice(obj: object) {
247    const jsonBody = JSON.stringify(obj);
248    fetch(DEVICES_URL, {
249      method: 'PATCH',
250      headers: {
251        'Content-Type': 'application/json',
252        'Content-Length': jsonBody.length.toString(),
253      },
254      body: jsonBody,
255    })
256        .then(response => response.json())
257        .catch(error => {
258          // eslint-disable-next-line
259          console.error('Error:', error);
260        });
261    this.notifyObservers();
262  }
263
264  registerObserver(elem: Notifiable) {
265    this.observers.push(elem);
266    elem.onNotify(this.simulationInfo);
267  }
268
269  removeObserver(elem: Notifiable) {
270    const index = this.observers.indexOf(elem);
271    this.observers.splice(index, 1);
272  }
273
274  notifyObservers() {
275    for (const observer of this.observers) {
276      observer.onNotify(this.simulationInfo);
277    }
278  }
279
280  getDeviceList() {
281    return this.simulationInfo.devices;
282  }
283}
284
285/** Subscribed observers must register itself to the simulationState */
286export const simulationState = new SimulationState();
287
288async function subscribe() {
289  const delay = (ms: number) => new Promise(res => setTimeout(res, ms));
290  while (true) {
291    simulationState.invokeGetDevice();
292    simulationState.invokeListCaptures();
293    await delay(1000);
294  }
295}
296
297subscribe();
298