Skip to content
This repository has been archived by the owner on Jan 5, 2019. It is now read-only.

Python peripherals #278

Merged
merged 36 commits into from Jun 26, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
06b79c4
Begin stubbing in am2315 code
gordonbrander Mar 27, 2017
264f44c
Stub out sensor polling loop
gordonbrander Mar 27, 2017
7715214
Add +x to sensor_am2315.py
gordonbrander Mar 27, 2017
0c30123
Roughing out what it looks like to use Periphery I2C
gordonbrander Mar 29, 2017
f878fa6
Update AM2315 to reconnect if fails first time
gordonbrander Mar 29, 2017
3c364d5
Update launch file
gordonbrander Mar 30, 2017
02d0ba3
Add python peripheral launch file
gordonbrander Apr 4, 2017
c7ae4ed
Export peripherals sub directory for catkin to build
gordonbrander Apr 4, 2017
098a403
Add minimal ros indigo install script
Apr 4, 2017
8eb470e
Fix problem with am2315 base class
gordonbrander Apr 4, 2017
1dca099
verified code works on am2315 harware
Jun 2, 2017
2ebaa6d
added peripheral for mhz16, verified funcationality w/hardware
Jun 2, 2017
4d03a7f
added modules for atlas ph & ec, verified code works on hardware
Jun 5, 2017
8b9a7b0
added ds18b20 module, verified on hardware
Jun 5, 2017
a33f645
added plumbing for 16 channel hid relay..still need to add code & int…
Jun 9, 2017
9974f43
hid relay works sometimes but misses others, still need to make more …
Jun 13, 2017
09c0bd9
added relay controlled via gpio, confirmed functionality on hardware
Jun 14, 2017
cc07f42
added minimal touchscreen ui
Jun 14, 2017
d8f0a0e
Remove merge conflict artifacts
Jun 14, 2017
e1f952b
Add some feedback loops and database persistence nodes into python_pe…
Jun 14, 2017
bb6084e
include additional dependencies for peripherals
Jun 14, 2017
c5bc1b7
fix topic name for co2 in touchscreen
Jun 14, 2017
e5c755b
Add O2 sensor support
Jun 20, 2017
2f21d70
Add setpoint capabilities to UI
Jun 20, 2017
f401130
Remove redundant dependency in package file
Jun 20, 2017
dfb315c
Add missing comma in setup.py
sp4ghet Jun 21, 2017
0a72336
Patch ui bug
sp4ghet Jun 22, 2017
fe197ba
Add air_oxygen to var_types
sp4ghet Jun 22, 2017
cae2e08
Change install script to catkin_make and fix a few bugs
sp4ghet Jun 23, 2017
574d0d4
Dynamically locate atlas sensor ID
sp4ghet Jun 23, 2017
9b8d0cf
Specify correct actuator pins
sp4ghet Jun 23, 2017
7a39a8b
Actuator relay now assumes correct message type
sp4ghet Jun 23, 2017
9409bef
Simple bugs
sp4ghet Jun 23, 2017
851dc17
Fix race condition where atlas sensor nodes used same port
sp4ghet Jun 23, 2017
94fa108
I2C sensors actually retry when they fail to connect
sp4ghet Jun 23, 2017
ee56793
Atlas sensor code had some weird lines, fixed to make more sense
sp4ghet Jun 23, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions launch/personal_food_computer_var_types.yaml
Expand Up @@ -23,6 +23,11 @@ environment_variables:
description: "The amount of Carbon Dioxide in the air"
units: "ppm"
type: "std_msgs/Float64"
air_oxygen:
name: air_oxygen
description: "Oxygen density in the air"
units: "percent"
type: "std_msgs/Float64"
air_flush_on:
name: air_flush_on
description: "Turn on air flush (off by default)"
Expand Down
51 changes: 51 additions & 0 deletions launch/python_peripherals_only.launch
@@ -0,0 +1,51 @@
<launch>
<arg name="environment_1" value="environments/environment_1" />

