About three to six times a day, the sun decides to make life in our trendy, skylit San Francisco office ever so slightly painful for at least one of my colleagues, forcing them to get up and use a long metal rod to slide the fashionable skylight drapes shut.
In my never-ending quest to make something in the IoT (Internet of Things) sphere that would remedy this perennial obstacle to productivity, I began with the data collection side of things. Sensors. I bought a fairly cheap Arduino-for-babies kit, grabbed the closest Raspberry Pi I could reach, and got started.
The goal for this blog post is to showcase the following:
The idea is that I'd just use an Arduino and a cheap photocell resistor/light sensor to handle collecting the signals to send via USB to a Raspberry Pi, which will be responsible for taking those signals and jettisoning them out into the internet in some way.
Setting up the hardware side of things was relatively straightforward with the Arduino Uno. Here is a page that describes the setup more succinctly than I ever could. They use a photocell, which is pretty similar in every way to the photo transistor light sensor I'm using, except potentially cheaper!
Essentially, you have a 5V circuit with the light sensor and 10kΩ resistor arranged serially. There is an alternate line branching off after these components that is piped to Analog pin 0. You can change the range of current values piped to this pin by changing the 10kΩ resistor with other resistors at different resistances. Higher resistor values mean more current gets routed to the pin instead, while lower resistor values mean more charge goes to ground. This project uses the 10kΩ resistor so the current registered by pin 0 ranges between 0-1000. I tested out a 1kΩ resistor for funsies and saw the range plummet to 0-100. Yay science!
The development lifecycle of an Arduino is such that you simply install the Arduino IDE or use the new online code Editor, type in your code, plug the Arduino into your computer via USB, and then press a button on the IDE/Editor that flashes your code onto the Arduino. In the words of the Barefoot Contessa, how easy is that?
Programming on the Arduino is a joy in that the boilerplate has been kept to a minimum. You have the setup function, which handles one-time setup of things. And you have the more interesting loop function, which as you might imagine runs the code inside of it on an infinite loop. Assuming that I've hooked everything up correctly, this is the code that I'd flash to the Arduino:
// Initialize the variablesint photoRPin = 0;int lightLevel;void setup() { // One-time setup. 9600 is a value that is called the "baud rate" Serial.begin(9600);}void loop() { // Collect a data point lightLevel=analogRead(photoRPin); // Send the adjusted Light level result to Serial port Serial.println(lightLevel); // Slow down the transmission to keep the call-stack down. 500ms! delay(500);}
If you're using the IDE, once the code is flashed to the Arduino, you can check the serial port for the output by navigating through Tools > Serial Monitor. If you see a beautiful stream of numbers, you're where you need to be on the Arduino front!
What's great about the hardware setup is the only hardware that needs to be plugged into the Raspberry Pi is the USB attached to the Arduino. To verify that the Arduino is plugged into the Pi sufficiently, run lsusb. You should see something like this:
Bus 001 Device 004: ID 2341:0043 Arduino SA Uno R3 (CDC ACM)Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet AdapterBus 001 Device 002: ID 0424:9514 Standard Microsystems Corp.Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
You'll want to know what Serial Port path to point the Raspberry Pi to read from, since that is where the Arduino will be writing data from the light sensor. With the Arduino plugged in, pip install pyserial and use the list_ports tool by entering python -m serial.tools.list_ports -v in the terminal. You'll get something that looks like the snippet below:
/dev/ttyACM0 desc: ttyACM0 hwid: USB VID:PID=2341:0043 SER=854393031333514140C1 LOCATION=1-1.2/dev/ttyAMA0 desc: ttyAMA0 hwid: 3f201000.uart
In this case, where there are multiple ports and you don't know which one to use, I'd probably just keep track of both and use one or the other, depending on what ends up working.
The software on the Raspberry Pi is only slightly more complicated. I'm using Python 3.5 on this Pi, and I want to be able to read from the serial port in an asynchronous way, so that I greedily gather as much data as possible without the read process itself blocking anything. To accomplish that I've pip-installed both pyserial and pyserial-asyncio and used the pyserial-asyncio short introduction as a starting point for the python code. I also pip-installed websockets, and used the client to asynchronously send incoming data to some websocket server, but that bit is commented out since this post isn't including the server-side.
Alternatively, one could send this data via HTTP/HTTPS to a web server the old-fashioned way with the aiohttp library!
import asyncioimport serial_asyncioimport websockets# Try this port. If I get nothing printing out, try '/dev/ttyAMA0'SERIAL_PORT = '/dev/ttyACM0' BAUDRATE = 9600class SerialConnection(asyncio.Protocol): data_buffer = '' # Example of co-routine that sends data to a server async def send_data(self, data): async with websockets.connect(os.getenv('PUBLISH_SOCKET_LINK')) as ws: dataline = data if dataline != '': await ws.send(dataline) # Built-in protocol method provided by pyserial-asyncio def data_received(self, data): # Gather the data in a chunk until it's ready to send self.data_buffer += data.decode('utf-8') if '\r\n' in self.data_buffer: data_to_send = self.data_buffer.strip('\r\n') print(data_to_send) # Reset the data_buffer! self.data_buffer = '' ''' At this point, you can make an HTTP Request with this data or use websockets! ''' # asyncio.get_event_loop().create_task(self.send_data(data_to_send))def record(path = None): # Boiler-plate for getting the pyserial-asyncio goodness to work loop = asyncio.get_event_loop() serial_connection = serial_asyncio.create_serial_connection(loop, SerialConnection, SERIAL_PORT, baudrate=BAUDRATE) try: loop.run_until_complete(serial_connection) loop.run_forever() finally: loop.close()if __name__ == '__main__': record()
EDIT: I've added my commands for doing installing all the things I said we'd need below!
pip install websockets pyserial-asyncio
So if everything is successful, you should have a Python program running on the Pi that 1.) reads ambient light levels coming from an Arduino and 2.) prints those light levels. The next steps will involve setting up a server of some sort to handle the signals this system is putting out, as well as setting up other clients to read from that server. Happy building!