onewire.tc¶
1-Wire Driver: Temperature Sensors + Output Switches
// 1-Wire Driver: Temperature Sensors + Output Switches
// Supports:
// DS18B20/DS18S20/DS1822 — temperature sensors
// DS2413 — 2-channel open-drain switch (family 0x3A)
// DS2408 — 8-channel GPIO (family 0x29)
// Modes:
// GPIO mode — native 1-Wire syscalls (timing in C, needs 4.7k pullup)
// DS2480B mode — via DS2480B serial-to-1-Wire bridge (9600 baud)
//
// WebUI lets user select mode and pin(s)
// Supports up to 16 temp sensors + 16 switch devices on one bus
// WebUI buttons for first 2 switch devices; rest via OW command
#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
// --- config ---
persist int ow_mode; // 0=GPIO, 1=DS2480B (persist across reboot)
persist int ow_pin; // GPIO mode pin
persist int ow_rxpin; // DS2480B serial RX
persist int ow_txpin; // DS2480B serial TX
int ow_serial = 0; // serial opened flag
int ow_ok = 0; // driver active
int ow_tick = 0; // even/odd second counter
int ow_ds_cmd = 1; // DS2480B in command mode flag
int ow_act_mode = -1; // active config (for change detection)
int ow_act_pin = -1;
int ow_act_rx = -1;
int ow_act_tx = -1;
// temperature sensor data
int ow_cnt = 0; // number of temp sensors
char ow_rom[128]; // 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[128]; // 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() > 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 != 1) return 0;
ow_serial = 1;
delay(100);
// send 0xC1 to init (same as library begin())
serialWriteByte(DS_RESET);
if (ow_ds_wait()) {
serialRead(); // discard init response
}
ow_ds_cmd = 1;
return 1;
}
int ow_ds_reset() {
if (!ow_ds_cmd) {
serialWriteByte(DS_CMD_MODE);
delay(1);
ow_ds_cmd = 1;
}
serialWriteByte(DS_RESET);
if (ow_ds_wait()) {
int resp = serialRead() & 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(DS_CMD_MODE);
ow_ds_cmd = 1;
}
if (b) {
serialWriteByte(DS_WRITE1);
} else {
serialWriteByte(DS_WRITE0);
}
if (ow_ds_wait()) {
serialRead();
}
}
int ow_ds_rbit() {
if (!ow_ds_cmd) {
serialWriteByte(DS_CMD_MODE);
ow_ds_cmd = 1;
}
serialWriteByte(DS_WRITE1);
if (ow_ds_wait()) {
int resp = serialRead() & 0xFF;
return resp & 0x01;
}
return 1;
}
void ow_ds_write(int byte) {
if (ow_ds_cmd) {
serialWriteByte(DS_DATA_MODE);
ow_ds_cmd = 0;
}
serialWriteByte(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(byte);
}
if (ow_ds_wait()) {
serialRead();
}
}
int ow_ds_read() {
if (ow_ds_cmd) {
serialWriteByte(DS_DATA_MODE);
ow_ds_cmd = 0;
}
serialWriteByte(0xFF);
if (ow_ds_wait()) {
return serialRead() & 0xFF;
}
return 0xFF;
}
// ========================================
// Abstraction layer
// GPIO mode uses native owXxx() syscalls (timing in C)
// DS2480B mode uses serial functions
// ========================================
int ow_reset() {
if (ow_mode == 0) return owReset();
return ow_ds_reset();
}
void ow_write(int byte) {
if (ow_mode == 0) owWrite(byte);
else ow_ds_write(byte);
}
int ow_read() {
if (ow_mode == 0) return owRead();
return ow_ds_read();
}
void ow_wbit(int b) {
if (ow_mode == 0) owWriteBit(b);
else ow_ds_wbit(b);
}
int ow_rbit() {
if (ow_mode == 0) return owReadBit();
return ow_ds_rbit();
}
// ========================================
// ROM Search — uses native OneWire library search
// ========================================
char ow_srom[8];
void ow_scan() {
ow_cnt = 0;
ow_sw_cnt = 0;
if (ow_mode == 0) {
// GPIO mode: use native owSearch() from OneWire library
owSearchReset();
int total = 0;
while (total < (OW_MAX_SENS + OW_MAX_SW)) {
if (!owSearch(ow_srom)) break;
int fam = ow_srom[0] & 0xFF;
char dd[48];
sprintf(dd, "OW: found fam=0x%02X", fam);
addLog(dd);
int i = 0;
if ((fam == FAM_DS18B20 || fam == FAM_DS18S20 || fam == FAM_DS1822) && ow_cnt < OW_MAX_SENS) {
int base = ow_cnt * 8;
while (i < 8) {
ow_rom[base + i] = ow_srom[i];
i = i + 1;
}
ow_valid[ow_cnt] = 0;
ow_cnt = ow_cnt + 1;
}
if ((fam == FAM_DS2413 || fam == FAM_DS2406 || fam == FAM_DS2408) && ow_sw_cnt < OW_MAX_SW) {
int base = ow_sw_cnt * 8;
while (i < 8) {
ow_sw_rom[base + i] = ow_srom[i];
i = i + 1;
}
ow_sw_type[ow_sw_cnt] = fam;
ow_sw_state[ow_sw_cnt] = 0xFF; // all off (open-drain: 1=off)
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; // DS2413=2, DS2406 default=2 (updated on first read)
}
ow_sw_cnt = ow_sw_cnt + 1;
}
total = total + 1;
}
} else {
// DS2480B mode: use TinyC bit-bang search via serial bridge
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)) {
// --- inline search for DS2480B mode ---
if (ow_last_done) break;
if (!ow_ds_reset()) break;
ow_ds_write(OW_SEARCH_ROM);
int last_zero = 0;
int pos = 1;
int ok = 1;
while (pos <= 64) {
int id_bit = ow_ds_rbit();
int cmp_bit = ow_ds_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_ds_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;
}
int fam = ow_srom[0] & 0xFF;
i = 0;
if ((fam == FAM_DS18B20 || fam == FAM_DS18S20 || fam == FAM_DS1822) && ow_cnt < OW_MAX_SENS) {
int base = ow_cnt * 8;
while (i < 8) {
ow_rom[base + i] = ow_srom[i];
i = i + 1;
}
ow_valid[ow_cnt] = 0;
ow_cnt = ow_cnt + 1;
}
if ((fam == FAM_DS2413 || fam == FAM_DS2406 || fam == FAM_DS2408) && ow_sw_cnt < OW_MAX_SW) {
int base = ow_sw_cnt * 8;
while (i < 8) {
ow_sw_rom[base + 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;
}
total = total + 1;
}
}
}
// ========================================
// Device selection helpers
// ========================================
void ow_select(int idx) {
ow_write(OW_MATCH_ROM);
int base = idx * 8;
int i = 0;
while (i < 8) {
ow_write(ow_rom[base + i] & 0xFF);
i = i + 1;
}
}
void ow_sw_select(int idx) {
ow_write(OW_MATCH_ROM);
int base = idx * 8;
int i = 0;
while (i < 8) {
ow_write(ow_sw_rom[base + 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 * 8] & 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
if (ow_serial) {
serialClose();
ow_serial = 0;
}
ow_ok = 0;
ow_cnt = 0;
ow_sw_cnt = 0;
// save active config (even if init fails, so we don't retry every second)
ow_act_mode = ow_mode;
ow_act_pin = ow_pin;
ow_act_rx = ow_rxpin;
ow_act_tx = ow_txpin;
if (ow_mode == 0) {
owSetPin(ow_pin);
if (!owReset()) {
addLog("1-Wire: no devices (GPIO mode)");
return;
}
} else {
if (!ow_ds_init()) {
addLog("1-Wire: DS2480B serial failed");
return;
}
if (!ow_ds_reset()) {
addLog("1-Wire: no devices (DS2480B mode)");
return;
}
}
ow_scan();
if (ow_cnt == 0 && ow_sw_cnt == 0) {
addLog("1-Wire: no devices found");
return;
}
char dt[48];
sprintf(dt, "1-Wire: %d temp, %d switch", ow_cnt, ow_sw_cnt);
addLog(dt);
// 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 config change from WebUI pulldowns
if (ow_mode != ow_act_mode || ow_pin != ow_act_pin ||
ow_rxpin != ow_act_rx || ow_txpin != ow_act_tx) {
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 base = idx * 8;
int i = 0;
while (i < 8) { ow_arom[i] = ow_rom[base + i]; i = i + 1; }
}
void ow_copy_srom(int idx) {
int base = idx * 8;
int i = 0;
while (i < 8) { ow_arom[i] = ow_sw_rom[base + 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;
int base = idx * 8;
strcpy(dst, "DS18x20-");
char hx[4];
sprintf(hx, "%02X", ow_rom[base + 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
strcpy(vt, "{s}");
strcat(vt, name);
strcat(vt, " ");
strcat(vt, ow_lbl);
strcat(vt, "{m}");
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;
strcpy(vt, "{s}");
strcat(vt, name);
strcat(vt, " A{m}");
webSend(vt);
if (a_on) {
webSend("ON{e}");
} else {
webSend("OFF{e}");
}
if (ow_is_2ch(idx)) {
strcpy(vt, "{s}");
strcat(vt, name);
strcat(vt, " B{m}");
webSend(vt);
if (b_on) {
webSend("ON{e}");
} else {
webSend("OFF{e}");
}
}
} else {
// DS2408: show hex port state
strcpy(vt, "{s}");
strcat(vt, name);
strcat(vt, "{m}");
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);
strcpy(buf, ",\"");
strcat(buf, name);
strcat(buf, "\":{");
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);
strcpy(buf, ",\"");
strcat(buf, name);
strcat(buf, "\":{");
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() {
webPulldown(ow_mode, "Mode", "GPIO|DS2480B");
if (ow_mode == 0) {
webPulldown(ow_pin, "1-Wire Pin", "@getfreepins");
} else {
webPulldown(ow_rxpin, "DS2480B RX", "@getfreepins");
webPulldown(ow_txpin, "DS2480B TX", "@getfreepins");
}
// 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);
strcpy(tmp, "\"");
strcat(tmp, name);
strcat(tmp, "\":{");
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) {
serialClose();
}
}
int main() {
// first-run defaults (persist vars start at 0)
if (ow_pin == 0 && ow_rxpin == 0 && ow_txpin == 0) {
ow_mode = 1; // DS2480B
ow_pin = 4;
ow_rxpin = 6;
ow_txpin = 5;
}
// DS2480B needs delay to avoid serial boot conflicts
if (ow_mode == 1) {
delay(5000);
}
// setup WebUI pulldowns (persist handles storage, webPulldown handles UI)
webPulldown(ow_mode, "Mode", "GPIO|DS2480B");
webPulldown(ow_pin, "1-Wire Pin", "@getfreepins");
webPulldown(ow_rxpin, "DS2480B RX", "@getfreepins");
webPulldown(ow_txpin, "DS2480B TX", "@getfreepins");
ow_reinit();
addCommand("OW");
return 0;
}