Skip to content

Commit

Permalink
New config options for daemonization, configfile location and basedir…
Browse files Browse the repository at this point in the history
… location

Using --daemon {start|stop|restart} OctoPrint can now be daemonized/controlled in daemon mode. Via --pidfile it's possible to set the pidfile to use, --configfile allows specification of the config.yaml to use, --basedir specifies the location of the basedir from which to base off the upload, timelapse and log folders. I also updated the README to include some config file settings which were previously undocumented.
  • Loading branch information
foosel committed Mar 11, 2013
1 parent 8570f73 commit 363f007
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 136 deletions.
69 changes: 52 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,39 +39,39 @@ Usage

Just start the server via

python -m octoprint.server

or alternatively

./run

By default it binds to all interfaces on port 5000 (so pointing your browser to `http://127.0.0.1:5000`
will do the trick). If you want to change that, use the additional command line parameters `host` and `port`,
which accept the host ip to bind to and the numeric port number respectively. If for example you want to the server
which accept the host ip to bind to and the numeric port number respectively. If for example you want the server
to only listen on the local interface on port 8080, the command line would be

python -m octoprint.server --host=127.0.0.1 --port=8080

or

./run --host=127.0.0.1 --port=8080

Alternatively, the host and port on which to bind can be defined via the configuration.

If you want to run OctoPrint as a daemon, there's another script for that:
If you want to run OctoPrint as a daemon (only supported on Linux), use

./run-as-daemon [start|stop|restart]
./run --daemon {start|stop|restart} [--pidfile PIDFILE]

It will create a pid file at `/tmp/octoprint.pid` for now. Further commandline arguments will not be evaluated,
so you'll need to define host and port in the configuration file if you want something different there than the default.
If you do not supply a custom pidfile location via `--pidfile PIDFILE`, it will be created at `/tmp/octoprint.pid`.

You can also specify the configfile or the base directory (for basing off the `uploads`, `timelapse` and `logs` folders),
e.g.:

./run --config /path/to/another/config.yaml --basedir /path/to/my/basedir

See `run --help` for further information.

Configuration
-------------

The config-file `config.yaml` for OctoPrint is expected in its settings folder, which is located at `~/.octoprint`
on Linux, at `%APPDATA%/OctoPrint` on Windows and at `~/Library/Application Support/OctoPrint` on MacOS.
If not specified via the commandline, the configfile `config.yaml` for OctoPrint is expected in its settings folder,
which is located at `~/.octoprint` on Linux, at `%APPDATA%/OctoPrint` on Windows and at
`~/Library/Application Support/OctoPrint` on MacOS.

The following example config should explain the available options:
The following example config should explain the available options, most of which can also be configured via the
settings dialog within OctoPrint:

# Use the following settings to configure the serial connection to the printer
serial:
Expand Down Expand Up @@ -111,6 +111,10 @@ The following example config should explain the available options:
# Whether to enable the gcode viewer in the UI or not
gCodeVisualizer: true

# Specified whether OctoPrint should wait for the start response from the printer before trying to send commands
# during connect
waitForStartOnConnect: false

# Use the following settings to set custom paths for folders used by OctoPrint
folder:
# Absolute path where to store gcode uploads. Defaults to the uploads folder in the OctoPrint settings folder
Expand All @@ -122,7 +126,38 @@ The following example config should explain the available options:

# Absolute path where to store temporary timelapse files. Defaults to the timelapse/tmp folder in the OctoPrint
# settings dir
timelapse_tmp: /path/timelapse/tmp/folder
timelapse_tmp: /path/to/timelapse/tmp/folder

# Absolute path where to store log files. Defaults to the logs folder in the OctoPrint settings dir
logs: /path/to/logs/folder

# Use the following settings to configure temperature profiles which will be displayed in the temperature tab.
temperature:
profiles:
- name: ABS
extruder: 210
bed: 100
- name: PLA
extruder: 180
bed: 60

