2026-03-08 12:20:21 +07:00
|
|
|
|
/**
|
|
|
|
|
|
* SensorParts.ts — Simulation logic for sensors, stepper motor, and NeoPixel devices.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Implements:
|
|
|
|
|
|
* - tilt-switch
|
|
|
|
|
|
* - ntc-temperature-sensor
|
|
|
|
|
|
* - gas-sensor (MQ-series)
|
|
|
|
|
|
* - flame-sensor
|
|
|
|
|
|
* - heart-beat-sensor
|
|
|
|
|
|
* - big-sound-sensor
|
|
|
|
|
|
* - small-sound-sensor
|
|
|
|
|
|
* - stepper-motor (NEMA full-step decode)
|
|
|
|
|
|
* - led-ring (WS2812B NeoPixel ring)
|
|
|
|
|
|
* - neopixel-matrix (WS2812B NeoPixel matrix)
|
2026-03-19 11:52:46 +07:00
|
|
|
|
* - pir-motion-sensor
|
|
|
|
|
|
* - hc-sr04
|
2026-03-08 12:20:21 +07:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
import { PartSimulationRegistry } from './PartSimulationRegistry';
|
|
|
|
|
|
import { setAdcVoltage } from './partUtils';
|
2026-03-19 11:52:46 +07:00
|
|
|
|
import { registerSensorUpdate, unregisterSensorUpdate } from '../SensorUpdateRegistry';
|
2026-03-08 12:20:21 +07:00
|
|
|
|
|
|
|
|
|
|
// ─── Tilt Switch ─────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Tilt switch — click the element to toggle between tilted (OUT HIGH) and
|
2026-03-19 11:52:46 +07:00
|
|
|
|
* upright (OUT LOW). Also controllable via SensorControlPanel "Toggle tilt" button.
|
2026-03-08 12:20:21 +07:00
|
|
|
|
*/
|
|
|
|
|
|
PartSimulationRegistry.register('tilt-switch', {
|
2026-03-19 11:52:46 +07:00
|
|
|
|
attachEvents: (element, simulator, getArduinoPinHelper, componentId) => {
|
2026-03-08 12:20:21 +07:00
|
|
|
|
const pin = getArduinoPinHelper('OUT');
|
|
|
|
|
|
if (pin === null) return () => {};
|
|
|
|
|
|
|
|
|
|
|
|
let tilted = false;
|
|
|
|
|
|
|
2026-03-19 11:52:46 +07:00
|
|
|
|
const triggerToggle = () => {
|
2026-03-08 12:20:21 +07:00
|
|
|
|
tilted = !tilted;
|
|
|
|
|
|
simulator.setPinState(pin, tilted);
|
2026-03-19 11:52:46 +07:00
|
|
|
|
console.log(`[TiltSwitch] pin ${pin} → ${tilted ? 'HIGH (tilted)' : 'LOW (upright)'}`);
|
2026-03-08 12:20:21 +07:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Start LOW (upright)
|
|
|
|
|
|
simulator.setPinState(pin, false);
|
2026-03-19 11:52:46 +07:00
|
|
|
|
element.addEventListener('click', triggerToggle);
|
|
|
|
|
|
|
|
|
|
|
|
// SensorControlPanel callback
|
|
|
|
|
|
registerSensorUpdate(componentId, (values) => {
|
|
|
|
|
|
if (values.toggle === true) triggerToggle();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
|
element.removeEventListener('click', triggerToggle);
|
|
|
|
|
|
unregisterSensorUpdate(componentId);
|
|
|
|
|
|
};
|
2026-03-08 12:20:21 +07:00
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ─── NTC Temperature Sensor ──────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-03-19 11:52:46 +07:00
|
|
|
|
* NTC thermistor sensor — injects analog voltage representing temperature.
|
|
|
|
|
|
* Default 25°C → 2.5V. SensorControlPanel slider adjusts temperature.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Linear approximation: volts = clamp(2.5 - (temp - 25) * 0.02, 0, 5)
|
|
|
|
|
|
* (25°C = 2.5V; lower temp = higher voltage, higher temp = lower voltage)
|
2026-03-08 12:20:21 +07:00
|
|
|
|
*/
|
|
|
|
|
|
PartSimulationRegistry.register('ntc-temperature-sensor', {
|
2026-03-19 11:52:46 +07:00
|
|
|
|
attachEvents: (element, simulator, getArduinoPinHelper, componentId) => {
|
2026-03-08 12:20:21 +07:00
|
|
|
|
const pin = getArduinoPinHelper('OUT');
|
|
|
|
|
|
if (pin === null) return () => {};
|
|
|
|
|
|
|
2026-03-19 11:52:46 +07:00
|
|
|
|
const tempToVolts = (temp: number) =>
|
|
|
|
|
|
Math.max(0, Math.min(5, 2.5 - (temp - 25) * 0.02));
|
|
|
|
|
|
|
|
|
|
|
|
// Room temperature default
|
|
|
|
|
|
setAdcVoltage(simulator, pin, tempToVolts(25));
|
2026-03-08 12:20:21 +07:00
|
|
|
|
|
|
|
|
|
|
const onInput = () => {
|
|
|
|
|
|
const val = (element as any).value;
|
|
|
|
|
|
if (val !== undefined) {
|
|
|
|
|
|
setAdcVoltage(simulator, pin, (val / 1023.0) * 5.0);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
element.addEventListener('input', onInput);
|
2026-03-19 11:52:46 +07:00
|
|
|
|
|
|
|
|
|
|
registerSensorUpdate(componentId, (values) => {
|
|
|
|
|
|
if ('temperature' in values) {
|
|
|
|
|
|
setAdcVoltage(simulator, pin, tempToVolts(values.temperature as number));
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
|
element.removeEventListener('input', onInput);
|
|
|
|
|
|
unregisterSensorUpdate(componentId);
|
|
|
|
|
|
};
|
2026-03-08 12:20:21 +07:00
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ─── Gas Sensor (MQ-series) ──────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-03-19 11:52:46 +07:00
|
|
|
|
* Gas sensor — injects analog voltage on AOUT.
|
|
|
|
|
|
* Default 1.5V (clean air / low gas). SensorControlPanel slider adjusts level (0–1023).
|
|
|
|
|
|
* Higher value → higher voltage (more gas detected).
|
2026-03-08 12:20:21 +07:00
|
|
|
|
*/
|
|
|
|
|
|
PartSimulationRegistry.register('gas-sensor', {
|
2026-03-19 11:52:46 +07:00
|
|
|
|
attachEvents: (element, simulator, getArduinoPinHelper, componentId) => {
|
2026-03-08 12:20:21 +07:00
|
|
|
|
const pinAOUT = getArduinoPinHelper('AOUT');
|
|
|
|
|
|
const pinDOUT = getArduinoPinHelper('DOUT');
|
|
|
|
|
|
const pinManager = (simulator as any).pinManager;
|
|
|
|
|
|
|
|
|
|
|
|
const el = element as any;
|
|
|
|
|
|
el.ledPower = true;
|
|
|
|
|
|
|
|
|
|
|
|
const unsubscribers: (() => void)[] = [];
|
|
|
|
|
|
|
|
|
|
|
|
// Inject baseline analog voltage (1.5V ≈ clean air / low gas)
|
|
|
|
|
|
if (pinAOUT !== null) {
|
|
|
|
|
|
setAdcVoltage(simulator, pinAOUT, 1.5);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// DOUT from Arduino → threshold LED indicator
|
|
|
|
|
|
if (pinDOUT !== null && pinManager) {
|
|
|
|
|
|
unsubscribers.push(
|
|
|
|
|
|
pinManager.onPinChange(pinDOUT, (_: number, state: boolean) => {
|
|
|
|
|
|
el.ledD0 = state;
|
|
|
|
|
|
})
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Allow element to update analog value if it fires input events
|
|
|
|
|
|
const onInput = () => {
|
|
|
|
|
|
const val = (el as any).value;
|
|
|
|
|
|
if (val !== undefined && pinAOUT !== null) {
|
|
|
|
|
|
setAdcVoltage(simulator, pinAOUT, (val / 1023.0) * 5.0);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
element.addEventListener('input', onInput);
|
|
|
|
|
|
unsubscribers.push(() => element.removeEventListener('input', onInput));
|
|
|
|
|
|
|
2026-03-19 11:52:46 +07:00
|
|
|
|
registerSensorUpdate(componentId, (values) => {
|
|
|
|
|
|
if ('gasLevel' in values && pinAOUT !== null) {
|
|
|
|
|
|
setAdcVoltage(simulator, pinAOUT, ((values.gasLevel as number) / 1023) * 5.0);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
|
unsubscribers.forEach(u => u());
|
|
|
|
|
|
unregisterSensorUpdate(componentId);
|
|
|
|
|
|
};
|
2026-03-08 12:20:21 +07:00
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ─── Flame Sensor ────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-03-19 11:52:46 +07:00
|
|
|
|
* Flame sensor — injects analog voltage on AOUT.
|
|
|
|
|
|
* Default 4.5V (no flame). SensorControlPanel slider: 0 = no flame (high V),
|
|
|
|
|
|
* 1023 = intense flame (low V).
|
2026-03-08 12:20:21 +07:00
|
|
|
|
*/
|
|
|
|
|
|
PartSimulationRegistry.register('flame-sensor', {
|
2026-03-19 11:52:46 +07:00
|
|
|
|
attachEvents: (element, simulator, getArduinoPinHelper, componentId) => {
|
2026-03-08 12:20:21 +07:00
|
|
|
|
const pinAOUT = getArduinoPinHelper('AOUT');
|
|
|
|
|
|
const pinDOUT = getArduinoPinHelper('DOUT');
|
|
|
|
|
|
const pinManager = (simulator as any).pinManager;
|
|
|
|
|
|
|
|
|
|
|
|
const el = element as any;
|
|
|
|
|
|
el.ledPower = true;
|
|
|
|
|
|
|
|
|
|
|
|
const unsubscribers: (() => void)[] = [];
|
|
|
|
|
|
|
|
|
|
|
|
if (pinAOUT !== null) {
|
2026-03-19 11:52:46 +07:00
|
|
|
|
setAdcVoltage(simulator, pinAOUT, 4.5); // no flame = high voltage
|
2026-03-08 12:20:21 +07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (pinDOUT !== null && pinManager) {
|
|
|
|
|
|
unsubscribers.push(
|
|
|
|
|
|
pinManager.onPinChange(pinDOUT, (_: number, state: boolean) => {
|
|
|
|
|
|
el.ledSignal = state;
|
|
|
|
|
|
})
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const onInput = () => {
|
|
|
|
|
|
const val = (el as any).value;
|
|
|
|
|
|
if (val !== undefined && pinAOUT !== null) {
|
|
|
|
|
|
setAdcVoltage(simulator, pinAOUT, (val / 1023.0) * 5.0);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
element.addEventListener('input', onInput);
|
|
|
|
|
|
unsubscribers.push(() => element.removeEventListener('input', onInput));
|
|
|
|
|
|
|
2026-03-19 11:52:46 +07:00
|
|
|
|
registerSensorUpdate(componentId, (values) => {
|
|
|
|
|
|
if ('intensity' in values && pinAOUT !== null) {
|
|
|
|
|
|
// 0 = no flame → high voltage (4.5V); 1023 = flame → low voltage (0.2V)
|
|
|
|
|
|
const volts = 5.0 - ((values.intensity as number) / 1023) * 5.0;
|
|
|
|
|
|
setAdcVoltage(simulator, pinAOUT, volts);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
|
unsubscribers.forEach(u => u());
|
|
|
|
|
|
unregisterSensorUpdate(componentId);
|
|
|
|
|
|
};
|
2026-03-08 12:20:21 +07:00
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ─── Heart Beat Sensor ───────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Heart beat sensor — simulates a 60 BPM signal on OUT pin.
|
|
|
|
|
|
* Every 1000ms: briefly pulls OUT HIGH for 100ms, then LOW again.
|
|
|
|
|
|
*/
|
|
|
|
|
|
PartSimulationRegistry.register('heart-beat-sensor', {
|
|
|
|
|
|
attachEvents: (element, simulator, getArduinoPinHelper) => {
|
|
|
|
|
|
const pin = getArduinoPinHelper('OUT');
|
|
|
|
|
|
if (pin === null) return () => {};
|
|
|
|
|
|
|
|
|
|
|
|
simulator.setPinState(pin, false);
|
|
|
|
|
|
|
|
|
|
|
|
const intervalId = setInterval(() => {
|
|
|
|
|
|
simulator.setPinState(pin, true); // pulse HIGH
|
|
|
|
|
|
setTimeout(() => simulator.setPinState(pin, false), 100);
|
|
|
|
|
|
}, 1000);
|
|
|
|
|
|
|
|
|
|
|
|
return () => clearInterval(intervalId);
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ─── Big Sound Sensor ────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-03-19 11:52:46 +07:00
|
|
|
|
* Big sound sensor (FC-04) — injects mid-range analog on AOUT.
|
|
|
|
|
|
* SensorControlPanel slider adjusts sound level (0–1023).
|
2026-03-08 12:20:21 +07:00
|
|
|
|
*/
|
|
|
|
|
|
PartSimulationRegistry.register('big-sound-sensor', {
|
2026-03-19 11:52:46 +07:00
|
|
|
|
attachEvents: (element, simulator, getArduinoPinHelper, componentId) => {
|
2026-03-08 12:20:21 +07:00
|
|
|
|
const pinAOUT = getArduinoPinHelper('AOUT');
|
|
|
|
|
|
const pinDOUT = getArduinoPinHelper('DOUT');
|
|
|
|
|
|
const pinManager = (simulator as any).pinManager;
|
|
|
|
|
|
|
|
|
|
|
|
const el = element as any;
|
|
|
|
|
|
el.led2 = true; // Power LED
|
|
|
|
|
|
|
|
|
|
|
|
const unsubscribers: (() => void)[] = [];
|
|
|
|
|
|
|
|
|
|
|
|
if (pinAOUT !== null) {
|
|
|
|
|
|
setAdcVoltage(simulator, pinAOUT, 2.5);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (pinDOUT !== null && pinManager) {
|
|
|
|
|
|
unsubscribers.push(
|
|
|
|
|
|
pinManager.onPinChange(pinDOUT, (_: number, state: boolean) => {
|
|
|
|
|
|
el.led1 = state;
|
|
|
|
|
|
})
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const onInput = () => {
|
|
|
|
|
|
const val = (el as any).value;
|
|
|
|
|
|
if (val !== undefined && pinAOUT !== null) {
|
|
|
|
|
|
setAdcVoltage(simulator, pinAOUT, (val / 1023.0) * 5.0);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
element.addEventListener('input', onInput);
|
|
|
|
|
|
unsubscribers.push(() => element.removeEventListener('input', onInput));
|
|
|
|
|
|
|
2026-03-19 11:52:46 +07:00
|
|
|
|
registerSensorUpdate(componentId, (values) => {
|
|
|
|
|
|
if ('soundLevel' in values && pinAOUT !== null) {
|
|
|
|
|
|
setAdcVoltage(simulator, pinAOUT, ((values.soundLevel as number) / 1023) * 5.0);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
|
unsubscribers.forEach(u => u());
|
|
|
|
|
|
unregisterSensorUpdate(componentId);
|
|
|
|
|
|
};
|
2026-03-08 12:20:21 +07:00
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ─── Small Sound Sensor ──────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-03-19 11:52:46 +07:00
|
|
|
|
* Small sound sensor (KY-038) — injects mid-range analog on AOUT.
|
|
|
|
|
|
* SensorControlPanel slider adjusts sound level (0–1023).
|
2026-03-08 12:20:21 +07:00
|
|
|
|
*/
|
|
|
|
|
|
PartSimulationRegistry.register('small-sound-sensor', {
|
2026-03-19 11:52:46 +07:00
|
|
|
|
attachEvents: (element, simulator, getArduinoPinHelper, componentId) => {
|
2026-03-08 12:20:21 +07:00
|
|
|
|
const pinAOUT = getArduinoPinHelper('AOUT');
|
|
|
|
|
|
const pinDOUT = getArduinoPinHelper('DOUT');
|
|
|
|
|
|
const pinManager = (simulator as any).pinManager;
|
|
|
|
|
|
|
|
|
|
|
|
const el = element as any;
|
|
|
|
|
|
el.ledPower = true;
|
|
|
|
|
|
|
|
|
|
|
|
const unsubscribers: (() => void)[] = [];
|
|
|
|
|
|
|
|
|
|
|
|
if (pinAOUT !== null) {
|
|
|
|
|
|
setAdcVoltage(simulator, pinAOUT, 2.5);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (pinDOUT !== null && pinManager) {
|
|
|
|
|
|
unsubscribers.push(
|
|
|
|
|
|
pinManager.onPinChange(pinDOUT, (_: number, state: boolean) => {
|
|
|
|
|
|
el.ledSignal = state;
|
|
|
|
|
|
})
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const onInput = () => {
|
|
|
|
|
|
const val = (el as any).value;
|
|
|
|
|
|
if (val !== undefined && pinAOUT !== null) {
|
|
|
|
|
|
setAdcVoltage(simulator, pinAOUT, (val / 1023.0) * 5.0);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
element.addEventListener('input', onInput);
|
|
|
|
|
|
unsubscribers.push(() => element.removeEventListener('input', onInput));
|
|
|
|
|
|
|
2026-03-19 11:52:46 +07:00
|
|
|
|
registerSensorUpdate(componentId, (values) => {
|
|
|
|
|
|
if ('soundLevel' in values && pinAOUT !== null) {
|
|
|
|
|
|
setAdcVoltage(simulator, pinAOUT, ((values.soundLevel as number) / 1023) * 5.0);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
|
unsubscribers.forEach(u => u());
|
|
|
|
|
|
unregisterSensorUpdate(componentId);
|
|
|
|
|
|
};
|
2026-03-08 12:20:21 +07:00
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ─── Stepper Motor (NEMA full-step decode) ───────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Stepper motor — monitors the 4 coil pins (A-, A+, B+, B-).
|
|
|
|
|
|
* Uses a full-step lookup table to detect direction of rotation and
|
|
|
|
|
|
* accumulates the shaft angle (1.8° per step = 200 steps per revolution).
|
|
|
|
|
|
*/
|
|
|
|
|
|
PartSimulationRegistry.register('stepper-motor', {
|
|
|
|
|
|
attachEvents: (element, simulator, getArduinoPinHelper) => {
|
|
|
|
|
|
const pinManager = (simulator as any).pinManager;
|
|
|
|
|
|
if (!pinManager) return () => {};
|
|
|
|
|
|
|
|
|
|
|
|
const el = element as any;
|
|
|
|
|
|
const STEP_ANGLE = 1.8; // degrees per step
|
|
|
|
|
|
|
|
|
|
|
|
const pinAMinus = getArduinoPinHelper('A-');
|
|
|
|
|
|
const pinAPlus = getArduinoPinHelper('A+');
|
|
|
|
|
|
const pinBPlus = getArduinoPinHelper('B+');
|
|
|
|
|
|
const pinBMinus = getArduinoPinHelper('B-');
|
|
|
|
|
|
|
|
|
|
|
|
const coils = { aMinus: false, aPlus: false, bPlus: false, bMinus: false };
|
|
|
|
|
|
let cumAngle = el.angle ?? 0;
|
|
|
|
|
|
let prevStepIndex = -1;
|
|
|
|
|
|
|
|
|
|
|
|
// Full-step table: index → [A+, B+, A-, B-]
|
|
|
|
|
|
const stepTable: [boolean, boolean, boolean, boolean][] = [
|
|
|
|
|
|
[true, false, false, false], // step 0
|
|
|
|
|
|
[false, true, false, false], // step 1
|
|
|
|
|
|
[false, false, true, false], // step 2
|
|
|
|
|
|
[false, false, false, true], // step 3
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
function coilToStepIndex(): number {
|
|
|
|
|
|
for (let i = 0; i < stepTable.length; i++) {
|
|
|
|
|
|
const [ap, bp, am, bm] = stepTable[i];
|
|
|
|
|
|
if (coils.aPlus === ap && coils.bPlus === bp &&
|
|
|
|
|
|
coils.aMinus === am && coils.bMinus === bm) {
|
|
|
|
|
|
return i;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-19 11:52:46 +07:00
|
|
|
|
return -1;
|
2026-03-08 12:20:21 +07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function onCoilChange() {
|
|
|
|
|
|
const idx = coilToStepIndex();
|
2026-03-19 11:52:46 +07:00
|
|
|
|
if (idx < 0) return;
|
2026-03-08 12:20:21 +07:00
|
|
|
|
if (prevStepIndex < 0) { prevStepIndex = idx; return; }
|
|
|
|
|
|
|
|
|
|
|
|
const diff = (idx - prevStepIndex + 4) % 4;
|
|
|
|
|
|
if (diff === 1) {
|
|
|
|
|
|
cumAngle += STEP_ANGLE;
|
|
|
|
|
|
} else if (diff === 3) {
|
|
|
|
|
|
cumAngle -= STEP_ANGLE;
|
|
|
|
|
|
}
|
|
|
|
|
|
prevStepIndex = idx;
|
|
|
|
|
|
el.angle = ((cumAngle % 360) + 360) % 360;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const unsubscribers: (() => void)[] = [];
|
|
|
|
|
|
|
|
|
|
|
|
if (pinAMinus !== null) {
|
|
|
|
|
|
unsubscribers.push(pinManager.onPinChange(pinAMinus, (_: number, s: boolean) => {
|
|
|
|
|
|
coils.aMinus = s; onCoilChange();
|
|
|
|
|
|
}));
|
|
|
|
|
|
}
|
|
|
|
|
|
if (pinAPlus !== null) {
|
|
|
|
|
|
unsubscribers.push(pinManager.onPinChange(pinAPlus, (_: number, s: boolean) => {
|
|
|
|
|
|
coils.aPlus = s; onCoilChange();
|
|
|
|
|
|
}));
|
|
|
|
|
|
}
|
|
|
|
|
|
if (pinBPlus !== null) {
|
|
|
|
|
|
unsubscribers.push(pinManager.onPinChange(pinBPlus, (_: number, s: boolean) => {
|
|
|
|
|
|
coils.bPlus = s; onCoilChange();
|
|
|
|
|
|
}));
|
|
|
|
|
|
}
|
|
|
|
|
|
if (pinBMinus !== null) {
|
|
|
|
|
|
unsubscribers.push(pinManager.onPinChange(pinBMinus, (_: number, s: boolean) => {
|
|
|
|
|
|
coils.bMinus = s; onCoilChange();
|
|
|
|
|
|
}));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return () => unsubscribers.forEach(u => u());
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ─── WS2812B NeoPixel decode helper ──────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Decode WS2812B bit-stream from DIN pin changes for NeoPixel devices.
|
|
|
|
|
|
*/
|
|
|
|
|
|
function createNeopixelDecoder(
|
|
|
|
|
|
simulator: any,
|
|
|
|
|
|
pinDIN: number,
|
|
|
|
|
|
onPixel: (index: number, r: number, g: number, b: number) => void,
|
|
|
|
|
|
): () => void {
|
|
|
|
|
|
const pinManager = simulator.pinManager;
|
|
|
|
|
|
if (!pinManager) return () => {};
|
|
|
|
|
|
|
2026-03-19 11:52:46 +07:00
|
|
|
|
const RESET_CYCLES = 800;
|
|
|
|
|
|
const BIT1_THRESHOLD = 8;
|
2026-03-08 12:20:21 +07:00
|
|
|
|
|
|
|
|
|
|
let lastRisingCycle = 0;
|
|
|
|
|
|
let lastFallingCycle = 0;
|
|
|
|
|
|
let lastHigh = false;
|
|
|
|
|
|
|
|
|
|
|
|
let bitBuf = 0;
|
|
|
|
|
|
let bitsCollected = 0;
|
|
|
|
|
|
let byteBuf: number[] = [];
|
|
|
|
|
|
let pixelIndex = 0;
|
|
|
|
|
|
|
|
|
|
|
|
const unsub = pinManager.onPinChange(pinDIN, (_: number, high: boolean) => {
|
|
|
|
|
|
const cpu = simulator.cpu ?? (simulator as any).cpu;
|
|
|
|
|
|
const now: number = cpu?.cycles ?? 0;
|
|
|
|
|
|
|
|
|
|
|
|
if (high) {
|
|
|
|
|
|
const lowDur = now - lastFallingCycle;
|
|
|
|
|
|
if (lowDur > RESET_CYCLES) {
|
|
|
|
|
|
pixelIndex = 0;
|
|
|
|
|
|
byteBuf = [];
|
|
|
|
|
|
bitBuf = 0;
|
|
|
|
|
|
bitsCollected = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
lastRisingCycle = now;
|
|
|
|
|
|
lastHigh = true;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if (lastHigh) {
|
|
|
|
|
|
const highDur = now - lastRisingCycle;
|
|
|
|
|
|
const bit = highDur > BIT1_THRESHOLD ? 1 : 0;
|
|
|
|
|
|
|
|
|
|
|
|
bitBuf = (bitBuf << 1) | bit;
|
|
|
|
|
|
bitsCollected++;
|
|
|
|
|
|
|
|
|
|
|
|
if (bitsCollected === 8) {
|
|
|
|
|
|
byteBuf.push(bitBuf & 0xFF);
|
|
|
|
|
|
bitBuf = 0;
|
|
|
|
|
|
bitsCollected = 0;
|
|
|
|
|
|
|
|
|
|
|
|
if (byteBuf.length === 3) {
|
|
|
|
|
|
const g = byteBuf[0];
|
|
|
|
|
|
const r = byteBuf[1];
|
|
|
|
|
|
const b = byteBuf[2];
|
|
|
|
|
|
onPixel(pixelIndex++, r, g, b);
|
|
|
|
|
|
byteBuf = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
lastFallingCycle = now;
|
|
|
|
|
|
lastHigh = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return unsub;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ─── LED Ring (WS2812B NeoPixel ring) ────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
PartSimulationRegistry.register('led-ring', {
|
|
|
|
|
|
attachEvents: (element, simulator, getArduinoPinHelper) => {
|
|
|
|
|
|
const pinDIN = getArduinoPinHelper('DIN');
|
|
|
|
|
|
if (pinDIN === null) return () => {};
|
|
|
|
|
|
|
|
|
|
|
|
const el = element as any;
|
|
|
|
|
|
|
|
|
|
|
|
const unsub = createNeopixelDecoder(
|
|
|
|
|
|
(simulator as any),
|
|
|
|
|
|
pinDIN,
|
|
|
|
|
|
(index, r, g, b) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
el.setPixel(index, { r, g, b });
|
|
|
|
|
|
} catch (_) {
|
|
|
|
|
|
// setPixel not yet available (element not upgraded) — ignore
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
return unsub;
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ─── NeoPixel Matrix (WS2812B matrix grid) ────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
PartSimulationRegistry.register('neopixel-matrix', {
|
|
|
|
|
|
attachEvents: (element, simulator, getArduinoPinHelper) => {
|
|
|
|
|
|
const pinDIN = getArduinoPinHelper('DIN');
|
|
|
|
|
|
if (pinDIN === null) return () => {};
|
|
|
|
|
|
|
|
|
|
|
|
const el = element as any;
|
|
|
|
|
|
|
|
|
|
|
|
const unsub = createNeopixelDecoder(
|
|
|
|
|
|
(simulator as any),
|
|
|
|
|
|
pinDIN,
|
|
|
|
|
|
(index, r, g, b) => {
|
|
|
|
|
|
const cols: number = el.cols ?? 8;
|
|
|
|
|
|
const row = Math.floor(index / cols);
|
|
|
|
|
|
const col = index % cols;
|
|
|
|
|
|
try {
|
|
|
|
|
|
el.setPixel(row, col, { r, g, b });
|
|
|
|
|
|
} catch (_) {
|
|
|
|
|
|
// ignore
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
return unsub;
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
feat: add new components and simulations for logic gates, protocols, and sensors
- Added LogicGateParts.ts for simulating various logic gates (AND, NAND, OR, NOR, XOR, NOT).
- Introduced ProtocolParts.ts for simulating I2C and SPI components including SSD1306 OLED, DS1307 RTC, MPU6050 IMU, DHT22 sensor, HX711 load cell, IR receiver, IR remote, and MicroSD card.
- Implemented BasicParts.ts with a membrane keypad and rotary dialer simulations.
- Enhanced SensorParts.ts with a single NeoPixel and PIR motion sensor.
- Updated index.ts to include new parts for logic gates and protocols.
- Modified vite-env.d.ts to declare new custom elements for the added components.
2026-03-09 02:14:03 +07:00
|
|
|
|
|
|
|
|
|
|
// ─── Single NeoPixel (WS2812B) ───────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-03-19 11:52:46 +07:00
|
|
|
|
* Single addressable RGB LED — decodes the WS2812B data stream on DIN.
|
feat: add new components and simulations for logic gates, protocols, and sensors
- Added LogicGateParts.ts for simulating various logic gates (AND, NAND, OR, NOR, XOR, NOT).
- Introduced ProtocolParts.ts for simulating I2C and SPI components including SSD1306 OLED, DS1307 RTC, MPU6050 IMU, DHT22 sensor, HX711 load cell, IR receiver, IR remote, and MicroSD card.
- Implemented BasicParts.ts with a membrane keypad and rotary dialer simulations.
- Enhanced SensorParts.ts with a single NeoPixel and PIR motion sensor.
- Updated index.ts to include new parts for logic gates and protocols.
- Modified vite-env.d.ts to declare new custom elements for the added components.
2026-03-09 02:14:03 +07:00
|
|
|
|
*/
|
|
|
|
|
|
PartSimulationRegistry.register('neopixel', {
|
|
|
|
|
|
attachEvents: (element, simulator, getArduinoPinHelper) => {
|
|
|
|
|
|
const pinDIN = getArduinoPinHelper('DIN');
|
|
|
|
|
|
if (pinDIN === null) return () => {};
|
|
|
|
|
|
|
|
|
|
|
|
const el = element as any;
|
|
|
|
|
|
|
|
|
|
|
|
const unsub = createNeopixelDecoder(
|
|
|
|
|
|
(simulator as any),
|
|
|
|
|
|
pinDIN,
|
|
|
|
|
|
(_index, r, g, b) => {
|
|
|
|
|
|
el.r = r / 255;
|
|
|
|
|
|
el.g = g / 255;
|
|
|
|
|
|
el.b = b / 255;
|
|
|
|
|
|
},
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
return unsub;
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ─── PIR Motion Sensor ───────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-03-19 11:52:46 +07:00
|
|
|
|
* PIR motion sensor — click the element OR press "Simulate motion" in the
|
|
|
|
|
|
* SensorControlPanel to trigger a 3-second HIGH pulse on OUT.
|
feat: add new components and simulations for logic gates, protocols, and sensors
- Added LogicGateParts.ts for simulating various logic gates (AND, NAND, OR, NOR, XOR, NOT).
- Introduced ProtocolParts.ts for simulating I2C and SPI components including SSD1306 OLED, DS1307 RTC, MPU6050 IMU, DHT22 sensor, HX711 load cell, IR receiver, IR remote, and MicroSD card.
- Implemented BasicParts.ts with a membrane keypad and rotary dialer simulations.
- Enhanced SensorParts.ts with a single NeoPixel and PIR motion sensor.
- Updated index.ts to include new parts for logic gates and protocols.
- Modified vite-env.d.ts to declare new custom elements for the added components.
2026-03-09 02:14:03 +07:00
|
|
|
|
*/
|
|
|
|
|
|
PartSimulationRegistry.register('pir-motion-sensor', {
|
2026-03-19 11:52:46 +07:00
|
|
|
|
attachEvents: (element, simulator, getArduinoPinHelper, componentId) => {
|
feat: add new components and simulations for logic gates, protocols, and sensors
- Added LogicGateParts.ts for simulating various logic gates (AND, NAND, OR, NOR, XOR, NOT).
- Introduced ProtocolParts.ts for simulating I2C and SPI components including SSD1306 OLED, DS1307 RTC, MPU6050 IMU, DHT22 sensor, HX711 load cell, IR receiver, IR remote, and MicroSD card.
- Implemented BasicParts.ts with a membrane keypad and rotary dialer simulations.
- Enhanced SensorParts.ts with a single NeoPixel and PIR motion sensor.
- Updated index.ts to include new parts for logic gates and protocols.
- Modified vite-env.d.ts to declare new custom elements for the added components.
2026-03-09 02:14:03 +07:00
|
|
|
|
const pin = getArduinoPinHelper('OUT');
|
|
|
|
|
|
if (pin === null) return () => {};
|
|
|
|
|
|
|
|
|
|
|
|
simulator.setPinState(pin, false); // idle LOW
|
|
|
|
|
|
|
|
|
|
|
|
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
|
|
|
|
|
2026-03-19 11:52:46 +07:00
|
|
|
|
const triggerMotion = () => {
|
feat: add new components and simulations for logic gates, protocols, and sensors
- Added LogicGateParts.ts for simulating various logic gates (AND, NAND, OR, NOR, XOR, NOT).
- Introduced ProtocolParts.ts for simulating I2C and SPI components including SSD1306 OLED, DS1307 RTC, MPU6050 IMU, DHT22 sensor, HX711 load cell, IR receiver, IR remote, and MicroSD card.
- Implemented BasicParts.ts with a membrane keypad and rotary dialer simulations.
- Enhanced SensorParts.ts with a single NeoPixel and PIR motion sensor.
- Updated index.ts to include new parts for logic gates and protocols.
- Modified vite-env.d.ts to declare new custom elements for the added components.
2026-03-09 02:14:03 +07:00
|
|
|
|
if (timer !== null) clearTimeout(timer);
|
2026-03-19 11:52:46 +07:00
|
|
|
|
simulator.setPinState(pin, true);
|
feat: add new components and simulations for logic gates, protocols, and sensors
- Added LogicGateParts.ts for simulating various logic gates (AND, NAND, OR, NOR, XOR, NOT).
- Introduced ProtocolParts.ts for simulating I2C and SPI components including SSD1306 OLED, DS1307 RTC, MPU6050 IMU, DHT22 sensor, HX711 load cell, IR receiver, IR remote, and MicroSD card.
- Implemented BasicParts.ts with a membrane keypad and rotary dialer simulations.
- Enhanced SensorParts.ts with a single NeoPixel and PIR motion sensor.
- Updated index.ts to include new parts for logic gates and protocols.
- Modified vite-env.d.ts to declare new custom elements for the added components.
2026-03-09 02:14:03 +07:00
|
|
|
|
console.log('[PIR] Motion detected → OUT HIGH');
|
|
|
|
|
|
timer = setTimeout(() => {
|
|
|
|
|
|
simulator.setPinState(pin, false);
|
|
|
|
|
|
timer = null;
|
|
|
|
|
|
console.log('[PIR] Motion ended → OUT LOW');
|
|
|
|
|
|
}, 3000);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-19 11:52:46 +07:00
|
|
|
|
element.addEventListener('click', triggerMotion);
|
|
|
|
|
|
|
|
|
|
|
|
registerSensorUpdate(componentId, (values) => {
|
|
|
|
|
|
if (values.trigger === true) triggerMotion();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
feat: add new components and simulations for logic gates, protocols, and sensors
- Added LogicGateParts.ts for simulating various logic gates (AND, NAND, OR, NOR, XOR, NOT).
- Introduced ProtocolParts.ts for simulating I2C and SPI components including SSD1306 OLED, DS1307 RTC, MPU6050 IMU, DHT22 sensor, HX711 load cell, IR receiver, IR remote, and MicroSD card.
- Implemented BasicParts.ts with a membrane keypad and rotary dialer simulations.
- Enhanced SensorParts.ts with a single NeoPixel and PIR motion sensor.
- Updated index.ts to include new parts for logic gates and protocols.
- Modified vite-env.d.ts to declare new custom elements for the added components.
2026-03-09 02:14:03 +07:00
|
|
|
|
return () => {
|
2026-03-19 11:52:46 +07:00
|
|
|
|
element.removeEventListener('click', triggerMotion);
|
feat: add new components and simulations for logic gates, protocols, and sensors
- Added LogicGateParts.ts for simulating various logic gates (AND, NAND, OR, NOR, XOR, NOT).
- Introduced ProtocolParts.ts for simulating I2C and SPI components including SSD1306 OLED, DS1307 RTC, MPU6050 IMU, DHT22 sensor, HX711 load cell, IR receiver, IR remote, and MicroSD card.
- Implemented BasicParts.ts with a membrane keypad and rotary dialer simulations.
- Enhanced SensorParts.ts with a single NeoPixel and PIR motion sensor.
- Updated index.ts to include new parts for logic gates and protocols.
- Modified vite-env.d.ts to declare new custom elements for the added components.
2026-03-09 02:14:03 +07:00
|
|
|
|
if (timer !== null) clearTimeout(timer);
|
2026-03-19 11:52:46 +07:00
|
|
|
|
unregisterSensorUpdate(componentId);
|
feat: add new components and simulations for logic gates, protocols, and sensors
- Added LogicGateParts.ts for simulating various logic gates (AND, NAND, OR, NOR, XOR, NOT).
- Introduced ProtocolParts.ts for simulating I2C and SPI components including SSD1306 OLED, DS1307 RTC, MPU6050 IMU, DHT22 sensor, HX711 load cell, IR receiver, IR remote, and MicroSD card.
- Implemented BasicParts.ts with a membrane keypad and rotary dialer simulations.
- Enhanced SensorParts.ts with a single NeoPixel and PIR motion sensor.
- Updated index.ts to include new parts for logic gates and protocols.
- Modified vite-env.d.ts to declare new custom elements for the added components.
2026-03-09 02:14:03 +07:00
|
|
|
|
};
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ─── KS2E-M-DC5 Relay ────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Dual-coil relay — listens for COIL1/COIL2 pin state changes.
|
|
|
|
|
|
*/
|
|
|
|
|
|
PartSimulationRegistry.register('ks2e-m-dc5', {
|
|
|
|
|
|
onPinStateChange: (pinName, state, _element) => {
|
|
|
|
|
|
if (pinName === 'COIL1' || pinName === 'COIL2') {
|
|
|
|
|
|
console.log(`[Relay KS2E] ${pinName} → ${state ? 'ACTIVATED' : 'RELEASED'}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ─── HC-SR04 Ultrasonic Distance Sensor ──────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Ultrasonic sensor — monitors the TRIG pin.
|
2026-03-19 11:52:46 +07:00
|
|
|
|
* When TRIG goes HIGH, responds with an ECHO HIGH pulse whose duration
|
|
|
|
|
|
* encodes the configured distance (default 10 cm).
|
|
|
|
|
|
*
|
|
|
|
|
|
* Echo timing: echoMs = distanceCm / 17.15
|
|
|
|
|
|
* (speed of sound ~343 m/s; round-trip halves: 17150 cm/s)
|
feat: add new components and simulations for logic gates, protocols, and sensors
- Added LogicGateParts.ts for simulating various logic gates (AND, NAND, OR, NOR, XOR, NOT).
- Introduced ProtocolParts.ts for simulating I2C and SPI components including SSD1306 OLED, DS1307 RTC, MPU6050 IMU, DHT22 sensor, HX711 load cell, IR receiver, IR remote, and MicroSD card.
- Implemented BasicParts.ts with a membrane keypad and rotary dialer simulations.
- Enhanced SensorParts.ts with a single NeoPixel and PIR motion sensor.
- Updated index.ts to include new parts for logic gates and protocols.
- Modified vite-env.d.ts to declare new custom elements for the added components.
2026-03-09 02:14:03 +07:00
|
|
|
|
*/
|
|
|
|
|
|
PartSimulationRegistry.register('hc-sr04', {
|
2026-03-19 11:52:46 +07:00
|
|
|
|
attachEvents: (element, simulator, getArduinoPinHelper, componentId) => {
|
feat: add new components and simulations for logic gates, protocols, and sensors
- Added LogicGateParts.ts for simulating various logic gates (AND, NAND, OR, NOR, XOR, NOT).
- Introduced ProtocolParts.ts for simulating I2C and SPI components including SSD1306 OLED, DS1307 RTC, MPU6050 IMU, DHT22 sensor, HX711 load cell, IR receiver, IR remote, and MicroSD card.
- Implemented BasicParts.ts with a membrane keypad and rotary dialer simulations.
- Enhanced SensorParts.ts with a single NeoPixel and PIR motion sensor.
- Updated index.ts to include new parts for logic gates and protocols.
- Modified vite-env.d.ts to declare new custom elements for the added components.
2026-03-09 02:14:03 +07:00
|
|
|
|
const trigPin = getArduinoPinHelper('TRIG');
|
|
|
|
|
|
const echoPin = getArduinoPinHelper('ECHO');
|
|
|
|
|
|
if (trigPin === null || echoPin === null) return () => {};
|
|
|
|
|
|
|
|
|
|
|
|
simulator.setPinState(echoPin, false); // ECHO LOW initially
|
|
|
|
|
|
|
2026-03-19 11:52:46 +07:00
|
|
|
|
let distanceCm = 10; // default distance in cm
|
feat: add new components and simulations for logic gates, protocols, and sensors
- Added LogicGateParts.ts for simulating various logic gates (AND, NAND, OR, NOR, XOR, NOT).
- Introduced ProtocolParts.ts for simulating I2C and SPI components including SSD1306 OLED, DS1307 RTC, MPU6050 IMU, DHT22 sensor, HX711 load cell, IR receiver, IR remote, and MicroSD card.
- Implemented BasicParts.ts with a membrane keypad and rotary dialer simulations.
- Enhanced SensorParts.ts with a single NeoPixel and PIR motion sensor.
- Updated index.ts to include new parts for logic gates and protocols.
- Modified vite-env.d.ts to declare new custom elements for the added components.
2026-03-09 02:14:03 +07:00
|
|
|
|
|
|
|
|
|
|
const cleanup = simulator.pinManager.onPinChange(trigPin, (_: number, state: boolean) => {
|
2026-03-19 18:22:36 +07:00
|
|
|
|
if (!state) return; // only react on TRIG HIGH
|
|
|
|
|
|
// HC-SR04 timing (at 16 MHz):
|
|
|
|
|
|
// - Sensor processing delay after TRIG: ~600 µs = 9600 cycles
|
|
|
|
|
|
// - Echo duration = distanceCm / 17150 s × 16 000 000 cycles/s
|
|
|
|
|
|
// (17150 cm/s = speed of sound, one-way = round-trip/2)
|
|
|
|
|
|
if (typeof simulator.schedulePinChange === 'function') {
|
|
|
|
|
|
const now = simulator.getCurrentCycles() as number;
|
|
|
|
|
|
const processingCycles = 9600; // ~600 µs sensor overhead
|
|
|
|
|
|
const echoCycles = Math.round((distanceCm / 17150) * 16_000_000);
|
|
|
|
|
|
simulator.schedulePinChange(echoPin, true, now + processingCycles);
|
|
|
|
|
|
simulator.schedulePinChange(echoPin, false, now + processingCycles + echoCycles);
|
|
|
|
|
|
console.log(`[HC-SR04] Scheduled ECHO (${distanceCm} cm, echo=${(echoCycles/16000).toFixed(1)} µs)`);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// Fallback: best-effort async (works with delay()-based sketches, not pulseIn)
|
2026-03-19 11:52:46 +07:00
|
|
|
|
const echoMs = Math.max(1, distanceCm / 17.15);
|
2026-03-19 18:22:36 +07:00
|
|
|
|
setTimeout(() => {
|
2026-03-19 11:52:46 +07:00
|
|
|
|
simulator.setPinState(echoPin, true);
|
2026-03-19 18:22:36 +07:00
|
|
|
|
setTimeout(() => { simulator.setPinState(echoPin, false); }, echoMs);
|
feat: add new components and simulations for logic gates, protocols, and sensors
- Added LogicGateParts.ts for simulating various logic gates (AND, NAND, OR, NOR, XOR, NOT).
- Introduced ProtocolParts.ts for simulating I2C and SPI components including SSD1306 OLED, DS1307 RTC, MPU6050 IMU, DHT22 sensor, HX711 load cell, IR receiver, IR remote, and MicroSD card.
- Implemented BasicParts.ts with a membrane keypad and rotary dialer simulations.
- Enhanced SensorParts.ts with a single NeoPixel and PIR motion sensor.
- Updated index.ts to include new parts for logic gates and protocols.
- Modified vite-env.d.ts to declare new custom elements for the added components.
2026-03-09 02:14:03 +07:00
|
|
|
|
}, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-03-19 11:52:46 +07:00
|
|
|
|
registerSensorUpdate(componentId, (values) => {
|
|
|
|
|
|
if ('distance' in values) {
|
|
|
|
|
|
distanceCm = Math.max(2, Math.min(400, values.distance as number));
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
feat: add new components and simulations for logic gates, protocols, and sensors
- Added LogicGateParts.ts for simulating various logic gates (AND, NAND, OR, NOR, XOR, NOT).
- Introduced ProtocolParts.ts for simulating I2C and SPI components including SSD1306 OLED, DS1307 RTC, MPU6050 IMU, DHT22 sensor, HX711 load cell, IR receiver, IR remote, and MicroSD card.
- Implemented BasicParts.ts with a membrane keypad and rotary dialer simulations.
- Enhanced SensorParts.ts with a single NeoPixel and PIR motion sensor.
- Updated index.ts to include new parts for logic gates and protocols.
- Modified vite-env.d.ts to declare new custom elements for the added components.
2026-03-09 02:14:03 +07:00
|
|
|
|
return () => {
|
|
|
|
|
|
cleanup();
|
2026-03-19 11:52:46 +07:00
|
|
|
|
unregisterSensorUpdate(componentId);
|
feat: add new components and simulations for logic gates, protocols, and sensors
- Added LogicGateParts.ts for simulating various logic gates (AND, NAND, OR, NOR, XOR, NOT).
- Introduced ProtocolParts.ts for simulating I2C and SPI components including SSD1306 OLED, DS1307 RTC, MPU6050 IMU, DHT22 sensor, HX711 load cell, IR receiver, IR remote, and MicroSD card.
- Implemented BasicParts.ts with a membrane keypad and rotary dialer simulations.
- Enhanced SensorParts.ts with a single NeoPixel and PIR motion sensor.
- Updated index.ts to include new parts for logic gates and protocols.
- Modified vite-env.d.ts to declare new custom elements for the added components.
2026-03-09 02:14:03 +07:00
|
|
|
|
};
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|