<rosparam command="load" file="$(find openag_brain)/launch/personal_food_computer_var_types.yaml" ns="var_types"/>
<group ns="$(arg environment_1)">
<param name="atlas/ready" value="True" type="bool"/>
<node pkg="openag_brain" type="topic_filter.py" name="topic_filter_1"/>
<node pkg="openag_brain" type="recipe_handler.py" name="recipe_handler_1"/>
<node pkg="openag_brain" type="sensor_persistence.py" name="sensor_persistence_1">
<param name="max_update_interval" value="600" type="int"/>
<param name="min_update_interval" value="5" type="int"/>
</node>
<node pkg="openag_brain" type="pid.py" name="air_temperature_controller_1">
<param name="Kp" value="1" type="double"/>
<param name="Ki" value="0" type="double"/>
<param name="Kd" value="0" type="double"/>
<param name="deadband_width" value="0.5" type="double"/>
<param name="windup_limit" value="1000" type="double"/>
<param name="upper_limit" value="1" type="double"/>
<param name="lower_limit" value="0" type="double"/>
<param name="variable" value="air_temperature" type="str"/>
</node>
<node pkg="openag_brain" type="pid.py" name="air_humidity_controller_1">
<param name="Kp" value="1" type="double"/>
<param name="Ki" value="0" type="double"/>
<param name="Kd" value="0" type="double"/>
<param name="deadband_width" value="0.5" type="double"/>
<param name="windup_limit" value="1000" type="double"/>
<param name="upper_limit" value="1" type="double"/>
<param name="lower_limit" value="0" type="double"/>
<param name="variable" value="air_humidity" type="str"/>
</node>
<node pkg="openag_brain" type="sensor_am2315.py" name="am2315_1" />
<node pkg="openag_brain" type="sensor_mhz16.py" name="mhz16_1" />
<node pkg="openag_brain" type="sensor_atlas_ph.py" name="atlas_ph_1" />
<node pkg="openag_brain" type="sensor_atlas_ec.py" name="atlas_ec_1" />
<node pkg="openag_brain" type="sensor_ds18b20.py" name="ds18b20_1" />
<node pkg="openag_brain" type="sensor_grove_o2.py" name="grove_o2_1" />
<node pkg="openag_brain" type="actuator_relay.py" name="relay_heater">
<param name="topic" value="air_temperature/commanded" type="str"/>
<param name="pin" value="23" type="int"/>
</node>
<node pkg="openag_brain" type="actuator_relay.py" name="relay_humidifier">
<param name="topic" value="air_humidity/commanded" type="str"/>
<param name="pin" value="24" type="int"/>
</node>
<node pkg="openag_brain" type="ui_touchscreen.py" name="touchscreen_1" />
</group>

<node pkg="openag_brain" type="api.py" name="api"/>
</launch>
29 changes: 29 additions & 0 deletions nodes/actuator_hid_relay_16.py
@@ -0,0 +1,29 @@
#!/usr/bin/env python
"""
`actuator_hid_relay_16.py` handles communication with the
`Sainsmart USB HID programmable control relay module
<https://www.sainsmart.com/sainsmart-16-channel-controller-usb-hid-programmable-control-relay-module.html>
that connects the the Sainsmart 16-channel relay module
<https://www.amazon.com/SainSmart-101-70-103-16-Channel-Relay-Module/dp/B0057OC66U/ref=sr_1_1?ie=UTF8&qid=1497021207&sr=8-1&keywords=sainsmart+relay+16>`_.
"""
import rospy
from std_msgs.msg import Bool
from openag_brain.peripherals.hid_relay_16 import HidRelay16

if __name__ == '__main__':
rospy.init_node("actuator_hid_relay_16")
default_relay_map = {"red_light_intensity/commanded": "2", # red light is connected to relay 1
"white_light_intensity/commanded": "3",
"blue_light_intensity/commanded": "4"}

relay_map = rospy.get_param("~relay_map", default_relay_map)

hid_relay_16 = HidRelay16()

def on_set(msg, relay_id):
hid_relay_16.set(int(relay_id), msg.data)

for topic in relay_map:
subscriber = rospy.Subscriber(topic, Bool, callback=on_set, callback_args=relay_map[topic])

rospy.spin()
22 changes: 22 additions & 0 deletions nodes/actuator_relay.py
@@ -0,0 +1,22 @@
#!/usr/bin/env python
"""
`actuator_relay.py` handles communication with an active low relay
driven by GPIO pins
"""
import rospy
from std_msgs.msg import Float64
from openag_brain.peripherals.relay import Relay

