Skip to content
  • MySensors
  • OpenHardware.io
  • Categories
  • Recent
  • Tags
  • Popular
Skins
  • Light
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Default (No Skin)
  • No Skin
Collapse
Brand Logo
  1. Home
  2. Troubleshooting
  3. Node only works for a few days

Node only works for a few days

Scheduled Pinned Locked Moved Troubleshooting
22 Posts 10 Posters 5.2k Views 7 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • gohanG Offline
    gohanG Offline
    gohan
    Mod
    wrote on last edited by
    #11

    Before buying new hw it's better to do some cross tests to pin point where the problem is.

    1 Reply Last reply
    0
    • FotoFieberF Offline
      FotoFieberF Offline
      FotoFieber
      Hardware Contributor
      wrote on last edited by
      #12

      I had a similar problem with the control of a heating mixer and three dallas sensors. I solved the problem with:

      • replacement of the relays with solid state relays
      • moved conttroller more in distance of the high voltage parts

      I also added a watchdog and a reboot mechanism in case of sensor problems.

      Maybe this sketch can give you an inspiration: (work in progress, MYS-Part not tested in depth)

      // Enable debug prints to serial monitor
      //#define MY_DEBUG      // in Mysensors
      #define EN_DEBUG      // in this sketch
      //#define NO_MYS      // ohne Mysensors Unterstützung?
      //#define SIMULATION
      #define NO_AC_DETECT  // ohne AC sensor
      #define NO_RTC
      
      // RTC nur zusammen mit Mysensors
      #ifdef NO_MYS
      #ifndef NO_RTC
      #define NO_RTC
      #endif
      #endif
      
      #ifdef SIMULATION
      // für Simulation ohne Sensoren
      #define SIMULATE_VOR  35
      #define SIMULATE_RUE  30
      #define SIMULATE_ZU   65
      
      
      #ifdef SIMULATE_VOR
      #warning Achtung! Keine Echtentemperaturmessungen -> Simulation
      #endif
      
      #ifdef SIMULATE_RUE
      #warning Achtung! Keine Echtentemperaturmessungen -> Simulation
      #endif
      
      #ifdef SIMULATE_ZU
      #warning Achtung! Keine Echtentemperaturmessungen -> Simulation
      #endif
      #endif
      
      
      #define START_TARGET_TEMP 40
      #define EEPROM_TARGET_TEMP 900            // Save Porisiton. It is above the Mysensors range of lib 2.1
      #define EEPROM_POWERSTATE  902            // Save Porisiton. It is above the Mysensors range of lib 2.1
      #define MAX_TEMP 50
      #define VORLAUF 0
      #define RUECKLAUF 1
      #define ZULAUF 2
      #define PUMPE 0
      #define MISCHER_ZU  1
      #define MISCHER_AUF 2
      #define POWERLED 7                        // 8. LED
      #define TEMPDOWNLED 5                     // 6. Led
      #define TEMPUPLED 6                       // 7. Led
      #ifndef NO_AC_DETECT
      #define BUDERUSLED 4                      // 5. Led
      #endif
      #define RETRY_TIMEOUT_PUMP 2*60*1000UL     // alle 2 Minuten testen, ob Vorlauf nicht besser (5 Sekunden pumpen)
      #define REGULATION_TIMEOUT_PUMP 30*1000UL  // 30 Sekunden warten nach neuer Einstellung
      #define PROBE_TIMEOUT_PUMP 5*1000UL        // 5 Sekunden Pumpe für Test einschalten, wenn Zulauf zu kalt
      #define BUDERUS_PIN 3                     // Buderus Powererkennung Pumpe auf PIN 3
      
      #ifndef SIMULATION
      #define MISCHER_RESET_TIME 120*1000UL      // 2 Minuten bis Nullstellung
      #else
      #define MISCHER_RESET_TIME 10*1000UL      // 10 Sekunden bis Nullstellung nei Simulation
      #endif
      
      
      
      #ifdef EN_DEBUG
      #define DEBUG_PRINT(x) Serial.print (x)
      #else
      #define DEBUG_PRINT(x)
      #endif
      
      #ifdef EN_DEBUG
      #define DEBUG_PRINTLN(x)  Serial.println (x)
      #else
      #define DEBUG_PRINTLN(x)
      #endif
      
      #ifndef NO_MYS
      // Radio Configuration
      #define MY_TRANSPORT_WAIT_READY_MS (10000ul)
      #define MY_RADIO_RFM69
      #define MY_RFM69_FREQUENCY RF69_868MHZ
      #define MY_RFM69_NETWORKID 13
      #define MY_RFM69_ENABLE_ENCRYPTION
      #define MY_NODE_ID 168
      //#define MY_IS_RFM69HW
      #endif
      
      
      #include <Arduino.h>
      #include <avr/wdt.h>
      #include <EEPROM.h>
      #include <MemoryFree.h>
      
      #ifndef NO_MYS
      #define MIN_REPORT_INTERVAL  5 * 60 * 1000L   // mindestens alle 5 Minuten melden
      #include <SPI.h>
      #include <MySensors.h>
      #include <Time.h>        //http://www.arduino.cc/playground/Code/Time
      #include <Timezone.h>    //https://github.com/JChristensen/Timezone
      #include <TimeLib.h>
      
      //Central European Time (Frankfurt, Paris)
      TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120};     //Central European Summer Time
      TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60};       //Central European Standard Time
      Timezone CE(CEST, CET);
      bool timeReceived = false;
      unsigned long lastUpdate = 0, lastRequest = 0;
      #endif
      
      #ifndef NO_RTC
      #include <DS3232RTC.h>  // A  DS3231/DS3232 library
      #endif
      
      #define MAX_LEDS 8
      
      byte lastButtonState = 0;
      #define RELAY_ON HIGH
      #define RELAY_OFF LOW
      #define RELAY_1  A0         // Arduino Digital I/O pin number for first relay (second on pin+1 etc)
      #define NUMBER_OF_RELAYS 4  // Total number of attached relays
      
      
      boolean ledState[MAX_LEDS] = { false, false, false, false, false, false, false, false };
      boolean relState[NUMBER_OF_RELAYS] = { false, false, false, false };
      
      #include <TM1638.h>
      #include <DallasTemperature.h>
      #include <OneWire.h>
      
      #define ONE_WIRE_BUS 6 // Pin where dallase sensor is connected 
      #define TEMPERATURE_PRECISION 9
      #define MAX_ATTACHED_DS18B20 3
      
      //28B404080000804A
      DeviceAddress Probe01 = { 0x28, 0xB4, 0x04, 0x08, 0x00, 0x00, 0x80, 0x4A };
      //28C606080000803F
      DeviceAddress Probe02 = { 0x28, 0xC6, 0x06, 0x08, 0x00, 0x00, 0x80, 0x3F };
      //28750808000080C3
      DeviceAddress Probe03 = { 0x28, 0x75, 0x08, 0x08, 0x00, 0x00, 0x80, 0xC3 };
      
      OneWire oneWire(ONE_WIRE_BUS); // Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
      DallasTemperature sensors(&oneWire); // Pass the oneWire reference to Dallas Temperature.
      int numSensors = 0;
      int lastTemperature[MAX_ATTACHED_DS18B20] = { -100, -100, -100 };
      int16_t conversionTime;
      unsigned long previousTempMillis = -1;
      static unsigned long nowms = millis();    // update at start of loop()
      
      
      // define LCD module
      TM1638 ledModule(8, 9, 7);
      
      int displayInfo = -1;
      unsigned long previousDisplayMillis = 0;
      
      int targetTemp = START_TARGET_TEMP;
      #ifdef SIMULATION
      bool powerOn = true;
      #ifndef NO_AC_DETECT
      bool powerOnBuderus = true;
      #endif
      #else
      bool powerOn = false;
      #ifndef NO_AC_DETECT
      bool powerOnBuderus = false;
      #endif
      #endif
      bool resetMischer = true; // mischer zuerst in Nullstellung
      
      #ifndef NO_AC_DETECT
      bool testStateBuderus = false;
      unsigned long lastResetBuderus = -1;
      #endif
      
      #ifndef NO_MYS
      // Initialize messages
      MyMessage msgTemp(0, V_TEMP);
      MyMessage msgStatus(0, V_STATUS);
      MyMessage msgTargetTemp(MAX_ATTACHED_DS18B20 + NUMBER_OF_RELAYS, V_HVAC_SETPOINT_HEAT);
      MyMessage msgPower(MAX_ATTACHED_DS18B20 + NUMBER_OF_RELAYS + 1, V_STATUS);
      #ifndef NO_AC_DETECT
      MyMessage msgPowerBuderus(MAX_ATTACHED_DS18B20 + NUMBER_OF_RELAYS + 2, V_STATUS);
      #endif
      MyMessage msgDebug(MAX_ATTACHED_DS18B20 + NUMBER_OF_RELAYS + 3, V_TEXT);
      #endif
      
      // the setup function runs once when you press reset or power the board
      void setup() {
        DEBUG_PRINTLN(F("Starte setup"));
        wdt_enable(WDTO_8S);
        Serial.begin(115200);
      
      #ifndef NO_RTC
        // the function to get the time from the RTC
        setSyncProvider(RTC.get);
      #endif
      #ifndef NO_MYS
        // Request latest time from controller at startup
        requestTime();
      #endif
      
      
      
      
        // Zieltemperatur aus EERPOM lesem:
        targetTemp = EEPROM.read(EEPROM_TARGET_TEMP);
        if ((targetTemp < 30) || (targetTemp > MAX_TEMP)) {
          targetTemp = START_TARGET_TEMP;
          EEPROM.write(EEPROM_TARGET_TEMP, targetTemp);
        }
      
        int state = EEPROM.read(EEPROM_POWERSTATE);
        if (state == 0) {
          powerOn = false;
        }
        else powerOn = true;
      
      #ifndef NO_AC_DETECT
        // Buderuserkennung auf PIN3
        attachInterrupt(digitalPinToInterrupt(BUDERUS_PIN), buderusSet, CHANGE);
        testStateBuderus = false;
        lastResetBuderus = millis();
      #ifndef NO_MYS
        send(msgPowerBuderus.set(powerOnBuderus));
      #endif
      #endif
      
        // Relayausgänge initialisiern
        for (int sensor = 1, pin = RELAY_1; sensor <= NUMBER_OF_RELAYS; sensor++, pin++) {
          // Then set relay pins in output mode
          pinMode(pin, OUTPUT);
          // Set relay to last known state (using eeprom storage)
          digitalWrite(pin, RELAY_OFF);
        }
      
        // Dallas Temperatursensoren
        sensors.begin();
        sensors.setWaitForConversion(false);
        numSensors = sensors.getDeviceCount();
        DEBUG_PRINT(F("Dallas Sensoren "));
        DEBUG_PRINTLN(numSensors);
        DeviceAddress tempDeviceAddress; // We'll use this variable to store a found device address
      
        for (int i = 0; i < numSensors; i++)
        {
          wdt_reset();
          // Search the wire for address
          if (sensors.getAddress(tempDeviceAddress, i))
          {
            DEBUG_PRINT(F("Found device "));
      #ifdef EN_DEBUG
            Serial.print(i, DEC);
      #endif
            DEBUG_PRINT(F(" with address: "));
            printAddress(tempDeviceAddress);
            DEBUG_PRINTLN();
      
            DEBUG_PRINT(F("Setting resolution to "));
      #ifdef EN_DEBUG
            Serial.println(TEMPERATURE_PRECISION, DEC);
      #endif
            // set the resolution to 12 bit (Each Dallas/Maxim device is capable of several different resolutions)
            sensors.setResolution(tempDeviceAddress, TEMPERATURE_PRECISION);
      
            DEBUG_PRINT(F("Resolution actually set to: "));
      
      #ifdef EN_DEBUG
            Serial.print(sensors.getResolution(tempDeviceAddress), DEC);
      #endif
            DEBUG_PRINTLN();
          } else {
            DEBUG_PRINT(F("Found ghost device at "));
      
      #ifdef EN_DEBUG
            Serial.print(i, DEC);
      #endif
            DEBUG_PRINT(F(" but could not detect address. Check power and cabling"));
          }
        }
      
        wdt_reset();
        sensors.requestTemperatures();
      
        // query conversion time and sleep until conversion completed
        conversionTime = sensors.millisToWaitForConversion(sensors.getResolution());
      }
      
      /*****************************************************/
      // the loop function runs over and over again forever
      void loop() {
        nowms = millis();
      
      #ifndef NO_MYS
        // If no time has been received yet, request it every 10 second from controller
        // When time has been received, request update every hour
        if ((!timeReceived && (nowms - lastRequest) > (10UL * 1000UL))
            || (timeReceived && (nowms - lastRequest) > (60UL * 1000UL * 60UL))) {
          // Request time from controller.
          DEBUG_PRINTLN("requesting time");
          requestTime();
          lastRequest = nowms;
        }
      #endif
      
        wdt_reset();
      
      #ifndef NO_AC_DETECT
        readAC();
      #endif
      
        readTemp();
        readButtons();
      
        updateDisp();
      
        // Temperaturen senden
      
      #ifndef NO_MYS
        sendTemp();
      #endif
      
        // Kontroller
        control();
      
        // Loop 2x pro Sekunde ist aureichend
        unsigned long loopTime = millis() - nowms;
        if (loopTime < 450) {
      #ifdef NO_MYS
          delay (500 - loopTime);
      #else
          wait (500 - loopTime);
      #endif
        }
      }
      // end loop
      
      /**************************************************************/
      // Steuerung
      void control()
      {
      
        wdt_reset();
      
        static unsigned long lastPumpTest = 0;
        static unsigned long lastRegulation = -REGULATION_TIMEOUT_PUMP;
        static unsigned long resetStart = 0;                     // wann wurde die Rücksetzung des Mischers gestartet
        static bool lastPower = powerOn;
        static float moveSeconds = 0.0;                 // Zeiten für Stellmotor Mischer
      
        // controllerState
        // 0 initialized
        // 1 Pumpe für kurzen test aktiviert, warten auf Timeout für Deaktivierung
        // 2 in der Regelung, warten auf Timeout für Deaktivierung
        // 3 in Ausganglage fahren
      
        static int controllerState = 0;
      
        // Emergency
        if  (lastTemperature[VORLAUF] > MAX_TEMP) {
          if (resetStart == 0) {
            if (relState[PUMPE]) {
              DEBUG_PRINT(F("Zu heiss. Emergency Mischer schliessen"));
              DEBUG_PRINTLN(lastTemperature[VORLAUF]);
            }
            closeMischer();
            controllerState = 0;
            return;
          }
        }
      
      
        // Temperatur noch nicht gelesen;
        if (lastTemperature[VORLAUF] == -100) return;
      
        // Fehler mit den Sensoren
        if ((lastTemperature[VORLAUF] < 0) ||
            (lastTemperature[VORLAUF] > 100) ||
            (lastTemperature[RUECKLAUF] < 0) ||
            (lastTemperature[RUECKLAUF] > 100) ||
            (lastTemperature[ZULAUF] < 0) ||
            (lastTemperature[ZULAUF] > 100)) {
          relState[PUMPE] = false;
          updateRelays();
          ledState[PUMPE] = relState[PUMPE];
          // Test
          //powerOn = false;
      #ifndef NO_MYS
          //send(msgPower.set(powerOn));
      #endif
      
          controllerState = 0;
          DEBUG_PRINTLN(F("Fehler mit den Sensoren, Temperaturen unter 0 oder über 100. Poweroff!"));
          return;
        }
      
        if (resetMischer) {
          DEBUG_PRINTLN(F("Mischer initialisieren"));
          closeMischer();
          resetStart = millis();
          resetMischer = false;
          controllerState = 0;
          return;
        }
      
        if (resetStart > 0) {
          if ((millis() - resetStart) > MISCHER_RESET_TIME) {
            DEBUG_PRINTLN(F("Mischer fertig initialisiert"));
            relState[MISCHER_ZU] = false;
            updateRelays();
            ledState[PUMPE] = relState[PUMPE];
            ledState[MISCHER_ZU] = relState[MISCHER_ZU];
            resetStart = 0;
            controllerState = 0;
          }
          return;
        }
      
        // wenn ausser Betrieb -> verlassen;
        if (powerOn == false) {
          if (lastPower == false) return;
      
          DEBUG_PRINTLN("Go to power off state");
      
      #ifndef NO_MYS
          send(msgPower.set(powerOn));
      #endif
      
          lastPower = false;
          relState[PUMPE] = false;
          relState[MISCHER_ZU] = false;
          relState[MISCHER_AUF] = false;
          updateRelays();
          ledState[PUMPE] = relState[PUMPE];
          ledState[MISCHER_ZU] = relState[MISCHER_ZU];
          ledState[MISCHER_AUF] = relState[MISCHER_AUF];
          controllerState = 0;
          EEPROM.write(EEPROM_POWERSTATE, 0);
          return;
        }
        //DEBUG_PRINTLN("a");
        lastPower = true;
      
        switch (controllerState) {
          case  0:  // Initialisiert
            if (lastTemperature[ZULAUF] < 30) {
              if ((millis() - lastPumpTest) > RETRY_TIMEOUT_PUMP) {
                controllerState = 1;
                lastPumpTest = millis();
                DEBUG_PRINTLN(F("Zulauf zu kalt. Schalte Pumpe für 5 Sekunden ein."));
                relState[PUMPE] = true;
                updateRelays();
                ledState[PUMPE] = relState[PUMPE];
              }
              return;
            }
            //DEBUG_PRINTLN("x");
      
            relState[PUMPE] = true;
            updateRelays();
            ledState[PUMPE] = relState[PUMPE];
      
      
            // nicht regulieren, wenn schon genau genug
            if (abs(lastTemperature[VORLAUF] - targetTemp) <= 1) {
              // DEBUG_PRINTLN("diff zu klein, keine Steuerung notwendig");
              return;
            }
      
      
            if ((millis() - lastRegulation) > REGULATION_TIMEOUT_PUMP) {
              controllerState = 2;
              lastRegulation = millis();
      
              int diffZualaufRuecklauf = lastTemperature[ZULAUF] - lastTemperature[RUECKLAUF];
      
              float actPercent = (float)(lastTemperature[VORLAUF] - lastTemperature[RUECKLAUF]) * 100.0 / (float)diffZualaufRuecklauf;
              float shoulPercent = (float)(targetTemp - lastTemperature[RUECKLAUF]) * 100.0 / (float)diffZualaufRuecklauf;
      
              // 120 Sekunden für ganzen Weg
              moveSeconds = (float)MISCHER_RESET_TIME / 1000.0 / 100.0 * abs(shoulPercent - actPercent);
              DEBUG_PRINT(F("Berechnete Sekunden fuer Mischerumstellung "));
              DEBUG_PRINTLN(moveSeconds);
              if (moveSeconds > 8)
                moveSeconds = 8;
      
              if (targetTemp > lastTemperature[VORLAUF]) {
                DEBUG_PRINTLN(F("Mischer auf"));
                relState[MISCHER_ZU] = false;
                relState[MISCHER_AUF] = true;
                updateRelays();
                ledState[MISCHER_ZU] = relState[MISCHER_ZU];
                ledState[MISCHER_AUF] = relState[MISCHER_AUF];
              }
              else
              {
                DEBUG_PRINTLN(F("Mischer zu"));
                relState[MISCHER_ZU] = true;
                relState[MISCHER_AUF] = false;
                updateRelays();
                ledState[MISCHER_ZU] = relState[MISCHER_ZU];
                ledState[MISCHER_AUF] = relState[MISCHER_AUF];
              }
              return;
            }
            return;
            break;
          case  1: // in Testmodus bei zu wenig  Temp im Zulauf
            if ((millis() - lastPumpTest) > PROBE_TIMEOUT_PUMP) {
              relState[PUMPE] = false;
              updateRelays();
              ledState[PUMPE] = relState[PUMPE];
              DEBUG_PRINTLN(F("Pumpe ausgeschaltet"));
              controllerState = 0;
              lastPumpTest = millis();
            }
            return;
            break;
          case  2: // in der Regelung
            if ((millis() - lastRegulation) > moveSeconds * 1000.0) {
              DEBUG_PRINTLN("Mischer zu.");
              relState[MISCHER_ZU] = false;
              relState[MISCHER_AUF] = false;
              updateRelays();
              ledState[MISCHER_ZU] = relState[MISCHER_ZU];
              ledState[MISCHER_AUF] = relState[MISCHER_AUF];
              lastRegulation = millis();
              controllerState = 0;
            }
            return;
            break;
          default:
            DEBUG_PRINT(F("ERROR, unknown state :"));
            DEBUG_PRINTLN(controllerState);
            controllerState = 0;
            return;
        }
      }
      
      // function to print a device address
      void printAddress(DeviceAddress deviceAddress)
      {
        for (uint8_t i = 0; i < 8; i++)
        {
          if (deviceAddress[i] < 16) DEBUG_PRINT("0");
      
      #ifdef EN_DEBUG
          Serial.print(deviceAddress[i], HEX);
      #endif
        }
      }
      
      void updateRelays() {
        static boolean relayStateReported[8] = { false, false, false, false, false, false, false, false };
      #ifndef NO_MYS
        static unsigned long previousStateMillis[NUMBER_OF_RELAYS] = { 0, 0, 0, 0 };
      #endif
      
        for (int i = 0; i < NUMBER_OF_RELAYS; i++) {
          if (relState[i]) {
            digitalWrite(RELAY_1 + i, RELAY_ON);
          }
          else
          {
            digitalWrite(RELAY_1 + i, RELAY_OFF);
          }
      
      #ifndef NO_MYS
          bool bSend = false;
          if (previousStateMillis[i] == 0) {                               // noch nie gesendet
            bSend = true;
          }
          else if (relayStateReported[i] != relState[i] ) { // Änderungen melden
            bSend = true;
          }
          else if ((millis() - previousStateMillis[i]) > MIN_REPORT_INTERVAL) {
            bSend = true;
          }
      
          if (bSend) {
            // TODO: Check if we want to look at the return value of send
            send(msgStatus.setSensor(i + MAX_ATTACHED_DS18B20).set(relState[i] ? 1 : 0));
            previousStateMillis[i] = millis();
            relayStateReported[i] = relState[i];
          }
      #endif
        }
      }
      
      void updateDisp() {
        static char s[8];
        static unsigned long previousDisplayMillis = 0;
      
        // update display?
        if ((millis() - previousDisplayMillis) > 1000) {
          previousDisplayMillis = millis();
          displayInfo++;
      #ifdef NO_MYS
          if (displayInfo > 4) displayInfo = 0;
      #else
          if (displayInfo > 5) displayInfo = 0;
      #endif
      
      
      
          ledModule.clearDisplay();
          // sind die Temperaturen schon gelesen worden? Wenn nicht, abbrechen.
          if (lastTemperature[0] == -100) return;
      
          for (byte i = 0; i <= 7; i++) {
            if (ledState[i])
              ledModule.setLED(1, i);
            else
              ledModule.setLED(0, i);
          }
      
          // 0 Vorlauf, 1 Rücklauf, 2 Zulauf, 3 Sollwert
          switch (displayInfo) {
            case 0: // Vorlauf
              sprintf(s, "Vor %2i",  lastTemperature[VORLAUF]);
              break;
            case 1: // Rücklauf
              sprintf(s, "Rue %2i",  lastTemperature[RUECKLAUF]);
              break;
            case 2: // Zulauf
              sprintf(s, "Zu %2i",  lastTemperature[ZULAUF]);
              break;
            case 3: // Zielwert
              sprintf(s, "Soll %2i",  targetTemp);
              break;
            case 4: // Power
              if (powerOn) {
                sprintf(s, "PowerOn");
              }
              else {
                sprintf(s, "PowerOff");
              }
              break;
      #ifndef NO_MYS
            case 5: //clock
              TimeChangeRule *tcr;
              time_t utc = millis();
              time_t t = CE.toLocal(utc, &tcr);
              unsigned long dispTime = hour(t) * 100 * 100 + minute(t) * 100 + second(t);
              if (hour(t) < 10) {
                sprintf(s, "U 0%lu",  dispTime);
              }
              else
              {
                sprintf(s, "U %lu",  dispTime);
              }
              break;
      #endif
          }
      
          ledModule.setDisplayToString(s);
        }
      }
      
      void closeMischer() {
        relState[PUMPE] = false;
        relState[MISCHER_ZU] = true;
        relState[MISCHER_AUF] = false;
        updateRelays();
        ledState[PUMPE] = relState[PUMPE];
        ledState[MISCHER_ZU] = relState[MISCHER_ZU];
        ledState[MISCHER_AUF] = relState[MISCHER_AUF];
      }
      
      
      #ifndef NO_MYS
      
      void before()
      {
      
      }
      
      // 0 Vorlauf, 1 Rücklauf, 2 Zulauf
      void presentation() {
        // Send the sketch version information to the gateway and Controller
        sendSketchInfo("Mischer Node", "1.1");
      
        // Fetch the number of attached temperature sensors
        numSensors = sensors.getDeviceCount();
        DEBUG_PRINT(F("Numsensors "));
        DEBUG_PRINTLN(numSensors);
      
        // Present all sensors to controller
        for (int i = 0; i < numSensors && i < MAX_ATTACHED_DS18B20; i++) {
          present(i, S_TEMP);
        }
      
        for (int sensor = MAX_ATTACHED_DS18B20, pin = RELAY_1; sensor < (MAX_ATTACHED_DS18B20 + NUMBER_OF_RELAYS); sensor++, pin++) {
          // Register all sensors to gw (they will be created as child devices)
          present(sensor, S_BINARY);
        }
      
        // Thermopunkt
        present(MAX_ATTACHED_DS18B20 + NUMBER_OF_RELAYS, S_HVAC);
      
        // Power
        present(MAX_ATTACHED_DS18B20 + NUMBER_OF_RELAYS + 1, S_HEATER);
      
      #ifndef NO_AC_DETECT
        // AC Buderuus
        present(MAX_ATTACHED_DS18B20 + NUMBER_OF_RELAYS + 2, S_BINATY);
      #endif
      
        // Power
        present(MAX_ATTACHED_DS18B20 + NUMBER_OF_RELAYS + 3, S_INFO);
      
        // Initialltemperatur mitteilen
        send(msgTargetTemp.set(targetTemp));
        send(msgPower.set(powerOn));
      }
      
      
      void receive(const MyMessage & message)
      {
        DEBUG_PRINT(F("Meldung an Sensor "));
        DEBUG_PRINT(message.sensor);
        DEBUG_PRINT(F(" mit type "));
        DEBUG_PRINTLN(message.type);
        if (message.type == V_STATUS) {
          int i = message.sensor - MAX_ATTACHED_DS18B20;
          if ((i >= 0) && (i < NUMBER_OF_RELAYS)) {
            DEBUG_PRINT(F("Schaltbefehl für Relay "));
            DEBUG_PRINT(i);
            DEBUG_PRINT(F(" auf "));
            DEBUG_PRINTLN(message.getBool());
            relState[i] = message.getBool();
            ledState[i] = relState[i];
          }
          else if (i == MAX_ATTACHED_DS18B20 + NUMBER_OF_RELAYS + 1) {
            powerOn = message.getBool();
            send(msgPower.set(powerOn));
      
            if (powerOn)
              EEPROM.write(EEPROM_POWERSTATE, 1);
            else
              EEPROM.write(EEPROM_POWERSTATE, 0);
      
            if (powerOn == false) {
              relState[PUMPE] = false;
              ledState[POWERLED] = false;
              DEBUG_PRINTLN(F("Befehl für PowerOff"));
            }
            else {
              ledState[POWERLED] = true;
              DEBUG_PRINTLN(F("Befehl für PowerOn"));
            }
          }
      
          updateRelays();
        }
        else if (message.type == V_HVAC_SETPOINT_HEAT) {
          if (message.sensor == (MAX_ATTACHED_DS18B20 + NUMBER_OF_RELAYS)) {
            targetTemp = message.getInt();
            DEBUG_PRINT(F("Zieltemp gesetzt auf "));
            DEBUG_PRINTLN(targetTemp);
            if (targetTemp > MAX_TEMP) targetTemp = START_TARGET_TEMP;
            if (targetTemp < 20) targetTemp = 20;
            send(msgTargetTemp.set(targetTemp));
          }
        }
      }
      
      // This is called when a new time value was received
      void receiveTime(unsigned long controllerTime) {
        // Ok, set incoming time
        DEBUG_PRINT(F("Time value received: "));
        DEBUG_PRINTLN(controllerTime);
      #ifndef NO_RTC
        RTC.set(controllerTime);
        else
          setTime(controllerTime);
      #endif
        timeReceived = true;
      }
      
      void sendTemp() {
        static int lastSentTemperature[MAX_ATTACHED_DS18B20] = { -100, -100, -100 };
        static unsigned long previousTempMillis[MAX_ATTACHED_DS18B20] = { 0, 0, 0 };
      
        for (int i = 0; i < MAX_ATTACHED_DS18B20 ; i++) {
          if (lastTemperature[i] != -100) {
            bool bSend = false;
            if (previousTempMillis[i] == 0) {                               // noch nie gesendet
              bSend = true;
            }
            else if (abs(lastSentTemperature[i] - lastTemperature[i]) >= 2) { // Differenz von >= zwei Grad werden gemeldet
              bSend = true;
            }
            else if ((millis() - previousTempMillis[i]) > MIN_REPORT_INTERVAL) {
              bSend = true;
            }
      
            if (bSend) {
              // TODO: Check if we want to look at the return value of send
              send(msgTemp.setSensor(i).set(lastTemperature[i], 1));
              previousTempMillis[i] = millis();
              lastSentTemperature[i] = lastTemperature[i];
            }
          }
        }
      }
      #endif
      
      #ifndef NO_AC_DETECT
      void buderusSet() {
        testStateBuderus = true;
      }
      #endif
      
      void reboot() {
        wdt_enable(WDTO_30MS);
        while (1) {};
      }
      
      void readButtons() {
      
        byte buttons = ledModule.getButtons();
      
        if (lastButtonState != buttons) {
          lastButtonState = buttons;
          if (buttons > 0) {
            for (byte i = 0; i <= 7; i++) {
              if ((buttons >> i) & 1) {
                ledState[i] = !ledState[i];
                if (i < NUMBER_OF_RELAYS) relState[i] = ledState[i];
                updateRelays();
                if (ledState[i])
                  ledModule.setLED(1, i);
                else
                  ledModule.setLED(0, i);
                if (i == POWERLED) {
                  powerOn = ledState[i];
      #ifndef NO_MYS
                  send(msgPower.set(powerOn));
      #endif
                  if (powerOn)
                    EEPROM.write(EEPROM_POWERSTATE, 1);
                  else
                    EEPROM.write(EEPROM_POWERSTATE, 0);
      
                }
      
                if (i == TEMPDOWNLED) {
                  targetTemp--;
                  if (targetTemp < 30) targetTemp = 30;
                  EEPROM.write(EEPROM_TARGET_TEMP, targetTemp);
                }
                if (i == TEMPUPLED) {
                  targetTemp++;
                  if (targetTemp > MAX_TEMP) targetTemp = MAX_TEMP;
                  EEPROM.write(EEPROM_TARGET_TEMP, targetTemp);
                }
                DEBUG_PRINT(F("ButtonState changed "));
                DEBUG_PRINTLN(i);
              }
            }
          }
        }
      }
      
      void readTemp() {
        static float temp;
        static int tempi;
        static unsigned long lastReadTemp1 = -1;
        static unsigned long lastReadTemp2 = -1;
        static unsigned long lastReadTemp3 = -1;
      
        if (previousTempMillis == -1) {
          // Dallas
          sensors.requestTemperatures();
          previousTempMillis = nowms;
        }
      
        if ((millis() - previousTempMillis) >= conversionTime) {
          temp = sensors.getTempC(Probe01);
          if (temp != DEVICE_DISCONNECTED_C) {
            tempi = (int)(temp + 0.5);
            lastTemperature[VORLAUF] = tempi;
            lastReadTemp1 = nowms;
          }
      
          if (temp != DEVICE_DISCONNECTED_C) {
            temp = sensors.getTempC(Probe02);
            tempi = (int)(temp + 0.5);
            lastTemperature[RUECKLAUF] = tempi;
            lastReadTemp2 = nowms;
          }
      
          if (temp != DEVICE_DISCONNECTED_C) {
            temp = sensors.getTempC(Probe03);
            tempi = (int)(temp + 0.5);
            lastTemperature[ZULAUF] = tempi;
            lastReadTemp3 = nowms;
          }
      
          if (nowms > 60000) {    // ersta nach einer minute prüfen
            unsigned long compare = nowms - 60000; // vor 1 Minute
            if ((lastReadTemp1 < compare) || (lastReadTemp2 < compare) || (lastReadTemp3 < compare)) {
              DEBUG_PRINTLN(F("Zu lange keine Temeratur -> reset"));
              lastReadTemp1 = -1;
              lastReadTemp2 = -1;
              lastReadTemp3 = -1;
              ledModule.setDisplayToString(F("ERR Reb"));
              delay(1000);
              reboot();
            }
          }
      
          previousTempMillis = -1;
        }
      
      #ifdef SIMULATE_VOR
        lastTemperature[VORLAUF] = SIMULATE_VOR;
        lastReadTemp1 = nowms;
      #endif
      
      #ifdef SIMULATE_RUE
        lastTemperature[RUECKLAUF] = SIMULATE_RUE;
        lastReadTemp3 = nowms;
      #endif
      
      #ifdef SIMULATE_ZU
        lastTemperature[ZULAUF] = SIMULATE_ZU;
        lastReadTemp3 = nowms;
      #endif
      }
      
      #ifndef NO_AC_DETECT
      
      void readAC() {
        if ((millis() - lastResetBuderus) > 100) // bei 50 HZ sind das 25 Durchgänge...
        {
          noInterrupts();
          if (testStateBuderus != powerOnBuderus ) {
            powerOnBuderus = testStateBuderus;
            testStateBuderus = false;
            interrupts();
            ledState[BUDERUSLED] = powerOnBuderus;
      #ifndef NO_MYS
            send(msgPowerBuderus.set(powerOnBuderus));
      #endif
          }
          lastResetBuderus = nowms;
          testStateBuderus = false;
        } else
          interrupts();
      }
      #endif
      
      raptorjrR 1 Reply Last reply
      1
      • FotoFieberF FotoFieber

        I had a similar problem with the control of a heating mixer and three dallas sensors. I solved the problem with:

        • replacement of the relays with solid state relays
        • moved conttroller more in distance of the high voltage parts

        I also added a watchdog and a reboot mechanism in case of sensor problems.

        Maybe this sketch can give you an inspiration: (work in progress, MYS-Part not tested in depth)

        // Enable debug prints to serial monitor
        //#define MY_DEBUG      // in Mysensors
        #define EN_DEBUG      // in this sketch
        //#define NO_MYS      // ohne Mysensors Unterstützung?
        //#define SIMULATION
        #define NO_AC_DETECT  // ohne AC sensor
        #define NO_RTC
        
        // RTC nur zusammen mit Mysensors
        #ifdef NO_MYS
        #ifndef NO_RTC
        #define NO_RTC
        #endif
        #endif
        
        #ifdef SIMULATION
        // für Simulation ohne Sensoren
        #define SIMULATE_VOR  35
        #define SIMULATE_RUE  30
        #define SIMULATE_ZU   65
        
        
        #ifdef SIMULATE_VOR
        #warning Achtung! Keine Echtentemperaturmessungen -> Simulation
        #endif
        
        #ifdef SIMULATE_RUE
        #warning Achtung! Keine Echtentemperaturmessungen -> Simulation
        #endif
        
        #ifdef SIMULATE_ZU
        #warning Achtung! Keine Echtentemperaturmessungen -> Simulation
        #endif
        #endif
        
        
        #define START_TARGET_TEMP 40
        #define EEPROM_TARGET_TEMP 900            // Save Porisiton. It is above the Mysensors range of lib 2.1
        #define EEPROM_POWERSTATE  902            // Save Porisiton. It is above the Mysensors range of lib 2.1
        #define MAX_TEMP 50
        #define VORLAUF 0
        #define RUECKLAUF 1
        #define ZULAUF 2
        #define PUMPE 0
        #define MISCHER_ZU  1
        #define MISCHER_AUF 2
        #define POWERLED 7                        // 8. LED
        #define TEMPDOWNLED 5                     // 6. Led
        #define TEMPUPLED 6                       // 7. Led
        #ifndef NO_AC_DETECT
        #define BUDERUSLED 4                      // 5. Led
        #endif
        #define RETRY_TIMEOUT_PUMP 2*60*1000UL     // alle 2 Minuten testen, ob Vorlauf nicht besser (5 Sekunden pumpen)
        #define REGULATION_TIMEOUT_PUMP 30*1000UL  // 30 Sekunden warten nach neuer Einstellung
        #define PROBE_TIMEOUT_PUMP 5*1000UL        // 5 Sekunden Pumpe für Test einschalten, wenn Zulauf zu kalt
        #define BUDERUS_PIN 3                     // Buderus Powererkennung Pumpe auf PIN 3
        
        #ifndef SIMULATION
        #define MISCHER_RESET_TIME 120*1000UL      // 2 Minuten bis Nullstellung
        #else
        #define MISCHER_RESET_TIME 10*1000UL      // 10 Sekunden bis Nullstellung nei Simulation
        #endif
        
        
        
        #ifdef EN_DEBUG
        #define DEBUG_PRINT(x) Serial.print (x)
        #else
        #define DEBUG_PRINT(x)
        #endif
        
        #ifdef EN_DEBUG
        #define DEBUG_PRINTLN(x)  Serial.println (x)
        #else
        #define DEBUG_PRINTLN(x)
        #endif
        
        #ifndef NO_MYS
        // Radio Configuration
        #define MY_TRANSPORT_WAIT_READY_MS (10000ul)
        #define MY_RADIO_RFM69
        #define MY_RFM69_FREQUENCY RF69_868MHZ
        #define MY_RFM69_NETWORKID 13
        #define MY_RFM69_ENABLE_ENCRYPTION
        #define MY_NODE_ID 168
        //#define MY_IS_RFM69HW
        #endif
        
        
        #include <Arduino.h>
        #include <avr/wdt.h>
        #include <EEPROM.h>
        #include <MemoryFree.h>
        
        #ifndef NO_MYS
        #define MIN_REPORT_INTERVAL  5 * 60 * 1000L   // mindestens alle 5 Minuten melden
        #include <SPI.h>
        #include <MySensors.h>
        #include <Time.h>        //http://www.arduino.cc/playground/Code/Time
        #include <Timezone.h>    //https://github.com/JChristensen/Timezone
        #include <TimeLib.h>
        
        //Central European Time (Frankfurt, Paris)
        TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120};     //Central European Summer Time
        TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60};       //Central European Standard Time
        Timezone CE(CEST, CET);
        bool timeReceived = false;
        unsigned long lastUpdate = 0, lastRequest = 0;
        #endif
        
        #ifndef NO_RTC
        #include <DS3232RTC.h>  // A  DS3231/DS3232 library
        #endif
        
        #define MAX_LEDS 8
        
        byte lastButtonState = 0;
        #define RELAY_ON HIGH
        #define RELAY_OFF LOW
        #define RELAY_1  A0         // Arduino Digital I/O pin number for first relay (second on pin+1 etc)
        #define NUMBER_OF_RELAYS 4  // Total number of attached relays
        
        
        boolean ledState[MAX_LEDS] = { false, false, false, false, false, false, false, false };
        boolean relState[NUMBER_OF_RELAYS] = { false, false, false, false };
        
        #include <TM1638.h>
        #include <DallasTemperature.h>
        #include <OneWire.h>
        
        #define ONE_WIRE_BUS 6 // Pin where dallase sensor is connected 
        #define TEMPERATURE_PRECISION 9
        #define MAX_ATTACHED_DS18B20 3
        
        //28B404080000804A
        DeviceAddress Probe01 = { 0x28, 0xB4, 0x04, 0x08, 0x00, 0x00, 0x80, 0x4A };
        //28C606080000803F
        DeviceAddress Probe02 = { 0x28, 0xC6, 0x06, 0x08, 0x00, 0x00, 0x80, 0x3F };
        //28750808000080C3
        DeviceAddress Probe03 = { 0x28, 0x75, 0x08, 0x08, 0x00, 0x00, 0x80, 0xC3 };
        
        OneWire oneWire(ONE_WIRE_BUS); // Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
        DallasTemperature sensors(&oneWire); // Pass the oneWire reference to Dallas Temperature.
        int numSensors = 0;
        int lastTemperature[MAX_ATTACHED_DS18B20] = { -100, -100, -100 };
        int16_t conversionTime;
        unsigned long previousTempMillis = -1;
        static unsigned long nowms = millis();    // update at start of loop()
        
        
        // define LCD module
        TM1638 ledModule(8, 9, 7);
        
        int displayInfo = -1;
        unsigned long previousDisplayMillis = 0;
        
        int targetTemp = START_TARGET_TEMP;
        #ifdef SIMULATION
        bool powerOn = true;
        #ifndef NO_AC_DETECT
        bool powerOnBuderus = true;
        #endif
        #else
        bool powerOn = false;
        #ifndef NO_AC_DETECT
        bool powerOnBuderus = false;
        #endif
        #endif
        bool resetMischer = true; // mischer zuerst in Nullstellung
        
        #ifndef NO_AC_DETECT
        bool testStateBuderus = false;
        unsigned long lastResetBuderus = -1;
        #endif
        
        #ifndef NO_MYS
        // Initialize messages
        MyMessage msgTemp(0, V_TEMP);
        MyMessage msgStatus(0, V_STATUS);
        MyMessage msgTargetTemp(MAX_ATTACHED_DS18B20 + NUMBER_OF_RELAYS, V_HVAC_SETPOINT_HEAT);
        MyMessage msgPower(MAX_ATTACHED_DS18B20 + NUMBER_OF_RELAYS + 1, V_STATUS);
        #ifndef NO_AC_DETECT
        MyMessage msgPowerBuderus(MAX_ATTACHED_DS18B20 + NUMBER_OF_RELAYS + 2, V_STATUS);
        #endif
        MyMessage msgDebug(MAX_ATTACHED_DS18B20 + NUMBER_OF_RELAYS + 3, V_TEXT);
        #endif
        
        // the setup function runs once when you press reset or power the board
        void setup() {
          DEBUG_PRINTLN(F("Starte setup"));
          wdt_enable(WDTO_8S);
          Serial.begin(115200);
        
        #ifndef NO_RTC
          // the function to get the time from the RTC
          setSyncProvider(RTC.get);
        #endif
        #ifndef NO_MYS
          // Request latest time from controller at startup
          requestTime();
        #endif
        
        
        
        
          // Zieltemperatur aus EERPOM lesem:
          targetTemp = EEPROM.read(EEPROM_TARGET_TEMP);
          if ((targetTemp < 30) || (targetTemp > MAX_TEMP)) {
            targetTemp = START_TARGET_TEMP;
            EEPROM.write(EEPROM_TARGET_TEMP, targetTemp);
          }
        
          int state = EEPROM.read(EEPROM_POWERSTATE);
          if (state == 0) {
            powerOn = false;
          }
          else powerOn = true;
        
        #ifndef NO_AC_DETECT
          // Buderuserkennung auf PIN3
          attachInterrupt(digitalPinToInterrupt(BUDERUS_PIN), buderusSet, CHANGE);
          testStateBuderus = false;
          lastResetBuderus = millis();
        #ifndef NO_MYS
          send(msgPowerBuderus.set(powerOnBuderus));
        #endif
        #endif
        
          // Relayausgänge initialisiern
          for (int sensor = 1, pin = RELAY_1; sensor <= NUMBER_OF_RELAYS; sensor++, pin++) {
            // Then set relay pins in output mode
            pinMode(pin, OUTPUT);
            // Set relay to last known state (using eeprom storage)
            digitalWrite(pin, RELAY_OFF);
          }
        
          // Dallas Temperatursensoren
          sensors.begin();
          sensors.setWaitForConversion(false);
          numSensors = sensors.getDeviceCount();
          DEBUG_PRINT(F("Dallas Sensoren "));
          DEBUG_PRINTLN(numSensors);
          DeviceAddress tempDeviceAddress; // We'll use this variable to store a found device address
        
          for (int i = 0; i < numSensors; i++)
          {
            wdt_reset();
            // Search the wire for address
            if (sensors.getAddress(tempDeviceAddress, i))
            {
              DEBUG_PRINT(F("Found device "));
        #ifdef EN_DEBUG
              Serial.print(i, DEC);
        #endif
              DEBUG_PRINT(F(" with address: "));
              printAddress(tempDeviceAddress);
              DEBUG_PRINTLN();
        
              DEBUG_PRINT(F("Setting resolution to "));
        #ifdef EN_DEBUG
              Serial.println(TEMPERATURE_PRECISION, DEC);
        #endif
              // set the resolution to 12 bit (Each Dallas/Maxim device is capable of several different resolutions)
              sensors.setResolution(tempDeviceAddress, TEMPERATURE_PRECISION);
        
              DEBUG_PRINT(F("Resolution actually set to: "));
        
        #ifdef EN_DEBUG
              Serial.print(sensors.getResolution(tempDeviceAddress), DEC);
        #endif
              DEBUG_PRINTLN();
            } else {
              DEBUG_PRINT(F("Found ghost device at "));
        
        #ifdef EN_DEBUG
              Serial.print(i, DEC);
        #endif
              DEBUG_PRINT(F(" but could not detect address. Check power and cabling"));
            }
          }
        
          wdt_reset();
          sensors.requestTemperatures();
        
          // query conversion time and sleep until conversion completed
          conversionTime = sensors.millisToWaitForConversion(sensors.getResolution());
        }
        
        /*****************************************************/
        // the loop function runs over and over again forever
        void loop() {
          nowms = millis();
        
        #ifndef NO_MYS
          // If no time has been received yet, request it every 10 second from controller
          // When time has been received, request update every hour
          if ((!timeReceived && (nowms - lastRequest) > (10UL * 1000UL))
              || (timeReceived && (nowms - lastRequest) > (60UL * 1000UL * 60UL))) {
            // Request time from controller.
            DEBUG_PRINTLN("requesting time");
            requestTime();
            lastRequest = nowms;
          }
        #endif
        
          wdt_reset();
        
        #ifndef NO_AC_DETECT
          readAC();
        #endif
        
          readTemp();
          readButtons();
        
          updateDisp();
        
          // Temperaturen senden
        
        #ifndef NO_MYS
          sendTemp();
        #endif
        
          // Kontroller
          control();
        
          // Loop 2x pro Sekunde ist aureichend
          unsigned long loopTime = millis() - nowms;
          if (loopTime < 450) {
        #ifdef NO_MYS
            delay (500 - loopTime);
        #else
            wait (500 - loopTime);
        #endif
          }
        }
        // end loop
        
        /**************************************************************/
        // Steuerung
        void control()
        {
        
          wdt_reset();
        
          static unsigned long lastPumpTest = 0;
          static unsigned long lastRegulation = -REGULATION_TIMEOUT_PUMP;
          static unsigned long resetStart = 0;                     // wann wurde die Rücksetzung des Mischers gestartet
          static bool lastPower = powerOn;
          static float moveSeconds = 0.0;                 // Zeiten für Stellmotor Mischer
        
          // controllerState
          // 0 initialized
          // 1 Pumpe für kurzen test aktiviert, warten auf Timeout für Deaktivierung
          // 2 in der Regelung, warten auf Timeout für Deaktivierung
          // 3 in Ausganglage fahren
        
          static int controllerState = 0;
        
          // Emergency
          if  (lastTemperature[VORLAUF] > MAX_TEMP) {
            if (resetStart == 0) {
              if (relState[PUMPE]) {
                DEBUG_PRINT(F("Zu heiss. Emergency Mischer schliessen"));
                DEBUG_PRINTLN(lastTemperature[VORLAUF]);
              }
              closeMischer();
              controllerState = 0;
              return;
            }
          }
        
        
          // Temperatur noch nicht gelesen;
          if (lastTemperature[VORLAUF] == -100) return;
        
          // Fehler mit den Sensoren
          if ((lastTemperature[VORLAUF] < 0) ||
              (lastTemperature[VORLAUF] > 100) ||
              (lastTemperature[RUECKLAUF] < 0) ||
              (lastTemperature[RUECKLAUF] > 100) ||
              (lastTemperature[ZULAUF] < 0) ||
              (lastTemperature[ZULAUF] > 100)) {
            relState[PUMPE] = false;
            updateRelays();
            ledState[PUMPE] = relState[PUMPE];
            // Test
            //powerOn = false;
        #ifndef NO_MYS
            //send(msgPower.set(powerOn));
        #endif
        
            controllerState = 0;
            DEBUG_PRINTLN(F("Fehler mit den Sensoren, Temperaturen unter 0 oder über 100. Poweroff!"));
            return;
          }
        
          if (resetMischer) {
            DEBUG_PRINTLN(F("Mischer initialisieren"));
            closeMischer();
            resetStart = millis();
            resetMischer = false;
            controllerState = 0;
            return;
          }
        
          if (resetStart > 0) {
            if ((millis() - resetStart) > MISCHER_RESET_TIME) {
              DEBUG_PRINTLN(F("Mischer fertig initialisiert"));
              relState[MISCHER_ZU] = false;
              updateRelays();
              ledState[PUMPE] = relState[PUMPE];
              ledState[MISCHER_ZU] = relState[MISCHER_ZU];
              resetStart = 0;
              controllerState = 0;
            }
            return;
          }
        
          // wenn ausser Betrieb -> verlassen;
          if (powerOn == false) {
            if (lastPower == false) return;
        
            DEBUG_PRINTLN("Go to power off state");
        
        #ifndef NO_MYS
            send(msgPower.set(powerOn));
        #endif
        
            lastPower = false;
            relState[PUMPE] = false;
            relState[MISCHER_ZU] = false;
            relState[MISCHER_AUF] = false;
            updateRelays();
            ledState[PUMPE] = relState[PUMPE];
            ledState[MISCHER_ZU] = relState[MISCHER_ZU];
            ledState[MISCHER_AUF] = relState[MISCHER_AUF];
            controllerState = 0;
            EEPROM.write(EEPROM_POWERSTATE, 0);
            return;
          }
          //DEBUG_PRINTLN("a");
          lastPower = true;
        
          switch (controllerState) {
            case  0:  // Initialisiert
              if (lastTemperature[ZULAUF] < 30) {
                if ((millis() - lastPumpTest) > RETRY_TIMEOUT_PUMP) {
                  controllerState = 1;
                  lastPumpTest = millis();
                  DEBUG_PRINTLN(F("Zulauf zu kalt. Schalte Pumpe für 5 Sekunden ein."));
                  relState[PUMPE] = true;
                  updateRelays();
                  ledState[PUMPE] = relState[PUMPE];
                }
                return;
              }
              //DEBUG_PRINTLN("x");
        
              relState[PUMPE] = true;
              updateRelays();
              ledState[PUMPE] = relState[PUMPE];
        
        
              // nicht regulieren, wenn schon genau genug
              if (abs(lastTemperature[VORLAUF] - targetTemp) <= 1) {
                // DEBUG_PRINTLN("diff zu klein, keine Steuerung notwendig");
                return;
              }
        
        
              if ((millis() - lastRegulation) > REGULATION_TIMEOUT_PUMP) {
                controllerState = 2;
                lastRegulation = millis();
        
                int diffZualaufRuecklauf = lastTemperature[ZULAUF] - lastTemperature[RUECKLAUF];
        
                float actPercent = (float)(lastTemperature[VORLAUF] - lastTemperature[RUECKLAUF]) * 100.0 / (float)diffZualaufRuecklauf;
                float shoulPercent = (float)(targetTemp - lastTemperature[RUECKLAUF]) * 100.0 / (float)diffZualaufRuecklauf;
        
                // 120 Sekunden für ganzen Weg
                moveSeconds = (float)MISCHER_RESET_TIME / 1000.0 / 100.0 * abs(shoulPercent - actPercent);
                DEBUG_PRINT(F("Berechnete Sekunden fuer Mischerumstellung "));
                DEBUG_PRINTLN(moveSeconds);
                if (moveSeconds > 8)
                  moveSeconds = 8;
        
                if (targetTemp > lastTemperature[VORLAUF]) {
                  DEBUG_PRINTLN(F("Mischer auf"));
                  relState[MISCHER_ZU] = false;
                  relState[MISCHER_AUF] = true;
                  updateRelays();
                  ledState[MISCHER_ZU] = relState[MISCHER_ZU];
                  ledState[MISCHER_AUF] = relState[MISCHER_AUF];
                }
                else
                {
                  DEBUG_PRINTLN(F("Mischer zu"));
                  relState[MISCHER_ZU] = true;
                  relState[MISCHER_AUF] = false;
                  updateRelays();
                  ledState[MISCHER_ZU] = relState[MISCHER_ZU];
                  ledState[MISCHER_AUF] = relState[MISCHER_AUF];
                }
                return;
              }
              return;
              break;
            case  1: // in Testmodus bei zu wenig  Temp im Zulauf
              if ((millis() - lastPumpTest) > PROBE_TIMEOUT_PUMP) {
                relState[PUMPE] = false;
                updateRelays();
                ledState[PUMPE] = relState[PUMPE];
                DEBUG_PRINTLN(F("Pumpe ausgeschaltet"));
                controllerState = 0;
                lastPumpTest = millis();
              }
              return;
              break;
            case  2: // in der Regelung
              if ((millis() - lastRegulation) > moveSeconds * 1000.0) {
                DEBUG_PRINTLN("Mischer zu.");
                relState[MISCHER_ZU] = false;
                relState[MISCHER_AUF] = false;
                updateRelays();
                ledState[MISCHER_ZU] = relState[MISCHER_ZU];
                ledState[MISCHER_AUF] = relState[MISCHER_AUF];
                lastRegulation = millis();
                controllerState = 0;
              }
              return;
              break;
            default:
              DEBUG_PRINT(F("ERROR, unknown state :"));
              DEBUG_PRINTLN(controllerState);
              controllerState = 0;
              return;
          }
        }
        
        // function to print a device address
        void printAddress(DeviceAddress deviceAddress)
        {
          for (uint8_t i = 0; i < 8; i++)
          {
            if (deviceAddress[i] < 16) DEBUG_PRINT("0");
        
        #ifdef EN_DEBUG
            Serial.print(deviceAddress[i], HEX);
        #endif
          }
        }
        
        void updateRelays() {
          static boolean relayStateReported[8] = { false, false, false, false, false, false, false, false };
        #ifndef NO_MYS
          static unsigned long previousStateMillis[NUMBER_OF_RELAYS] = { 0, 0, 0, 0 };
        #endif
        
          for (int i = 0; i < NUMBER_OF_RELAYS; i++) {
            if (relState[i]) {
              digitalWrite(RELAY_1 + i, RELAY_ON);
            }
            else
            {
              digitalWrite(RELAY_1 + i, RELAY_OFF);
            }
        
        #ifndef NO_MYS
            bool bSend = false;
            if (previousStateMillis[i] == 0) {                               // noch nie gesendet
              bSend = true;
            }
            else if (relayStateReported[i] != relState[i] ) { // Änderungen melden
              bSend = true;
            }
            else if ((millis() - previousStateMillis[i]) > MIN_REPORT_INTERVAL) {
              bSend = true;
            }
        
            if (bSend) {
              // TODO: Check if we want to look at the return value of send
              send(msgStatus.setSensor(i + MAX_ATTACHED_DS18B20).set(relState[i] ? 1 : 0));
              previousStateMillis[i] = millis();
              relayStateReported[i] = relState[i];
            }
        #endif
          }
        }
        
        void updateDisp() {
          static char s[8];
          static unsigned long previousDisplayMillis = 0;
        
          // update display?
          if ((millis() - previousDisplayMillis) > 1000) {
            previousDisplayMillis = millis();
            displayInfo++;
        #ifdef NO_MYS
            if (displayInfo > 4) displayInfo = 0;
        #else
            if (displayInfo > 5) displayInfo = 0;
        #endif
        
        
        
            ledModule.clearDisplay();
            // sind die Temperaturen schon gelesen worden? Wenn nicht, abbrechen.
            if (lastTemperature[0] == -100) return;
        
            for (byte i = 0; i <= 7; i++) {
              if (ledState[i])
                ledModule.setLED(1, i);
              else
                ledModule.setLED(0, i);
            }
        
            // 0 Vorlauf, 1 Rücklauf, 2 Zulauf, 3 Sollwert
            switch (displayInfo) {
              case 0: // Vorlauf
                sprintf(s, "Vor %2i",  lastTemperature[VORLAUF]);
                break;
              case 1: // Rücklauf
                sprintf(s, "Rue %2i",  lastTemperature[RUECKLAUF]);
                break;
              case 2: // Zulauf
                sprintf(s, "Zu %2i",  lastTemperature[ZULAUF]);
                break;
              case 3: // Zielwert
                sprintf(s, "Soll %2i",  targetTemp);
                break;
              case 4: // Power
                if (powerOn) {
                  sprintf(s, "PowerOn");
                }
                else {
                  sprintf(s, "PowerOff");
                }
                break;
        #ifndef NO_MYS
              case 5: //clock
                TimeChangeRule *tcr;
                time_t utc = millis();
                time_t t = CE.toLocal(utc, &tcr);
                unsigned long dispTime = hour(t) * 100 * 100 + minute(t) * 100 + second(t);
                if (hour(t) < 10) {
                  sprintf(s, "U 0%lu",  dispTime);
                }
                else
                {
                  sprintf(s, "U %lu",  dispTime);
                }
                break;
        #endif
            }
        
            ledModule.setDisplayToString(s);
          }
        }
        
        void closeMischer() {
          relState[PUMPE] = false;
          relState[MISCHER_ZU] = true;
          relState[MISCHER_AUF] = false;
          updateRelays();
          ledState[PUMPE] = relState[PUMPE];
          ledState[MISCHER_ZU] = relState[MISCHER_ZU];
          ledState[MISCHER_AUF] = relState[MISCHER_AUF];
        }
        
        
        #ifndef NO_MYS
        
        void before()
        {
        
        }
        
        // 0 Vorlauf, 1 Rücklauf, 2 Zulauf
        void presentation() {
          // Send the sketch version information to the gateway and Controller
          sendSketchInfo("Mischer Node", "1.1");
        
          // Fetch the number of attached temperature sensors
          numSensors = sensors.getDeviceCount();
          DEBUG_PRINT(F("Numsensors "));
          DEBUG_PRINTLN(numSensors);
        
          // Present all sensors to controller
          for (int i = 0; i < numSensors && i < MAX_ATTACHED_DS18B20; i++) {
            present(i, S_TEMP);
          }
        
          for (int sensor = MAX_ATTACHED_DS18B20, pin = RELAY_1; sensor < (MAX_ATTACHED_DS18B20 + NUMBER_OF_RELAYS); sensor++, pin++) {
            // Register all sensors to gw (they will be created as child devices)
            present(sensor, S_BINARY);
          }
        
          // Thermopunkt
          present(MAX_ATTACHED_DS18B20 + NUMBER_OF_RELAYS, S_HVAC);
        
          // Power
          present(MAX_ATTACHED_DS18B20 + NUMBER_OF_RELAYS + 1, S_HEATER);
        
        #ifndef NO_AC_DETECT
          // AC Buderuus
          present(MAX_ATTACHED_DS18B20 + NUMBER_OF_RELAYS + 2, S_BINATY);
        #endif
        
          // Power
          present(MAX_ATTACHED_DS18B20 + NUMBER_OF_RELAYS + 3, S_INFO);
        
          // Initialltemperatur mitteilen
          send(msgTargetTemp.set(targetTemp));
          send(msgPower.set(powerOn));
        }
        
        
        void receive(const MyMessage & message)
        {
          DEBUG_PRINT(F("Meldung an Sensor "));
          DEBUG_PRINT(message.sensor);
          DEBUG_PRINT(F(" mit type "));
          DEBUG_PRINTLN(message.type);
          if (message.type == V_STATUS) {
            int i = message.sensor - MAX_ATTACHED_DS18B20;
            if ((i >= 0) && (i < NUMBER_OF_RELAYS)) {
              DEBUG_PRINT(F("Schaltbefehl für Relay "));
              DEBUG_PRINT(i);
              DEBUG_PRINT(F(" auf "));
              DEBUG_PRINTLN(message.getBool());
              relState[i] = message.getBool();
              ledState[i] = relState[i];
            }
            else if (i == MAX_ATTACHED_DS18B20 + NUMBER_OF_RELAYS + 1) {
              powerOn = message.getBool();
              send(msgPower.set(powerOn));
        
              if (powerOn)
                EEPROM.write(EEPROM_POWERSTATE, 1);
              else
                EEPROM.write(EEPROM_POWERSTATE, 0);
        
              if (powerOn == false) {
                relState[PUMPE] = false;
                ledState[POWERLED] = false;
                DEBUG_PRINTLN(F("Befehl für PowerOff"));
              }
              else {
                ledState[POWERLED] = true;
                DEBUG_PRINTLN(F("Befehl für PowerOn"));
              }
            }
        
            updateRelays();
          }
          else if (message.type == V_HVAC_SETPOINT_HEAT) {
            if (message.sensor == (MAX_ATTACHED_DS18B20 + NUMBER_OF_RELAYS)) {
              targetTemp = message.getInt();
              DEBUG_PRINT(F("Zieltemp gesetzt auf "));
              DEBUG_PRINTLN(targetTemp);
              if (targetTemp > MAX_TEMP) targetTemp = START_TARGET_TEMP;
              if (targetTemp < 20) targetTemp = 20;
              send(msgTargetTemp.set(targetTemp));
            }
          }
        }
        
        // This is called when a new time value was received
        void receiveTime(unsigned long controllerTime) {
          // Ok, set incoming time
          DEBUG_PRINT(F("Time value received: "));
          DEBUG_PRINTLN(controllerTime);
        #ifndef NO_RTC
          RTC.set(controllerTime);
          else
            setTime(controllerTime);
        #endif
          timeReceived = true;
        }
        
        void sendTemp() {
          static int lastSentTemperature[MAX_ATTACHED_DS18B20] = { -100, -100, -100 };
          static unsigned long previousTempMillis[MAX_ATTACHED_DS18B20] = { 0, 0, 0 };
        
          for (int i = 0; i < MAX_ATTACHED_DS18B20 ; i++) {
            if (lastTemperature[i] != -100) {
              bool bSend = false;
              if (previousTempMillis[i] == 0) {                               // noch nie gesendet
                bSend = true;
              }
              else if (abs(lastSentTemperature[i] - lastTemperature[i]) >= 2) { // Differenz von >= zwei Grad werden gemeldet
                bSend = true;
              }
              else if ((millis() - previousTempMillis[i]) > MIN_REPORT_INTERVAL) {
                bSend = true;
              }
        
              if (bSend) {
                // TODO: Check if we want to look at the return value of send
                send(msgTemp.setSensor(i).set(lastTemperature[i], 1));
                previousTempMillis[i] = millis();
                lastSentTemperature[i] = lastTemperature[i];
              }
            }
          }
        }
        #endif
        
        #ifndef NO_AC_DETECT
        void buderusSet() {
          testStateBuderus = true;
        }
        #endif
        
        void reboot() {
          wdt_enable(WDTO_30MS);
          while (1) {};
        }
        
        void readButtons() {
        
          byte buttons = ledModule.getButtons();
        
          if (lastButtonState != buttons) {
            lastButtonState = buttons;
            if (buttons > 0) {
              for (byte i = 0; i <= 7; i++) {
                if ((buttons >> i) & 1) {
                  ledState[i] = !ledState[i];
                  if (i < NUMBER_OF_RELAYS) relState[i] = ledState[i];
                  updateRelays();
                  if (ledState[i])
                    ledModule.setLED(1, i);
                  else
                    ledModule.setLED(0, i);
                  if (i == POWERLED) {
                    powerOn = ledState[i];
        #ifndef NO_MYS
                    send(msgPower.set(powerOn));
        #endif
                    if (powerOn)
                      EEPROM.write(EEPROM_POWERSTATE, 1);
                    else
                      EEPROM.write(EEPROM_POWERSTATE, 0);
        
                  }
        
                  if (i == TEMPDOWNLED) {
                    targetTemp--;
                    if (targetTemp < 30) targetTemp = 30;
                    EEPROM.write(EEPROM_TARGET_TEMP, targetTemp);
                  }
                  if (i == TEMPUPLED) {
                    targetTemp++;
                    if (targetTemp > MAX_TEMP) targetTemp = MAX_TEMP;
                    EEPROM.write(EEPROM_TARGET_TEMP, targetTemp);
                  }
                  DEBUG_PRINT(F("ButtonState changed "));
                  DEBUG_PRINTLN(i);
                }
              }
            }
          }
        }
        
        void readTemp() {
          static float temp;
          static int tempi;
          static unsigned long lastReadTemp1 = -1;
          static unsigned long lastReadTemp2 = -1;
          static unsigned long lastReadTemp3 = -1;
        
          if (previousTempMillis == -1) {
            // Dallas
            sensors.requestTemperatures();
            previousTempMillis = nowms;
          }
        
          if ((millis() - previousTempMillis) >= conversionTime) {
            temp = sensors.getTempC(Probe01);
            if (temp != DEVICE_DISCONNECTED_C) {
              tempi = (int)(temp + 0.5);
              lastTemperature[VORLAUF] = tempi;
              lastReadTemp1 = nowms;
            }
        
            if (temp != DEVICE_DISCONNECTED_C) {
              temp = sensors.getTempC(Probe02);
              tempi = (int)(temp + 0.5);
              lastTemperature[RUECKLAUF] = tempi;
              lastReadTemp2 = nowms;
            }
        
            if (temp != DEVICE_DISCONNECTED_C) {
              temp = sensors.getTempC(Probe03);
              tempi = (int)(temp + 0.5);
              lastTemperature[ZULAUF] = tempi;
              lastReadTemp3 = nowms;
            }
        
            if (nowms > 60000) {    // ersta nach einer minute prüfen
              unsigned long compare = nowms - 60000; // vor 1 Minute
              if ((lastReadTemp1 < compare) || (lastReadTemp2 < compare) || (lastReadTemp3 < compare)) {
                DEBUG_PRINTLN(F("Zu lange keine Temeratur -> reset"));
                lastReadTemp1 = -1;
                lastReadTemp2 = -1;
                lastReadTemp3 = -1;
                ledModule.setDisplayToString(F("ERR Reb"));
                delay(1000);
                reboot();
              }
            }
        
            previousTempMillis = -1;
          }
        
        #ifdef SIMULATE_VOR
          lastTemperature[VORLAUF] = SIMULATE_VOR;
          lastReadTemp1 = nowms;
        #endif
        
        #ifdef SIMULATE_RUE
          lastTemperature[RUECKLAUF] = SIMULATE_RUE;
          lastReadTemp3 = nowms;
        #endif
        
        #ifdef SIMULATE_ZU
          lastTemperature[ZULAUF] = SIMULATE_ZU;
          lastReadTemp3 = nowms;
        #endif
        }
        
        #ifndef NO_AC_DETECT
        
        void readAC() {
          if ((millis() - lastResetBuderus) > 100) // bei 50 HZ sind das 25 Durchgänge...
          {
            noInterrupts();
            if (testStateBuderus != powerOnBuderus ) {
              powerOnBuderus = testStateBuderus;
              testStateBuderus = false;
              interrupts();
              ledState[BUDERUSLED] = powerOnBuderus;
        #ifndef NO_MYS
              send(msgPowerBuderus.set(powerOnBuderus));
        #endif
            }
            lastResetBuderus = nowms;
            testStateBuderus = false;
          } else
            interrupts();
        }
        #endif
        
        raptorjrR Offline
        raptorjrR Offline
        raptorjr
        wrote on last edited by
        #13

        @FotoFieber

        That was very advanced =) I don't have any high voltage involved. Only have a test setup on a breadboard.

        I tried to move the sensors.begin to the setup, but then I don't get any temperature readings?

        1 Reply Last reply
        0
        • gohanG Offline
          gohanG Offline
          gohan
          Mod
          wrote on last edited by
          #14

          Try post your code so we can take a look. Did you cross check the hw yet?

          raptorjrR 1 Reply Last reply
          0
          • gohanG gohan

            Try post your code so we can take a look. Did you cross check the hw yet?

            raptorjrR Offline
            raptorjrR Offline
            raptorjr
            wrote on last edited by
            #15

            @gohan

            It is the same sketch as my second post.

            Haven't done anything with the hardware yet. Was following Fabiens advice with using 2.1 on both sketch and gateway. And restart gateway with problem. And then test again and restart node when the problem occurs.

            1 Reply Last reply
            0
            • Darren McInnesD Offline
              Darren McInnesD Offline
              Darren McInnes
              wrote on last edited by
              #16

              You may want to check which version of the onewire library you are running. I was having exactly the same issue and fixed it by upgrading the onewire library from 2.7.2 (I think?) To the latest version.

              It's been a few weeks now with no crashes.

              raptorjrR 1 Reply Last reply
              1
              • Darren McInnesD Darren McInnes

                You may want to check which version of the onewire library you are running. I was having exactly the same issue and fixed it by upgrading the onewire library from 2.7.2 (I think?) To the latest version.

                It's been a few weeks now with no crashes.

                raptorjrR Offline
                raptorjrR Offline
                raptorjr
                wrote on last edited by
                #17

                @Darren-McInnes said:

                You may want to check which version of the onewire library you are running. I was having exactly the same issue and fixed it by upgrading the onewire library from 2.7.2 (I think?) To the latest version.

                It's been a few weeks now with no crashes.

                In the Arduino IDE version 2.3.2 seems to be the latest. But is there a newer one that I can download and install?

                Maybe should figure out why I don't get any sensor readings when I have sensors.begin() in the setup. Maybe that is a problem also that I have it in the loop()?

                1 Reply Last reply
                0
                • Darren McInnesD Offline
                  Darren McInnesD Offline
                  Darren McInnes
                  wrote on last edited by
                  #18

                  Sorry i meant the dallastemperature library!

                  i'm running 3.7.5 now without issues but had problems after a day with 3.7.2

                  my ide version is 1.6.9 and onewire version 2.3.2

                  raptorjrR 1 Reply Last reply
                  0
                  • Darren McInnesD Darren McInnes

                    Sorry i meant the dallastemperature library!

                    i'm running 3.7.5 now without issues but had problems after a day with 3.7.2

                    my ide version is 1.6.9 and onewire version 2.3.2

                    raptorjrR Offline
                    raptorjrR Offline
                    raptorjr
                    wrote on last edited by
                    #19

                    @Darren-McInnes

                    I have 3.7.6 installed. On thing might just be updated libraries. I have been using Visual Studio for development with the plugin from Visual Micro. But then the libraries don't get updated. Had some problems with a update from Visual Micro so I went back to the Arduino IDE.
                    Have been running since Monday now. Although it could take more than a week before any problem, but I'm keeping my fingers crossed.

                    gohanG 1 Reply Last reply
                    0
                    • raptorjrR raptorjr

                      @Darren-McInnes

                      I have 3.7.6 installed. On thing might just be updated libraries. I have been using Visual Studio for development with the plugin from Visual Micro. But then the libraries don't get updated. Had some problems with a update from Visual Micro so I went back to the Arduino IDE.
                      Have been running since Monday now. Although it could take more than a week before any problem, but I'm keeping my fingers crossed.

                      gohanG Offline
                      gohanG Offline
                      gohan
                      Mod
                      wrote on last edited by
                      #20

                      @raptorjr
                      I am also using visual studio but libraries and boards get updated through the arduino ide without problems

                      raptorjrR 1 Reply Last reply
                      0
                      • gohanG gohan

                        @raptorjr
                        I am also using visual studio but libraries and boards get updated through the arduino ide without problems

                        raptorjrR Offline
                        raptorjrR Offline
                        raptorjr
                        wrote on last edited by
                        #21

                        @gohan

                        But if you never open the Arduino IDE it can't get updated? Or are you saying that they get updated through Visual Studio?

                        I hadn't used the IDE for several months. Thought I was a step closer to the problem and that it was bugs in older libraries.

                        gohanG 1 Reply Last reply
                        0
                        • raptorjrR raptorjr

                          @gohan

                          But if you never open the Arduino IDE it can't get updated? Or are you saying that they get updated through Visual Studio?

                          I hadn't used the IDE for several months. Thought I was a step closer to the problem and that it was bugs in older libraries.

                          gohanG Offline
                          gohanG Offline
                          gohan
                          Mod
                          wrote on last edited by
                          #22

                          @raptorjr

                          Visual Studio uses libraries from Arduino ide, so the only way to get updates is through the Arduino ide unless you install libraries manually. So it's a good thing to open Arduino ide every now and then if you want updates

                          1 Reply Last reply
                          0
                          Reply
                          • Reply as topic
                          Log in to reply
                          • Oldest to Newest
                          • Newest to Oldest
                          • Most Votes


                          19

                          Online

                          11.7k

                          Users

                          11.2k

                          Topics

                          113.1k

                          Posts


                          Copyright 2025 TBD   |   Forum Guidelines   |   Privacy Policy   |   Terms of Service
                          • Login

                          • Don't have an account? Register

                          • Login or register to search.
                          • First post
                            Last post
                          0
                          • MySensors
                          • OpenHardware.io
                          • Categories
                          • Recent
                          • Tags
                          • Popular