├── .gitattributes ├── MH-Z19 └── MH-Z19.ino ├── README.md ├── SCD30 └── SCD30.ino └── SGP30 └── SGP30.ino /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /MH-Z19/MH-Z19.ino: -------------------------------------------------------------------------------- 1 | /* 2 | This shetch reads values from the Winsen MH-Z19 CO2 sensor and transmits the results via MQTT 3 | It can be adabted to the ESP8266 by using the AsynchMQTT Library example of the ESP8266 and by filling in the needed sensor commands from this sketch 4 | 5 | It is based on the examples of the libraries 6 | 7 | Copyright: Andreas Spiess 2019 8 | */ 9 | #include 10 | extern "C" { 11 | #include "freertos/FreeRTOS.h" 12 | #include "freertos/timers.h" 13 | } 14 | #include 15 | #include 16 | #include 17 | // #include 18 | 19 | #define MQTT_HOST IPAddress(192, 168, 0, 203) 20 | #define MQTT_PORT 1883 21 | 22 | 23 | MHZ19 mhz(&Serial2); 24 | AsyncMqttClient mqttClient; 25 | TimerHandle_t mqttReconnectTimer; 26 | TimerHandle_t wifiReconnectTimer; 27 | 28 | void connectToWifi() { 29 | Serial.println("Connecting to Wi-Fi..."); 30 | WiFi.begin(mySSID, myPASSWORD); 31 | } 32 | 33 | void connectToMqtt() { 34 | Serial.println("Connecting to MQTT..."); 35 | mqttClient.connect(); 36 | } 37 | 38 | void WiFiEvent(WiFiEvent_t event) { 39 | Serial.printf("[WiFi-event] event: %d\n", event); 40 | switch (event) { 41 | case SYSTEM_EVENT_STA_GOT_IP: 42 | Serial.println("WiFi connected"); 43 | Serial.println("IP address: "); 44 | Serial.println(WiFi.localIP()); 45 | connectToMqtt(); 46 | break; 47 | case SYSTEM_EVENT_STA_DISCONNECTED: 48 | Serial.println("WiFi lost connection"); 49 | xTimerStop(mqttReconnectTimer, 0); // ensure we don't reconnect to MQTT while reconnecting to Wi-Fi 50 | xTimerStart(wifiReconnectTimer, 0); 51 | break; 52 | } 53 | } 54 | 55 | void onMqttConnect(bool sessionPresent) { 56 | Serial.println("Connected to MQTT."); 57 | Serial.print("Session present: "); 58 | Serial.println(sessionPresent); 59 | uint16_t packetIdSub = mqttClient.subscribe("test/lol", 2); 60 | Serial.print("Subscribing at QoS 2, packetId: "); 61 | Serial.println(packetIdSub); 62 | mqttClient.publish("MH-Z19/status", 0, true, "MH-Z19 up"); 63 | Serial.println("Publishing at QoS 0"); 64 | /* 65 | uint16_t packetIdPub1 = mqttClient.publish("test/lol", 1, true, "test 2"); 66 | Serial.print("Publishing at QoS 1, packetId: "); 67 | Serial.println(packetIdPub1); 68 | uint16_t packetIdPub2 = mqttClient.publish("test/lol", 2, true, "test 3"); 69 | Serial.print("Publishing at QoS 2, packetId: "); 70 | Serial.println(packetIdPub2); 71 | */ 72 | } 73 | 74 | void onMqttDisconnect(AsyncMqttClientDisconnectReason reason) { 75 | Serial.println("Disconnected from MQTT."); 76 | 77 | if (WiFi.isConnected()) { 78 | xTimerStart(mqttReconnectTimer, 0); 79 | } 80 | } 81 | 82 | void onMqttSubscribe(uint16_t packetId, uint8_t qos) { 83 | Serial.println("Subscribe acknowledged."); 84 | Serial.print(" packetId: "); 85 | Serial.println(packetId); 86 | Serial.print(" qos: "); 87 | Serial.println(qos); 88 | } 89 | 90 | void onMqttUnsubscribe(uint16_t packetId) { 91 | Serial.println("Unsubscribe acknowledged."); 92 | Serial.print(" packetId: "); 93 | Serial.println(packetId); 94 | } 95 | 96 | void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) { 97 | Serial.println("Publish received."); 98 | Serial.print(" topic: "); 99 | Serial.println(topic); 100 | Serial.print(" qos: "); 101 | Serial.println(properties.qos); 102 | Serial.print(" dup: "); 103 | Serial.println(properties.dup); 104 | Serial.print(" retain: "); 105 | Serial.println(properties.retain); 106 | Serial.print(" len: "); 107 | Serial.println(len); 108 | Serial.print(" index: "); 109 | Serial.println(index); 110 | Serial.print(" total: "); 111 | Serial.println(total); 112 | } 113 | 114 | void onMqttPublish(uint16_t packetId) { 115 | Serial.println("Publish acknowledged."); 116 | Serial.print(" packetId: "); 117 | Serial.println(packetId); 118 | } 119 | 120 | 121 | void setup() 122 | { 123 | Serial.begin(115200); 124 | Serial.println("SCD30 Example"); 125 | 126 | mqttReconnectTimer = xTimerCreate("mqttTimer", pdMS_TO_TICKS(2000), pdFALSE, (void*)0, reinterpret_cast(connectToMqtt)); 127 | wifiReconnectTimer = xTimerCreate("wifiTimer", pdMS_TO_TICKS(2000), pdFALSE, (void*)0, reinterpret_cast(connectToWifi)); 128 | 129 | WiFi.onEvent(WiFiEvent); 130 | 131 | mqttClient.onConnect(onMqttConnect); 132 | mqttClient.onDisconnect(onMqttDisconnect); 133 | mqttClient.onSubscribe(onMqttSubscribe); 134 | mqttClient.onUnsubscribe(onMqttUnsubscribe); 135 | mqttClient.onMessage(onMqttMessage); 136 | mqttClient.onPublish(onMqttPublish); 137 | mqttClient.setServer(MQTT_HOST, MQTT_PORT); 138 | Serial2.begin(9600); 139 | connectToWifi(); 140 | } 141 | 142 | void loop() 143 | { 144 | static String payload; 145 | MHZ19_RESULT response = mhz.retrieveData(); 146 | if (response == MHZ19_RESULT_OK) 147 | { 148 | 149 | int co2 = mhz.getCO2(); 150 | Serial.print("co2(ppm):"); 151 | Serial.print(co2, 1); 152 | 153 | float temp = mhz.getTemperature(); 154 | Serial.print(" temp(C):"); 155 | Serial.print(temp); 156 | 157 | float hum = 0.0; 158 | int accuracy = mhz.getAccuracy(); 159 | Serial.print(" Accuracy:"); 160 | Serial.println(accuracy, 1); 161 | 162 | payload = "{\"co2 \":" + String(co2) + ", \"temperature \":" + temp + ", \"accuracy \":" + accuracy + "}"; 163 | Serial.println(payload); 164 | 165 | static char payloadStr[100]; 166 | payload.toCharArray(payloadStr, payload.length() + 1); 167 | mqttClient.publish("MH-Z19/data", 0, true, payloadStr); 168 | } 169 | else 170 | { 171 | Serial.print(F("Error, code: ")); 172 | Serial.println(response); 173 | } 174 | 175 | delay(120 * 1000); 176 | } 177 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CO2-Sensors 2 | 3 | These are experimental sketches and not at all optimized. Please feel free to create a pull request if you want to increase the quality... 4 | 5 | Based on this video: https://youtu.be/FL0L-nic9Vw 6 | -------------------------------------------------------------------------------- /SCD30/SCD30.ino: -------------------------------------------------------------------------------- 1 | /* 2 | This shetch reads values from the Sensirion SCD30 CO2 sensor and transmits it via MQTT 3 | It can be adabted to the ESP32 by using the AsynchMQTT Library example of the ESP32 and by filling in the needed sensor commands from this sketch 4 | 5 | It is based on the examples of the libraries 6 | 7 | Copyright: Andreas Spiess 2019 8 | 9 | Copyright: Andreas Spiess 2019 10 | */ 11 | #include 12 | #include 13 | #include 14 | #include 15 | // #include 16 | 17 | unsigned long entryLoop, entryPublish; 18 | static String payload; 19 | int co2; 20 | float temp, hum; 21 | 22 | #define MQTT_HOST IPAddress(192, 168, 0, 203) 23 | #define MQTT_PORT 1883 24 | 25 | //Click here to get the library: http://librarymanager/All#SparkFun_SCD30 26 | #include "SparkFun_SCD30_Arduino_Library.h" 27 | 28 | SCD30 airSensor; 29 | AsyncMqttClient mqttClient; 30 | Ticker mqttReconnectTimer; 31 | 32 | WiFiEventHandler wifiConnectHandler; 33 | WiFiEventHandler wifiDisconnectHandler; 34 | Ticker wifiReconnectTimer; 35 | 36 | void connectToWifi() { 37 | Serial.println("Connecting to Wi-Fi..."); 38 | WiFi.begin(mySSID, myPASSWORD); 39 | } 40 | 41 | void onWifiConnect(const WiFiEventStationModeGotIP& event) { 42 | Serial.println("Connected to Wi-Fi."); 43 | connectToMqtt(); 44 | } 45 | 46 | void onWifiDisconnect(const WiFiEventStationModeDisconnected& event) { 47 | Serial.println("Disconnected from Wi-Fi."); 48 | mqttReconnectTimer.detach(); // ensure we don't reconnect to MQTT while reconnecting to Wi-Fi 49 | wifiReconnectTimer.once(2, connectToWifi); 50 | } 51 | 52 | void connectToMqtt() { 53 | Serial.println("Connecting to MQTT..."); 54 | mqttClient.connect(); 55 | } 56 | 57 | void onMqttConnect(bool sessionPresent) { 58 | Serial.println("Connected to MQTT."); 59 | Serial.print("Session present: "); 60 | Serial.println(sessionPresent); 61 | uint16_t packetIdSub = mqttClient.subscribe("test/lol", 2); 62 | Serial.print("Subscribing at QoS 2, packetId: "); 63 | Serial.println(packetIdSub); 64 | mqttClient.publish("SDG30/status", 0, true, "SDG30 up"); 65 | /* 66 | Serial.println("Publishing at QoS 0"); 67 | uint16_t packetIdPub1 = mqttClient.publish("test/lol", 1, true, "test 2"); 68 | Serial.print("Publishing at QoS 1, packetId: "); 69 | Serial.println(packetIdPub1); 70 | uint16_t packetIdPub2 = mqttClient.publish("test/lol", 2, true, "test 3"); 71 | Serial.print("Publishing at QoS 2, packetId: "); 72 | Serial.println(packetIdPub2); 73 | */ 74 | } 75 | 76 | void onMqttDisconnect(AsyncMqttClientDisconnectReason reason) { 77 | Serial.println("Disconnected from MQTT."); 78 | 79 | if (WiFi.isConnected()) { 80 | mqttReconnectTimer.once(2, connectToMqtt); 81 | } 82 | } 83 | 84 | void onMqttSubscribe(uint16_t packetId, uint8_t qos) { 85 | Serial.println("Subscribe acknowledged."); 86 | Serial.print(" packetId: "); 87 | Serial.println(packetId); 88 | Serial.print(" qos: "); 89 | Serial.println(qos); 90 | } 91 | 92 | void onMqttUnsubscribe(uint16_t packetId) { 93 | Serial.println("Unsubscribe acknowledged."); 94 | Serial.print(" packetId: "); 95 | Serial.println(packetId); 96 | } 97 | 98 | void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) { 99 | Serial.println("Publish received."); 100 | Serial.print(" topic: "); 101 | Serial.println(topic); 102 | Serial.print(" qos: "); 103 | Serial.println(properties.qos); 104 | Serial.print(" dup: "); 105 | Serial.println(properties.dup); 106 | Serial.print(" retain: "); 107 | Serial.println(properties.retain); 108 | Serial.print(" len: "); 109 | Serial.println(len); 110 | Serial.print(" index: "); 111 | Serial.println(index); 112 | Serial.print(" total: "); 113 | Serial.println(total); 114 | } 115 | 116 | void onMqttPublish(uint16_t packetId) { 117 | Serial.println("Publish acknowledged."); 118 | Serial.print(" packetId: "); 119 | Serial.println(packetId); 120 | } 121 | 122 | 123 | void setup() 124 | { 125 | Serial.begin(115200); 126 | Serial.println("SCD30 Example"); 127 | 128 | wifiConnectHandler = WiFi.onStationModeGotIP(onWifiConnect); 129 | wifiDisconnectHandler = WiFi.onStationModeDisconnected(onWifiDisconnect); 130 | 131 | mqttClient.onConnect(onMqttConnect); 132 | mqttClient.onDisconnect(onMqttDisconnect); 133 | mqttClient.onSubscribe(onMqttSubscribe); 134 | mqttClient.onUnsubscribe(onMqttUnsubscribe); 135 | mqttClient.onMessage(onMqttMessage); 136 | mqttClient.onPublish(onMqttPublish); 137 | mqttClient.setServer(MQTT_HOST, MQTT_PORT); 138 | 139 | connectToWifi(); 140 | Serial.println("WiFi connected"); 141 | Wire.begin(); // ESP8266 SCL 142 | // Wire.setClock(100000); 143 | airSensor.begin(); //This will cause readings to occur every two seconds 144 | airSensor.setMeasurementInterval(2); 145 | // airSensor.setAltitudeCompensation(350); 146 | airSensor.setAmbientPressure(1001); 147 | airSensor.beginMeasuring(); 148 | while (!airSensor.dataAvailable()); { 149 | Serial.print("."); 150 | delay(100); 151 | } 152 | co2 = airSensor.getCO2(); 153 | Serial.print("co2(ppm):"); 154 | Serial.print(co2, 1); 155 | 156 | temp = airSensor.getTemperature(); 157 | Serial.print(" temp(C):"); 158 | Serial.print(temp); 159 | 160 | hum = airSensor.getHumidity(); 161 | Serial.print(" humidity(%):"); 162 | Serial.println(hum, 1); 163 | delay(100); 164 | 165 | } 166 | 167 | void loop() 168 | { 169 | while (!airSensor.dataAvailable()) { 170 | Serial.print("."); 171 | delay(100); 172 | } 173 | co2 = ((19 * co2) + airSensor.getCO2()) / 20; 174 | Serial.print("co2(ppm):"); 175 | Serial.print(co2, 1); 176 | 177 | temp = ((19 * temp) + airSensor.getTemperature()) / 20.0; 178 | Serial.print(" temp(C):"); 179 | Serial.print(temp); 180 | 181 | hum = ((19 * hum) + airSensor.getHumidity()) / 20.0; 182 | Serial.print(" humidity(%):"); 183 | Serial.print(hum, 1); 184 | 185 | Serial.print(" "); 186 | Serial.println((millis() - entryPublish) / 1000); 187 | 188 | if (millis() - entryPublish > 120000) { 189 | payload = "{\"co2 \":" + String(co2) + ", \"temperature \":" + temp + ", \"humidity \":" + hum + "}"; 190 | Serial.println(payload); 191 | 192 | static char payloadStr[100]; 193 | payload.toCharArray(payloadStr, payload.length() + 1); 194 | Serial.println(payloadStr); 195 | mqttClient.publish("SDG30/data", 0, true, payloadStr); 196 | entryPublish = millis(); 197 | } 198 | Serial.println(); 199 | while (millis() - entryLoop < 2500) yield(); 200 | entryLoop = millis(); 201 | } 202 | -------------------------------------------------------------------------------- /SGP30/SGP30.ino: -------------------------------------------------------------------------------- 1 | /* 2 | This shetch reads values from the Sensirion SGP30 VOC sensor and transmits it via MQTT 3 | It can be adabted to the ESP32 by using the AsynchMQTT Library example of the ESP32 and by filling in the needed sensor commands from this sketch 4 | 5 | It is based on the examples of the libraries 6 | 7 | Copyright: Andreas Spiess 2019 8 | 9 | Copyright: Andreas Spiess 2019 10 | */ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | unsigned int eco2; 20 | unsigned int tvoc; 21 | char payloadStr[100]; 22 | unsigned long entryLoop, entryPublish; 23 | 24 | #define MQTT_HOST IPAddress(192, 168, 0, 203) 25 | #define MQTT_PORT 1883 26 | 27 | Adafruit_SGP30 sgp; 28 | 29 | AsyncMqttClient mqttClient; 30 | Ticker mqttReconnectTimer; 31 | 32 | WiFiEventHandler wifiConnectHandler; 33 | WiFiEventHandler wifiDisconnectHandler; 34 | Ticker wifiReconnectTimer; 35 | 36 | uint32_t getAbsoluteHumidity(float temperature, float humidity) { 37 | // approximation formula from Sensirion SGP30 Driver Integration chapter 3.15 38 | const float absoluteHumidity = 216.7f * ((humidity / 100.0f) * 6.112f * exp((17.62f * temperature) / (243.12f + temperature)) / (273.15f + temperature)); // [g/m^3] 39 | const uint32_t absoluteHumidityScaled = static_cast(1000.0f * absoluteHumidity); // [mg/m^3] 40 | return absoluteHumidityScaled; 41 | } 42 | 43 | void connectToWifi() { 44 | Serial.println("Connecting to Wi-Fi..."); 45 | WiFi.begin(mySSID, myPASSWORD); 46 | } 47 | 48 | void onWifiConnect(const WiFiEventStationModeGotIP& event) { 49 | Serial.println("Connected to Wi-Fi."); 50 | connectToMqtt(); 51 | } 52 | 53 | void onWifiDisconnect(const WiFiEventStationModeDisconnected& event) { 54 | Serial.println("Disconnected from Wi-Fi."); 55 | mqttReconnectTimer.detach(); // ensure we don't reconnect to MQTT while reconnecting to Wi-Fi 56 | wifiReconnectTimer.once(2, connectToWifi); 57 | } 58 | 59 | void connectToMqtt() { 60 | Serial.println("Connecting to MQTT..."); 61 | mqttClient.connect(); 62 | } 63 | 64 | void onMqttConnect(bool sessionPresent) { 65 | Serial.println("Connected to MQTT."); 66 | Serial.print("Session present: "); 67 | Serial.println(sessionPresent); 68 | uint16_t packetIdSub = mqttClient.subscribe("test/lol", 2); 69 | Serial.print("Subscribing at QoS 2, packetId: "); 70 | Serial.println(packetIdSub); 71 | mqttClient.publish("SGP30/status", 0, true, "SGP30 up"); 72 | /* 73 | Serial.println("Publishing at QoS 0"); 74 | uint16_t packetIdPub1 = mqttClient.publish("test/lol", 1, true, "test 2"); 75 | Serial.print("Publishing at QoS 1, packetId: "); 76 | Serial.println(packetIdPub1); 77 | uint16_t packetIdPub2 = mqttClient.publish("test/lol", 2, true, "test 3"); 78 | Serial.print("Publishing at QoS 2, packetId: "); 79 | Serial.println(packetIdPub2); 80 | */ 81 | } 82 | 83 | void onMqttDisconnect(AsyncMqttClientDisconnectReason reason) { 84 | Serial.println("Disconnected from MQTT."); 85 | 86 | if (WiFi.isConnected()) { 87 | mqttReconnectTimer.once(2, connectToMqtt); 88 | } 89 | } 90 | 91 | void onMqttSubscribe(uint16_t packetId, uint8_t qos) { 92 | Serial.println("Subscribe acknowledged."); 93 | Serial.print(" packetId: "); 94 | Serial.println(packetId); 95 | Serial.print(" qos: "); 96 | Serial.println(qos); 97 | } 98 | 99 | void onMqttUnsubscribe(uint16_t packetId) { 100 | Serial.println("Unsubscribe acknowledged."); 101 | Serial.print(" packetId: "); 102 | Serial.println(packetId); 103 | } 104 | 105 | void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) { 106 | Serial.println("Publish received."); 107 | Serial.print(" topic: "); 108 | Serial.println(topic); 109 | Serial.print(" qos: "); 110 | Serial.println(properties.qos); 111 | Serial.print(" dup: "); 112 | Serial.println(properties.dup); 113 | Serial.print(" retain: "); 114 | Serial.println(properties.retain); 115 | Serial.print(" len: "); 116 | Serial.println(len); 117 | Serial.print(" index: "); 118 | Serial.println(index); 119 | Serial.print(" total: "); 120 | Serial.println(total); 121 | } 122 | 123 | void onMqttPublish(uint16_t packetId) { 124 | Serial.println("Publish acknowledged."); 125 | Serial.print(" packetId: "); 126 | Serial.println(packetId); 127 | } 128 | 129 | 130 | void setup() 131 | { 132 | Serial.begin(115200); 133 | Serial.println("SGP30 test"); 134 | 135 | if (! sgp.begin()) { 136 | Serial.println("Sensor not found :("); 137 | while (1); 138 | } 139 | Serial.print("Found SGP30 serial #"); 140 | Serial.print(sgp.serialnumber[0], HEX); 141 | Serial.print(sgp.serialnumber[1], HEX); 142 | Serial.println(sgp.serialnumber[2], HEX); 143 | wifiConnectHandler = WiFi.onStationModeGotIP(onWifiConnect); 144 | wifiDisconnectHandler = WiFi.onStationModeDisconnected(onWifiDisconnect); 145 | 146 | mqttClient.onConnect(onMqttConnect); 147 | mqttClient.onDisconnect(onMqttDisconnect); 148 | mqttClient.onSubscribe(onMqttSubscribe); 149 | mqttClient.onUnsubscribe(onMqttUnsubscribe); 150 | mqttClient.onMessage(onMqttMessage); 151 | mqttClient.onPublish(onMqttPublish); 152 | mqttClient.setServer(MQTT_HOST, MQTT_PORT); 153 | 154 | connectToWifi(); 155 | Serial.println("WiFi connected"); 156 | 157 | sgp.setHumidity(8200); //8200 mg/m3 humidity (35% at 25 degrees 158 | 159 | // the first eco2 meaurements are always 400 160 | do { 161 | while (!sgp.IAQmeasure()) { 162 | Serial.print("."); 163 | delay(100); 164 | } 165 | tvoc = sgp.TVOC; 166 | Serial.print("TVOC(ppb):"); 167 | Serial.print(tvoc); 168 | 169 | eco2 = sgp.eCO2; 170 | tvoc = sgp.TVOC; 171 | Serial.print(" eco2(ppm):"); 172 | Serial.println(eco2); 173 | delay(1000); 174 | } while (eco2 == 400); 175 | entryLoop = millis(); 176 | } 177 | 178 | void loop() 179 | { 180 | static String payload; 181 | while (!sgp.IAQmeasure()) { 182 | Serial.print("."); 183 | delay(100); 184 | } 185 | 186 | tvoc = ((19 * tvoc) + sgp.TVOC) / 20; 187 | Serial.print("TVOC(ppb):"); 188 | Serial.print(tvoc); 189 | 190 | eco2 = ((19 * eco2) + sgp.eCO2) / 20; 191 | Serial.print(" eco2(ppm):"); 192 | Serial.print(eco2); 193 | 194 | 195 | while (!sgp.IAQmeasureRaw()) { 196 | Serial.print("."); 197 | delay(100); 198 | } 199 | unsigned int rawh2 = sgp.rawH2; 200 | Serial.print(" rawH2:"); 201 | Serial.print(rawh2); 202 | 203 | unsigned int ethanol = sgp.rawEthanol; 204 | Serial.print(" Ethanol:"); 205 | Serial.print(ethanol); 206 | 207 | Serial.print(" "); 208 | Serial.println((millis() - entryPublish) / 1000); 209 | 210 | if (millis() - entryPublish > 120000) { 211 | payload = "{\"tvoc\":" + String(tvoc) + ", \"eco2\":" + String(eco2) + ", \"rawh2\":" + String(rawh2) + ", \"ethanol\":" + String(ethanol) + "}"; 212 | Serial.println(payload); 213 | payload.toCharArray(payloadStr, payload.length() + 1); 214 | mqttClient.publish("SGP30/data", 0, true, payloadStr); 215 | entryPublish = millis(); 216 | } 217 | Serial.println(); 218 | while (millis() - entryLoop < 1000) yield(); 219 | entryLoop = millis(); 220 | 221 | } 222 | --------------------------------------------------------------------------------