Skip to content

Peripheral Configuration ​

This document is based on source code include/core/PeripheralTypes.h, src/core/PeripheralManager.cpp, data/config/peripherals.json, include/core/FeatureFlags.h, include/core/interfaces/ISensorDriver.h, include/core/DriverRegistry.h and platformio.ini, describing all types, pin requirements, parameter meanings, support status, and best practices supported by the peripheral configuration system.

Current Version Notes ​

  • All nine release environments share the basic UART/I2C/SPI, GPIO, sensor, display, Modbus, and peripheral-execution baseline. Actual availability is the intersection of compile support, the Web feature switch, memory budget, and valid chip pins.
  • Ethernet and cellular are excluded from esp32-F4R0, esp32c3-F4R0, esp32c6-F4R0, and esp32c6-F8R0; IR/RF support also has S3-specific environment overrides. Check /api/features/status instead of relying on an old Lite/Standard/Full label.
  • Peripheral configuration is saved to /config/peripherals.json, which can be individually backed up and restored as "Peripheral Configuration" via Web "Configuration Import/Export".
  • Factory data/config/peripherals.json is empty by default ({"peripherals": []}) with no pre-configured peripherals; add your own configuration following the examples in this guide. enabled: true in documentation examples represents the target state after completing wiring verification — it is recommended to save new peripherals with enabled: false first and enable them only after verification.
  • For sensor data to serve as a peripheral execution event trigger source, you must first configure sensor collection actions in peripheral execution. The system caches collection results as ds:<peripheralId>_<field> data sources.
  • Modbus sub-devices are managed by the communication protocol page, participate in device control and peripheral execution as virtual peripherals, and do not occupy local GPIO.

The Web entry for peripheral configuration is shown below. When adding new peripherals, first select the type, then fill in pins and type parameters; for batch migration, back up peripherals.json from Device Configuration > Advanced Configuration > Configuration Import/Export first.

IDNameTypePinsStatusActions
adcADC Analog ConverterADC36DisabledEdit / Enable / Delete
canCAN BusCAN4, 5DisabledEdit / Enable / Delete
i2cI2C BusI2C17, 18DisabledEdit / Enable / Delete
jtagJTAG Debug InterfaceJTAG12, 13, 14, 15DisabledEdit / Enable / Delete
modbus_rtuModbus RTU-Industrial DeviceUART16, 17DisabledEdit / Enable / Delete
oled_ssd1306_c3OLED Display-SSD1306LCD5, 6DisabledEdit / Enable / Delete
gpio_pwmPWM OutputPWM Output21DisabledEdit / Enable / Delete
sdioSD Card SDIO InterfaceSDIO14, 15, 2, 4, 12, 13DisabledEdit / Enable / Delete
swdSWD Debug InterfaceSWD13, 14DisabledEdit / Enable / Delete
uartUART Serial PortUART16, 17DisabledEdit / Enable / Delete

Add Peripheral Dialog

When integrating a new module, it is not recommended to enable immediately after batch importing from JSON. A more stable order is to first confirm hardware and version support, then save disabled configuration via the page, verify individual items, and then enter peripheral execution rules.

If you are unsure which type to select, first narrow the scope using the five entry categories above (digital, analog, bus devices, RS485 slaves, and display output), then return to the type ID table later in this document to confirm fields.

Peripheral configuration is not a single form field but a chain from Web forms, JSON files, validation logic, runtime drivers to rule consumers. After importing configuration on-site, it is recommended to check id, type, pins, params, enabled, and version capability switches in the order shown in the diagram, confirming each layer can explain the current runtime state.

Peripheral TypeRequired FieldsKey ParametersPre-Enable CheckCommon Use
GPIO Outputid / type / pins[0]initialState / invertLoad current vs relay levelLEDs, relays, buzzers
GPIO Inputid / type / pins[0]pullup / debouncePull-up/down and debounce timeButtons, PIR, switches
ADC / Analogpins[0] / categoryvRef / ratio / offsetPrefer ADC1, voltage within rangeVoltage, current, light, rain
I2C DeviceSDA / SCL / addressbus / frequency / driverAddress scan and pull-up resistorsOLED, SHT31, BH1750
UART / RS485TX / RX / baudrateparity / stopBits / dePinA/B wiring and direction controlModbus RTU, serial devices
Display Outputtype / pins / addresswidth / height / brightnessPower, bus, display orientationOLED, TM1637, LCD
Motion Controlpins / speed / limitsangle / steps / rpmExternal power and mechanical limitsServos, steppers, motors
Virtual Peripheralid / source / mappingeventId / slaveId / registerReference chain and version capabilityDeviceEvent, Modbus sub-devices

