diff --git a/icbm.ino b/icbm.ino index c43b3cf30726d99a9c137b62a6b96d70b63a1de3..cb83fd8509e4a18a140a06e539773f54c7535da9 100644 --- a/icbm.ino +++ b/icbm.ino @@ -15,11 +15,13 @@ void setup() { initializeRoutes(); initializeSNMP(); - start = millis(); - + // Poll port expander to initialize the PINS struct. poll_port_expander(); + PINS.last_triggered = millis(); PINS.stage = ""; PINS.ramping = false; + + start = millis(); } enum { @@ -28,7 +30,7 @@ enum { OFF, }; -void serial_loop() { +void serialLoop() { if (millis() - start >= 1000) { start = millis(); Serial.print(eth_connected ? "." : "-"); @@ -40,5 +42,8 @@ void loop() { snmp.loop(); restServer.handleClient(); toggle_status_led(); - serial_loop(); + serialLoop(); + if (eth_connected) { // We may be triggered, but disconnected from the network. + powerOffCheckLoop(); + } } diff --git a/mpod.cpp b/mpod.cpp index 4ceb264867b8642de2ca540992e7a299ca29412f..d46fd3f791ea5cc1bf08b69b1ccc70412305dc56 100644 --- a/mpod.cpp +++ b/mpod.cpp @@ -5,6 +5,7 @@ MPOD::MPOD() { bool _on = false; + bool _interlocked = false; bool _rampingUp = false; bool _rampingDown = false; float _measurementSenseVoltage = 0; @@ -85,6 +86,7 @@ bool MPOD::message(const SNMP::Message *message) { break; } _on = status->getBit(0); + _interlocked = status->getBit(1); _rampingUp = status->getBit(11); _rampingDown = status->getBit(12); } @@ -105,9 +107,6 @@ bool MPOD::message(const SNMP::Message *message) { found++; break; case OID::OUTPUTSWITCH: - // Use private helper function to extract integer value - Serial.print("Output Switch Length: "); - Serial.println(varbind->getLength()); _on = getIntegerFromVarBind(varbind); found++; break; @@ -154,6 +153,8 @@ bool MPOD::message(const SNMP::Message *message) { bool MPOD::isOn() const { return _on; } +bool MPOD::isInterlocked() const { return _interlocked; } + bool MPOD::isRampingUp() const { return _rampingUp; } bool MPOD::isRampingDown() const { return _rampingDown; } @@ -185,6 +186,7 @@ String MPOD::toJSON() { String json = "{\n"; json += "\"channel\":" + String(getChannel()) + ","; json += "\"is_on\":" + String(isOn()) + ","; + json += "\"is_interlocked\":" + String(isInterlocked()) + ","; json += "\"ramping_up\":" + String(isRampingUp()) + ","; json += "\"ramping_down\":" + String(isRampingDown()) + ","; json += "\"sense_voltage\":" + String(getMeasurementSenseVoltage()) + ","; @@ -261,7 +263,7 @@ void setChannelStateAndWait(const IPAddress *ipAddr, const uint16_t channel, con loopCount += 1; ramping = (mpod.isRampingUp() || mpod.isRampingDown()); if (!ramping) { - if (mpod.isOn() == (bool)output) { + if ((mpod.isOn() == (bool)output || mpod.isInterlocked())) { settingChannelState = false; } else if (loopCount >= 5) { // Resend set command diff --git a/mpod.hpp b/mpod.hpp index 8ecd78f17ddca1c1f65409a0f27da26575fd3e2b..ac64d040e8be88facffe66a2e3f8cee49e52d7e1 100644 --- a/mpod.hpp +++ b/mpod.hpp @@ -34,7 +34,7 @@ class MPOD { UNKNOWN, COUNT = UNKNOWN, }; - + static inline const char *NAMES[] = { // These are incomplete SNMP commands needing to be // formatted with the channel id after the final dot @@ -46,7 +46,7 @@ class MPOD { "1.3.6.1.4.1.19947.1.3.2.1.12.", "1.3.6.1.4.1.19947.1.3.2.1.13.", }; - + // Returns index of OID equals to name // Returns UNKNOWN if none static unsigned int match(const char *name) { @@ -58,13 +58,14 @@ class MPOD { return UNKNOWN; } }; - - MPOD(); + + MPOD(); SNMP::Message* read(const uint16_t); SNMP::Message* setChannelState(const uint16_t, const bool); SNMP::Message* setTargetVoltage(const uint16_t, const float); bool message(const SNMP::Message*); bool isOn() const; + bool isInterlocked() const; bool isRampingUp() const; bool isRampingDown() const; float getMeasurementSenseVoltage() const; @@ -74,12 +75,13 @@ class MPOD { float getVoltageRiseRate() const; uint16_t getChannel() const; String toJSON(); - + private: unsigned int getIntegerFromVarBind(const VarBind*); float getFloatFromVarBind(const VarBind*); uint16_t _channel; bool _on; + bool _interlocked; bool _rampingUp; bool _rampingDown; float _measurementSenseVoltage; diff --git a/pins.cpp b/pins.cpp index c64146c4b3562c038a48d1fa582a581abb837675..03a94ccebd9cbf5ef6bff9fca4021c4694b8c2d9 100644 --- a/pins.cpp +++ b/pins.cpp @@ -1,10 +1,16 @@ #include "pins.hpp" +#include "powerproc.hpp" + MCP23S08 MCP(SS, MISO, MOSI, SCK); -volatile byte ISR_FLAG = 0; + +// Semaphores for handling interrupts and power down events. +volatile byte MCP_ISR_FLAG = 0; +volatile byte PERFORM_PROCEDURE_FROM_INTERRUPT = 0; + struct pins PINS; -void IRAM_ATTR isr() { ISR_FLAG = 1; } +void IRAM_ATTR isr() { MCP_ISR_FLAG = 1; } void initializeMCP() { pinMode(ISR_PIN, INPUT_PULLUP); @@ -18,40 +24,54 @@ void initializeMCP() { MCP.enableInterrupt(0, CHANGE); MCP.enableInterrupt(1, CHANGE); MCP.enableInterrupt(2, CHANGE); + + // Inform SIB we're ready + MCP.write1(ALERT_SIB_PIN, HIGH); + Serial.println("MCP23S08 Started"); } void isr_check_loop() { - if (ISR_FLAG == 1) { - PINS.sib = false; - PINS.plc = false; - PINS.ups = false; - PINS.last_triggered = millis(); - - Serial.print("Interrupt: "); - uint8_t regval = MCP.getInterruptCaptureRegister(); // INTCAP - Serial.print(regval, BIN); - - uint8_t mask = 1 << 0; - if (!(regval & mask)) { - Serial.print(" UPS"); - PINS.ups = true; - } - - mask = 1 << 1; - if (!(regval & mask)) { - Serial.print(" PLC"); - PINS.plc = true; - } - - mask = 1 << 2; - if (!(regval & mask)) { - Serial.print(" SIB"); - PINS.sib = true; - } - - Serial.println(); - ISR_FLAG = 0; + if (MCP_ISR_FLAG == 0) { + return; + } + // False means triggered (not ok). + PINS.sib = false; + PINS.plc = false; + PINS.ups = false; + PINS.last_triggered = millis(); + + // Get the pins status at the time of the interrupt, and check their statuses. + Serial.print("Interrupt: "); + uint8_t regval = MCP.getInterruptCaptureRegister(); // INTCAP + Serial.print(regval, BIN); + + uint8_t mask = 1 << 0; + if (!(regval & mask)) { + Serial.print(" UPS"); + PINS.ups = true; + } + + mask = 1 << 1; + if (!(regval & mask)) { + Serial.print(" PLC"); + PINS.plc = true; + } + + mask = 1 << 2; + if (!(regval & mask)) { + Serial.print(" SIB"); + PINS.sib = true; + } + + Serial.println(); + + // This interrupt was handled + MCP_ISR_FLAG = 0; + + // If not all ok, set the power down flag, later handled. + if (!(PINS.sib && PINS.plc && PINS.ups)) { + PERFORM_PROCEDURE_FROM_INTERRUPT = 1; } } @@ -59,7 +79,6 @@ void poll_port_expander() { PINS.sib = false; PINS.plc = false; PINS.ups = false; - PINS.last_triggered = millis(); int regval = MCP.read8(); uint8_t mask = 1 << 0; @@ -92,3 +111,56 @@ void toggle_status_led() { MCP.write1(STATUS_LED_PIN, on); } } + +void powerOffCheckLoop() { + if (!PERFORM_PROCEDURE_FROM_INTERRUPT) { + return; + } + // Disable interrupts while performing power procedure. + // Once we've been triggered, we perform the power down procedure, uninterrupted, + // regardless of changes in the environment. + detachInterrupt(digitalPinToInterrupt(ISR_PIN)); + + // Update status on serial interface and status webpage. + PINS.ramping = true; + Serial.println("POWERING DOWN DUE TO TRIGGER."); + + // Inform SIB that we're about to perform a procedure. + MCP.write1(ALERT_SIB_PIN, LOW); + Serial.println("asked sib down"); + + // Wait for SIB to acknowledge by checking its input (should go to triggered). + /* This require hardware changes, not implemented yet. + // TODO: apply a timeout and go ahead anyway? + do { + delay(200); + poll_port_expander(); + } while(!(PINS.sib)); + Serial.println("SIB ACK"); + */ + + // Iterate through each group and power down. + String group; + String groups; + for (int8_t groupIdx = pproc.stagesCount - 1; groupIdx >= 0; groupIdx--) { + group = pproc.stages[groupIdx].name; + groups += group; + groups += ","; + PINS.stage = group; + pproc.powerOff(group); + delay(1000); + } + + // Clear statuses on webpage and serial interfaces. + PINS.ramping = false; + Serial.print("Finished powering down from interrupt: "); + Serial.println(groups); + PINS.stage = "Ramped down"; + + // This event has been handled. + PERFORM_PROCEDURE_FROM_INTERRUPT = 0; + // Inform SIB that we're done and it can take data again. + MCP.write1(ALERT_SIB_PIN, HIGH); + // Re-enable interrupts before exiting. + attachInterrupt(digitalPinToInterrupt(ISR_PIN), isr, CHANGE); +} diff --git a/pins.hpp b/pins.hpp index 0cd10be8355680ed00f69e82155f5bf4df5fe46f..e1efb053f3824ff5eeb9d3b30c7312e44c2caec9 100644 --- a/pins.hpp +++ b/pins.hpp @@ -13,9 +13,12 @@ //AlCon Logic Board interrupt #define TRIGGER_SIGNAL 14 -// LED pin on MCP23S08 extender +// LED pin on MCP23S08 expander #define STATUS_LED_PIN 6 +// Pin to inform SIB that a power down is about to happen, on MCP23S08 expander +#define ALERT_SIB_PIN 7 + struct pins { bool sib; bool plc; @@ -32,3 +35,4 @@ void initializeMCP(); void isr_check_loop(); void poll_port_expander(); void toggle_status_led(); +void powerOffCheckLoop(); diff --git a/powerproc.hpp b/powerproc.hpp index 979df469bee14954ac6bd1b95ccbbee18c5e62a6..8e91b8810d72d700e23d62f628a8db8e387ec948 100644 --- a/powerproc.hpp +++ b/powerproc.hpp @@ -34,4 +34,6 @@ public: String toJSON(); }; +void powerOffCheckLoop(); + extern PowerProcedure pproc; diff --git a/rest.cpp b/rest.cpp index e4bbea1f025e0beae09f5fd353127b3718a6f917..e71b0837da9a35ae53a3aa3b931ba765fa5b8cc2 100644 --- a/rest.cpp +++ b/rest.cpp @@ -274,10 +274,9 @@ void pollMPODChannel() { } } - String ret; - if (!channel) { - ret = "\"reason\": \"Invalid channel\""; - } else { + String ret = "\"Invalid channel\""; + + if (channel) { SNMP::Message *snmp_msg = mpod.read(channel); snmp.send(snmp_msg, pproc.ipAddr, SNMP::Port::SNMP); delete snmp_msg; @@ -285,20 +284,22 @@ void pollMPODChannel() { delay(MPOD_UPDATE_LATENCY); snmp.loop(); // Force loop to update now - if (mpod.getChannel()) { + if (mpod.getChannel()) { // Valid channel requested ret = mpod.toJSON(); - } else { - ret = "\"reason\": \"Invalid channel\""; - channel = 0; } } String http_msg = "{\n"; + http_msg += "\"success\": "; + http_msg += channel ? 1 : 0; + http_msg += ",\n"; + http_msg += "\"arguments\": {"; + http_msg += "\"channel\": "; + http_msg += channel; + http_msg += "},\n"; // arguments http_msg += "\"status\": "; http_msg += ret; - http_msg += ",\"success\": "; - http_msg += channel ? 1 : 0; - http_msg += "\n}"; + http_msg += "}"; restServer.send(channel ? 200 : 406, "text/json", http_msg); }