# Use the following settings to configure printer parameters
printerParameters:
# Use this to define the movement speed on X, Y, Z and E to use for the controls on the controls tab
movementSpeed:
x: 6000
y: 6000
z: 200
e: 300

# Use the following settings to tweak OctoPrint's appearance a bit to better distinguish multiple instances/printers
appearance:
# Use this to give your printer a name. It will be displayed in the title bar (as "<Name> [OctoPrint]") and in the
# navigation bar (as "OctoPrint: <Name>")
name: My Printer Model

# Use this to color the navigation bar. Supported colors are red, orange, yellow, green, blue, violet and default.
color: blue

# Use the following settings to add custom controls to the "Controls" tab within OctoPrint
#
Expand Down
54 changes: 28 additions & 26 deletions octoprint/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import octoprint.util as util

SUCCESS = {}
UPLOAD_FOLDER = settings().getBaseFolder("uploads")
BASEURL = "/ajax/"
app = Flask("octoprint")
# Only instantiated by the Server().run() method
Expand Down Expand Up @@ -249,7 +248,7 @@ def readGcodeFiles():

@app.route(BASEURL + "gcodefiles/<path:filename>", methods=["GET"])
def readGcodeFile(filename):
return send_from_directory(UPLOAD_FOLDER, filename, as_attachment=True)
return send_from_directory(settings().getBaseFolder("uploads"), filename, as_attachment=True)

@app.route(BASEURL + "gcodefiles/upload", methods=["POST"])
def uploadGcodeFile():
Expand Down Expand Up @@ -442,7 +441,14 @@ def performSystemAction():

#~~ startup code
class Server():
def run(self, host = "0.0.0.0", port = 5000, debug = False):
def __init__(self, configfile=None, basedir=None, host="0.0.0.0", port=5000, debug=False):
self._configfile = configfile
self._basedir = basedir
self._host = host
self._port = port
self._debug = debug

def run(self):
# Global as I can't work out a way to get it into PrinterStateConnection
global printer
global gcodeManager
Expand All @@ -452,22 +458,36 @@ def run(self, host = "0.0.0.0", port = 5000, debug = False):
from tornado.ioloop import IOLoop
from tornado.web import Application, FallbackHandler

# first initialize the settings singleton and make sure it uses given configfile and basedir if available
self._initSettings(self._configfile, self._basedir)

# then initialize logging
self._initLogging(self._debug)

gcodeManager = gcodefiles.GcodeManager()
printer = Printer(gcodeManager)

logging.getLogger(__name__).info("Listening on http://%s:%d" % (host, port))
app.debug = debug
if self._host is None:
self._host = settings().get(["server", "host"])
if self._port is None:
self._port = settings().getInt(["server", "port"])

logging.getLogger(__name__).info("Listening on http://%s:%d" % (self._host, self._port))
app.debug = self._debug

self._router = tornadio2.TornadioRouter(PrinterStateConnection)

self._tornado_app = Application(self._router.urls + [
(".*", FallbackHandler, {"fallback": WSGIContainer(app)})
])
self._server = HTTPServer(self._tornado_app)
self._server.listen(port, address=host)
self._server.listen(self._port, address=self._host)
IOLoop.instance().start()

def initLogging(self):
def _initSettings(self, configfile, basedir):
s = settings(init=True, basedir=basedir, configfile=configfile)

