Connecting the gas sensor to the Raspberry Pi

The MQ series of gas sensors have an analog output. The Raspberry Pi is a marvel of connectivity. It’s 40 pin header and associated peripheral ports provide a spectacular range of options to interface with the world outside the Raspberry Pi. However, one feature that the Pi doesn’t have built in is the facility to accept an analog input.

Analog and Digital

Signals (or even information in general) can be broken down into two different types; Analog and digital.

Analog

An analog signal is one that has an infinitely variable range of values that can change over time.

Analog
Analog

If we consider the question of how much light is shining outside we could imagine that the level of brightnesses varies between the blackness of a moonless night and a overcast sky to a cloudless day with the sun high in the sky.

These are rough approximations of dark and light, but between the two extremes is a range of brightness levels which are always changing. If we wanted to measure how bright it was at any particular time we could set ourselves a numeric range of 0 representing the middle of the night and 100 representing the middle of the day and the number that represented the brightness at any particular time would be somewhere between those two numbers. Typically in electronics an analog signal is a voltage that will be anywhere on that variable range between two limits.

Digital

A digital signal represents information as discrete values.

Digital
Digital

For example at it’s most fundamental the light level outside could be described as dark or light. Represented numerically this could be dark = 0 and bright = 1. While this is perfectly valid, we would often prefer to have a little more granularity in our measurement and so we can increase the number of discrete steps that represent light levels to match our expectations of the type of information we’re interested in. if we add another couple of levels in we could have light that was dark = 0, dim = 0.33, glowing = 0.66 and bright = 1. We can continue to improve the resolution of our numerical perception of the level of light in a process that is called Analog to Digital Conversion or ADC.

Analog to Digital Conversion (ADC)

Luckily there are a wide range of options available to convert analog signals into digital ones. Therefore, people wanting to input an analog signal into a Raspberry Pi can simply include a separate ADC into their project and it will work wonderfully. That’s what we’re going to do here using the ADS1015 from Adafruit. The ADS1015 has a 12bit resolution giving it the ability to convert an analog signal into one of 4096 discrete levels.

The Sensor

MQ-2 Gas Sensor
MQ-2 Gas Sensor

The MQ-2 is a commonly used gas sensors in MQ sensor series. It is what referred to as a Chemiresistor as the detection is based upon change of resistance of the sensing material when the gas comes in contact with a Metal Oxide Semiconductor (MOS). The value of the analog signal output varies as the gas concentration varies.

Different metal oxides have different chemiresistive properties allowing them to sense different gasses.

The most obvious feature of the sensor is the surrounding layer (actually two layers) of stainless steel mesh called an ‘anti-explosion network’. This is present to make sure that the heater element inside the sensor doesn’t cause an explosion while it is in the presence of flammable gasses. It also acts as a filter to allow only gases to pass through to the sensor.

The MQ-2 which we will be using sensor can detect LPG, butane, propane, methane, alcohol, Hydrogen and smoke concentrations from 200 to 10000ppm.

The sensor we will be using is mounted on a circuit board for ease of connection. We provide it with a 5VDC supply and it returns an analog signal that varies in proportion to the concentration of our target gas. That signal will vary between 0VDC and 5VDC. The board also includes a digital output option, but this is designed to provide a breakpoint level of gas, rather than a value. The variable resistor (potentiometer) on the board allows this breakpoint to be varied.

MQ-2 Sensor Board Underside
MQ-2 Sensor Board Underside

The connections on the board are as follows

  • VCC: Is the power input. This will require 5VDC applied
  • GND: Is the ground pin
  • DO: Provides the digital output set by the potentiometer
  • AO: is the analog output signal.

It is this analog voltage that is then digitised with our Analog to Digital Converter (ADC).

Because the digital input to our Pi has a maximum voltage of 3.3V, we will use a voltage divider network to reduce the maximum output from our sensor prior to is being applied to the ADC.

Analog to Digital Conversion (ADC)

