Skip to content

Commit

Permalink
Update camera poller to only take pictures when there is sufficient l…
Browse files Browse the repository at this point in the history
…ight.
  • Loading branch information
JeetShetty committed Apr 15, 2017
1 parent 46e6d56 commit 8869efd
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 26 deletions.
1 change: 1 addition & 0 deletions greenpithumb/camera_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# Format of filename to write for camera image file (assumes timestamp is in
# UTC), as YYYY-MM-DDTHH:MMZ (minutes-level precision).
_FILENAME_FORMAT = '%Y-%m-%dT%H%MZ.jpg'
LIGHT_THRESHOLD_PCT = 75


class CameraManager(object):
Expand Down
37 changes: 16 additions & 21 deletions greenpithumb/greenpithumb.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@


def make_sensor_pollers(poll_interval, wiring_config, moisture_threshold,
record_queue, sleep_windows, raspberry_pi_io):
record_queue, sleep_windows, raspberry_pi_io,
photo_interval, image_path):
logger.info('creating sensor pollers (poll interval=%ds")',
poll_interval.total_seconds())
utc_clock = clock.Clock()
Expand Down Expand Up @@ -59,6 +60,8 @@ def make_sensor_pollers(poll_interval, wiring_config, moisture_threshold,
make_scheduler_func = lambda: poller.Scheduler(utc_clock, poll_interval)
poller_factory = poller.SensorPollerFactory(make_scheduler_func,
record_queue)
camera_poller_factory = poller.SensorPollerFactory(make_scheduler_func,
record_queue)

return [
poller_factory.create_temperature_poller(
Expand All @@ -71,25 +74,19 @@ def make_sensor_pollers(poll_interval, wiring_config, moisture_threshold,
pi_io.IO(GPIO), wiring_config.adc_channels.soil_moisture_sensor,
wiring_config.gpio_pins.soil_moisture_1,
wiring_config.gpio_pins.soil_moisture_2, utc_clock),
pump_manager),
poller_factory.create_light_poller(
pump_manager), poller_factory.create_light_poller(
light_sensor.LightSensor(
adc, wiring_config.adc_channels.light_sensor)),
camera_poller_factory.create_camera_poller(
camera_manager.CameraManager(
image_path,
utc_clock,
picamera.PiCamera(resolution=picamera.PiCamera.MAX_RESOLUTION)),
light_sensor.LightSensor(adc,
wiring_config.adc_channels.light_sensor)),
wiring_config.adc_channels.light_sensor))
]


def make_camera_poller(photo_interval, image_path, record_queue):
utc_clock = clock.Clock()
make_scheduler_func = lambda: poller.Scheduler(utc_clock, photo_interval)
poller_factory = poller.SensorPollerFactory(make_scheduler_func,
record_queue)
return poller_factory.create_camera_poller(
camera_manager.CameraManager(
image_path,
utc_clock,
picamera.PiCamera(resolution=picamera.PiCamera.MAX_RESOLUTION)))


def read_wiring_config(config_filename):
logger.info('reading wiring config at "%s"', config_filename)
with open(config_filename) as config_file:
Expand Down Expand Up @@ -130,11 +127,9 @@ def main(args):
raspberry_pi_io = pi_io.IO(GPIO)
poll_interval = datetime.timedelta(minutes=args.poll_interval)
photo_interval = datetime.timedelta(minutes=args.photo_interval)
pollers = make_sensor_pollers(poll_interval, wiring_config,
args.moisture_threshold, record_queue,
parsed_windows, raspberry_pi_io)
pollers.append(
make_camera_poller(photo_interval, args.image_path, record_queue))
pollers = make_sensor_pollers(
poll_interval, wiring_config, args.moisture_threshold, record_queue,
parsed_windows, raspberry_pi_io, photo_interval, args.image_path)
with contextlib.closing(db_store.open_or_create_db(
args.db_file)) as db_connection:
record_processor = create_record_processor(db_connection, record_queue)
Expand Down
21 changes: 18 additions & 3 deletions greenpithumb/poller.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import pytz