If you only want to quickly determine which fields a peripheral type needs, check the parameter matrix first, then return to the type ID table and field descriptions later in this document. The "Pre-Enable Check" in the matrix serves as a field wiring checklist.

Table of Contents ​


1. Overview ​

The peripheral configuration system adopts a three-layer model of "type-driven + pin occupation validation + runtime state":

  • Configuration Layer: data/config/peripherals.json, loaded from LittleFS on cold boot.
  • Management Layer: PeripheralManager singleton, responsible for CRUD, pin conflict detection, hardware initialization/teardown, and generic read/write interfaces.
  • Driver Layer: Dispatches to underlying implementations by type:
    • GPIO series → pinMode/digitalWrite/ledcWrite
    • I²C/SPI/UART → Wire/SPI/Serial
    • LCD/OLED → U8g2 (LCDManager)
    • TM1637 → Custom bit-bang (SevenSegmentDriver)
    • Modbus → Delegated to ModbusHandler via callbacks

Convention: All hardware operations go through PeripheralManager uniformly; direct pinMode/digitalWrite in business code is prohibited to avoid pin occupation conflicts and state inconsistency.


Hardware Initialization and Teardown Behavior ​

When a peripheral is enabled, PeripheralManager::setupHardware() automatically performs hardware initialization; when disabled or deleted, teardownHardware() handles resource cleanup.

Peripheral TypeInitialization Behavior (setupHardware)Teardown Behavior (teardownHardware)
I2C (2)Wire.begin(sda, scl, freq)Wire.end()
SPI (3)SPI.begin(sck, miso, mosi, cs) + CS pin OUTPUT/HIGHSPI.end() + CS pin released as INPUT
DAC (27)dacWrite(pin, defaultValue)dacWrite(pin, 0) zero out
ADC (26)analogReadResolution(res) + idle read to stabilize ADC(No special cleanup needed)
PWM_SERVO (41)ledcAttach(pin, freq, res)ledcDetach(pin) + pin release
ONE_WIRE (44)pinMode(pin, INPUT_PULLUP) enable internal pull-uppinMode(pin, INPUT) release pull-up
SENSOR (38)pinMode(pin, INPUT_PULLUP) + log(No special cleanup needed)
RF_MODULE (48)TX: OUTPUT + initial level; RX: INPUTTX: restore default level
RADAR_SENSOR (49)pinMode(pin, INPUT)(No special cleanup needed)

Note: CAN(4), USB(5), JTAG(31), SWD(32), CAMERA(39), ETHERNET(40) etc. are mainly configuration placeholders or chip-level fixed capabilities; SDIO(37) currently supports SPI lazy initialization; ENCODER(43) supports GPIO interrupt counting, see section 7.8.

2. Peripheral Type ID Mapping Table ​