There are a wide range of options available to convert analog signals into digital ones. Therefore, people wanting to receive an analog signal with a Raspberry Pi can simply include a separate ADC into their project and it will work wonderfully. That’s what we’re going to do here using the ADS1015 from Adafruit. The ADS1015 has a 12bit resolution giving it the ability to convert an analog signal into one of 4096 discrete levels.

The ADS1015 Analog to Digital Converter

The ADS1015 is actually a component on our ADC board. This component is manufactured by integrated circuits manufacturer Texas Instruments. The circuit board that we’re using in the project is from Adafruit. It incorporates some interconnection circuity to make the signals as stable as practical and to provide a convenient physical interface (via header pins). The ADS1015 provides 12-bit (4096 levels) precision at up to 3300 readings per second (the rate is programmable). The board can be configured to accept four sensors of the type we will be using (single-ended), or two differential channels (which use two varying signals instead of a single signal and a ground). There is also a programmable gain amplifier built in with up to x16 gain, to help amplify smaller signals to the full range. The ADC can operate on a voltage range from 2V to 5V Which is applied to the VDD pin).

ADS1015 Sensor Board
ADS1015 Sensor Board

If all this sounds a bit ‘electrickery’, don’t worry. The aim here is to provide ourselves with enough information to get us started and if we feel like pressing on and learning more we will :-).

The ADS1015 will send the digital levels to the Pi via the I2C communications protocol. The address that this connection is made on can be changed to one of four options so you can have up to 4 ADS1015’s connected for up 16 sensor inputs!

Measure

Hardware required

  • A Raspberry Pi (huge range of sources)
  • ADS1015 Analog to Digital Converter from Adafruit.
  • Female to Female Dupont connector cables (Deal Extreme or build your own!)
  • MQ-2 gas sensor module.
  • 3 x 1k Ohm resistors
  • Dupont connectors, various

Connect

The gas sensor board should be connected with ground pin (labelled ‘GND’) to a ground connector, the voltage pin (labelled ‘VCC’) to a 5V connector and the analog output pin (labelled ‘AO’) to the single resistor end of our voltage divider.

The ADS1015 board should have the VDD pin connected to a 3.3V pin, the GND to a ground pin, the SCL pin to the SCL I2C connector on pin 5 and the SDA pin to the SDA I2C connector on pin 3, the A0 pin to the double resistor end of the voltage divider and lastly the ADDR pin should be connected to ground.

Both boards will support a connection to the ‘VDD’ and reference voltage connector of 5V, but this is not advisable for the Raspberry Pi as the resulting signal levels on the SDA connector may be higher than desired for the Pi’s input. This connection can be safely used with an Arduino board.

Connection diagram
Connection diagram

Connecting the sensor practically can be achieved in a number of ways. You could use a Pi Cobbler break out connector mounted on a bread board connected to the appropriate pins. But because the connection is relatively simple we could build a minimal configuration that will plug directly onto the pins using Dupont header connectors and jumper wire. The image below shows how simple this can be.

Physical Connection of ADS1015 and gas Sensor
Physical Connection of ADS1015 and gas Sensor

Test

Since the ADS1015 uses the I2C protocol to communicate, we need to load the appropriate kernel support modules onto the Raspberry Pi to allow this to happen.

Firstly make sure that our software is up to date

Since we are using the Raspbian distribution there is a simple method to start the process of configuring the Pi to use the I2C protocol.

We can start by running the command;

This will start the Raspberry Pi Software Configuration Tool.

On the first page select the Interfacing Options with the arrow keys and then tab to select

Interfacing Options
Interfacing Options

Then we select the I2C option for automatic loading of the kernel module;

Automatic Loading
Automatic Loading

Would we like the ARM I2C interface to be enabled? Yes we would;

ARM I2C Interface Enabled
ARM I2C Interface Enabled

Press ‘OK’ to acknowledge that the interface is enabled.

Enabled!
Enabled!

Press tab to select ‘Finish’.

We're Finished
We’re Finished

There’s still some work to do to get things sorted. We need to check the /etc/modules file using:

Where we need to ensure that the following line is at the end of the file:

i2c-dev

Under some circumstances (depending on the kernel version we are using) we would also need to update the /boot/config.txt file. We can do this using;

