import React from "react"; import { observable, makeObservable, runInAction, autorun } from "mobx"; import { Stream } from "stream"; import { Dialog, showDialog } from "eez-studio-ui/dialog"; import { parseScpi, SCPI_PART_EXPR, SCPI_PART_QUERY_WITH_ASSIGNMENT, SCPI_PART_STRING } from "eez-studio-shared/scpi-parser"; import type { InstrumentObject } from "instrument/instrument-object"; import type * as InstrumentObjectModule from "instrument/instrument-object"; import type { ConnectionParameters } from "instrument/connection/interface"; import type * as WaveformFormatModule from "eez-studio-ui/chart/WaveformFormat"; import type * as ActivityLogModule from "instrument/window/history/activity-log"; import type { IDashboardComponentContext } from "eez-studio-types"; import { registerClass, PropertyType, makeDerivedClassInfo, MessageType, ProjectType, IMessage } from "project-editor/core/object"; import { ActionComponent, ComponentOutput, makeExpressionProperty } from "project-editor/flow/component"; import type { IFlowContext } from "project-editor/flow//flow-interfaces"; import { Assets, DataBuffer } from "project-editor/build/assets"; import { getChildOfObject, getProjectStore, Message, ProjectStore } from "project-editor/store"; import { isNotDashboardProject } from "project-editor/project/project-type-traits"; import { buildExpression, buildAssignableExpression, checkExpression, checkAssignableExpression } from "project-editor/flow/expression"; import { IObjectVariableValue, IObjectVariableValueConstructorParams, isStructType, registerObjectVariableType, ValueType } from "project-editor/features/variable/value-type"; import type { IVariable } from "project-editor/flow/flow-interfaces"; import { specificGroup } from "project-editor/ui-components/PropertyGrid/groups"; import { COMPONENT_TYPE_SCPI_ACTION } from "project-editor/flow/components/component-types"; import { ProjectContext } from "project-editor/project/context"; import { ProjectEditor } from "project-editor/project-editor-interface"; import { findVariable } from "project-editor/project/project"; import { Instruments, InstrumentsStore } from "home/instruments"; import { observer } from "mobx-react"; import { AlertDanger } from "eez-studio-ui/alert"; import { Loader } from "eez-studio-ui/loader"; import { offWasmFlowRuntimeTerminate, onWasmFlowRuntimeTerminate } from "project-editor/flow/runtime/wasm-worker"; import { DashboardComponentContext } from "project-editor/flow/runtime/worker-dashboard-component-context"; import type { PlotlyLineChartExecutionState } from "../widgets/dashboard/plotly"; import type { TabulatorExecutionState } from "../widgets/dashboard/tabulator"; //////////////////////////////////////////////////////////////////////////////// export class SCPIActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_SCPI_ACTION, enabledInComponentPalette: (projectType: ProjectType) => projectType !== ProjectType.LVGL, properties: [ makeExpressionProperty( { name: "instrument", type: PropertyType.MultilineText, propertyGridGroup: specificGroup, disabled: isNotDashboardProject }, "object:Instrument" ), makeExpressionProperty( { name: "scpi", type: PropertyType.MultilineText, propertyGridGroup: specificGroup, monospaceFont: true, flowProperty: "scpi-template-literal", expressionType: undefined, getInstrumentId: (component: SCPIActionComponent) => { const projectStore = ProjectEditor.getProjectStore(component); const instrumentVariable = findVariable( projectStore.project, component.instrument ); if ( !instrumentVariable || instrumentVariable.type != "object:Instrument" ) { return undefined; } const value = projectStore.runtimeSettings.getVariableValue( instrumentVariable ); if ( !value || value.id == undefined || typeof value.id != "string" ) { return undefined; } return value.id; } }, "any" ), makeExpressionProperty( { name: "timeout", displayName: "Timeout (ms)", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "integer" ), makeExpressionProperty( { name: "delay", displayName: "Delay (ms)", formText: "Minimum delay between commands.", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "integer" ) ], beforeLoadHook: (component: SCPIActionComponent, jsObject: any) => { if (jsObject.scpi) { if (!jsObject.customInputs && !jsObject.customOutputs) { jsObject.customInputs = []; jsObject.customOutputs = []; try { const parts = parseScpi(jsObject.scpi); for (const part of parts) { const tag = part.tag; const str = part.value!; if (tag == SCPI_PART_EXPR) { const inputName = str.substring( 1, str.length - 1 ); if ( !jsObject.customInputs.find( (customInput: { name: string; type: PropertyType; }) => customInput.name == inputName ) ) { jsObject.customInputs.push({ name: inputName, type: "string" }); } } else if (tag == SCPI_PART_QUERY_WITH_ASSIGNMENT) { const outputName = str[0] == "{" ? str.substring(1, str.length - 1) : str; jsObject.customOutputs.push({ name: outputName, type: "any" }); } } } catch (err) {} } } if (jsObject.timeout == undefined) { jsObject.timeout = "null"; } if (jsObject.delay == undefined) { jsObject.delay = "null"; } }, check: (component: SCPIActionComponent, messages: IMessage[]) => { try { const parts = parseScpi(component.scpi); for (const part of parts) { const tag = part.tag; const str = part.value!; if (tag == SCPI_PART_EXPR) { try { const expr = str.substring(1, str.length - 1); checkExpression(component, expr); } catch (err) { messages.push( new Message( MessageType.ERROR, `Invalid expression: ${err}`, getChildOfObject(component, "scpi") ) ); } } else if (tag == SCPI_PART_QUERY_WITH_ASSIGNMENT) { try { const assignableExpression = str[0] == "{" ? str.substring(1, str.length - 1) : str; checkAssignableExpression( component, assignableExpression ); } catch (err) { messages.push( new Message( MessageType.ERROR, `Invalid assignable expression: ${err}`, getChildOfObject(component, "scpi") ) ); } } } } catch (err) { messages.push( new Message( MessageType.ERROR, `Invalid SCPI: ${err}`, getChildOfObject(component, "scpi") ) ); } }, label: (component: SCPIActionComponent) => { const project = getProjectStore(component).project; if (project.projectTypeTraits.isDashboard && component.instrument) { return `SCPI ${component.instrument}`; } return "SCPI"; }, componentPaletteLabel: "SCPI", icon: ( ), componentHeaderColor: "#FDD0A2", componentPaletteGroupName: "Instrument" }); instrument: string; scpi: string; timeout: string; delay: string; override makeEditable() { super.makeEditable(); makeObservable(this, { instrument: observable, scpi: observable, timeout: observable, delay: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: true }, ...super.getInputs() ]; } getOutputs() { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, ...super.getOutputs() ]; } getBody(flowContext: IFlowContext): React.ReactNode { return (
{this.scpi}
); } buildFlowComponentSpecific(assets: Assets, dataBuffer: DataBuffer) { const parts = parseScpi(this.scpi); for (const part of parts) { dataBuffer.writeUint8(part.tag); const str = part.value!; if (part.tag == SCPI_PART_STRING) { dataBuffer.writeUint16NonAligned(str.length); for (const ch of str) { dataBuffer.writeUint8(ch.codePointAt(0)!); } } else if (part.tag == SCPI_PART_EXPR) { const expression = str.substring(1, str.length - 1); buildExpression(assets, dataBuffer, this, expression); } else if (part.tag == SCPI_PART_QUERY_WITH_ASSIGNMENT) { const lValueExpression = str[0] == "{" ? str.substring(1, str.length - 1) : str; buildAssignableExpression( assets, dataBuffer, this, lValueExpression ); } } } } registerClass("SCPIActionComponent", SCPIActionComponent); //////////////////////////////////////////////////////////////////////////////// export class SelectInstrumentActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { properties: [], icon: ( ), componentHeaderColor: "#FDD0A2", componentPaletteGroupName: "Instrument", execute: async (context: IDashboardComponentContext) => { try { context.startAsyncExecution(); const instrument = await showSelectInstrumentDialog( undefined, undefined, undefined, false ); context.endAsyncExecution(); if (instrument) { context.propagateValue("instrument", instrument); } context.propagateValueThroughSeqout(); } catch (err) { context.endAsyncExecution(); context.throwError(err.toString()); } } }); getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: false }, ...super.getInputs() ]; } getOutputs(): ComponentOutput[] { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, { name: "instrument", type: "object:Instrument", isSequenceOutput: false, isOptionalOutput: false }, ...super.getOutputs() ]; } } registerClass( "SelectInstrumentActionComponent", SelectInstrumentActionComponent ); //////////////////////////////////////////////////////////////////////////////// export class GetInstrumentActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { properties: [ makeExpressionProperty( { name: "instrumentId", displayName: "Instrument ID", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "string" ) ], icon: ( ), componentHeaderColor: "#FDD0A2", componentPaletteGroupName: "Instrument", execute: (context: IDashboardComponentContext) => { const instrumentId = context.evalProperty("instrumentId"); if (instrumentId == undefined || typeof instrumentId != "string") { context.throwError(`Invalid Instrument ID property`); return; } const { instruments } = require("instrument/instrument-object") as typeof InstrumentObjectModule; const instrument = instruments.get(instrumentId); if (!instrument) { context.throwError("Instrument not found"); return; } context.propagateValue("instrument", instrument); context.propagateValueThroughSeqout(); } }); instrumentId: string; override makeEditable() { super.makeEditable(); makeObservable(this, { instrumentId: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: false }, ...super.getInputs() ]; } getOutputs(): ComponentOutput[] { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, { name: "instrument", type: "object:Instrument", isSequenceOutput: false, isOptionalOutput: false }, ...super.getOutputs() ]; } } registerClass("GetInstrumentActionComponent", GetInstrumentActionComponent); //////////////////////////////////////////////////////////////////////////////// export class ConnectInstrumentActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { properties: [ makeExpressionProperty( { name: "instrument", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "object:Instrument" ) ], icon: ( ), componentHeaderColor: "#FDD0A2", componentPaletteGroupName: "Instrument", execute: (context: IDashboardComponentContext) => { interface InstrumentVariableTypeConstructorParams { id: string; } const instrument = context.evalProperty( "instrument" ); if (instrument == undefined || typeof instrument.id != "string") { context.throwError(`Invalid instrument property`); return; } const { instruments } = require("instrument/instrument-object") as typeof InstrumentObjectModule; const instrumentObject = instruments.get(instrument.id); if (!instrumentObject) { context.throwError(`Instrument not found`); return; } instrumentObject.connection.connect(); context.propagateValueThroughSeqout(); } }); instrument: string; override makeEditable() { super.makeEditable(); makeObservable(this, { instrument: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: false }, ...super.getInputs() ]; } getOutputs() { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, ...super.getOutputs() ]; } getBody(flowContext: IFlowContext): React.ReactNode { if (!this.instrument) { return null; } if (this.customInputs.find(input => input.name == this.instrument)) { return null; } return (
{this.instrument}
); } } registerClass( "ConnectInstrumentActionComponent", ConnectInstrumentActionComponent ); //////////////////////////////////////////////////////////////////////////////// export class DisconnectInstrumentActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { properties: [ makeExpressionProperty( { name: "instrument", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "object:Instrument" ) ], icon: ( ), componentHeaderColor: "#FDD0A2", componentPaletteGroupName: "Instrument", execute: (context: IDashboardComponentContext) => { interface InstrumentVariableTypeConstructorParams { id: string; } const instrument = context.evalProperty( "instrument" ); if (instrument == undefined || typeof instrument.id != "string") { context.throwError(`Invalid instrument property`); return; } const { instruments } = require("instrument/instrument-object") as typeof InstrumentObjectModule; const instrumentObject = instruments.get(instrument.id); if (!instrumentObject) { context.throwError(`Instrument not found`); return; } instrumentObject.connection.disconnect(); context.propagateValueThroughSeqout(); } }); instrument: string; override makeEditable() { super.makeEditable(); makeObservable(this, { instrument: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: false }, ...super.getInputs() ]; } getOutputs() { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, ...super.getOutputs() ]; } getBody(flowContext: IFlowContext): React.ReactNode { if (!this.instrument) { return null; } if (this.customInputs.find(input => input.name == this.instrument)) { return null; } return (
{this.instrument}
); } } registerClass( "DisconnectInstrumentActionComponent", DisconnectInstrumentActionComponent ); //////////////////////////////////////////////////////////////////////////////// export class InstrumentRead extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { properties: [ makeExpressionProperty( { name: "instrument", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "object:Instrument" ) ], icon: ( ), componentHeaderColor: "#FDD0A2", componentPaletteGroupName: "Instrument", execute: (context: IDashboardComponentContext) => { interface InstrumentVariableTypeConstructorParams { id: string; } const instrument = context.evalProperty( "instrument" ); if (instrument == undefined || typeof instrument.id != "string") { context.throwError(`Invalid instrument property`); return; } const { instruments } = require("instrument/instrument-object") as typeof InstrumentObjectModule; const instrumentObject = instruments.get(instrument.id); if (!instrumentObject) { context.throwError(`Instrument not found`); return; } context = context.startAsyncExecution(); const readableStream = new Stream.Readable(); readableStream._read = () => {}; readableStream.on("close", () => { context.endAsyncExecution(); connection.offRead(onReadCallback); }); context.propagateValue("data", readableStream); const connection = instrumentObject.connection; const onReadCallback = (data: string | undefined) => { if (data) { readableStream.push(data); } else { readableStream.destroy(); } }; connection.onRead(onReadCallback); const onTerminateCallback = () => { offWasmFlowRuntimeTerminate(onTerminateCallback); connection.offRead(onReadCallback); }; onWasmFlowRuntimeTerminate(onTerminateCallback); context.propagateValueThroughSeqout(); } }); instrument: string; override makeEditable() { super.makeEditable(); makeObservable(this, { instrument: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: false }, ...super.getInputs() ]; } getOutputs() { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, { name: "data", type: "stream" as ValueType, isSequenceOutput: false, isOptionalOutput: false }, ...super.getOutputs() ]; } getBody(flowContext: IFlowContext): React.ReactNode { if (!this.instrument) { return null; } if (this.customInputs.find(input => input.name == this.instrument)) { return null; } return (
{this.instrument}
); } } registerClass("InstrumentRead", InstrumentRead); //////////////////////////////////////////////////////////////////////////////// export class InstrumentWrite extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { properties: [ makeExpressionProperty( { name: "instrument", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "object:Instrument" ), makeExpressionProperty( { name: "data", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "string" ) ], icon: ( ), componentHeaderColor: "#FDD0A2", componentPaletteGroupName: "Instrument", execute: async (context: IDashboardComponentContext) => { interface InstrumentVariableTypeConstructorParams { id: string; } const instrument = context.evalProperty( "instrument" ); if (instrument == undefined || typeof instrument.id != "string") { context.throwError(`Invalid instrument property`); return; } const { instruments } = require("instrument/instrument-object") as typeof InstrumentObjectModule; const instrumentObject = instruments.get(instrument.id); if (!instrumentObject) { context.throwError(`Instrument not found`); return; } const data = context.evalProperty("data"); if (typeof data != "string") { context.throwError(`Data is not a string`); return; } context.startAsyncExecution(); const connection = instrumentObject.connection; try { await connection.acquire(false); try { await connection.command(data); context.endAsyncExecution(); } finally { connection.release(); } } catch (err) { context.endAsyncExecution(); context.throwError(err.toString()); } context.propagateValueThroughSeqout(); } }); instrument: string; data: string; override makeEditable() { super.makeEditable(); makeObservable(this, { instrument: observable, data: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: false }, ...super.getInputs() ]; } getOutputs() { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, ...super.getOutputs() ]; } getBody(flowContext: IFlowContext): React.ReactNode { if (!this.instrument) { return null; } if (this.customInputs.find(input => input.name == this.instrument)) { return null; } return (
{this.instrument}
{this.data}
); } } registerClass("InstrumentWrite", InstrumentWrite); //////////////////////////////////////////////////////////////////////////////// export class GetInstrumentPropertiesActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { properties: [ makeExpressionProperty( { name: "instrument", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "object:Instrument" ) ], defaultValue: { customOutputs: [ { name: "properties", type: "any" } ] }, icon: ( ), componentHeaderColor: "#FDD0A2", componentPaletteGroupName: "Instrument", check: ( component: GetInstrumentPropertiesActionComponent, messages: IMessage[] ) => { const output = component.customOutputs.find( output => output.name == "properties" ); if (output) { if (!isStructType(output.type)) { messages.push( new Message( MessageType.ERROR, `Output "properties" must be of struct type`, component ) ); } } else { messages.push( new Message( MessageType.ERROR, `Output "properties" not found`, component ) ); } }, execute: (context: IDashboardComponentContext) => { interface InstrumentVariableTypeConstructorParams { id: string; } const instrument = context.evalProperty( "instrument" ); if (instrument == undefined || typeof instrument.id != "string") { context.throwError(`Invalid instrument property`); return; } const { instruments } = require("instrument/instrument-object") as typeof InstrumentObjectModule; const instrumentObject = instruments.get(instrument.id); if (!instrumentObject) { context.throwError(`Instrument ${instrument.id} not found`); return; } context.propagateValue("properties", instrumentObject.properties); context.propagateValueThroughSeqout(); } }); instrument: string; override makeEditable() { super.makeEditable(); makeObservable(this, { instrument: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: false }, ...super.getInputs() ]; } getOutputs(): ComponentOutput[] { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, ...super.getOutputs() ]; } } registerClass( "GetInstrumentPropertiesActionComponent", GetInstrumentPropertiesActionComponent ); //////////////////////////////////////////////////////////////////////////////// export class AddToInstrumentHistoryActionComponent extends ActionComponent { static ITEM_TYPE_NONE = 0; static ITEM_TYPE_EEZ_CHART = 1; static ITEM_TYPE_WIDGET = 2; static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { properties: [ makeExpressionProperty( { name: "instrument", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "object:Instrument" ), { name: "itemType", type: PropertyType.Enum, enumItems: [ { id: "chart", label: "EEZ-Chart" }, { id: "widget", label: "Widget" } ], propertyGridGroup: specificGroup }, makeExpressionProperty( { name: "chartDescription", type: PropertyType.MultilineText, propertyGridGroup: specificGroup, disabled: ( component: AddToInstrumentHistoryActionComponent ) => { return component.itemType != "chart"; } }, "string" ), makeExpressionProperty( { name: "chartData", type: PropertyType.MultilineText, propertyGridGroup: specificGroup, disabled: ( component: AddToInstrumentHistoryActionComponent ) => { return component.itemType != "chart"; } }, "blob" ), makeExpressionProperty( { name: "chartSamplingRate", type: PropertyType.MultilineText, propertyGridGroup: specificGroup, disabled: ( component: AddToInstrumentHistoryActionComponent ) => { return component.itemType != "chart"; } }, "float" ), makeExpressionProperty( { name: "chartOffset", type: PropertyType.MultilineText, propertyGridGroup: specificGroup, disabled: ( component: AddToInstrumentHistoryActionComponent ) => { return component.itemType != "chart"; } }, "double" ), makeExpressionProperty( { name: "chartScale", type: PropertyType.MultilineText, propertyGridGroup: specificGroup, disabled: ( component: AddToInstrumentHistoryActionComponent ) => { return component.itemType != "chart"; } }, "double" ), makeExpressionProperty( { name: "chartFormat", type: PropertyType.MultilineText, propertyGridGroup: specificGroup, disabled: ( component: AddToInstrumentHistoryActionComponent ) => { return component.itemType != "chart"; }, formText: `"float", "double", "rigol-byte", "rigol-word", "csv"` }, "string" ), makeExpressionProperty( { name: "chartUnit", type: PropertyType.MultilineText, propertyGridGroup: specificGroup, disabled: ( component: AddToInstrumentHistoryActionComponent ) => { return component.itemType != "chart"; }, formText: `"voltage", "current", "watt", "power", "time", "frequency", "joule"` }, "integer" ), makeExpressionProperty( { name: "chartColor", type: PropertyType.MultilineText, propertyGridGroup: specificGroup, disabled: ( component: AddToInstrumentHistoryActionComponent ) => { return component.itemType != "chart"; } }, "string" ), makeExpressionProperty( { name: "chartColorInverse", type: PropertyType.MultilineText, propertyGridGroup: specificGroup, disabled: ( component: AddToInstrumentHistoryActionComponent ) => { return component.itemType != "chart"; } }, "string" ), makeExpressionProperty( { name: "chartLabel", type: PropertyType.MultilineText, propertyGridGroup: specificGroup, disabled: ( component: AddToInstrumentHistoryActionComponent ) => { return component.itemType != "chart"; } }, "string" ), makeExpressionProperty( { name: "chartMajorSubdivisionHorizontal", type: PropertyType.MultilineText, propertyGridGroup: specificGroup, disabled: ( component: AddToInstrumentHistoryActionComponent ) => { return component.itemType != "chart"; } }, "integer" ), makeExpressionProperty( { name: "chartMajorSubdivisionVertical", type: PropertyType.MultilineText, propertyGridGroup: specificGroup, disabled: ( component: AddToInstrumentHistoryActionComponent ) => { return component.itemType != "chart"; } }, "integer" ), makeExpressionProperty( { name: "chartMinorSubdivisionHorizontal", type: PropertyType.MultilineText, propertyGridGroup: specificGroup, disabled: ( component: AddToInstrumentHistoryActionComponent ) => { return component.itemType != "chart"; } }, "integer" ), makeExpressionProperty( { name: "chartMinorSubdivisionVertical", type: PropertyType.MultilineText, propertyGridGroup: specificGroup, disabled: ( component: AddToInstrumentHistoryActionComponent ) => { return component.itemType != "chart"; } }, "integer" ), makeExpressionProperty( { name: "chartHorizontalScale", type: PropertyType.MultilineText, propertyGridGroup: specificGroup, disabled: ( component: AddToInstrumentHistoryActionComponent ) => { return component.itemType != "chart"; } }, "double" ), makeExpressionProperty( { name: "chartVerticalScale", type: PropertyType.MultilineText, propertyGridGroup: specificGroup, disabled: ( component: AddToInstrumentHistoryActionComponent ) => { return component.itemType != "chart"; } }, "double" ), makeExpressionProperty( { name: "widget", type: PropertyType.MultilineText, propertyGridGroup: specificGroup, disabled: ( component: AddToInstrumentHistoryActionComponent ) => { return component.itemType != "widget"; } }, "widget" ) ], defaultValue: {}, beforeLoadHook: ( component: AddToInstrumentHistoryActionComponent, jsObject: any ) => { if ( jsObject.itemType == "plotly" || jsObject.itemType == "tabulator" ) { jsObject.itemType = "widget"; } if (jsObject.plotlyWidget != undefined) { jsObject.widget = jsObject.plotlyWidget; delete jsObject.plotlyWidget; } }, icon: ( ), componentHeaderColor: "#FDD0A2", componentPaletteGroupName: "Instrument", execute: (context: IDashboardComponentContext) => { interface InstrumentVariableTypeConstructorParams { id: string; } const instrument = context.evalProperty( "instrument" ); if (instrument == undefined || typeof instrument.id != "string") { context.throwError(`Invalid instrument property`); return; } const itemType = context.getUint8Param(0); let message; let chartData; let historyItemType; if ( itemType == AddToInstrumentHistoryActionComponent.ITEM_TYPE_EEZ_CHART ) { const chartDescription = context.evalProperty("chartDescription"); if (chartDescription == undefined) { context.throwError(`Invalid Chart description property`); return; } const chartData = context.evalProperty("chartData"); if (chartData == undefined) { context.throwError(`Invalid Chart data property`); return; } const chartSamplingRate = context.evalProperty("chartSamplingRate"); if (chartSamplingRate == undefined) { context.throwError(`Invalid Chart sampling rate property`); return; } const chartOffset = context.evalProperty("chartOffset"); if (chartOffset == undefined) { context.throwError(`Invalid Chart offet property`); return; } const chartScale = context.evalProperty("chartScale"); if (chartScale == undefined) { context.throwError(`Invalid Chart scale property`); return; } const chartFormatStr = context.evalProperty("chartFormat"); if (chartFormatStr == undefined) { context.throwError(`Invalid Chart format property`); return; } const { WaveformFormat } = require("eez-studio-ui/chart/WaveformFormat") as typeof WaveformFormatModule; let chartFormat = chartFormatStr == "float" ? WaveformFormat.FLOATS_32BIT : chartFormatStr == "double" ? WaveformFormat.FLOATS_64BIT : chartFormatStr == "rigol-byte" ? WaveformFormat.RIGOL_BYTE : chartFormatStr == "rigol-word" ? WaveformFormat.RIGOL_WORD : chartFormatStr == "csv" ? WaveformFormat.CSV_STRING : WaveformFormat.JS_NUMBERS; const chartUnit = context.evalProperty("chartUnit"); if (chartUnit == undefined) { context.throwError(`Invalid Chart unit property`); return; } const chartColor = context.evalProperty("chartColor"); if (chartColor == undefined) { context.throwError(`Invalid Chart color property`); return; } const chartColorInverse = context.evalProperty("chartColorInverse"); if (chartColorInverse == undefined) { context.throwError(`Invalid Chart color inverse property`); return; } const chartLabel = context.evalProperty("chartLabel"); if (chartLabel == undefined) { context.throwError(`Invalid Chart label property`); return; } const chartMajorSubdivisionHorizontal = context.evalProperty( "chartMajorSubdivisionHorizontal" ); if (chartMajorSubdivisionHorizontal == undefined) { context.throwError( `Invalid Chart major subdivision horizontal property` ); return; } const chartMajorSubdivisionVertical = context.evalProperty( "chartMajorSubdivisionVertical" ); if (chartMajorSubdivisionVertical == undefined) { context.throwError( `Invalid Chart major subdivision vertical property` ); return; } const chartMinorSubdivisionHorizontal = context.evalProperty( "chartMinorSubdivisionHorizontal" ); if (chartMinorSubdivisionHorizontal == undefined) { context.throwError( `Invalid Chart minor subdivision horizontal property` ); return; } const chartMinorSubdivisionVertical = context.evalProperty( "chartMinorSubdivisionVertical" ); if (chartMinorSubdivisionVertical == undefined) { context.throwError( `Invalid Chart minor subdivision vertical property` ); return; } const chartHorizontalScale = context.evalProperty( "chartHorizontalScale" ); if (chartHorizontalScale == undefined) { context.throwError( `Invalid Chart horizontal scale property` ); return; } const chartVerticalScale = context.evalProperty("chartVerticalScale"); if (chartVerticalScale == undefined) { context.throwError(`Invalid Chart vertical scale property`); return; } const message: any = { state: "success", fileType: { mime: "application/eez-raw" }, description: chartDescription, waveformDefinition: { samplingRate: chartSamplingRate, format: chartFormat, unitName: chartUnit.toLowerCase(), color: chartColor, colorInverse: chartColorInverse, label: chartLabel, offset: chartOffset, scale: chartScale }, viewOptions: { axesLines: { type: "fixed", majorSubdivision: { horizontal: chartMajorSubdivisionHorizontal, vertical: chartMajorSubdivisionVertical }, minorSubdivision: { horizontal: chartMinorSubdivisionHorizontal, vertical: chartMinorSubdivisionVertical } } }, horizontalScale: chartHorizontalScale, verticalScale: chartVerticalScale }; message.dataLength = chartData.length; historyItemType = "instrument/file-download"; } else if ( itemType == AddToInstrumentHistoryActionComponent.ITEM_TYPE_WIDGET ) { const widget = context.evalProperty("widget"); if (widget == undefined) { context.throwError(`Invalid widget property`); return; } const widgetInfo = context.WasmFlowRuntime.getWidgetHandleInfo(widget); if (!widgetInfo) { context.throwError(`Invalid widget handle`); return; } const widgetContext = new DashboardComponentContext( context.WasmFlowRuntime, widgetInfo.flowStateIndex, widgetInfo.componentIndex ); const executionState = widgetContext.getComponentExecutionState< PlotlyLineChartExecutionState | TabulatorExecutionState >(); if (!executionState || !executionState.getInstrumentItemData) { context.throwError(`Invalid Plotly widget execution state`); return; } const instrumentItemData = executionState.getInstrumentItemData(); historyItemType = instrumentItemData.itemType; message = instrumentItemData.message; chartData = undefined; } else { context.throwError("Invalid item type"); return; } const { activityLogStore, log } = require("instrument/window/history/activity-log") as typeof ActivityLogModule; const historyId = log( activityLogStore, { oid: instrument.id, type: historyItemType, message: JSON.stringify(message), data: chartData, temporary: false }, { undoable: false } ); context.propagateValue("id", historyId); context.propagateValueThroughSeqout(); } }); instrument: string; itemType: string; chartDescription: string; chartData: string; chartSamplingRate: string; chartOffset: string; chartScale: string; chartFormat: string; chartUnit: string; chartColor: string; chartColorInverse: string; chartLabel: string; chartMajorSubdivisionHorizontal: string; chartMajorSubdivisionVertical: string; chartMinorSubdivisionHorizontal: string; chartMinorSubdivisionVertical: string; chartHorizontalScale: string; chartVerticalScale: string; widget: string; override makeEditable() { super.makeEditable(); makeObservable(this, { instrument: observable, itemType: observable, chartDescription: observable, chartData: observable, chartSamplingRate: observable, chartOffset: observable, chartScale: observable, chartFormat: observable, chartUnit: observable, chartColor: observable, chartColorInverse: observable, chartLabel: observable, chartMajorSubdivisionHorizontal: observable, chartMajorSubdivisionVertical: observable, chartMinorSubdivisionHorizontal: observable, chartMinorSubdivisionVertical: observable, chartHorizontalScale: observable, chartVerticalScale: observable, widget: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: false }, ...super.getInputs() ]; } getOutputs(): ComponentOutput[] { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, { name: "id", type: "string", isSequenceOutput: false, isOptionalOutput: true }, ...super.getOutputs() ]; } buildFlowComponentSpecific(assets: Assets, dataBuffer: DataBuffer) { if (this.itemType == "chart") { dataBuffer.writeUint8( AddToInstrumentHistoryActionComponent.ITEM_TYPE_EEZ_CHART ); } else if (this.itemType == "widget") { dataBuffer.writeUint8( AddToInstrumentHistoryActionComponent.ITEM_TYPE_WIDGET ); } else { dataBuffer.writeUint8( AddToInstrumentHistoryActionComponent.ITEM_TYPE_NONE ); } } } registerClass( "AddToInstrumentHistoryActionComponent", AddToInstrumentHistoryActionComponent ); //////////////////////////////////////////////////////////////////////////////// const Connection = observer( class Connection extends React.Component<{ instrumentsStore: InstrumentsStore; }> { dismissError = () => { const instrument = this.props.instrumentsStore.selectedInstrument; if (!instrument) { return; } instrument.connection.dismissError(); }; render() { const instrument = this.props.instrumentsStore.selectedInstrument; if (!instrument) { return null; } let connection = instrument.connection; let info; let error; let connectionParameters; let abort; if (connection) { if (connection.isIdle) { error = connection.error && ( {connection.error} ); const { ConnectionProperties } = require("instrument/window/connection-dialog") as typeof import("instrument/window/connection-dialog"); connectionParameters = ( { this.props.instrumentsStore.connectionParameters = connectionParameters; }} availableConnections={ instrument.availableConnections } serialBaudRates={instrument.serialBaudRates} /> ); } else { if (connection.isTransitionState) { info = ; } const { ConnectionParametersDetails } = require("home/instruments/instrument-object-details") as typeof import("home/instruments/instrument-object-details"); connectionParameters = ( ); abort = ( ); } } return (
{info} {error} {connectionParameters} {abort}
); } } ); export const SelectInstrumentDialog = observer( class SelectInstrumentDialog extends React.Component<{ projectStore?: ProjectStore; instrumentsStore: InstrumentsStore; selectAndConnect?: boolean; resolve: (instrument: InstrumentObject | undefined) => void; }> { connectToInstrument: boolean = false; connectionParameters: ConnectionParameters | null; dispose: any; constructor(props: any) { super(props); makeObservable(this, { connectToInstrument: observable }); } componentDidMount() { this.dispose = autorun(() => { if ( this.connectToInstrument && this.props.instrumentsStore.selectedInstrument && this.props.instrumentsStore.selectedInstrument.isConnected ) { this.props.resolve( this.props.instrumentsStore.selectedInstrument ); } }); this.props.instrumentsStore.onSelectInstrument = this.onOk; } componentWillUnmount() { this.dispose(); this.props.instrumentsStore.onSelectInstrument = undefined; } onOk = () => { const instrument = this.props.instrumentsStore.selectedInstrument; if (instrument) { if (this.connectToInstrument) { this.props.instrumentsStore.selectedInstrumentConnect(); } else { if ( this.props.selectAndConnect && !instrument.isConnected ) { runInAction(() => (this.connectToInstrument = true)); } else { this.props.resolve(instrument); } } } }; render() { const instrument = this.props.instrumentsStore.selectedInstrument; let content; if (this.connectToInstrument && instrument) { content = (
{instrument.connection?.isIdle ? `Connect to ${instrument.name}` : `Connecting to ${instrument.name} ...`}
); } else { content = ( ); } const dialog = ( { if (this.props.instrumentsStore.selectedInstrumentId) { if (!this.connectToInstrument) { return true; } if ( this.props.instrumentsStore .selectedInstrument && this.props.instrumentsStore.selectedInstrument .connection && this.props.instrumentsStore.selectedInstrument .connection.isIdle ) { return true; } } return false; }} onOk={this.onOk} okButtonText={ this.connectToInstrument && instrument ? "Conect" : "Select" } cancelButtonText={ this.connectToInstrument && instrument ? "Back" : "Cancel" } onCancel={() => { if (this.connectToInstrument) { runInAction(() => { this.connectToInstrument = false; }); } else { this.props.resolve(undefined); } }} > {content} ); if (this.props.projectStore) { return ( {dialog} ); } else { return dialog; } } } ); export async function showSelectInstrumentDialog( projectStore: ProjectStore | undefined, name?: string, instrumentId?: string | undefined, selectAndConnect?: boolean ) { return new Promise(resolve => { const instrumentsStore = new InstrumentsStore(true); instrumentsStore.selectedInstrumentId = instrumentId; const [modalDialog] = showDialog( { if (instrument) { resolve(instrument); } modalDialog.close(); }} />, { jsPanel: { id: "select-instrument-5", title: name ? `Select: ${name}` : "Select Instrument", width: 920, height: 680 } } ); }); } //////////////////////////////////////////////////////////////////////////////// async function connectToInstrument(instrument: InstrumentObject) { if (!instrument.lastConnection) { return; } const connection = instrument.connection; connection.connect(); for (let i = 0; i < 10; i++) { try { await connection.acquire(false); connection.release(); return; } catch (err) { await new Promise(resolve => setTimeout(resolve, 100)); } } } type InstrumentConstructorParams = "string" | { id: "string" }; function getInstrumentIdFromConstructorParams( constructorParams: InstrumentConstructorParams | undefined ) { return constructorParams == null ? null : typeof constructorParams === "string" ? constructorParams : (constructorParams.id as string); } registerObjectVariableType("Instrument", { editConstructorParams: async ( variable: IVariable, constructorParams?: InstrumentConstructorParams, runtime?: boolean ): Promise => { let instrument = await showSelectInstrumentDialog( ProjectEditor.getProjectStore(variable as any), variable.description || variable.fullName, getInstrumentIdFromConstructorParams(constructorParams) ?? undefined, !(runtime === false) ); return instrument ? { id: instrument.id } : undefined; }, createValue: ( constructorParams: InstrumentConstructorParams, isRuntime: boolean ) => { const instrumentId = getInstrumentIdFromConstructorParams(constructorParams); if (instrumentId) { const { instruments } = require("instrument/instrument-object") as typeof InstrumentObjectModule; const instrument = instruments.get(instrumentId); if (instrument) { if (isRuntime) { connectToInstrument(instrument); } return instrument; } } return { constructorParams, status: { label: "Unknown instrument", error: `Instrument with ID [${constructorParams}] is not found` } }; }, destroyValue: (value: IObjectVariableValue) => {}, getValue: (variableValue: any): IObjectVariableValue | null => { const { instruments } = require("instrument/instrument-object") as typeof InstrumentObjectModule; return instruments.get(variableValue.id) ?? null; }, valueFieldDescriptions: [ { name: "id", valueType: "string", getFieldValue: (value: InstrumentObject): string => { return value.id; } }, { name: "isConnected", valueType: "boolean", getFieldValue: (value: InstrumentObject): boolean => { return value.isConnected; } } ] });