MrDraw/octoprint/settings.py
Gina Häußge 09ae8fcdc2 Always send M110 with the new line number (default 0)
Previously it worked since the first command of every print was forced to be an M110 and the line number at the beginning of each print was always forced back to 0 as well. Now it just uses the actual line number (increased on each sent of a checksumed/numbered line) and resets that when an M110 is encountered. What was missing was forcing the line number of the actual M110 command to the desired line number as well. Should be "more correct" than before now, and work.
2013-06-25 20:05:31 +02:00

325 lines
7.9 KiB
Python

# coding=utf-8
__author__ = "Gina Häußge <osd@foosel.net>"
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
import sys
import os
import yaml
import logging
import re
import uuid
APPNAME="OctoPrint"
instance = None
def settings(init=False, configfile=None, basedir=None):
global instance
if instance is None:
if init:
instance = Settings(configfile, basedir)
else:
raise ValueError("Settings not initialized yet")
return instance
default_settings = {
"serial": {
"port": None,
"baudrate": None
},
"server": {
"host": "0.0.0.0",
"port": 5000
},
"webcam": {
"stream": None,
"snapshot": None,
"ffmpeg": None,
"bitrate": "5000k",
"watermark": True,
"flipH": False,
"flipV": False
},
"feature": {
"gCodeVisualizer": True,
"waitForStartOnConnect": False,
"alwaysSendChecksum": False,
"sdSupport": True
},
"folder": {
"uploads": None,
"timelapse": None,
"timelapse_tmp": None,
"logs": None,
"virtualSd": None
},
"temperature": {
"profiles":
[
{"name": "ABS", "extruder" : 210, "bed" : 100 },
{"name": "PLA", "extruder" : 180, "bed" : 60 }
]
},
"printerParameters": {
"movementSpeed": {
"x": 6000,
"y": 6000,
"z": 200,
"e": 300
}
},
"appearance": {
"name": "",
"color": "default"
},
"controls": [],
"system": {
"actions": []
},
"accessControl": {
"enabled": False,
"userManager": "octoprint.users.FilebasedUserManager",
"userfile": None
},
"events": {
"systemCommandTrigger": {
"enabled": False
},
"gcodeCommandTrigger": {
"enabled": False
}
},
"api": {
"enabled": False,
"key": ''.join('%02X' % ord(z) for z in uuid.uuid4().bytes)
}
}
valid_boolean_trues = ["true", "yes", "y", "1"]
class Settings(object):
def __init__(self, configfile=None, basedir=None):
self._logger = logging.getLogger(__name__)
self.settings_dir = None
self._config = None
self._dirty = False
self._init_settings_dir(basedir)
if configfile is not None:
self._configfile = configfile
else:
self._configfile = os.path.join(self.settings_dir, "config.yaml")
self.load()
def _init_settings_dir(self, basedir):
if basedir is not None:
self.settings_dir = basedir
else:
self.settings_dir = _resolveSettingsDir(APPNAME)
def _getDefaultFolder(self, type):
folder = default_settings["folder"][type]
if folder is None:
folder = os.path.join(self.settings_dir, type.replace("_", os.path.sep))
return folder
#~~ load and save
def load(self):
if os.path.exists(self._configfile) and os.path.isfile(self._configfile):
with open(self._configfile, "r") as f:
self._config = yaml.safe_load(f)
# chamged from else to handle cases where the file exists, but is empty / 0 bytes
if not self._config:
self._config = {}
def save(self, force=False):
if not self._dirty and not force:
return
with open(self._configfile, "wb") as configFile:
yaml.safe_dump(self._config, configFile, default_flow_style=False, indent=" ", allow_unicode=True)
self._dirty = False
self.load()
#~~ getter
def get(self, path):
if len(path) == 0:
return None
config = self._config
defaults = default_settings
while len(path) > 1:
key = path.pop(0)
if key in config.keys() and key in defaults.keys():
config = config[key]
defaults = defaults[key]
elif key in defaults.keys():
config = {}
defaults = defaults[key]
else:
return None
k = path.pop(0)
if not isinstance(k, (list, tuple)):
keys = [k]
else:
keys = k
results = []
for key in keys:
if key in config.keys():
results.append(config[key])
elif key in defaults:
results.append(defaults[key])
else:
results.append(None)
if not isinstance(k, (list, tuple)):
return results.pop()
else:
return results
def getInt(self, path):
value = self.get(path)
if value is None:
return None
try:
return int(value)
except ValueError:
self._logger.warn("Could not convert %r to a valid integer when getting option %r" % (value, path))
return None
def getBoolean(self, path):
value = self.get(path)
if value is None:
return None
if isinstance(value, bool):
return value
return value.lower() in valid_boolean_trues
def getBaseFolder(self, type):
if type not in default_settings["folder"].keys():
return None
folder = self.get(["folder", type])
if folder is None:
folder = self._getDefaultFolder(type)
if not os.path.isdir(folder):
os.makedirs(folder)
return folder
def getFeedbackControls(self):
feedbackControls = []
for control in self.get(["controls"]):
feedbackControls.extend(self._getFeedbackControls(control))
return feedbackControls
def _getFeedbackControls(self, control=None):
if control["type"] == "feedback_command":
pattern = control["regex"]
try:
matcher = re.compile(pattern)
return [(control["name"], matcher, control["template"])]
except:
# invalid regex or something like this, we'll just skip this entry
pass
elif control["type"] == "section":
result = []
for c in control["children"]:
result.extend(self._getFeedbackControls(c))
return result
else:
return []
#~~ setter
def set(self, path, value, force=False):
if len(path) == 0:
return
config = self._config
defaults = default_settings
while len(path) > 1:
key = path.pop(0)
if key in config.keys() and key in defaults.keys():
config = config[key]
defaults = defaults[key]
elif key in defaults.keys():
config[key] = {}
config = config[key]
defaults = defaults[key]
else:
return
key = path.pop(0)
if not force and key in defaults.keys() and key in config.keys() and defaults[key] == value:
del config[key]
self._dirty = True
elif force or (not key in config.keys() and defaults[key] != value) or (key in config.keys() and config[key] != value):
if value is None:
del config[key]
else:
config[key] = value
self._dirty = True
def setInt(self, path, value, force=False):
if value is None:
self.set(path, None, force)
try:
intValue = int(value)
except ValueError:
self._logger.warn("Could not convert %r to a valid integer when setting option %r" % (value, path))
return
self.set(path, intValue, force)
def setBoolean(self, path, value, force=False):
if value is None or isinstance(value, bool):
self.set(path, value, force)
elif value.lower() in valid_boolean_trues:
self.set(path, True, force)
else:
self.set(path, False, force)
def setBaseFolder(self, type, path, force=False):
if type not in default_settings["folder"].keys():
return None
currentPath = self.getBaseFolder(type)
defaultPath = self._getDefaultFolder(type)
if (path is None or path == defaultPath) and "folder" in self._config.keys() and type in self._config["folder"].keys():
del self._config["folder"][type]
if not self._config["folder"]:
del self._config["folder"]
self._dirty = True
elif (path != currentPath and path != defaultPath) or force:
if not "folder" in self._config.keys():
self._config["folder"] = {}
self._config["folder"][type] = path
self._dirty = True
def _resolveSettingsDir(applicationName):
# taken from http://stackoverflow.com/questions/1084697/how-do-i-store-desktop-application-data-in-a-cross-platform-way-for-python
if sys.platform == "darwin":
from AppKit import NSSearchPathForDirectoriesInDomains
# http://developer.apple.com/DOCUMENTATION/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Functions/Reference/reference.html#//apple_ref/c/func/NSSearchPathForDirectoriesInDomains
# NSApplicationSupportDirectory = 14
# NSUserDomainMask = 1
# True for expanding the tilde into a fully qualified path
return os.path.join(NSSearchPathForDirectoriesInDomains(14, 1, True)[0], applicationName)
elif sys.platform == "win32":
return os.path.join(os.environ["APPDATA"], applicationName)
else:
return os.path.expanduser(os.path.join("~", "." + applicationName.lower()))