Wiring up pi-lot.
Ok. So I’ve had a go with each device separately. Let’s put it all together.
Components
Raspberry Pi Zero W.
DS18B20 - temperature probe
DHT22 - air temperature and humidity sensor
MCP3008 - analogue to digital converter
photoresistor
capacitive moisture sensor
water sensor
water pump
2 small breadboards
some jump cables and solid core wire
A takeaway food box
garden wire
some plastic food containers
Physical arrangement
A takeaway container is used to house the RPi and two breadboards, held in place by garden wire. Holes have been made to accommodate cables and wire providing fixtures.
Circuits
Thanks KiCad. Way better than fritzing, which is great, but… “doesn’t scale” (or words to that effect).
Code
EDIT : I am finishing this retrospectively. Apparently it was all just too exciting at the time to keep up with my notes. I detoured via some very elaborate database structures and with async polling and whatnot. And in the end returned to KISS. Classic.
I hacked together the code used when experimenting with the various components independently into a single main.py
.
This abstracts the various devices into their respective classes,
and Pilot
which then handles them.
Get it?! “pi-lot”/ “pilot”… clever ’ey.
Pilot collects the readings and logs them in a database.
The database logic is in db.py
(also below) and the
super simple schema consists of a single table “event”.
The water pump is not included after reading that automatic watering can be a killer. Believable.
""" main.py """
import time
import collections
import glob
from spidev import SpiDev
import Adafruit_DHT
import db
class Pilot:
"""
Main daemon of pilot sensors and instruments.
"""
def __init__(self, devices):
self._devices = devices
self._session_maker = db.session_maker()
def logging(self, interval=1):
def log_result(result):
= db.Event(category=result[0],state=result[1])
event
session.add(event)
session.commit()= self._session_maker()
session try:
while True:
if db.get_size() > 3e9: #~3gb
raise IOError("DB quite big")
for device in self._devices:
try:
for result in device.poll():
log_result(result)except Exception as E:
print(device, E)
time.sleep(interval)except KeyboardInterupt:
print("Interupt")
finally:
print("Exiting")
session.close()
class MCP3008:
def __init__(self, sensors=[], bus = 0, device = 0,):
self._bus, self._device = bus, device
self.spi = SpiDev()
self.open()
self._sensors = sensors + [None]*(8 - len(sensors))
def open(self):
self.spi.open(self._bus, self._device)
self.spi.max_speed_hz = 1350000
def poll_channel(self, channel=0):
= self.spi.xfer2([1, (8 + channel) << 4, 0])
adc = ((adc[1] & 3) << 8) + adc[2]
data return data
def poll(self):
= []
results for channel, sensor in enumerate(self._sensors):
if sensor:
self.poll_channel(channel)))
results.append((sensor, return results
def close(self):
self.spi.close()
class DS18B20:
def __init__(self):
pass
def read_temp_raw(self):
= '/sys/bus/w1/devices/'
base_dir = glob.glob(base_dir + '28*')[0]
device_folder = device_folder + '/w1_slave'
device_file with open(device_file, 'r') as fh:
= fh.readlines()
lines return lines
def poll(self):
= self.read_temp_raw()
lines while lines[0].strip()[-3:] != 'YES':
0.2)
time.sleep(= self.read_temp_raw()
lines = lines[1].find('t=')
equals_pos if equals_pos != -1:
= lines[1][equals_pos+2:]
temp_string = float(temp_string) / 1000.0
temp_c return [("soil_temp", temp_c)] #, temp_f
class DHT22:
def __init__(self, pin=27):
self._sensor = Adafruit_DHT.DHT22
self._pin = pin # GPIO pin number used.
def poll(self):
while True:
= Adafruit_DHT.read_retry(self._sensor, self._pin)
humidity, temperature if humidity is not None and temperature is not None:
return [("humidity", humidity), ("air_temp", temperature)]
if __name__ == "__main__":
= MCP3008(["moisture", "water_level", "photoresistor"])
mcp = DS18B20()
ds18b20 = DHT22()
dht22 = [mcp, ds18b20, dht22]
devices
= Pilot(devices)
pilot 1) pilot.logging(
with db.py
handling the database
"""db.py"""
import os
import datetime
import sqlalchemy as sql
from sqlalchemy.ext.declarative import as_declarative
= sql.ext.declarative.declarative_base()
Base
class Event(Base):
= 'event'
__tablename__ id = sql.Column(sql.Integer, primary_key=True)
= sql.Column(sql.DateTime, default=datetime.datetime.utcnow)
datetime = sql.Column(sql.String)
category = sql.Column(sql.Float)
state
def __repr__(self):
return "<Event(datetime='{}', category='{}')>".format(self.datetime, self.category)
def create_db(engine):
Base.metadata.create_all(engine)
def session_maker(name = "pilots_log.sqlite"):
if not os.path.exists(name):
= sql.create_engine('sqlite:///{}'.format(name), echo=True)
engine
create_db(engine)else:
= sql.create_engine('sqlite:///{}'.format(name), echo=True)
engine = sql.orm.sessionmaker(bind=engine)
Session return Session
def get_size(name = "pilots_log.sqlite"):
return os.stat(name).st_size
if __name__=="__main__":
= "pilots_log.sqlite"
name if not os.path.exists(name):
= sql.create_engine('sqlite:///{}'.format(name), echo=True)
engine
create_db(engine)else:
= sql.create_engine('sqlite:///{}'.format(name), echo=True)
engine = sql.orm.sessionmaker(bind=engine)
Session = Session()
session session.close()
Setting this up on a pi, we can kick off the logging with
python main.py
(Using screen
or tmux
to detach and leave alive.)