if __name__ == '__main__':
rospy.init_node("relay")
topic = rospy.get_param("~topic", "red_light_intensity/commanded")
pin = rospy.get_param("~pin", 27) # BCM pin
relay = Relay(pin)

def on_set(msg):
cmd = msg.data > 0.0
relay.set(cmd)

subscriber = rospy.Subscriber(topic, Float64, callback=on_set)

rospy.spin()
28 changes: 28 additions & 0 deletions nodes/sensor_am2315.py
@@ -0,0 +1,28 @@
#!/usr/bin/env python
"""
`sensor_am2315.py` handles communication with the
`AM2315 temperature and humidity sensor <https://www.adafruit.com/products/1293>`_.
"""
import rospy
from std_msgs.msg import Float64
from openag_brain.peripherals.am2315 import AM2315

if __name__ == '__main__':
rospy.init_node("sensor_am2315")
i2c_addr = rospy.get_param("~i2c_addr", 0x5c)
i2c_bus = rospy.get_param("~i2c_bus", "/dev/i2c-1")
temp_pub = rospy.Publisher("air_temperature/raw", Float64, queue_size=10)
humid_pub = rospy.Publisher("air_humidity/raw", Float64, queue_size=10)
rate = rospy.get_param("~rate_hz", 1)
r = rospy.Rate(rate)

with AM2315(i2c_addr, i2c_bus) as am2315:
while not rospy.is_shutdown():
am2315.poll()
if am2315.temperature is not None:
temp_pub.publish(am2315.temperature)
if am2315.humidity is not None:
humid_pub.publish(am2315.humidity)

# Use rate timer instance to sleep until next turn
r.sleep()
28 changes: 28 additions & 0 deletions nodes/sensor_atlas_ec.py
@@ -0,0 +1,28 @@
#!/usr/bin/env python
"""
`sensor_atlas_ec.py` handles communication with the Atlas EC sensor
`<https://www.atlas-scientific.com/product_pages/kits/ec_k1_0_kit.html> connected
to the Atlas Isolated USB EZO Board <https://www.atlas-scientific.com/product_pages/components/usb-iso.html>`_.
"""
import rospy
from std_msgs.msg import Float64
from openag_brain.peripherals.atlas_ec import AtlasEc

if __name__ == '__main__':
rospy.init_node("sensor_atlas_ec")
ec_pub = rospy.Publisher("water_electrical_conductivity/raw", Float64, queue_size=10)
rate = rospy.get_param("~rate_hz", 1)
r = rospy.Rate(rate)

while not rospy.get_param("atlas/ready", False):
pass

rospy.set_param("atlas/ready", False)
with AtlasEc() as atlas_ec:
while not rospy.is_shutdown():
atlas_ec.poll()
if atlas_ec.ec is not None:
rospy.set_param("atlas/ready", True)
ec_pub.publish(atlas_ec.ec)

r.sleep()
29 changes: 29 additions & 0 deletions nodes/sensor_atlas_ph.py
@@ -0,0 +1,29 @@
#!/usr/bin/env python
"""
`sensor_atlas_ph.py` handles communication with the Atlas pH sensor
`<https://www.atlas-scientific.com/product_pages/kits/ph-kit.html> connected
to the Atlas Isolated USB EZO Board <https://www.atlas-scientific.com/product_pages/components/usb-iso.html>`_.
"""
import rospy
from std_msgs.msg import Float64
from openag_brain.peripherals.atlas_ph import AtlasPh

if __name__ == '__main__':
rospy.init_node("sensor_atlas_ph")
ph_pub = rospy.Publisher("water_potential_hydrogen/raw", Float64, queue_size=10)
rate = rospy.get_param("~rate_hz", 1)
r = rospy.Rate(rate)

while not rospy.get_param("atlas/ready", False):
pass

rospy.set_param("atlas/ready", False)
with AtlasPh() as atlas_ph:

while not rospy.is_shutdown():
atlas_ph.poll()
if atlas_ph.ph is not None:
rospy.set_param("atlas/ready", True)
ph_pub.publish(atlas_ph.ph)

