drone-rigide/scripts/control_compute.py
2019-06-02 18:02:15 +02:00

233 lines
7.9 KiB
Python
Executable file

#! /usr/bin/python
from scipy.signal import savgol_coeffs
import numpy as np
import sys
import threading
import rospy
from std_msgs.msg import Float64, Empty
import dynamic_reconfigure.server
from detect_targets.cfg import ProportionalNodeConfig, IntegralNodeConfig
from detect_targets.cfg import DerivativeNodeConfig, InputNodeConfig,
from detect_targets.cfg import SaturateNodeConfig, RateNodeConfig
class ControlNode(object):
def __init__(self, refresh_time=1.0, config_class=None):
self.last_time = rospy.get_rostime()
self.refresh_time = rospy.Duration.from_sec(refresh_time)
self.output = 0.0
self.output_topic = rospy.Publisher('output', Float64, queue_size=1)
self.input_topics = {
"reset": rospy.Subscriber("reset", Empty, self.on_reset),
}
if config_class is not None:
self.server = dynamic_reconfigure.server.Server(config_class, self.on_reconf)
def on_reset(self, value):
if value:
self.output = 0.0
def on_reconf(self, config, level):
self.refresh_time = rospy.Duration.from_sec(config["refresh_time"])
return config
def on_compute(self, value):
self.output_topic.publish(data=self.output)
self.last_time = rospy.get_rostime()
def check_time(self, delta_time=0.0):
t = rospy.get_rostime()
if delta_time == 0.0:
delta_time = t - self.last_time
return delta_time >= self.refresh_time, delta_time.to_sec()
def on_mainloop(self):
rospy.spin()
class InputControlNode(ControlNode):
def __init__(self, *args, **kwargs):
super(InputControlNode, self).__init__(*args, **kwargs)
self.input_topics["input"] = rospy.Subscriber("input", Float64, self.on_compute)
class RateNode(InputControlNode):
def __init__(self, *args, **kwargs):
self.rate = rospy.Rate(20)
self.last_input = 0
self.lock = threading.Lock()
super(RateNode, self).__init__(*args, **kwargs)
def on_reconf(self, config, level):
c = super(SaturateNode, self).on_reconf(config, level)
self.rate = rospy.Rate(self.refresh_time)
return c
def on_compute(self, value):
self.lock.acquire()
self.last_input = value.data
self.lock.release()
def on_mainloop(self):
while not rospy.is_shutdown():
self.lock.acquire()
self.output.publish(self.last_input)
self.lock.release()
self.rate.sleep()
class ProportionalNode(InputControlNode):
def __init__(self, k=1.0, refresh_time=1.0):
super(ProportionalNode, self).__init__(refresh_time, ProportionalNodeConfig)
self.k = k
def on_reconf(self, config, level):
self.k = config["k"]
return super(ProportionalNode, self).on_reconf(config, level)
def on_compute(self, value):
time_ok,_ = self.check_time()
if time_ok:
self.output = self.k * value.data
super(ProportionalNode, self).on_compute(value)
class SaturateNode(InputControlNode):
def __init__(self, min=None, max=None, refresh_time=1.0, config=SaturateNodeConfig):
super(SaturateNode, self).__init__(refresh_time, config)
self.min = min
self.max = max
def on_reconf(self, config, level):
self.min = config["min"] if config["use_min"] else None
self.max = config["max"] if config["use_max"] else None
return super(SaturateNode, self).on_reconf(config, level)
def on_compute(self, value):
time_ok,_ = self.check_time()
if time_ok:
self.output = min(self.max or value.data, value.data)
self.output = max(self.min or self.output, self.output)
super(SaturateNode, self).on_compute(value)
class IntegralNode(SaturateNode):
def __init__(self, k=1.0, min=None, max=None, refresh_time=1.0):
super(IntegralNode, self).__init__(min, max, refresh_time, IntegralNodeConfig)
self.k = k
def on_reconf(self, config, level):
self.k = config["k"]
return super(IntegralNode, self).on_reconf(config, level)
def on_compute(self, value):
time_ok, delta = self.check_time()
if time_ok:
self.output += value.data * delta
return super(IntegralNode, self).on_compute(value)
class DerivativeNode(InputControlNode):
def __init__(self, k=0.05, size=9, polyorder=3, deriv=1, refresh_time=1.0):
super(DerivativeNode, self).__init__(refresh_time, DerivativeNodeConfig)
self.k = k
self.filter = savgol_coeffs(size, polyorder, deriv, delta=refresh_time, use='dot')
self.last_points = np.zeros(size)
def on_reconf(self, config, level):
self.k = config["k"]
try:
self.filter = savgol_coeffs(
config["size"],
config["poly_order"],
config["deriv"],
delta=config["refresh_time"],
use='dot'
)
except ValueError as e:
rospy.logerr(e)
self.last_points = np.zeros(config["size"])
return super(DerivativeNode, self).on_reconf(config, level)
def on_compute(self, value):
time_ok, delta = self.check_time()
if time_ok:
self.last_points = np.concatenate([self.last_points[1:], [value.data]])
self.output = self.last_points.dot(self.filter)
return super(DerivativeNode, self).on_compute(value)
def on_reset(self, value):
if value:
self.last_points = np.zeros(len(self.filter))
return super(DerivativeNode, self).on_reset(value)
class DifferenciateNode(InputControlNode):
def __init__(self):
self.measure = 0.0
self.input = 0.0
super(DifferenciateNode, self).__init__()
self.input_topics["measure"] = rospy.Subscriber("measure", Float64, self.on_compute_measure)
def on_compute(self, value):
self.input = value.data
self.output = self.input - self.measure
super(DifferenciateNode, self).on_compute(value)
def on_compute_measure(self, value):
self.measure = value.data
self.output = self.input - self.measure
super(DifferenciateNode, self).on_compute(value)
class InputNode(InputControlNode):
def __init__(self):
super(InputNode, self).__init__(refresh_time=1.0, config_class=InputNodeConfig)
def on_reconf(self, config, level):
self.output = config["value"]
super(InputNode, self).on_compute(config["value"])
return config
def on_compute(self, value):
self.output = value.data
super(InputNode, self).on_compute(value)
class SumNode(ControlNode):
def __init__(self, nb_topics):
super(SumNode, self).__init__(refresh_time=1.0)
self.nb_topics = int(nb_topics)
self.inputs = dict()
for i in range(self.nb_topics):
topic_name = 'input_'+str(i)
self.input_topics[topic_name] = rospy.Subscriber(topic_name, Float64, lambda v: self.inputs.update(dict(topic_name=v.data)) or self.on_compute(v))
def on_compute(self, value):
self.output = sum(self.inputs.values())
super(SumNode, self).on_compute(value)
if __name__ == '__main__':
nodes = {
'sum' : (SumNode, True),
'differenciate' : (DifferenciateNode, False),
'input' : (InputNode, False),
'saturate' : (SaturateNode, False),
'derivative' : (DerivativeNode, False),
'integral' : (IntegralNode, False),
'proportional' : (ProportionalNode, False),
'rate': (RateNode, False),
}
rospy.init_node('control_node', anonymous=True)
try:
node_class, need_param = nodes[sys.argv[1]]
except KeyError as e:
raise ValueError(sys.argv[1] + " is not a valid node name.")
if need_param:
param = sys.argv[2]
node = node_class(param)
else:
node = node_class()
node.on_mailoop()