IDEnum ConstantCategoryDisplay NameImplementation StatusPin Count
0UNCONFIGURED——UnconfiguredPlaceholder0
1UARTCommunicationSerial port✅ Implemented2 (RX,TX)
2I2CCommunicationI2C bus✅ Implemented2 (SDA,SCL)
3SPICommunicationSPI bus✅ Implemented4 (MISO,MOSI,SCK,CS)
4CANCommunicationCAN bus⚠️ Config framework ready, driver TODO2
5USBCommunicationUSB interface⚠️ Config framework ready, driver TODO2
11GPIO_DIGITAL_INPUTGPIODigital input✅ Implemented1
12GPIO_DIGITAL_OUTPUTGPIODigital output✅ Implemented1
13GPIO_DIGITAL_INPUT_PULLUPGPIODigital input (pull-up)✅ Implemented / Button default type1
14GPIO_DIGITAL_INPUT_PULLDOWNGPIODigital input (pull-down)✅ Implemented1
15GPIO_ANALOG_INPUTGPIOAnalog input✅ Implemented (analogRead)1
16GPIO_ANALOG_OUTPUTGPIOAnalog output✅ Implemented (LEDC PWM analog)1
17GPIO_PWM_OUTPUTGPIOPWM output✅ Implemented (LEDC)1
18GPIO_INTERRUPT_RISINGGPIOInterrupt (rising edge)🟡 ISR skeleton exists, event queue TODO1
19GPIO_INTERRUPT_FALLINGGPIOInterrupt (falling edge)🟡 ISR skeleton exists, event queue TODO1
20GPIO_INTERRUPT_CHANGEGPIOInterrupt (change)🟡 ISR skeleton exists, event queue TODO1
21GPIO_TOUCHGPIOCapacitive touch⚠️ Chip dependent; ESP32 classic supports1
26ADCAnalog SignalADC✅ Implemented1
27DACAnalog SignalDAC✅ Implemented (GPIO25/26 only, ESP32 classic)1
31JTAGDebugJTAG debug🔘 Type marker only, chip-level fixed4
32SWDDebugSWD debug🔘 Type marker only, chip-level fixed2
36LCDDedicatedLCD/OLED display✅ Implemented (U8g2: SSD1306/SH1106 etc.)2+
37SDIODedicatedSD card interface🟡 SPI mode lazy init; SDMMC GPIO init only6
38SENSORDedicatedGeneric sensor (DHT/DS18B20)✅ Implemented (FASTBEE_ENABLE_SENSOR_DRIVER)1
39CAMERADedicatedCamera⚠️ Config framework ready, driver TODO8
40ETHERNETDedicatedEthernet⚠️ Config framework ready, driver TODO4
41PWM_SERVODedicatedServo✅ Implemented (LEDC)1
42STEPPER_MOTORDedicatedStepper motor✅ Implemented (ULN2003 4-phase half-step, non-blocking Ticker)4 (IN1,IN2,IN3,IN4)
43ENCODERDedicatedEncoder✅ GPIO interrupt counting implemented2
44ONE_WIREDedicatedOne-Wire✅ Implemented via SENSOR driver chain (DS18B20)1
45NEO_PIXELDedicatedWS2812B LED/pixel strip✅ Implemented (ESP32 RMT, disabled by default)1
46RESERVED_46Compat reservedReserved🔒 Legacy buzzer type placeholder, UI no longer shows0
47SEVEN_SEGMENT_TM1637DedicatedTM1637 4-digit display✅ Implemented (FASTBEE_ENABLE_SEVEN_SEGMENT)2 (CLK,DIO)
48RF_MODULEDedicated433MHz RF module✅ Implemented (OOK/EV1527)1
49RADAR_SENSORDedicatedMicrowave radar sensor (RCWL-0516)✅ Implemented1
51MODBUS_DEVICEModbusModbus sub-device✅ Implemented (no GPIO occupied)0
60DEVICE_EVENTVirtualDevice event emitter✅ Implemented (no hardware)0

Legend: ✅ Fully implemented and enabled by default / 🟡 Partially implemented / ⚠️ Config placeholder only / 🔒 Disabled by default / 🔘 Type marker only


3. Communication Interfaces ​

3.1 UART (type=1) ​

FieldMeaningValues
pins[0]RX (Receive)Any valid GPIO
pins[1]TX (Transmit)Any valid GPIO
params.baudRateBaud rate1 ~ 5000000, commonly 9600/115200
params.dataBitsData bits5~8
params.stopBitsStop bits1 or 2
params.parityParity0=None / 1=Odd / 2=Even

Example:

json
{ "id": "uart0", "name": "Serial0-Debug", "type": 1, "enabled": true,
  "pins": [1, 3],
  "params": { "baudRate": 115200, "dataBits": 8, "stopBits": 1, "parity": 0 } }

Note: ESP32 defaults Serial to GPIO1/3 (USB debug). Modbus RTU typically uses Serial2 (GPIO16/17).

3.2 I2C (type=2) ​

FieldMeaningValues
pins[0]SDARecommended GPIO21
pins[1]SCLRecommended GPIO22
params.frequencyClock frequencyOnly 100000 / 400000 / 1000000 supported
params.addressSlave address0~127, master mode is 0
params.isMasterIs mastertrue (recommended)

Example:

json
{ "id": "i2c", "name": "I2C Bus", "type": 2, "enabled": false,
  "pins": [21, 22],
  "params": { "frequency": 100000, "address": 0, "isMaster": true } }

3.3 SPI (type=3) ​

FieldMeaning
pins[0]MISO
pins[1]MOSI
pins[2]SCK
pins[3]CS
params.frequency1 ~ 80 MHz
params.mode0~3
params.msbFirsttrue = MSB first

3.4 CAN / USB (Driver Not Implemented) ​

Configuration data will be saved, but setupHardware will print not yet implemented. Business layer must implement drivers before use.


