import { ipcRenderer } from "electron"; import React from "react"; import { observable, action, runInAction, autorun, makeObservable } from "mobx"; import { observer } from "mobx-react"; const os = require("os"); import * as notification from "eez-studio-ui/notification"; import { objectClone } from "eez-studio-shared/util"; import { PropertyList, TextInputProperty, NumberInputProperty, SelectProperty } from "eez-studio-ui/properties"; import { Dialog, showDialog } from "eez-studio-ui/dialog"; import type { ConnectionParameters, SerialConnectionParameters } from "instrument/connection/interface"; import type * as UsbTmcModule from "instrument/connection/interfaces/usbtmc"; import { guid } from "eez-studio-shared/guid"; import { getSerialPorts } from "instrument/connection/interfaces/serial-ports-renderer"; function openLink(url: string) { const { shell } = require("electron"); shell.openExternal(url); } interface ConnectionPropertiesProps { connectionParameters: ConnectionParameters; onConnectionParametersChanged: ( connectionParameters: ConnectionParameters ) => void; availableConnections: ( | "ethernet" | "serial" | "usbtmc" | "visa" | "web-simulator" )[]; serialBaudRates: number[]; } class Devices { neverEnumerated = true; serialPortPaths: { path: string; description: string; uniqueId: string; }[] = []; usbDevices: { name?: string; idVendor: number; idProduct: number; }[] = []; constructor() { makeObservable(this, { serialPortPaths: observable, usbDevices: observable }); } } const devices = new Devices(); export const ConnectionProperties = observer( class ConnectionProperties extends React.Component { constructor(props: any) { super(props); makeObservable(this, { iface: observable, ethernetAddress: observable, ethernetPort: observable, serialParameters: observable, selectedUsbDeviceIndex: observable, idVendor: observable, idProduct: observable, visaResource: observable, visaResources: observable, timeout: observable, delay: observable, componentDidUpdate: action, onIfaceChange: action.bound, onEthernetAddressChange: action.bound, onEthernetPortChange: action.bound, onSerialPortPathChange: action.bound, onSerialPortBaudRateChange: action.bound, onSerialPortDataBitsChange: action.bound, onSerialPortStopBitsChange: action.bound, onSerialPortParityChange: action.bound, onSerialPortFlowControlChange: action.bound, onUsbDeviceChange: action.bound, initUsbDevices: action, onVisaResourceChange: action.bound, onTimeoutChange: action.bound, onDelayChange: action.bound, applyConnectionParameters: action }); this.applyConnectionParameters(this.props.connectionParameters); } div: HTMLDivElement; form: HTMLFormElement; iface: string; ethernetAddress: string; ethernetPort: number; serialParameters: SerialConnectionParameters; selectedUsbDeviceIndex: number = -1; idVendor: number; idProduct: number; visaResource: string; visaResources: string[] | undefined = []; timeout: number; delay: number; disposer: any; componentDidUpdate(prevProps: any) { if (this.props != prevProps) { this.applyConnectionParameters(this.props.connectionParameters); } } applyConnectionParameters(connectionParameters: ConnectionParameters) { if ( this.props.availableConnections.indexOf( connectionParameters.type ) != -1 ) { this.iface = connectionParameters.type; } else { this.iface = this.props.availableConnections[0]; } this.ethernetAddress = connectionParameters.ethernetParameters.address; this.ethernetPort = connectionParameters.ethernetParameters.port; this.serialParameters = Object.assign( {}, connectionParameters.serialParameters ); if (this.serialParameters.dataBits == undefined) { this.serialParameters.dataBits = 8; } if (this.serialParameters.stopBits == undefined) { this.serialParameters.stopBits = 1; } if (this.serialParameters.parity == undefined) { this.serialParameters.parity = "none"; } if (this.serialParameters.flowControl == undefined) { this.serialParameters.flowControl = "none"; } this.idVendor = connectionParameters.usbtmcParameters.idVendor; this.idProduct = connectionParameters.usbtmcParameters.idProduct; this.visaResource = connectionParameters.visaParameters.resource; this.timeout = connectionParameters.timeout ?? 60000; this.delay = connectionParameters.delay ?? 0; } async componentDidMount() { if (os.platform() !== "darwin") { if (devices.neverEnumerated) { devices.neverEnumerated = false; await this.refreshSerialPortPaths(); // TODO doesn't work on Raspbian if (process.arch != "arm") { await this.refreshUsbDevices(false); } } else { this.initUsbDevices(); } } await this.refreshVisaResources(false); $(this.div).modal(); $(this.div).on("hidden.bs.modal", () => { const parent = this.div.parentElement as HTMLElement; parent.remove(); }); this.disposer = autorun(() => { let connectionParameters: ConnectionParameters = objectClone( this.props.connectionParameters ); if (this.iface === "ethernet") { connectionParameters.type = "ethernet"; connectionParameters.ethernetParameters.address = this.ethernetAddress; connectionParameters.ethernetParameters.port = this.ethernetPort; } else if (this.iface === "serial") { connectionParameters.type = "serial"; connectionParameters.serialParameters = Object.assign( {}, this.serialParameters ); } else if (this.iface === "usbtmc") { connectionParameters.type = "usbtmc"; connectionParameters.usbtmcParameters.idVendor = this.selectedUsbDeviceIndex != -1 ? this.idVendor : 0; connectionParameters.usbtmcParameters.idProduct = this.selectedUsbDeviceIndex != -1 ? this.idProduct : 0; } else if (this.iface === "web-simulator") { connectionParameters.type = "web-simulator"; connectionParameters.webSimulatorParameters.id = guid(); } else { connectionParameters.type = "visa"; connectionParameters.visaParameters.resource = this.visaResources != undefined ? this.visaResource : ""; } connectionParameters.timeout = this.timeout; connectionParameters.delay = this.delay; this.props.onConnectionParametersChanged(connectionParameters); }); } componentWillUnmount() { if (this.disposer) { this.disposer(); } } onIfaceChange(value: string) { this.iface = value; } onEthernetAddressChange(value: string) { this.ethernetAddress = value; } onEthernetPortChange(value: number) { this.ethernetPort = value; } onSerialPortPathChange(value: string) { this.serialParameters.port = value; } onSerialPortBaudRateChange(value: string) { this.serialParameters.baudRate = parseInt(value); } onSerialPortDataBitsChange(value: string) { this.serialParameters.dataBits = parseInt(value) as any; } onSerialPortStopBitsChange(value: string) { this.serialParameters.stopBits = parseInt(value) as any; } onSerialPortParityChange(value: string) { this.serialParameters.parity = value as any; } onSerialPortFlowControlChange(value: string) { this.serialParameters.flowControl = value as any; } async refreshSerialPortPaths() { try { const serialPorts = await getSerialPorts(); runInAction(() => { let found; devices.serialPortPaths = [ { path: "", description: "", uniqueId: "" } ].concat( serialPorts.map(port => { if (this.serialParameters.port === port.path) { found = true; } return { path: port.path, description: port.path + (port.manufacturer ? " - " + port.manufacturer : "") + (port.productId ? " - " + port.productId : ""), uniqueId: port.pnpId || port.path }; }) ); if (!found) { this.serialParameters.port = ""; } }); } catch (err) { console.error(err); } } onRefreshSerialPortPaths = (event: React.MouseEvent) => { event.preventDefault(); this.refreshSerialPortPaths(); }; onUsbDeviceChange(value: number) { this.selectedUsbDeviceIndex = value; if (value >= 0 && value < devices.usbDevices.length) { this.idVendor = devices.usbDevices[value].idVendor; this.idProduct = devices.usbDevices[value].idProduct; } } initUsbDevices() { let selectedUsbDeviceIndex: number | undefined; for (let i = 0; i < devices.usbDevices.length; ++i) { if ( devices.usbDevices[i].idVendor === this.idVendor || devices.usbDevices[i].idProduct === this.idProduct ) { selectedUsbDeviceIndex = i; break; } } if (selectedUsbDeviceIndex == undefined) { selectedUsbDeviceIndex = -1; this.onUsbDeviceChange(selectedUsbDeviceIndex); } this.selectedUsbDeviceIndex = selectedUsbDeviceIndex; } async refreshUsbDevices(reportError: boolean) { const { getUsbDevices } = require("instrument/connection/interfaces/usbtmc") as typeof UsbTmcModule; try { const usbDevices = await getUsbDevices(); runInAction(() => { devices.usbDevices = usbDevices; }); this.initUsbDevices(); } catch (err) { if (reportError) { notification.error(err.toString()); } } } onRefreshUsbDevices = (event: React.MouseEvent) => { event.preventDefault(); this.refreshUsbDevices(true); }; refreshVisaResources(includeNetworkResources: boolean) { return new Promise(resolve => { ipcRenderer.send("get-visa-resources", includeNetworkResources); ipcRenderer.once("visa-resources", (event, args) => { runInAction(() => (this.visaResources = args)); resolve(); }); }); } onRefreshVisaResources = (event: React.MouseEvent) => { event.preventDefault(); this.refreshVisaResources(true); }; onVisaResourceChange(value: string) { this.visaResource = value; } onTimeoutChange(value: number) { this.timeout = value; } onDelayChange(value: number) { this.delay = value; } render() { let options: JSX.Element[] | null = null; if (this.iface === "ethernet") { options = [ , ]; } else if (this.iface === "serial") { options = [ Refresh } > {devices.serialPortPaths.map(serialPortPath => ( ))} , {this.props.serialBaudRates.map(baudRate => ( ))} , , , , ]; } else if (this.iface === "usbtmc") { options = [ this.onUsbDeviceChange(parseInt(optionValue)) } inputGroupButton={ } > {(() => { const options = devices.usbDevices.map( (usbDevice, i) => ( ) ); if (this.selectedUsbDeviceIndex == -1) { options.unshift( ); } return options; })()} ]; } else if (this.iface === "web-simulator") { options = []; } else { options = this.visaResources ? [ ) )} } /> ] : [
R&S® VISA was not found on your system. For more information on how to install R&S® VISA please visit{" "} { event.preventDefault(); openLink( "https://www.rohde-schwarz.com/fi/applications/r-s-visa-application-note_56280-148812.html" ); }} > this page .
]; } options.push( ); options.push( ); return ( {this.props.availableConnections.indexOf("ethernet") !== -1 && } {this.props.availableConnections.indexOf("serial") !== -1 && } {this.props.availableConnections.indexOf("usbtmc") !== -1 && } {this.props.availableConnections.indexOf( "web-simulator" ) !== -1 && ( )} {options} ); } } ); interface ConnectionDialogProps { connectionParameters: ConnectionParameters; connect: (connectionParameters: ConnectionParameters) => void; availableConnections: ( | "ethernet" | "serial" | "usbtmc" | "web-simulator" )[]; serialBaudRates: number[]; } const ConnectionDialog = observer( class ConnectionDialog extends React.Component { constructor(props: ConnectionDialogProps) { super(props); makeObservable(this, { connectionParameters: observable }); this.connectionParameters = this.props.connectionParameters; } connectionParameters: ConnectionParameters; onConnectionParametersChanged = action( (connectionParameters: ConnectionParameters) => { this.connectionParameters = connectionParameters; } ); isValidConnectionParameters = () => { if (!this.connectionParameters) { return false; } if (this.connectionParameters.type == "ethernet") { return ( this.connectionParameters.ethernetParameters?.address ?.length > 0 ); } if (this.connectionParameters.type == "serial") { return ( this.connectionParameters.serialParameters?.port?.length > 0 ); } if (this.connectionParameters.type == "usbtmc") { return ( this.connectionParameters.usbtmcParameters?.idVendor != 0 && this.connectionParameters.usbtmcParameters?.idProduct != 0 ); } if (this.connectionParameters.type == "web-simulator") { return true; } if (this.connectionParameters.type == "visa") { return ( this.connectionParameters.visaParameters?.resource?.length > 0 ); } return false; }; handleSubmit = () => { this.props.connect(this.connectionParameters); return true; }; render() { return ( ); } } ); export function showConnectionDialog( connectionParameters: ConnectionParameters, connect: (connectionParameters: ConnectionParameters) => void, availableConnections: ( | "ethernet" | "serial" | "usbtmc" | "web-simulator" )[], serialBaudRates: number[] ) { showDialog( ); }