Compare commits

...

12 Commits

@ -16,34 +16,6 @@ import shepherd.plugin
#Start new thread, and push ID and core config to api.shepherd.distreon.net/client/update
class UpdateManager():
def __init__(self):
pass
class SequenceUpdate():
Item = namedtuple('Item', ['sequence_number', 'content'])
def __init__(self):
self.items = []
self._sequence_count = 0
self._dirty = False
def _next_sequence_number(self):
# TODO: need to establish a max sequence number, so that it can be compared to half
# that range and wrap around.
self._sequence_count +=1
return self._sequence_count
def mark_as_dirty(self):
self._dirty = True
def add_item(self, item):
self.items.append(self.Item(self._next_sequence_number(), item))
self.mark_as_dirty()
def get_payload():
pass
def process_ack():
pass
client_id = None
control_url = None

@ -169,30 +169,29 @@ def compile_config(default_config_path):
return core_conf, plugin_classes, plugin_configs
@click.group(invoke_without_command = True)
@click.group(invoke_without_command=True)
#help="Path to default config TOML file"
@click.argument('default_config', default="shepherd-default.toml", type=click.Path())
@click.pass_context
def cli(ctx, default_config):
"""
Core service. Expects the default config to be set as an argument.
"""
#argparser = argparse.ArgumentParser(description="Keep track of a mob "
# argparser = argparse.ArgumentParser(description="Keep track of a mob "
# "of roaming Pis")
#argparser.add_argument("configfile", nargs='?', metavar="configfile",
# argparser.add_argument("configfile", nargs='?', metavar="configfile",
# help="Path to configfile", default="shepherd.toml")
#argparser.add_argument(
# argparser.add_argument(
# '-e', '--noedit', help="Disable the editable config temporarily", action="store_true", default=False)
#argparser.add_argument("-t", "--test", help="Test and interface function of the from 'plugin:function'",
# argparser.add_argument("-t", "--test", help="Test and interface function of the from 'plugin:function'",
# default=None)
#args = argparser.parse_args()
core_conf, plugin_classes, plugin_configs = compile_config(default_config)
if args.test is None:
control.init_control(core_conf, plugin_configs)
# if args.test is None:
# control.init_control(core_conf, plugin_configs)
scheduler.init_scheduler(core_conf)
plugin.init_plugins(plugin_classes, plugin_configs, core_conf)
@ -209,11 +208,11 @@ def cli(ctx, default_config):
except (KeyboardInterrupt, SystemExit):
pass
@click.argument('plugin_function')
@cli.command()
def test():
if args.test is not None:
(test_plugin, test_func) = args.test.split(':')
func = getattr(shepherd.plugin.plugin_functions[test_plugin], test_func)
print(func())
return
def test(plugin_function):
(test_plugin, test_func) = plugin_function.split(':')
func = getattr(plugin.plugin_functions[test_plugin], test_func)
print(func())
return