4. GPIO Interfaces ​

4.1 Digital Input / Output (type=11~14) ​

TypeDescriptionTypical Use
11 GPIO_DIGITAL_INPUTHigh-impedance input, external pull neededEncoder A/B phases
12 GPIO_DIGITAL_OUTPUTPush-pull outputRelays, LEDs
13 GPIO_DIGITAL_INPUT_PULLUPInternal pull-upButtons (press to GND)
14 GPIO_DIGITAL_INPUT_PULLDOWNInternal pull-downButtons (press to VCC)

Button Events: Only type=13/14 are scanned by PeriphExecScheduler::checkButtonEvents, supporting button_click / button_double_click / button_long_press_2s/5s/10s.

4.2 Analog Input (type=15) / ADC (type=26) ​

  • ESP32 classic: ADC1 (GPIO32~39) recommended for concurrent WiFi use; ADC2 conflicts with WiFi.
  • analogRead(pin) returns 0~4095 (12-bit).

4.3 PWM Output (type=17) / Analog Output (type=16) / Servo (type=41) ​

ParameterDescriptionConstraint
pwmChannelLEDC channel0 ~ CHIP_MAX_PWM_CH-1 (16 for ESP32)
pwmFrequencyFrequency Hzfreq × 2^resolution ≤ 80MHz
pwmResolutionResolution bits1~16
defaultDutyDefault duty cycle0 ~ (2^resolution - 1)

Frequency/Resolution Combination Limits (examples):

  • 1kHz × 13 bits = 8.192M ✅
  • 40kHz × 12 bits = 163.84M ❌

4.4 Interrupts (type=18/19/20) ​

Current implementation: isrHandler only records the trigger pin number; FreeRTOS queue dispatch to main loop is not yet implemented. Use button events (type=13/14) as an alternative.

4.5 Touch (type=21) ​

Only valid for chips with CHIP_HAS_TOUCH (ESP32 classic supports T0~T9).


5. Analog Signal Interfaces ​

5.1 DAC (type=27) ​

  • Hardware Limitation: Only ESP32 classic GPIO25/26 have real DAC; other chips/pins will be rejected.
  • Output range: 8-bit (0~255), corresponding to 0V~VDD.

5.2 ADC (type=26) ​

Equivalent to GPIO_ANALOG_INPUT, distinguished by params.attenuation (0~3) and params.resolution (9~12).


6. Debug Interfaces ​

JTAG=31 / SWD=32 are type marker positions. ESP32's JTAG pins are fixed at GPIO12~15; enabling them affects normal GPIO functionality of these pins.


7. Dedicated Peripherals ​

7.1 LCD/OLED Display (type=36) ​

Implementation Status: ✅ Fully implemented and compiled in the release baseline (FASTBEE_ENABLE_LCD=1); runtime use requires the lcd feature to be active.

Supported Controllers (covered by U8g2 library):

  • SSD1306 (128×64 / 128×32 OLED, I²C 0x3C)
  • SH1106 (128×64 OLED)
  • Other character/graphic LCDs supported by U8g2

Parameters:

FieldMeaningValues
pins[0]SDA (I²C) / MOSI (SPI)
pins[1]SCL (I²C) / SCK (SPI)
pins[2]CS (SPI only)Optional
pins[3]DC (SPI only)Optional
params.widthWidth128
params.heightHeight64 / 32
params.interfaceInterface0=Parallel / 1=SPI / 2=I2C (default)

Example:

json
{ "id": "oled_display", "name": "OLED Display", "type": 36, "enabled": true,
  "pins": [23, 22],
  "params": { "width": 128, "height": 64, "interface": 2 } }

Related Rule Actions: ACTION_DISPLAY_CUSTOM (OLED custom display, supports multi-line text + variable interpolation). See oled-usage-guide.md.

7.2 TM1637 Seven-Segment Display (type=47) ​

Implementation Status: ✅ Implemented with the custom bit-bang SevenSegmentDriver; runtime use requires the feature to be active.

FieldMeaning
pins[0]CLK
pins[1]DIO
params.brightnessBrightness 0~7

Constraint: CLK/DIO cannot share pins with other peripherals. Due to historical pin conflict issues with buttons, always allocate separate GPIOs for the seven-segment display.

7.3 Generic Sensor (type=38) / One-Wire (type=44) ​

SubclassPinsDescription
DHT11/DHT221 DATATemperature and humidity
DS18B201 DQ (1-Wire)Temperature

