--- /dev/null
+Buildbotics CNC Controller Firmware Change Log
+==============================================
+
+## v0.3.4
+ - Added alternate units for motor parameters
+ - Automatic config file upgrading
+ - Fixed planner/jog sync
+ - Fixed planner limits config
+ - Accel units mm/min² -> m/min²
+ - Search and latch velocity mm/min -> m/min
+ - Fixed password update (broken in last version)
+ - Start Web server eariler in case of Python coding errors
$(GPLAN_MOD): $(GPLAN_IMG)
./scripts/gplan-init-build.sh
- git -C rpi-share/cbang pull
- git -C rpi-share/camotics pull
+ git -C rpi-share/cbang fetch
+ git -C rpi-share/cbang reset --hard FETCH_HEAD
+ git -C rpi-share/camotics fetch
+ git -C rpi-share/camotics reset --hard FETCH_HEAD
cp ./scripts/gplan-build.sh rpi-share/
sudo ./scripts/rpi-chroot.sh $(GPLAN_IMG) /mnt/host/gplan-build.sh
{
"name": "bbctrl",
- "version": "0.3.3",
+ "version": "0.3.4",
"homepage": "http://buildbotics.com/",
"repository": "https://github.com/buildbotics/bbctrl-firmware",
"license": "GPL-3.0+",
fi
# Get repos
+function fetch_local_repo() {
+ mkdir -p $1
+ git -C $1 init
+ git -C $1 fetch -t "$2" $3
+ git -C $1 reset --hard FETCH_HEAD
+}
+
mkdir -p rpi-share || true
if [ ! -e rpi-share/cbang ]; then
if [ "$CBANG_HOME" != "" ]; then
- git clone $CBANG_HOME rpi-share/cbang
+ fetch_local_repo rpi-share/cbang "$CBANG_HOME" master
else
git clone https://github.com/CauldronDevelopmentLLC/cbang \
rpi-share/cbang
if [ ! -e rpi-share/camotics ]; then
if [ "$CAMOTICS_HOME" != "" ]; then
- git clone $CAMOTICS_HOME rpi-share/camotics
+ fetch_local_repo rpi-share/camotics "$CAMOTICS_HOME" master
else
git clone https://github.com/CauldronDevelopmentLLC/camotics \
rpi-share/camotics
#define CURRENT_SENSE_REF 2.75 // volts
#define MAX_CURRENT 10 // amps
#define VELOCITY_MULTIPLIER 1000.0
-#define ACCEL_MULTIPLIER 1000.0
+#define ACCEL_MULTIPLIER 1000000.0
#define JERK_MULTIPLIER 1000000.0
#define SYNC_QUEUE_SIZE 4096
#define EXEC_FILL_TARGET 8
memset(&ex, 0, sizeof(ex));
ex.feed_override = 1;
ex.spindle_override = 1;
- // TODO implement pause
// TODO implement move stepping
// TODO implement overrides
// TODO implement optional pause
templated-input(v-for="templ in category", :name="$key",
:model.sync="motor[$key]", :template="templ")
+
+ label.extra(v-if="$key == 'max-velocity'", slot="extra",
+ title="Revolutions Per Minute")
+ | ({{1000 * motor[$key] / motor['travel-per-rev'] | fixed 0}} RPM)
+
+ label.extra(v-if="$key == 'max-accel'", slot="extra",
+ title="G-force")
+ | ({{motor[$key] * 0.0283254504 | fixed 3}} g)
+
+ label.extra(v-if="$key == 'max-jerk'", slot="extra",
+ title="G-force per minute")
+ | ({{motor[$key] * 0.0283254504 | fixed 2}} g/min)
+
+ label.extra(v-if="$key == 'step-angle'", slot="extra",
+ title="Steps per revolution")
+ | ({{360 / motor[$key] | fixed 0}} steps/rev)
+
+ label.extra(v-if="$key == 'travel-per-rev'", slot="extra",
+ title="Micrometers per step")
+ | ({{motor[$key] * motor['step-angle'] / 0.36 | fixed 1}}
+ | µm/step)
script#templated-input-template(type="text/x-template")
- .pure-control-group(:class="name")
+ .pure-control-group(class="tmpl-input-{{name}}",
+ title="Default {{template.default}} {{template.unit}}")
label(:for="name") {{name}}
select(v-if="template.type == 'enum' || template.values", v-model="model",
| {{model}}
label.units {{template.unit}}
+
+ slot(name="extra")
send: function (msg) {
if (this.status == 'connected') {
console.debug('>', msg);
- this.sock.send(msg)
+ this.sock.send(msg);
}
},
}.bind(this)).fail(function () {
alert('Invalid password');
- }.bind(this));
+ }.bind(this))
},
if (key == 'msg') this.$broadcast('message', msg.msg);
else Vue.set(this.state, key, msg[key]);
}
- }.bind(this);
+ }.bind(this)
this.sock.onopen = function (e) {
this.status = 'connected';
this.$emit(this.status);
this.$broadcast(this.status);
- }.bind(this);
+ }.bind(this)
this.sock.onclose = function (e) {
this.status = 'disconnected';
this.$broadcast(this.status);
- }.bind(this);
+ }.bind(this)
},
watch: {
- index: function() {this.update();}
+ index: function() {this.update()}
},
# Mark axis homed and set absolute position
axis_homing_procedure = '''
- G28.2 %(axis)s0 F[#<_%(axis)s_sv>]
+ G28.2 %(axis)s0 F[#<_%(axis)s_sv> * 1000]
G38.6 %(axis)s[#<_%(axis)s_hd> * [#<_%(axis)s_tm> - #<_%(axis)s_tn>] * 1.5]
- G38.8 %(axis)s[#<_%(axis)s_hd> * -#<_%(axis)s_lb>] F[#<_%(axis)s_lv>]
+ G38.8 %(axis)s[#<_%(axis)s_hd> * -#<_%(axis)s_lb>] F[#<_%(axis)s_lv> * 1000]
G38.6 %(axis)s[#<_%(axis)s_hd> * #<_%(axis)s_lb> * 1.5]
G91 G0 G53 %(axis)s[#<_%(axis)s_hd> * -#<_%(axis)s_zb>]
G90 G28.3 %(axis)s[#<_%(axis)s_hp>]
class Config(object):
def __init__(self, ctrl):
self.ctrl = ctrl
- self.version = pkg_resources.require('bbctrl')[0].version
- default_config['version'] = self.version
- # Load config template
- with open(bbctrl.get_resource('http/config-template.json'), 'r',
- encoding = 'utf-8') as f:
- self.template = json.load(f)
+ try:
+ self.version = pkg_resources.require('bbctrl')[0].version
+ default_config['version'] = self.version
+
+ # Load config template
+ with open(bbctrl.get_resource('http/config-template.json'), 'r',
+ encoding = 'utf-8') as f:
+ self.template = json.load(f)
+
+ except Exception as e: log.exception(e)
def load_path(self, path):
def load(self):
try:
config = self.load_path('config.json')
- config['version'] = self.version
+
+ try:
+ self.upgrade(config)
+ except Exception as e: log.exception(e)
# Add missing sections
for key, value in default_config.items():
return default_config
- def save(self, config):
- self.update(config)
+ def upgrade(self, config):
+ version = tuple(map(int, config['version'].split('.')))
+
+ if version < (0, 2, 4):
+ for motor in config['motors']:
+ for key in 'max-jerk max-velocity'.split():
+ if key in motor: motor[key] /= 1000
+
+ if version < (0, 3, 4):
+ for motor in config['motors']:
+ for key in 'max-accel latch-velocity search-velocity'.split():
+ if key in motor: motor[key] /= 1000
config['version'] = self.version
+
+ def save(self, config):
+ self.upgrade(config)
+ self.update(config)
+
with open('config.json', 'w') as f:
json.dump(config, f)
self.msgs = bbctrl.Messages(self)
self.state = bbctrl.State(self)
- self.planner = bbctrl.Planner(self)
- self.i2c = bbctrl.I2C(args.i2c_port)
self.config = bbctrl.Config(self)
- self.lcd = bbctrl.LCD(self)
self.web = bbctrl.Web(self)
- self.avr = bbctrl.AVR(self)
- self.jog = bbctrl.Jog(self)
- self.pwr = bbctrl.Pwr(self)
- self.avr.connect()
+ try:
+ self.planner = bbctrl.Planner(self)
+ self.i2c = bbctrl.I2C(args.i2c_port)
+ self.lcd = bbctrl.LCD(self)
+ self.avr = bbctrl.AVR(self)
+ self.jog = bbctrl.Jog(self)
+ self.pwr = bbctrl.Pwr(self)
- self.lcd.add_new_page(IPPage(self.lcd))
+ self.avr.connect()
+
+ self.lcd.add_new_page(IPPage(self.lcd))
+
+ except Exception as e: log.exception(e)
value = state.get(axis + 'p', None)
if value is not None: start[axis] = value
- return {
+ config = {
"start": start,
"max-vel": get_vector('vm', 1000),
- "max-accel": get_vector('am', 1000),
+ "max-accel": get_vector('am', 1000000),
"max-jerk": get_vector('jm', 1000000),
# TODO junction deviation & accel
}
+ log.info('Config:' + json.dumps(config))
+
+ return config
+
def update(self, update):
if 'id' in update: self.planner.set_active(update['id'])
raise Exception('Cannot issue MDI command while GCode running')
log.info('MDI:' + cmd)
- self.planner.load_string(cmd)
+ self.planner.load_string(cmd, self.get_config())
self.mode = 'mdi'
raise Exception('Busy, cannot start new GCode program')
log.info('GCode:' + path)
- self.planner.load('upload' + path)
+ self.planner.load('upload' + path, self.get_config())
def reset(self):
- self.planner = gplan.Planner(self.get_config())
+ self.planner = gplan.Planner()
self.planner.set_resolver(self.get_var)
self.planner.set_logger(self.log, 1, 'LinePlanner:3')
def next(self):
- if not self.is_running():
- config = self.get_config()
- log.info('Planner config:' + json.dumps(config))
- self.planner.set_config(config)
-
while self.planner.has_more():
cmd = self.planner.next()
self.lastID = cmd['id']
check_password(self.json['current'])
# Set password
- s = '%s:%s' % (username, self.json['password'])
+ s = '%s:%s' % (get_username(), self.json['password'])
s = s.encode('utf-8')
p = subprocess.Popen(['chpasswd', '-c', 'MD5'],
return self
+ def next(self): return self.__next__()
+
+
def __next__(self):
"""
Returns the next waiting event.
"max-accel": {
"type": "float",
"min": 0,
- "unit": "m/min²",
- "default": 1000,
+ "unit": "km/min²",
+ "default": 10,
"code": "am"
},
"max-jerk": {
"search-velocity": {
"type": "float",
"min": 0,
- "unit": "mm/min",
- "default": 500,
+ "unit": "m/min",
+ "default": 0.5,
"code": "sv"
},
"latch-velocity": {
"type": "float",
"min": 0,
- "unit": "mm/min",
- "default": 100,
+ "unit": "m/min",
+ "default": 0.1,
"code": "lv"
},
"latch-backoff": {
text-transform capitalize
.pure-control-group
+ label.units
+ width 6em
+
label.units
text-align left
width 24em
height 12em
- .switch
- h3, .pure-control-group
- display inline-block
-
@keyframes blink
50%
fill #ff9d00