@ -1,77 +1,48 @@
#!/usr/bin/env python3
import shepherd.config
import shepherd.module
import shepherd.config as shconf
import shepherd.plugin
import sys
import os
import time
import argparse
from gpiozero import OutputDevice, Device
from gpiozero.pins.pigpio import PiGPIOFactory
from shepherd.modules.betterservo import BetterServo
class AphidtrapModule(shepherd.plugin.Plugin):
@staticmethod
def define_config(confdef):
pass
Device.pin_factory = PiGPIOFactory()
def __init__(self, pluginInterface, config):
super().__init__(pluginInterface, config)
APHIDTRAP_LED_PIN = 5 #Out2
class AphidtrapConfDef(shepherd.config.ConfDefinition):
def __init__(self):
super().__init__()
class AphidtrapModule(shepherd.module.SimpleModule):
conf_def = AphidtrapConfDef()
def setup(self):
self.config = config
self.interface = pluginInterface
self.plugins = pluginInterface.other_plugins
self.hooks = pluginInterface.hooks
print("Aphidtrap config:")
print(self.config)
self.led_power = OutputDevice(APHIDTRAP_LED_PIN,
active_high=True,
initial_value=False)
if "picam" in self.plugins:
self.interface.attach_hook("picam", "pre_cam", self.led_on)
self.interface.attach_hook("picam", "post_cam", self.led_off)
elif "usbcam" in self.plugins:
self.interface.attach_hook("usbcam", "pre_cam", self.led_on)
self.interface.attach_hook("usbcam", "post_cam", self.led_off)
else:
raise ValueError("Need to either have picam or usbcam plugin loaded")
def setup_other_modules(self):
self.modules.picam.hook_pre_cam.attach(self.led_on)
self.modules.picam.hook_post_cam.attach(self.led_off)
self.interface.register_function(self.test)
def led_on(self):
self.led_power.on()
self.plugins["scout"].set_out2(True)
def led_off(self):
self.led_power.off()
def main(argv):
argparser = argparse.ArgumentParser(
description='Module for aphidtrap control functions. Run for testing')
argparser.add_argument("configfile", nargs='?', metavar="configfile",
help="Path to configfile", default="conf.toml")
args = argparser.parse_args()
confman = shepherd.config.ConfigManager()
srcdict = {"aphidtrap": {}}
if os.path.isfile(args.configfile):
confman.load(args.configfile)
else:
confman.load(srcdict)
aphidtrap_mod = AphidtrapModule(confman.get_config("aphidtrap", AphidtrapConfDef()),
shepherd.module.Interface(None))
aphidtrap_mod.led_on()
time.sleep(2)
aphidtrap_mod.led_off()
self.plugins["scout"].set_out2(False)
if __name__ == "__main__":
main(sys.argv[1:])
def test(self):
self.led_on()
time.sleep(2)
self.led_off()

@ -25,7 +25,7 @@ class FlytrapPlugin(shepherd.plugin.Plugin):
self.hooks = pluginInterface.hooks
self.root_dir = os.path.expanduser(pluginInterface.coreconfig["root_dir"])
self.id = pluginInterface.coreconfig["id"]
self.id = pluginInterface.coreconfig["name"]
print("Flytrap config:")
print(self.config)

@ -9,7 +9,6 @@ import time
import argparse
class MothtrapPlugin(shepherd.plugin.Plugin):
@staticmethod
def define_config(confdef):
@ -25,40 +24,43 @@ class MothtrapPlugin(shepherd.plugin.Plugin):
self.hooks = pluginInterface.hooks
self.root_dir = os.path.expanduser(pluginInterface.coreconfig["root_dir"])
self.id = pluginInterface.coreconfig["id"]
self.id = pluginInterface.coreconfig["name"]
print("Mothtrap config:")
print(self.config)
#servo_max = self.config["servo_open_pulse"] / 1000000
#servo_min = self.config["servo_closed_pulse"] / 1000000
#if servo_min > servo_max:
# if servo_min > servo_max:
# servo_min, servo_max = servo_max, servo_min
#print(F"Supplied min: {servo_min}, max: {servo_max}")
self.interface.attach_hook("usbcam", "pre_cam", self.led_on)
self.interface.attach_hook("usbcam", "post_cam", self.led_off)
self.interface.attach_hook("usbcam", "post_cam", self.run_servo)
if "picam" in self.plugins:
self.interface.attach_hook("picam", "pre_cam", self.led_on)
self.interface.attach_hook("picam", "post_cam", self.led_off)
self.interface.attach_hook("picam", "post_cam", self.run_servo)
elif "usbcam" in self.plugins:
self.interface.attach_hook("usbcam", "pre_cam", self.led_on)
self.interface.attach_hook("usbcam", "post_cam", self.led_off)
self.interface.attach_hook("usbcam", "post_cam", self.run_servo)
else:
raise ValueError("Need to either have picam or usbcam plugin loaded")
self.interface.register_function(self.test)
def led_on(self):
self.plugins["scout"].set_out1(True)
#self.led_power.on()
# self.led_power.on()
def led_off(self):
self.plugins["scout"].set_out1(False)
#self.led_power.off()
# self.led_power.off()
def run_servo(self):
self.plugins["scout"].set_aux5v(True)
#self.door_servo_power.on()
# self.door_servo_power.on()
time.sleep(0.5)
self.plugins["scout"].set_pwm1(True, self.config["servo_open_pulse"])
@ -69,9 +71,9 @@ class MothtrapPlugin(shepherd.plugin.Plugin):
#self.door_servo.pulse_width = self.config["servo_closed_pulse"] / 1000000
time.sleep(self.config["servo_open_time"])
self.plugins["scout"].set_pwm1(False, self.config["servo_closed_pulse"])
#self.door_servo.detach()
# self.door_servo.detach()
self.plugins["scout"].set_aux5v(False)
#self.door_servo_power.off()
# self.door_servo_power.off()
def test(self):
self.led_on()

