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. Development
  3. Combine several scetches to 1 node

Combine several scetches to 1 node

Scheduled Pinned Locked Moved Development
4 Posts 2 Posters 749 Views 2 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.
  • X Offline
    X Offline
    xypzo
    wrote on last edited by
    #1

    Hi
    I am having trouble combining multiple scetches into one node since the last mysensors library update.
    I have combined the DHT and RELAY scetches and they work great. I just want to add an interrupt sensor (door, motion, whatever) to pin 3.
    I think the problem is the sleep function. it is different in all 3 scetches, i am an absolute NOOB with programming, but simply cut/paste is not working anymore.

    Can somebody please be of any assistance? You would save the day! thanks!

    #define MY_DEBUG
    #define MY_RADIO_NRF24
    #define MY_REPEATER_FEATURE
    
    #include <SPI.h>
    #include <MySensors.h>
    #include <DHT.h>
    
    static const uint64_t UPDATE_INTERVAL = 60000;
    static const uint8_t FORCE_UPDATE_N_READS = 10;
    
    #define CHILD_ID_HUM 3
    #define CHILD_ID_TEMP 4
    #define DHT_DATA_PIN 8
    #define SENSOR_TEMP_OFFSET 0
    
    #define RELAY_PIN 4  // Arduino Digital I/O pin number for first relay (second on pin+1 etc)
    #define NUMBER_OF_RELAYS 2 // Total number of attached relays
    #define RELAY_ON 1  // GPIO value to write to turn on attached relay
    #define RELAY_OFF 0 // GPIO value to write to turn off attached relay
    
    float lastTemp;
    float lastHum;
    uint8_t nNoUpdatesTemp;
    uint8_t nNoUpdatesHum;
    bool metric = true;
    
    MyMessage msgHum(CHILD_ID_HUM, V_HUM);
    MyMessage msgTemp(CHILD_ID_TEMP, V_TEMP);
    
    DHT dht;
    
    
    void before()
    {
      for (int sensor = 1, pin = RELAY_PIN; 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, loadState(sensor) ? RELAY_ON : RELAY_OFF);
      }
    }
    
    void presentation()
    {
      // Send the sketch version information to the gateway
      sendSketchInfo("DHT & Relays", "2.0");
    
      // Register all sensors to gw (they will be created as child devices)
      present(CHILD_ID_HUM, S_HUM);
      present(CHILD_ID_TEMP, S_TEMP);
      for (int sensor = 1, pin = RELAY_PIN; sensor <= NUMBER_OF_RELAYS; sensor++, pin++) {
        // Register all sensors to gw (they will be created as child devices)
        present(sensor, S_BINARY);
    
        metric = getControllerConfig().isMetric;
      }
    }
    
    void setup()
    {
    
      dht.setup(DHT_DATA_PIN); // set data pin of DHT sensor
    
      // Sleep for the time of the minimum sampling period to give the sensor time to power up
      // (otherwise, timeout errors might occure for the first reading)
      sleep(dht.getMinimumSamplingPeriod());
    
    }
    
    void loop()
    {
      // Force reading sensor, so it works also after sleep()
      dht.readSensor(true);
    
      // Get temperature from DHT library
      float temperature = dht.getTemperature();
      if (isnan(temperature)) {
        Serial.println("Failed reading temperature from DHT!");
      } else if (temperature != lastTemp || nNoUpdatesTemp == FORCE_UPDATE_N_READS) {
        // Only send temperature if it changed since the last measurement or if we didn't send an update for n times
        lastTemp = temperature;
    
        // apply the offset before converting to something different than Celsius degrees
        temperature += SENSOR_TEMP_OFFSET;
    
        if (!metric) {
          temperature = dht.toFahrenheit(temperature);
        }
        // Reset no updates counter
        nNoUpdatesTemp = 0;
        send(msgTemp.set(temperature, 1));
    
    #ifdef MY_DEBUG
        Serial.print("T: ");
        Serial.println(temperature);
    #endif
      } else {
        // Increase no update counter if the temperature stayed the same
        nNoUpdatesTemp++;
      }
    
      // Get humidity from DHT library
      float humidity = dht.getHumidity();
      if (isnan(humidity)) {
        Serial.println("Failed reading humidity from DHT");
      } else if (humidity != lastHum || nNoUpdatesHum == FORCE_UPDATE_N_READS) {
        // Only send humidity if it changed since the last measurement or if we didn't send an update for n times
        lastHum = humidity;
        // Reset no updates counter
        nNoUpdatesHum = 0;
        send(msgHum.set(humidity, 1));
    
    #ifdef MY_DEBUG
        Serial.print("H: ");
        Serial.println(humidity);
    #endif
      } else {
        // Increase no update counter if the humidity stayed the same
        nNoUpdatesHum++;
      }
    
      // Sleep for a while to save energy
      sleep(UPDATE_INTERVAL);
    }
    void receive(const MyMessage &message)
    {
      // We only expect one type of message from controller. But we better check anyway.
      if (message.type == V_STATUS) {
        // Change relay state
        digitalWrite(message.sensor - 1 + RELAY_PIN, message.getBool() ? RELAY_ON : RELAY_OFF);
        // Store state in eeprom
        saveState(message.sensor, message.getBool());
        // Write some debug info
        Serial.print("Incoming change for sensor:");
        Serial.print(message.sensor);
        Serial.print(", New status: ");
        Serial.println(message.getBool());
      }
    }
    
    skywatchS 1 Reply Last reply
    0
    • X xypzo

      Hi
      I am having trouble combining multiple scetches into one node since the last mysensors library update.
      I have combined the DHT and RELAY scetches and they work great. I just want to add an interrupt sensor (door, motion, whatever) to pin 3.
      I think the problem is the sleep function. it is different in all 3 scetches, i am an absolute NOOB with programming, but simply cut/paste is not working anymore.

      Can somebody please be of any assistance? You would save the day! thanks!

      #define MY_DEBUG
      #define MY_RADIO_NRF24
      #define MY_REPEATER_FEATURE
      
      #include <SPI.h>
      #include <MySensors.h>
      #include <DHT.h>
      
      static const uint64_t UPDATE_INTERVAL = 60000;
      static const uint8_t FORCE_UPDATE_N_READS = 10;
      
      #define CHILD_ID_HUM 3
      #define CHILD_ID_TEMP 4
      #define DHT_DATA_PIN 8
      #define SENSOR_TEMP_OFFSET 0
      
      #define RELAY_PIN 4  // Arduino Digital I/O pin number for first relay (second on pin+1 etc)
      #define NUMBER_OF_RELAYS 2 // Total number of attached relays
      #define RELAY_ON 1  // GPIO value to write to turn on attached relay
      #define RELAY_OFF 0 // GPIO value to write to turn off attached relay
      
      float lastTemp;
      float lastHum;
      uint8_t nNoUpdatesTemp;
      uint8_t nNoUpdatesHum;
      bool metric = true;
      
      MyMessage msgHum(CHILD_ID_HUM, V_HUM);
      MyMessage msgTemp(CHILD_ID_TEMP, V_TEMP);
      
      DHT dht;
      
      
      void before()
      {
        for (int sensor = 1, pin = RELAY_PIN; 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, loadState(sensor) ? RELAY_ON : RELAY_OFF);
        }
      }
      
      void presentation()
      {
        // Send the sketch version information to the gateway
        sendSketchInfo("DHT & Relays", "2.0");
      
        // Register all sensors to gw (they will be created as child devices)
        present(CHILD_ID_HUM, S_HUM);
        present(CHILD_ID_TEMP, S_TEMP);
        for (int sensor = 1, pin = RELAY_PIN; sensor <= NUMBER_OF_RELAYS; sensor++, pin++) {
          // Register all sensors to gw (they will be created as child devices)
          present(sensor, S_BINARY);
      
          metric = getControllerConfig().isMetric;
        }
      }
      
      void setup()
      {
      
        dht.setup(DHT_DATA_PIN); // set data pin of DHT sensor
      
        // Sleep for the time of the minimum sampling period to give the sensor time to power up
        // (otherwise, timeout errors might occure for the first reading)
        sleep(dht.getMinimumSamplingPeriod());
      
      }
      
      void loop()
      {
        // Force reading sensor, so it works also after sleep()
        dht.readSensor(true);
      
        // Get temperature from DHT library
        float temperature = dht.getTemperature();
        if (isnan(temperature)) {
          Serial.println("Failed reading temperature from DHT!");
        } else if (temperature != lastTemp || nNoUpdatesTemp == FORCE_UPDATE_N_READS) {
          // Only send temperature if it changed since the last measurement or if we didn't send an update for n times
          lastTemp = temperature;
      
          // apply the offset before converting to something different than Celsius degrees
          temperature += SENSOR_TEMP_OFFSET;
      
          if (!metric) {
            temperature = dht.toFahrenheit(temperature);
          }
          // Reset no updates counter
          nNoUpdatesTemp = 0;
          send(msgTemp.set(temperature, 1));
      
      #ifdef MY_DEBUG
          Serial.print("T: ");
          Serial.println(temperature);
      #endif
        } else {
          // Increase no update counter if the temperature stayed the same
          nNoUpdatesTemp++;
        }
      
        // Get humidity from DHT library
        float humidity = dht.getHumidity();
        if (isnan(humidity)) {
          Serial.println("Failed reading humidity from DHT");
        } else if (humidity != lastHum || nNoUpdatesHum == FORCE_UPDATE_N_READS) {
          // Only send humidity if it changed since the last measurement or if we didn't send an update for n times
          lastHum = humidity;
          // Reset no updates counter
          nNoUpdatesHum = 0;
          send(msgHum.set(humidity, 1));
      
      #ifdef MY_DEBUG
          Serial.print("H: ");
          Serial.println(humidity);
      #endif
        } else {
          // Increase no update counter if the humidity stayed the same
          nNoUpdatesHum++;
        }
      
        // Sleep for a while to save energy
        sleep(UPDATE_INTERVAL);
      }
      void receive(const MyMessage &message)
      {
        // We only expect one type of message from controller. But we better check anyway.
        if (message.type == V_STATUS) {
          // Change relay state
          digitalWrite(message.sensor - 1 + RELAY_PIN, message.getBool() ? RELAY_ON : RELAY_OFF);
          // Store state in eeprom
          saveState(message.sensor, message.getBool());
          // Write some debug info
          Serial.print("Incoming change for sensor:");
          Serial.print(message.sensor);
          Serial.print(", New status: ");
          Serial.println(message.getBool());
        }
      }
      
      skywatchS Offline
      skywatchS Offline
      skywatch
      wrote on last edited by skywatch
      #2

      @xypzo said in Combine several scetches to 1 node:

      This should do it - It compiles but I cannot test it, so that is down to you!

      #define MY_DEBUG
      #define MY_RADIO_NRF24
      #define MY_REPEATER_FEATURE
      
      static const uint64_t UPDATE_INTERVAL = 60000;
      static const uint8_t FORCE_UPDATE_N_READS = 10;
      #define DIGITAL_INPUT_SENSOR 3
      #define CHILD_ID 1
      #define CHILD_ID_HUM 3
      #define CHILD_ID_TEMP 4
      #define DHT_DATA_PIN 8
      #define SENSOR_TEMP_OFFSET 0
      
      #define RELAY_PIN 4  // Arduino Digital I/O pin number for first relay (second on pin+1 etc)
      #define NUMBER_OF_RELAYS 2 // Total number of attached relays
      #define RELAY_ON 1  // GPIO value to write to turn on attached relay
      #define RELAY_OFF 0 // GPIO value to write to turn off attached relay
      
      #include <SPI.h>
      #include <MySensors.h>
      #include <DHT.h>
      
      float lastTemp;
      float lastHum;
      uint8_t nNoUpdatesTemp;
      uint8_t nNoUpdatesHum;
      bool metric = true;
      
      MyMessage msgHum(CHILD_ID_HUM, V_HUM);
      MyMessage msgTemp(CHILD_ID_TEMP, V_TEMP);
      MyMessage msg(CHILD_ID, V_TRIPPED);
      DHT dht;
      
      
      void before()
      {
        for (int sensor = 1, pin = RELAY_PIN; 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, loadState(sensor) ? RELAY_ON : RELAY_OFF);
          pinMode(DIGITAL_INPUT_SENSOR, INPUT);
         digitalWrite(DIGITAL_INPUT_SENSOR, HIGH);
        }
      }
      
      void presentation()
      {
        // Send the sketch version information to the gateway
        sendSketchInfo("DHT & Relays", "2.0");
      
        // Register all sensors to gw (they will be created as child devices)
        present(CHILD_ID, S_DOOR);
        present(CHILD_ID_HUM, S_HUM);
        present(CHILD_ID_TEMP, S_TEMP);
        for (int sensor = 1, pin = RELAY_PIN; sensor <= NUMBER_OF_RELAYS; sensor++, pin++) {
          // Register all sensors to gw (they will be created as child devices)
          present(sensor, S_BINARY);
      
          metric = getControllerConfig().isMetric;
        }
      }
      
      void setup()
      {
      
        dht.setup(DHT_DATA_PIN); // set data pin of DHT sensor
      
        // Sleep for the time of the minimum sampling period to give the sensor time to power up
        // (otherwise, timeout errors might occure for the first reading)
        sleep(dht.getMinimumSamplingPeriod());
      
      }
      
      void loop()
      {
        // Force reading sensor, so it works also after sleep()
        dht.readSensor(true);
      
        // Get temperature from DHT library
        float temperature = dht.getTemperature();
        if (isnan(temperature)) {
          Serial.println("Failed reading temperature from DHT!");
        } else if (temperature != lastTemp || nNoUpdatesTemp == FORCE_UPDATE_N_READS) {
          // Only send temperature if it changed since the last measurement or if we didn't send an update for n times
          lastTemp = temperature;
      
          // apply the offset before converting to something different than Celsius degrees
          temperature += SENSOR_TEMP_OFFSET;
      
          if (!metric) {
            temperature = dht.toFahrenheit(temperature);
          }
          // Reset no updates counter
          nNoUpdatesTemp = 0;
          send(msgTemp.set(temperature, 1));
      
      #ifdef MY_DEBUG
          Serial.print("T: ");
          Serial.println(temperature);
      #endif
        } else {
          // Increase no update counter if the temperature stayed the same
          nNoUpdatesTemp++;
        }
      
        // Get humidity from DHT library
        float humidity = dht.getHumidity();
        if (isnan(humidity)) {
          Serial.println("Failed reading humidity from DHT");
        } else if (humidity != lastHum || nNoUpdatesHum == FORCE_UPDATE_N_READS) {
          // Only send humidity if it changed since the last measurement or if we didn't send an update for n times
          lastHum = humidity;
          // Reset no updates counter
          nNoUpdatesHum = 0;
          send(msgHum.set(humidity, 1));
      
      #ifdef MY_DEBUG
          Serial.print("H: ");
          Serial.println(humidity);
      #endif
        } else {
          // Increase no update counter if the humidity stayed the same
          nNoUpdatesHum++;
        }
      bool tripped = digitalRead(DIGITAL_INPUT_SENSOR) == HIGH;
        
          send(msg.set(tripped?"1":"0"));  // Send tripped value to gw
      
          // Sleep until interrupt comes in on motion sensor. Send update every two minute.
          sleep(digitalPinToInterrupt(DIGITAL_INPUT_SENSOR), CHANGE, UPDATE_INTERVAL);
      
        // Sleep for a while to save energy
        //sleep(UPDATE_INTERVAL);
      }
      void receive(const MyMessage &message)
      {
        // We only expect one type of message from controller. But we better check anyway.
        if (message.type == V_STATUS) {
          // Change relay state
          digitalWrite(message.sensor - 1 + RELAY_PIN, message.getBool() ? RELAY_ON : RELAY_OFF);
          // Store state in eeprom
          saveState(message.sensor, message.getBool());
          // Write some debug info
          Serial.print("Incoming change for sensor:");
          Serial.print(message.sensor);
          Serial.print(", New status: ");
          Serial.println(message.getBool());
        }
      }
      

      I am not sure it is possible for the node to receive a message when it is asleep?

      X 1 Reply Last reply
      1
      • skywatchS skywatch

        @xypzo said in Combine several scetches to 1 node:

        This should do it - It compiles but I cannot test it, so that is down to you!

        #define MY_DEBUG
        #define MY_RADIO_NRF24
        #define MY_REPEATER_FEATURE
        
        static const uint64_t UPDATE_INTERVAL = 60000;
        static const uint8_t FORCE_UPDATE_N_READS = 10;
        #define DIGITAL_INPUT_SENSOR 3
        #define CHILD_ID 1
        #define CHILD_ID_HUM 3
        #define CHILD_ID_TEMP 4
        #define DHT_DATA_PIN 8
        #define SENSOR_TEMP_OFFSET 0
        
        #define RELAY_PIN 4  // Arduino Digital I/O pin number for first relay (second on pin+1 etc)
        #define NUMBER_OF_RELAYS 2 // Total number of attached relays
        #define RELAY_ON 1  // GPIO value to write to turn on attached relay
        #define RELAY_OFF 0 // GPIO value to write to turn off attached relay
        
        #include <SPI.h>
        #include <MySensors.h>
        #include <DHT.h>
        
        float lastTemp;
        float lastHum;
        uint8_t nNoUpdatesTemp;
        uint8_t nNoUpdatesHum;
        bool metric = true;
        
        MyMessage msgHum(CHILD_ID_HUM, V_HUM);
        MyMessage msgTemp(CHILD_ID_TEMP, V_TEMP);
        MyMessage msg(CHILD_ID, V_TRIPPED);
        DHT dht;
        
        
        void before()
        {
          for (int sensor = 1, pin = RELAY_PIN; 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, loadState(sensor) ? RELAY_ON : RELAY_OFF);
            pinMode(DIGITAL_INPUT_SENSOR, INPUT);
           digitalWrite(DIGITAL_INPUT_SENSOR, HIGH);
          }
        }
        
        void presentation()
        {
          // Send the sketch version information to the gateway
          sendSketchInfo("DHT & Relays", "2.0");
        
          // Register all sensors to gw (they will be created as child devices)
          present(CHILD_ID, S_DOOR);
          present(CHILD_ID_HUM, S_HUM);
          present(CHILD_ID_TEMP, S_TEMP);
          for (int sensor = 1, pin = RELAY_PIN; sensor <= NUMBER_OF_RELAYS; sensor++, pin++) {
            // Register all sensors to gw (they will be created as child devices)
            present(sensor, S_BINARY);
        
            metric = getControllerConfig().isMetric;
          }
        }
        
        void setup()
        {
        
          dht.setup(DHT_DATA_PIN); // set data pin of DHT sensor
        
          // Sleep for the time of the minimum sampling period to give the sensor time to power up
          // (otherwise, timeout errors might occure for the first reading)
          sleep(dht.getMinimumSamplingPeriod());
        
        }
        
        void loop()
        {
          // Force reading sensor, so it works also after sleep()
          dht.readSensor(true);
        
          // Get temperature from DHT library
          float temperature = dht.getTemperature();
          if (isnan(temperature)) {
            Serial.println("Failed reading temperature from DHT!");
          } else if (temperature != lastTemp || nNoUpdatesTemp == FORCE_UPDATE_N_READS) {
            // Only send temperature if it changed since the last measurement or if we didn't send an update for n times
            lastTemp = temperature;
        
            // apply the offset before converting to something different than Celsius degrees
            temperature += SENSOR_TEMP_OFFSET;
        
            if (!metric) {
              temperature = dht.toFahrenheit(temperature);
            }
            // Reset no updates counter
            nNoUpdatesTemp = 0;
            send(msgTemp.set(temperature, 1));
        
        #ifdef MY_DEBUG
            Serial.print("T: ");
            Serial.println(temperature);
        #endif
          } else {
            // Increase no update counter if the temperature stayed the same
            nNoUpdatesTemp++;
          }
        
          // Get humidity from DHT library
          float humidity = dht.getHumidity();
          if (isnan(humidity)) {
            Serial.println("Failed reading humidity from DHT");
          } else if (humidity != lastHum || nNoUpdatesHum == FORCE_UPDATE_N_READS) {
            // Only send humidity if it changed since the last measurement or if we didn't send an update for n times
            lastHum = humidity;
            // Reset no updates counter
            nNoUpdatesHum = 0;
            send(msgHum.set(humidity, 1));
        
        #ifdef MY_DEBUG
            Serial.print("H: ");
            Serial.println(humidity);
        #endif
          } else {
            // Increase no update counter if the humidity stayed the same
            nNoUpdatesHum++;
          }
        bool tripped = digitalRead(DIGITAL_INPUT_SENSOR) == HIGH;
          
            send(msg.set(tripped?"1":"0"));  // Send tripped value to gw
        
            // Sleep until interrupt comes in on motion sensor. Send update every two minute.
            sleep(digitalPinToInterrupt(DIGITAL_INPUT_SENSOR), CHANGE, UPDATE_INTERVAL);
        
          // Sleep for a while to save energy
          //sleep(UPDATE_INTERVAL);
        }
        void receive(const MyMessage &message)
        {
          // We only expect one type of message from controller. But we better check anyway.
          if (message.type == V_STATUS) {
            // Change relay state
            digitalWrite(message.sensor - 1 + RELAY_PIN, message.getBool() ? RELAY_ON : RELAY_OFF);
            // Store state in eeprom
            saveState(message.sensor, message.getBool());
            // Write some debug info
            Serial.print("Incoming change for sensor:");
            Serial.print(message.sensor);
            Serial.print(", New status: ");
            Serial.println(message.getBool());
          }
        }
        

        I am not sure it is possible for the node to receive a message when it is asleep?

        X Offline
        X Offline
        xypzo
        wrote on last edited by xypzo
        #3

        IT WORKS, thank you very much!
        One thing, the interrupt (motion) status is only sent when the other data (temp hum) is being send. So once a minute it jumps from "on" to "off" in stead of every time the Sensor goes HI/LO.
        Is there a way to fix that?
        Thanks again, you are a HERO

        skywatchS 1 Reply Last reply
        0
        • X xypzo

          IT WORKS, thank you very much!
          One thing, the interrupt (motion) status is only sent when the other data (temp hum) is being send. So once a minute it jumps from "on" to "off" in stead of every time the Sensor goes HI/LO.
          Is there a way to fix that?
          Thanks again, you are a HERO

          skywatchS Offline
          skywatchS Offline
          skywatch
          wrote on last edited by skywatch
          #4

          @xypzo > One thing, the interrupt (motion) status is only sent when the other data (temp hum) is being send. So once a minute it jumps from "on" to "off" in stead of every time the Sensor goes HI/LO.

          Is there a way to fix that?

          Don't sleep the node. It won't be able to receive messages during it's sleep anyway so incomming messages will be lost as well.

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


          17

          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