Read and cache via peripheral execution action ACTION_SENSOR_READ. Requires FASTBEE_ENABLE_SENSOR_DRIVER enabled (on by default).

7.4 Reserved (type=46) ​

The legacy dedicated buzzer type has been removed. type=46 is retained as a historical number only; the Web no longer provides an add entry.

7.5 WS2812B / NeoPixel LED (type=45) ​

Uses ESP32 RMT peripheral to send WS2812B GRB timing, without depending on third-party NeoPixel libraries. pins[0] connects to WS2812B's DIN; default test template is ws2812b, GPIO4, 1 LED, brightness 64, disabled by default.

FieldMeaningDefault / Range
pins[0]DIN data pinValid output GPIO
params.countLED countDefault 1, max 64
params.brightnessGlobal brightnessDefault 64, range 0~255

Peripheral execution controls via ACTION_CALL_PERIPHERAL:

actionValue ExampleBehavior
{"periphId":"ws2812b","action":"color","value":"#ff0000"}Display red
{"periphId":"ws2812b","action":"off"}Turn off
{"periphId":"ws2812b","action":"rainbow"}Advance one step through rainbow cycle
{"periphId":"ws2812b","action":"brightness","value":"96"}Set brightness

7.6 Servo (type=41) ​

Uses LEDC 50Hz PWM, pulse width 0.5ms~2.5ms corresponding to 0°~180°.

7.7 Stepper Motor (type=42) ​

Designed for 28BYJ-48 + ULN2003 type 4-phase stepper motor driver boards. pins[0..3] connect to ULN2003's IN1, IN2, IN3, IN4 in order. The driver uses an 8-step half-step sequence with non-blocking Ticker output, avoiding long blocking of the Web service during peripheral execution actions.

FieldMeaningDefault / Range
pins[0]IN1Valid GPIO
pins[1]IN2Valid GPIO
pins[2]IN3Valid GPIO
pins[3]IN4Valid GPIO
params.stepsPerRevolutionSteps per revolutionDefault 2048
params.speedDefault RPMDefault 8, max 30

Peripheral execution can control this peripheral via ACTION_CALL_PERIPHERAL, supporting forward, reverse, stop, faster, slower, setSpeed, direction and other actions.

Safety Reminder: GPIO9/10/11 on classic ESP32 are typically occupied by Flash SPI. The firmware will reject enabling stepper motors on reserved pins for the current chip to avoid restarts from misconfiguration. GPIO 11/10/9/13 are more suitable for ESP32-S3; classic ESP32 should use available free GPIOs.

7.8 SD Card / Encoder / Camera / Ethernet ​

Current implementation status:

TypeStatusDescription
SDIO🟡 Partially implementedSPI mode initializes CLK/MOSI/MISO/CS with lazy mount; SDMMC mode currently only does GPIO initialization and log prompts
ENCODER✅ ImplementedUses GPIO interrupt for A/B phase counting, supports reading counter status and resetting counter
CAMERA⚠️ PlaceholderConfiguration only, camera driver not integrated
ETHERNET peripheral type⚠️ PlaceholderPeripheral type saves config only; real W5500 networking controlled by NetworkManager / EthernetAdapter and FASTBEE_ENABLE_ETHERNET

7.9 RF Module (type=48) ​

433MHz OOK/EV1527 RF module, supporting transmit (TX) and receive (RX) modes.

Pin Requirements: 1 data pin (TX mode requires output-capable pin, RX mode can be input pin)

params Parameters:

FieldTypeDefaultDescription
modeuint8_t00=TX transmit, 1=RX receive level monitor
pulseWidthuint16_t350Pulse width (microseconds)
repeatuint8_t3Transmit repeat count
bitLengthuint8_t24Code bit length
activeHighbooltruetrue: HIGH = RF active

Hardware Initialization: TX mode sets pin as OUTPUT, RX mode sets pin as INPUT.

7.10 Radar Sensor (type=49) ​

RCWL-0516 / 5.8GHz microwave radar digital output sensor.

Pin Requirements: 1 OUT pin (digital input)

params Parameters:

FieldTypeDefaultDescription
modeuint8_t00=Digital OUT pin mode
activeHighbooltruetrue: HIGH = target detected
debounceMsuint16_t200Debounce time (milliseconds)
holdMsuint16_t2000Hold time (milliseconds)

Hardware Initialization: Pin set to INPUT, reads digital level to determine if target is present.


8. Modbus Peripherals ​

