253 lines
9.2 KiB
Python
253 lines
9.2 KiB
Python
################################################################################
|
|
# #
|
|
# This file is part of the Buildbotics firmware. #
|
|
# #
|
|
# Copyright (c) 2015 - 2018, Buildbotics LLC #
|
|
# All rights reserved. #
|
|
# #
|
|
# This file ("the software") is free software: you can redistribute it #
|
|
# and/or modify it under the terms of the GNU General Public License, #
|
|
# version 2 as published by the Free Software Foundation. You should #
|
|
# have received a copy of the GNU General Public License, version 2 #
|
|
# along with the software. If not, see <http://www.gnu.org/licenses/>. #
|
|
# #
|
|
# The software is distributed in the hope that it will be useful, but #
|
|
# WITHOUT ANY WARRANTY; without even the implied warranty of #
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU #
|
|
# Lesser General Public License for more details. #
|
|
# #
|
|
# You should have received a copy of the GNU Lesser General Public #
|
|
# License along with the software. If not, see #
|
|
# <http://www.gnu.org/licenses/>. #
|
|
# #
|
|
# For information regarding this software email: #
|
|
# "Joseph Coffland" <joseph@buildbotics.com> #
|
|
# #
|
|
################################################################################
|
|
|
|
import os
|
|
import json
|
|
import pkg_resources
|
|
from pkg_resources import Requirement, resource_filename
|
|
|
|
|
|
def get_resource(path):
|
|
return resource_filename(Requirement.parse('bbctrl'), 'bbctrl/' + path)
|
|
|
|
|
|
class Config(object):
|
|
def __init__(self, ctrl):
|
|
self.ctrl = ctrl
|
|
self.log = ctrl.log.get('Config')
|
|
|
|
self.values = {}
|
|
|
|
try:
|
|
self.version = "1.4.0"
|
|
|
|
# Load config template
|
|
with open(get_resource('http/config-template.json'), 'r',
|
|
encoding = 'utf-8') as f:
|
|
self.template = json.load(f)
|
|
|
|
except Exception: self.log.exception('Internal error: Failed to load config template')
|
|
|
|
def load(self):
|
|
path = self.ctrl.get_path('config.json')
|
|
|
|
try:
|
|
if os.path.exists(path):
|
|
with open(path, 'r') as f: config = json.load(f)
|
|
else: config = {'version': self.version}
|
|
|
|
try:
|
|
self._upgrade(config)
|
|
except Exception: self.log.exception('Internal error: Failed to upgrade config')
|
|
|
|
except Exception as e:
|
|
self.log.warning('%s', e)
|
|
config = {'version': self.version}
|
|
|
|
self._defaults(config)
|
|
return config
|
|
|
|
|
|
def reload(self):
|
|
self._update(self.load(), True)
|
|
|
|
|
|
def get(self, name, default = None):
|
|
return self.values.get(name, default)
|
|
|
|
|
|
def save(self, config):
|
|
self._upgrade(config)
|
|
self._update(config, False)
|
|
|
|
|
|
with open(self.ctrl.get_path('config.json'), 'w') as f:
|
|
json.dump(config, f, indent=2)
|
|
|
|
os.sync()
|
|
|
|
self.ctrl.preplanner.invalidate_all()
|
|
self.log.info('Saved')
|
|
|
|
|
|
def reset(self):
|
|
if os.path.exists('config.json'): os.unlink('config.json')
|
|
self.reload()
|
|
self.ctrl.preplanner.invalidate_all()
|
|
|
|
|
|
def _valid_value(self, template, value):
|
|
type = template['type']
|
|
|
|
try:
|
|
if type == 'int': value = int(value)
|
|
if type == 'float': value = float(value)
|
|
if type == 'text': value = str(value)
|
|
if type == 'bool': value = bool(value)
|
|
except:
|
|
return False
|
|
|
|
if 'values' in template and value not in template['values']:
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def __defaults(self, config, name, template):
|
|
if 'type' in template:
|
|
if (not name in config or
|
|
not self._valid_value(template, config[name])):
|
|
config[name] = template['default']
|
|
|
|
elif 'max' in template and template['max'] < config[name]:
|
|
config[name] = template['max']
|
|
|
|
elif 'min' in template and config[name] < template['min']:
|
|
config[name] = template['min']
|
|
|
|
if template['type'] == 'list':
|
|
if 'index' in template:
|
|
config = config[name]
|
|
|
|
for i in range(len(template['index'])):
|
|
if len(config) <= i: config.append({})
|
|
|
|
for name, tmpl in template['template'].items():
|
|
self.__defaults(config[i], name, tmpl)
|
|
|
|
else:
|
|
for name, tmpl in template.items():
|
|
self.__defaults(config, name, tmpl)
|
|
|
|
|
|
def _defaults(self, config):
|
|
for name, tmpl in self.template.items():
|
|
if not 'type' in tmpl:
|
|
if not name in config: config[name] = {}
|
|
conf = config[name]
|
|
else: conf = config
|
|
|
|
self.__defaults(conf, name, tmpl)
|
|
|
|
|
|
def _upgrade(self, config):
|
|
version = config['version']
|
|
version = version.split('b')[0] # Strip off any "beta" suffix
|
|
version = version.split('-')[0]
|
|
version = tuple(map(int, version.split('.'))) # Break it into a tuple of integers
|
|
|
|
if version < (1, 0, 7):
|
|
config['settings']['max-deviation'] = 0.001
|
|
config['settings']['junction-accel'] = 200000
|
|
config['settings']['probing-prompts'] = True
|
|
config['probe']['probe-fast-seek'] = 75
|
|
config['probe']['probe-slow-seek'] = 25
|
|
for motor in config['motors']:
|
|
motor['stall-microstep'] = 8
|
|
motor['stall-current'] = 1
|
|
motor['idle-current'] = 1
|
|
motor['max-accel'] = 750
|
|
motor['latch-backoff'] = 5
|
|
if motor['axis'] == 'X' or motor['axis'] == 'Y':
|
|
motor['search-velocity'] = 1.688
|
|
motor['max-velocity'] = 10
|
|
motor['max-jerk'] = 1000
|
|
motor['zero-backoff'] = 1.5
|
|
if motor['axis'] == 'Z':
|
|
motor['search-velocity'] = 0.675
|
|
motor['max-velocity'] = 3
|
|
motor['max-jerk'] = 1000
|
|
motor['zero-backoff'] = 1
|
|
|
|
if version < (1, 0, 8):
|
|
config['settings']['max-deviation'] = 0.05
|
|
config['settings']['junction-accel'] = 200000
|
|
|
|
if version < (1, 0, 9):
|
|
with open(get_resource('http/onefinity_defaults.json'), 'r', encoding = 'utf-8') as f:
|
|
defaults = json.load(f)
|
|
config['selected-tool-settings'] = defaults['selected-tool-settings'];
|
|
|
|
config['version'] = self.version.split('b')[0]
|
|
config['full_version'] = self.version
|
|
|
|
|
|
def _encode(self, name, index, config, tmpl, with_defaults):
|
|
# Handle category
|
|
if not 'type' in tmpl:
|
|
for name, entry in tmpl.items():
|
|
if 'type' in entry and config is not None:
|
|
conf = config.get(name, None)
|
|
else: conf = config
|
|
self._encode(name, index, conf, entry, with_defaults)
|
|
return
|
|
|
|
# Handle defaults
|
|
if config is not None: value = config
|
|
elif with_defaults: value = tmpl['default']
|
|
else: return
|
|
|
|
# Handle list
|
|
if tmpl['type'] == 'list':
|
|
if 'index' in tmpl:
|
|
for i in range(len(tmpl['index'])):
|
|
if config is not None and i < len(config): conf = config[i]
|
|
else: conf = None
|
|
self._encode(name, index + tmpl['index'][i], conf,
|
|
tmpl['template'], with_defaults)
|
|
else:
|
|
self.values[name]=value;
|
|
return
|
|
|
|
# Update config values
|
|
if index:
|
|
if not name in self.values: self.values[name] = {}
|
|
self.values[name][index] = value
|
|
|
|
else:
|
|
self.values[name] = value
|
|
if name == "macros":
|
|
self.ctrl.state.config(name,value)
|
|
|
|
# Update state variable
|
|
if not 'code' in tmpl: return
|
|
|
|
if tmpl['type'] == 'enum':
|
|
if value in tmpl['values']: value = tmpl['values'].index(value)
|
|
else: value = tmpl['default']
|
|
|
|
elif tmpl['type'] == 'bool': value = 1 if value else 0
|
|
elif tmpl['type'] == 'percent': value /= 100.0
|
|
|
|
self.ctrl.state.config(index + tmpl['code'], value)
|
|
|
|
|
|
def _update(self, config, with_defaults):
|
|
for name, tmpl in self.template.items():
|
|
conf = config.get(name, None)
|
|
self._encode(name, '', conf, tmpl, with_defaults)
|