refactor(ssd1306): drop the i2c/spi aliases; CS-only auto-detect + protocol pin
Follow-up to the SSD1306 picker consolidation. All 68 saved projects that used the retired ssd1306-i2c / ssd1306-spi ids have been migrated to the single `ssd1306` (metadataId rewritten, protocol pinned), so the simulation aliases are no longer needed and are removed. - Auto-detect refined to CS-only: chip-select is the SPI-exclusive signal; DC does NOT imply SPI (on the 8-pin module DC doubles as the I2C address / SA0 line, so many I2C circuits wire it). Fixes false-SPI on those circuits. - The `ssd1306` part honors an explicit `protocol` property when present (migrated legacy projects carry it) and auto-detects otherwise. - loadProjectState normalizes any lingering ssd1306-i2c/spi ids (old .vlx files, pre-migration snapshots) to `ssd1306` + the matching protocol, so removing the aliases can never blank an old import.
This commit is contained in:
parent
b6dd2f5201
commit
b51bbcf06d
|
|
@ -17,6 +17,7 @@
|
|||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
import { PartSimulationRegistry } from '../simulation/parts/PartSimulationRegistry';
|
||||
import { dispatchSensorUpdate } from '../simulation/SensorUpdateRegistry';
|
||||
import { useSimulatorStore } from '../store/useSimulatorStore';
|
||||
import '../simulation/parts/ProtocolParts';
|
||||
|
||||
// ─── Globals ──────────────────────────────────────────────────────────────────
|
||||
|
|
@ -228,31 +229,35 @@ describe('ssd1306 — protocol auto-detect', () => {
|
|||
expect(sim.addI2CDevice).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('runs SPI when DC is wired to a GPIO', () => {
|
||||
const sim = makeSPISim();
|
||||
it('runs I2C when only DC is wired (DC is the I2C address-select, not SPI)', () => {
|
||||
const sim = makeI2CSim();
|
||||
PartSimulationRegistry.get('ssd1306')!.attachEvents!(
|
||||
makeElement(),
|
||||
sim as any,
|
||||
pinMap({ DC: 4 }),
|
||||
);
|
||||
expect(typeof sim.spi.onByte).toBe('function');
|
||||
expect(sim.addI2CDevice).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('legacy ssd1306-i2c alias forces I2C even with CS wired', () => {
|
||||
const sim = makeI2CSim();
|
||||
PartSimulationRegistry.get('ssd1306-i2c')!.attachEvents!(
|
||||
makeElement(),
|
||||
sim as any,
|
||||
pinMap({ CS: 5 }),
|
||||
);
|
||||
expect(sim.addI2CDevice).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('legacy ssd1306-spi alias forces SPI even with nothing wired', () => {
|
||||
const sim = makeSPISim();
|
||||
PartSimulationRegistry.get('ssd1306-spi')!.attachEvents!(makeElement(), sim as any, noPins);
|
||||
expect(typeof sim.spi.onByte).toBe('function');
|
||||
it('honors an explicit protocol property (migrated legacy projects)', () => {
|
||||
// A project migrated from the old ssd1306-spi entry carries protocol:'spi';
|
||||
// it must run SPI even though nothing SPI-specific is wired (CS absent).
|
||||
useSimulatorStore.setState({
|
||||
components: [{ id: 'oled-legacy', metadataId: 'ssd1306', properties: { protocol: 'spi' } }],
|
||||
} as any);
|
||||
try {
|
||||
const sim = makeSPISim();
|
||||
PartSimulationRegistry.get('ssd1306')!.attachEvents!(
|
||||
makeElement(),
|
||||
sim as any,
|
||||
noPins,
|
||||
'oled-legacy',
|
||||
);
|
||||
expect(typeof sim.spi.onByte).toBe('function');
|
||||
expect(sim.addI2CDevice).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
useSimulatorStore.setState({ components: [] } as any);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -329,8 +329,8 @@ function attachSSD1306SPI(
|
|||
|
||||
/**
|
||||
* Internal: SSD1306 attach logic, parameterised over the wire protocol.
|
||||
* Used by the three picker entries (the generic `ssd1306` plus the two
|
||||
* dedicated `ssd1306-i2c` / `ssd1306-spi` shortcuts).
|
||||
* Called by the single `ssd1306` entry once the protocol has been resolved
|
||||
* (auto-detected from the wiring, or read from an explicit `protocol` property).
|
||||
*/
|
||||
function attachSSD1306(
|
||||
element: HTMLElement,
|
||||
|
|
@ -376,52 +376,43 @@ function attachSSD1306(
|
|||
}
|
||||
|
||||
/**
|
||||
* Which wire protocol did the user build? A real SSD1306 breakout is ONE
|
||||
* board that talks either I2C or SPI depending on how it is wired: SPI drives
|
||||
* the chip-select (CS) and data/command (DC) lines from MCU GPIOs, while I2C
|
||||
* leaves them tied to power (address select) or unconnected. So if CS or DC is
|
||||
* wired to a GPIO we decode SPI; otherwise I2C. This mirrors the physical part
|
||||
* — one component, no protocol switch to set, just wire it up.
|
||||
* Which wire protocol did the user build? A real SSD1306 breakout is ONE board
|
||||
* that talks either I2C or SPI depending on how it is wired. The definitive
|
||||
* SPI-only signal is chip-select (CS): I2C never uses it. (DC deliberately does
|
||||
* NOT count — on the 8-pin module DC doubles as the I2C address-select/SA0 line,
|
||||
* so many I2C circuits wire it too.) So CS wired to a GPIO => SPI, otherwise
|
||||
* I2C. This mirrors the physical part — one component, no protocol switch to
|
||||
* set, just wire it up.
|
||||
*
|
||||
* Pure wiring check: it deliberately does NOT read `simulator.spi`, whose
|
||||
* getter on some boards (RP2040, and the ESP32/STM32 bridge shims) lazily
|
||||
* re-routes the board SPI bus as a side effect and must not fire in I2C mode.
|
||||
* Pure wiring check: it deliberately does NOT read `simulator.spi`, whose getter
|
||||
* on some boards (RP2040, and the ESP32/STM32 bridge shims) lazily re-routes the
|
||||
* board SPI bus as a side effect and must not fire in I2C mode.
|
||||
*/
|
||||
function detectSSD1306Protocol(getPin: (n: string) => number | null): 'i2c' | 'spi' {
|
||||
return getPin('CS') !== null || getPin('DC') !== null ? 'spi' : 'i2c';
|
||||
return getPin('CS') !== null ? 'spi' : 'i2c';
|
||||
}
|
||||
|
||||
/**
|
||||
* SSD1306 OLED — a single component that works on every board with an I2C or
|
||||
* SPI bus (AVR, RP2040, ESP32, STM32), auto-detecting the protocol from the
|
||||
* wiring like the physical module. Consolidates the old ssd1306 / ssd1306-i2c
|
||||
* / ssd1306-spi picker entries into one (issues #101 / #215).
|
||||
* SPI bus (AVR, RP2040, ESP32, STM32). New projects just wire it up and the
|
||||
* protocol is auto-detected from the wiring like the physical module; a
|
||||
* `protocol` property, when present, pins it explicitly (projects migrated from
|
||||
* the old ssd1306-i2c / ssd1306-spi entries carry it so their behaviour is
|
||||
* preserved exactly). Consolidates the old three picker entries into one
|
||||
* (issues #101 / #215).
|
||||
*/
|
||||
PartSimulationRegistry.register('ssd1306', {
|
||||
attachEvents: (element, simulator, getPin, componentId) => {
|
||||
const { components } = useSimulatorStore.getState();
|
||||
const comp = components.find((c) => c.id === componentId);
|
||||
const i2cAddr = parseI2cAddress(comp?.properties?.i2cAddress, 0x3c);
|
||||
const protocol = detectSSD1306Protocol(getPin);
|
||||
const explicit = comp?.properties?.protocol;
|
||||
const protocol: 'i2c' | 'spi' =
|
||||
explicit === 'i2c' || explicit === 'spi' ? explicit : detectSSD1306Protocol(getPin);
|
||||
return attachSSD1306(element, simulator, getPin, protocol, i2cAddr);
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Legacy aliases kept ONLY for projects saved before the picker entries merged
|
||||
* into the single auto-detecting `ssd1306` above. New projects never carry
|
||||
* these ids. They force a fixed protocol (no auto-detect) to reproduce the old
|
||||
* behaviour exactly.
|
||||
*/
|
||||
PartSimulationRegistry.register('ssd1306-i2c', {
|
||||
attachEvents: (element, simulator, getPin) =>
|
||||
attachSSD1306(element, simulator, getPin, 'i2c'),
|
||||
});
|
||||
PartSimulationRegistry.register('ssd1306-spi', {
|
||||
attachEvents: (element, simulator, getPin) =>
|
||||
attachSSD1306(element, simulator, getPin, 'spi'),
|
||||
});
|
||||
|
||||
// ─── DS1307 RTC ──────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1374,8 +1374,23 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
|
|||
// (createFileGroup is a no-op for existing ids) — overwrite their files.
|
||||
useEditorStore.getState().replaceFileGroups(payload.fileGroups);
|
||||
|
||||
// Components and wires
|
||||
setComponents(payload.components);
|
||||
// Components and wires. Normalize the retired ssd1306-i2c / ssd1306-spi
|
||||
// ids (merged into the single auto-detecting `ssd1306`, issues #101/#215)
|
||||
// so old .vlx files and pre-migration snapshots still render and simulate;
|
||||
// the old id's protocol is pinned so behaviour is preserved exactly.
|
||||
const normalizedComponents = payload.components.map((c) =>
|
||||
c.metadataId === 'ssd1306-i2c' || c.metadataId === 'ssd1306-spi'
|
||||
? {
|
||||
...c,
|
||||
metadataId: 'ssd1306',
|
||||
properties: {
|
||||
protocol: c.metadataId === 'ssd1306-spi' ? 'spi' : 'i2c',
|
||||
...(c.properties ?? {}),
|
||||
},
|
||||
}
|
||||
: c,
|
||||
);
|
||||
setComponents(normalizedComponents);
|
||||
setWires(payload.wires);
|
||||
|
||||
// Active board: prefer the saved one, fall back to the first.
|
||||
|
|
|
|||
Loading…
Reference in New Issue