r.sleep()
21 changes: 21 additions & 0 deletions nodes/sensor_ds18b20.py
@@ -0,0 +1,21 @@
#!/usr/bin/env python
"""
`sensor_ds18b20.py` handles communication with the
`DS18B20 temperature sensor <https://www.adafruit.com/product/381>`_.
"""
import rospy
from std_msgs.msg import Float64
from openag_brain.peripherals.ds18b20 import DS18B20

if __name__ == '__main__':
rospy.init_node("sensor_ds18b20")
temp_pub = rospy.Publisher("water_temperature/raw", Float64, queue_size=10)
rate = rospy.get_param("~rate_hz", 1)
r = rospy.Rate(rate)

with DS18B20() as ds18b20:
while not rospy.is_shutdown():
ds18b20.poll()
if ds18b20.temperature is not None:
temp_pub.publish(ds18b20.temperature)
r.sleep()
21 changes: 21 additions & 0 deletions nodes/sensor_grove_o2.py
@@ -0,0 +1,21 @@
#!/usr/bin/python
"""
This module consists of code for interacting with a Grove O2 sensor.
"""

import rospy
from std_msgs.msg import Float64
from openag_brain.peripherals.grove_o2 import GroveO2

if __name__ == "__main__":
rospy.init_node("sensor_grove_o2")
o2_pub = rospy.Publisher("air_oxygen/raw", Float64, queue_size=10)
rate = rospy.get_param("~rate_hz", 1)
r = rospy.Rate(rate)

with GroveO2() as grove_o2:
while not rospy.is_shutdown():
grove_o2.poll()
if grove_o2.o2 is not None:
o2_pub.publish(grove_o2.o2)
r.sleep()
25 changes: 25 additions & 0 deletions nodes/sensor_mhz16.py
@@ -0,0 +1,25 @@
#!/usr/bin/env python
"""
`sensor_mhz16.py` handles communication with the
`MHZ16 co2 sensor <http://sandboxelectronics.com/?product=mh-z16-ndir-co2-sensor-with-i2cuart-5v3-3v-interface-for-arduinoraspeberry-pi>`_.
"""
import rospy
from std_msgs.msg import Float64
from openag_brain.peripherals.mhz16 import MHZ16

if __name__ == '__main__':
rospy.init_node("sensor_mhz16")
i2c_addr = 0x4d
i2c_bus = rospy.get_param("~i2c_bus", "/dev/i2c-1")
co2_pub = rospy.Publisher("air_carbon_dioxide/raw", Float64, queue_size=10)
rate = rospy.get_param("~rate_hz", 1)
r = rospy.Rate(rate)

with MHZ16(i2c_addr, i2c_bus) as mhz16:
while not rospy.is_shutdown():
mhz16.poll()
if mhz16.co2 is not None:
co2_pub.publish(mhz16.co2)

# Use rate timer instance to sleep until next turn
r.sleep()
81 changes: 81 additions & 0 deletions nodes/ui_touchscreen.py
@@ -0,0 +1,81 @@
#!/usr/bin/env python
"""
`ui_touchscreen.py` handles communication with an raspberry pi 7" touchcreen
"""
import rospy
from std_msgs.msg import Float64
from openag_brain.peripherals.touchscreen import Touchscreen

if __name__ == '__main__':
rospy.init_node("touchscreen")


touchscreen = Touchscreen()

def set_air_temp(msg):
touchscreen.air_temp = msg.data

def set_humidity(msg):
touchscreen.humidity= msg.data

def set_co2(msg):
touchscreen.co2 = msg.data

def set_o2(msg):
touchscreen.o2 = msg.data

def set_water_temp(msg):
touchscreen.water_temp = msg.data

def set_ph(msg):
touchscreen.ph = msg.data

def set_ec(msg):
touchscreen.ec = msg.data

def set_cmd_temp(msg):
touchscreen.cmd_temp = msg.data

def set_cmd_hum(msg):
touchscreen.cmd_hum = msg.data


