Skip to content

Your First Connector

This walkthrough guides you through creating a connector configuration that wraps a hardware driver with custom methods and computed properties.

Prerequisites

  • Muxit server running (node start.js server --gui)
  • A driver available (we'll use the built-in TestDevice driver, no hardware needed)

Step 1: Create the Config File

Create workspace/connectors/my-device.js:

javascript
export default {
  driver: "TestDevice",
  config: {},
};

That's a valid connector. Restart the server (or use hot-reload) and you'll see "my-device" in the Connector Browser.

The filename (my-device.js) becomes the connector name — use it with connector("my-device") in scripts.

Step 2: Add Configuration

Pass settings to the driver via config:

javascript
export default {
  driver: "TestDevice",
  config: {
    defaultVoltage: 5,
    defaultCurrent: 1.0,
  },
};

What goes in config depends on the driver. Check the driver documentation for supported keys.

Step 3: Add Custom Methods

Custom methods compose multiple driver calls into higher-level operations:

javascript
export default {
  driver: "TestDevice",
  config: {
    defaultVoltage: 5,
    defaultCurrent: 1.0,
  },

  methods: {
    // Shorthand: bare function
    on: (c) => c.output(true),

    // Shorthand: [function, description] tuple
    off: [(c) => c.output(false), "Turn output OFF"],

    // Full form: object with fn and description
    rampTo: {
      fn: async (c, target) => {
        const current = await c.voltage();
        const step = (target - current) / 10;
        for (let i = 1; i <= 10; i++) {
          await c.voltage(current + step * i);
          await delay(50);
        }
        return await c.voltage();
      },
      description: "Ramp voltage to target in 10 steps",
    },
  },
};

The first argument c is a proxy to the driver. Use it like:

  • await c.voltage() → reads the "voltage" property
  • await c.voltage(12) → writes the "voltage" property
  • await c.reset() → executes the "reset" action

Step 4: Add Computed Properties

Computed properties derive values from driver properties:

javascript
export default {
  driver: "TestDevice",
  config: { defaultVoltage: 5, defaultCurrent: 1.0 },

  methods: {
    on: (c) => c.output(true),
    off: [(c) => c.output(false), "Turn output OFF"],
    rampTo: {
      fn: async (c, target) => {
        const current = await c.voltage();
        const step = (target - current) / 10;
        for (let i = 1; i <= 10; i++) {
          await c.voltage(current + step * i);
          await delay(50);
        }
        return await c.voltage();
      },
      description: "Ramp voltage to target in 10 steps",
    },
  },

  properties: {
    power: {
      get: async (c) => {
        const v = await c.measured_voltage();
        const i = await c.measured_current();
        return Math.round(v * i * 1000) / 1000;
      },
      description: "Calculated power (W)",
    },
  },
};

Step 5: Enable Polling

Make properties update automatically by adding poll:

javascript
export default {
  driver: "TestDevice",
  config: { defaultVoltage: 5, defaultCurrent: 1.0 },
  poll: ["power", "measured_voltage", "measured_current"],

  methods: {
    on: (c) => c.output(true),
    off: [(c) => c.output(false), "Turn output OFF"],
    rampTo: {
      fn: async (c, target) => {
        const current = await c.voltage();
        const step = (target - current) / 10;
        for (let i = 1; i <= 10; i++) {
          await c.voltage(current + step * i);
          await delay(50);
        }
        return await c.voltage();
      },
      description: "Ramp voltage to target in 10 steps",
    },
  },

  properties: {
    power: {
      get: async (c) => {
        const v = await c.measured_voltage();
        const i = await c.measured_current();
        return Math.round(v * i * 1000) / 1000;
      },
      description: "Calculated power (W)",
    },
  },
};

The poll array lists property names that should be read automatically (default every 200ms). Changed values are pushed to dashboard widgets via WebSocket.

Step 6: Use It in a Script

javascript
const dev = connector("my-device");

await dev.on();
await dev.rampTo(12);

const power = await dev.power();
log.info(`Power: ${power}W`);

await dev.off();

Step 7: Connect Real Hardware

For real devices, use the GenericScpi driver with instrument-specific properties in the connector. Here's a serial PSU example:

javascript
export default {
  driver: "GenericScpi",
  config: {
    transport: "serial",
    serialPort: "/dev/ttyUSB0",
    baudRate: 9600,
    terminator: "\n",
    timeout: 3000,
    autoConnect: true,
    initCommands: "SYST:REM",
  },
  poll: ["measured_voltage", "measured_current"],

  methods: {
    on:  [c => c.output(true), "Turn output ON"],
    off: [c => c.output(false), "Turn output OFF"],
  },

  properties: {
    voltage: {
      get: c => c.query({ command: "VOLT?" }).then(parseFloat),
      set: (c, v) => c.send({ command: `VOLT ${Number(v)}` }),
      description: "Programmed voltage (V)",
    },
    measured_voltage: {
      get: c => c.query({ command: "MEAS:VOLT?" }).then(parseFloat),
      description: "Measured output voltage (V)",
    },
    measured_current: {
      get: c => c.query({ command: "MEAS:CURR?" }).then(parseFloat),
      description: "Measured output current (A)",
    },
    output: {
      get: async c => (await c.query({ command: "OUTP?" })).trim() === "1",
      set: (c, v) => c.send({ command: `OUTP ${v ? "ON" : "OFF"}` }),
      description: "Output enable/disable",
    },
    power: {
      get: async (c) => {
        const v = await c.measured_voltage();
        const i = await c.measured_current();
        return Math.round(v * i * 1000) / 1000;
      },
      description: "Calculated power (W)",
    },
  },
};

Connector Config Cheat Sheet

javascript
export default {
  driver: "DriverName",         // Required — which driver to use
  config: { /* ... */ },        // Optional — passed to driver.init()
  poll: ["prop1", "prop2"],     // Optional — auto-poll these properties

  methods: {
    name: fn,                   // Bare function
    name: [fn, "description"],  // Tuple
    name: { fn, description },  // Full form
  },

  properties: {
    name: fn,                                    // Bare getter
    name: [fn, "description", { poll: true }],   // Tuple with options
    name: { get: fn, set: fn, poll, description }, // Full form
  },
};

Next Steps

  • See the full Connector Guide for transports, virtual stores, and real hardware examples
  • Learn about dashboard widgets that bind to connector properties
  • Build a custom driver for your specific hardware

Muxit — Hardware Orchestration Platform