Encoder Module

Drive a 2-pin quadrature rotary encoder. The firmware polls the A/B lines, accumulates a signed tick count, and emits unsolicited tick events whenever the count changes — so the host doesn't have to poll.

Firmware setup

; platformio.ini
build_flags = -DCONDUYT_MODULE_ENCODER
#include <Conduyt.h>

ConduytSerial transport(Serial, 115200);
ConduytDevice device("EncoderBot", "1.0.0", transport);

void setup() {
  Serial.begin(115200);
  device.addModule(new ConduytModuleEncoder());
  device.begin();
}

void loop() {
  device.poll();
}

Wiring

Encoder         Board
─────           ─────
A           ──► any GPIO with INPUT_PULLUP support (e.g. 2)
B           ──► any GPIO with INPUT_PULLUP support (e.g. 3)
GND         ──► GND
+ (if any)  ──► 3V3 / 5V depending on encoder

The firmware enables INPUT_PULLUP on both pins automatically, so external pull-ups are not required for most rotary encoders. For very long wire runs you may still want them.

Pin choice tip: the encoder polls inside device.poll(), so it doesn't depend on hardware interrupts. Any digital input pin works — but if you're spinning the encoder very fast and your loop is busy, consider routing A to an interrupt-capable pin and adding an attach-interrupt fork in your firmware.

Host usage

JavaScript

import { ConduytDevice } from 'conduyt-js'
import { SerialTransport } from 'conduyt-js/transports/serial'
import { ConduytEncoder } from 'conduyt-js/modules/encoder'

const device = await ConduytDevice.connect(new SerialTransport({ path: '<YOUR_PORT>' }))
const enc = new ConduytEncoder(device)   // resolves "encoder" by name from HELLO_RESP

await enc.attach(2, 3)                   // pinA = 2, pinB = 3
await enc.reset()

enc.onTick((count, delta) => {
  console.log(`count=${count}  delta=${delta}`)
})

setInterval(async () => {
  console.log('current count:', await enc.read())
}, 1000)

Python

from conduyt import ConduytDevice
from conduyt.transports.serial import SerialTransport
from conduyt.modules import ConduytEncoder

device = ConduytDevice(SerialTransport('<YOUR_PORT>'))
await device.connect()

enc = ConduytEncoder(device, module_id=0)
await enc.attach(pin_a=2, pin_b=3)
await enc.reset()

count = await enc.read()
print(f"count = {count}")

Command reference

CommandIDPayloadDescription
Attach0x01pinA(1) + pinB(1)Claim two pins as A/B inputs
Read0x02(none)MOD_RESP with int32 count
Reset0x03(none)Zero the count

Events

EventIDPayloadWhen
Tick0x01int32 count + int16 deltaEmitted automatically on each detent / step change

Notes

  • Counts are signed int32. Direction depends on which physical wire you call A vs B — swap them or invert in software if it counts backwards.
  • Software polling means very fast turns can drop ticks. For 600+ PPR optical encoders at high RPM, you'll want a dedicated interrupt-driven sketch.
  • reset() zeros the count immediately; in-flight tick events still report from the new zero.