- Read a "wait" as an empty line in order to send keep alive temperature updates - Added option to add a checksum to all commands. Needed to add current line tracking for this, let's hope that we'll never get out of synch here...
279 lines
6.7 KiB
Python
279 lines
6.7 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 ConfigParser
|
|
import sys
|
|
import os
|
|
import yaml
|
|
import logging
|
|
|
|
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
|
|
},
|
|
"feature": {
|
|
"gCodeVisualizer": True,
|
|
"waitForStartOnConnect": False,
|
|
"alwaysSendChecksum": False
|
|
},
|
|
"folder": {
|
|
"uploads": None,
|
|
"timelapse": None,
|
|
"timelapse_tmp": None,
|
|
"logs": 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": []
|
|
}
|
|
}
|
|
|
|
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)
|
|
else:
|
|
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
|
|
|
|
#~~ 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()))
|