@ -43,7 +43,7 @@ class PiCamPlugin(shepherd.plugin.Plugin):
confdef.add_def('jpeg_quality', shconf.IntDef(default=80, minval=60, maxval=95, optional=True,
helptext="JPEG quality to save with. Max of 95, passed directly to Pillow"))
array = confdef.add_def('trigger', shconf.TableArrayDef(
array = confdef.add_def('trigger', shconf.DictListDef(
helptext="Array of triggers that will use all cameras"))
array.add_def('hour', shconf.StringDef())
array.add_def('minute', shconf.StringDef())
@ -57,7 +57,7 @@ class PiCamPlugin(shepherd.plugin.Plugin):
self.hooks = pluginInterface.hooks
self.root_dir = os.path.expanduser(pluginInterface.coreconfig["root_dir"])
self.id = pluginInterface.coreconfig["id"]
self.id = pluginInterface.coreconfig["name"]
self.interface.register_hook("pre_cam")
self.interface.register_hook("post_cam")
@ -70,7 +70,7 @@ class PiCamPlugin(shepherd.plugin.Plugin):
# Seconds to wait for exposure and white balance auto-adjust to stabilise
self.stabilise_delay = 3
if self.config["save_directory"] is "":
if self.config["save_directory"] == "":
self.save_directory = os.path.join(self.root_dir, "picamera")
else:
self.save_directory = self.config["save_directory"]
@ -120,21 +120,20 @@ class PiCamPlugin(shepherd.plugin.Plugin):
draw = ImageDraw.Draw(overlay)
draw.text((margin_size*2, margin_size), desc_text,
font=thisfont, fill=(255, 255, 255, 255))
font=thisfont, fill=(255, 255, 255, 255))
datewidth, _ = draw.textsize(time_text, thisfont)
draw.text((overlay.width-(margin_size*2)-datewidth, margin_size), time_text, font=thisfont,
fill=(255, 255, 255, 255))
fill=(255, 255, 255, 255))
# make whole overlay half transparent
overlay.putalpha(128)
return overlay
def camera_job(self):
self.hooks.pre_cam()
#Capture image
# Capture image
print("Running camera...")
stream = io.BytesIO()
with PiCamera() as picam:
@ -146,7 +145,7 @@ class PiCamPlugin(shepherd.plugin.Plugin):
stream.seek(0)
img = Image.open(stream)
#Process image
# Process image
image_time = datetime.now()
if self.config["show_overlay"]:

