Skip to content

Commit

Permalink
Merge 75e1b2e into a86ad4b
Browse files Browse the repository at this point in the history
  • Loading branch information
mtlynch committed Dec 24, 2016
2 parents a86ad4b + 75e1b2e commit 61b6530
Show file tree
Hide file tree
Showing 6 changed files with 464 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,6 @@ target/

# rope files
.ropeproject/*

# GreenPiThumb wiring configuration file
wiring_config.ini
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ cd ..
git clone https://github.com/JeetShetty/GreenPiThumb.git
cd GreenPiThumb
sudo pip install -r requirements.txt
cp greenpithumb/wiring_config.ini.example greenpithumb/wiring_config.ini
```

### Dev Installation
Expand All @@ -48,4 +49,5 @@ git clone https://github.com/JeetShetty/GreenPiThumb.git
cd GreenPiThumb
sudo pip install -r requirements.txt
sudo pip install -r dev_requirements.txt
cp greenpithumb/wiring_config.ini.example greenpithumb/wiring_config.ini
```
15 changes: 13 additions & 2 deletions greenpithumb/greenpithumb.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
import light_sensor
import moisture_sensor
import temperature_sensor
import wiring_config_parser


class SensorHarness(object):
"""Simple container for GreenPiThumbs that polls their values and prints."""

def __init__(self):
def __init__(self, wiring_config):
# TODO(mtlynch): Hook wiring_config up to components that depend on it.
self._adc = fake_sensors.FakeAdc(light_start=500.0,
moisture_start=600.0)
self._light_sensor = light_sensor.LightSensor(self._adc)
Expand All @@ -38,8 +40,13 @@ def print_readings(self):
temperature_reading, humidity_reading)


def read_wiring_config(config_filename):
with open(config_filename) as config_file:
return wiring_config_parser.parse(config_file.read())


def main(args):
sensor_harness = SensorHarness()
sensor_harness = SensorHarness(read_wiring_config(args.config_file))
sensor_harness.print_readings_header()
while True:
sensor_harness.print_readings()
Expand All @@ -55,4 +62,8 @@ def main(args):
type=float,
help='Number of seconds between each sensor poll',
default=0.5)
parser.add_argument('-c',
'--config_file',
help='Wiring config file',
default='wiring_config.ini')
main(parser.parse_args())
15 changes: 15 additions & 0 deletions greenpithumb/wiring_config.ini.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# gpio_pins section maps which GreenPiThumb component is connected to which
# Raspberry Pi GPIO pin (in BCM numbering).
[gpio_pins]
pump: 26
dht11: 21
mcp3008_clk: 18
mcp3008_dout: 23
mcp3008_din: 24
mcp3008_cs_shdn: 25

# adc_channels section maps which GreenPiThumb component is connected to which
# channel of the MCP3008 ADC.
[adc_channels]
soil_moisture_sensor: 0
light_sensor: 6
233 changes: 233 additions & 0 deletions greenpithumb/wiring_config_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
import ConfigParser
import io


class Error(Exception):
pass


class InvalidConfigError(Error):
"""Indicates an error parsing a GreenPiThumb wiring config."""
pass


class IllegalGpioPinNumberError(Error):
"""Indicates an attempt to parse a GPIO pin with an invalid value."""


class IllegalAdcChannelError(Error):
"""Indicates an attempt to parse an ADC channel with an invalid value."""


class DuplicateGpioPinNumberError(Error):
"""Indicates an attempt to parse a GPIO pin with an invalid value."""


class DuplicateAdcChannelError(Error):
"""Indicates an attempt to parse an ADC channel with an invalid value."""


class _GpioPinConfig(object):
"""Represents GreenPiThumb's Rapsberry Pi GPIO pin configuration."""

def __init__(self, pump, dht11, mcp3008_clk, mcp3008_dout, mcp3008_din,
mcp3008_cs_shdn):
self._pump = pump
self._dht11 = dht11
self._mcp3008_clk = mcp3008_clk
self._mcp3008_dout = mcp3008_dout
self._mcp3008_din = mcp3008_din
self._mcp3008_cs_shdn = mcp3008_cs_shdn

@property
def pump(self):
return self._pump

@property
def dht11(self):
return self._dht11

@property
def mcp3008_clk(self):
return self._mcp3008_clk

@property
def mcp3008_dout(self):
return self._mcp3008_dout

@property
def mcp3008_din(self):
return self._mcp3008_din

@property
def mcp3008_cs_shdn(self):
return self._mcp3008_cs_shdn


def _validate_gpio_pin_config(gpio_config):
"""Validates a GPIO pin configuration.
Args:
gpio_config: The GPIO configuration object to validate.
Raises:
DuplicateGpioPinNumberError when the same GPIO pin is assigned to
multiple components.
"""
used_pins = set()
for pin in [gpio_config.pump, gpio_config.dht11, gpio_config.mcp3008_clk,
gpio_config.mcp3008_dout, gpio_config.mcp3008_din,
gpio_config.mcp3008_cs_shdn]:
if pin in used_pins:
raise DuplicateGpioPinNumberError(
'GPIO pin cannot be assigned to multiple components: %d' % pin)
used_pins.add(pin)


class _AdcChannelConfig(object):
"""Represents GreenPiThumb's ADC channel configuration."""

def __init__(self, soil_moisture_sensor, light_sensor):
self._soil_moisture_sensor = soil_moisture_sensor
self._light_sensor = light_sensor

@property
def soil_moisture_sensor(self):
return self._soil_moisture_sensor

@property
def light_sensor(self):
return self._light_sensor


def _validate_adc_channel_config(adc_config):
"""Validates an ADC channel configuration.
Args:
adc_config: The ADC channel configuration to validate
Raises:
DuplicateAdcChannelError when the same ADC channel is assigned to
multiple components.
"""
if adc_config.soil_moisture_sensor == adc_config.light_sensor:
raise DuplicateAdcChannelError(
'Soil moisture sensor and light sensor cannot have the same ADC '
'channel: %d' % adc_config.soil_moisture_sensor)


class _WiringConfig(object):
"""Represents GreenPiThumb's wiring configuration."""

def __init__(self, gpio_pin_config, adc_channel_config):
self._gpio_pin_config = gpio_pin_config
self._adc_channel_config = adc_channel_config

@property
def gpio_pins(self):
return self._gpio_pin_config

@property
def adc_channels(self):
return self._adc_channel_config


def _parse_gpio_pin(pin_raw):
"""Parses a GPIO pin value from the configuration file.
Parses a GPIO pin value. Must be a valid Raspberry Pi GPIO pin number. Must
be a value from 2 to 27.
Args:
pin_raw: The raw GPIO pin value from the configuration file.
Returns:
The parsed GPIO pin value as an int.
Raises:
IllegalGpioPinNumberError when the value is invalid.
"""
try:
pin = int(pin_raw)
except ValueError:
raise IllegalGpioPinNumberError(
'Invalid GPIO pin: %s. Pin must be a value from 2 to 27. '
'Be sure to use BCM numbering, not BOARD numbering.' % pin_raw)
if not (2 <= pin <= 27):
raise IllegalGpioPinNumberError(
'Invalid GPIO pin: %s. Pin must be a value from 2 to 27. '
'Be sure to use BCM numbering, not BOARD numbering.' % pin_raw)
return pin


def _parse_adc_channel(channel_raw):
"""Parses an ADC channel value from the configuration file.
Parses an ADC channel value. Must be a value from 0 to 7.
Args:
channel_raw: The raw ADC channel value from the configuration file.
Returns:
The parsed channel value as an int.
Raises:
IllegalAdcChannelError when the value is invalid.
"""
try:
channel = int(channel_raw)
except ValueError:
raise IllegalAdcChannelError(
'Invalid ADC channel: %s. Channel must be a value from 0 to 7.' %
channel_raw)
if not (0 <= channel <= 7):
raise IllegalAdcChannelError(
'Invalid ADC channel: %s. Channel must be a value from 0 to 7.' %
channel_raw)
return channel


def parse(config_data):
"""Parse GreenPiThumb wiring configuration from text.
Given the contents of a GreenPiThumb wiring configuration file, parses
the configuration into a wiring config object.
Args:
config_data: The contents of a GreenPiThumb configuration file.
Returns:
A wiring configuration object with the following properties:
* gpio_pins.pump
* gpio_pins.dht11
* gpio_pins.mcp3008_clk
* gpio_pins.mcp3008_dout
* gpio_pins.mcp3008_din
* gpio_pins.mcp3008_cs_shdn
* adc_channels.soil_moisture_sensor
* adc_channels.light_sensor
"""
raw_parser = ConfigParser.RawConfigParser()
try:
raw_parser.readfp(io.BytesIO(config_data))
gpio_pin_config = _GpioPinConfig(
pump=_parse_gpio_pin(raw_parser.get('gpio_pins', 'pump')),
dht11=_parse_gpio_pin(raw_parser.get('gpio_pins', 'dht11')),
mcp3008_clk=_parse_gpio_pin(raw_parser.get('gpio_pins',
'mcp3008_clk')),
mcp3008_din=_parse_gpio_pin(raw_parser.get('gpio_pins',
'mcp3008_din')),
mcp3008_dout=_parse_gpio_pin(raw_parser.get('gpio_pins',
'mcp3008_dout')),
mcp3008_cs_shdn=_parse_gpio_pin(raw_parser.get('gpio_pins',
'mcp3008_cs_shdn')))
_validate_gpio_pin_config(gpio_pin_config)
adc_channel_config = _AdcChannelConfig(
soil_moisture_sensor=_parse_adc_channel(raw_parser.get(
'adc_channels', 'soil_moisture_sensor')),
light_sensor=_parse_adc_channel(raw_parser.get('adc_channels',
'light_sensor')))
_validate_adc_channel_config(adc_channel_config)
return _WiringConfig(gpio_pin_config, adc_channel_config)
except ConfigParser.Error as ex:
raise InvalidConfigError('Failed to parse wiring config', ex)
Loading

0 comments on commit 61b6530

Please sign in to comment.