Humidifier / Essence oil vaporize - anti dry-out monitor (WIP)


  • Contest Winner

    First of all. I'm sorry I've been away for a really long time. Life was just very busy. But I graduated as a psycho therapist a couple of months ago. And finally had some time to build this project.

    So my better half bought a humidifier in which you can drop some drops of essence oil. It's suppose to be good when she's doing Yoga.

    https://nl.aliexpress.com/item/USB-Luchtbevochtiger-Essenti-le-Olie-Diffuser-Diffuseur-Huile-Essentiel-Humidificador-Ultrasonico-500-ml-10/32856306452.html?spm=a2g0s.9042311.0.0.1ec64c4dVkQqGE

    alt text

    It's really a simple system. There's a water tank and a sponge, which is spring held against a piezo element. So when there's power on the piezo element it vaporizes the water in the tank. I'm not sure, but I think the piezo element stimulates the capillary function of the sponge. But when there's no power on the piezo element for a couple of hours, the humidifier smells terrible when you turn it on again. You suppose to remove the sponge when you turn it off, but we forget that for most of the time.

    A couple of weeks ago, I was doing some meditation and turned on the humidifier. And it is actually very relaxing. I like the smell of Vata oil. So I put some drops in the humidifier. It was during that meditation that it came to me, that I needed to add a MySensors monitor.

    Design Goals:

    1. It must be possible to operate the humidifier manually all of the time.
    2. The monitor should turn on the humidifier on regular intervals for a set amount of time.
    3. When the humidifier has ran for more than 4 hours in total, the humidifier is not allowed to be turned on again. Until the user confirms a refill of the water tank.
    4. It would be awesome, if I can control the humidifier with an IR remote control - I'm gonna be honest. After a couple of years of doing Home Automation, I really don't like the fact that I have to use my mobile phone for controlling devices.
    5. It would be nice if I can get a visual on the water level in the tank. Without opening the humidifier.

    So after prototyping a lot. The design I have now is:

    • a one button operated monitor. Used to turn the humidifier on and off and to confirm to the monitor, that I have refilled the tank.
    • a WS28b12 which changes it's color to the calculated percentage of the water level. (Couldn't fit a distance sensor in the humidifier)
    • MySensorfied (still version 2.0.0 need to upgrade to 2.3.0 but that takes a lot of time)
    • I can control domoticz with a IR Remote Control project I'm working on.
      I like this setup, it's really basic but does what I need.

    The main sketch humidifierSystem.ino

    /*
      Sketch : humidifierSystem
      Author : by Theo
    
      Controls a USB powered connector. To which an USB powered humitifier is connected, which is also able to humiditfy esence oil.
    
      History:
      -  December 1st 2018 : Initial Version. 
    */
    
    /* Use static Node id. We'll use another Arduino for production version */
    #define MY_NODE_ID 5
    
    // Enable debug prints
    #define MY_DEBUG_OFF
    
    // Enable and select radio type attached
    #define MY_RADIO_NRF24
    
    
    /* Include used libraries */
    #include <Bounce2.h>
    #include "FastLED.h"
    #include "avr/power.h"
    #include "config.h" // Project specific settings and definitions.
    #include <SPI.h>
    #include <MySensors.h>
    
    #include "mysUtils.h"
    
    
    /* Needs to be declared prior to serialDebug inclussion. */
    HUMIDIFIER_STATES currentState = HS_INITIAL;
    unsigned long currentTime; // The current running time in millis since Arduino has been started. Is set in main loop
    unsigned long preventSmellStartlInterval = 3600; // Restart every hour. Is reset each time the humidifier is turnedOff
    unsigned long preventSmellRunDuration =  30; // Run 30 Secods. If we don't use it. It needs refill once every xx days....
    
    unsigned long humidifierRefillPeriod = 14400; // needs refill after 180 seconds (3 minutes = 10 runs)
    unsigned long totalRunningTime = humidifierRefillPeriod; // Indicates the amount of seconds the humidifier has been running 14400 is 4 hour. We start with an empty tank, because we have no idea what the state is.
    
    unsigned long lastActivationTime = 0 - ( preventSmellStartlInterval * 1000 );
    unsigned long currentStateRunningTime = 0;
    
    unsigned long maxUserRunDuration = 900; // meaning 15 minutes if 0 no maxDuration but I think that that's not a good idea. We control cheap Chinese crap, which shouldn't be powered on infinitly. It's up to the user.
    HUMIDIFIER_STATES oldState; // Used in set state, so that we can do something depending on previous state
    unsigned long stateStarted = 0; // Indicates the TS when the currentState has been set.
    
    
    #include "serialDebug.h"
    
    double currentTankPercentage = 100;
    double percentageCalculator = 0;
    
    CRGB leds[ WS28b12_NUM_LEDS ];
    Bounce toggleButtonDebouncer = Bounce();
    
    
    
    /* Declare outside of dispSecondsLeft function, to be more heap space friendly */
    unsigned long secondsLeft = 0;
    int days = 0;
    
    /*
      Calculate how much time is left before the tank is empty, Based upon the total time it takes for our tank to be empty if run contineously,
      the amount of time the humidifier alreada ran after last refill, the duration of a dry-out prevention run and the interval between dry-out preventions.
    */
    void dispSecondsLeft() {
      secondsLeft = ( ( humidifierRefillPeriod - totalRunningTime ) / (float)preventSmellRunDuration ) * (float)preventSmellStartlInterval; // amount of seconds left
    
      if ( secondsLeft >= SECONDS_IN_DAY) {
        days = secondsLeft / SECONDS_IN_DAY;
        secondsLeft -=  days * SECONDS_IN_DAY;
      }
    
      if ( secondsLeft >= SECONDS_IN_HOUR ) {
        hours = secondsLeft / SECONDS_IN_HOUR;
        secondsLeft -= hours * SECONDS_IN_HOUR;
      }
    
      if ( secondsLeft >= SECONDS_IN_MINUTES ) {
        minutes = secondsLeft / SECONDS_IN_MINUTES;
        secondsLeft -= minutes * SECONDS_IN_MINUTES;
      }
    
    #ifdef DEBUG_HUMIDIFIER
      Serial.print( "Tank left " ); Serial.print( days ); Serial.print( " days, " ); Serial.print( hours ); Serial.print( ":" ); Serial.print( minutes ); Serial.print( ":" ); Serial.print( secondsLeft ); Serial.println();
    #endif
      sendTimeLeft( days, hours, minutes, secondsLeft );
    }
    
    /*
      Turns off our visual indicator.
    */
    void turnOffVisualIndicator() {
      FastLED.clear();
      FastLED.show();
    }
    
    /*
      Shows the level of our tank. Until 10% it'll show a green color. Until 5% we'll show yellos. Until 2% we show orange and of we have 2 or less % available we show red
    */
    void showVisualTankPercentage() {
      if ( currentTankPercentage <= 2.0 ) {
        leds[0] = CRGB::Red;
      }
      else if ( currentTankPercentage <= 5.0 ) {
        leds[0] = CRGB::Orange;
      }
      else if ( currentTankPercentage <= 10.0 ) {
        leds[0] = CRGB::Yellow;
      }
      else {
        leds[0] = CRGB::Green;
      }
      FastLED.show();
    }
    
    /*
      MySensors before method. It's being called before Node tries to connect to the gateway.
    
      Since we want to visualize connecting to the gateway. We have to Initialize our WS28b12 led.
    */
    void before() {
      FastLED.addLeds<NEOPIXEL, WS28b12_INDICATOR_PIN>(leds, WS28b12_NUM_LEDS  );
    
      leds[0] = CRGB::Blue;
      FastLED.show();
    
    
    #ifdef DEBUG_HUMIDIFIER
      Serial.begin( 115200 );
      while (!Serial) { // Wait for the serial connection to be establised.
        delay( 50 );
      }
    #endif
    }
    
    /*
      Setup method of the sketch.
    */
    void setup() {
      /* Turn of peripherals we don't use for power savings */
      ADCSRA = 0;  // disable ADC to save power consumptione, we don't use Analog input.
    
      // Assign each pin and write low to it.
      for ( byte b = 2; b <= A5; b++ ) {
        pinMode( b, OUTPUT );
        digitalWrite( b, 0 );
      }
    
      pinMode( POWER_STATE_PIN, OUTPUT ); // It's binary we don't use PWM. So setting it to OUTPUT is good enough
    
      pinMode( TOGGLE_BUTTON_PIN, INPUT_PULLUP ); // Setup the button with an internal pull-up :
      toggleButtonDebouncer.attach( TOGGLE_BUTTON_PIN ); // After setting up the button, setup the Bounce instance
      toggleButtonDebouncer.interval( DEBOUNCE_DURATION ); // Set debounce interval
    
    #ifdef DEBUG_HUMIDIFIER
      Serial.println( "Starting humidifier monitor" );
    #endif
      setState( HS_IDLE );
    }
    
    /*
      MySensors presentation method. It's used to present out sensors to the gateway.
      Note: You can use a request, but had no luck with using a send cmd
    */
    void presentation() {
      sendSketchInfo(SN, SV);
      present( CHILD_ID_HUMIDIFIER_STATE_CHANGE_REQUEST, S_BINARY );
      wait( 50 );
      present(CHILD_ID_CONFIG, S_INFO );
      wait( 50 );
      present(CHILD_ID_TANK_EMPTY, S_MOTION );
      wait( 50 );
      present( CHILD_ID_NEW_CONFIG, S_BINARY );
      wait( 50 );
      present( CHILD_ID_TANKLEVEL_PERCENTAGE, S_INFO );
      wait( 50 );
      present( CHILD_ID_TANKLEVEL_TIMELEFT, S_INFO );
      wait( 50 );
      request( CHILD_ID_CONFIG, V_TEXT, 0 );
      wait( 50 );
      turnOffVisualIndicator();
    }
    
    /*
    
    */
    double getCalculatedTankLevel( long currentRunningtime ) {
      percentageCalculator = 100 - ( double( currentRunningtime )  / double( humidifierRefillPeriod ) * 100 );
      percentageCalculator = ( (double)(round( percentageCalculator * 10 ) / (double)10) );
      return percentageCalculator;
    }
    
    unsigned long toggleButtonLastPress = 0;
    bool buttonChanged = false;
    unsigned long refillAnimationLastIndicatorSet;
    bool refillBlyncOn;
    
    bool longPressDetectionSend = false;
    
    void loop() {
      currentTime = millis(); // Remember currentTime millis for other parts in sketch.
      buttonChanged = toggleButtonDebouncer.update();
    #ifdef DEBUG_HUMIDIFIER
      displayCurrentTime();
    #endif
      switch ( currentState ) {
        case HS_IDLE:
          if ( currentTime >= ( lastActivationTime + ( preventSmellStartlInterval * 1000 ) ) ) {
            lastActivationTime = currentTime; // The interval is based upon the last time the prevention has run
            setState( HS_RUNNING_PREVENTION );
          }
          else { // Check for user humidifier on request
            if ( buttonChanged ) {
              if ( toggleButtonDebouncer.read() == LOW ) { // Call code if Bounce fell (transition from HIGH to LOW) :
    #ifdef DEBUG_HUMIDIFIER
                Serial.println( "press detected...???" );
    #endif
                toggleButtonLastPress = currentTime;
                longPressDetectionSend = false;
              }
              else {
    #ifdef DEBUG_HUMIDIFIER
                Serial.println( "Released... " );
    #endif
                if ( currentTime <= toggleButtonLastPress + 500 ) {
    #ifdef DEBUG_HUMIDIFIER
                  Serial.println( "short press" );
    #endif
                  setState( HS_RUNNING_USER );
                }
                else {
                  turnOffVisualIndicator();
                  totalRunningTime = 0; // (unsigned long)( humidifierRefillPeriod * 0.898 ); // Note: This is for debug. Should actually be 0 meaning full tank
    #ifdef DEBUG_HUMIDIFIER
                  Serial.println( "User indicates inbetween refill" ); // totalRunningTime
                  Serial.println( totalRunningTime );
    #endif
                }
              }
            }
            else if ( toggleButtonDebouncer.read() == LOW && currentTime >= toggleButtonLastPress + 500 && longPressDetectionSend == false ) {
    #ifdef DEBUG_HUMIDIFIER
              Serial.println( "Long pressed detected" );
    #endif
              longPressDetectionSend = true;
              leds[0] = CRGB::Green;
              FastLED.show();
    
            }
          }
          break;
        case HS_RUNNING_PREVENTION:
          toggleButtonDebouncer.read(); // remove debounce change bool
          currentStateRunningTime = ( currentTime - stateStarted ) / 1000; // Interval is in seconds.
          if ( currentStateRunningTime + totalRunningTime >= humidifierRefillPeriod  ) {
            totalRunningTime += currentStateRunningTime;
            setState( HS_NEEDSREFILL );
            break;
          }
          else if (  currentStateRunningTime >= preventSmellRunDuration || ( totalRunningTime + currentStateRunningTime ) >= humidifierRefillPeriod ) {
            totalRunningTime += currentStateRunningTime;
            if ( totalRunningTime >= humidifierRefillPeriod ) {
              setState( HS_NEEDSREFILL );
            }
            else {
    #ifdef DEBUG_HUMIDIFIER
              Serial.print( "Next dry up prevention is at " ); Serial.println( lastActivationTime + ( preventSmellStartlInterval * 1000 ) );
    #endif
              setState( HS_IDLE );
            }
            break;
          }
          if ( getCalculatedTankLevel( totalRunningTime + currentStateRunningTime) != currentTankPercentage ) {
    #ifdef DEBUG_HUMIDIFIER
            Serial.print( currentTime  ); Serial.print( " - Humidifier tank percentage " ); Serial.print( percentageCalculator, 1 ); Serial.println( "%" );
    
    #endif
            currentTankPercentage = percentageCalculator;
            showVisualTankPercentage();
            sendTankLevelUpdate( currentTankPercentage );
          }
          break;
        case HS_RUNNING_USER:
          currentStateRunningTime = ( currentTime - stateStarted ) / 1000; // Interval is in seconds.
          if ( currentStateRunningTime + totalRunningTime >= humidifierRefillPeriod  ) {
            totalRunningTime += currentStateRunningTime;
            lastActivationTime = currentTime;
            setState( HS_NEEDSREFILL );
          }
          else {
            if ( getCalculatedTankLevel( totalRunningTime + currentStateRunningTime) != currentTankPercentage ) {
    #ifdef DEBUG_HUMIDIFIER
              Serial.print( currentTime  ); Serial.print( " - Humidifier tank percentage " ); Serial.print( percentageCalculator, 1 ); Serial.println( "%" );
    #endif
    
              currentTankPercentage = percentageCalculator;
              showVisualTankPercentage();
              sendTankLevelUpdate( currentTankPercentage );
            }
            if ( ( maxUserRunDuration > 0 && currentStateRunningTime >= maxUserRunDuration )  || ( buttonChanged && toggleButtonDebouncer.read() == HIGH ) ) {
    #ifdef DEBUG_HUMIDIFIER
              Serial.println( "(auto)shutdown user running" );
    #endif
              totalRunningTime += currentStateRunningTime;
              lastActivationTime = currentTime;
              setState( HS_IDLE );
            }
          }
          break;
        case HS_NEEDSREFILL:
          // Wait for user to press Refill button... Should we allow to do this via MySensors.???
          if ( buttonChanged && toggleButtonDebouncer.read() == HIGH ) { // Call code if Bounce fell (transition from HIGH to LOW) :
            totalRunningTime = 0;
    #ifdef DEBUG_HUMIDIFIER
            Serial.println( "User refilled tank" );
    #endif
            send( tankEmptyMsg.set( "0" ) );
    
            lastActivationTime = currentTime - ( preventSmellStartlInterval * 1000 ) - 500; // force a dry-out protection
            setState( HS_IDLE );
          }
          else {
            if ( currentTime >= refillAnimationLastIndicatorSet + refillBlyncDuration ) {
              refillBlyncOn = !refillBlyncOn;
              refillAnimationLastIndicatorSet = currentTime;
    
              if ( refillBlyncOn ) {
                leds[0] = CRGB::Red;
                FastLED.show();
              }
              else {
                turnOffVisualIndicator();
              }
            }
          }
          break;
      }
    }
    
    void setState( HUMIDIFIER_STATES newState ) {
      if ( newState != currentState ) {
        oldState = currentState;
        currentState = newState;
        stateStarted = currentTime;
    
    #ifdef DEBUG_HUMIDIFIER
        Serial.print( currentTime  ); Serial.print( " - State changed to " ); Serial.println( stateNames[ currentState ] );
        Serial.print( currentTime  ); Serial.print( " - Humidifier tank percentage " ); Serial.print( getCalculatedTankLevel( totalRunningTime ), 1 ); Serial.println( "%" );
    #endif
    
        currentTankPercentage = percentageCalculator;
    
    #ifdef DEBUG_HUMIDIFIER
        lastTimeDisplayed = currentTime;
        displayCurrentTime( true );
    #endif
    
        switch ( newState ) { // Turn on/off the power pin according the new state. Much easier to handle it here throughtout the Code.
          case HS_INITIAL:
            digitalWrite( POWER_STATE_PIN, LOW );
            break;
          case HS_IDLE:
            dispSecondsLeft();
            turnOffVisualIndicator();
    
            digitalWrite( POWER_STATE_PIN, LOW );
    
            send( humidifierRunningMsg.set( "0" ) ); // Let gateway know we humidifier is no longer running
            wait( 5 );
            sendTankLevelUpdate( currentTankPercentage );
    
            break;
          case HS_RUNNING_PREVENTION:
            showVisualTankPercentage();
            digitalWrite( POWER_STATE_PIN, HIGH );
    
            send( humidifierRunningMsg.set( "1" ) ); // Let gateway know we humidifier is longer running
            wait( 5 );
            sendTankLevelUpdate( currentTankPercentage );
            break;
          case HS_RUNNING_USER:
            showVisualTankPercentage();
            digitalWrite( POWER_STATE_PIN, HIGH );
    
            send( humidifierRunningMsg.set( "1" ) ); // Let gateway know we humidifier is longer running
            wait( 5 );
            sendTankLevelUpdate( currentTankPercentage );
            break;
          case HS_NEEDSREFILL:
            sendTankLevelUpdate( 0.0 );
            turnOffVisualIndicator(); // Should be red
            refillAnimationLastIndicatorSet = currentTime;
            refillBlyncOn = false;
    
            // Turn off humidifier powerrrrr!!! :p
            digitalWrite( POWER_STATE_PIN, LOW );
    
            send( humidifierRunningMsg.set( "0" ) ); // Let gateway know we humidifier is no longer running
            wait(5);
            send( tankEmptyMsg.set( "1" ) );
            break;
        }
      }
    }
    
    /*
      MySensors callback method for when a message from the gateway - or another Node - has been received.
    */
    void receive(const MyMessage &message) {
    #ifdef DEBUG_HUMIDIFIER
      Serial.println( "Message received:" );
      Serial.print( "\tType: " ); Serial.println( message.type );
      Serial.print( "\tSensor: " ); Serial.println( message.sensor);
      Serial.print( "\tString: '" ); Serial.print( message.getString() ); Serial.println( "'" );
    #endif
    
      if ( message.sensor == CHILD_ID_CONFIG ) {
        bufferTmp = message.getString();
    
        if ( getMysBufferSeparatorCount() == 3 ) {
          if ( updateHumidifierConfig() ) {
            preventSmellStartlInterval = tmpDryOutPreventionInterval;
            preventSmellRunDuration =  tmpDryOutPreventionRunTime;
            humidifierRefillPeriod = tmpTankStock;
            maxUserRunDuration = tmpMaxUserRunTimeDuration;
    
            if ( totalRunningTime > humidifierRefillPeriod ) {
              totalRunningTime = humidifierRefillPeriod;
            }
          }
        }
        else if ( strcmp( bufferTmp, "-" ) == 0 ) {
          sendCurrentConfiguration( humidifierRefillPeriod, preventSmellStartlInterval, preventSmellRunDuration, maxUserRunDuration );
        }
      }
      else if ( message.sensor == CHILD_ID_NEW_CONFIG ) {
        request( CHILD_ID_CONFIG, V_TEXT, 0 );
      }
      else if ( message.sensor == CHILD_ID_HUMIDIFIER_STATE_CHANGE_REQUEST ) {
        //    Serial.print( "Request received for Humidifier: " ); Serial.println( message.getBool() == true ? "on" : "off" );
        bool onStateRequested = message.getBool();
        switch ( currentState ) {
          case HS_IDLE:
            if ( onStateRequested == true ) {
              setState( HS_RUNNING_USER );
            }
            else {
              send( humidifierRunningMsg.set( "0" ) ); // Let gateway know we didn't accept the request
            }
            break;
          case HS_RUNNING_PREVENTION:
            send( humidifierRunningMsg.set( "1" ) ); // Let gateway know we didn't accept the request
            break;
          case HS_RUNNING_USER:
            if ( onStateRequested == false ) {
              totalRunningTime += currentStateRunningTime;
              lastActivationTime = currentTime;
              setState( HS_IDLE );
            }
            else {
              send( humidifierRunningMsg.set( "1" ) ); // Let gateway know we didn't accept the request
            }
            break;
          case HS_NEEDSREFILL:
            if ( onStateRequested == true ) {
              send( humidifierRunningMsg.set( "0" ) ); // Let gateway know we didn't accept the request
            }
            break;
          case HS_INITIAL: // Can't be in this state. Simply not possible
            break;
        }
      }
    }
    

    config.h

    /*
      file   : config.h
      author : by Theo
    
      description:
        contains settings and definitions for the humidifier monitor.
    */
    
    #define DEBUG_HUMIDIFIER_OFF // If defined DEBUG_HUMIDIFIER Serial debug is enabled. Just append _OFF if you don't need debug.
    
    
    #define DEBOUNCE_DURATION   5 // The amount of millis soft debouncing has to determine the value of a debounced switch
    #define TOGGLE_BUTTON_PIN   3 // The pin to wich the switch for manual operating is connected
    #define POWER_STATE_PIN     5 // The pin which connects to the USB terminal for controlling the power of the humidifier (will be connected to a mosfet controlling the USB terminal
    
    #define WS28b12_INDICATOR_PIN 6 // The pin to which the WS28b12 led is connected that we'll be using for a visual indicator
    #define WS28b12_NUM_LEDS 1      // The amount of leds. Can be more than one of chained more
    
    /* Constants used to calculate the total time left, before our tank is empty. Based upon the current settings. */
    #define SECONDS_IN_DAY 86400ul
    #define SECONDS_IN_HOUR 3600ul
    #define SECONDS_IN_MINUTES 60ul
    
    #define refillBlyncDuration 1000 // the duration between blync on and blync off, during refill animation
    
    /*
     State machines states for a humidifier monitorL
     - HS_INITIAL : Means we're in the setup part of the Sketch
     - HS_IDLE    : Means waiting for user required power on, or ant dry out time out
     - HS_RUNNING_PREVENTION : The anti dry-out timer went of and we're powering on the humidifier just long enough that it won't dry out
     - HS_RUNNING_USER : The user has requested a power on. Will run until user request a stop or when the max on duration timer has timed oud
     - HS_NEEDSREFILL  : Hold your horses. ank needs refilling. We don't allow any request to power on the humidifier
     */
    enum HUMIDIFIER_STATES { HS_INITIAL, HS_IDLE, HS_RUNNING_PREVENTION, HS_RUNNING_USER, HS_NEEDSREFILL };
    
    /* Contains the names of our states. Can be used for debug purposes */
    char stateNames[5][22] = { "HS_INITIAL", "HS_IDLE", "HS_RUNNING_PREVENTION", "HS_RUNNING_USER", "HS_NEEDSREFILL" };
    
    // Enable debug prints
    #define MY_DEBUG_OFF
    
    // Enable and select radio type attached
    #define MY_RADIO_NRF24
    

    mysUtils.h

    /*
    
    */
    
    #define SN "Dry-Out protector"
    #define SV "1.0"
    
    #define CHILD_ID_HUMIDIFIER_STATE_CHANGE_REQUEST  1
    #define CHILD_ID_CONFIG 2
    #define CHILD_ID_TANK_EMPTY 3
    #define CHILD_ID_NEW_CONFIG 4
    #define CHILD_ID_TANKLEVEL_PERCENTAGE 5
    #define CHILD_ID_TANKLEVEL_TIMELEFT 6
    
    MyMessage humidifierRunningMsg( CHILD_ID_HUMIDIFIER_STATE_CHANGE_REQUEST, V_LIGHT );
    MyMessage textMsg( CHILD_ID_CONFIG, V_TEXT);
    MyMessage tankEmptyMsg( CHILD_ID_TANK_EMPTY, V_TRIPPED);
    MyMessage tankLevelUpdatedMsg( CHILD_ID_TANKLEVEL_PERCENTAGE, V_TEXT );
    MyMessage tankLevelTimeLeftMsg( CHILD_ID_TANKLEVEL_TIMELEFT, V_TEXT );
    
    
    char *mysBufferHelper;
    char *bufferTmp;
    
    char *getMysBuffertNextSeparator() {
      return strchr( bufferTmp, '|' );
    }
    
    //bool mySensorsInitialized = false;
    
    
    int getMysBufferSeparatorCount() {
      int res = 0;
      char *cPtr = bufferTmp;
      char *sepPtr;
    
      sepPtr = strchr( cPtr, '|' ) ;
      while ( sepPtr != NULL ) {
        cPtr += ( sepPtr - cPtr ) + 1;
        res++;
        sepPtr = strchr( cPtr, '|' ) ;
      }
      return res;
    }
    
    unsigned long tmpTankStock;
    unsigned long tmpDryOutPreventionInterval;
    unsigned long tmpDryOutPreventionRunTime;
    unsigned long tmpMaxUserRunTimeDuration;
    
    bool updateHumidifierConfig() {
      mysBufferHelper = getMysBuffertNextSeparator();
      tmpTankStock = strtoul( bufferTmp, NULL, 10 );
      bufferTmp += ( mysBufferHelper - bufferTmp ) + 1;
      mysBufferHelper = getMysBuffertNextSeparator();
      tmpDryOutPreventionInterval = strtoul( bufferTmp, NULL, 10 );
      bufferTmp += ( mysBufferHelper - bufferTmp ) + 1;
      mysBufferHelper = getMysBuffertNextSeparator();
      tmpDryOutPreventionRunTime = strtoul( bufferTmp, NULL, 10 );
      bufferTmp += ( mysBufferHelper - bufferTmp ) + 1;
      tmpMaxUserRunTimeDuration = strtoul( bufferTmp, NULL, 10 );
    
      if ( tmpTankStock != 0 && tmpDryOutPreventionInterval != 0 && tmpDryOutPreventionRunTime != 0 ) {
        return true;
      }
      return false;
    }
    
    char mySensorsPayloadBuffer[ MAX_PAYLOAD ];
    void sendTankLevelUpdate( float newLevel ) {
      dtostrf( newLevel, 4, 1, mySensorsPayloadBuffer);
      send( tankLevelUpdatedMsg.set( mySensorsPayloadBuffer ) );
    }
    
    void sendTimeLeft( int days, int hours, int minutes, int seconds ) { // seconds is allways 0
      for ( int i = 0; i < MAX_PAYLOAD; i++ ) { // clearUp buffer
        mySensorsPayloadBuffer[ i ] = '\0';
      }
      char *hlpPtr;
      itoa ( days, mySensorsPayloadBuffer, 10 );
      hlpPtr = &mySensorsPayloadBuffer[0];
      hlpPtr += strlen( mySensorsPayloadBuffer );
      strcpy( hlpPtr, " days, " );
      hlpPtr = &mySensorsPayloadBuffer[0];
      hlpPtr += strlen( mySensorsPayloadBuffer );
      sprintf ( hlpPtr, "%02d:%02d:%02d", hours, minutes,  seconds );
      send( tankLevelTimeLeftMsg.set( mySensorsPayloadBuffer ) );
    }
    
    void sendCurrentConfiguration( unsigned long tankStock, unsigned long dryOutPreventionInterval, unsigned long dryOutPreventionRunTime, unsigned long maxUserRunTimeDuration ) {
      // //        send( textMsg.set( "14400|3600|30|900" ) );
      for ( int i = 0; i < MAX_PAYLOAD; i++ ) { // clearUp buffer
        mySensorsPayloadBuffer[ i ] = '\0';
      }
      sprintf ( mySensorsPayloadBuffer, "%lu|%lu|%lu|%lu", tankStock, dryOutPreventionInterval, dryOutPreventionRunTime,  maxUserRunTimeDuration );
      send( textMsg.set( mySensorsPayloadBuffer ) );
    }
    
    

    serialDebug.h

    /*
      file   : config.h
      author : by Theo
    
      description:
        contains methods used for serial debugging.
    */
    
    int minutes = 0;
    int hours = 0;
    
    
    #ifdef DEBUG_HUMIDIFIER
    /* vars for time display */
    unsigned long lastTimeDisplayed = 0;
    unsigned long elapsed = 0;
    int seconds = 0;
    
    /*
      Displays the given int value as a time. Meaning 00:00
    */
    void displayTime( int value ) {
      if ( value >= 3600 ) {
        hours = value / 3600;
        value -= ( hours * 3600 );
        if ( hours < 10 ) {
          Serial.print( "0" );
        }
        Serial.print( hours );
      }
      else {
         Serial.print( "00" );
      }
      Serial.print( ":" );
    
      minutes = value / 60;
      seconds = value % 60;
      if ( minutes < 10 ) {
        Serial.print( "0" );
      }
      Serial.print( minutes );
      Serial.print( ":" );
      if ( seconds < 10 ) {
        Serial.print( "0" );
      }
      Serial.println( seconds );
    }
    
    /*
      Displays the ellapsed time since a state has been set for each state
    */
    void displayCurrentTime( bool init = false) {
      // clear display...
      switch ( currentState ) {
        case HS_IDLE:
          elapsed = ( currentTime - lastTimeDisplayed  ) / 1000;
          if ( elapsed >= 1 || init ) {
            displayTime( preventSmellStartlInterval - ( ( currentTime - lastActivationTime ) / 1000 ) );
            lastTimeDisplayed = currentTime;
          }
          break;
        case HS_RUNNING_PREVENTION:
          elapsed = ( currentTime - lastTimeDisplayed  ) / 1000;
          if ( elapsed >= 1 || init ) {
            displayTime( preventSmellRunDuration - ( ( currentTime - stateStarted ) / 1000 ) );
            lastTimeDisplayed = currentTime;
          }
          break;
        case HS_RUNNING_USER:
          elapsed = ( currentTime - lastTimeDisplayed  ) / 1000;
          if ( elapsed >= 1 || init ) {
            displayTime( ( currentTime - stateStarted ) / 1000 );
            lastTimeDisplayed = currentTime;
          }
          break;
        case HS_NEEDSREFILL:
          elapsed = ( currentTime - lastTimeDisplayed  ) / 1000;
          if ( elapsed >= 1 || init ) {
            displayTime( ( currentTime - stateStarted ) / 1000 );
            lastTimeDisplayed = currentTime;
          }
          break;
        default:
          Serial.print( "Disp time: unsupported state" ); Serial.println( stateNames[ currentState ] );
      }
    }
    #endif
    

    The code is not completed yet. Lacks comments. The c code needs to be seperated from the .h files.

    But it works great. I has the following child nodes:

    1. Switch for controlling the power state through Domoticz
    2. Text node for configuring the monitor through Domoticz
    3. One update control switch. When pressed the current config is queried from Domoticz. Doesn't make sense to me, to poll it each and every while
    4. text node to which the tank level in percentages is written
    5. text node to which the remaining days en time is written after the humidifier has run
    6. Alarm node, for when the water tank is empty.

    0_1543788555926_Schermafbeelding 2018-12-02 om 23.08.07.png

    0_1543788579565_Schermafbeelding 2018-12-02 om 23.08.34.png

    Will post a schematic and some photo's soon. And put the code on my Github. Just wanted to share this one with you guys.

    If you don't use it for Yoga or meditation, you can use it as device that makes your house smell nice. You only need to refill it once every month. If you don't turn it on manually. You're better half is gonna love it.

    Currently deciding on whether I can use a 2n2222 transistor to control the USB. It says 500 mWa. It 5 Volt that should be sufficient to drive a 1 amp device??? As an alternative I can use a Mosfet. But the ones I have can drive up to 60 amps. Which is really overkill. I think I have some darlingtons laying around somewhere.

    As you can read. Still in prototyping phase. But the sketch is done.


  • Mod

    @theol nice project and good to see you're back!



  • Wow, this is amazing! Also: congratulations!


 

381
Online

8.0k
Users

8.8k
Topics

94.3k
Posts