Next generation dust sensor for MySensors


  • Plugin Developer

    I was struggling with the current dust sensor for a bit, and this got me thinking: if I were to create a new MySensors dust sensor today, what would that look like?

    The requirements:

    • Cheap
    • Able to sense PM10 and PM2.5 particles
    • No need to calibrate, and reasonable calibration from the factory.
    • Should have a built-in fan.

    After looking around I suspect these would currently be the best candidates:

    Nova Fitness SDS011 ($20)

    • It has good reviews, kinda.
      "With its size, it is probably one of the best sensor in terms of accuracy"
    • It has a UART interface.
    • It's the most popular one on AliExpress.
    • A new version is out, the SDS 021, which should be compatible. No word on how it does yet, although the test results don't look too good.
    • It's not too accurate though: "While SDS011 seemed to achieve good results in tests I've seen specification says its relative error is maximum of 15% and +/- 10ug/mΒ³ (at room temperature and humidity). While 15% seems good enough to me, +/- 10ug is problematic since we are aiming at measuring values in the range of 5-50 ug/mΒ³ (50 being the 100% of the norm)"

    PMS7003 ($25)

    • Claims to be able to measure even more 'bins' (particle sizes), although suspicions a-plenty that these aren't honest.
    • Some experiments here on MySensors.

    Most of these devices, also the laser ones, are very imprecise at small amounts of dust. Below 50 ug/m3 they claim a possible measurement error of +-10ug, which is a bit ridiculous.

    .

    The other devices have more faults:

    • PMS5003 (and earlier). Good design but cheap 8-bit chip with low resolution.
    • Sharp sensor is bad at [low levels],(http://www.howmuchsnow.com/arduino/airquality/) of dust, and doesn't have a fan.
    • Shinyei is only good if you hack it to remove the resistor and just blow air into it constantly with a fan. Otherwise it can fluctuate a lot.
    • Cubic PM2005 ($40). This is the one used by the VAIR monitor, and it sounds like they know what they're talking about. It's not on Aliexpress though.

    Sources
    http://aqicn.org/sensor
    http://vair-monitor.com/2017/01/19/measuring-dust-levels-measure-part-23/

    .

    So, I'm curious to hear what you think: what's should be the new standard?


  • Plugin Developer

    After some more research and some reaching out, it seems the PMS7003 is a good fit.

    I've ordered one online.

    It's available on both eBay and Aliexpress. It's important to get the version with the little wired plug so that you can connect the wires to the Arduino easily.

    If anyone has any other thoughts: please share them!


  • Admin

    So how do these laser sensors work. Are they running all the time or do you wake them up once an hour taking measurements?


  • Plugin Developer

    @hek I know that the SDS011 has a 'sleep' mode, not sure about the PMS7003. There is a PDF with details, but it's in Chinese πŸ™‚

    The Specsheet:
    http://aqicn.org/air/view/sensor/spec/pms7003.pdf

    All good air quality sensors use a fan to blow air past the sensor. This makes air quality sensors not the best option for battery powered use. But nothing would stop you from enabling and disabling the whole thing for a while and then go into sleep. It depends on how long it takes for the fan to spin up. I'll check once I get it.

    Personally I would like to have multiple measurements and then average them. All these cheap sensors are quite noisy.In practise I poll them once a minute, and it then does 30 measurements internally (once a second) which it averages. The other 30 seconds it sleeps. But the fan stays on.


  • Plugin Developer

    I've created a sketch that uses this sensor. I'll share it here after a little clean-up. I'll also try to get it on the website.

    Here's the code that really helped me:

    https://github.com/openiod/apri-sensor/tree/master/apri-sensor-pms7003/pms7003


  • Plugin Developer

    Here's a English data sheet for the PMS-7003 dust sensor: https://www.pdf-archive.com/2017/04/12/plantower-pms-7003-sensor-data-sheet/


  • Plugin Developer

    Here's MySensors Arduino code for the PMS-7003. Many thanks to scapeler.com for the core of the code.

    /*
     * The MySensors Arduino library handles the wireless radio link and protocol
     * between your home built sensors/actuators and HA controller of choice.
     * The sensors forms a self healing radio network with optional repeaters. Each
     * repeater and gateway builds a routing tables in EEPROM which keeps track of the
     * network topology allowing messages to be routed to nodes.
     *
     * Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
     * Copyright (C) 2013-2015 Sensnology AB
     * Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
     *
     * Documentation: http://www.mysensors.org
     * Support Forum: http://forum.mysensors.org
     *
     * This program is free software; you can redistribute it and/or
     * modify it under the terms of the GNU General Public License
     * version 2 as published by the Free Software Foundation.
     *
     *******************************
     *
     *  DESCRIPTION
     *
     *  Plantower PMS-7003 fine dust sensor
     *  
     *  This sensor uses a frickin' laser to measure lots of different fine dust levels, between 0.3 and 10 microns.
     *  
     *  It communicates with your board over serial at 9600 speed.
     *  
     *  
     *  This code makes use of kindly shared code by Scapeler:
     *  
     *  - - -
     *  Copyright 2017 Scapeler
     * 
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
    
        http://www.apache.org/licenses/LICENSE-2.0
    
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     * - - -
    */
    
    //------------------------------------------------------------------------------
    
    // if you uncomment this, you can get test and debug updates about the sensor' wireless connection by using the serial monitor tool.
    #define MY_DEBUG
    
    // Enable and select radio type attached
    #define MY_RADIO_NRF24                              // A 2.4Ghz transmitter and receiver, often used with MySensors.
    #define MY_RF24_PA_LEVEL RF24_PA_MIN                // This sets a low-power mode for the radio. Useful if you use the verison with the bigger antenna, but don't want to power that from a separate power source. It can also fix problems with fake Chinese versions of the radio.
    // #define MY_RADIO_RFM69                           // 433Mhz transmitter and reveiver.
    
    // Choose if you want this sensor to also be a repeater.
    // #define MY_REPEATER_FEATURE                      // Just remove the two slashes at the beginning of this line to also enable this sensor to act as a repeater for other sensors. If this node is on battery power, you probably shouldn't enable this.
    
    // Are you using this sensor on battery power?
    // #define BATTERY_POWERED                          // Just remove the two slashes at the beginning of this line if your node is battery powered. It will then go into deep sleep as much as possible. While it's sleeping it can't work as a repeater!
    
    #include <SPI.h>
    #include <MySensors.h>
    #include <SoftwareSerial.h>
    
    #define CHILD_ID_DUST_PM10 0
    #define CHILD_ID_DUST_PM25 1
    #define CHILD_ID_DUST_PM100 2
    
    
    // Mysensors settings.
    MyMessage msgDust10(CHILD_ID_DUST_PM10, V_LEVEL);   // Sets up the message format that we'l be sending to the MySensors gateway later.
    MyMessage msgDust10b(CHILD_ID_DUST_PM10, V_UNIT_PREFIX);
    MyMessage msgDust25(CHILD_ID_DUST_PM25, V_LEVEL);   // Sets up the message format that we'l be sending to the MySensors gateway later.
    MyMessage msgDust25b(CHILD_ID_DUST_PM25, V_UNIT_PREFIX);
    MyMessage msgDust100(CHILD_ID_DUST_PM100, V_LEVEL); // Sets up the message format that we'l be sending to the MySensors gateway later.
    MyMessage msgDust100b(CHILD_ID_DUST_PM100, V_UNIT_PREFIX);
    
    
    
    // These defines and variables can be changed:
    int dustSlowDown = 20;                              // The dust sensor is internally checked approximately every 700 or 800 milliseconds. Once in how many loops should it send the data?
    unsigned long dustMeasurementInterval = 700;        // This is a fickle thing. Changing it can give more time-outs.
    SoftwareSerial mySerial(10, 11);                    // RX, TX . You can choose other pins if you prefer.
    
    
    // PROBABLY BEST NOT TO CHANGE THESE VARIABLES
    int inputHigh = 0;
    int inputLow = 0;
    uint16_t inputChecksum = 0;                         // variable to caclulate checksum input variables
    uint16_t concPM1_0_CF1;                             // Lots of sensor variables
    uint16_t concPM2_5_CF1;
    uint16_t concPM10_0_CF1;
    uint16_t concPM1_0_amb;
    uint16_t concPM2_5_amb;
    uint16_t concPM10_0_amb;
    uint16_t rawGt0_3um;
    uint16_t rawGt0_5um;
    uint16_t rawGt1_0um;
    uint16_t rawGt2_5um;
    uint16_t rawGt5_0um;
    uint16_t rawGt10_0um;
    uint8_t  version;
    uint8_t  errorCode;
    uint16_t checksum;
    int dustSlowDownCounter = 0;
    
    
    
    void presentation()
    {
      // Send the sketch version information to the gateway and Controller
      sendSketchInfo("Air Quality Sensor PMS-7003", "1.1");
    
      // Register all sensors to gateway (they will be created as child devices): 
      present(CHILD_ID_DUST_PM10, S_DUST);
      send(msgDust10b.set("um/m3"));  
      present(CHILD_ID_DUST_PM25, S_DUST);
      send(msgDust25b.set("um/m3"));
      present(CHILD_ID_DUST_PM100, S_DUST);
      send(msgDust100b.set("um/m3"));  
    }
    
    
    void setup() {
      Serial.begin(115200);
      delay(1000);
      mySerial.begin(9600);
      delay(1000);
      Serial.println("hello world, I am a sensor.");
    }
    
    
    void loop() {
    
      // The dust sensor sends its data continuously, so let's  catch that data.
      int dustSensorOutput = pms7003ReadData();
      
    }
    
    
    // MAIN FUNCTION FOR THE DUST SENSOR, Thanks to Scapeler.nl
    int pms7003ReadData() {
        
      // while (mySerial.read()!=-1) {};  //clear buffer
    
      if (mySerial.available() < 32) {
        if (mySerial.available() == 0) {
          delay(150);
          return -1;
        };
        if (mySerial.available() > 16) {
          delay(10);
          return -1;
        };
        if (mySerial.available() > 0) {
          delay(30);
          return -1;
        };
        delay(100);
        return -1;
      }
      if (mySerial.read() != 0x42) return -1;
      if (mySerial.read() != 0x4D) return -1;
    
      inputChecksum = 0x42 + 0x4D;
    
      inputHigh = mySerial.read();
      inputLow = mySerial.read();
      inputChecksum += inputHigh + inputLow;
      if (inputHigh != 0x00) return -1; 
      if (inputLow != 0x1c) return -1;    
    
      inputHigh = mySerial.read();
      inputLow = mySerial.read();
      inputChecksum += inputHigh + inputLow;
      concPM1_0_CF1 = inputLow+(inputHigh<<8);
    
      inputHigh = mySerial.read();
      inputLow = mySerial.read();
      inputChecksum += inputHigh + inputLow;
      concPM2_5_CF1 = inputLow+(inputHigh<<8);
    
      inputHigh = mySerial.read();
      inputLow = mySerial.read();
      inputChecksum += inputHigh + inputLow;
      concPM10_0_CF1 = inputLow+(inputHigh<<8);
    
      inputHigh = mySerial.read();
      inputLow = mySerial.read();
      inputChecksum += inputHigh + inputLow;
      concPM1_0_amb = inputLow+(inputHigh<<8);
    
      inputHigh = mySerial.read();
      inputLow = mySerial.read();
      inputChecksum += inputHigh + inputLow;
      concPM2_5_amb = inputLow+(inputHigh<<8);
    
      inputHigh = mySerial.read();
      inputLow = mySerial.read();
      inputChecksum += inputHigh + inputLow;
      concPM10_0_amb = inputLow+(inputHigh<<8);
    
      inputHigh = mySerial.read();
      inputLow = mySerial.read();
      inputChecksum += inputHigh + inputLow;
      rawGt0_3um = inputLow+(inputHigh<<8);
    
      inputHigh = mySerial.read();
      inputLow = mySerial.read();
      inputChecksum += inputHigh + inputLow;
      rawGt0_5um = inputLow+(inputHigh<<8);
    
      inputHigh = mySerial.read();
      inputLow = mySerial.read();
      inputChecksum += inputHigh + inputLow;
      rawGt1_0um = inputLow+(inputHigh<<8);
    
      inputHigh = mySerial.read();
      inputLow = mySerial.read();
      inputChecksum += inputHigh + inputLow;
      rawGt2_5um = inputLow+(inputHigh<<8);
    
      inputHigh = mySerial.read();
      inputLow = mySerial.read();
      inputChecksum += inputHigh + inputLow;
      rawGt5_0um = inputLow+(inputHigh<<8);
    
      inputHigh = mySerial.read();
      inputLow = mySerial.read();
      inputChecksum += inputHigh + inputLow;
      rawGt10_0um = inputLow+(inputHigh<<8);
    
      inputLow = mySerial.read();
      inputChecksum += inputLow;
      version = inputLow;
    
      inputLow = mySerial.read();
      inputChecksum += inputLow;
      errorCode = inputLow;
    
      Serial.print("PMS7003;"); 
    
      // The measurement recalculated to micrograms per cubic meter, a common standard.
      Serial.print(concPM1_0_CF1);
      Serial.print(';'); 
      Serial.print(concPM2_5_CF1);
      Serial.print(';'); 
      Serial.print(concPM10_0_CF1);
      Serial.print(';'); 
    
      // The measurement recalculated to micrograms per cubic meter, a common standard. Not quite sure what the difference is..
      Serial.print(concPM1_0_amb);
      Serial.print(';'); 
      Serial.print(concPM2_5_amb);
      Serial.print(';');     
      Serial.print(concPM10_0_amb);
      Serial.print(';');     
    
      // this is the 'raw' data that the sensor gathers internally, before it calculates the above values.
      Serial.print(rawGt0_3um); // This indicates total number or particles in 0.1 liter of air that have a diameter above 0.3um. This will be the biggest number, as it measures the most particles. But it's very imprecise: 50% accuracy..
      Serial.print(';');
      Serial.print(rawGt0_5um); // This indicates the total number or particles in 0.1 liter of air that have a diameter above 0.5um (so it will be a smaller count than the line above)
      Serial.print(';'); 
      Serial.print(rawGt1_0um); // This indicates the total number or particles in 0.1 liter of air that have a diameter above 1 micron. And so on..
      Serial.print(';'); 
      Serial.print(rawGt2_5um);
      Serial.print(';'); 
      Serial.print(rawGt5_0um); // Acording to the datashet, at this point the accuracy has reached at 98%.
      Serial.print(';'); 
      Serial.print(rawGt10_0um);
      Serial.print(';'); 
      Serial.print(version);
      Serial.print(';'); 
      Serial.print(errorCode);
      Serial.println("---");
      
      inputHigh = mySerial.read();
      inputLow = mySerial.read();
      checksum = inputLow+(inputHigh<<8);
      if (checksum != inputChecksum) {
        Serial.print(';'); 
        Serial.print(checksum); 
        Serial.print(';'); 
        Serial.print(inputChecksum); 
      }
    
      // Time to send the results to the gateway.
      dustSlowDownCounter = dustSlowDownCounter + 1;
      if(dustSlowDownCounter > dustSlowDown){
        
        /*  MEASUREMENT  */
        if(rawGt1_0um > 0){
          Serial.println(" Dust1_0 = " + String(concPM1_0_CF1));
          send(msgDust10.set(concPM1_0_CF1,1));
        }         
        if(rawGt2_5um > 0){
          Serial.println(" Dust1_0 = " + String(concPM2_5_CF1));
          send(msgDust25.set(concPM2_5_CF1,1));
        }     
        if(rawGt10_0um > 0){
          Serial.println(" Dust1_0 = " + String(concPM10_0_CF1));
          send(msgDust100.set(concPM10_0_CF1,1));
        }     
    
        /*  RAW DATA  */            
        /*
        if(rawGt0_3um > 0){
          Serial.println(" Dust03 = " + String(rawGt0_3um));
          send(msgDust03.set(rawGt0_3um,1));
        }
        if(rawGt0_5um > 0){
          Serial.println(" Dust05 = " + String(rawGt0_5um));
          send(msgDust05.set(rawGt0_5um,1));
        }
        if(rawGt1_0um > 0){
          Serial.println(" Dust10 = " + String(rawGt1_0um));
          send(msgDust10.set(rawGt1_0um,1));
        }
        if(rawGt2_5um > 0){
          Serial.println(" Dust25 = " + String(rawGt2_5um));
          send(msgDust25.set(rawGt2_5um,1));
        }
        if(rawGt5_0um > 0){
          Serial.println(" Dust50 = " + String(rawGt5_0um));
          send(msgDust50.set(rawGt5_0um,1));
        }      
        if(rawGt10_0um > 0){
          Serial.println(" Dust100 = " + String(rawGt10_0um));
          send(msgDust100.set(rawGt10_0um,1));
        }   
        */
    
        dustSlowDownCounter = 0;
      } // End of sensing message.
      
      delay(700);  // Data will come between 200 and 800 miliseconds. If you set this be be higher than 700 you will get checksum errors.
        
      return concPM2_5_CF1; // this data doesn't really go anywhere..
    }
    
    
    
    

  • Banned

    @alowhum said in Next generation dust sensor for MySensors:

    Nova Fitness SDS011

    You can go with Nova Fitness SDS011. It is really too good.


  • Plugin Developer

    Your advice is a little late πŸ™‚


  • Mod

    So, which is better? πŸ˜€



  • @alowhum said

    But nothing would stop you from enabling and disabling the whole thing for a while and then go into sleep. It depends on how long it takes for the fan to spin up. I'll check once I get it.

    These sensors have to run all the time, otherwise your measurements are not going to be reliable. PMS7003 real world power consumption was measured incredibly low (210mW) so you can run it on batteries.



  • @gohan This is hard to say. Every sensor has got its pros and cons. You may be happy with $2-3 sensor or need a more precision sensor for $25. It depends.

    I would say that PMS7003 is very advanced and precise enough. However, this is my subjective opinion.



  • @alowhum Firstly thanks for the code! Just a question to see if this is a cut and paste error. In the section under /* MEASUREMENT */ should the second and third serial.println be Dust2_5 and Dust10_0?

    Also I cant get my head around one thing. The RAW data seems logical in that your comments say that the 0.3 micron values will be the largest as it is measuring everything bigger than that. The 10 should be larger than 25 which will be larger than 100. In my case the raw values match that relationship. HOWEVER, the values sent back (concPMx_y_CF1) are reversed. They start small and go bigger? Is this correct?


  • Plugin Developer

    @gohan - I went with the PM7003 because it has less dust buildup and because I spoke with people in the field who were using it as part of scientific sensor networks.

    @Gavin-Nicholson - Yes you are right on the serial print output. I don't know about the reversed output though. I haven't used the sensor in a while. I'll see if I can dig it up.



  • @alowhum I am not sure the recalculated figures are correct so I am just going to modify the code and send the raw values.


  • Hero Member

    @alowhum said in Next generation dust sensor for MySensors:

    After some more research and some reaching out, it seems the PMS7003 is a good fit.

    I've ordered one online.

    It's available on both eBay and Aliexpress. It's important to get the version with the little wired plug so that you can connect the wires to the Arduino easily.

    If anyone has any other thoughts: please share them!

    Dos the PMS7003 have the same interface as the PMS5003? The PMS5003 is the one that Adafruit has adopted, and they have a library for it.

    That said, there are many github libraries for the SDS011, and it can communicate over serial (or USB if using a common TTL-to-USB converter).

    My motivation in getting one of these is to measure particle emissions from my 3D printer. I'll probably build an enclosure for my 3D printer so as to contain these emissions until they can be safely exhausted or filtered.


  • Plugin Developer

    I own both the SDS011 and the PMS7003. A little review:

    PMS7003

    • Three times as small as the SDS011 (!).
    • Metal housing (thin folded aluminium).
    • Can be run on batteries, as it's fan is so tiny.
    • Has 1,25mm spacing on its header, requiring some soldering to connect to Arduino.
    • Complex data output, many particle sizes output (bins), as well as raw particle counts.
    • Haven't tested 5v tolerance on the serial interface.

    SDS001

    • Huge.
    • Has an air-intake valve which you can connect at most a 1 meter hose to.
    • Plastic (with reflective coating so it looks like metal).
    • Can be directly connected with normal female Dupont wires. So you can buy it without the serial adapter an save a dollar..
    • Runs fine with power directly from nano 5v pin.
    • The serial interface is 5v tolerant (currently running it with an Arduino nano).
    • Very simple data output. The data is immediately useful.
    • Very basic control (sleep, wake up).
    • Seems to have analog data output too, but I haven't found any code using that.

    I'd recommend the SDS011 for beginners. It's more foolproof.
    I'd recommend the PMS7003 for portable projects.

    Ideally, both devices' data should be cleaned up by using the data from a moisture sensor. When there is a lot of moisture in the air, the sensor tends to see this as dust.

    I'm still looking for a bit of code that does this. Does anyone have that by any chance?


  • Hero Member

    @alowhum said in Next generation dust sensor for MySensors:

    I'm still looking for a bit of code that does this. Does anyone have that by any chance?

    Did you ever find code which does this?

    Also, regarding your OP, I think the Honeywell would be the best bet, since it's factory calibrated. The only regrettable thing is that it doesn't seem to measure the really tiny particles that some of the others claim to.


  • Plugin Developer

    Is this is the Honeywell you're talking about: HPMA115S0
    https://www.aliexpress.com/item/PM2-5-sensor-HONEYWELL-HPMA115S0-XXX-laser-pm2-5-air-quality-detection-sensor-module-Super-dust/32829242280.html

    From looking at the datasheet there are some other downsides:

    • 1,25mm molex connector (so not as easy to connect to as the SDS011, which takes normal Dupont wires)
    • Doesn't send data by itself? See this thread.
    • Precision seems poorer than the Chinese models (although they may just be more honest?). +-15ug at levels below 100, while the Chinese sensors claim +-10 below 50.
    • Can't find anything about it being 5v tolerant.

    What does look good is the estimated life of the laser, 20.000 years. That's 7 years if you use it 8 hours a day.


  • Hero Member


  • Hero Member

    BTW, I hooked up the Honeywell, and this is the total amount of what it displays:

    PS- Reading Particle Measurements...
    PS- Sending cmd: 68 1 4 93 
    PS- Waiting for cmd resp...
    PS- Received valid data!!!
    PM 2.5: 3 ug/m3
    PM 10: 4 ug/m3
    

    It shows less for PM2.5 and PM10 than the PMS5003. I presume the numbers are more trustworthy, though, since it was factory calibrated.



  • Hi. I am late here but i will ask anyways. So what is the review between PMS7003 and Honeywell HPM sensors?
    I'd like to build a low cost opensource AQI device for Indian cities with added temperature, humidity and ambient pressure data and then send it to cloud using GPRS/GSM or Wifi (esp8266 as MCU). But i am confused about the Honeywell HPM sensor - its accuracy as compared to BAM or other PM measurement standards. Any help will be great!



  • @NeverDie could you share your Honeywell code, I have trouble getting it to work. An example will help me a lot t learn from!


  • Hero Member

    @Sebex Hi, I had looked into this for the purpose of testing the effectiveness of air filtration on a 3D printer enclosure. Unfortunately, for lack of effective, yet quiet, filtration at the time, I shelved the project until a future date and so didn't keep the files. However, I'm pretty sure there were libraries for the sensor on github, so I recommend you look there. Good luck!


  • Hardware Contributor

    @Sebex said in Next generation dust sensor for MySensors:

    @NeverDie could you share your Honeywell code, I have trouble getting it to work. An example will help me a lot t learn from!

    I made a library for that, I'm updating it to autodetect some various PM, CO2, Formaldehyde UART sensors but I'll find the old version for Honeywell only tomorrow if you want. If I forget, harass me by MP πŸ˜„



  • @Nca78 Do you have any updates on the sensor you were building or the Honeywell code?

    I am thinking of moving the existing sensor to another room and getting a new noe for the bedroom.


  • Hardware Contributor

    Hello @skywatch, I will put a library online today. Sorry for the long delay.


  • Hardware Contributor

    @skywatch here you go :
    https://github.com/Nca78/NcaPMSensorLight

    @Sebex in case you're still looking for a code, you might be interested.



  • @Nca78 Thanks πŸ™‚

    I didn't purchase another sensor yet, but will in the furure for sure. Thanks for sharing it will help a lot of people I expect (not just me)! πŸ˜‰


  • Mod


Log in to reply
 

Suggested Topics

  • 4
  • 2
  • 3
  • 17
  • 20
  • 9

21
Online

11.4k
Users

11.1k
Topics

112.7k
Posts