Make sure that the following line is uncommented (the ‘#’ is removed from in front of the line) in the file;

dtparam=i2c_arm=on

The we should load tools for working with I2C devices using the following command;

… and now we should reboot to load the config.txt if we changed it earlier

We can now check to see if our sensor is working using;

The output should look something like;

pi@raspberrypi ~ $ sudo i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- 48 -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

This shows us that we have detected our ADS1015 on address ‘48’. The ADS1015 can support four different addresses as shown on page 17 of the data sheet. The address is selected by what the ADDR (short for address!) pin on the board is connected to;

ADDR PIN   ADDRESS
Ground     48
VDD        49
SDA        4A
SCL        4B

As we noted earlier, this means we can connect up to four ADS1015’s on the same I2C bus.

Now we want to install Python libraries designed to read the values from the ADS1015. The library we are going to use was designed specifically to work with the Adafruit ADS1015/ADS1115 ADCs. In carrying out this library development, Adafruit have invested a not inconsiderable amount of time and resources. In return please consider supporting Adafruit and open-source hardware by purchasing products from Adafruit!

We need to change our default version of python running on the Pi to python 3

We can check what version is running by executing rhe following command;

If that indicates python 2.x, then we need to change that;

To find out what versions of Python 3 is available run the following

Hopefully you will see a 3.x version.

To change the default python version system-wide we can use the update-alternatives command. First list all available python alternatives;

There is a good chance that the output will be something like;

update-alternatives: error: no alternatives for python

The above error message means that no python alternatives have been recognised by the update-alternatives command. For this reason we need to update our alternatives table and include both python 2 and 3.

The last number on each of the previous lines is the priority, with the higher number being the highest priority

We can check again by running;

Now the default version should be Python 3

Install pip3 (for Python 3)

Then install setuptools via pip

Install python libraries

Install the ADC libraries (Note: I occasionally get errors when carrying out the following command and need to re-run it a few times);

We should now be able to run a simple program to test our sensor.

Make a file called gas-print.py (using nano) with the following contents in the home directory

#!/usr/bin/python
#encoding:utf-8

import time
import board
import busio
import adafruit_ads1x15.ads1015 as ADS
from adafruit_ads1x15.analog_in import AnalogIn

# Create the I2C bus
i2c = busio.I2C(board.SCL, board.SDA)

# Create the ADC object using the I2C bus
ads = ADS.ADS1015(i2c)
ads.gain = 8

# Create single-ended input on channel 0
chan = AnalogIn(ads, ADS.P0)

# Create differential input between channel 0 and 1
#chan = AnalogIn(ads, ADS.P0, ADS.P1)

print("{:>5}\t{:>5}".format('raw', 'v'))

while True:
    print("{:>5}\t{:>5.5f}".format(chan.value, chan.voltage))
    time.sleep(0.5)

The file above is essentially the simpletest.py file that Adafruit publish with a couple of minor amendments.

Now we can run it using

We should see a print out of the values represented by the value of the ADC as an integer and the voltage from the ADC as a floating point value.

  raw       v
   69   0.01726
   70   0.01751
   71   0.01751
   70   0.01826
   72   0.01776
   71   0.01776
   79   0.01926
   87   0.02151
   88   0.02276
   93   0.02351
   90   0.02251
   89   0.02026
   85   0.02126
   83   0.02076
   81   0.02026
   80   0.02001

In the example above the sensor was reading around the 70-ish mark and I breathed on it. The values rose to the 90-ish mark before starting to drop away.

We can press Ctrl-C to stop the program.

Bearing mind that we are wanting to be able to detect volatile gases, I directed some butane from a small blow torch (without igniting it) onto the sensor and saw the following;

  raw       v
   80   0.01951
   79   0.01976
   79   0.02001
  120   0.03102
 1447   0.36268
 1717   0.42846
 1809   0.45297
 1848   0.46248
 1859   0.46498
 1905   0.47648
 1665   0.41570
 1243   0.31015
  903   0.22511
  704   0.17584
  585   0.14807
  509   0.12731
  460   0.11506
  420   0.10480

At this point we have successfully read and displayed an analog signal from a gas sensor using the Raspberry Pi.

Record

To record this data we will use a Python program that connects to our sensor via the ADC, reads the returned values and writes them into our database. At the same time a time stamp will be added automatically.

Our Python program will use cron to execute the program at a regular intervals (We used this earlier to automatically reconnect to the network if required.).

Record the readings

The following Python code simply reads channel 0 of our ADC and writes the value for time, value and voltage into our database.

The full code can be found in the code samples bundled with this book (gas-record.py).

#!/usr/bin/python
#encoding:utf-8

import time
import sqlite3
import board
import busio
import adafruit_ads1x15.ads1015 as ADS
from adafruit_ads1x15.analog_in import AnalogIn

print("Gas sensor measurement in progress")

# Create the I2C bus
i2c = busio.I2C(board.SCL, board.SDA)

# Create the ADC object using the I2C bus
ads = ADS.ADS1015(i2c)

# Adjust the Gain
ads.gain = 8

# Create single-ended input on channel 0
chan = AnalogIn(ads, ADS.P0)

# Get the time in the right format
dtg = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())

