Skip to content

Commit

Permalink
Enhance pin dropdowns.
Browse files Browse the repository at this point in the history
- add pins for PCF8574 (POC)
- bugfix for saving
Reduced maximum relays to 8.
Changed MultiRelay config parameter name.
  • Loading branch information
blazoncek committed Jun 16, 2023
1 parent 7524485 commit 4ea5723
Show file tree
Hide file tree
Showing 4 changed files with 287 additions and 298 deletions.
100 changes: 46 additions & 54 deletions usermods/multi_relay/usermod_multi_relay.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
#ifndef MULTI_RELAY_MAX_RELAYS
#define MULTI_RELAY_MAX_RELAYS 4
#else
#if MULTI_RELAY_MAX_RELAYS>16
#if MULTI_RELAY_MAX_RELAYS>8
#undef MULTI_RELAY_MAX_RELAYS
#define MULTI_RELAY_MAX_RELAYS 16
#define MULTI_RELAY_MAX_RELAYS 8
#warning Maximum relays set to 8
#endif
#endif

Expand Down Expand Up @@ -45,7 +46,7 @@ typedef struct relay_t {
int8_t pin;
struct { // reduces memory footprint
bool active : 1; // is the relay waiting to be switched
bool mode : 1; // does On mean 1 or 0 (inverted output)
bool invert : 1; // does On mean 1 or 0
bool state : 1; // 1 relay is On, 0 relay is Off
bool external : 1; // is the relay externally controlled
int8_t button : 4; // which button triggers relay
Expand Down Expand Up @@ -315,15 +316,15 @@ int MultiRelay::getValue(String data, char separator, int index) {

//Write a byte to the IO expander
byte MultiRelay::IOexpanderWrite(byte address, byte _data ) {
Wire.beginTransmission(addrPcf8574 + address);
Wire.beginTransmission(address);
Wire.write(_data);
return Wire.endTransmission();
}

//Read a byte from the IO expander
byte MultiRelay::IOexpanderRead(int address) {
byte _data = 0;
Wire.requestFrom(addrPcf8574 + address, 1);
Wire.requestFrom(address, 1);
if (Wire.available()) {
_data = Wire.read();
}
Expand All @@ -347,7 +348,7 @@ MultiRelay::MultiRelay()
for (size_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
_relay[i].pin = i<sizeof(defPins) ? defPins[i] : -1;
_relay[i].delay = 0;
_relay[i].mode = false;
_relay[i].invert = false;
_relay[i].active = false;
_relay[i].state = false;
_relay[i].external = false;
Expand All @@ -359,25 +360,27 @@ MultiRelay::MultiRelay()
* switch relay on/off
*/
void MultiRelay::switchRelay(uint8_t relay, bool mode) {
if (relay>=MULTI_RELAY_MAX_RELAYS || (_relay[relay].pin<0 && !usePcf8574)) return;
if (relay>=MULTI_RELAY_MAX_RELAYS || _relay[relay].pin<0) return;
_relay[relay].state = mode;
if (usePcf8574) {
byte expander = relay/8;
uint16_t state = 0;
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) state |= (uint16_t)_relay[i].state << i; // fill relay states for all pins
state = (mode ? !_relay[relay].mode : _relay[relay].mode) ? state | (1<<relay) : state & ~(1<<relay); // take into account invert mode
IOexpanderWrite(expander, state>>(8*expander));
DEBUG_PRINT(F("PCF8574 Writing to ")); DEBUG_PRINT(addrPcf8574 + expander); DEBUG_PRINT(F(" with data ")); DEBUG_PRINTLN(state>>(8*expander));
} else {
if (usePcf8574 && _relay[relay].pin >= 100) {
// we need to send all ouputs at the same time
uint8_t state = 0;
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
if (_relay[i].pin < 100) continue;
uint8_t pin = _relay[i].pin - 100;
state |= (_relay[i].invert ? !_relay[i].state : _relay[i].state) << pin; // fill relay states for all pins
}
IOexpanderWrite(addrPcf8574, state);
DEBUG_PRINT(F("Writing to PCF8574: ")); DEBUG_PRINTLN(state);
} else if (_relay[relay].pin < 100) {
pinMode(_relay[relay].pin, OUTPUT);
digitalWrite(_relay[relay].pin, mode ? !_relay[relay].mode : _relay[relay].mode);
}
digitalWrite(_relay[relay].pin, _relay[relay].invert ? !_relay[relay].state : _relay[relay].state);
} else return;
publishMqtt(relay);
}

uint8_t MultiRelay::getActiveRelayCount() {
uint8_t count = 0;
if (usePcf8574) return MULTI_RELAY_MAX_RELAYS; // we don't know how many there are
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) if (_relay[i].pin>=0) count++;
return count;
}
Expand Down Expand Up @@ -478,27 +481,26 @@ void MultiRelay::setup() {
// if we want PCF8574 expander I2C pins need to be valid
if (i2c_sda == i2c_scl && i2c_sda == -1) usePcf8574 = false;

if (usePcf8574) {
uint16_t state = 0;
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) state |= (uint16_t)(_relay[i].external ? (_relay[i].mode ? !_relay[i].state : _relay[i].state) : (_relay[i].mode ? !offMode : offMode)) << i; // fill relay states for all pins
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i += 8) {
byte expander = i/8;
IOexpanderWrite(expander, state>>(8*expander)); // init expander (set all outputs)
delay(1);
}
DEBUG_PRINTLN(F("PCF8574(s) inited."));
} else {
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
if (_relay[i].pin<0) continue;
if (!pinManager.allocatePin(_relay[i].pin,true, PinOwner::UM_MultiRelay)) {
_relay[i].pin = -1; // allocation failed
} else {
uint8_t state = 0;
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
if (usePcf8574 && _relay[i].pin >= 100) {
uint8_t pin = _relay[i].pin - 100;
if (!_relay[i].external) _relay[i].state = !offMode;
state |= (uint8_t)(_relay[i].invert ? !_relay[i].state : _relay[i].state) << pin;
} else if (_relay[i].pin<100 && _relay[i].pin>=0) {
if (pinManager.allocatePin(_relay[i].pin,true, PinOwner::UM_MultiRelay)) {
if (!_relay[i].external) _relay[i].state = !offMode;
switchRelay(i, _relay[i].state);
_relay[i].active = false;
} else {
_relay[i].pin = -1; // allocation failed
}
}
}
if (usePcf8574) {
IOexpanderWrite(addrPcf8574, state); // init expander (set all outputs)
DEBUG_PRINTLN(F("PCF8574(s) inited."));
}
_oldMode = offMode;
initDone = true;
}
Expand All @@ -519,7 +521,7 @@ void MultiRelay::loop() {
_oldMode = offMode;
_switchTimerStart = millis();
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
if ((_relay[i].pin>=0 || usePcf8574) && !_relay[i].external) _relay[i].active = true;
if ((_relay[i].pin>=0) && !_relay[i].external) _relay[i].active = true;
}
}