@ -151,7 +151,7 @@ class Bucket():
(not item.endswith(".uploading")) and
(not item.endswith(".uploaded"))):
bucket_files.append(item_path)
#TODO check for .uploaded files and either delete or
# TODO check for .uploaded files and either delete or
# if keep_copy, move to self.old_path
if bucket_files:
@ -161,7 +161,7 @@ class Bucket():
class UploaderPlugin(shepherd.plugin.Plugin):
@staticmethod
def define_config(confdef):
dests = confdef.add_def('destination', shconf.TableArrayDef())
dests = confdef.add_def('destination', shconf.DictListDef())
dests.add_def('name', shconf.StringDef())
dests.add_def('protocol', shconf.StringDef())
dests.add_def('address', shconf.StringDef(optional=True))
@ -174,7 +174,7 @@ class UploaderPlugin(shepherd.plugin.Plugin):
dests.add_def('add_id_to_path', shconf.BoolDef(
default=True, optional=True))
buckets = confdef.add_def('bucket', shconf.TableArrayDef())
buckets = confdef.add_def('bucket', shconf.DictListDef())
buckets.add_def('name', shconf.StringDef())
buckets.add_def('open_link_on_new', shconf.BoolDef())
buckets.add_def('opportunistic', shconf.BoolDef(
@ -190,7 +190,7 @@ class UploaderPlugin(shepherd.plugin.Plugin):
self.hooks = pluginInterface.hooks
self.root_dir = os.path.expanduser(pluginInterface.coreconfig["root_dir"])
self.id = pluginInterface.coreconfig["id"]
self.id = pluginInterface.coreconfig["name"]
print("Uploader config:")
print(self.config)

@ -29,11 +29,15 @@ CameraPort = namedtuple(
'CameraPort', ['usbPath', 'devicePath'])
# Short wrapper to allow use in a ``with`` context
class VideoCaptureCtx():
def __init__(self, *args, **kwargs):
self.capture_dev = cv2.VideoCapture(*args, **kwargs)
def __enter__(self):
return self.capture_dev
def __exit__(self, *args):
self.capture_dev.release()
@ -116,13 +120,13 @@ class USBCamPlugin(shepherd.plugin.Plugin):
confdef.add_def('stabilise_delay', shconf.IntDef(default=5, minval=1, maxval=30, optional=True,
helptext="Number of seconds to wait after starting each camera for exposure and white balance to settle"))
array = confdef.add_def('trigger', shconf.TableArrayDef(
array = confdef.add_def('trigger', shconf.DictListDef(
helptext="Array of triggers that will use all cameras"))
array.add_def('hour', shconf.StringDef())
array.add_def('minute', shconf.StringDef())
array.add_def('second', shconf.StringDef(default="0", optional=True))
camarray = confdef.add_def('camera', shconf.TableArrayDef(
camarray = confdef.add_def('camera', shconf.DictListDef(
helptext="List of cameras to try and connect to. Multiple ports may be listed, and any not connected will be skipped on each trigger."))
camarray.add_def('name', shconf.StringDef(default="", optional=False,
helptext="Name of camera, appended to filename and added to overlay"))
@ -137,7 +141,7 @@ class USBCamPlugin(shepherd.plugin.Plugin):
self.hooks = pluginInterface.hooks
self.root_dir = os.path.expanduser(pluginInterface.coreconfig["root_dir"])
self.id = pluginInterface.coreconfig["id"]
self.id = pluginInterface.coreconfig["name"]
self.interface.register_hook("pre_cam")
self.interface.register_hook("post_cam")
@ -150,7 +154,7 @@ class USBCamPlugin(shepherd.plugin.Plugin):
self.gstlock = threading.Lock()
if self.config["save_directory"] is "":
if self.config["save_directory"] == "":
self.save_directory = os.path.join(self.root_dir, "usbcamera")
else:
self.save_directory = self.config["save_directory"]
@ -237,7 +241,7 @@ class USBCamPlugin(shepherd.plugin.Plugin):
if self.config["append_id"]:
image_filename = image_filename + " " + self.id
if camera_name is not "":
if camera_name != "":
image_filename = image_filename+" "+camera_name
image_filename = image_filename + ".jpg"
image_filename = os.path.join(self.save_directory, image_filename)
@ -252,10 +256,10 @@ class USBCamPlugin(shepherd.plugin.Plugin):
with self.gstlock:
#gst_str = ('v4l2src device='+device_path+' ! '
# gst_str = ('v4l2src device='+device_path+' ! '
# 'videoconvert ! appsink drop=true max-buffers=1 sync=false')
#vidcap = cv2.VideoCapture(gst_str, cv2.CAP_GSTREAMER)
# vidcap = cv2.VideoCapture(gst_str, cv2.CAP_GSTREAMER)
fmts = get_capture_formats(device_path)
@ -264,7 +268,6 @@ class USBCamPlugin(shepherd.plugin.Plugin):
size = get_largest_resolution(fmts["MJPG"])
set_camera_format_opencv(vidcap, "MJPG", size[0], size[1])
# stream only starts after first grab
print("Starting cam")
@ -272,28 +275,27 @@ class USBCamPlugin(shepherd.plugin.Plugin):
delay_start = time.time()
while (time.time() - delay_start) < self.config["stabilise_delay"]:
vidcap.grab()
#time.sleep(self.config["stabilise_delay"])
# time.sleep(self.config["stabilise_delay"])
# clear old buffer
#print("Flushing capture")
#vidcap.grab()
# print("Flushing capture")
# vidcap.grab()
print("Reading")
read_flag, frame = vidcap.read()
#print("Changing to YUYV")
#if "YUYV" in fmts:
# print("Changing to YUYV")
# if "YUYV" in fmts:
# size = get_largest_resolution(fmts["YUYV"])
# set_camera_format_opencv(vidcap, "YUYV", size[0], size[1])
#print("Reading again")
#read_flag, frame2 = vidcap.read()
# print("Reading again")
# read_flag, frame2 = vidcap.read()
if read_flag:
self._process_image(frame, camera_name)
#self._process_image(frame2, camera_name+"(2)")
# self._process_image(frame2, camera_name+"(2)")
else:
print("Could not read camera "+camera_name +
" on USB port "+device_path)
def run_cameras(self, name_suffix = ""):
def run_cameras(self, name_suffix=""):
connected_cams = OrderedDict(get_connected_cameras())
for defined_name, defined_usb_path in self.defined_cams.items():

@ -130,12 +130,12 @@ def save_jobs():
raise Exception("Could not save scheduler job " +
job.id+" - Trigger is not a CronTrigger")
saved_jobs.append(job.id)
if next_job_time is not None:
if job.next_run_time < next_job_time:
if hasattr(job,"next_run_time"):
if next_job_time is not None:
if job.next_run_time < next_job_time:
next_job_time = job.next_run_time
else:
next_job_time = job.next_run_time
else:
next_job_time = job.next_run_time
with open(joblist_path+".writing", 'w+') as f:
for saved_job in saved_jobs:
@ -189,8 +189,8 @@ def _jobs_changed(event):
print(retval)
if retval == alarm_str:
if is_raspberry_pi():
print("Shutting down in 1 minute")
time.sleep(60)
print("Shutting down in 2 minutes")
time.sleep(120)
subprocess.run(["shutdown","now"])
else:

@ -0,0 +1,38 @@
[shepherd]
hostname = "DPIRD-test"
id = "DPIRD-test"
plugin_dir = "/home/pi/plugins/"
plugins = ["scout", "picam", "uploader", "mothtrap"]
root_dir = "/home/pi/"
conf_edit_path = "/boot/shepherd.toml"
control_server = "api.shepherd.distreon.net"
api_key = "v2EgvYzx79c8fCP4P7jlWxTZ3pc"
[scout]
boardver = "3"
serialport = "/dev/ttyS0"
[uploader]
[[uploader.destination]]
name = "agricSFTP"
protocol = "sftp"
address = "agric.files.distreon.net"
port = 2222
path = "/mothtraps"
username = "agric"
password = "asherhaze"
[[uploader.bucket]]
name = "imageupload"
open_link_on_new = true
keep_copy = false
destination = "agricSFTP"
[picam]
upload_images = true
upload_bucket = "imageupload"
[[picam.trigger]]
hour = "*"
minute ="*/15"
second = "0"
[mothtrap]
servo_open_pulse = 900
servo_closed_pulse = 2100
servo_open_time = 3
# shepherd_message: Successfully applied this config at:2020-03-29 18:29:28.566836

@ -0,0 +1,50 @@
[shepherd]
hostname = "DPIRD-test"
id = "DPIRD-test"
plugin_dir = "/home/pi/plugins/"
plugins = ["scout", "usbcam", "uploader", "flytrap"]
root_dir = "/home/pi/"
conf_edit_path = "/boot/shepherd.toml"
control_server = "api.shepherd.distreon.net"
api_key = "v2EgvYzx79c8fCP4P7jlWxTZ3pc"
[scout]
boardver = "3"
serialport = "/dev/ttyS0"
[uploader]
[[uploader.destination]]
name = "agricSFTP"
protocol = "sftp"
address = "agric.files.distreon.net"
port = 2222
path = "/mothtraps"
username = "agric"
password = "asherhaze"
[[uploader.bucket]]
name = "imageupload"
open_link_on_new = true
keep_copy = false
destination = "agricSFTP"
[usbcam]
upload_images = true
upload_bucket = "imageupload"
[[usbcam.camera]]
name = "USB1"
usb_port = "1.2"
[[usbcam.camera]]
name = "USB2"
usb_port = "1.3"
[[usbcam.camera]]
name = "USB3"
usb_port = "1.1"
[[usbcam.camera]]
name = "USB"
usb_port = "*"
[[usbcam.trigger]]
hour = "*"
minute ="0"
second = "0"
[flytrap]
servo_open_pulse = 2100
servo_closed_pulse = 900
servo_open_time = 3
# shepherd_message: Successfully applied this config at:2019-12-16 00:59:30.985447
Loading…
Cancel
Save