onewire.tc¶
1-Wire Driver — Temperature Sensors + Output Switches
// ============================================================================
// 1-Wire Driver — Temperature Sensors + Output Switches
// ============================================================================
//
// Supported devices on the 1-Wire bus
// -----------------------------------
// Temperature DS18B20 / DS18S20 / DS1822
// Switches DS2413 (2-channel open-drain) family 0x3A
// DS2406 (1- or 2-channel open-drain) family 0x12
// DS2408 (8-channel GPIO) family 0x29
// Caps 16 temp sensors + 16 switch devices per bus
//
// Bus backends (auto-detected priority cascade at startup)
// --------------------------------------------------------
// 1. DS2484 I2C-to-1-Wire bridge (I2C addr 0x18, both buses probed)
// 2. DS2480B serial-to-1-Wire (rx/tx pins; presence response required)
// 3. GPIO bit-bang (timing in C, needs external 4.7k pullup)
//
// The first backend that answers wins; the others stay silent. Whichever
// won is shown in the WebUI status line. Re-running the cascade (e.g.
// after rewiring) only takes changing any pin/bus dropdown in the WebUI.
//
// WebUI
// -----
// Configuration form lets you set:
// GPIO Pin — pin for GPIO bit-bang fallback
// DS2480B RX — UART RX, only meaningful if a DS2480B chip is wired
// DS2480B TX — UART TX, same
// DS2484 has no pin field — its I2C bus is sniffed automatically. The
// status line reports which backend won and its config.
//
// First two switch devices get on-page buttons (A / B channels). Devices
// beyond #1 are controlled via the OW console command.
//
// Console commands (prefix "OW")
// -------------------------------
// OW — show status JSON of all devices
// OW <n> — show status of one switch device
// OW SCAN — re-run cascade + ROM search
//
// Switch state — DS2413 / DS2406 convention: 0 = ON, 1 = OFF
// OW <n> A <0|1> — set channel A
// OW <n> B <0|1> — set channel B (2-channel devices only)
// OW <n> <byte> — DS2408: write 8-bit port byte (0xFF=all off)
//
// Aliases — friendly names that follow each device's 64-bit ROM,
// persist across reboots, show up in WebUI + JSON status
// OW NAME T <idx> <name> — name temp sensor #idx
// OW NAME S <idx> <name> — name switch device #idx
// OW NAME T|S <idx> — (no name arg) clear the alias
//
// Examples
// --------
// OW NAME T 0 Boiler OW 1 A 0 → switch 1 PIO-A ON
// OW NAME S 0 Pump OW 1 0x55 → DS2408 device 1: bits 0,2,4,6 on
// OW SCAN OW → JSON dump of everything
//
// ============================================================================
#define OW_MAX_SENS 16
#define OW_MAX_SW 16
// 1-Wire ROM commands
#define OW_SEARCH_ROM 0xF0
#define OW_SKIP_ROM 0xCC
#define OW_MATCH_ROM 0x55
// DS18B20 commands
#define DS_CONVERT 0x44
#define DS_READ_SCRATCH 0xBE
// DS18x20 family codes
#define FAM_DS18B20 0x28
#define FAM_DS18S20 0x10
#define FAM_DS1822 0x22
// DS2413 (2-ch switch) family + commands
#define FAM_DS2413 0x3A
#define DS2413_ACCESS_WRITE 0x5A
#define DS2413_ACCESS_READ 0xF5
// DS2406 (2-ch switch) family + commands
#define FAM_DS2406 0x12
#define DS2406_CH_ACCESS 0xF5
#define DS2406_WRITE_STATUS 0x55
// DS2408 (8-ch GPIO) family + commands
#define FAM_DS2408 0x29
#define DS2408_CHANNEL_WRITE 0x5A
#define DS2408_CHANNEL_READ 0xF0
#define DS2408_READ_PIO 0xF5
// DS2480B constants
#define DS_RESET 0xC1
#define DS_DATA_MODE 0xE1
#define DS_CMD_MODE 0xE3
#define DS_PULSE_TERM 0xF1
#define DS_WRITE1 0x91
#define DS_WRITE0 0x81
#define DS_RESET_OK 0xCD
// DS2484 I2C-to-1-Wire bridge constants
#define DS84_ADDR 0x18 // 7-bit I2C address (slave addr is fixed)
#define DS84_DRST 0xF0 // Device Reset (single-byte cmd)
#define DS84_SRP 0xE1 // Set Read Pointer (then ptr byte)
#define DS84_WCFG 0xD2 // Write Configuration (then byte: hi nibble = ~lo)
#define DS84_1WRS 0xB4 // 1-Wire Reset (single byte; status auto-pointed)
#define DS84_1WSB 0x87 // 1-Wire Single Bit (arg: 0x80=write-1, 0x00=write-0)
#define DS84_1WRB 0x96 // 1-Wire Read Byte (then read data from RDATA)
#define DS84_1WWB 0xA5 // 1-Wire Write Byte (then byte)
#define DS84_1WT 0x78 // 1-Wire Triplet (arg: branch direction in bit 7)
#define DS84_RP_STAT 0xF0 // read-pointer target: Status register
#define DS84_RP_RDATA 0xE1 // read-pointer target: Read-Data register
#define DS84_ST_1WB 0x01 // Status bit: 1-Wire Busy
#define DS84_ST_PPD 0x02 // Status bit: Presence Pulse Detected
#define DS84_ST_SBR 0x20 // Status bit: Single-Bit Result
// --- config ---
// Single-backend operation chosen via auto-detect cascade in ow_reinit():
// 1. probe DS2484 on the configured I2C bus (auto)
// 2. else try DS2480B (serial init using the configured rx/tx pins)
// 3. else fall back to native GPIO bit-bang on the configured pin
// ow_mode is the result, NOT a user choice — the WebUI just shows it
// and lets the user pick the pin/bus that each backend would use.
persist watch int ow_pin; // GPIO mode pin (WebUI-writable → watch)
persist watch int ow_rxpin; // DS2480B serial RX (WebUI-writable → watch)
persist watch int ow_txpin; // DS2480B serial TX (WebUI-writable → watch)
persist int ow_i2c_bus; // DS2484 bus 0/1 — auto-set by ow_84_init probe
int ow_mode = 0; // detected backend: 0=GPIO, 1=DS2480B, 2=DS2484
int ow_serial = -1; // serial port handle (-1 = closed; 0..2 = valid TasmotaSerial slot returned by serialBegin)
int ow_ok = 0; // driver active
int ow_tick = 0; // even/odd second counter
int ow_ds_cmd = 1; // DS2480B in command mode flag
// temperature sensor data
int ow_cnt = 0; // number of temp sensors
char ow_rom[16][8]; // 16 sensors x 8 bytes ROM
float ow_temp[16]; // temperatures
int ow_valid[16]; // valid reading flag
// switch device data
int ow_sw_cnt = 0; // number of switch devices
char ow_sw_rom[16][8]; // 16 devices x 8 bytes ROM
int ow_sw_type[16]; // family code (0x3A or 0x29)
int ow_sw_state[16]; // output state (DS2413: bits 0-1, DS2408: bits 0-7)
int ow_sw_input[16]; // readback input state
int ow_sw_valid[16]; // readback valid flag
int ow_sw_ch[16]; // channel count (1=DS2406P, 2=DS2413/DS2406, 8=DS2408)
// WebUI button variables for DS2413 switches (up to 2 devices x 2 channels)
int ow_sw0a = 0; // device 0 PIO-A
int ow_sw0b = 0; // device 0 PIO-B
int ow_sw1a = 0; // device 1 PIO-A
int ow_sw1b = 0; // device 1 PIO-B
// WebUI number inputs for DS2408 (up to 2 devices)
int ow_port0 = 0; // device 0 port byte
int ow_port1 = 0; // device 1 port byte
// track previous values for change detection
int ow_sw0a_prev = 0;
int ow_sw0b_prev = 0;
int ow_sw1a_prev = 0;
int ow_sw1b_prev = 0;
int ow_port0_prev = 0;
int ow_port1_prev = 0;
// alias names (persist across reboots)
// ROM-based: 16 entries x 20 bytes (8 ROM + 12 name) = 320 bytes
#define OW_NLEN 12
#define OW_ALEN 20
#define OW_MAX_ALIAS 16
persist char ow_alias[320]; // ROM-to-name alias table
// buffers
char ow_buf[16];
char ow_lbl[32];
char ow_arom[8]; // temp ROM buffer for alias lookup
char ow_aname[16]; // temp name buffer for alias result
// ---- CRC8 (1-Wire polynomial 0x8C reflected) ----
int ow_crc8(int len) {
int crc = 0;
int i = 0;
while (i < len) {
int byte = ow_buf[i] & 0xFF;
int j = 0;
while (j < 8) {
int mix = (crc ^ byte) & 0x01;
crc = crc >> 1;
if (mix) crc = crc ^ 0x8C;
byte = byte >> 1;
j = j + 1;
}
i = i + 1;
}
return crc;
}
// ========================================
// DS2480B serial 1-Wire bridge
// ========================================
// Wait for serial reply, up to 50ms
int ow_ds_wait() {
int i = 0;
while (i < 50) {
if (serialAvailable(ow_serial) > 0) return 1;
delay(1);
i = i + 1;
}
return 0;
}
int ow_ds_init() {
int ret = serialBegin(ow_rxpin, ow_txpin, 9600, 3, 64);
if (ret < 0) return 0;
ow_serial = ret; // store the handle (0..2) returned by serialBegin
delay(100);
// send 0xC1 to init (same as library begin()). A real DS2480B chip
// echoes a presence byte; serialBegin alone returning OK just means
// the pins were free, NOT that anything's wired there — so require
// an actual response or release the port and fail the probe.
serialWriteByte(ow_serial, DS_RESET);
if (!ow_ds_wait()) {
serialClose(ow_serial);
ow_serial = -1;
return 0;
}
serialRead(ow_serial); // discard the presence byte
ow_ds_cmd = 1;
return 1;
}
int ow_ds_reset() {
if (!ow_ds_cmd) {
serialWriteByte(ow_serial,DS_CMD_MODE);
delay(1);
ow_ds_cmd = 1;
}
serialWriteByte(ow_serial,DS_RESET);
if (ow_ds_wait()) {
int resp = serialRead(ow_serial) & 0xFF;
if (resp == DS_RESET_OK) return 1; // 0xCD = presence detected
return 0;
}
return 0;
}
void ow_ds_wbit(int b) {
if (!ow_ds_cmd) {
serialWriteByte(ow_serial,DS_CMD_MODE);
ow_ds_cmd = 1;
}
if (b) {
serialWriteByte(ow_serial,DS_WRITE1);
} else {
serialWriteByte(ow_serial,DS_WRITE0);
}
if (ow_ds_wait()) {
serialRead(ow_serial);
}
}
int ow_ds_rbit() {
if (!ow_ds_cmd) {
serialWriteByte(ow_serial,DS_CMD_MODE);
ow_ds_cmd = 1;
}
serialWriteByte(ow_serial,DS_WRITE1);
if (ow_ds_wait()) {
int resp = serialRead(ow_serial) & 0xFF;
return resp & 0x01;
}
return 1;
}
void ow_ds_write(int byte) {
if (ow_ds_cmd) {
serialWriteByte(ow_serial,DS_DATA_MODE);
ow_ds_cmd = 0;
}
serialWriteByte(ow_serial,byte);
// escape: if byte is 0xE1, 0xE3, or 0xF1, send it twice
if (byte == DS_DATA_MODE || byte == DS_CMD_MODE || byte == DS_PULSE_TERM) {
serialWriteByte(ow_serial,byte);
}
if (ow_ds_wait()) {
serialRead(ow_serial);
}
}
int ow_ds_read() {
if (ow_ds_cmd) {
serialWriteByte(ow_serial,DS_DATA_MODE);
ow_ds_cmd = 0;
}
serialWriteByte(ow_serial,0xFF);
if (ow_ds_wait()) {
return serialRead(ow_serial) & 0xFF;
}
return 0xFF;
}
// ========================================
// DS2484 I2C-to-1-Wire bridge
// Same chip family as DS2480B, but I2C instead of UART. No pins to
// claim — sits on whichever I2C bus the user picked. Status polling
// (1WB bit) gates each 1-Wire operation.
// ========================================
char ow_84_buf[2];
// Encode the WCFG argument: lower nibble = config bits, upper nibble = ~lower.
int ow_84_cfg_byte(int v) {
return (((~v) & 0x0F) << 4) | (v & 0x0F);
}
// Poll Status until 1WB (1-Wire Busy) clears. Caller is expected to have
// issued a 1-Wire command that auto-points to the Status register (Reset /
// Single Bit / Read Byte / Write Byte / Triplet all do). Returns the final
// Status byte, or 0xFF on I2C timeout.
int ow_84_wait() {
int i = 0;
while (i < 50) {
if (i2cRead0(DS84_ADDR, ow_84_buf, 1, ow_i2c_bus) > 0) {
int st = ow_84_buf[0] & 0xFF;
if (!(st & DS84_ST_1WB)) return st;
}
delay(1);
i = i + 1;
}
return 0xFF;
}
// Probe both I2C buses for a DS2484 at the fixed address 0x18. If found,
// sets ow_i2c_bus to whichever bus answered and initialises the chip.
// Returns 1 on success, 0 if no DS2484 is reachable on either bus.
int ow_84_init() {
int b = 0;
while (b < 2) {
if (i2cExists(DS84_ADDR, b)) {
ow_i2c_bus = b;
if (!i2cWrite0(DS84_ADDR, DS84_DRST, b)) return 0;
delay(1);
// Write config: APU=1 (active pullup → cleaner edges, faster bus)
i2cWrite8(DS84_ADDR, DS84_WCFG, ow_84_cfg_byte(0x01), b);
return 1;
}
b = b + 1;
}
return 0;
}
int ow_84_reset() {
if (!i2cWrite0(DS84_ADDR, DS84_1WRS, ow_i2c_bus)) return 0;
int st = ow_84_wait();
if (st == 0xFF) return 0;
return (st & DS84_ST_PPD) ? 1 : 0;
}
void ow_84_write(int byte) {
i2cWrite8(DS84_ADDR, DS84_1WWB, byte & 0xFF, ow_i2c_bus);
ow_84_wait();
}
int ow_84_read() {
i2cWrite0(DS84_ADDR, DS84_1WRB, ow_i2c_bus);
ow_84_wait();
// Switch read pointer to Read-Data register, then read 1 byte
i2cWrite8(DS84_ADDR, DS84_SRP, DS84_RP_RDATA, ow_i2c_bus);
if (i2cRead0(DS84_ADDR, ow_84_buf, 1, ow_i2c_bus) > 0) {
return ow_84_buf[0] & 0xFF;
}
return 0xFF;
}
void ow_84_wbit(int b) {
// 1-Wire Single Bit: arg bit 7 carries the value (0x80=1, 0x00=0).
i2cWrite8(DS84_ADDR, DS84_1WSB, b ? 0x80 : 0x00, ow_i2c_bus);
ow_84_wait();
}
int ow_84_rbit() {
// To read a bit, write a 1 (releases the bus) and inspect SBR in Status.
i2cWrite8(DS84_ADDR, DS84_1WSB, 0x80, ow_i2c_bus);
int st = ow_84_wait();
return (st & DS84_ST_SBR) ? 1 : 0;
}
// ========================================
// Abstraction layer
// GPIO mode uses native owXxx() syscalls (timing in C)
// DS2480B mode uses serial functions
// DS2484 mode uses I2C functions
// ========================================
// Dispatch helpers — operate on the active backend (ow_mode).
int ow_reset() {
if (ow_mode == 0) return owReset();
if (ow_mode == 2) return ow_84_reset();
return ow_ds_reset();
}
void ow_write(int byte) {
if (ow_mode == 0) owWrite(byte);
else if (ow_mode == 2) ow_84_write(byte);
else ow_ds_write(byte);
}
int ow_read() {
if (ow_mode == 0) return owRead();
if (ow_mode == 2) return ow_84_read();
return ow_ds_read();
}
void ow_wbit(int b) {
if (ow_mode == 0) owWriteBit(b);
else if (ow_mode == 2) ow_84_wbit(b);
else ow_ds_wbit(b);
}
int ow_rbit() {
if (ow_mode == 0) return owReadBit();
if (ow_mode == 2) return ow_84_rbit();
return ow_ds_rbit();
}
// ========================================
// ROM Search — uses native OneWire library search
// ========================================
char ow_srom[8];
// Record one found ROM as a temp sensor or switch device.
void ow_record_found() {
int fam = ow_srom[0] & 0xFF;
int i = 0;
if ((fam == FAM_DS18B20 || fam == FAM_DS18S20 || fam == FAM_DS1822) && ow_cnt < OW_MAX_SENS) {
while (i < 8) { ow_rom[ow_cnt][i] = ow_srom[i]; i = i + 1; }
ow_valid[ow_cnt] = 0;
ow_cnt = ow_cnt + 1;
} else if ((fam == FAM_DS2413 || fam == FAM_DS2406 || fam == FAM_DS2408) && ow_sw_cnt < OW_MAX_SW) {
while (i < 8) { ow_sw_rom[ow_sw_cnt][i] = ow_srom[i]; i = i + 1; }
ow_sw_type[ow_sw_cnt] = fam;
ow_sw_state[ow_sw_cnt] = 0xFF;
ow_sw_input[ow_sw_cnt] = 0;
ow_sw_valid[ow_sw_cnt] = 0;
if (fam == FAM_DS2408) ow_sw_ch[ow_sw_cnt] = 8;
else ow_sw_ch[ow_sw_cnt] = 2;
ow_sw_cnt = ow_sw_cnt + 1;
}
}
// GPIO backend search — uses the native owSearch() library.
void ow_scan_gpio() {
owSetPin(ow_pin);
owSearchReset();
int total = 0;
while (total < (OW_MAX_SENS + OW_MAX_SW)) {
if (!owSearch(ow_srom)) break;
addLog("OW: found fam=0x%02X (gpio)", ow_srom[0] & 0xFF);
ow_record_found();
total = total + 1;
}
}
// Bit-bang ROM search via the dispatch layer (works for both DS2480B and
// DS2484). Could be sped up on DS2484 via 1-Wire Triplet (0x78) — keeping
// the unified code path for simplicity.
void ow_scan_bitbang() {
int ow_last_disc = 0;
int ow_last_done = 0;
int i = 0;
while (i < 8) { ow_srom[i] = 0; i = i + 1; }
int total = 0;
while (total < (OW_MAX_SENS + OW_MAX_SW)) {
if (ow_last_done) break;
if (!ow_reset()) break;
ow_write(OW_SEARCH_ROM);
int last_zero = 0;
int pos = 1;
int ok = 1;
while (pos <= 64) {
int id_bit = ow_rbit();
int cmp_bit = ow_rbit();
if (id_bit == 1 && cmp_bit == 1) { ok = 0; break; }
int direction = 0;
if (id_bit == 0 && cmp_bit == 0) {
if (pos == ow_last_disc) {
direction = 1;
} else if (pos > ow_last_disc) {
direction = 0;
} else {
int byteN = (pos - 1) / 8;
int bitN = (pos - 1) % 8;
direction = (ow_srom[byteN] >> bitN) & 1;
}
if (direction == 0) last_zero = pos;
} else {
direction = id_bit;
}
int byteN = (pos - 1) / 8;
int bitN = (pos - 1) % 8;
if (direction) ow_srom[byteN] = ow_srom[byteN] | (1 << bitN);
else ow_srom[byteN] = ow_srom[byteN] & ~(1 << bitN);
ow_wbit(direction);
pos = pos + 1;
}
if (!ok) break;
ow_last_disc = last_zero;
if (ow_last_disc == 0) ow_last_done = 1;
// verify CRC
i = 0;
while (i < 8) { ow_buf[i] = ow_srom[i]; i = i + 1; }
if (ow_crc8(8) != 0) { total = total + 1; continue; }
char dd[48];
if (ow_mode == 1) sprintf(dd, "OW: found fam=0x%02X (ds2480b)", ow_srom[0] & 0xFF);
else sprintf(dd, "OW: found fam=0x%02X (ds2484)", ow_srom[0] & 0xFF);
addLog(dd);
ow_record_found();
total = total + 1;
}
}
void ow_scan() {
ow_cnt = 0;
ow_sw_cnt = 0;
if (ow_mode == 0) ow_scan_gpio();
else ow_scan_bitbang();
}
// ========================================
// Device selection helpers
// ========================================
void ow_select(int idx) {
ow_write(OW_MATCH_ROM);
int i = 0;
while (i < 8) {
ow_write(ow_rom[idx][i] & 0xFF);
i = i + 1;
}
}
void ow_sw_select(int idx) {
ow_write(OW_MATCH_ROM);
int i = 0;
while (i < 8) {
ow_write(ow_sw_rom[idx][i] & 0xFF);
i = i + 1;
}
}
// ========================================
// DS18B20 Temperature Reading
// ========================================
void ow_convert_all() {
// Use MATCH_ROM per sensor (not SKIP_ROM) to avoid
// sending CONVERT_T to DS2406 switches on the same bus
int i = 0;
while (i < ow_cnt) {
if (ow_reset()) {
ow_select(i);
ow_write(DS_CONVERT);
}
i = i + 1;
}
}
void ow_read_temp(int idx) {
if (!ow_reset()) return;
ow_select(idx);
ow_write(DS_READ_SCRATCH);
int i = 0;
while (i < 9) {
ow_buf[i] = ow_read();
i = i + 1;
}
if (ow_crc8(9) != 0) return;
int fam = ow_rom[idx][0] & 0xFF;
int lsb = ow_buf[0] & 0xFF;
int msb = ow_buf[1] & 0xFF;
int raw = (msb << 8) | lsb;
if (raw & 0x8000) {
raw = raw | 0xFFFF0000;
}
if (fam == FAM_DS18S20) {
ow_temp[idx] = raw / 2.0;
} else {
ow_temp[idx] = raw / 16.0;
}
ow_valid[idx] = 1;
}
// ========================================
// DS2413 — 2-channel switch
// ========================================
// Write output state to DS2413
// state bits: bit0=PIO-A, bit1=PIO-B (0=conducting/ON, 1=off)
int ow_ds2413_write(int idx) {
if (!ow_reset()) return 0;
ow_sw_select(idx);
ow_write(DS2413_ACCESS_WRITE);
int st = ow_sw_state[idx] & 0x03;
ow_write(st);
ow_write((~st) & 0xFF); // complement for verification
int confirm = ow_read();
if ((confirm & 0xFF) != 0xAA) return 0; // write failed
// read back status
int status = ow_read();
ow_sw_input[idx] = status & 0xFF;
ow_sw_valid[idx] = 1;
return 1;
}
// Read status from DS2413
// Returns status byte: bit0=PIO-A_latch, bit1=PIO-A_pin,
// bit2=PIO-B_latch, bit3=PIO-B_pin
// Upper nibble = complement of lower (for verification)
int ow_ds2413_read(int idx) {
if (!ow_reset()) return 0;
ow_sw_select(idx);
ow_write(DS2413_ACCESS_READ);
int status = ow_read();
int lo = status & 0x0F;
int hi = (status >> 4) & 0x0F;
// verify: upper nibble should be complement of lower
if ((lo ^ hi) != 0x0F) return 0;
ow_sw_input[idx] = status & 0xFF;
ow_sw_valid[idx] = 1;
return 1;
}
// ========================================
// DS2406 — 2-channel switch
// ========================================
// Write output state to DS2406
// ow_sw_state uses DS2413 convention: 0=conducting/ON, 1=off
// DS2406 hardware: 1=conducting, 0=off → invert before writing
// CCB1 layout: [ALR(7) IM(6) TOG(5) IC(4) CHS1(3) CHS0(2) CRC1(1) CRC0(0)]
int ow_ds2406_write(int idx) {
if (!ow_reset()) return 0;
ow_sw_select(idx);
ow_write(DS2406_CH_ACCESS);
// CCB1: IM=0(write), CHS based on channel count, no CRC
if (ow_sw_ch[idx] >= 2) {
ow_write(0x0C); // CHS=11 both channels
} else {
ow_write(0x04); // CHS=01 channel A only
}
ow_write(0xFF); // CCB2
// read Channel Info byte (always sent by device)
int info = ow_read();
// Invert: ow_sw_state has 0=ON (DS2413 convention)
// DS2406 needs 1=conducting, so invert bits
int st = (~ow_sw_state[idx]) & 0x03;
// DS2406 Channel Access writes bits sequentially to PIO flip-flops.
// Only the LAST bit for each channel determines the final state.
// CHS=01: all 8 bits → PIO-A, bit7 is last
// CHS=11: bits alternate A,B,A,B... bit6→A(last), bit7→B(last)
int data = 0;
if (ow_sw_ch[idx] >= 2) {
// CHS=11: 0x55 pattern sets A bits, 0xAA sets B bits
if (st & 0x01) data = data | 0x55; // PIO-A conducting
if (st & 0x02) data = data | 0xAA; // PIO-B conducting
} else {
// CHS=01: 0xFF for A=ON, 0x00 for A=OFF
if (st & 0x01) data = 0xFF;
}
ow_write(data);
// End channel access
ow_reset();
// Set expected input state immediately (DS2413-compatible format)
// This prevents the readback from overriding the button on the same tick
int mapped = 0x05; // default: both OFF
if (st & 0x01) mapped = mapped & 0xFE; // PIO-A on → clear bit0
if (st & 0x02) mapped = mapped & 0xFB; // PIO-B on → clear bit2
ow_sw_input[idx] = mapped;
ow_sw_valid[idx] = 1;
return 1;
}
// Read status from DS2406 via Channel Access
// Returns data in DS2413-compatible format:
// bit0=PIO-A (0=conducting/ON), bit2=PIO-B (0=conducting/ON)
int ow_ds2406_read(int idx) {
if (!ow_reset()) return 0;
ow_sw_select(idx);
ow_write(DS2406_CH_ACCESS);
// CCB1: IM=1(read), CHS based on channel count, no CRC
if (ow_sw_ch[idx] >= 2) {
ow_write(0x4C); // CHS=11 both channels
} else {
ow_write(0x44); // CHS=01 channel A only
}
ow_write(0xFF); // CCB2
// Channel Info byte:
// bit0=PIO-A flip-flop (1=conducting)
// bit1=PIO-B flip-flop (1=conducting)
// bit6=has PIO-B (1=2ch, 0=1ch 3-pin device)
// bit7=supply (1=VDD, 0=parasitic)
int info = ow_read();
// Detect single-channel DS2406P (bit 6 = 0 → no PIO-B)
if (info & 0x40) {
ow_sw_ch[idx] = 2;
} else {
ow_sw_ch[idx] = 1;
}
// Convert to DS2413-compatible format:
// DS2413: bit0=PIO-A(0=conducting), bit2=PIO-B(0=conducting)
int mapped = 0x05; // default: both OFF
if (info & 0x01) mapped = mapped & 0xFE; // PIO-A conducting → clear bit0
if (info & 0x02) mapped = mapped & 0xFB; // PIO-B conducting → clear bit2
ow_sw_input[idx] = mapped;
ow_sw_valid[idx] = 1;
ow_reset();
return 1;
}
// ========================================
// DS2408 — 8-channel GPIO
// ========================================
// Write output state to DS2408 (all 8 channels)
int ow_ds2408_write(int idx) {
if (!ow_reset()) return 0;
ow_sw_select(idx);
ow_write(DS2408_CHANNEL_WRITE);
int st = ow_sw_state[idx] & 0xFF;
ow_write(st);
ow_write((~st) & 0xFF); // complement for verification
int confirm = ow_read();
if ((confirm & 0xFF) != 0xAA) return 0;
// read back PIO state
int status = ow_read();
ow_sw_input[idx] = status & 0xFF;
ow_sw_valid[idx] = 1;
return 1;
}
// Read PIO input register from DS2408
int ow_ds2408_read(int idx) {
if (!ow_reset()) return 0;
ow_sw_select(idx);
ow_write(DS2408_READ_PIO);
// read PIO Logic State register at address 0x0088
ow_write(0x88); // address LSB
ow_write(0x00); // address MSB
int status = ow_read();
ow_sw_input[idx] = status & 0xFF;
ow_sw_valid[idx] = 1;
return 1;
}
// Is this a 2-channel switch? (DS2413 or DS2406 with 2 channels)
int ow_is_2ch(int idx) {
if (ow_sw_type[idx] == FAM_DS2413) return 1;
if (ow_sw_type[idx] == FAM_DS2406 && ow_sw_ch[idx] >= 2) return 1;
return 0;
}
// Is this a 1-channel switch? (DS2406P — 3-pin package)
int ow_is_1ch(int idx) {
return (ow_sw_type[idx] == FAM_DS2406 && ow_sw_ch[idx] == 1);
}
// ========================================
// Switch write helper — writes current state
// ========================================
void ow_sw_write(int idx) {
if (ow_sw_type[idx] == FAM_DS2413) {
ow_ds2413_write(idx);
} else if (ow_sw_type[idx] == FAM_DS2406) {
ow_ds2406_write(idx);
} else {
ow_ds2408_write(idx);
}
}
void ow_sw_read(int idx) {
if (ow_sw_type[idx] == FAM_DS2413) {
ow_ds2413_read(idx);
} else if (ow_sw_type[idx] == FAM_DS2406) {
ow_ds2406_read(idx);
} else {
ow_ds2408_read(idx);
}
}
// ========================================
// Sync WebUI button vars → sw_state[] and vice versa
// ========================================
// Copy WebUI button/number vars to sw_state array
// All 2-ch devices use DS2413 convention: 0=conducting/ON, 1=off
// DS2406 write function handles the inversion internally
void ow_ui_to_state() {
if (ow_sw_cnt > 0) {
if (ow_is_2ch(0) || ow_is_1ch(0)) {
int st = 0x03; // default: both off
if (ow_sw0a) st = st & 0xFE; // clear bit 0 = PIO-A on
if (ow_is_2ch(0) && ow_sw0b) st = st & 0xFD; // clear bit 1 = PIO-B on
ow_sw_state[0] = st;
} else {
ow_sw_state[0] = ow_port0 & 0xFF;
}
}
if (ow_sw_cnt > 1) {
if (ow_is_2ch(1) || ow_is_1ch(1)) {
int st = 0x03;
if (ow_sw1a) st = st & 0xFE;
if (ow_is_2ch(1) && ow_sw1b) st = st & 0xFD;
ow_sw_state[1] = st;
} else {
ow_sw_state[1] = ow_port1 & 0xFF;
}
}
}
// Copy sw_input readback to WebUI variables
// All 2-ch devices now use DS2413-compatible format:
// bit0=PIO-A (0=conducting=ON), bit2=PIO-B (0=conducting=ON)
void ow_state_to_ui() {
if (ow_sw_cnt > 0 && ow_sw_valid[0]) {
if (ow_is_2ch(0) || ow_is_1ch(0)) {
if ((ow_sw_input[0] & 0x01) == 0) { ow_sw0a = 1; } else { ow_sw0a = 0; }
if (ow_is_2ch(0)) {
if ((ow_sw_input[0] & 0x04) == 0) { ow_sw0b = 1; } else { ow_sw0b = 0; }
}
} else {
ow_port0 = ow_sw_input[0] & 0xFF;
}
}
if (ow_sw_cnt > 1 && ow_sw_valid[1]) {
if (ow_is_2ch(1) || ow_is_1ch(1)) {
if ((ow_sw_input[1] & 0x01) == 0) { ow_sw1a = 1; } else { ow_sw1a = 0; }
if (ow_is_2ch(1)) {
if ((ow_sw_input[1] & 0x04) == 0) { ow_sw1b = 1; } else { ow_sw1b = 0; }
}
} else {
ow_port1 = ow_sw_input[1] & 0xFF;
}
}
}
// Check if any WebUI var changed and write to device
void ow_check_ui_changes() {
if (ow_sw_cnt > 0) {
if (ow_is_2ch(0) || ow_is_1ch(0)) {
if (ow_sw0a != ow_sw0a_prev || ow_sw0b != ow_sw0b_prev) {
ow_ui_to_state();
ow_sw_write(0);
ow_sw0a_prev = ow_sw0a;
ow_sw0b_prev = ow_sw0b;
}
} else {
if (ow_port0 != ow_port0_prev) {
ow_ui_to_state();
ow_sw_write(0);
ow_port0_prev = ow_port0;
}
}
}
if (ow_sw_cnt > 1) {
if (ow_is_2ch(1) || ow_is_1ch(1)) {
if (ow_sw1a != ow_sw1a_prev || ow_sw1b != ow_sw1b_prev) {
ow_ui_to_state();
ow_sw_write(1);
ow_sw1a_prev = ow_sw1a;
ow_sw1b_prev = ow_sw1b;
}
} else {
if (ow_port1 != ow_port1_prev) {
ow_ui_to_state();
ow_sw_write(1);
ow_port1_prev = ow_port1;
}
}
}
}
// ========================================
// Re-initialize 1-Wire bus with current config
// ========================================
void ow_reinit() {
// close existing serial if open (previous DS2480B attempt)
if (ow_serial >= 0) {
serialClose(ow_serial);
ow_serial = -1;
}
ow_ok = 0;
ow_cnt = 0;
ow_sw_cnt = 0;
ow_mode = -1; // sentinel until cascade picks a backend
// Cascade priority: DS2484 (I2C) → DS2480B (serial) → GPIO bit-bang.
// The first backend that initialises wins; the others stay silent.
if (ow_84_init()) {
ow_mode = 2;
addLog("1-Wire: backend = DS2484 (I2C)");
} else if (ow_ds_init()) {
ow_mode = 1;
addLog("1-Wire: backend = DS2480B (serial)");
} else {
if (!pinFree(ow_pin)) {
char m[64];
sprintf(m, "1-Wire: GPIO pin %d claimed — pick another in WebUI", ow_pin);
addLog(m);
return;
}
ow_mode = 0;
owSetPin(ow_pin);
char m[64];
sprintf(m, "1-Wire: backend = GPIO bit-bang on pin %d (fallback)", ow_pin);
addLog(m);
}
if (!ow_reset()) {
addLog("1-Wire: backend up but no devices on bus");
return;
}
ow_scan();
if (ow_cnt == 0 && ow_sw_cnt == 0) {
addLog("1-Wire: no devices found");
return;
}
addLog("1-Wire: %d temp, %d switch", ow_cnt, ow_sw_cnt);
// initial switch state read
if (ow_sw_cnt > 0) {
int i = 0;
while (i < ow_sw_cnt) {
ow_sw_read(i);
i = i + 1;
}
ow_state_to_ui();
ow_sw0a_prev = ow_sw0a;
ow_sw0b_prev = ow_sw0b;
ow_sw1a_prev = ow_sw1a;
ow_sw1b_prev = ow_sw1b;
ow_port0_prev = ow_port0;
ow_port1_prev = ow_port1;
}
ow_ok = 1;
}
// ========================================
// Callbacks
// ========================================
void EverySecond() {
// detect WebUI dropdown writes via watch — fires on any pulldown change
if (changed(ow_pin) || changed(ow_rxpin) || changed(ow_txpin)) {
snapshot(ow_pin);
snapshot(ow_rxpin);
snapshot(ow_txpin);
ow_reinit();
return;
}
if (!ow_ok) return;
ow_tick = ow_tick + 1;
// Temperature cycle (if any temp sensors)
if (ow_cnt > 0) {
if ((ow_tick & 1) == 1) {
ow_convert_all();
} else {
int i = 0;
while (i < ow_cnt) {
ow_read_temp(i);
i = i + 1;
}
}
}
// Switch devices: check UI changes and read back state
if (ow_sw_cnt > 0) {
ow_check_ui_changes();
// poll input state every 2 seconds
if ((ow_tick & 1) == 0) {
int i = 0;
while (i < ow_sw_cnt) {
ow_sw_read(i);
i = i + 1;
}
ow_state_to_ui();
}
}
}
// ---- Alias helpers (ROM-based) ----
void ow_copy_trom(int idx) {
int i = 0;
while (i < 8) { ow_arom[i] = ow_rom[idx][i]; i = i + 1; }
}
void ow_copy_srom(int idx) {
int i = 0;
while (i < 8) { ow_arom[i] = ow_sw_rom[idx][i]; i = i + 1; }
}
// Find alias for ROM in ow_arom. Copies name to dst, returns 1 if found.
int ow_get_alias(char dst[]) {
int e = 0;
while (e < OW_MAX_ALIAS) {
int aoff = e * OW_ALEN;
if (ow_alias[aoff] != 0) {
int match = 1;
int b = 0;
while (b < 8) {
if ((ow_alias[aoff + b] & 0xFF) != (ow_arom[b] & 0xFF)) {
match = 0;
break;
}
b = b + 1;
}
if (match) {
// copy name byte-by-byte to global buffer
int c = 0;
while (c < OW_NLEN) {
ow_aname[c] = ow_alias[aoff + 8 + c];
c = c + 1;
}
strcpy(dst, ow_aname);
return 1;
}
}
e = e + 1;
}
return 0;
}
// Store alias for ROM in ow_arom
void ow_set_alias(char name[], int nlen) {
char nm[16];
strcpy(nm, name);
int found = -1;
int empty = -1;
int e = 0;
while (e < OW_MAX_ALIAS) {
int aoff = e * OW_ALEN;
if (ow_alias[aoff] == 0) {
if (empty < 0) empty = e;
} else {
int match = 1;
int b = 0;
while (b < 8) {
if ((ow_alias[aoff + b] & 0xFF) != (ow_arom[b] & 0xFF)) {
match = 0;
break;
}
b = b + 1;
}
if (match) { found = e; break; }
}
e = e + 1;
}
int slot = found;
if (slot < 0) slot = empty;
if (slot < 0) return;
int aoff = slot * OW_ALEN;
int b = 0;
while (b < 8) { ow_alias[aoff + b] = ow_arom[b]; b = b + 1; }
b = 0;
while (b < OW_NLEN) {
if (b < nlen) { ow_alias[aoff + 8 + b] = nm[b]; }
else { ow_alias[aoff + 8 + b] = 0; }
b = b + 1;
}
}
// Clear alias for ROM in ow_arom. Returns 1 if found and cleared.
int ow_clear_alias() {
int e = 0;
while (e < OW_MAX_ALIAS) {
int aoff = e * OW_ALEN;
if (ow_alias[aoff] != 0) {
int match = 1;
int b = 0;
while (b < 8) {
if ((ow_alias[aoff + b] & 0xFF) != (ow_arom[b] & 0xFF)) {
match = 0;
break;
}
b = b + 1;
}
if (match) {
b = 0;
while (b < OW_ALEN) { ow_alias[aoff + b] = 0; b = b + 1; }
return 1;
}
}
e = e + 1;
}
return 0;
}
void ow_temp_label(char dst[], int idx) {
ow_copy_trom(idx);
if (ow_get_alias(dst)) return;
strcpy(dst, "DS18x20-");
char hx[4];
sprintf(hx, "%02X", ow_rom[idx][6] & 0xFF);
strcat(dst, hx);
}
void ow_sw_label(char dst[], int idx) {
ow_copy_srom(idx);
if (ow_get_alias(dst)) return;
if (ow_sw_type[idx] == FAM_DS2413) {
sprintf(dst, "DS2413-%d", idx);
} else if (ow_sw_type[idx] == FAM_DS2408) {
sprintf(dst, "DS2408-%d", idx);
} else {
sprintf(dst, "DS2406-%d", idx);
}
}
// ---- WebCall: show sensor values on main page ----
void ow_web_sensor(int idx) {
char vt[64];
if (!ow_valid[idx]) return;
char name[16];
ow_temp_label(name, idx);
LGetString(0, ow_lbl); // 0 = Temperature
sprintf(vt, "{s}%s %s{m}", name, ow_lbl);
webSend(vt);
sprintf(vt, "%.1f °C{e}", ow_temp[idx]);
webSend(vt);
}
void ow_web_switch(int idx) {
char vt[64];
if (!ow_sw_valid[idx]) return;
char name[16];
ow_sw_label(name, idx);
if (ow_is_2ch(idx) || ow_is_1ch(idx)) {
int a_on = 0;
int b_on = 0;
if ((ow_sw_input[idx] & 0x01) == 0) a_on = 1;
if ((ow_sw_input[idx] & 0x04) == 0) b_on = 1;
sprintf(vt, "{s}%s A{m}", name);
webSend(vt);
if (a_on) {
webSend("ON{e}");
} else {
webSend("OFF{e}");
}
if (ow_is_2ch(idx)) {
sprintf(vt, "{s}%s B{m}", name);
webSend(vt);
if (b_on) {
webSend("ON{e}");
} else {
webSend("OFF{e}");
}
}
} else {
// DS2408: show hex port state
sprintf(vt, "{s}%s{m}", name);
webSend(vt);
sprintf(vt, "0x%02X{e}", ow_sw_input[idx] & 0xFF);
webSend(vt);
}
}
void WebCall() {
if (!ow_ok) {
webSend("{s}1-Wire{m}no devices{e}");
return;
}
int i = 0;
while (i < ow_cnt) {
ow_web_sensor(i);
i = i + 1;
}
i = 0;
while (i < ow_sw_cnt) {
ow_web_switch(i);
i = i + 1;
}
}
// ---- JsonCall: MQTT sensor output ----
void JsonCall() {
if (!ow_ok) return;
char buf[64];
// temperature sensors
int i = 0;
char name[16];
while (i < ow_cnt) {
if (ow_valid[i]) {
ow_temp_label(name, i);
sprintf(buf, ",\"%s\":{", name);
responseAppend(buf);
sprintf(buf, "\"Temperature\":%.1f}", ow_temp[i]);
responseAppend(buf);
}
i = i + 1;
}
// switch devices
i = 0;
while (i < ow_sw_cnt) {
if (ow_sw_valid[i]) {
ow_sw_label(name, i);
sprintf(buf, ",\"%s\":{", name);
responseAppend(buf);
if (ow_is_2ch(i) || ow_is_1ch(i)) {
int a_on = 0;
int b_on = 0;
if ((ow_sw_input[i] & 0x01) == 0) a_on = 1;
if ((ow_sw_input[i] & 0x04) == 0) b_on = 1;
if (ow_is_1ch(i)) {
sprintf(buf, "\"PIO_A\":%d}", a_on);
responseAppend(buf);
} else {
sprintf(buf, "\"PIO_A\":%d,", a_on);
responseAppend(buf);
sprintf(buf, "\"PIO_B\":%d}", b_on);
responseAppend(buf);
}
} else {
sprintf(buf, "\"State\":%d,", ow_sw_state[i] & 0xFF);
responseAppend(buf);
sprintf(buf, "\"Input\":%d}", ow_sw_input[i] & 0xFF);
responseAppend(buf);
}
}
i = i + 1;
}
}
// ---- WebUI: configuration page ----
void WebUI() {
// Backend is auto-detected (DS2484 → DS2480B → GPIO). Show what won
// and expose the pin/bus config for all three so the user can move
// the wiring at will and re-scan picks it up.
char status[80];
if (ow_mode == 2) sprintf(status, "<div><b>Backend:</b> DS2484 (I2C bus %d)</div>", ow_i2c_bus);
else if (ow_mode == 1) sprintf(status, "<div><b>Backend:</b> DS2480B (serial rx=%d tx=%d)</div>", ow_rxpin, ow_txpin);
else if (ow_mode == 0) sprintf(status, "<div><b>Backend:</b> GPIO bit-bang (pin %d)</div>", ow_pin);
else strcpy (status, "<div><b>Backend:</b> none — no bus available</div>");
webSend(status);
webPulldown(ow_pin, "GPIO Pin", "@getfreepins");
webPulldown(ow_rxpin, "DS2480B RX", "@getfreepins");
webPulldown(ow_txpin, "DS2480B TX", "@getfreepins");
// DS2484 I2C bus is auto-probed (both 0 and 1) on each reinit, so no
// pulldown needed — the status line above shows where it was found.
// switch controls
if (ow_sw_cnt > 0) {
if (ow_is_2ch(0)) {
webButton(ow_sw0a, "SW0 PIO-A");
webButton(ow_sw0b, "SW0 PIO-B");
} else if (ow_is_1ch(0)) {
webButton(ow_sw0a, "SW0 PIO-A");
} else {
webNumber(ow_port0, 0, 255, "SW0 Port");
}
}
if (ow_sw_cnt > 1) {
if (ow_is_2ch(1)) {
webButton(ow_sw1a, "SW1 PIO-A");
webButton(ow_sw1b, "SW1 PIO-B");
} else if (ow_is_1ch(1)) {
webButton(ow_sw1a, "SW1 PIO-A");
} else {
webNumber(ow_port1, 0, 255, "SW1 Port");
}
}
}
// ========================================
// Command handler: OW <dev> [channel] <state>
// OW → show status
// OW 0 1 → 1-ch/2-ch: set PIO-A on
// OW 0 A 1 → set device 0 PIO-A on
// OW 0 B 0 → set device 0 PIO-B off
// OW 0 255 → DS2408: set port byte
// OW NAME T 0 Kitchen → set temp sensor 0 alias
// OW NAME S 1 Relay → set switch 1 alias
// OW NAME T 0 → clear temp sensor 0 alias
// ========================================
// Handle NAME subcommand — set/clear alias for a device
void ow_cmd_name(char cmd[], int pos, int len) {
char cs[64];
strcpy(cs, cmd);
while (pos < len && cs[pos] == ' ') pos = pos + 1;
if (pos >= len) {
responseCmnd("OW NAME T/S idx [name]");
return;
}
int is_temp = 0;
if (cs[pos] == 'T') {
is_temp = 1;
} else if (cs[pos] != 'S') {
responseCmnd("Use T or S");
return;
}
pos = pos + 1;
while (pos < len && cs[pos] == ' ') pos = pos + 1;
if (pos >= len) {
responseCmnd("Missing index");
return;
}
char arg[4];
strSub(arg, cs, pos, 1);
int idx = atoi(arg);
pos = pos + 1;
while (pos < len && cs[pos] == ' ') pos = pos + 1;
if (is_temp) {
if (idx < 0 || idx >= ow_cnt) { responseCmnd("Bad index"); return; }
ow_copy_trom(idx);
} else {
if (idx < 0 || idx >= ow_sw_cnt) { responseCmnd("Bad index"); return; }
ow_copy_srom(idx);
}
if (pos >= len) {
// no name → clear alias
if (ow_clear_alias()) {
responseCmnd("Alias cleared");
} else {
responseCmnd("No alias set");
}
return;
}
// extract name (max OW_NLEN-1 chars)
char name[16];
int nlen = len - pos;
if (nlen >= OW_NLEN) nlen = OW_NLEN - 1;
strSub(name, cs, pos, nlen);
name[nlen] = 0;
ow_set_alias(name, nlen);
char resp[32];
strcpy(resp, "Alias: ");
strcat(resp, name);
responseCmnd(resp);
}
void ow_cmd_status(char resp[]) {
int i = 0;
strcpy(resp, "{");
char tmp[32];
char name[16];
while (i < ow_sw_cnt) {
if (i > 0) strcat(resp, ",");
ow_sw_label(name, i);
sprintf(tmp, "\"%s\":{", name);
strcat(resp, tmp);
if (ow_is_2ch(i) || ow_is_1ch(i)) {
int a_on = 0;
int b_on = 0;
if ((ow_sw_input[i] & 0x01) == 0) a_on = 1;
if ((ow_sw_input[i] & 0x04) == 0) b_on = 1;
sprintf(tmp, "\"A\":%d", a_on);
strcat(resp, tmp);
if (ow_is_2ch(i)) {
sprintf(tmp, ",\"B\":%d", b_on);
strcat(resp, tmp);
}
strcat(resp, "}");
} else {
sprintf(tmp, "\"State\":%d}", ow_sw_input[i] & 0xFF);
strcat(resp, tmp);
}
i = i + 1;
}
strcat(resp, "}");
}
void Command(char cmd[]) {
char resp[192];
char arg[16];
char cs[64];
strcpy(cs, cmd);
int len = strlen(cs);
// skip leading space
int pos = 0;
while (pos < len && cs[pos] == ' ') pos = pos + 1;
// no args → show status
if (pos >= len) {
ow_cmd_status(resp);
responseCmnd(resp);
return;
}
// check for NAME subcommand
if (len - pos >= 4 && cs[pos] == 'N' && cs[pos+1] == 'A' && cs[pos+2] == 'M' && cs[pos+3] == 'E') {
ow_cmd_name(cs, pos + 4, len);
return;
}
// check for SCAN subcommand
if (len - pos >= 4 && cs[pos] == 'S' && cs[pos+1] == 'C' && cs[pos+2] == 'A' && cs[pos+3] == 'N') {
ow_reinit();
char dt[48];
char dt2[16];
sprintf(dt, "%d temp, ", ow_cnt);
sprintf(dt2, "%d switch", ow_sw_cnt);
strcat(dt, dt2);
responseCmnd(dt);
return;
}
// parse device index
strSub(arg, cs, pos, 1);
int dev = atoi(arg);
pos = pos + 1;
if (dev < 0 || dev >= ow_sw_cnt) {
responseCmnd("Invalid device");
return;
}
// skip space
while (pos < len && cs[pos] == ' ') pos = pos + 1;
if (pos >= len) {
// just "OW 0" → show single device status
ow_cmd_status(resp);
responseCmnd(resp);
return;
}
// check for A/B channel specifier
int ch = cs[pos];
int val = 0;
if (ch == 'A' || ch == 'a' || ch == 'B' || ch == 'b') {
// channel letter
pos = pos + 1;
while (pos < len && cs[pos] == ' ') pos = pos + 1;
if (pos < len) {
strSub(arg, cs, pos, 0);
val = atoi(arg);
}
if (ch == 'A' || ch == 'a') {
// set PIO-A
if (ow_is_2ch(dev) || ow_is_1ch(dev)) {
if (dev == 0) { ow_sw0a = val; }
if (dev == 1) { ow_sw1a = val; }
}
} else {
// set PIO-B
if (ow_is_2ch(dev)) {
if (dev == 0) { ow_sw0b = val; }
if (dev == 1) { ow_sw1b = val; }
} else {
responseCmnd("No PIO-B on this device");
return;
}
}
} else {
// numeric value
strSub(arg, cs, pos, 0);
val = atoi(arg);
if (ow_sw_type[dev] == FAM_DS2408) {
// DS2408: set full port byte
if (dev == 0) ow_port0 = val;
if (dev == 1) ow_port1 = val;
} else {
// 1-ch or 2-ch: treat as PIO-A
if (dev == 0) { ow_sw0a = val; }
if (dev == 1) { ow_sw1a = val; }
}
}
// trigger write
ow_ui_to_state();
ow_sw_write(dev);
// sync prev to avoid double-write from ow_check_ui_changes
ow_sw0a_prev = ow_sw0a;
ow_sw0b_prev = ow_sw0b;
ow_sw1a_prev = ow_sw1a;
ow_sw1b_prev = ow_sw1b;
ow_port0_prev = ow_port0;
ow_port1_prev = ow_port1;
// respond with current state
ow_cmd_status(resp);
responseCmnd(resp);
}
void OnExit() {
if (ow_serial >= 0) {
serialClose(ow_serial);
}
}
int main() {
// first-run defaults (persist vars start at 0)
if (ow_pin == 0 && ow_rxpin == 0 && ow_txpin == 0) {
ow_pin = 4;
ow_rxpin = 6;
ow_txpin = 5;
}
// Since 1.3.36 the runtime defers autoexec main() until Tasmota's
// uptime ≥ 3 s, so serialBegin / I2C are race-free here — no manual
// boot delay needed.
//
// WebUI pulldowns live in WebUI() — calling them here writes via
// WSContentSend_P with no active web-request context and corrupts
// the heap. Persist handles storage; WebUI() renders on each hit.
ow_reinit();
addCommand("OW");
return 0;
}