def _initLogging(self, debug):
self._config = {
"version": 1,
"formatters": {
Expand Down Expand Up @@ -503,24 +523,6 @@ def initLogging(self):
}
logging.config.dictConfig(self._config)

def start(self):
from optparse import OptionParser

self._defaultHost = settings().get(["server", "host"])
self._defaultPort = settings().get(["server", "port"])

self._parser = OptionParser(usage="usage: %prog [options]")
self._parser.add_option("-d", "--debug", action="store_true", dest="debug",
help="Enable debug mode")
self._parser.add_option("--host", action="store", type="string", default=self._defaultHost, dest="host",
help="Specify the host on which to bind the server, defaults to %s if not set" % (self._defaultHost))
self._parser.add_option("--port", action="store", type="int", default=self._defaultPort, dest="port",
help="Specify the port on which to bind the server, defaults to %s if not set" % (self._defaultPort))
(options, args) = self._parser.parse_args()

self.initLogging()
self.run(host=options.host, port=options.port, debug=options.debug)

if __name__ == "__main__":
octoprint = Server()
octoprint.start()
octoprint.run()
88 changes: 35 additions & 53 deletions octoprint/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,19 @@
import logging

APPNAME="OctoPrint"
OLD_APPNAME="PrinterWebUI"

instance = None

def settings():
def settings(init=False, configfile=None, basedir=None):
global instance
if instance is None:
instance = Settings()
if init:
instance = Settings(configfile, basedir)
else:
raise ValueError("Settings not initialized yet")
return instance

old_default_settings = {
default_settings = {
"serial": {
"port": None,
"baudrate": None
Expand All @@ -35,25 +37,23 @@ def settings():
"bitrate": "5000k",
"watermark": True
},
"feature": {
"gCodeVisualizer": True,
"waitForStartOnConnect": False
},
"folder": {
"uploads": None,
"timelapse": None,
"timelapse_tmp": None,
"logs": None
},
"feature": {
"gCodeVisualizer": True,
"waitForStartOnConnect": False
},
}

default_settings = old_default_settings.copy()
default_settings.update({
"appearance": {
"name": "",
"color": "default"
"temperature": {
"profiles":
[
{"name": "ABS", "extruder" : 210, "bed" : 100 },
{"name": "PLA", "extruder" : 180, "bed" : 60 }
]
},
"controls": [],
"printerParameters": {
"movementSpeed": {
"x": 6000,
Expand All @@ -62,40 +62,36 @@ def settings():
"e": 300
}
},
"temperature": {
"profiles":
[
{"name": "ABS", "extruder" : 210, "bed" : 100 },
{"name": "PLA", "extruder" : 180, "bed" : 60 }
]
"appearance": {
"name": "",
"color": "default"
},
"controls": [],
"system": {
"actions": []
}
})
}

valid_boolean_trues = ["true", "yes", "y", "1"]

class Settings(object):

def __init__(self):
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()
self.load()

def _init_settings_dir(self):
self.settings_dir = _resolveSettingsDir(APPNAME)
self._init_settings_dir(basedir)
self.load(configfile)

# migration due to rename
old_settings_dir = _resolveSettingsDir(OLD_APPNAME)
if os.path.exists(old_settings_dir) and os.path.isdir(old_settings_dir) and not os.path.exists(self.settings_dir):
os.rename(old_settings_dir, self.settings_dir)
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]
Expand All @@ -105,29 +101,15 @@ def _getDefaultFolder(self, type):

#~~ load and save

def load(self):
filename = os.path.join(self.settings_dir, "config.yaml")
oldFilename = os.path.join(self.settings_dir, "config.ini")
def load(self, configfile):
if configfile is not None:
filename = configfile
else:
filename = os.path.join(self.settings_dir, "config.yaml")

if os.path.exists(filename) and os.path.isfile(filename):
with open(filename, "r") as f:
self._config = yaml.safe_load(f)
elif os.path.exists(oldFilename) and os.path.isfile(oldFilename):
config = ConfigParser.ConfigParser(allow_no_value=True)
config.read(oldFilename)
self._config = {}
for section in old_default_settings.keys():
if not config.has_section(section):
continue

self._config[section] = {}
for option in old_default_settings[section].keys():
if not config.has_option(section, option):
continue

self._config[section][option] = config.get(section, option)
self._dirty = True
self.save(force=True)
os.rename(oldFilename, oldFilename + ".bck")
else:
self._config = {}

Expand Down
Loading

0 comments on commit 363f007

Please sign in to comment.