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 { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||||
import { PartSimulationRegistry } from '../simulation/parts/PartSimulationRegistry';
|
import { PartSimulationRegistry } from '../simulation/parts/PartSimulationRegistry';
|
||||||
import { dispatchSensorUpdate } from '../simulation/SensorUpdateRegistry';
|
import { dispatchSensorUpdate } from '../simulation/SensorUpdateRegistry';
|
||||||
|
import { useSimulatorStore } from '../store/useSimulatorStore';
|
||||||
import '../simulation/parts/ProtocolParts';
|
import '../simulation/parts/ProtocolParts';
|
||||||
|
|
||||||
// ─── Globals ──────────────────────────────────────────────────────────────────
|
// ─── Globals ──────────────────────────────────────────────────────────────────
|
||||||
|
|
@ -228,31 +229,35 @@ describe('ssd1306 — protocol auto-detect', () => {
|
||||||
expect(sim.addI2CDevice).not.toHaveBeenCalled();
|
expect(sim.addI2CDevice).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('runs SPI when DC is wired to a GPIO', () => {
|
it('runs I2C when only DC is wired (DC is the I2C address-select, not SPI)', () => {
|
||||||
const sim = makeSPISim();
|
const sim = makeI2CSim();
|
||||||
PartSimulationRegistry.get('ssd1306')!.attachEvents!(
|
PartSimulationRegistry.get('ssd1306')!.attachEvents!(
|
||||||
makeElement(),
|
makeElement(),
|
||||||
sim as any,
|
sim as any,
|
||||||
pinMap({ DC: 4 }),
|
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();
|
expect(sim.addI2CDevice).toHaveBeenCalledOnce();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('legacy ssd1306-spi alias forces SPI even with nothing wired', () => {
|
it('honors an explicit protocol property (migrated legacy projects)', () => {
|
||||||
const sim = makeSPISim();
|
// A project migrated from the old ssd1306-spi entry carries protocol:'spi';
|
||||||
PartSimulationRegistry.get('ssd1306-spi')!.attachEvents!(makeElement(), sim as any, noPins);
|
// it must run SPI even though nothing SPI-specific is wired (CS absent).
|
||||||
expect(typeof sim.spi.onByte).toBe('function');
|
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.
|
* Internal: SSD1306 attach logic, parameterised over the wire protocol.
|
||||||
* Used by the three picker entries (the generic `ssd1306` plus the two
|
* Called by the single `ssd1306` entry once the protocol has been resolved
|
||||||
* dedicated `ssd1306-i2c` / `ssd1306-spi` shortcuts).
|
* (auto-detected from the wiring, or read from an explicit `protocol` property).
|
||||||
*/
|
*/
|
||||||
function attachSSD1306(
|
function attachSSD1306(
|
||||||
element: HTMLElement,
|
element: HTMLElement,
|
||||||
|
|
@ -376,52 +376,43 @@ function attachSSD1306(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Which wire protocol did the user build? A real SSD1306 breakout is ONE
|
* Which wire protocol did the user build? A real SSD1306 breakout is ONE board
|
||||||
* board that talks either I2C or SPI depending on how it is wired: SPI drives
|
* that talks either I2C or SPI depending on how it is wired. The definitive
|
||||||
* the chip-select (CS) and data/command (DC) lines from MCU GPIOs, while I2C
|
* SPI-only signal is chip-select (CS): I2C never uses it. (DC deliberately does
|
||||||
* leaves them tied to power (address select) or unconnected. So if CS or DC is
|
* NOT count — on the 8-pin module DC doubles as the I2C address-select/SA0 line,
|
||||||
* wired to a GPIO we decode SPI; otherwise I2C. This mirrors the physical part
|
* so many I2C circuits wire it too.) So CS wired to a GPIO => SPI, otherwise
|
||||||
* — one component, no protocol switch to set, just wire it up.
|
* 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
|
* Pure wiring check: it deliberately does NOT read `simulator.spi`, whose getter
|
||||||
* getter on some boards (RP2040, and the ESP32/STM32 bridge shims) lazily
|
* on some boards (RP2040, and the ESP32/STM32 bridge shims) lazily re-routes the
|
||||||
* re-routes the board SPI bus as a side effect and must not fire in I2C mode.
|
* board SPI bus as a side effect and must not fire in I2C mode.
|
||||||
*/
|
*/
|
||||||
function detectSSD1306Protocol(getPin: (n: string) => number | null): 'i2c' | 'spi' {
|
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
|
* 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
|
* SPI bus (AVR, RP2040, ESP32, STM32). New projects just wire it up and the
|
||||||
* wiring like the physical module. Consolidates the old ssd1306 / ssd1306-i2c
|
* protocol is auto-detected from the wiring like the physical module; a
|
||||||
* / ssd1306-spi picker entries into one (issues #101 / #215).
|
* `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', {
|
PartSimulationRegistry.register('ssd1306', {
|
||||||
attachEvents: (element, simulator, getPin, componentId) => {
|
attachEvents: (element, simulator, getPin, componentId) => {
|
||||||
const { components } = useSimulatorStore.getState();
|
const { components } = useSimulatorStore.getState();
|
||||||
const comp = components.find((c) => c.id === componentId);
|
const comp = components.find((c) => c.id === componentId);
|
||||||
const i2cAddr = parseI2cAddress(comp?.properties?.i2cAddress, 0x3c);
|
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);
|
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 ──────────────────────────────────────────────────────────────
|
// ─── DS1307 RTC ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1374,8 +1374,23 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
|
||||||
// (createFileGroup is a no-op for existing ids) — overwrite their files.
|
// (createFileGroup is a no-op for existing ids) — overwrite their files.
|
||||||
useEditorStore.getState().replaceFileGroups(payload.fileGroups);
|
useEditorStore.getState().replaceFileGroups(payload.fileGroups);
|
||||||
|
|
||||||
// Components and wires
|
// Components and wires. Normalize the retired ssd1306-i2c / ssd1306-spi
|
||||||
setComponents(payload.components);
|
// 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);
|
setWires(payload.wires);
|
||||||
|
|
||||||
// Active board: prefer the saved one, fall back to the first.
|
// Active board: prefer the saved one, fall back to the first.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue