• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import {Capture, Chip, Chip_Radio, Device as ProtoDevice,} from './netsim/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 Boolean(this.device.visible);
94  }
95
96  set visible(value: boolean) {
97    this.device.visible = value;
98  }
99
100  toggleChipState(radio: Chip_Radio) {
101    radio.state = !radio.state;
102  }
103
104  toggleCapture(device: Device, chip: Chip) {
105    if ('capture' in chip && chip.capture) {
106      chip.capture = !chip.capture;
107      simulationState.patchDevice({
108        device: {
109          name: device.name,
110          chips: device.chips,
111        },
112      });
113    }
114  }
115}
116
117/**
118 * The most recent state of the simulation.
119 * Subscribed observers must refer to this info and patch accordingly.
120 */
121export interface SimulationInfo {
122  devices: Device[];
123  captures: Capture[];
124  selectedId: string;
125  dimension: {x: number; y: number; z: number};
126  lastModified: string;
127}
128
129interface Observable {
130  registerObserver(elem: Notifiable): void;
131  removeObserver(elem: Notifiable): void;
132}
133
134class SimulationState implements Observable {
135  private observers: Notifiable[] = [];
136
137  private simulationInfo: SimulationInfo = {
138    devices: [],
139    captures: [],
140    selectedId: '',
141    dimension: {x: 10, y: 10, z: 0},
142    lastModified: '',
143  };
144
145  constructor() {
146    // initial GET
147    this.invokeGetDevice();
148    this.invokeListCaptures();
149  }
150
151  async invokeGetDevice() {
152    await fetch(DEVICES_URL, {
153      method: 'GET',
154    })
155        .then(response => response.json())
156        .then(data => {
157          this.fetchDevice(data.devices);
158          this.updateLastModified(data.lastModified);
159        })
160        .catch(error => {
161          // eslint-disable-next-line
162          console.log('Cannot connect to netsim web server', error);
163        });
164  }
165
166  async invokeListCaptures() {
167    await fetch(CAPTURES_URL, {
168      method: 'GET',
169    })
170        .then(response => response.json())
171        .then(data => {
172          this.simulationInfo.captures = data.captures;
173          this.notifyObservers();
174        })
175        .catch(error => {
176          console.log('Cannot connect to netsim web server', error);
177        });
178  }
179
180  fetchDevice(devices?: ProtoDevice[]) {
181    this.simulationInfo.devices = [];
182    if (devices) {
183      this.simulationInfo.devices = devices.map(device => new Device(device));
184    }
185    this.notifyObservers();
186  }
187
188  getLastModified() {
189    return this.simulationInfo.lastModified;
190  }
191
192  updateLastModified(timestamp: string) {
193    this.simulationInfo.lastModified = timestamp;
194  }
195
196  patchSelected(id: string) {
197    this.simulationInfo.selectedId = id;
198    this.notifyObservers();
199  }
200
201  handleDrop(id: string, x: number, y: number) {
202    for (const device of this.simulationInfo.devices) {
203      if (id === device.name) {
204        device.position = {x, y, z: device.position.z};
205        this.patchDevice({
206          device: {
207            name: device.name,
208            position: device.position,
209          },
210        });
211        break;
212      }
213    }
214  }
215
216  patchCapture(id: string, state: string) {
217    fetch(CAPTURES_URL + '/' + id, {
218      method: 'PATCH',
219      headers: {
220        'Content-Type': 'text/plain',
221        'Content-Length': state.length.toString(),
222      },
223      body: state,
224    });
225    this.notifyObservers();
226  }
227
228  patchDevice(obj: object) {
229    const jsonBody = JSON.stringify(obj);
230    fetch(DEVICES_URL, {
231      method: 'PATCH',
232      headers: {
233        'Content-Type': 'application/json',
234        'Content-Length': jsonBody.length.toString(),
235      },
236      body: jsonBody,
237    })
238        .then(response => response.json())
239        .catch(error => {
240          // eslint-disable-next-line
241          console.error('Error:', error);
242        });
243    this.notifyObservers();
244  }
245
246  registerObserver(elem: Notifiable) {
247    this.observers.push(elem);
248    elem.onNotify(this.simulationInfo);
249  }
250
251  removeObserver(elem: Notifiable) {
252    const index = this.observers.indexOf(elem);
253    this.observers.splice(index, 1);
254  }
255
256  notifyObservers() {
257    for (const observer of this.observers) {
258      observer.onNotify(this.simulationInfo);
259    }
260  }
261
262  getDeviceList() {
263    return this.simulationInfo.devices;
264  }
265}
266
267/** Subscribed observers must register itself to the simulationState */
268export const simulationState = new SimulationState();
269
270async function subscribeCaptures() {
271  const delay = (ms: number) => new Promise(res => setTimeout(res, ms));
272  while (true) {
273    await simulationState.invokeListCaptures();
274    await simulationState.invokeGetDevice();
275    await delay(1000);
276  }
277}
278
279async function subscribeDevices() {
280  await simulationState.invokeGetDevice();
281  while (true) {
282    const jsonBody = JSON.stringify({
283      lastModified: simulationState.getLastModified(),
284    });
285    await fetch(DEVICES_URL, {
286      method: 'SUBSCRIBE',
287      headers: {
288        'Content-Type': 'application/json',
289        'Content-Length': jsonBody.length.toString(),
290      },
291      body: jsonBody,
292    })
293        .then(response => response.json())
294        .then(data => {
295          simulationState.fetchDevice(data.devices);
296          simulationState.updateLastModified(data.lastModified);
297        })
298        .catch(error => {
299          // eslint-disable-next-line
300          console.log('Cannot connect to netsim web server', error);
301        });
302  }
303}
304
305subscribeCaptures();
306subscribeDevices();
307