8.1 MODBUS_DEVICE (type=51) ​

Virtual peripheral, does not occupy local GPIO, and communicates with a slave through the configured RS485 / Modbus RTU bus.

params.modbus.* FieldMeaningValues
slaveAddressSlave address1~247
deviceTypeDevice type0=Relay / 1=PWM / 2=PID etc.
controlProtocolControl protocol0=Coil FC05 / 1=Register FC06
coilBaseCoil base address
pwmRegBasePWM register base address
ncModeNormally closed mode (state inversion)bool

Notes:

  • Modbus peripherals are not persisted via peripherals.json, managed uniformly by protocol.json.
  • Physical bus (RS485) requires one UART + DE control pin; DE pin must not be multiplexed by button or other GPIO peripherals.
  • See historical Lesson: Modbus dePin and button pin non-multiplexable.

9. Virtual/Logical Peripherals ​

9.1 DEVICE_EVENT (type=60) ​

  • No pins, no hardware.
  • Serves only as an "event emitter" in the rule system, reported via MQTT DEVICE_EVENT topic after triggering.
  • Configuration uses pinCount=0, pins can be omitted, validation only checks id/name non-empty.

Typical Use: System state changes, user actions, composite events (e.g., "three consecutive button clicks trigger alarm").


10. Configuration Structure and Fields ​

The parameter matrix is best used with this chapter: first confirm common fields id, type, pins, enabled, then check params for address, range, calibration, frequency, or action boundaries by peripheral type.

peripherals.json top-level structure:

jsonc
{
  "peripherals": [
    {
      "id": "Unique ID (letters/numbers/underscores)",
      "name": "Display name",
      "type": 36,              // PeripheralType enum value
      "enabled": true,         // Only enabled=true peripherals occupy pins
      "pins": [21, 22],        // Ordered, unused positions set to 255 or omitted
      "params": {              // Type-specific parameters (see each chapter)
        "width": 128,
        "height": 64,
        "interface": 2
      }
    }
  ]
}

10.1 General Constraints ​

  • id must be globally unique; renaming (name) can be done via PUT directly; changing ID requires DELETE then POST.
  • pins[] maximum 8 entries, pinCount auto-inferred from non-255 count.
  • Disabled (enabled=false) peripherals do not occupy pins — allowing multiple peripherals to declare the same pin with only one enabled.
  • Reserved pins (Flash SPI, Boot, USB D+/D-) are defined by ChipConfig.h, validatePinForType rejects out-of-bounds.

10.2 Pin Conflict Detection ​

  • When adding or enabling a peripheral, checkPinConflict scans all enabled non-Modbus peripherals.
  • If residual cache is detected (pinToPeripheral inconsistent with peripherals), it auto-runs rebuildPinMapping once and retries.

11. Feature Compile Flags (FeatureFlags) ​

Key flags are in include/core/FeatureFlags.h, overridable in platformio.ini build_flags:

MacroTier DefaultDescription
FASTBEE_ENABLE_LCD1U8g2 LCD/OLED driver
FASTBEE_ENABLE_SEVEN_SEGMENT1TM1637 driver
FASTBEE_ENABLE_NEOPIXEL1WS2812B / NeoPixel strip
FASTBEE_ENABLE_LED_SCREEN1 in the unified baselineLED screen extension entry; runtime-gated
FASTBEE_ENABLE_SENSOR_DRIVER1DHT/DS18B20
FASTBEE_ENABLE_MODBUS1 in the unified baselineModbus RTU master; final availability is runtime-gated
FASTBEE_ENABLE_PERIPH_EXEC1Peripheral execution rules (timer/button/event)

11.1 Preset Environments (from platformio.ini) ​

CapabilityRelease BaselineRuntime Rule
LCD/OLED✅Requires lcd active; large fonts may be budget-rejected
TM1637✅Requires the driver feature active
NeoPixel✅Requires neoPixel active and a safe LED count
CoAP❌Not part of the current release firmware

12. FAQ and Troubleshooting ​

12.1 "Pin X already occupied by peripheral '<Unknown>'" ​

Cause: pinToPeripheral cache residual (auto rebuildPinMapping fallback added in addPeripheral). Troubleshooting:

  1. Confirm whether an enabled peripheral with this pin actually exists in peripherals.json.
  2. Watch serial log for stale pin mapping detected, rebuilding cache.
  3. If conflict still reported, the target peripheral is actually enabled; disable it or change pins first.