Expand Down Expand Up @@ -635,7 +637,7 @@ void MultiRelay::addToJsonInfo(JsonObject &root) {

String uiDomString;
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
if ((_relay[i].pin<0 && !usePcf8574) || !_relay[i].external) continue;
if (_relay[i].pin<0 || !_relay[i].external) continue;
uiDomString = F("Relay "); uiDomString += i;
JsonArray infoArr = user.createNestedArray(uiDomString); // timer value

Expand Down Expand Up @@ -726,7 +728,7 @@ void MultiRelay::addToConfig(JsonObject &root) {
String parName = FPSTR(_relay_str); parName += '-'; parName += i;
JsonObject relay = top.createNestedObject(parName);
relay["pin"] = _relay[i].pin;
relay[FPSTR(_activeHigh)] = _relay[i].mode;
relay[FPSTR(_activeHigh)] = _relay[i].invert;
relay[FPSTR(_delay_str)] = _relay[i].delay;
relay[FPSTR(_external)] = _relay[i].external;
relay[FPSTR(_button)] = _relay[i].button;
Expand All @@ -735,9 +737,10 @@ void MultiRelay::addToConfig(JsonObject &root) {
}

void MultiRelay::appendConfigData() {
oappend(SET_F("addInfo('MultiRelay:first-PCF8574',1,'<i>(not hex!)</i>','address');"));
oappend(SET_F("addInfo('MultiRelay:PCF8574-address',1,'<i>(not hex!)</i>');"));
oappend(SET_F("addInfo('MultiRelay:broadcast-sec',1,'(MQTT message)');"));
oappend(SET_F("addInfo('MultiRelay:relay-0:pin',1,'(use -1 for PCF8574)');"));
//oappend(SET_F("addInfo('MultiRelay:relay-0:pin',1,'(use -1 for PCF8574)');"));
oappend(SET_F("d.extra.push({'MultiRelay':{pin:[['P0',100],['P1',101],['P2',102],['P3',103],['P4',104],['P5',105],['P6',106],['P7',107]]}});"));
}

/**
Expand Down Expand Up @@ -771,14 +774,14 @@ bool MultiRelay::readFromConfig(JsonObject &root) {
String parName = FPSTR(_relay_str); parName += '-'; parName += i;
oldPin[i] = _relay[i].pin;
_relay[i].pin = top[parName]["pin"] | _relay[i].pin;
_relay[i].mode = top[parName][FPSTR(_activeHigh)] | _relay[i].mode;
_relay[i].invert = top[parName][FPSTR(_activeHigh)] | _relay[i].invert;
_relay[i].external = top[parName][FPSTR(_external)] | _relay[i].external;
_relay[i].delay = top[parName][FPSTR(_delay_str)] | _relay[i].delay;
_relay[i].button = top[parName][FPSTR(_button)] | _relay[i].button;
// begin backwards compatibility (beta) remove when 0.13 is released
parName += '-';
_relay[i].pin = top[parName+"pin"] | _relay[i].pin;
_relay[i].mode = top[parName+FPSTR(_activeHigh)] | _relay[i].mode;
_relay[i].invert = top[parName+FPSTR(_activeHigh)] | _relay[i].invert;
_relay[i].external = top[parName+FPSTR(_external)] | _relay[i].external;
_relay[i].delay = top[parName+FPSTR(_delay_str)] | _relay[i].delay;
// end compatibility
Expand All @@ -792,22 +795,11 @@ bool MultiRelay::readFromConfig(JsonObject &root) {
} else {
// deallocate all pins 1st
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++)
if (oldPin[i]>=0) {
if (oldPin[i]>=0 && oldPin[i]<100) {
pinManager.deallocatePin(oldPin[i], PinOwner::UM_MultiRelay);
}
// allocate new pins
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
if (_relay[i].pin>=0 && pinManager.allocatePin(_relay[i].pin, true, PinOwner::UM_MultiRelay)) {
if (!_relay[i].external) {
_relay[i].state = !offMode;
switchRelay(i, _relay[i].state);
_oldMode = offMode;
}
} else {
_relay[i].pin = -1;
}
_relay[i].active = false;
}
setup();
DEBUG_PRINTLN(F(" config (re)loaded."));
}
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
Expand All @@ -825,4 +817,4 @@ const char MultiRelay::_button[] PROGMEM = "button";
const char MultiRelay::_broadcast[] PROGMEM = "broadcast-sec";
const char MultiRelay::_HAautodiscovery[] PROGMEM = "HA-autodiscovery";
const char MultiRelay::_pcf8574[] PROGMEM = "use-PCF8574";
const char MultiRelay::_pcfAddress[] PROGMEM = "first-PCF8574";
const char MultiRelay::_pcfAddress[] PROGMEM = "PCF8574-address";
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ class RotaryEncoderUIUsermod : public Usermod {
*/
void addToConfig(JsonObject &root);

//void appendConfigData();
void appendConfigData();

/**
* restore the changeable values
Expand Down Expand Up @@ -376,6 +376,7 @@ class RotaryEncoderUIUsermod : public Usermod {
*/
byte RotaryEncoderUIUsermod::readPin(uint8_t pin) {
if (usePcf8574) {
if (pin >= 100) pin -= 100; // PCF I/O ports
return (pcfPortData>>pin) & 1;
} else {
return digitalRead(pin);
Expand Down Expand Up @@ -1071,9 +1072,10 @@ void RotaryEncoderUIUsermod::addToConfig(JsonObject &root) {
DEBUG_PRINTLN(F("Rotary Encoder config saved."));
}

//void RotaryEncoderUIUsermod::appendConfigData() {
// oappend(SET_F("addInfo('RotaryEncoderUIUsermod:PCF8574-address',1,'<i>(not hex!)</i>');"));
//}
void RotaryEncoderUIUsermod::appendConfigData() {
oappend(SET_F("addInfo('Rotary-Encoder:PCF8574-address',1,'<i>(not hex!)</i>');"));
oappend(SET_F("d.extra.push({'Rotary-Encoder':{pin:[['P0',100],['P1',101],['P2',102],['P3',103],['P4',104],['P5',105],['P6',106],['P7',107]]}});"));
}

/**
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json
Expand Down Expand Up @@ -1122,7 +1124,7 @@ bool RotaryEncoderUIUsermod::readFromConfig(JsonObject &root) {
pinManager.deallocatePin(pinIRQ, PinOwner::UM_RotaryEncoderUI);
DEBUG_PRINTLN(F("Deallocated old IRQ pin."));
}
pinIRQ = newIRQpin;
pinIRQ = newIRQpin<100 ? newIRQpin : -1; // ignore PCF8574 pins
} else {
pinManager.deallocatePin(pinA, PinOwner::UM_RotaryEncoderUI);
pinManager.deallocatePin(pinB, PinOwner::UM_RotaryEncoderUI);
Expand Down
46 changes: 26 additions & 20 deletions wled00/data/settings_um.htm
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
d.um_p = [];
d.rsvd = [];
d.ro_gpio = [];
d.extra = [];
var umCfg = {};
var pins = [], pinO = [], owner;
var loc = false, locip, locproto = "http:";
Expand All @@ -30,16 +31,11 @@
// success event
scE.addEventListener("load", () => {
GetV();
for (let k=0; k<d.rsvd.length; k++) { pins.push(d.rsvd[k]); pinO.push("rsvd"); }
if (d.um_p[0]==-1) d.um_p.shift();
d.Sf.SDA.max = d.max_gpio;
d.Sf.SCL.max = d.max_gpio;
d.Sf.MOSI.max = d.max_gpio;
d.Sf.SCLK.max = d.max_gpio;
d.Sf.MISO.max = d.max_gpio;
let inp = d.getElementsByTagName("input");
for (let i of inp) if (i.type === "number" && i.name.replace("[]","").substr(-3) === "pin") i.max = d.max_gpio;
pinDropdowns();
for (let r of d.rsvd) { pins.push(r); pinO.push("rsvd"); } // reserved pins
if (d.um_p[0]==-1) d.um_p.shift(); // remove filler
d.Sf.SDA.max = d.Sf.SCL.max = d.Sf.MOSI.max = d.Sf.SCLK.max = d.Sf.MISO.max = d.max_gpio;
//for (let i of d.getElementsByTagName("input")) if (i.type === "number" && i.name.replace("[]","").substr(-3) === "pin") i.max = d.max_gpio;
pinDropdowns(); // convert INPUT to SELECT for pins
});
// error event
scE.addEventListener("error", (ev) => {
Expand Down Expand Up @@ -77,6 +73,7 @@
function isF(n) { return n === +n && n !== (n|0); }
function isI(n) { return n === +n && n === (n|0); }
function check(o,k) { // input object, pin owner key
/* no longer necessary with pin dropdown fields
var n = o.name.replace("[]","").substr(-3);
if (o.type=="number" && n.substr(0,3)=="pin") {
for (var i=0; i<pins.length; i++) {
Expand All @@ -99,6 +96,7 @@
if (o.value==pins[i] || o.value<-1 || o.value>d.max_gpio) { o.style.color="red"; break; } else o.style.color=d.ro_gpio.some((e)=>e==parseInt(o.value,10))?"orange":"#fff";
}
}
*/
}
function getPins(o) {
if (isO(o)) {
Expand Down Expand Up @@ -181,6 +179,13 @@
if (j === v) opt.selected = true; // this is "our" pin
else if (pins.includes(j)) opt.disabled = true; // someone else's pin
}
let um = i.name.split(":")[0];
d.extra.forEach((o)=>{
if (o[um] && o[um].pin) o[um].pin.forEach((e)=>{
let opt = addOption(sel,e[0],e[1]);
if (e[1]==v) opt.selected = true;
});
});
}
}
}
Expand All @@ -198,15 +203,17 @@
i.text = "unused";
return
}
i.text = i.value;
if (i.value==oldV) {
i.disabled = false;
}
if (i.value==e.value) {
i.disabled = true;
i.text += ` ${txt}`;
if (i.value<100) { // TODO remove this hack and use d.extra
i.text = i.value;
if (i.value==oldV) {
i.disabled = false;
}
if (i.value==e.value) {
i.disabled = true;
i.text += ` ${txt}`;
}
if (d.ro_gpio.includes(parseInt(i.value))) i.text += " (R/O)";
}
if (d.ro_gpio.includes(parseInt(i.value))) i.text += " (R/O)";
});
}
}
Expand Down Expand Up @@ -235,15 +242,14 @@
var att = inp.attributes[i];
// type and value don't apply, so skip them
// ** you might also want to skip style, or others -- modify as needed **
if (att.name != 'type' && att.name != 'value' && att.name != 'class' && att.name != 'style') {
if (att.name != 'type' && att.name != 'value' && att.name != 'class' && att.name != 'style' && att.name != 'oninput' && att.name != 'max' && att.name != 'min') {
sel.setAttribute(att.name, att.value);
}
}
sel.setAttribute("data-val", v);
sel.setAttribute("onchange", "UI(this)");
// finally, replace the old input element with the new select element
inp.parentElement.replaceChild(sel, inp);
if (arr[0].type==="hidden") arr[0].parentElement.removeChild(arr[0]); // remove hidden element from DOM
return sel;
}
return null;
Expand Down
Loading

0 comments on commit 4ea5723

Please sign in to comment.