# Print the time and LDR values
print("Local current time :", dtg)
print("Value via ADC :", chan.value)
print("Voltage via ADC :", chan.voltage)

# Write the values to the database
try:
    # Opens a file called measurements
    db = sqlite3.connect('/home/pi/measurements')
    # Get a cursor object
    cursor = db.cursor()
    # Insers the values into the table
    cursor.execute('''INSERT INTO gas(dtg, value, voltage)
                  VALUES(?,?,?)''', (dtg,chan.value,chan.voltage))
    # Commit the change
    db.commit()
# Catch any exception
except Exception as e:
    # Roll back any change if something goes horribly wrong
    db.rollback()
    raise e
finally:
    # Close the db connection
    db.close()

This script can be saved in our home directory (/home/pi) and can be run by typing;

The output should look something like this;

Gas sensor measurement in progress
Local current time : 2019-04-21 13:14:14
Value via ADC : 82
Voltage via ADC : 0.01875915974596971

Run this script a few times and then we can check the results in our database by starting up SQLite as follows;

From the SQLite prompt we can query the database to return all the records using SELECT * FROM gas; as follows;

sqlite> select * from gas;
2019-04-21 13:14:06|81|0.0210102589154861
2019-04-21 13:14:14|83|0.020009770395701
2019-04-21 13:17:32|83|0.0205100146555935
sqlite>

There are three records, including the times they were taken and the levels recorded.

Recording data on a regular basis with cron

As mentioned earlier, while our code is a thing of beauty, it only records a single entry every time it is run.

What we need to implement is a schedule so that at a regular time, the program is run. This is achieved using cron via the crontab.

To set up our schedule we need to edit the crontab file. This is is done using the following command;

Once run it will open the crontab in the nano editor. We want to add in an entry at the end of the file that looks like the following;

* * * * * /usr/bin/python /home/pi/gas-record.py