import camera_manager
import db_store

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -48,10 +49,10 @@ def create_soil_watering_poller(self, soil_moisture_sensor, pump_manager):
_SoilWateringPollWorker(self._make_scheduler_func(
), self._record_queue, soil_moisture_sensor, pump_manager))

def create_camera_poller(self, camera_manager):
def create_camera_poller(self, camera_manager, light_sensor):
return _SensorPoller(
_CameraPollWorker(self._make_scheduler_func(), self._record_queue,
camera_manager))
camera_manager, light_sensor))


def _datetime_to_unix_time(dt):
Expand Down Expand Up @@ -256,9 +257,23 @@ def _poll_once(self):
class _CameraPollWorker(_SensorPollWorkerBase):
"""Captures and stores pictures pictures from a camera."""

def __init__(self, scheduler, record_queue, camera_manager, light_sensor):
"""Creates a new CameraPollWorker object.
Args:
scheduler: Poll time scheduler.
record_queue: Not used by CameraPollWorker.
camera_manager: Manages the capture and saving of photos.
light_sensor: An interface for reading the light level.
"""
super(_CameraPollWorker, self).__init__(scheduler, record_queue,
camera_manager)
self._light_sensor = light_sensor

def _poll_once(self):
"""Captures and stores an image."""
self._sensor.save_photo()
if self._light_sensor.light() >= camera_manager.LIGHT_THRESHOLD_PCT:
self._sensor.save_photo()

def stop(self):
"""End worker polling and close camera."""
Expand Down
39 changes: 37 additions & 2 deletions tests/test_poller.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import mock
import pytz

from greenpithumb import camera_manager
from greenpithumb import db_store
from greenpithumb import poller

Expand Down Expand Up @@ -267,12 +268,16 @@ class CameraPollerTest(PollerTest):
def setUp(self):
super(CameraPollerTest, self).setUp()
self.mock_camera_manager = mock.Mock()
self.mock_light_sensor = mock.Mock()

def test_camera_poller(self):
def test_camera_poller_light_above_threshold(self):
with contextlib.closing(
self.factory.create_camera_poller(
self.mock_camera_manager)) as camera_poller:
self.mock_camera_manager,
self.mock_light_sensor)) as camera_poller:
self.mock_is_poll_time = True
self.mock_light_sensor.light.return_value = (
camera_manager.LIGHT_THRESHOLD_PCT + 1)
camera_poller.start_polling_async()
self.block_until_poll_completes()

Expand All @@ -281,3 +286,33 @@ def test_camera_poller(self):
# Should be nothing items in the queue because CameraPoller does not
# create database records.
self.assertTrue(self.record_queue.empty())

def test_camera_poller_light_at_threshold(self):
with contextlib.closing(
self.factory.create_camera_poller(
self.mock_camera_manager,
self.mock_light_sensor)) as camera_poller:
self.mock_is_poll_time = True
self.mock_light_sensor.light.return_value = (
camera_manager.LIGHT_THRESHOLD_PCT)
camera_poller.start_polling_async()
self.block_until_poll_completes()

self.mock_camera_manager.save_photo.assert_called()
self.mock_camera_manager.close.assert_called()
self.assertTrue(self.record_queue.empty())

def test_camera_poller_light_below_threshold(self):
with contextlib.closing(
self.factory.create_camera_poller(
self.mock_camera_manager,
self.mock_light_sensor)) as camera_poller:
self.mock_is_poll_time = True
self.mock_light_sensor.light.return_value = (
camera_manager.LIGHT_THRESHOLD_PCT - 1)
camera_poller.start_polling_async()
self.block_until_poll_completes()

self.mock_camera_manager.save_photo.assert_not_called()
self.mock_camera_manager.close.assert_called()
self.assertTrue(self.record_queue.empty())

0 comments on commit 8869efd

Please sign in to comment.