AXIS_GET(velocity_max, float, 0)
AXIS_GET(homed, bool, false)
AXIS_SET(homed, bool)
-AXIS_GET(homing_mode, homing_mode_t, HOMING_DISABLED)
+AXIS_GET(homing_mode, homing_mode_t, HOMING_MANUAL)
AXIS_SET(homing_mode, homing_mode_t)
AXIS_GET(radius, float, 0)
AXIS_GET(travel_min, float, 0)
float get_homing_dir(int axis) {
switch (axes[axis].homing_mode) {
- case HOMING_DISABLED: break;
+ case HOMING_MANUAL: break;
case HOMING_STALL_MIN: case HOMING_SWITCH_MIN: return -1;
case HOMING_STALL_MAX: case HOMING_SWITCH_MAX: return 1;
}
float get_home(int axis) {
switch (axes[axis].homing_mode) {
- case HOMING_DISABLED: break;
+ case HOMING_MANUAL: break;
case HOMING_STALL_MIN: case HOMING_SWITCH_MIN: return get_travel_min(axis);
case HOMING_STALL_MAX: case HOMING_SWITCH_MAX: return get_travel_max(axis);
}
typedef enum {
- HOMING_DISABLED,
+ HOMING_MANUAL,
HOMING_STALL_MIN,
HOMING_STALL_MAX,
HOMING_SWITCH_MIN,
static char *_cmd = 0;
-static void _reboot() {hw_request_hard_reset();}
-
-
-static unsigned _parse_axis(uint8_t axis) {
- switch (axis) {
- case 'x': return 0; case 'y': return 1; case 'z': return 2;
- case 'a': return 3; case 'b': return 4; case 'c': return 5;
- case 'X': return 0; case 'Y': return 1; case 'Z': return 2;
- case 'A': return 3; case 'B': return 4; case 'C': return 5;
- default: return axis;
- }
-}
-
-
static void command_i2c_cb(i2c_cmd_t cmd, uint8_t *data, uint8_t length) {
switch (cmd) {
case I2C_NULL: break;
case I2C_STEP: mp_request_step(); break;
case I2C_FLUSH: mp_request_flush(); break;
case I2C_REPORT: report_request_full(); break;
- case I2C_REBOOT: _reboot(); break;
- case I2C_ZERO:
- if (length == 0) mach_zero_all();
- else if (length == 1) mach_zero_axis(_parse_axis(*data));
- break;
+ case I2C_REBOOT: hw_request_hard_reset(); break;
}
}
uint8_t command_reboot(int argc, char *argv[]) {
- _reboot();
+ hw_request_hard_reset();
return 0;
}
I2C_FLUSH,
I2C_REPORT,
I2C_REBOOT,
- I2C_ZERO,
} i2c_cmd_t;
bool mach_get_absolute_mode() {return mach.gm.absolute_mode;}
path_mode_t mach_get_path_mode() {return mach.gm.path_mode;}
bool mach_is_exact_stop() {return mach.gm.path_mode == PATH_EXACT_STOP;}
+bool mach_in_absolute_mode() {return mach.gm.distance_mode == ABSOLUTE_MODE;}
distance_mode_t mach_get_distance_mode() {return mach.gm.distance_mode;}
distance_mode_t mach_get_arc_distance_mode() {return mach.gm.arc_distance_mode;}
* Axes that need processing are signaled in @param flags.
*/
void mach_calc_target(float target[], const float values[],
- const bool flags[]) {
+ const bool flags[], bool absolute) {
for (int axis = 0; axis < AXES; axis++) {
target[axis] = mach.position[axis];
if (!flags[axis] || !axis_is_enabled(axis)) continue;
- target[axis] = mach.gm.distance_mode == ABSOLUTE_MODE ?
- mach_get_active_coord_offset(axis) : mach.position[axis];
+ target[axis] = absolute ? mach_get_active_coord_offset(axis) :
+ mach.position[axis];
float radius = axis_get_radius(axis);
if (radius) // Handle radius mode if radius is non-zero
}
-stat_t mach_zero_all() {
- for (unsigned axis = 0; axis < AXES; axis++) {
- stat_t status = mach_zero_axis(axis);
- if (status != STAT_OK) return status;
- }
-
- return STAT_OK;
-}
-
-
-stat_t mach_zero_axis(unsigned axis) {
- if (!mp_is_quiescent()) return STAT_MACH_NOT_QUIESCENT;
- if (AXES <= axis) return STAT_INVALID_AXIS;
-
- mach_set_axis_position(axis, 0);
-
- return STAT_OK;
-}
-
-
// G28.3 functions and support
static stat_t _exec_home(mp_buffer_t *bf) {
- const float *origin = bf->target;
+ const float *target = bf->target;
const float *flags = bf->unit;
for (int axis = 0; axis < AXES; axis++)
- if (flags[axis]) mp_runtime_set_axis_position(axis, origin[axis]);
+ if (flags[axis]) {
+ mp_runtime_set_axis_position(axis, target[axis]);
+ axis_set_homed(axis, true);
+ }
mp_runtime_set_steps_from_position();
void mach_set_home(float origin[], bool flags[]) {
mp_buffer_t *bf = mp_queue_get_tail();
+ // Compute target position
+ mach_calc_target(bf->target, origin, flags, true);
+
for (int axis = 0; axis < AXES; axis++)
if (flags[axis] && isfinite(origin[axis])) {
- // TODO What about work offsets?
- mach.position[axis] = TO_MM(origin[axis]); // set model position
- mp_set_axis_position(axis, mach.position[axis]); // set mm position
- axis_set_homed(axis, true);
+ bf->target[axis] -= mach_get_active_coord_offset(axis);
+ mach.position[axis] = bf->target[axis];
+ mp_set_axis_position(axis, bf->target[axis]); // set mm position
+ bf->unit[axis] = true;
- bf->target[axis] = origin[axis];
- bf->unit[axis] = flags[axis];
- }
+ } else bf->unit[axis] = false;
// Synchronized update of runtime position
mp_queue_push_nonstop(_exec_home, mach_get_line());
if (flags[axis])
mach.origin_offset[axis] = mach.position[axis] -
mach.offset[mach.gm.coord_system][axis] - TO_MM(offset[axis]);
+
+ mach_update_work_offsets(); // update resolved offsets
}
for (int axis = 0; axis < AXES; axis++)
mach.origin_offset[axis] = 0;
+
+ mach_update_work_offsets(); // update resolved offsets
}
/// G92.2
-void mach_suspend_origin_offsets() {mach.origin_offset_enable = false;}
+void mach_suspend_origin_offsets() {
+ mach.origin_offset_enable = false;
+ mach_update_work_offsets(); // update resolved offsets
+}
/// G92.3
-void mach_resume_origin_offsets() {mach.origin_offset_enable = true;}
+void mach_resume_origin_offsets() {
+ mach.origin_offset_enable = true;
+ mach_update_work_offsets(); // update resolved offsets
+}
stat_t mach_plan_line(float target[], switch_id_t sw) {
// Compute target position
float target[AXES];
- mach_calc_target(target, values, flags);
+ mach_calc_target(target, values, flags, mach_in_absolute_mode());
// test soft limits
stat_t status = mach_test_soft_limits(target);
switch_id_t sw = SW_PROBE;
for (int axis = 0; axis < AXES; axis++)
- if (flags[axis]) {
+ if (flags[axis] && isfinite(target[axis])) {
// Convert to incremental move
if (mach.gm.distance_mode == ABSOLUTE_MODE)
target[axis] += mach.position[axis];
bool mach_get_absolute_mode();
path_mode_t mach_get_path_mode();
bool mach_is_exact_stop();
+bool mach_in_absolute_mode();
distance_mode_t mach_get_distance_mode();
distance_mode_t mach_get_arc_distance_mode();
void mach_set_position_from_runtime();
// Critical helpers
-void mach_calc_target(float target[], const float values[], const bool flags[]);
+void mach_calc_target(float target[], const float values[], const bool flags[],
+ bool absolute);
stat_t mach_test_soft_limits(float target[]);
// machining functions defined by NIST [organized by NIST Gcode doc]
void mach_set_home(float origin[], bool flags[]);
void mach_clear_home(bool flags[]);
-stat_t mach_zero_all();
-stat_t mach_zero_axis(unsigned axis);
-
void mach_set_origin_offsets(float offset[], bool flags[]);
void mach_reset_origin_offsets();
void mach_suspend_origin_offsets();
// Set model target
const float *position = mach_get_position();
- mach_calc_target(arc.target, values, values_f);
+ mach_calc_target(arc.target, values, values_f, mach_in_absolute_mode());
// in radius mode it's an error for start == end
if (radius_f && fp_EQ(position[AXIS_X], arc.target[AXIS_X]) &&
void mp_buffer_validate(const mp_buffer_t *bp) {
ASSERT(bp);
+ if (!(bp->flags & BUFFER_LINE)) return; // Only check line buffers
+
ASSERT(isfinite(bp->value));
ASSERT(isfinite(bp->target[0]) && isfinite(bp->target[1]) &&
BUFFER_RAPID = 1 << 5,
BUFFER_INVERSE_TIME = 1 << 6,
BUFFER_EXACT_STOP = 1 << 7,
+ BUFFER_LINE = 1 << 8,
} buffer_flags_t;
#include <stdlib.h>
+typedef struct {
+ float delta;
+ float t;
+ bool changed;
+
+ int sign;
+ float velocity;
+ float next;
+ float initial;
+ float target;
+} jog_axis_t;
+
+
typedef struct {
bool writing;
+ bool done;
+
float Vi;
float Vt;
- float velocity_delta[AXES];
- float velocity_t[AXES];
- int sign[AXES];
- float velocity[AXES];
- float next_velocity[AXES];
- float initial_velocity[AXES];
- float target_velocity[AXES];
+ jog_axis_t axes[AXES];
} jog_runtime_t;
static jog_runtime_t jr;
-static stat_t _exec_jog(mp_buffer_t *bf) {
- // Load next velocity
- bool changed = false;
- bool done = true;
- if (!jr.writing)
- for (int axis = 0; axis < AXES; axis++) {
- if (!axis_is_enabled(axis)) continue;
+static bool _next_axis_velocity(int axis) {
+ jog_axis_t *a = &jr.axes[axis];
- float Vn = jr.next_velocity[axis] * axis_get_velocity_max(axis);
- float Vi = jr.velocity[axis];
- float Vt = jr.target_velocity[axis];
+ float Vn = a->next * axis_get_velocity_max(axis);
+ float Vi = a->velocity;
+ float Vt = a->target;
- if (JOG_MIN_VELOCITY < fabs(Vn)) done = false;
+ if (JOG_MIN_VELOCITY < fabs(Vn)) jr.done = false;
- if (!fp_ZERO(Vi) && (Vn < 0) != (Vi < 0))
- Vn = 0; // Plan to zero on sign change
+ if (!fp_ZERO(Vi) && (Vn < 0) != (Vi < 0))
+ Vn = 0; // Plan to zero on sign change
- if (fabs(Vn) < JOG_MIN_VELOCITY) Vn = 0;
+ if (fabs(Vn) < JOG_MIN_VELOCITY) Vn = 0;
- if (Vt != Vn) {
- jr.target_velocity[axis] = Vn;
- if (Vn) jr.sign[axis] = Vn < 0 ? -1 : 1;
- changed = true;
- }
- }
+ if (Vt == Vn) return false; // No change
- float velocity_sqr = 0;
+ a->target = Vn;
+ if (Vn) a->sign = Vn < 0 ? -1 : 1;
- // Compute per axis velocities
- for (int axis = 0; axis < AXES; axis++) {
- if (!axis_is_enabled(axis)) continue;
+ return true;
+}
- float V = fabs(jr.velocity[axis]);
- float Vt = fabs(jr.target_velocity[axis]);
- if (changed) {
- if (fp_EQ(V, Vt)) {
- V = Vt;
- jr.velocity_t[axis] = 1;
+static float _compute_axis_velocity(int axis) {
+ jog_axis_t *a = &jr.axes[axis];
- } else {
- // Compute axis max jerk
- float jerk = axis_get_jerk_max(axis) * JERK_MULTIPLIER;
+ float V = fabs(a->velocity);
+ float Vt = fabs(a->target);
- // Compute length to velocity given max jerk
- float length = mp_get_target_length(V, Vt, jerk * JOG_JERK_MULT);
+ if (JOG_MIN_VELOCITY < Vt) jr.done = false;
- // Compute move time
- float move_time = 2 * length / (V + Vt);
+ if (fp_EQ(V, Vt)) return Vt;
- if (move_time < SEGMENT_TIME) {
- V = Vt;
- jr.velocity_t[axis] = 1;
+ if (a->changed) {
+ // Compute axis max jerk
+ float jerk = axis_get_jerk_max(axis) * JERK_MULTIPLIER;
- } else {
- jr.initial_velocity[axis] = V;
- jr.velocity_t[axis] = jr.velocity_delta[axis] =
- SEGMENT_TIME / move_time;
- }
- }
- }
+ // Compute length to velocity given max jerk
+ float length = mp_get_target_length(V, Vt, jerk * JOG_JERK_MULT);
- if (jr.velocity_t[axis] < 1) {
- // Compute quintic Bezier curve
- V = velocity_curve(jr.initial_velocity[axis], Vt, jr.velocity_t[axis]);
- jr.velocity_t[axis] += jr.velocity_delta[axis];
+ // Compute move time
+ float move_time = 2 * length / (V + Vt);
- } else V = Vt;
+ if (move_time <= SEGMENT_TIME) return Vt;
- if (JOG_MIN_VELOCITY < V || JOG_MIN_VELOCITY < Vt) done = false;
+ a->initial = V;
+ a->t = a->delta = SEGMENT_TIME / move_time;
+ }
+ if (a->t <= 0) return V;
+ if (1 <= a->t) return Vt;
+
+ // Compute quintic Bezier curve
+ V = velocity_curve(a->initial, Vt, a->t);
+ a->t += a->delta;
+
+ return V;
+}
+
+
+static stat_t _exec_jog(mp_buffer_t *bf) {
+ // Load next velocity
+ jr.done = true;
+
+ if (!jr.writing)
+ for (int axis = 0; axis < AXES; axis++) {
+ if (!axis_is_enabled(axis)) continue;
+ jr.axes[axis].changed = _next_axis_velocity(axis);
+ }
+
+ float velocity_sqr = 0;
+
+ // Compute per axis velocities
+ for (int axis = 0; axis < AXES; axis++) {
+ if (!axis_is_enabled(axis)) continue;
+ float V = _compute_axis_velocity(axis);
velocity_sqr += square(V);
- jr.velocity[axis] = V * jr.sign[axis];
+ jr.axes[axis].velocity = V * jr.axes[axis].sign;
+ if (JOG_MIN_VELOCITY < V) jr.done = false;
}
// Check if we are done
- if (done) {
+ if (jr.done) {
// Update machine position
mach_set_position_from_runtime();
mp_set_cycle(CYCLE_MACHINING); // Default cycle
float target[AXES];
for (int axis = 0; axis < AXES; axis++)
target[axis] = mp_runtime_get_axis_position(axis) +
- jr.velocity[axis] * SEGMENT_TIME;
+ jr.axes[axis].velocity * SEGMENT_TIME;
// Set velocity and target
mp_runtime_set_velocity(sqrt(velocity_sqr));
jr.writing = true;
for (int axis = 0; axis < AXES; axis++)
- jr.next_velocity[axis] = velocity[axis];
+ jr.axes[axis].next = velocity[axis];
jr.writing = false;
if (mp_get_cycle() != CYCLE_JOGGING) {
#include "buffer.h"
#include <stdio.h>
+#include <float.h>
/* Sonny's algorithm - simple
for (int axis = 0; axis < AXES; axis++)
costheta -= a_unit[axis] * b_unit[axis];
- if (costheta < -0.99) return 10000000; // straight line cases
+ if (costheta < -0.99) return FLT_MAX; // straight line cases
if (0.99 < costheta) return 0; // reversal cases
// Fuse the junction deviations into a vector sum
bf->cruise_vmax = bf->length / move_time; // target velocity requested
- float junction_velocity =
- _get_junction_vmax(mp_buffer_prev(bf)->unit, bf->unit);
+ float junction_velocity = FLT_MAX;
+
+ mp_buffer_t *prev = mp_buffer_prev(bf);
+ while (prev->state != BUFFER_OFF)
+ if (prev->flags & BUFFER_LINE) {
+ _get_junction_vmax(prev->unit, bf->unit);
+ break;
+
+ } else prev = mp_buffer_prev(prev);
bf->entry_vmax = min(bf->cruise_vmax, junction_velocity);
bf->delta_vmax = mp_get_target_velocity(0, bf->length, bf);
// Note, the following lines must remain in order.
bf->line = line; // Planner needs this when planning steps
+ flags |= BUFFER_LINE;
bf->flags = flags; // Move flags
bf->sw = sw; // Seek switche
mp_plan(bf); // Plan block list
mp_buffer_t *bp = mp_queue_get_tail();
bp->entry_vmax = bp->cruise_vmax = bp->exit_vmax = INFINITY;
- copy_vector(bp->unit, bp->prev->unit);
bp->flags |= BUFFER_REPLANNABLE;
mp_queue_push(cb, line);
#include "plan/state.h"
// Axis
-float get_position(int axis) {return mp_runtime_get_work_position(axis);}
+float get_axis_mach_coord(int axis) {return mp_runtime_get_axis_position(axis);}
-void set_position(int axis, float position) {
+void set_axis_mach_coord(int axis, float position) {
mach_set_axis_position(axis, position);
}
+float get_axis_work_coord(int axis) {return mp_runtime_get_work_position(axis);}
+
+
// GCode getters
int32_t get_line() {return mp_runtime_get_line();}
PGM_P get_unit() {return gs_get_units_pgmstr(mach_get_units());}
// Homing
VAR(homing_mode, ho, uint8_t, MOTORS, 1, 1, "Homing type")
VAR(homing_dir, hd, float, MOTORS, 0, 1, "Homing direction")
-VAR(home, h, float, MOTORS, 0, 1, "Home position")
+VAR(home, hp, float, MOTORS, 0, 1, "Home position")
+VAR(homed, h, bool, MOTORS, 0, 1, "True if axis is homed")
VAR(search_velocity,sv, float, MOTORS, 1, 1, "Homing search velocity")
VAR(latch_velocity, lv, float, MOTORS, 1, 1, "Homing latch velocity")
VAR(latch_backoff, lb, float, MOTORS, 1, 1, "Homing latch backoff")
VAR(zero_backoff, zb, float, MOTORS, 1, 1, "Homing zero backoff")
// Axis
-VAR(position, p, float, AXES, 1, 1, "Current axis position")
+VAR(axis_mach_coord, p, float, AXES, 1, 1, "Axis machine coordinate")
+VAR(axis_work_coord, w, float, AXES, 0, 1, "Axis work coordinate")
// Spindle
VAR(spindle_type, st, uint8_t, 0, 1, 1, "PWM=0 or HUANYANG=1")
{
"name": "bbctrl",
- "version": "0.1.11",
+ "version": "0.1.12",
"homepage": "https://github.com/buildbotics/rpi-firmware",
"license": "GPL 3+",
if $UPDATE_PY; then
rm -rf /usr/local/lib/python*/dist-packages/bbctrl-*
- ./setup.py install
+ ./setup.py install --force
service bbctrl start
fi
'bbctrl = bbctrl:run'
]
},
- scripts = ['scripts/update-bbctrl', 'scripts/upgrade-bbctrl'],
+ scripts = [
+ 'scripts/update-bbctrl',
+ 'scripts/upgrade-bbctrl',
+ 'scripts/sethostname',
+ ],
install_requires = 'tornado sockjs-tornado pyserial pyudev smbus2'.split(),
zip_safe = False,
)
script#admin-view-template(type="text/x-template")
#admin
+ h2 Hostname
+ .pure-form.pure-form-aligned
+ .pure-control-group
+ label(for="hostname") Hostname
+ input(name="hostname", v-model="hostname", @keyup.enter="set_hostname")
+ button.pure-button.pure-button-primary(@click="set_hostname") Set
+
+ h2 Remote SSH User
+ .pure-form.pure-form-aligned
+ .pure-control-group
+ label(for="username") Username
+ input(name="username", v-model="username", @keyup.enter="set_username")
+ button.pure-button.pure-button-primary(@click="set_username") Set
+
+ .pure-form.pure-form-aligned
+ .pure-control-group
+ label(for="current") Current Password
+ input(name="current", v-model="current", type="password")
+ .pure-control-group
+ label(for="pass1") New Password
+ input(name="pass1", v-model="password", type="password")
+ .pure-control-group
+ label(for="pass2") New Password
+ input(name="pass2", v-model="password2", type="password")
+ button.pure-button.pure-button-primary(@click="set_password") Set
+
h2 Configuration
button.pure-button.pure-button-primary(@click="backup") Backup
message(:show.sync="firmwareUpgrading")
h3(slot="header") Firmware upgrading
p(slot="body") Please wait. . .
+
+ message(:show.sync="hostnameSet")
+ h3(slot="header") Hostname Set
+ p(slot="body")
+ | Hostname was successfuly set to {{hostname}}.
+ | Poweroff and restart the controller for this change to take effect.
+
+ message(:show.sync="passwordSet")
+ h3(slot="header") Password Set
+ p(slot="body")
+
+ message(:show.sync="usernameSet")
+ h3(slot="header") Username Set
+ p(slot="body")
tr
th.name Axis
th.position Position
+ th.absolute Absolute
th.offset Offset
- th.errors Errors
th.actions Actions
each axis in 'xyzabc'
- tr.axis(class="axis-#{axis}", v-if="enabled('#{axis}')")
+ tr.axis(:class="{'homed': is_homed('#{axis}'), 'axis-#{axis}': true}",
+ v-if="enabled('#{axis}')")
th.name #{axis}
- td.position {{state.#{axis}p || 0 | fixed 3}}
- td.offset {{state.#{axis}o || 0 | fixed 3}}
- td.errors
- .fa.fa-hot(v-if="state.#{axis}t", title="Driver overtemp.")
- .fa.fa-ban(v-if="state.#{axis}s", title="Motor stalled.")
+ td.position {{state.#{axis}w || 0 | fixed 3}}
+ td.absolute {{state.#{axis}p || 0 | fixed 3}}
+ td.offset {{(state.#{axis}w - state.#{axis}p) || 0 | fixed 3}}
th.actions
- button.pure-button(title="Zero {{'#{axis}' | upper}} axis.",
- @click="zero('#{axis}')")
+ button.pure-button(
+ title="Set {{'#{axis}' | upper}} axis position.",
+ @click="show_set_position('#{axis}')")
+ .fa.fa-cog
+
+ button.pure-button(
+ title="Zero {{'#{axis}' | upper}} axis offset.",
+ @click="set_position('#{axis}', state['#{axis}p'])")
| ∅
+
button.pure-button(title="Home {{'#{axis}' | upper}} axis.",
@click="home('#{axis}')")
.fa.fa-home
- table.info
+ message(:show.sync="position_msg['#{axis}']")
+ h3(slot="header") Set {{'#{axis}' | upper}} axis position
+
+ div(slot="body")
+ .pure-form
+ .pure-control-group
+ label Position
+ input(v-model="axis_position",
+ @keyup.enter="set_position('#{axis}', axis_position)")
+ p
+
+ div(slot="footer")
+ button.pure-button(@click="position_msg['#{axis}'] = false")
+ | Cancel
+
+ button.pure-button.button-success(
+ @click="set_position('#{axis}', axis_position)") Set
+
+ message(:show.sync="manual_home['#{axis}']")
+ h3(slot="header") Manually home {{'#{axis}' | upper}} axis
+
+ div(slot="body")
+ p Set axis absolute position.
+
+ .pure-form
+ .pure-control-group
+ label Absolute
+ input(v-model="axis_position",
+ @keyup.enter="set_home('#{axis}', axis_position)")
+
+ p
+
+ div(slot="footer")
+ button.pure-button(@click="manual_home['#{axis}'] = false")
+ | Cancel
+
+ button.pure-button.button-success(
+ title="Home {{'#{axis}' | upper}} axis.",
+ @click="set_home('#{axis}', axis_position)") Set
+
+ table.info
tr
th State
td {{get_state()}}
.modal-container
.modal-header
slot(name="header") default header
+
.modal-body
slot(name="body") default body
+
.modal-footer
slot(name="footer")
button.pure-button.button-success(@click="show = false") OK
confirmReset: false,
configReset: false,
firmwareUpgrading: false,
+ hostnameSet: false,
+ usernameSet: false,
+ passwordSet: false,
latest: '',
+ hostname: '',
+ username: '',
+ current: '',
+ password: '',
+ password2: ''
}
},
},
- ready: function () {},
+ ready: function () {
+ api.get('hostname').done(function (hostname) {
+ this.hostname = hostname;
+ }.bind(this));
+ api.get('remote/username').done(function (username) {
+ this.username = username;
+ }.bind(this));
+ },
methods: {
+ set_hostname: function () {
+ api.put('hostname', {hostname: this.hostname}).done(function () {
+ this.hostnameSet = true;
+ }.bind(this)).fail(function (error) {
+ alert('Set hostname failed: ' + JSON.stringify(error));
+ })
+ },
+
+
+ set_username: function () {
+ api.put('remote/username', {username: this.username}).done(function () {
+ this.usernameSet = true;
+ }.bind(this)).fail(function (error) {
+ alert('Set username failed: ' + JSON.stringify(error));
+ })
+ },
+
+
+ set_password: function () {
+ if (this.password != this.password2) {
+ alert('Passwords to not match');
+ return;
+ }
+
+ if (this.password.length < 6) {
+ alert('Password too short');
+ return;
+ }
+
+ api.put('remote/password', {
+ current: this.current,
+ password: this.password
+ }).done(function () {
+ this.passwordSet = true;
+ }.bind(this)).fail(function (error) {
+ alert('Set password failed: ' + JSON.stringify(error));
+ })
+ },
+
+
backup: function () {
document.getElementById('download-target').src = '/api/config/download';
},
history: [],
console: [],
speed_override: 1,
- feed_override: 1
+ feed_override: 1,
+ manual_home: {x: false, y: false, z: false, a: false, b: false, c: false},
+ position_msg:
+ {x: false, y: false, z: false, a: false, b: false, c: false},
+ axis_position: 0
}
},
},
- enabled: function (axis) {
+ get_axis_motor_id: function (axis) {
var axis = axis.toLowerCase();
for (var i = 0; i < this.config.motors.length; i++) {
var motor = this.config.motors[i];
- if (motor.axis.toLowerCase() == axis &&
- (motor.enabled || typeof motor.enabled == 'undefined')) return true;
+ if (motor.axis.toLowerCase() == axis) return i;
}
- return false;
+ return -1;
+ },
+
+
+ get_axis_motor: function (axis) {
+ var motor = this.get_axis_motor_id(axis);
+ if (motor != -1) return this.config.motors[motor];
+ },
+
+
+ enabled: function (axis) {
+ var motor = this.get_axis_motor(axis);
+ return typeof motor != 'undefined' && motor['power-mode'] != 'disabled';
+ },
+
+
+ is_homed: function (axis) {
+ var motor = this.get_axis_motor_id(axis);
+ return motor != -1 && this.state[motor + 'h'];
},
},
- home: function () {api.put('home')},
+ home: function (axis) {
+ var motor = this.get_axis_motor(axis);
+ if (motor['homing-mode'] == 'manual') {
+ this.axis_position = this.state[axis + 'w'];
+ this.manual_home[axis] = true;
+ } else api.put('home' + (typeof axis == 'undefined' ? '' : ('/' + axis)));
+ },
- zero: function (axis) {
- api.put('zero' + (typeof axis == 'undefined' ? '' : '/' + axis));
+
+ set_home: function (axis, position) {
+ this.manual_home[axis] = false;
+ api.put('home/' + axis + '/set', {position: parseFloat(position)});
+ },
+
+
+ show_set_position: function (axis) {
+ this.axis_position = 0;
+ this.position_msg[axis] = true;
+ },
+
+
+ get_offset: function (axis) {
+ return this.state[axis + 'w'] - this.state[axis + 'p'];
+ },
+
+
+ set_position: function (axis, position) {
+ this.position_msg[axis] = false;
+ api.put('position/' + axis, {position: parseFloat(position)});
},
step: function () {api.put('step/' + this.file).done(this.update)},
- override_feed: function () {
- api.put('override/feed/' + this.feed_override)
- },
+ override_feed: function () {api.put('override/feed/' + this.feed_override)},
override_speed: function () {
if (!(this instanceof Sock)) return new Sock(url, retry);
if (typeof retry == 'undefined') retry = 2000;
- if (typeof timeout == 'undefined') timeout = 5000;
+ if (typeof timeout == 'undefined') timeout = 8000;
this.url = url;
this.retry = retry;
GATE_DDR = (1 << GATE1_PIN) | (1 << 2);
GATE_PORT = (1 << GATE1_PIN) | (1 << 2);
+ while (true) continue;
+
// Start ADC
adc_conversion();
def write_error(self, status_code, **kwargs):
e = {}
- e['message'] = str(kwargs['exc_info'][1])
+
+ if 'message' in kwargs: e['message'] = kwargs['message']
+ elif 'exc_info' in kwargs:
+ e['message'] = str(kwargs['exc_info'][1])
+ else: e['message'] = 'Unknown error'
+
e['code'] = status_code
self.write_json(e)
I2C_FLUSH = 7
I2C_REPORT = 8
I2C_REBOOT = 9
-I2C_ZERO = 10
machine_state_vars = '''
xp yp zp ap bp cp u s f t fm pa cs ao pc dm ad fo so mc fc
'''.split()
+# Axis homing procedure
+# - Set axis unhomed
+# - Find switch
+# - Backoff switch
+# - Find switch more accurately
+# - Backoff to machine zero
+# - Set axis home position
+axis_homing_procedure = '''
+ G28.2 %(axis)c0 F[#<%(axis)c.sv>]
+ G38.6 %(axis)c[#<%(axis)c.hd> * [#<%(axis)c.tm> - #<%(axis)c.tn>]]
+ G38.8 %(axis)c[#<%(axis)c.hd> * -#<%(axis)c.lb>] F[#<%(axis)c.lv>]
+ G38.6 %(axis)c[#<%(axis)c.hd> * #<%(axis)c.lb> * 1.5]
+ G0 %(axis)c[#<%(axis)c.hd> * -#<%(axis)c.zb> + #<%(axis)cp>]
+ G28.3 %(axis)c[#<%(axis)c.hp>]
+'''
class AVR():
def __init__(self, ctrl):
self.queue_command('${}{}={}'.format(index, code, value))
- def home(self): log.debug('NYI')
+ def home(self, axis, position = None):
+ if self.stream is not None: raise Exception('Busy, cannot home')
+
+ if position is not None:
+ self.queue_command('G28.3 %c%f' % (axis, position))
+
+ else:
+ gcode = axis_homing_procedure % {'axis': axis}
+ for line in gcode.splitlines():
+ self.queue_command(line.strip())
+
+
def estop(self): self._i2c_command(I2C_ESTOP)
def clear(self): self._i2c_command(I2C_CLEAR)
# Resume processing once current queue of GCode commands has flushed
self.queue_command('$resume')
+
def pause(self): self._i2c_command(I2C_PAUSE)
def unpause(self): self._i2c_command(I2C_RUN)
def optional_pause(self): self._i2c_command(I2C_OPTIONAL_PAUSE)
- def zero(self, axis = None): self._i2c_command(I2C_ZERO, byte = axis)
+
+
+ def set_position(self, axis, position):
+ if self.stream is not None: raise Exception('Busy, cannot set position')
+ self.queue_command('G92 %c%f' % (axis, position))
def encode_cmd(self, index, value, spec):
if not 'code' in spec: return
- if spec['type'] == 'enum': value = spec['values'].index(value)
+ if spec['type'] == 'enum':
+ if value in spec['values']:
+ value = spec['values'].index(value)
+ else: value = spec['default']
+
elif spec['type'] == 'bool': value = 1 if value else 0
elif spec['type'] == 'percent': value /= 100.0
import logging
+import subprocess
import bbctrl
log = logging.getLogger('Ctrl')
+class IPPage(bbctrl.LCDPage):
+ def update(self):
+ p = subprocess.Popen(['hostname', '-I'], stdout = subprocess.PIPE)
+ ips = p.communicate()[0].decode('utf-8').split()
+
+ p = subprocess.Popen(['hostname'], stdout = subprocess.PIPE)
+ hostname = p.communicate()[0].decode('utf-8').strip()
+
+ self.clear()
+
+ self.text('Host: %s' % hostname[0:14], 0, 0)
+
+ for i in range(min(3, len(ips))):
+ self.text('IP: %s' % ips[i], 0, i + 1)
+
+
+ def activate(self): self.update()
+
+
class Ctrl(object):
def __init__(self, args, ioloop):
self.args = args
self.pwr = bbctrl.Pwr(self)
self.avr.connect()
+
+ self.lcd.add_new_page(IPPage(self.lcd))
log = logging.getLogger('LCD')
-class Page:
+class LCDPage:
def __init__(self, lcd, text = None):
self.lcd = lcd
self.data = lcd.new_screen()
self.text(text, (lcd.width - len(text)) // 2, 1)
+ def activate(self): pass
+ def deactivate(self): pass
+
+
def put(self, c, x, y):
y += x // self.lcd.width
x %= self.lcd.width
self.put(c, x, y)
x += 1
+
+ def clear(self):
+ self.data = self.lcd.new_screen()
+ self.lcd.redraw = True
+
+
def shift_left(self): pass
def shift_right(self): pass
def shift_up(self): pass
def set_message(self, msg):
try:
- self.load_page(Page(self, msg))
+ self.load_page(LCDPage(self, msg))
self._update()
except IOError as e:
log.error('LCD communication failed: %s' % e)
return [[' ' for y in range(self.height)] for x in range(self.width)]
- def new_page(self): return Page(self)
+ def new_page(self): return LCDPage(self)
def add_page(self, page): self.pages.append(page)
- def add_new_page(self):
- page = self.new_page()
+ def add_new_page(self, page = None):
+ if page is None: page = self.new_page()
page.id = len(self.pages)
self.add_page(page)
return page
def load_page(self, page):
if self.page != page:
+ if self.page is not None: self.page.deactivate()
+ page.activate()
self.page = page
self.redraw = True
self.update()
log = logging.getLogger('PWR')
-# Must match regs in pwr firmare
+# Must match regs in pwr firmware
TEMP_REG = 0
VIN_REG = 1
VOUT_REG = 2
import datetime
import shutil
import tarfile
+import subprocess
import bbctrl
log = logging.getLogger('Web')
+def call_get_output(cmd):
+ p = subprocess.Popen(cmd, stdout = subprocess.PIPE)
+ s = p.communicate()[0].decode('utf-8')
+ if p.returncode: raise Exception('Command failed')
+ return s
+
+
+class HostnameHandler(bbctrl.APIHandler):
+ def get(self):
+ p = subprocess.Popen(['hostname'], stdout = subprocess.PIPE)
+ hostname = p.communicate()[0].decode('utf-8').strip()
+ self.write_json(hostname)
+
+
+ def put(self):
+ if 'hostname' in self.json:
+ if subprocess.call(['/usr/local/bin/sethostname',
+ self.json['hostname']]) == 0:
+ self.write_json('ok')
+ return
+
+ self.send_error(400, message = 'Failed to set hostname: %s' % self.json)
+
+
+def get_username():
+ return call_get_output(['getent', 'passwd', '1001']).split(':')[0]
+
+
+class UsernameHandler(bbctrl.APIHandler):
+ def get(self): self.write_json(get_username())
+
+
+ def put(self):
+ if 'username' in self.json:
+ username = get_username()
+
+ if subprocess.call(['usermod', '-l', self.json['username'],
+ username]) == 0:
+ self.write_json('ok')
+ return
+
+ self.send_error(400, message = 'Failed to set username: %s' % self.json)
+
+
+class PasswordHandler(bbctrl.APIHandler):
+ def put(self):
+ if 'current' in self.json and 'password' in self.json:
+ # Get current user name
+ username = get_username()
+
+ # Get current password
+ s = call_get_output(['getent', 'shadow', username])
+ password = s.split(':')[1].split('$')
+
+ # Check password type
+ if password[1] != '1':
+ self.send_error(400, message =
+ "Don't know how to update non-MD5 password")
+ return
+
+ # Check current password
+ cmd = ['openssl', 'passwd', '-salt', password[2], '-1',
+ self.json['current']]
+ s = call_get_output(cmd).strip()
+ if s.split('$') != password:
+ print('%s != %s' % (s.split('$'), password))
+ self.send_error(401, message = 'Wrong password')
+ return
+
+ # Set password
+ s = '%s:%s' % (username, self.json['password'])
+ s = s.encode('utf-8')
+
+ p = subprocess.Popen(['chpasswd', '-c', 'MD5'],
+ stdin = subprocess.PIPE)
+ p.communicate(input = s)
+
+ if p.returncode == 0:
+ self.write_json('ok')
+ return
+
+ self.send_error(400, message = 'Failed to set password')
+
class ConfigLoadHandler(bbctrl.APIHandler):
def get(self): self.write_json(self.ctrl.config.load())
with open('firmware/update.tar.bz2', 'wb') as f:
f.write(firmware['body'])
- import subprocess
- ret = subprocess.Popen(['update-bbctrl'])
+ subprocess.Popen(['/usr/local/bin/update-bbctrl'])
self.write_json('ok')
class UpgradeHandler(bbctrl.APIHandler):
- def put_ok(self):
- import subprocess
- ret = subprocess.Popen(['upgrade-bbctrl'])
+ def put_ok(self): subprocess.Popen(['/usr/local/bin/upgrade-bbctrl'])
class HomeHandler(bbctrl.APIHandler):
- def put_ok(self): self.ctrl.avr.home()
+ def put_ok(self, axis, set_home):
+ if axis is not None: axis = ord(axis[1:2].lower())
+
+ if set_home:
+ if not 'position' in self.json:
+ raise Exception('Missing "position"')
+
+ self.ctrl.avr.home(axis, self.json['position'])
+
+ else: self.ctrl.avr.home(axis)
class StartHandler(bbctrl.APIHandler):
def put_ok(self, path): self.ctrl.avr.step(path)
-class ZeroHandler(bbctrl.APIHandler):
+class PositionHandler(bbctrl.APIHandler):
def put_ok(self, axis):
- if axis is not None: axis = ord(axis[1:].lower())
- self.ctrl.avr.zero(axis)
+ self.ctrl.avr.set_position(ord(axis.lower()), self.json['position'])
class OverrideFeedHandler(bbctrl.APIHandler):
handlers = [
(r'/websocket', WSConnection),
+ (r'/api/hostname', HostnameHandler),
+ (r'/api/remote/username', UsernameHandler),
+ (r'/api/remote/password', PasswordHandler),
(r'/api/config/load', ConfigLoadHandler),
(r'/api/config/download', ConfigDownloadHandler),
(r'/api/config/save', ConfigSaveHandler),
(r'/api/firmware/update', FirmwareUpdateHandler),
(r'/api/upgrade', UpgradeHandler),
(r'/api/file(/.+)?', bbctrl.FileHandler),
- (r'/api/home', HomeHandler),
+ (r'/api/home(/[xyzabcXYZABC](/set)?)?', HomeHandler),
(r'/api/start(/.+)', StartHandler),
(r'/api/estop', EStopHandler),
(r'/api/clear', ClearHandler),
(r'/api/unpause', UnpauseHandler),
(r'/api/pause/optional', OptionalPauseHandler),
(r'/api/step(/.+)', StepHandler),
- (r'/api/zero(/[xyzabcXYZABC])?', ZeroHandler),
+ (r'/api/position/([xyzabcXYZABC])', PositionHandler),
(r'/api/override/feed/([\d.]+)', OverrideFeedHandler),
(r'/api/override/speed/([\d.]+)', OverrideSpeedHandler),
(r'/(.*)', StaticFileHandler,
from bbctrl.FileHandler import FileHandler
from bbctrl.GCodeStream import GCodeStream
from bbctrl.Config import Config
-from bbctrl.LCD import LCD
+from bbctrl.LCD import LCD, LCDPage
from bbctrl.AVR import AVR
from bbctrl.Web import Web
from bbctrl.Jog import Jog
"code": "pm"
},
"drive-current": {
- "type": "aloat",
+ "type": "float",
"min": 0,
"max": 8,
"unit": "amps",
"type": "float",
"min": 0,
"max": 8,
- "unit": "Amps",
+ "unit": "amps",
"default": 0,
"code": "ic"
}
"homing-mode": {
"type": "enum",
"values": [
- "disabled", "stall-min", "stall-max", "switch-min", "switch-max"
+ "manual", "stall-min", "stall-max", "switch-min", "switch-max"
],
- "default": "disabled",
+ "default": "manual",
"code": "ho"
},
"search-velocity": {
font-family Courier
.axis
+ &.homed
+ background-color #ccffcc
+ color #000
+
.name
text-transform capitalize
.position
width 99%
- .offset
- min-width 8em
-
- .errors
+ .absolute, .offset
min-width 6em
- white-space normal
.jog svg
text
font-family Helvetica, Arial, sans-serif
+.modal-header
+ text-decoration underline
+
+.modal-footer
+ text-align right
+
.modal-enter, .modal-leave
opacity 0
-.modal-enter .modal-container, .modal-leave .modal-container
+.modal-enter .modal-container
transform scale(1.1)
+.modal-leave .modal-container
+ transform scale(0.9)
label.file-upload
display inline-block
font-size 24pt
line-height 24pt
- .offset, .errors
+ .absolute, .offset
display none
> *:nth-of-type(n)