This instructs the computer that every minute, every hour of every day of every month we run the command /usr/bin/python /home/pi/gas-record.py (which, if we were at the command line in the pi home directory, we would run as python gas-record.py, but since we can’t guarantee where we will be when running the script, we are supplying the full path to the python command and the gas-record.py script.

Save the file and when the next minute rolls over our program will run on its designated schedule and we will have values written to our database every minute. After a while we can check it out by running a SELECT * FROM gas; on the measurement database.

sqlite> select * from gas;
2019-04-21 13:14:06|81|0.0210102589154861
2019-04-21 13:14:14|83|0.020009770395701
2019-04-21 13:17:32|83|0.0205100146555935
2019-04-21 13:20:01|83|0.0210102589154861
2019-04-21 13:21:01|82|0.0205100146555935
sqlite>

Managing database size

While it’s a great idea to save our local data into a database, we stand the risk of gradually letting that database fill up until it exceeds the capacity of our storage.

In the case of the measurements that we are carrying out, the readings are happening pretty regularly, so it’s worth thinking about. Capturing some simple measurements every minute means in the scheme of things that’s about 10,000 recordings per week.

What we’re looking for is a script that will run on a repeating schedule and remove old records. Sound familiar? That’s a very similar process to what we are doing when we record our data. A python script that is executed regularly by cron.

Here’s how we can do it.

The following python script (which we can name db-manage.py) opens our database, deletes any records older than a year, cleans up and exits.

#!/usr/bin/python
#encoding:utf-8

#Import SQLite library
import sqlite3

# Opens a database file called measurements
conn = sqlite3.connect('/home/pi/measurements', isolation_level=None)
db = conn.cursor()

# Delete any records that are older than 1 year
db.execute('DELETE FROM gas WHERE dtg<DATETIME("now","localtime", "-1 years")')
# VACUUM the database to remove any unnecessary data
db.execute('VACUUM')

# Commit the changes to the database and close the connection
conn.commit()
conn.close

The file is available as db-manage.py and can be found in the code sample extras that can be downloaded with this book.

It’s a pretty simple script and we can schedule its operation by editing the crontab file like so;

We want to add in an entry at the end of the file that looks like the following;

1 0 */1 * * /usr/bin/python /home/pi/db-manage.py

This instructs the computer that at 1 minute past the hour at midnight (hence the 0) on the 1st day of every month we run the command /usr/bin/python /home/pi/db-manage.py (which, if we were at the command line in the pi home directory, we would run as python db-manage.py, but since we can’t guarantee where we will be when running the script, we are supplying the full path to the python command and the db-manage.py script.

Save the file and every month our program will run on its designated schedule and will make sure to delete any records older than a year.

Explore

Simple data point API

The main mechanism for exploring and using our data is going to be via a simple data block returned from a http request.

What does all that actually mean?

That’s a good question. Ultimately we’re measuring something and we want to be able to communicate that measurement to an external service. That service could be another database somewhere or to a web page or to a system that will alert based on the value of the measured levels being within certain boundaries.

The very simplest way that we can do this is to present the data as the measured values when we ask for them in a web request. This could be thought of as a simplified form of an API (and I plan to make something more complicated in the future).

The data will be presented as JSON as that is one of the most ubiquitous data forms around.

Enough esoterica, what does the magic code look like that will do this?

<?php

$db = new PDO('sqlite://home/pi/measurements');

$result = $db->query('SELECT * FROM gas ORDER BY dtg DESC LIMIT 1');

$datapie = array();

$result->setFetchMode(PDO::FETCH_ASSOC);

while ($row = $result->fetch()) {
    extract($row);
    echo json_encode($row);
}

?>

We can save this file as gas.php and have it in the /var/www/html directory on our Pi (gas.php can be found in the code sample extras that can be downloaded with this book).

How we can put in the IP address of our Pi to our browser along with our distance php file (http://10.1.1.120/gas.php) and we should get something like the following appear in the browser;

{"dtg":"2019-04-21 13:29:01","value":"84","voltage":"0.0195095261358085"}

What good will getting this data be? Well……. I’m a bit of a believer that the information that gets captured by the Pi shouldn’t ultimately reside on the device in the long term. In the perfect world I would see it being requested by an external service that was checking a range of data points that would exist around the home (pressure, temperature inside / outside, CO2 levels, is the car parked in the garage, that sort of thing) so this is more of an enabling device than a ‘let’s display stuff’ deal. But I hear what you’re saying. “That’s lame. How can I impress people with that?”. Fair point. To deal with that problem let’s make a simple graph.

Extracting a Range of Data

Righto… If we’re going to make a graph of our gas levels we’ll need a variation of our API that will gather and present a range of data that our graph can then display.

This will form a piece of code that our graph will use as a JSON formatted data source.

It will look as follows;

<?php

$data= array();

// connect to the database
$db = new PDO("sqlite://home/pi/measurements");
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// prepare SQL command and execute
$query = "SELECT * FROM gas WHERE 
          dtg>DATETIME('now','localtime', '-24 hours')";        
$result = $db->prepare( $query );
$result->execute();

// compile the returned data
$values = $result->fetchAll(PDO::FETCH_ASSOC);
array_push($data, $values);

// print the data
echo json_encode($values);

// close the database connection
$db = NULL;

?>

This block of PHP code will connect to our database and instead of returning a single piece of data it will return (‘echo’) a range of values from the past 24 hours. We’ll call the file gas-range.php and it will be in the /var/www/html directory. A copy of the file can be found in the code sample extras that can be downloaded with this book.

The following file is our graph which will use our gas-range.php file and display it. It uses the d3.js visualisation library and for a full description of the workings of the code please feel free to consult a copy of ‘D3 Tips and Tricks v4.x’. It’s free and can be downloaded from here.

<!DOCTYPE html>
<meta charset="utf-8">
<style> /* set the CSS */

.line {
  fill: none;
  stroke: steelblue;
  stroke-width: 2px;
}

</style>
<body>

<!-- load the d3.js library -->    	
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>

// set the dimensions and margins of the graph
var margin = {top: 20, right: 20, bottom: 30, left: 50},
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

// parse the date / time
var parseTime = d3.timeParse("%Y-%m-%d %H:%M:%S");

// set the ranges
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);

// define the line
var valueline = d3.line()
    .curve(d3.curveBasis)
    .x(function(d) { return x(d.date); })
    .y(function(d) { return y(d.close); });

// append the svg obgect to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select("body").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform",
          "translate(" + margin.left + "," + margin.top + ")");

// Get the data
d3.json("gas-range.php", function(error, data) {
  if (error) throw error;

  // format the data
  data.forEach(function(d) {
      d.date = parseTime(d.dtg);
      d.close = +d.value;
  });

  // Scale the range of the data
  x.domain(d3.extent(data, function(d) { return d.date; }));
  y.domain([40, d3.max(data, function(d) { return d.close; })]);

  // Add the valueline path.
  svg.append("path")
      .data([data])
      .attr("class", "line")
      .attr("d", valueline);

  // Add the X Axis
  svg.append("g")
      .attr("transform", "translate(0," + height + ")")
      .call(d3.axisBottom(x));

  // Add the Y Axis
  svg.append("g")
      .call(d3.axisLeft(y));

});

</script>
</body>

We will want to place a copy of this file which we will call gas-graph.html in the /var/www/html directory. A copy of it can be found in the code sample extras that can be downloaded with this book.

We can see the end result by putting the web address into our browser. It should look something like http://10.1.1.120/gas-graph.html. The end result should look a bit like the following;

Light Levels Graph
Light Levels Graph

Wrap Up

There we have it.

We’ve assembled our Raspberry Pi with a gas sensor and an analog to digital converter. We installed an operating system and configured it for use. We’ve set up networking and installed a database and a web server. We’ve written code to record data into our database and an API to pull data out of it. We’ve even installed a graph to display it in a visual form. Nice work.

There is a strong possibility that the information I have laid out here could be littered with evil practices and gross inaccuracies.

But look on the bright side. Irrespective of the nastiness of the way that any of it was accomplished or the inelegance of the code, if the picture drawn on the screen is relatively pretty, you can walk away with a smile. :-)

Those with a smattering of knowledge of any of the topics I have butchered above (or below) are fully justified in feeling a large degree of righteous indignation. To those I say, please feel free to amend where practical and possible, but please bear in mind this was written from the point of view of someone with only a little experience in the topic and therefore try to keep any instructions at a level where a new entrant can step in.

Bibliography

The following texts were incredibly useful in drafting up this project.

RPi and I2C Analog-Digital Converter (OpenLabTools)

ADS1015 12-Bit ADC - 4 Channel with Programmable Gain Amplifier(Adafruit)

ADS1015 datasheet (Adafruit)

Adafruit 4-Channel ADC Breakouts (Adafruit Learning System)

Analog Sensors On The Raspberry Pi Using An MCP3008 (Matt, raspberrypi-spy.co.uk)

Analog Input Board for the Espiresso Pressure Sensor (int03.co.uk)

Arduino KY-018 Photo resistor module (TkkrLab)

Shenzhen KEYES DIY Robot co., Ltd

Light dependant resister datasheet (Sunroom Technologies)