{
"name": "bbctrl",
- "version": "0.1.22",
+ "version": "0.2.1",
"homepage": "https://github.com/buildbotics/rpi-firmware",
"license": "GPL 3+",
echo -e "\ndtoverlay=pi3-disable-bt" >> /boot/config.txt
# sudo systemctl disable hciuart
+# Install hawkeye
+dpkg -i hawkeye_0.5_armhf.deb
+echo 'ACTION=="add", KERNEL=="video0", RUN+="/usr/sbin/service hawkeye restart"' > /etc/udev/rules.d/50-hawkeye.rules
+
# TODO setup input and serial device permissions in udev & forward 80 -> 8080
reboot
int axis_get_motor(int axis) {return motor_map[axis];}
-void axis_set_motor(int axis, int motor) {motor_map[axis] = motor;}
+
+
+// Map axes to first matching motor
+void axis_map_motors() {
+ for (int axis = 0; axis < AXES; axis++)
+ for (int motor = 0; motor < MOTORS; motor++)
+ if (motor_get_axis(motor) == axis) {
+ motor_map[axis] = motor;
+ break;
+ }
+}
float axis_get_vector_length(const float a[], const float b[]) {
AXIS_VAR_SET(NAME, TYPE)
+AXIS_SET(homed, bool)
+
AXIS_GET(velocity_max, float, 0)
AXIS_GET(homed, bool, false)
-AXIS_SET(homed, bool)
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)
AXIS_GET(travel_max, float, 0)
AXIS_VAR_SET(radius, float)
AXIS_VAR_SET(travel_min, float)
AXIS_VAR_SET(travel_max, float)
+AXIS_VAR_SET(homing_mode, homing_mode_t)
AXIS_VAR_SET(search_velocity, float)
AXIS_VAR_SET(latch_velocity, float)
AXIS_VAR_SET(zero_backoff, float)
char axis_get_char(int axis);
int axis_get_id(char axis);
int axis_get_motor(int axis);
-void axis_set_motor(int axis, int motor);
+void axis_map_motors();
float axis_get_vector_length(const float a[], const float b[]);
float axis_get_velocity_max(int axis);
bool axis_get_homed(int axis);
void axis_set_homed(int axis, bool homed);
homing_mode_t axis_get_homing_mode(int axis);
-void axis_set_homing_mode(int axis, homing_mode_t mode);
float axis_get_radius(int axis);
float axis_get_travel_min(int axis);
float axis_get_travel_max(int axis);
typedef struct {
// Config
uint8_t axis; // map motor to axis
+ bool slave;
uint16_t microsteps; // microsteps per full step
bool reverse;
motor_power_mode_t power_mode;
motor_t *m = &motors[motor];
_update_config(motor);
- axis_set_motor(m->axis, motor);
// IO pins
DIRSET_PIN(m->step_pin); // Output
m->dma->REPCNT = 0;
m->dma->CTRLB = 0;
m->dma->CTRLA = DMA_CH_SINGLE_bm | DMA_CH_BURSTLEN_1BYTE_gc;
-
- drv8711_set_microsteps(motor, m->microsteps);
}
+
+ axis_map_motors();
}
int motor_get_axis(int motor) {return motors[motor].axis;}
-void motor_set_axis(int motor, uint8_t axis) {
- if (MOTORS <= motor || AXES <= axis || axis == motors[motor].axis) return;
- axis_set_motor(motors[motor].axis, -1);
- motors[motor].axis = axis;
- axis_set_motor(axis, motor);
-}
-
-
float motor_get_steps_per_unit(int motor) {return motors[motor].steps_per_unit;}
-uint16_t motor_get_microsteps(int motor) {return motors[motor].microsteps;}
-
-
-void motor_set_microsteps(int motor, uint16_t microsteps) {
- switch (microsteps) {
- case 1: case 2: case 4: case 8: case 16: case 32: case 64: case 128: case 256:
- break;
- default: return;
- }
-
- motors[motor].microsteps = microsteps;
- _update_config(motor);
- drv8711_set_microsteps(motor, microsteps);
-}
void motor_set_position(int motor, int32_t position) {
void set_step_angle(int motor, float value) {
- motors[motor].step_angle = value;
- _update_config(motor);
+ if (motors[motor].slave) return;
+
+ for (int m = motor; m < MOTORS; m++)
+ if (motors[m].axis == motors[motor].axis) {
+ motors[m].step_angle = value;
+ _update_config(m);
+ }
}
void set_travel(int motor, float value) {
- motors[motor].travel_rev = value;
- _update_config(motor);
+ if (motors[motor].slave) return;
+
+ for (int m = motor; m < MOTORS; m++)
+ if (motors[m].axis == motors[motor].axis) {
+ motors[m].travel_rev = value;
+ _update_config(m);
+ }
}
void set_microstep(int motor, uint16_t value) {
if (motor < 0 || MOTORS <= motor) return;
- motor_set_microsteps(motor, value);
+
+ switch (value) {
+ case 1: case 2: case 4: case 8: case 16: case 32: case 64: case 128: case 256:
+ break;
+ default: return;
+ }
+
+ if (motors[motor].slave) return;
+
+ for (int m = motor; m < MOTORS; m++)
+ if (motors[m].axis == motors[motor].axis) {
+ motors[m].microsteps = value;
+ _update_config(m);
+ drv8711_set_microsteps(m, value);
+ }
}
void set_reverse(int motor, bool value) {motors[motor].reverse = value;}
-char get_motor_axis(int motor) {return motors[motor].axis;}
-void set_motor_axis(int motor, uint8_t axis) {motor_set_axis(motor, axis);}
uint8_t get_power_mode(int motor) {return motors[motor].power_mode;}
void set_power_mode(int motor, uint8_t value) {
- if (value <= MOTOR_POWERED_ONLY_WHEN_MOVING)
- motors[motor].power_mode = value;
- else motors[motor].power_mode = MOTOR_DISABLED;
+ if (motors[motor].slave) return;
+
+ for (int m = motor; m < MOTORS; m++)
+ if (motors[m].axis == motors[motor].axis)
+ motors[m].power_mode =
+ value <= MOTOR_POWERED_ONLY_WHEN_MOVING ? value : MOTOR_DISABLED;
+}
+
+
+char get_motor_axis(int motor) {return motors[motor].axis;}
+
+void set_motor_axis(int motor, uint8_t axis) {
+ if (MOTORS <= motor || AXES <= axis || axis == motors[motor].axis) return;
+ motors[motor].axis = axis;
+ axis_map_motors();
+ mp_runtime_set_steps_from_position(); // Reset encoder counts
+
+ // Check if this is now a slave motor
+ motors[motor].slave = false;
+ for (int m = 0; m < motor; m++)
+ if (motors[m].axis == motors[motor].axis) {
+ // Sync with master
+ set_step_angle(motor, motors[m].step_angle);
+ set_travel(motor, motors[m].travel_rev);
+ set_microstep(motor, motors[m].microsteps);
+ set_power_mode(motor, motors[m].power_mode);
+ motors[motor].slave = true;
+ break;
+ }
}
bool motor_is_enabled(int motor);
int motor_get_axis(int motor);
float motor_get_steps_per_unit(int motor);
-uint16_t motor_get_microsteps(int motor);
-void motor_set_microsteps(int motor, uint16_t microsteps);
void motor_set_position(int motor, int32_t position);
int32_t motor_get_position(int motor);
VAR(max_sw_mode, xs, uint8_t, MOTORS, 1, 1, "Maximum switch mode")
VAR(estop_mode, et, uint8_t, 0, 1, 1, "Estop switch mode")
VAR(probe_mode, pt, uint8_t, 0, 1, 1, "Probe switch mode")
-VAR(min_switch, lw, uint8_t, MOTORS, 0, 1, "Minimum switch state")
-VAR(max_switch, xw, uint8_t, MOTORS, 0, 1, "Maximum switch state")
-VAR(estop_switch, ew, uint8_t, 0, 0, 1, "Estop switch state")
-VAR(probe_switch, pw, uint8_t, 0, 0, 1, "Probe switch state")
+VAR(min_switch, lw, uint8_t, MOTORS, 0, 1, "Minimum switch state")
+VAR(max_switch, xw, uint8_t, MOTORS, 0, 1, "Maximum switch state")
+VAR(estop_switch, ew, uint8_t, 0, 0, 1, "Estop switch state")
+VAR(probe_switch, pw, uint8_t, 0, 0, 1, "Probe switch state")
// Homing
VAR(homing_mode, ho, uint8_t, MOTORS, 1, 1, "Homing type")
int main(int argc, char *argv[]) {
mp_init(); // motion planning
machine_init(); // gcode machine
- for (int i = 0; i < 4; i++) axis_set_motor(i, i);
+ axis_map_motors();
stat_t status = STAT_OK;
a#menuLink.menu-link(href="#menu"): span
#menu
- button.save(:disabled="!modified", class="pure-button button-success"
+ button.save.pure-button.button-success(:disabled="!modified",
@click="save") Save
.pure-menu
a.pure-menu-link(:href="'#motor:' + $index") Motor {{$index}}
li.pure-menu-heading
- a.pure-menu-link(href="#spindle") Spindle
+ a.pure-menu-link(href="#tool") Tool
li.pure-menu-heading
a.pure-menu-link(href="#io") I/O
- li.pure-menu-heading
+ li.pure-menu-heading(style="display:none")
a.pure-menu-link(href="#gcode") Gcode
li.pure-menu-heading
h3(slot="header") Reset to default configuration?
p(slot="body") All configuration changes will be lost.
div(slot="footer")
- button.pure-button.button-error(@click="confirmReset = false") Cancel
+ button.pure-button(@click="confirmReset = false") Cancel
button.pure-button.button-success(@click="reset") OK
message(:show.sync="configReset")
button.pure-button(:disabled="state.x != 'READY'",
title="Zero {{'#{axis}' | upper}} axis offset.",
- @click="set_position('#{axis}', state['#{axis}p'])")
- | ∅
+ @click="zero_axis('#{axis}')") ∅
button.pure-button(:disabled="state.x != 'READY'",
title="Home {{'#{axis}' | upper}} axis.",
table.info
tr
th State
- td {{get_state()}}
+ td(:class="{attention: highlight_reason()}") {{get_state()}}
td
tr
th Reason
- td.reason {{get_reason()}}
+ td.reason(:class="{attention: highlight_reason()}") {{get_reason()}}
td
tr
th Feed
section#content1.tab-content
.toolbar
- button.pure-button(title="Upload a new program file.", @click="open",
+ button.pure-button(title="Upload a new GCode program.", @click="open",
:disabled="state.x == 'RUNNING' || state.x == 'STOPPING'")
.fa.fa-folder-open
input.gcode-file-input(type="file", @change="upload",
style="display:none", accept=".nc,.gcode,.gc,.ngc")
- button.pure-button(title="Delete current program file.",
- @click="delete", :disabled="!file")
+ button.pure-button(title="Delete current GCode program.",
+ @click="deleteGCode = true", :disabled="!file")
.fa.fa-trash
- select(title="Select previously uploaded program files.",
+ message(:show.sync="deleteGCode")
+ h3(slot="header") Delete GCode?
+ p(slot="body")
+ div(slot="footer")
+ button.pure-button(@click="deleteGCode = false") Cancel
+ button.pure-button.button-error(@click="deleteAll")
+ .fa.fa-trash
+ | all
+ button.pure-button.button-success(@click="deleteCurrent")
+ .fa.fa-trash
+ | selected
+
+ select(title="Select previously uploaded GCode programs.",
v-model="file", @change="load",
:disabled="state.x == 'RUNNING' || state.x == 'STOPPING'")
option(v-for="file in files", :value="file") {{file}}
+++ /dev/null
-script#spindle-view-template(type="text/x-template")
- #spindle
- h1 Spindle Configuration
-
- form.pure-form.pure-form-aligned
- fieldset
- templated-input(v-for="templ in template.spindle", :name="$key",
- :model.sync="spindle[$key]", :template="templ")
--- /dev/null
+script#tool-view-template(type="text/x-template")
+ #tool
+ h1 Spindle Configuration
+
+ form.pure-form.pure-form-aligned
+ fieldset
+ templated-input(v-for="templ in template.tool", :name="$key",
+ :model.sync="tool[$key]", :template="templ")
'loading-view': {template: '<h1>Loading...</h1>'},
'control-view': require('./control-view'),
'motor-view': require('./motor-view'),
- 'spindle-view': require('./spindle-view'),
+ 'tool-view': require('./tool-view'),
'io-view': require('./io-view'),
'gcode-view': require('./gcode-view'),
'admin-view': require('./admin-view')
module.exports = {
template: '#control-view-template',
- props: ['config', 'state'],
+ props: ['config', 'template', 'state'],
data: function () {
position_msg:
{x: false, y: false, z: false, a: false, b: false, c: false},
axis_position: 0,
- video_url: ''
+ video_url: '',
+ deleteGCode: false
}
},
},
- send: function (msg) {
- this.$dispatch('send', msg);
- },
+ highlight_reason: function () {return this.get_reason() != ''},
+ send: function (msg) {this.$dispatch('send', msg)},
get_axis_motor_id: function (axis) {
},
- enabled: function (axis) {
+ get_axis_motor_param: function (axis, name) {
var motor = this.get_axis_motor(axis);
- return typeof motor != 'undefined' && motor['power-mode'] != 'disabled';
+ if (typeof motor == 'undefined') return;
+ if (typeof motor[name] != 'undefined') return motor[name];
+
+ for (var section in this.template['motors']) {
+ var sec = this.template['motors'][section];
+ if (typeof sec[name] != 'undefined') return sec[name]['default'];
+ }
+ },
+
+
+ enabled: function (axis) {
+ var pm = this.get_axis_motor_param(axis, 'power-mode')
+ return typeof pm != 'undefined' && pm != 'disabled';
},
update: function () {
+ // Update file list
api.get('file')
.done(function (files) {
var index = files.indexOf(this.file);
},
- delete: function () {
- if (!this.file) return;
- api.delete('file/' + this.file).done(this.update);
+ deleteCurrent: function () {
+ if (this.file) api.delete('file/' + this.file).done(this.update);
+ this.deleteGCode = false;
+ },
+
+
+ deleteAll: function () {
+ api.delete('file').done(this.update);
+ this.deleteGCode = false;
},
if (typeof axis == 'undefined') api.put('home');
else {
- var motor = this.get_axis_motor(axis);
-
- if (motor['homing-mode'] == 'manual')
- this.manual_home[axis] = true;
- else api.put('home/' + axis);
+ if (this.get_axis_motor_param(axis, 'homing-mode') != 'manual')
+ api.put('home/' + axis);
+ else this.manual_home[axis] = true;
}
},
set_position: function (axis, position) {
this.position_msg[axis] = false;
- api.put('position/' + axis, {position: 0});
+ api.put('position/' + axis, {'position': parseFloat(position)});
},
+ zero_axis: function (axis) {this.set_position(axis, 0)},
+
+
start_pause: function () {
if (this.state.x == 'RUNNING') this.pause();
load_video: function () {
- this.video_url = '//' + document.location.hostname + ':8000/stream/0';
+ this.video_url = '//' + document.location.hostname + ':8000/stream/0?=' +
+ Math.random();
}
}
}
events: {
'input-changed': function() {
+ this.slave_update();
this.$dispatch('config-changed');
return false;
}
methods: {
+ slave_update: function () {
+ var slave = false;
+ for (var i = 0; i < this.index; i++)
+ if (this.motor['axis'] == this.config.motors[i]['axis'])
+ slave = true;
+
+ var el = $(this.$el);
+
+ if (slave) {
+ el.find('.axis .units').text('(slave motor)');
+ el.find('.limits, .homing, .motion').find('input, select')
+ .attr('disabled', 1);
+ el.find('.power-mode select').attr('disabled', 1);
+ el.find('.motion .reverse input').removeAttr('disabled');
+
+ } else {
+ el.find('.axis .units').text('');
+ el.find('input,select').removeAttr('disabled');
+ }
+ },
+
+
update: function () {
if (!this.active) return;
if (!this.motor.hasOwnProperty(key))
this.$set('motor["' + key + '"]',
template[category][key].default);
+
+ this.slave_update();
}.bind(this));
}
}
+++ /dev/null
-'use strict'
-
-
-module.exports = {
- template: '#spindle-view-template',
- props: ['config', 'template'],
-
-
- data: function () {
- return {
- spindle: {}
- }
- },
-
-
- events: {
- 'input-changed': function() {
- this.$dispatch('config-changed');
- return false;
- }
- },
-
-
- ready: function () {
- this.update();
- },
-
-
- methods: {
- update: function () {
- Vue.nextTick(function () {
- if (this.config.hasOwnProperty('spindle'))
- this.spindle = this.config.spindle;
-
- var template = this.template.spindle;
- for (var key in template)
- if (!this.spindle.hasOwnProperty(key))
- this.$set('spindle["' + key + '"]', template[key].default);
- }.bind(this));
- }
- }
-}
--- /dev/null
+'use strict'
+
+
+module.exports = {
+ template: '#tool-view-template',
+ props: ['config', 'template'],
+
+
+ data: function () {
+ return {
+ tool: {}
+ }
+ },
+
+
+ events: {
+ 'input-changed': function() {
+ this.$dispatch('config-changed');
+ return false;
+ }
+ },
+
+
+ ready: function () {
+ this.update();
+ },
+
+
+ methods: {
+ update: function () {
+ Vue.nextTick(function () {
+ if (this.config.hasOwnProperty('tool'))
+ this.tool = this.config.tool;
+
+ var template = this.template.tool;
+ for (var key in template)
+ if (!this.tool.hasOwnProperty(key))
+ this.$set('tool["' + key + '"]', template[key].default);
+ }.bind(this));
+ }
+ }
+}
inline static uint16_t convert_current(uint16_t sample) {
-#define CR1 1000
-#define CR2 137
-
- // TODO This isn't correct
- return sample * (VREF / 1024.0 * (CR1 + CR2) / CR2 * 100);
+ return sample * (VREF / 1024.0 * 1970);
}
OCR0A = 0; // 0% duty cycle
_delay_ms(3000);
}
+
+ if (10 < get_reg(MOTOR_REG)) {
+ OCR0A = 0; // 0% duty cycle
+ TCCR0A = 0;
+ GATE_DDR = 0;
+ _delay_ms(1000);
+ }
}
return 0;
self.stop();
self.ctrl.config.config_avr()
self._restore_machine_state()
- self.report()
except Exception as e:
log.warning('Connect failed: %s', e)
self.queue_command('${}={}'.format(var, value))
+ self.queue_command('$$') # Refresh all vars, must come after above
+
def report(self): self._i2c_command(I2C_REPORT)
return motor
+ def _is_axis_homed(self, axis):
+ motor = self._find_motor(axis)
+ if axis is None: return False
+ return self.vars['%dh' % motor]
+
+
def _update_lcd(self, msg):
if 'x' in msg or 'c' in msg:
v = self.vars
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))
+ if self._is_axis_homed('%c' % axis):
+ self.queue_command('G92 %c%f' % (axis, position))
+ else: self.queue_command('$%cp=%f' % (axis, position))
{"axis": "X"},
{"axis": "Y"},
{"axis": "Z"},
- {"axis": "A"},
+ {"axis": "A", "power-mode" : "disabled"},
],
"switches": {},
"outputs": {},
- "spindle": {},
+ "tool": {},
"gcode": {},
}
def delete_ok(self, path):
- path = 'upload' + path
- if os.path.exists(path): os.unlink(path)
+ if not path:
+ if os.path.exists('upload'):
+ for path in os.listdir('upload'):
+ if os.path.isfile('upload/' + path):
+ os.unlink('upload/' + path)
+ else:
+ path = 'upload' + path
+ if os.path.exists(path): os.unlink(path)
def put_ok(self, path):
self.ctrl = ctrl
config = {
- "deadband": 0.1,
- "axes": [ABS_X, ABS_Y, ABS_RZ, ABS_Z],
- "arrows": [ABS_HAT0X, ABS_HAT0Y],
- "speed": [0x120, 0x121, 0x122, 0x123],
- "activate": [0x124, 0x126, 0x125, 0x127],
+ "Logitech Logitech RumblePad 2 USB": {
+ "deadband": 0.1,
+ "axes": [ABS_X, ABS_Y, ABS_RZ, ABS_Z],
+ "dir": [1, -1, -1, 1],
+ "arrows": [ABS_HAT0X, ABS_HAT0Y],
+ "speed": [0x120, 0x121, 0x122, 0x123],
+ "lock": [0x124, 0x125],
+ },
+
+ "default": {
+ "deadband": 0.1,
+ "axes": [ABS_X, ABS_Y, ABS_RY, ABS_RX],
+ "dir": [1, -1, -1, 1],
+ "arrows": [ABS_HAT0X, ABS_HAT0Y],
+ "speed": [0x133, 0x130, 0x131, 0x134],
+ "lock": [0x136, 0x137],
+ }
}
super().__init__(config)
if self.speed == 4: scale = 1.0
self.v = [x * scale for x in self.axes]
- self.v[ABS_Y] = -self.v[ABS_Y]
- self.v[ABS_Z] = -self.v[ABS_Z]
"""
return (float(value) - float(self.minimum)) / \
float(self.maximum - self.minimum) * 2.0 - 1.0
-
self.buttons = dict()
- def event(self, event, handler):
+ def event(self, event, handler, name):
"""
Handles the given event.
elif event.type == EV_ABS:
state.abs[event.code] = event.stream.scale(event.code, event.value)
- if handler: handler(event, state)
+ if handler: handler.event(event, state, name)
def key_state(self, code):
ABS_DISTANCE, ABS_TILT_X, ABS_TILT_Y, ABS_TOOL_WIDTH]
- def __init__(self, devIndex, devType):
+ def __init__(self, devIndex, devType, devName):
"""
Opens the given /dev/input/event file and grabs it.
"""
self.devIndex = devIndex
self.devType = devType
+ self.devName = devName
self.filename = "/dev/input/event" + str(devIndex)
self.filehandle = os.open(self.filename, os.O_RDWR)
self.state = EventState()
def code_to_key(code): return CODE_KEY.get(code, '')
-def find_devices(types):
- """Finds the event indices of all devices of the specified types.
-
- A type is a string on the handlers line of /proc/bus/input/devices.
- Keyboards use "kbd", mice use "mouse" and joysticks (and gamepads) use "js".
-
- Returns a list of integer indexes N, where /dev/input/eventN is the event
- stream for each device.
-
- If butNot is given it holds a list of tuples which the returned values should
- not match.
-
- All devices of each type are returned; if you have two mice, they will both
- be used.
- """
- with open("/proc/bus/input/devices", "r") as filehandle:
- for line in filehandle:
- if line[0] == "H":
- for type in types:
- if type in line:
- match = re.search("event([0-9]+)", line)
- index = match and match.group(1)
- if index: yield int(index), type
- break
-
-
-
class InEvent(object):
"""Encapsulates the entire InEvent subsystem.
self.handler = EventHandler()
self.types = types
- devs = list(find_devices(types))
- for index, type in devs:
- self.add_stream(index, type)
-
self.udevCtx = pyudev.Context()
self.udevMon = pyudev.Monitor.from_netlink(self.udevCtx)
self.udevMon.filter_by(subsystem = 'input')
+
+ devs = list(self.find_devices(types))
+ for index, type, name in devs:
+ self.add_stream(index, type, name)
+
self.udevMon.start()
ioloop.add_handler(self.udevMon.fileno(), self.udev_handler, ioloop.READ)
+ def get_dev(self, index):
+ return pyudev.Device.from_name(self.udevCtx, 'input', 'event%s' % index)
+
+ def get_dev_name(self, index):
+ dev = self.get_dev(index)
+ for name, value in dev.parent.attributes.items():
+ if name == 'name': return value.decode('utf-8')
+
+
+ def find_devices(self, types):
+ """Finds the event indices of all devices of the specified types.
+
+ A type is a string on the handlers line of /proc/bus/input/devices.
+ Keyboards use "kbd", mice use "mouse" and joysticks (and gamepads) use "js".
+
+ Returns a list of integer indexes N, where /dev/input/eventN is the event
+ stream for each device.
+
+ If butNot is given it holds a list of tuples which the returned values
+ should not match.
+
+ All devices of each type are returned; if you have two mice, they will both
+ be used.
+ """
+ with open("/proc/bus/input/devices", "r") as filehandle:
+ for line in filehandle:
+ if line[0] == "H":
+ for type in types:
+ if type in line:
+ match = re.search("event([0-9]+)", line)
+ index = match and match.group(1)
+ if index:
+ yield int(index), type, self.get_dev_name(index)
+ break
+
+
def process_udev_event(self):
action, device = self.udevMon.receive_device()
if device is None: return
devIndex = int(devIndex)
if action == 'add':
- for index, devType in find_devices(self.types):
+ devName = None
+ for name, value in device.attributes.items():
+ if name == 'name': devName = value
+
+ for index, devType, devName in self.find_devices(self.types):
if index == devIndex:
- self.add_stream(devIndex, devType)
+ self.add_stream(devIndex, devType, devName)
break
if action == 'remove':
if stream.filehandle == fd:
while True:
event = stream.next()
- if event: self.handler.event(event, self.cb)
+ if event: self.handler.event(event, self.cb, stream.devName)
else: break
self.process_udev_event()
- def add_stream(self, devIndex, devType):
+ def add_stream(self, devIndex, devType, devName):
try:
- stream = EventStream(devIndex, devType)
+ stream = EventStream(devIndex, devType, devName)
self.streams.append(stream)
self.ioloop.add_handler(stream.filehandle, self.stream_handler,
self.ioloop.READ)
- log.info('Added %s[%d]', devType, devIndex)
+ log.info('Added %s[%d] %s', devType, devIndex, devName)
except OSError as e:
log.warning('Failed to add %s[%d]: %s', devType, devIndex, e)
self.streams.remove(stream)
self.ioloop.remove_handler(stream.filehandle)
stream.release()
+ self.cb.clear()
log.info('Removed %s[%d]', stream.devType, devIndex)
class JogHandler:
def __init__(self, config):
self.config = config
- self.axes = [0.0, 0.0, 0.0, 0.0]
- self.speed = 3
- self.activate = 0
+ self.reset()
def changed(self):
def right(self): log.debug('right')
- def __call__(self, event, state):
+ def reset(self):
+ self.axes = [0.0, 0.0, 0.0, 0.0]
+ self.speed = 3
+ self.vertical_lock = 0
+ self.horizontal_lock = 0
+
+
+ def clear(self):
+ self.reset()
+ self.changed()
+
+
+ def get_config(self, name):
+ if name in self.config: return self.config[name]
+ return self.config['default']
+
+
+ def event(self, event, state, dev_name):
if event.type not in [EV_ABS, EV_REL, EV_KEY]: return
+ config = self.get_config(dev_name)
changed = False
# Process event
- if event.type == EV_ABS and event.code in self.config['axes']:
+ if event.type == EV_ABS and event.code in config['axes']:
pass
- elif event.type == EV_ABS and event.code in self.config['arrows']:
- axis = self.config['arrows'].index(event.code)
+ elif event.type == EV_ABS and event.code in config['arrows']:
+ axis = config['arrows'].index(event.code)
if event.value < 0:
if axis == 1: self.up()
if axis == 1: self.down()
else: self.right()
- elif event.type == EV_KEY and event.code in self.config['speed']:
+ elif event.type == EV_KEY and event.code in config['speed']:
old_speed = self.speed
- self.speed = self.config['speed'].index(event.code) + 1
+ self.speed = config['speed'].index(event.code) + 1
if self.speed != old_speed: changed = True
- elif event.type == EV_KEY and event.code in self.config['activate']:
- index = self.config['activate'].index(event.code)
+ elif event.type == EV_KEY and event.code in config['lock']:
+ index = config['lock'].index(event.code)
- if event.value: self.activate |= 1 << index
- else: self.activate &= ~(1 << index)
+ self.horizontal_lock, self.vertical_lock = False, False
+
+ if event.value:
+ if index == 0: self.horizontal_lock = True
+ if index == 1: self.vertical_lock = True
log.debug(event_to_string(event, state))
old_axes = list(self.axes)
for axis in range(4):
- self.axes[axis] = event.stream.state.abs[self.config['axes'][axis]]
- if abs(self.axes[axis]) < self.config['deadband']:
+ self.axes[axis] = event.stream.state.abs[config['axes'][axis]]
+ self.axes[axis] *= config['dir'][axis]
+
+ if abs(self.axes[axis]) < config['deadband']:
self.axes[axis] = 0
- if not (1 << axis) & self.activate and self.activate:
+
+ if self.horizontal_lock and axis not in [0, 3]:
+ self.axes[axis] = 0
+
+ if self.vertical_lock and axis not in [1, 2]:
self.axes[axis] = 0
if old_axes != self.axes: changed = True
+++ /dev/null
-{
- "motors": [
- {
- "axis": "X"
- },
- {
- "axis": "Y"
- },
- {
- "axis": "Z"
- },
- {
- "axis": "A"
- },
- ],
-
- "spindle": {},
-}
"min": 0,
"max": 8,
"unit": "amps",
- "default": 2,
+ "default": 1.5,
"code": "dc"
},
"idle-current": {
},
"motion": {
- "step-angle": {
- "type": "float",
- "min": 0,
- "max": 360,
- "step": 0.1,
- "unit": "degrees",
- "default": 1.8,
- "code": "sa"
- },
- "travel-per-rev": {
- "type": "float",
- "unit": "mm",
- "default": 5,
- "code": "tr"
+ "reverse": {
+ "type": "bool",
+ "default": false,
+ "code": "rv"
},
"microsteps": {
"type": "int",
"default": 32,
"code": "mi"
},
- "reverse": {
- "type": "bool",
- "default": false,
- "code": "rv"
- },
"max-velocity": {
"type": "float",
"min": 0,
"unit": "mm/min",
- "default": 5000,
+ "default": 6000,
"code": "vm"
},
"max-jerk": {
"type": "float",
"min": 0,
"unit": "mm/min³",
- "default": 10,
+ "default": 50,
"code": "jm"
+ },
+ "step-angle": {
+ "type": "float",
+ "min": 0,
+ "max": 360,
+ "step": 0.1,
+ "unit": "degrees",
+ "default": 1.8,
+ "code": "sa"
+ },
+ "travel-per-rev": {
+ "type": "float",
+ "unit": "mm",
+ "default": 5,
+ "code": "tr"
}
},
}
},
- "spindle": {
+ "tool": {
"spindle-type": {
"type": "enum",
"values": ["HUANYANG", "PWM"],
display none
.button-success:not([disabled])
- background rgb(28, 184, 65)
+ background-color #1cb841
+
+.button-error:not([disabled])
+ background-color #ca3c3c
+
+.button-warning:not([disabled])
+ background-color #df7514
+
+.button-secondary:not([disabled])
+ background-color #42b8dd
.header, .content
padding 0
.success
background green
+@keyframes attention
+ 50%
+ opacity 0.5
+
+.attention
+ background-color #f5e138
+ color #000
+ animation attention 2s step-start 0s infinite
+
.status
color #eee
text-align center
clear right
.override
+ display none /* Hidden for now */
margin 0.5em 0
white-space nowrap