12.2 Button events stop working after long runtime ​

Historical root cause (fixed):

  • dispatchAsync self-healing blind spot (permanent skip when startTime missing).
  • dispatchByRuleId lock timeout without retry; checkTimerTriggers holds lock for 100ms during which button events are dropped.

Current implementation: dispatchAsync detects startTimeMissing || stuck>60s and auto-cleans; dispatchByRuleId retries 3 × 50ms. See Lesson: FastBee-Arduino button failure self-healing fix.

12.3 TM1637 Sharing Pins with Buttons ​

Prohibited. TM1637 bit-bang driver frequently toggles CLK/DIO, conflicting with button scanning and causing bidirectional failure. Solution: Allocate 2 separate GPIOs for TM1637 (typical GPIO18/19).

12.4 Modbus dePin Conflicts with Button/GPIO ​

Modbus RTU RS485 DE (Direction Enable) pin is configured in protocol.json as modbus.dePin, must not duplicate any enabled peripheral's pins in peripherals.json. Duplication causes communication direction switching anomalies or button failures.

12.5 DAC Write Failure ​

Only ESP32 classic GPIO25 / GPIO26 support hardware DAC. ESP32-S3/C3 do not support it; enabling returns DAC not supported on this chip.

12.6 OLED Not Lighting Up ​

Troubleshooting order:

  1. I²C address: Most SSD1306 are 0x3C, some 0x3D (need manual adjustment in LCDManager).
  2. Pins: SDA/SCL order must not be reversed.
  3. Power: 0.96" OLED typical operating voltage 3.3V, current within 20mA.
  4. params.interface must be 2 (I²C).

12.7 LCD vs LED Confusion ​

LCD/OLED (type=36)LED Strip (type=45, NeoPixel)
PurposeCharacter/graphic displayColorful pixel lights
InterfaceI²C / SPIRMT (single signal line WS2812B)
Driver LibraryU8g2Adafruit NeoPixel
Default Enabled✅✅

If you want to drive "a single LED", use GPIO_DIGITAL_OUTPUT (type=12) or GPIO_PWM_OUTPUT (type=17); for "WS2812 strip", use NEO_PIXEL (type=45); displays are LCD (type=36).


13. Best Practices ​

13.1 Naming ​

  • Use snake_case English for id (e.g., oled_display, key1, tm1637_01) for easy rule engine script reference.
  • name can be Chinese or English, user-facing display.

13.2 Pin Assignment Strategy ​

  1. Prefer safe pins: GPIO4/5/16/17/18/19/21/22/23/25/26/27/32/33.
  2. Reserve: GPIO0 (Boot), GPIO1/3 (UART0 debug), GPIO6-11 (Flash).
  3. Input-only: GPIO34~39, can only be used as input.
  4. Reserve separate GPIOs for buttons, do not share with TM1637/OLED/Modbus DE.

13.3 Configuration Evolution ​

  • When modifying peripheral type or pins, first disable in Web UI → save → re-enable to avoid residual interrupts during hot switching.
  • When peripheral IDs referenced by rule configuration (periph_exec.json) change, targets[] must be updated accordingly.

13.4 Debugging Tools ​

  • Serial logs: Filter by Peripheral Manager: ... prefix.
  • Web → Peripheral Management: Overview of all peripherals, real-time status, pin occupation.
  • pio device monitor -p COM6 -b 115200.

13.5 Extending New Peripherals ​

  1. Add enum value in PeripheralTypes.h (following segment ID rules).
  2. Update getPeripheralTypeName / parsePeripheralType / getPeripheralPinCount.
  3. Add initialization branch in PeripheralManager::setupHardware.
  4. Add <option> and data-i18n in web-src/pages/modals.html.
  5. Add translation keys in web-src/i18n/i18n-zh-CN.js / i18n-en.js.
  6. (Optional) Add rule action support in web-src/modules/runtime/periph-exec-form.js.

Tip: If the peripheral to extend is a sensor-type peripheral (requiring periodic reading of temperature/humidity/light values), it is recommended to prefer the ISensorDriver driver interface in Chapter 14, without modifying PeripheralManager core code.


14. Sensor Driver Extension (ISensorDriver) ​

This chapter describes the sensor driver abstraction interface and hot-plug registration mechanism added in this optimization. The goal is to decouple sensor hardware reading logic from hardcoded branches in PeripheralManager/PeriphExec, making it easier to add new devices like SHT31, BMP280, SCD41 without modifying the core scheduling layer.