air_temp_sub = rospy.Subscriber("air_temperature/raw", Float64, callback=set_air_temp)
humidity_sub = rospy.Subscriber("air_humidity/raw", Float64, callback=set_humidity)
co2_sub = rospy.Subscriber("air_carbon_dioxide/raw", Float64, callback=set_co2)
o2_sub = rospy.Subscriber("air_oxygen/raw", Float64, callback=set_o2)
water_temp_sub = rospy.Subscriber("water_temperature/raw", Float64, callback=set_water_temp)
ph_sub = rospy.Subscriber("water_potential_hydrogen/raw", Float64, callback=set_ph)
ec_sub = rospy.Subscriber("water_electrical_conductivity/raw", Float64, callback=set_ec)

temp_pub = rospy.Publisher("air_temperature/desired", Float64, queue_size=10)
hum_pub = rospy.Publisher("air_humidity/desired", Float64, queue_size=10)

temp_cmd_sub = rospy.Subscriber("air_temperature/commanded", Float64, callback=set_cmd_temp)
hum_cmd_sub = rospy.Subscriber("air_humidity/commanded", Float64, callback=set_cmd_hum)

# Closures are passed by reference such that any new substitutions are interpreted
# as declarations, causing prev_time to be "Referenced before declaration".
# This can be bypassed using an object reference
# https://stackoverflow.com/questions/3190706/nonlocal-keyword-in-python-2-x
def ros_next(rate_hz):
ros_next.prev_time = rospy.get_time()
timeout = 1 / rate_hz
def closure():
curr_time = rospy.get_time()
if curr_time - ros_next.prev_time > timeout:
ros_next.prev_time = curr_time
return True
else:
return False
return closure
rate = rospy.get_param("~rate_hz", 1)
is_pub = ros_next(rate)
r = rospy.Rate(60) # Frame rate

while not rospy.is_shutdown():
if is_pub():
temp_pub.publish(Float64(touchscreen.desired_temp))
hum_pub.publish(Float64(touchscreen.desired_hum))
touchscreen.refresh()
r.sleep()
8 changes: 8 additions & 0 deletions package.xml
Expand Up @@ -53,8 +53,16 @@
<run_depend>python-gevent</run_depend>
<run_depend>python-flask</run_depend>
<run_depend>python-couchdb</run_depend>
<run_depend>python-lxml</run_depend>
<run_depend>python-periphery-pip</run_depend>
<run_depend>python-pylibftdi-pip</run_depend>
<run_depend>python-w1thermsensor-pip</run_depend>
<run_depend>python-usb</run_depend>
<run_depend>python-numpy</run_depend>
<run_depend>python-pygame</run_depend>
<test_depend>rosunit</test_depend>
<test_depend>rostest</test_depend>

<!-- The export tag contains other, unspecified, tags -->
<export>
<!-- Other tools can request additional information be placed here -->
Expand Down
9 changes: 8 additions & 1 deletion scripts/init_dev
Expand Up @@ -48,7 +48,7 @@ sudo easy_install pip
sudo pip install rosdep rosinstall_generator wstool rosinstall

# Install some python dependencies that there aren't ros packages for.
sudo pip install voluptuous HTTPretty pytest
sudo pip install voluptuous HTTPretty pytest

# Initialize rosdep if it hasn't been already.
# Checking for the .list file first makes this script idempotent.
Expand Down Expand Up @@ -81,4 +81,11 @@ echo "Installing binary dependencies with rosdep"
# Install dependency binary deps
rosdep install --from-paths ~/catkin_ws/src --ignore-src --rosdistro indigo -y -r --os=debian:jessie

# Let pi user access hid relay device without having to be root
sudo sh -c 'echo SUBSYSTEM=="usb", ATTR{idVendor}=="0416", ATTR{idProduct}=="5020", MODE="0666", GROUP="plugdev" > /etc/udev/rules.d/99-hidrelay.rules'

# Let pi user access gpio without having to be root
echo SUBSYSTEM=="gpio*", PROGRAM="/bin/sh -c 'chown -R root:gpio /sys/class/gpio && chmod -R 770 /sys/class/gpio; chown -R root:gpio /sys/devices/virtual/gpio && chmod -R 770 /sys/devices/virtual/gpio'" | sudo tee -a /etc/udev/rules.d/99-com.rules > /dev/null
sudo usermod -a -G gpio pi

echo "Finished setting up development environment. Ready to build with catkin_make."