Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/KevinOConnor/klipper
Browse files Browse the repository at this point in the history
  • Loading branch information
dianlight committed Nov 8, 2020
2 parents 8c555dc + 9e16977 commit d592082
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 142 deletions.
30 changes: 30 additions & 0 deletions docs/API_Server.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,36 @@ transition to a "shutdown" state. It behaves similarly to the G-Code
`M112` command. For example:
`{"id": 123, "method": "emergency_stop"}`

### register_remote_method

This endpoint allows clients to register methods that can be called
from klipper. It will return an empty object upon success.

For example:
`{"id": 123, "method": "register_remote_method",
"params": {"response_template": {"action": "run_paneldue_beep"},
"remote_method": "paneldue_beep"}}`
will return:
`{"id": 123, "result": {}}`

The remote method `paneldue_beep` may now be called from Klipper. Note
that if the method takes parameters they should be provided as keyword
arguments. Below is an example of how it may called from a gcode_macro:
```
[gcode_macro PANELDUE_BEEP]
default_parameter_FREQUENCY: 300
default_parameter_DURATION: 1.
gcode:
{action_call_remote_method("paneldue_beep",
frequency=FREQUENCY|int,
duration=DURATION|float)}
```

When the PANELDUE_BEEP gcode macro is executed, Klipper would send something
like the following over the socket:
`{"action": "run_paneldue_beep",
"params": {"frequency": 300, "duration": 1.0}}

### objects/list

This endpoint queries the list of available printer "objects" that one
Expand Down
4 changes: 4 additions & 0 deletions docs/Command_Templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,10 @@ Available "action" commands:
- `action_emergency_stop(msg)`: Transition the printer to a shutdown
state. The `msg` parameter is optional, it may be useful to describe
the reason for the shutdown.
- `action_call_remote_method(method_name)`: Calls a method registered
by a remote client. If the method takes parameters they should
be provided via keyword arguments, ie:
`action_call_remote_method("print_stuff", my_arg="hello_world")`

### Variables

Expand Down
179 changes: 79 additions & 100 deletions klippy/chelper/itersolve.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,52 +22,6 @@ struct timepos {
double time, position;
};

// Find step using "false position" method (with "Illinois algorithm")
static struct timepos
itersolve_find_step(struct stepper_kinematics *sk, struct move *m
, struct timepos low, struct timepos high
, double target)
{
sk_calc_callback calc_position_cb = sk->calc_position_cb;
struct timepos best_guess = high;
low.position -= target;
high.position -= target;
if (!high.position)
// The high range was a perfect guess for the next step
return best_guess;
int high_sign = signbit(high.position);
if (high_sign == signbit(low.position))
// The target is not in the low/high range - return low range
return (struct timepos){ low.time, target };
int prev_choice = 0;
for (;;) {
double guess_time = ((low.time*high.position - high.time*low.position)
/ (high.position - low.position));
best_guess.time = guess_time;
best_guess.position = calc_position_cb(sk, m, guess_time);
double guess_position = best_guess.position - target;
if (fabs(guess_position) <= .000000001)
break;
int guess_sign = signbit(guess_position);
if (guess_sign == high_sign) {
high.time = guess_time;
high.position = guess_position;
if (prev_choice > 0)
low.position *= .5;
prev_choice = 1;
} else {
low.time = guess_time;
low.position = guess_position;
if (prev_choice < 0)
high.position *= .5;
prev_choice = -1;
}
if (high.time - low.time <= .000000001)
break;
}
return best_guess;
}

#define SEEK_TIME_RESET 0.000100

// Generate step times for a portion of a move
Expand All @@ -82,67 +36,92 @@ itersolve_gen_steps_range(struct stepper_kinematics *sk, struct move *m
start = 0.;
if (end > m->move_t)
end = m->move_t;
struct timepos last = { start, sk->commanded_pos }, low = last, high = last;
double seek_time_delta = SEEK_TIME_RESET;
int sdir = stepcompress_get_step_dir(sk->sc), is_dir_change = 0;
struct timepos old_guess = {start, sk->commanded_pos}, guess = old_guess;
int sdir = stepcompress_get_step_dir(sk->sc);
int is_dir_change = 0, have_bracket = 0, check_oscillate = 0;
double target = sk->commanded_pos + (sdir ? half_step : -half_step);
double last_time=start, low_time=start, high_time=start + SEEK_TIME_RESET;
if (high_time > end)
high_time = end;
for (;;) {
double diff = high.position - last.position, dist = sdir ? diff : -diff;
if (dist >= half_step) {
// Have valid upper bound - now find step
double target = last.position + (sdir ? half_step : -half_step);
struct timepos next = itersolve_find_step(sk, m, low, high, target);
// Add step at given time
int ret = stepcompress_append(sk->sc, sdir
, m->print_time, next.time);
if (ret)
return ret;
seek_time_delta = next.time - last.time;
if (seek_time_delta < .000000001)
seek_time_delta = .000000001;
if (is_dir_change && seek_time_delta > SEEK_TIME_RESET)
seek_time_delta = SEEK_TIME_RESET;
is_dir_change = 0;
last.position = target + (sdir ? half_step : -half_step);
last.time = next.time;
low = next;
if (low.time < high.time)
// The existing search range is still valid
continue;
} else if (dist > 0.) {
// Avoid rollback if stepper fully reaches target position
stepcompress_commit(sk->sc);
} else if (unlikely(dist < -(half_step + .000000001))) {
// Found direction change
is_dir_change = 1;
if (seek_time_delta > SEEK_TIME_RESET)
seek_time_delta = SEEK_TIME_RESET;
if (low.time > last.time) {
// Update direction and retry
// Use the "secant method" to guess a new time from previous guesses
double guess_dist = guess.position - target;
double og_dist = old_guess.position - target;
double next_time = ((old_guess.time*guess_dist - guess.time*og_dist)
/ (guess_dist - og_dist));
if (!(next_time > low_time && next_time < high_time)) { // or NaN
// Next guess is outside bounds checks - validate it
if (have_bracket) {
// A poor guess - fall back to bisection
next_time = (low_time + high_time) * .5;
check_oscillate = 0;
} else if (guess.time >= end) {
// No more steps present in requested time range
break;
} else {
// Might be a poor guess - limit to exponential search
next_time = high_time;
high_time = 2. * high_time - last_time;
if (high_time > end)
high_time = end;
}
}
// Calculate position at next_time guess
old_guess = guess;
guess.time = next_time;
guess.position = calc_position_cb(sk, m, next_time);
guess_dist = guess.position - target;
if (fabs(guess_dist) > .000000001) {
// Guess does not look close enough - update bounds
double rel_dist = sdir ? guess_dist : -guess_dist;
if (rel_dist > 0.) {
// Found position past target, so step is definitely present
if (have_bracket && old_guess.time <= low_time) {
if (check_oscillate)
// Force bisect next to avoid persistent oscillations
old_guess = guess;
check_oscillate = 1;
}
high_time = guess.time;
have_bracket = 1;
} else if (rel_dist < -(half_step + half_step + .000000010)) {
// Found direction change
sdir = !sdir;
continue;
target = (sdir ? target + half_step + half_step
: target - half_step - half_step);
low_time = last_time;
high_time = guess.time;
is_dir_change = have_bracket = 1;
check_oscillate = 0;
} else {
low_time = guess.time;
}
// Must update range to avoid re-finding previous time
if (high.time > last.time + .000000001) {
// Reduce the high bound - it will become a better low bound
high.time = (last.time + high.time) * .5;
high.position = calc_position_cb(sk, m, high.time);
if (!have_bracket || high_time - low_time > .000000001) {
if (!is_dir_change && rel_dist >= -half_step)
// Avoid rollback if stepper fully reaches step position
stepcompress_commit(sk->sc);
// Guess is not close enough - guess again with new time
continue;
}
}
// Need to increase the search range to find an upper bound
if (high.time >= end)
// At end of move
break;
low = high;
do {
high.time = last.time + seek_time_delta;
seek_time_delta += seek_time_delta;
} while (unlikely(high.time <= low.time));
if (high.time > end)
high.time = end;
high.position = calc_position_cb(sk, m, high.time);
// Found next step - submit it
int ret = stepcompress_append(sk->sc, sdir, m->print_time, guess.time);
if (ret)
return ret;
target = sdir ? target+half_step+half_step : target-half_step-half_step;
// Reset bounds checking
double seek_time_delta = 1.5 * (guess.time - last_time);
if (seek_time_delta < .000000001)
seek_time_delta = .000000001;
if (is_dir_change && seek_time_delta > SEEK_TIME_RESET)
seek_time_delta = SEEK_TIME_RESET;
last_time = low_time = guess.time;
high_time = guess.time + seek_time_delta;
if (high_time > end)
high_time = end;
is_dir_change = have_bracket = check_oscillate = 0;
}
sk->commanded_pos = last.position;
sk->commanded_pos = target - (sdir ? half_step : -half_step);
if (sk->post_cb)
sk->post_cb(sk);
return 0;
Expand Down
8 changes: 8 additions & 0 deletions klippy/extras/gcode_macro.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,20 @@ def _action_respond_info(self, msg):
return ""
def _action_raise_error(self, msg):
raise self.printer.command_error(msg)
def _action_call_remote_method(self, method, **kwargs):
webhooks = self.printer.lookup_object('webhooks')
try:
webhooks.call_remote_method(method, **kwargs)
except self.printer.command_error:
logging.exception("Remote Call Error")
return ""
def create_template_context(self, eventtime=None):
return {
'printer': GetStatusWrapper(self.printer, eventtime),
'action_emergency_stop': self._action_emergency_stop,
'action_respond_info': self._action_respond_info,
'action_raise_error': self._action_raise_error,
'action_call_remote_method': self._action_call_remote_method,
}

def load_config(config):
Expand Down
85 changes: 44 additions & 41 deletions klippy/extras/neopixel.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ def build_config(self):
self.neopixel_update_cmd = self.mcu.lookup_command(
"neopixel_update oid=%c pos=%hu data=%*s", cq=cmd_queue)
self.neopixel_send_cmd = self.mcu.lookup_query_command(
"neopixel_send oid=%c", "neopixel_result success=%c", cq=cmd_queue)
"neopixel_send oid=%c", "neopixel_result oid=%c success=%c",
oid=self.oid, cq=cmd_queue)
def update_color_data(self, red, green, blue, white, index=None):
red = int(red * 255. + .5)
blue = int(blue * 255. + .5)
Expand All @@ -76,41 +77,37 @@ def update_color_data(self, red, green, blue, white, index=None):
elem_size = len(color_data)
self.color_data[(index-1)*elem_size:index*elem_size] = color_data
def send_data(self, print_time=None):
with self.mutex:
old_data, new_data = self.old_color_data, self.color_data
if new_data == old_data:
return
# Find the position of all changed bytes in this framebuffer
diffs = [[i, 1] for i, (n, o) in enumerate(zip(new_data, old_data))
if n != o]
# Batch together changes that are close to each other
for i in range(len(diffs)-2, -1, -1):
pos, count = diffs[i]
nextpos, nextcount = diffs[i+1]
if pos + 5 >= nextpos and nextcount < 16:
diffs[i][1] = nextcount + (nextpos - pos)
del diffs[i+1]
# Transmit changes
ucmd = self.neopixel_update_cmd.send
for pos, count in diffs:
ucmd([self.oid, pos, new_data[pos:pos+count]],
reqclock=BACKGROUND_PRIORITY_CLOCK)
old_data[:] = new_data
# Instruct mcu to update the LEDs
minclock = 0
if print_time is not None:
minclock = self.mcu.print_time_to_clock(print_time)
scmd = self.neopixel_send_cmd.send
for i in range(8):
params = scmd([self.oid], minclock=minclock,
reqclock=BACKGROUND_PRIORITY_CLOCK)
if params['success']:
break
else:
logging.info("Neopixel update did not succeed")
def send_data_bg(self, print_time):
reactor = self.printer.get_reactor()
reactor.register_callback(lambda et: self.send_data(print_time))
old_data, new_data = self.old_color_data, self.color_data
if new_data == old_data:
return
# Find the position of all changed bytes in this framebuffer
diffs = [[i, 1] for i, (n, o) in enumerate(zip(new_data, old_data))
if n != o]
# Batch together changes that are close to each other
for i in range(len(diffs)-2, -1, -1):
pos, count = diffs[i]
nextpos, nextcount = diffs[i+1]
if pos + 5 >= nextpos and nextcount < 16:
diffs[i][1] = nextcount + (nextpos - pos)
del diffs[i+1]
# Transmit changes
ucmd = self.neopixel_update_cmd.send
for pos, count in diffs:
ucmd([self.oid, pos, new_data[pos:pos+count]],
reqclock=BACKGROUND_PRIORITY_CLOCK)
old_data[:] = new_data
# Instruct mcu to update the LEDs
minclock = 0
if print_time is not None:
minclock = self.mcu.print_time_to_clock(print_time)
scmd = self.neopixel_send_cmd.send
for i in range(8):
params = scmd([self.oid], minclock=minclock,
reqclock=BACKGROUND_PRIORITY_CLOCK)
if params['success']:
break
else:
logging.info("Neopixel update did not succeed")
cmd_SET_LED_help = "Set the color of an LED"
def cmd_SET_LED(self, gcmd):
# Parse parameters
Expand All @@ -120,11 +117,17 @@ def cmd_SET_LED(self, gcmd):
white = gcmd.get_float('WHITE', 0., minval=0., maxval=1.)
index = gcmd.get_int('INDEX', None, minval=1, maxval=self.chain_count)
transmit = gcmd.get_int('TRANSMIT', 1)
self.update_color_data(red, green, blue, white, index)
# Send command
if transmit:
toolhead = self.printer.lookup_object('toolhead')
toolhead.register_lookahead_callback(self.send_data_bg)
# Update and transmit data
def reactor_bgfunc(print_time):
with self.mutex:
self.update_color_data(red, green, blue, white, index)
if transmit:
self.send_data(print_time)
def lookahead_bgfunc(print_time):
reactor = self.printer.get_reactor()
reactor.register_callback(lambda et: reactor_bgfunc(print_time))
toolhead = self.printer.lookup_object('toolhead')
toolhead.register_lookahead_callback(lookahead_bgfunc)

def load_config_prefix(config):
return PrinterNeoPixel(config)
Loading

0 comments on commit d592082

Please sign in to comment.