14.1 Design Goals ​

  • Encapsulate "hardware initialization + periodic reading + multi-channel output" into independent driver classes.
  • Use static registration macros for self-registration before main(), adding new drivers only requires "create a header file + include once".
  • Driver registry uses fixed-capacity static array (MAX_SENSOR_DRIVERS = 8), zero dynamic allocation, zero fragmentation.
  • Supports up to 4-channel reading (e.g., DHT temperature + humidity), named channels (name/unit) for easy frontend rendering.

14.2 Core Interface ​

Header: include/core/interfaces/ISensorDriver.h

cpp
struct SensorReading {
    bool success = false;
    float values[4] = {0};      // Up to 4 channels
    uint8_t channelCount = 0;
    unsigned long timestamp = 0;
    // Semantic accessors
    float temperature() const { return channelCount > 0 ? values[0] : NAN; }
    float humidity()    const { return channelCount > 1 ? values[1] : NAN; }
};

class ISensorDriver {
public:
    virtual ~ISensorDriver() = default;
    virtual const char* getName() const = 0;                         // Driver type name, e.g. "sht31"
    virtual uint8_t getChannelCount() const = 0;                      // Channel count 1~4
    virtual const char* getChannelName(uint8_t ch) const = 0;         // "temperature" / "humidity" etc.
    virtual const char* getChannelUnit(uint8_t ch) const = 0;         // "C" / "%" / "lux" etc.
    virtual bool init(uint8_t pin, const char* params = nullptr) = 0; // params is JSON string, optional
    virtual bool read(SensorReading& reading) = 0;
    virtual void deinit() = 0;
    virtual unsigned long getMinInterval() const { return 1000; }     // Min sampling interval (ms)
};

14.3 Registration Mechanism ​

Header: include/core/DriverRegistry.h

ElementDescription
DriverRegistry::getInstance()Singleton, globally unique
registerDriver(name, factory)Called by static constructor automatically, usually no manual call needed
createDriver(name)Create driver instance by name, returns ISensorDriver*, nullptr on failure
hasDriver(name)Check if driver is registered
FASTBEE_REGISTER_SENSOR(name, Class)Macro, use once at end of file for self-registration

The registry internally uses std::array<SensorDriverEntry, 8>; when full, registerDriver returns false (visible at build time), avoiding runtime failures.

14.4 Writing Custom Drivers ​

Using include/peripherals/drivers/SHT31Driver.h as template:

cpp
#include "core/interfaces/ISensorDriver.h"
#include "core/DriverRegistry.h"

class MySensorDriver : public ISensorDriver {
public:
    const char* getName() const override { return "my_sensor"; }
    uint8_t getChannelCount() const override { return 2; }
    const char* getChannelName(uint8_t ch) const override {
        return ch == 0 ? "temperature" : "humidity";
    }
    const char* getChannelUnit(uint8_t ch) const override {
        return ch == 0 ? "C" : "%";
    }
    bool init(uint8_t pin, const char* params) override {
        // TODO: Initialize I2C / OneWire / pins
        return true;
    }
    bool read(SensorReading& r) override {
        r.values[0] = 25.3f;
        r.values[1] = 60.0f;
        r.channelCount = 2;
        r.success = true;
        r.timestamp = millis();
        return true;
    }
    void deinit() override {}
    unsigned long getMinInterval() const override { return 2000; }
};

// Auto-registration: include this header once in any .cpp
FASTBEE_REGISTER_SENSOR("my_sensor", MySensorDriver);

Key Points:

  1. Driver header placed in include/peripherals/drivers/ directory.
  2. Must be explicitly #included once by src/main.cpp (or any linked .cpp), triggering static object construction → completing registration.
  3. Usage: ISensorDriver* drv = DriverRegistry::getInstance().createDriver("my_sensor");

14.5 Relationship with Existing Sensor Logic ​

  • Current Status: Hardcoded reading branches for DHT11/DHT22/DS18B20 etc. in PeriphExec are still in use, not migrated to ISensorDriver; functionality and compatibility are unaffected.
  • Recommended Strategy:
    • New devices (SHT31/BMP280/SCD41/BH1750 etc.) should prefer ISensorDriver.
    • Existing devices migrate on demand after complete regression, not mandatory.
  • Capacity Increase: If 8 drivers are insufficient, modify MAX_SENSOR_DRIVERS constant in DriverRegistry.h and recompile.

Reference Documentation ​

FastBee | fastbee.cn