import {css, html, LitElement, TemplateResult} from 'lit'; import {customElement, property} from 'lit/decorators.js'; import {live} from 'lit/directives/live.js'; import {styleMap} from 'lit/directives/style-map.js'; import {Device, Notifiable, SimulationInfo, simulationState,} from './device-observer.js'; import {Chip, Chip_BleBeacon, Chip_Bluetooth, Chip_Radio,} from './netsim/model.js'; enum ChipKind { UNSPECIFIED = 'UNSPECIFIED', BLUETOOTH = 'BLUETOOTH', WIFI = 'WIFI', UWB = 'UWB', BLUETOOTH_BEACON = 'BLUETOOTH_BEACON', UNRECOGNIZED = 'UNRECOGNIZED', } const disabledCheckbox = html` `; @customElement('ns-device-info') export class DeviceInformation extends LitElement implements Notifiable { // Selected Device on scene @property() selectedDevice: Device|undefined; /** * the yaw value in orientation for ns-cube-sprite * unit: deg */ @property({type: Number}) yaw = 0; /** * the pitch value in orientation for ns-cube-spriteß * unit: deg */ @property({type: Number}) pitch = 0; /** * the roll value in orientation for ns-cube-sprite * unit: deg */ @property({type: Number}) roll = 0; /** * The state of device info. True if edit mode. */ @property({type: Boolean}) editMode = false; /** * the x value in position for ns-cube-sprite * unit: cm */ @property({type: Number}) posX = 0; /** * the y value in position for ns-cube-sprite * unit: cm */ @property({type: Number}) posY = 0; /** * the z value in position for ns-cube-sprite * unit: cm */ @property({type: Number}) posZ = 0; holdRange = false; static styles = css` :host { cursor: pointer; display: grid; place-content: center; color: white; font-size: 25px; font-family: 'Lato', sans-serif; border: 5px solid black; border-radius: 12px; padding: 10px; background-color: #9199a5; max-width: 600px; } .title { font-weight: bold; text-transform: uppercase; text-align: center; margin-bottom: 10px; } .setting { display: grid; grid-template-columns: auto auto; margin-top: 0px; margin-bottom: 30px; //border: 3px solid black; padding: 10px; } .setting .name { grid-column: 1 / span 2; text-transform: uppercase; text-align: left; margin-bottom: 10px; font-weight: bold; } .label { grid-column: 1; text-align: left; } .info { grid-column: 2; text-align: right; margin-bottom: 10px; } .switch { position: relative; float: right; width: 60px; height: 34px; } .switch input { opacity: 0; width: 0; height: 0; } .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; -webkit-transition: 0.4s; transition: 0.4s; } .slider:before { position: absolute; content: ''; height: 26px; width: 26px; left: 4px; bottom: 4px; background-color: white; -webkit-transition: 0.4s; transition: 0.4s; } input:checked + .slider { background-color: #2196f3; } input:focus + .slider { box-shadow: 0 0 1px #2196f3; } input:checked + .slider:before { -webkit-transform: translateX(26px); -ms-transform: translateX(26px); transform: translateX(26px); } /* Rounded sliders */ .slider.round { border-radius: 34px; } .slider.round:before { border-radius: 50%; } .text { display: inline-block; position: relative; width: 50px; } input[type='range'] { width: 400px; } input[type='text'] { width: 50%; font-size: inherit; text-align: right; max-height: 25px; } input[type='text'].orientation { max-width: 50px; } input[type='button'] { display: inline; font-size: inherit; max-width: 200px; } `; connectedCallback() { super.connectedCallback(); // eslint-disable-line simulationState.registerObserver(this); } disconnectedCallback() { simulationState.removeObserver(this); super.disconnectedCallback(); // eslint-disable-line } onNotify(data: SimulationInfo) { if (data.selectedId && this.editMode === false) { for (const device of data.devices) { if (device.name === data.selectedId) { this.selectedDevice = device; if (!this.holdRange) { this.yaw = device.orientation.yaw; this.pitch = device.orientation.pitch; this.roll = device.orientation.roll; } this.posX = Math.floor(device.position.x * 100); this.posY = Math.floor(device.position.y * 100); this.posZ = Math.floor(device.position.z * 100); break; } } } } private changeRange(ev: InputEvent) { this.holdRange = true; console.assert(this.selectedDevice !== null); // eslint-disable-line const range = ev.target as HTMLInputElement; const event = new CustomEvent('orientationEvent', { detail: { name: this.selectedDevice?.name, type: range.id, value: range.value, }, }); window.dispatchEvent(event); if (range.id === 'yaw') { this.yaw = Number(range.value); } else if (range.id === 'pitch') { this.pitch = Number(range.value); } else { this.roll = Number(range.value); } } private patchOrientation() { this.holdRange = false; console.assert(this.selectedDevice !== undefined); // eslint-disable-line if (this.selectedDevice === undefined) return; this.selectedDevice.orientation = { yaw: this.yaw, pitch: this.pitch, roll: this.roll, }; simulationState.patchDevice({ device: { name: this.selectedDevice.name, orientation: this.selectedDevice.orientation, }, }); } private patchRadio() { console.assert(this.selectedDevice !== undefined); // eslint-disable-line if (this.selectedDevice === undefined) return; simulationState.patchDevice({ device: { name: this.selectedDevice.name, chips: this.selectedDevice.chips, }, }); } private handleEditForm() { if (this.editMode) { simulationState.invokeGetDevice(); this.editMode = false; } else { this.editMode = true; } } static checkPositionBound(value: number) { return value > 10 ? 10 : value < 0 ? 0 : value; // eslint-disable-line } static checkOrientationBound(value: number) { return value > 90 ? 90 : value < -90 ? -90 : value; // eslint-disable-line } private handleSave() { console.assert(this.selectedDevice !== undefined); // eslint-disable-line if (this.selectedDevice === undefined) return; const elements = this.renderRoot.querySelectorAll(`[id^="edit"]`); const obj: Record = { name: this.selectedDevice.name, position: this.selectedDevice.position, orientation: this.selectedDevice.orientation, }; elements.forEach(element => { const inputElement = element as HTMLInputElement; if (inputElement.id === 'editName') { obj.name = inputElement.value; } else if (inputElement.id.startsWith('editPos')) { if (!Number.isNaN(Number(inputElement.value))) { obj.position[inputElement.id.slice(7).toLowerCase()] = DeviceInformation.checkPositionBound( Number(inputElement.value) / 100); } } else if (inputElement.id.startsWith('editOri')) { if (!Number.isNaN(Number(inputElement.value))) { obj.orientation[inputElement.id.slice(7).toLowerCase()] = DeviceInformation.checkOrientationBound( Number(inputElement.value)); } } }); this.selectedDevice.name = obj.name; this.selectedDevice.position = obj.position; this.selectedDevice.orientation = obj.orientation; this.handleEditForm(); simulationState.patchDevice({ device: obj, }); } private handleGetBleBeacon(ble_beacon: Chip_BleBeacon) { const handleGetSettings = () => { if (!ble_beacon.settings) { return html``; } return html`
Settings
${ ble_beacon.settings.advertiseMode ? html`
Advertise Mode:
${ble_beacon.settings.advertiseMode?.replace('-', ' ')}
` : html`
Advertise Interval:
${ble_beacon.settings.milliseconds?.toString().concat(' ms')}
`} ${ ble_beacon.settings.txPowerLevel ? html`
Transmit Power Level:
${ble_beacon.settings.txPowerLevel?.replace('-', ' ')}
` : html`
Transmit Power:
${ble_beacon.settings.dbm?.toString().concat(' dBm')}
`}
Scannable:
${ble_beacon.settings.scannable}
Timeout:
${ble_beacon.settings.timeout?.toString().concat(' ms')}
`; }; const handleGetAdvData = () => { if (!ble_beacon.advData) { return html``; } return html`
Advertise Data
Include Device Name:
${ble_beacon.advData.includeDeviceName}
Include Transmit Power:
${ble_beacon.advData.includeTxPowerLevel}
${ ble_beacon.advData.manufacturerData.length ? html`
Manufacturer Data Length:
${ble_beacon.advData.manufacturerData.length}
` : html``} ${ ble_beacon.advData.services.length ? html`
Number of Supported Services:
${ble_beacon.advData.services.length}
` : html``}
`; }; return html`${handleGetSettings()} ${handleGetAdvData()}`; } private getRadioCheckbox = (radio: Chip_Radio, id: string) => { return html` `; }; private getBluetoothRadioCheckboxes(bt: Chip_Bluetooth) { let lowEnergyCheckbox = undefined; let classicCheckbox = undefined; if ('lowEnergy' in bt && bt.lowEnergy) { lowEnergyCheckbox = this.getRadioCheckbox(bt.lowEnergy, 'lowEnergy'); } if ('classic' in bt && bt.classic) { classicCheckbox = this.getRadioCheckbox(bt.classic, 'classic'); } return [lowEnergyCheckbox, classicCheckbox]; } private handleGetChip(chip: Chip, idx: number) { if (chip.bleBeacon) { let checkboxes: {[name: string]: undefined|TemplateResult} = {}; if (chip.bleBeacon.bt) { [checkboxes['Bluetooth LE'], checkboxes['Bluetooth Classic']] = this.getBluetoothRadioCheckboxes(chip.bleBeacon.bt); } return html`
Chip ${idx + 1}: ${chip.kind.replace('_', ' ')}
Name
${chip.name}
${ Object.entries(checkboxes).length ? html`
Radios
` : html``} ${ Object.entries(checkboxes) .map(([name, template]) => html`
${name}
${template}
`)}
${this.handleGetBleBeacon(chip.bleBeacon)}`; } else { return ``; } } private handleGetChips() { if (!(this.selectedDevice && this.selectedDevice.chips)) { return html``; } const isBuiltin = (chip: Chip) => chip.kind === ChipKind.BLUETOOTH_BEACON && chip.bleBeacon; // If any chip is a builtin, display individual chip information if (this.selectedDevice.chips.some(chip => isBuiltin(chip))) { return html`${ this.selectedDevice.chips.map( (chip, idx) => this.handleGetChip(chip, idx))}`; } // Otherwise, just display the radios let checkboxes: {[name: string]: undefined|TemplateResult} = {}; for (const chip of this.selectedDevice.chips) { if (chip) { if (chip.bt) { [checkboxes['Bluetooth LE'], checkboxes['Bluetooth Classic']] = this.getBluetoothRadioCheckboxes(chip.bt); } if (chip.wifi) { checkboxes['WIFI'] = this.getRadioCheckbox(chip.wifi, 'wifi'); } if (chip.uwb) { checkboxes['UWB'] = this.getRadioCheckbox(chip.uwb, 'uwb'); } } } if (Object.keys(checkboxes).length) { return html`
Radios
${ Object.entries(checkboxes) .map(([name, template]) => html`
${name}
${template}
`)}
`; } else { return html``; } } render() { return html`${ this.selectedDevice ? html`
Device Info
Name
${this.selectedDevice.name}
Position
X
${ this.editMode ? html`` : html`${this.posX}`}
Y
${ this.editMode ? html`` : html`${this.posY}`}
Z
${ this.editMode ? html`` : html`${this.posZ}`}
Orientation
Yaw
${ this.editMode ? html`` : html`
(${this.yaw})
`}
Pitch
${ this.editMode ? html`` : html`
(${this.pitch})
`}
Roll
${ this.editMode ? html`` : html`
(${this.roll})
`}
${ this.editMode ? html` ` : html``}
${this.handleGetChips()} ` : html`
Device Info
`}`; } }