diff --git a/docs/api/fileops.rst b/docs/api/fileops.rst index 51c75d75..e06cdec6 100644 --- a/docs/api/fileops.rst +++ b/docs/api/fileops.rst @@ -258,7 +258,7 @@ Retrieve a specific file's information .. sourcecode:: http - HTTP/1.1 200 Ok + HTTP/1.1 200 OK Content-Type: application/json { diff --git a/docs/api/general.rst b/docs/api/general.rst index 1741361a..84f2d86a 100644 --- a/docs/api/general.rst +++ b/docs/api/general.rst @@ -55,6 +55,11 @@ Please be advised that clients should use the header field variant if at all pos The API key options in the "Change password" dialog. Users can generate and revoke their custom API key here. +.. note:: + OctoPrint's web interface uses a custom API key that is freshly generated on every server start. This key is not + intended to be used by any other client and would not be very useful in any case, since it basically represents + a completely anonymous client. + .. _sec-api-general-contenttype: Content Type @@ -67,4 +72,4 @@ If not otherwise stated OctoPrint's API expects request bodies and issues respon Encoding ======== -OctoPrint uses UTF-8 as charset. \ No newline at end of file +OctoPrint uses UTF-8 as charset. diff --git a/docs/api/printer.rst b/docs/api/printer.rst index a39c5121..7e7e6c56 100644 --- a/docs/api/printer.rst +++ b/docs/api/printer.rst @@ -16,15 +16,165 @@ Printer control is mostly achieved through the use of commands, issued to resour printer. OctoPrint currently knows the following components: Print head - Print head commands allow jogging and homing the print head in all three axes. See :ref:`sec-api-printer-printheadcommand`. -Heater - Heater commands allow setting the temperature and temperature offsets for the printer's hotend and bed. Currently - OctoPrint only supports one hotend heater (this will change in the future). See :ref:`sec-api-printer-heatercommand`. -Feeder - Feeder commands allow extrusion/extraction of filament. Currently OctoPrint only supports one feeder (this will - change in a future version). See :ref:`sec-api-printer-feedercommand`. + Print head commands allow jogging and homing the print head in all three axes. Querying the resource is currently + not supported. + See :ref:`sec-api-printer-printheadcommand`. +Tool + Tool commands allow setting the temperature and temperature offsets for the printer's hotends/tools. Querying the + corresponding resource returns temperature information including an optional history. + See :ref:`sec-api-printer-toolcommand`. +Bed + Bed commands allow setting the temperature and temperature offset for the printer's heated bed. Querying the + corresponding resource returns temperature information including an optional history. + See :ref:`sec-api-printer-bedcommand`. SD card - SD commands allow initialization, refresh and release of the printer's SD card (if available). See :ref:`sec-api-printer-sdcommand`. + SD commands allow initialization, refresh and release of the printer's SD card (if available). Querying the + corresponding resource returns the current SD card state. + See :ref:`sec-api-printer-sdcommand`. + +Besides that, OctoPrint also provides a :ref:`full state report of the printer `. + +.. _sec-api-printer-state: + +Retrieve the current printer state +================================== + +.. http:get:: /api/printer + + Retrieves the current state of the printer. Returned information includes: + + * temperature information (see also :ref:`Retrieve the current tool state ` and + :ref:`Retrieve the current bed state `) + * sd state (if available, see also :ref:`Retrieve the current SD state `) + * general printer state + + Temperature information can also be made to include the printer's temperature history by supplying the ``history`` + query parameter. The amount of data points to return here can be limited using the ``limit`` query parameter. + + Clients can specific a list of attributes to not return in the response (e.g. if they don't need it) via the + ``exclude`` query parameter. + + Returns a :http:statuscode:`200` with a :ref:`Full State Response ` in the + body upon success. + + **Example 1** + + Include temperature history data, but limit it to two entries. + + .. sourcecode:: http + + GET /api/printer?history=true&limit=2 HTTP/1.1 + Host: example.com + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "temperature": { + "tool0": { + "actual": 214.8821, + "target": 220.0, + "offset": 0 + }, + "tool1": { + "actual": 25.3, + "target": null, + "offset": 0 + }, + "bed": { + "actual": 50.221, + "target": 70.0, + "offset": 5 + }, + "history": [ + { + "time": 1395651928, + "tool0": { + "actual": 214.8821, + "target": 220.0 + }, + "tool1": { + "actual": 25.3, + "target": null + }, + "bed": { + "actual": 50.221, + "target": 70.0 + } + }, + { + "time": 1395651926, + "tool0": { + "actual": 212.32, + "target": 220.0 + }, + "tool1": { + "actual": 25.1, + "target": null + }, + "bed": { + "actual": 49.1123, + "target": 70.0 + } + } + ] + }, + "sd": { + "ready": true + }, + "state": { + "text": "Operational", + "flags": { + "operational": true, + "paused": false, + "printing": false, + "sdReady": true, + "error": false, + "ready": true, + "closedOrError": false + } + } + } + + **Example 2** + + Exclude temperature and sd data. + + .. sourcecode:: http + + GET /api/printer?exclude=temperature,sd HTTP/1.1 + Host: example.com + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "state": { + "text": "Operational", + "flags": { + "operational": true, + "paused": false, + "printing": false, + "sdReady": true, + "error": false, + "ready": true, + "closedOrError": false + } + } + } + + :query exclude: An optional comma-separated list of fields to exclude from the response (e.g. if not needed by + the client). Valid values to supply here are ``temperature``, ``sd`` and ``state``. + :query history: If set to ``true`` (or: ``yes``, ``y``, ``1``), history information will be included in the response + too. If no ``limit`` parameter is given, all available temperature history data will be returned. + :query limit: If set to an integer (``n``), only the last ``n`` data points from the printer's temperature history + will be returned. Will be ignored if ``history`` is not enabled. + :statuscode 200: No error + :statuscode 409: If the printer is not operational. .. _sec-api-printer-printheadcommand: @@ -70,6 +220,10 @@ Issue a print head command "z": 0.02 } + .. sourcecode:: http + + HTTP/1.1 204 No Content + **Example Home Request** Home the X and Y axes. @@ -86,6 +240,10 @@ Issue a print head command "axes": ["x", "y"] } + .. sourcecode:: http + + HTTP/1.1 204 No Content + :json string command: The command to issue, either ``jog`` or ``home``. :json number x: ``jog`` command: The amount to travel on the X axis in mm. :json number y: ``jog`` command: The amount to travel on the Y axis in mm. @@ -96,67 +254,74 @@ Issue a print head command request. :statuscode 409: If the printer is not operational or currently printing. -.. _sec-api-printer-heatercommand: +.. _sec-api-printer-toolcommand: -Issue a heater command -====================== +Issue a tool command +==================== -.. todo:: +.. http:post:: /api/printer/tool - Update to current implementation! + Tool commands allow setting the temperature and temperature offsets for the printer's tools (hotends), selecting + the current tool and extruding/retracting from the currently selected tool. Available commands are: -.. http:post:: /api/printer/heater + target + Sets the given target temperature on the printer's tools. Additional parameters: - Heater commands allow setting the temperature and temperature offsets for the printer's hotend and bed. Available - commands are: - - temp - Sets the given target temperature on the printer's hotend and/or bed. Additional parameters: - - * ``targets``: Target temperature(s) to set, allowed properties are: - - * ``hotend``: New target temperature of the printer's hotend in centigrade. - * ``bed``: New target temperature of the printer's bed in centigrade. + * ``targets``: Target temperature(s) to set, properties must match the format ``tool{n}`` with ``n`` being the + tool's index starting with 0. offset - Sets the given temperature offset on the printer's hotend and/or bed. Additional parameters: + Sets the given temperature offset on the printer's tools. Additional parameters: - * ``offsets``: Offset(s) to set, allowed properties are: + * ``offsets``: Offset(s) to set, properties must match the format ``tool{n}`` with ``n`` being the tool's index + starting with 0. - * ``hotend``: New offset of the printer's hotend temperature in centigrade, max/min of +/-50°C. - * ``bed``: New offset of the printer's bed temperature in centigrade, max/min of +/-50°C. + select + Selects the printer's current tool. Additional parameters: - All of these commands may only be sent if the printer is currently operational and not printing. Otherwise a - :http:statuscode:`409` is returned. + * ``tool``: Tool to select, format ``tool{n}`` with ``n`` being the tool's index starting with 0. + + extrude + Extrudes the given amount of filament from the currently selected tool. Additional parameters: + + * ``amount``: The amount of filament to extrude in mm. May be negative to retract. + + + All of these commands may only be sent if the printer is currently operational and -- in case of ``select`` and + ``extrude`` -- not printing. Otherwise a :http:statuscode:`409` is returned. Upon success, a status code of :http:statuscode:`204` and an empty body is returned. **Example Target Temperature Request** - Set the printer's hotend target temperature to 220°C and the bed target temperature to 75°C. + Set the target temperature for the printer's first hotend to 220°C and the printer's second extruder to 205°C. .. sourcecode:: http - POST /api/printer/heater HTTP/1.1 + POST /api/printer/tool HTTP/1.1 Host: example.com Content-Type: application/json X-Api-Key: abcdef... { - "command": "temp", - "temps": { - "hotend": 220, - "bed": 75 + "command": "target", + "targets": { + "tool0": 220, + "tool1": 205 } } + .. sourcecode:: http + + HTTP/1.1 204 No Content + **Example Offset Temperature Request** - Set the offset for hotend temperatures to +10°C and for bed temperatures to -5°C. + Set the offset for temperatures on ``tool0`` to +10°C and on ``tool1`` to -5°C. .. sourcecode:: http - POST /api/printer/heater HTTP/1.1 + POST /api/printer/tool HTTP/1.1 Host: example.com Content-Type: application/json X-Api-Key: abcdef... @@ -164,62 +329,62 @@ Issue a heater command { "command": "offset", "offsets": { - "hotend": 10, - "bed": -5 + "tool0": 10, + "tool1": -5 } } - :json string command: The command to issue, either ``temp`` or ``offset`` - :json object temps: ``temp`` command: The target temperatures to set. Valid properties are ``hotend`` and ``bed`` - :json object offsets: ``offset`` command: The offset temperature to set. Valid properties are ``hotend`` and ``bed`` - :statuscode 204: No error - :statuscode 400: If ``temps`` or ``offsets`` contains a property other than ``hotend`` or ``bed``, the - target or offset temperature is not a valid number or outside of the supported range, or if the - request is otherwise invalid. - :statuscode 409: If the printer is not operational. + .. sourcecode:: http -.. _sec-api-printer-feedercommand: + HTTP/1.1 204 No Content -Issue a feeder command -====================== + **Example Tool Select Request** -.. http:post:: /api/printer/feeder - - Feeder commands allow extrusion/extraction of filament. Available commands are: - - extrude - Extrudes the given amount of filament. Additional parameters: - - * ``amount``: The amount of filament to extrude in mm. May be negative to retract. - - All of these commands may only be sent if the printer is currently operational and not printing. Otherwise a - :http:statuscode:`409` is returned. - - Upon success, a status code of :http:statuscode:`204` and an empty body is returned. - - **Example Extrude Request** - - Extrudes 1mm of filament + Select the second hotend of the printer for any following ``extrude`` commands. .. sourcecode:: http - POST /api/printer/feeder HTTP/1.1 + POST /api/printer/tool HTTP/1.1 + Host: example.com + Content-Type: application/json + X-Api-Key: abcdef... + + { + "command": "select", + "tool": "tool1" + } + + .. sourcecode:: http + + HTTP/1.1 204 No Content + + **Example Extrude Request** + + Extrude 5mm on the currently selected tool. + + .. sourcecode:: http + + POST /api/printer/tool HTTP/1.1 Host: example.com Content-Type: application/json X-Api-Key: abcdef... { "command": "extrude", - "amount": 1 + "amount": 5 } - **Example Retract Request** - - Retracts 3mm of filament - .. sourcecode:: http - POST /api/printer/feeder HTTP/1.1 + HTTP/1.1 204 No Content + + **Example Retract Request** + + Retract 3mm of filament on the currently selected tool. + + .. sourcecode:: http + + POST /api/printer/tool HTTP/1.1 Host: example.com Content-Type: application/json X-Api-Key: abcdef... @@ -229,16 +394,238 @@ Issue a feeder command "amount": -3 } - :json string command: The command to issue, only ``extrude`` is supported right now. - :json number amount: ``extrude`` command: The amount of filament to extrude/retract in mm. + .. sourcecode:: http + + HTTP/1.1 204 No Content + + :json string command: The command to issue, either ``target``, ``offset``, ``select`` or ``extrude``. + :json object targets: ``target`` command: The target temperatures to set. Valid properties have to match the format ``tool{n}``. + :json object offsets: ``offset`` command: The offset temperature to set. Valid properties have to match the format ``tool{n}``. + :json object tool: ``select`` command: The tool to select, value has to match the format ``tool{n}``. + :json object amount: ``extrude`` command: The amount of filament to extrude from the currently selected tool. :statuscode 204: No error - :statuscode 400: If the value given for `amount` is not a valid number or the request is otherwise invalid. - :statuscode 409: If the printer is not operational or currently printing. + :statuscode 400: If ``targets`` or ``offsets`` contains a property or ``tool`` contains a value not matching the format + ``tool{n}``, the target/offset temperature or extrusion amount is not a valid number or outside of + the supported range, or if the request is otherwise invalid. + :statuscode 409: If the printer is not operational or -- in case of ``select`` or ``extrude`` -- currently printing. + +.. _sec-api-printer-toolstate: + +Retrieve the current tool state +=============================== + +.. http:get:: /api/printer/tool + + Retrieves the current temperature data (actual, target and offset) plus optionally a (limited) history (actual, target, + timestamp) for all of the printer's available tools. + + It's also possible to retrieve the temperature history by supplying the ``history`` query parameter set to ``true``. The + amount of returned history data points can be limited using the ``limit`` query parameter. + + Returns a :http:statuscode:`200` with a Temperature Response in the body upon success. + + .. note:: + If you want both tool and bed temperature information at the same time, take a look at + :ref:`Retrieve the current printer state `. + + **Example** + + Query the tool temperature data and also include the temperature history but limit it to two entries. + + .. sourcecode:: http + + GET /api/printer/tool?history=true&limit=2 HTTP/1.1 + Host: example.com + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "tool0": { + "actual": 214.8821, + "target": 220.0, + "offset": 0 + }, + "tool1": { + "actual": 25.3, + "target": null, + "offset": 0 + }, + "history": [ + { + "time": 1395651928, + "tool0": { + "actual": 214.8821, + "target": 220.0 + }, + "tool1": { + "actual": 25.3, + "target": null + } + }, + { + "time": 1395651926, + "tool0": { + "actual": 212.32, + "target": 220.0 + }, + "tool1": { + "actual": 25.1 + } + } + ] + } + + :query history: If set to ``true`` (or: ``yes``, ``y``, ``1``), history information will be included in the response + too. If no ``limit`` parameter is given, all available temperature history data will be returned. + :query limit: If set to an integer (``n``), only the last ``n`` data points from the printer's temperature history + will be returned. Will be ignored if ``history`` is not enabled. + :statuscode 200: No error + :statuscode 409: If the printer is not operational. + +.. _sec-api-printer-bedcommand: + +Issue a bed command +=================== + +.. http:post:: /api/printer/bed + + Bed commands allow setting the temperature and temperature offsets for the printer's heated bed. Available commands + are: + + target + Sets the given target temperature on the printer's tools. Additional parameters: + + * ``target``: Target temperature to set. + + offset + Sets the given temperature offset on the printer's tools. Additional parameters: + + * ``offsets``: Offset to set. + + All of these commands may only be sent if the printer is currently operational. Otherwise a :http:statuscode:`409` + is returned. + + Upon success, a status code of :http:statuscode:`204` and an empty body is returned. + + **Example Target Temperature Request** + + Set the target temperature for the printer's heated bed to 75°C. + + .. sourcecode:: http + + POST /api/printer/bed HTTP/1.1 + Host: example.com + Content-Type: application/json + X-Api-Key: abcdef... + + { + "command": "target", + "target": 75 + } + + .. sourcecode:: http + + HTTP/1.1 204 No Content + + **Example Offset Temperature Request** + + Set the temperature offset for the heated bed to -5°C. + + .. sourcecode:: http + + POST /api/printer/bed HTTP/1.1 + Host: example.com + Content-Type: application/json + X-Api-Key: abcdef... + + { + "command": "offset", + "offset": -5 + } + + .. sourcecode:: http + + HTTP/1.1 204 No Content + + :json string command: The command to issue, either ``target`` or ``offset``. + :json object target: ``target`` command: The target temperature to set. + :json object offset: ``offset`` command: The offset temperature to set. + :statuscode 204: No error + :statuscode 400: If ``target`` or ``offset`` is not a valid number or outside of the supported range, or if the + request is otherwise invalid. + :statuscode 409: If the printer is not operational. + +.. _sec-api-printer-bedstate: + +Retrieve the current bed state +============================== + +.. http:get:: /api/printer/bed + + Retrieves the current temperature data (actual, target and offset) plus optionally a (limited) history (actual, target, + timestamp) for the printer's heated bed. + + It's also possible to retrieve the temperature history by supplying the ``history`` query parameter set to ``true``. The + amount of returned history data points can be limited using the ``limit`` query parameter. + + Returns a :http:statuscode:`200` with a Temperature Response in the body upon success. + + .. note:: + If you want both tool and bed temperature information at the same time, take a look at + :ref:`Retrieve the current printer state `. + + **Example** + + Query the bed temperature data and also include the temperature history but limit it to two entries. + + .. sourcecode:: http + + GET /api/printer/bed?history=true&limit=2 HTTP/1.1 + Host: example.com + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "bed": { + "actual": 50.221, + "target": 70.0, + "offset": 5 + }, + "history": [ + { + "time": 1395651928, + "bed": { + "actual": 50.221, + "target": 70.0 + } + }, + { + "time": 1395651926, + "bed": { + "actual": 49.1123, + "target": 70.0 + } + } + ] + } + + :query history: If set to ``true`` (or: ``yes``, ``y``, ``1``), history information will be included in the response + too. If no ``limit`` parameter is given, all available temperature history data will be returned. + :query limit: If set to an integer (``n``), only the last ``n`` data points from the printer's temperature history + will be returned. Will be ignored if ``history`` is not enabled. + :statuscode 200: No error + :statuscode 409: If the printer is not operational. .. _sec-api-printer-sdcommand: -Issue a SD command -================== +Issue an SD command +=================== .. http:post:: /api/printer/sd @@ -268,6 +655,8 @@ Issue a SD command **Example Init Request** + Initialize the SD card. + .. sourcecode:: http POST /api/printer/sd HTTP/1.1 @@ -279,8 +668,14 @@ Issue a SD command "command": "init" } + .. sourcecode:: http + + HTTP/1.1 204 No Content + **Example Refresh Request** + Refresh the file list of the SD card + .. sourcecode:: http POST /api/printer/sd HTTP/1.1 @@ -292,8 +687,14 @@ Issue a SD command "command": "refresh" } + .. sourcecode:: http + + HTTP/1.1 204 No Content + **Example Release Request** + Release the SD card + .. sourcecode:: http POST /api/printer/sd HTTP/1.1 @@ -305,6 +706,10 @@ Issue a SD command "command": "release" } + .. sourcecode:: http + + HTTP/1.1 204 No Content + :json string command: The command to issue, either ``init``, ``refresh`` or ``release``. :statuscode 204: No error :statuscode 409: If a ``refresh`` or ``release`` command is issued but the SD card has not been initialized (e.g. @@ -324,15 +729,15 @@ Retrieve the current SD state Returns a :http:statuscode:`200` with an :ref:`SD State Response ` in the body upon success. - **Example Request** + **Example** + + Read the current state of the SD card. .. sourcecode:: http GET /api/printer/sd HTTP/1.1 Host: example.com - **Example Response** - .. sourcecode:: http HTTP/1.1 200 OK @@ -350,10 +755,116 @@ Retrieve the current SD state Datamodel ========= +.. _sec-api-printer-datamodel-fullstate: + +Full State Response +------------------- + +.. list-table:: + :widths: 15 5 10 30 + :header-rows: 1 + + * - Name + - Multiplicity + - Type + - Description + * - ``temperature`` + - 0..1 + - :ref:`Temperature State ` + - The printer's temperature state data + * - ``sd`` + - 0..1 + - :ref:`SD State ` + - The printer's sd state data + * - ``state`` + - 0..1 + - :ref:`Printer State ` + - The printer's general state + +.. _sec-api-printer-datamodel-temps: + +Temperature State +----------------- + +.. list-table:: + :widths: 15 5 10 30 + :header-rows: 1 + + * - Name + - Multiplicity + - Type + - Description + * - ``tool{n}`` + - 0..* + - :ref:`Temperature Data ` + - Current temperature stats for tool *n*. Enumeration starts at 0 for the first tool. Not included if querying + only bed state. + * - ``bed`` + - 0..1 + - :ref:`Temperature Data ` + - Current temperature stats for the printer's heated bed. Not included if querying only tool state. + * - ``history`` + - 0..1 + - List of :ref:`Historic Temperature Datapoint ` + - Temperature history + +.. _sec-api-printer-datamodel-temphistory: + +Historic Temperature Data Point +------------------------------- + +.. list-table:: + :widths: 15 5 10 30 + :header-rows: 1 + + * - Name + - Multiplicity + - Type + - Description + * - ``time`` + - 1 + - Unix Timestamp + - Timestamp of this data point + * - ``tool{n}`` + - 0..* + - :ref:`Temperature Data ` + - Temperature stats for tool *n*. Enumeration starts at 0 for the first tool. Not included if querying only + bed state. + * - ``bed`` + - 0..* + - :ref:`Temperature Data ` + - Temperature stats for the printer's heated bed. Not included if querying only tool state. + +.. _sec-api-printer-datamodel-tempdata: + +Temperature Data +---------------- + +.. list-table:: + :widths: 15 5 10 30 + :header-rows: 1 + + * - Name + - Multiplicity + - Type + - Description + * - ``actual`` + - 1 + - Number + - Current temperature + * - ``target`` + - 1 + - Number + - Target temperature, may be ``null`` if no target temperature is set. + * - ``offset`` + - 0..1 + - Number + - Currently configured temperature offset to apply, will be left out for historic temperature information. + .. _sec-api-printer-datamodel-sdstate: -SD State Response ------------------ +SD State +-------- .. list-table:: :widths: 15 5 10 30 @@ -366,4 +877,55 @@ SD State Response * - ``ready`` - 1 - Boolean - - Whether the SD card has been initialized (``true``) or not (``false``). \ No newline at end of file + - Whether the SD card has been initialized (``true``) or not (``false``). + +.. _sec-api-printer-datamodel-printerstate: + +Printer State +------------- + +.. list-table:: + :widths: 15 5 10 30 + :header-rows: 1 + + * - Name + - Multiplicity + - Type + - Description + * - ``text`` + - 1 + - String + - A textual representation of the current state of the printer, e.g. "Operational" or "Printing" + * - ``flags`` + - 1 + - Printer state flags + - A couple of boolean printer state flags + * - ``flags.operational`` + - 1 + - Boolean + - ``true`` if the printer is operational, ``false`` otherwise + * - ``flags.paused`` + - 1 + - Boolean + - ``true`` if the printer is currently paused, ``false`` otherwise + * - ``flags.printing`` + - 1 + - Boolean + - ``true`` if the printer is currently printing, ``false`` otherwise + * - ``flags.sdReady`` + - 1 + - Boolean + - ``true`` if the printer's SD card is available and initialized, ``false`` otherwise. This is redundant information + to :ref:`the SD State `. + * - ``flags.error`` + - 1 + - Boolean + - ``true`` if an unrecoverable error occurred, ``false`` otherwise + * - ``flags.ready`` + - 1 + - Boolean + - ``true`` if the printer is operational and no data is currently being streamed to SD, so ready to receive instructions + * - ``flags.closedOrError`` + - 1 + - Boolean + - ``true`` if the printer is disconnected (possibly due to an error), ``false`` otherwise diff --git a/src/octoprint/server/__init__.py b/src/octoprint/server/__init__.py index 51bf09f9..cc1eb51f 100644 --- a/src/octoprint/server/__init__.py +++ b/src/octoprint/server/__init__.py @@ -1,4 +1,6 @@ # coding=utf-8 +import uuid + __author__ = "Gina Häußge " __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' @@ -40,6 +42,9 @@ import octoprint.events as events import octoprint.timelapse +UI_API_KEY = ''.join('%02X' % ord(z) for z in uuid.uuid4().bytes) + + @app.route("/") def index(): branch = None @@ -64,7 +69,8 @@ def index(): gitCommit=commit, stylesheet=settings().get(["devel", "stylesheet"]), gcodeMobileThreshold=settings().get(["gcodeViewer", "mobileSizeThreshold"]), - gcodeThreshold=settings().get(["gcodeViewer", "sizeThreshold"]) + gcodeThreshold=settings().get(["gcodeViewer", "sizeThreshold"]), + uiApiKey=UI_API_KEY ) diff --git a/src/octoprint/server/api/__init__.py b/src/octoprint/server/api/__init__.py index 9ff5ae54..5d160725 100644 --- a/src/octoprint/server/api/__init__.py +++ b/src/octoprint/server/api/__init__.py @@ -1,4 +1,6 @@ # coding=utf-8 +from octoprint.server.util import getApiKey, getUserForApiKey + __author__ = "Gina Häußge " __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' @@ -13,7 +15,7 @@ from flask.ext.principal import Identity, identity_changed, AnonymousIdentity import octoprint.util as util import octoprint.users import octoprint.server -from octoprint.server import restricted_access, admin_permission, NO_CONTENT +from octoprint.server import restricted_access, admin_permission, NO_CONTENT, UI_API_KEY from octoprint.settings import settings as s, valid_boolean_trues #~~ init api blueprint, including sub modules @@ -30,6 +32,41 @@ from . import users as api_users from . import log as api_logs +@api.before_request +def beforeApiRequests(): + """ + All requests in this blueprint need to be made supplying an API key. This may be the UI_API_KEY, in which case + the underlying request processing will directly take place, or it may be the global or a user specific case. In any + case it has to be present and must be valid, so anything other than the above three types will result in denying + the request. + """ + + apikey = getApiKey(request) + if apikey is None: + # no api key => 401 + return make_response("No API key provided", 401) + + if apikey == UI_API_KEY: + # ui api key => continue regular request processing + return + + if not s().get(["api", "enabled"]): + # api disabled => 401 + return make_response("API disabled", 401) + + if apikey == s().get(["api", "key"]): + # global api key => continue regular request processing + return + + user = getUserForApiKey(apikey) + if user is not None: + # user specific api key => continue regular request processing + return + + # invalid api key => 401 + return make_response("Invalid API key", 401) + + #~~ first run setup diff --git a/src/octoprint/server/api/printer.py b/src/octoprint/server/api/printer.py index 547f0422..d7f4a3dd 100644 --- a/src/octoprint/server/api/printer.py +++ b/src/octoprint/server/api/printer.py @@ -12,13 +12,36 @@ import octoprint.util as util #~~ Printer + @api.route("/printer", methods=["GET"]) def printerState(): if not printer.isOperational(): return make_response("Printer is not operational", 409) + # process excludes + excludes = [] + if "exclude" in request.values: + excludeStr = request.values["exclude"] + if len(excludeStr.strip()) > 0: + excludes = filter(lambda x: x in ["temperature", "sd", "state"], map(lambda x: x.strip(), excludeStr.split(","))) + result = {} - result.update(_getTemperatureData(lambda x: x)) + + # add temperature information + if not "temperature" in excludes: + result.update({"temperature": _getTemperatureData(lambda x: x)}) + + # add sd information + if not "sd" in excludes and settings().getBoolean(["feature", "sdSupport"]): + result.update({"sd": {"ready": printer.isSdReady()}}) + + # add state information + if not "state" in excludes: + state = printer.getCurrentData()["state"] + result.update({"state": { + "text": state["stateString"], + "flags": state["flags"] + }}) return jsonify(result) @@ -276,11 +299,14 @@ def printerCommand(): data = request.json parameters = {} - if "parameters" in data.keys(): parameters = data["parameters"] + if "parameters" in data.keys(): + parameters = data["parameters"] commands = [] - if "command" in data.keys(): commands = [data["command"]] - elif "commands" in data.keys(): commands = data["commands"] + if "command" in data.keys(): + commands = [data["command"]] + elif "commands" in data.keys(): + commands = data["commands"] commandsToSend = [] for command in commands: @@ -306,9 +332,6 @@ def _getTemperatureData(filter): return make_response("Printer is not operational", 409) tempData = printer.getCurrentTemperatures() - result = { - "temps": filter(tempData) - } if "history" in request.values.keys() and request.values["history"] in valid_boolean_trues: tempHistory = printer.getTemperatureHistory() @@ -320,9 +343,9 @@ def _getTemperatureData(filter): history = list(tempHistory) limit = min(limit, len(history)) - result.update({ + tempData.update({ "history": map(lambda x: filter(x), history[-limit:]) }) - return result + return filter(tempData) diff --git a/src/octoprint/server/util.py b/src/octoprint/server/util.py index 71149686..80320e86 100644 --- a/src/octoprint/server/util.py +++ b/src/octoprint/server/util.py @@ -50,9 +50,9 @@ def restricted_access(func, apiEnabled=True): if settings().getBoolean(["server", "firstRun"]) and (octoprint.server.userManager is None or not octoprint.server.userManager.hasBeenCustomized()): return make_response("OctoPrint isn't setup yet", 403) - # if API is globally enabled, enabled for this request and an api key is provided, try to use that - apikey = _getApiKey(request) - if settings().get(["api", "enabled"]) and apiEnabled and apikey is not None: + # if API is globally enabled, enabled for this request and an api key is provided that is not the current UI API key, try to use that + apikey = getApiKey(request) + if settings().get(["api", "enabled"]) and apiEnabled and apikey is not None and apikey != octoprint.server.UI_API_KEY: if apikey == settings().get(["api", "key"]): # master key was used user = ApiUser() @@ -61,7 +61,7 @@ def restricted_access(func, apiEnabled=True): user = octoprint.server.userManager.findUser(apikey=apikey) if user is None: - make_response("Invalid API key", 401) + return make_response("Invalid API key", 401) if login_user(user, remember=False): identity_changed.send(current_app._get_current_object(), identity=Identity(user.get_id())) return func(*args, **kwargs) @@ -76,7 +76,7 @@ def api_access(func): def decorated_view(*args, **kwargs): if not settings().get(["api", "enabled"]): make_response("API disabled", 401) - apikey = _getApiKey(request) + apikey = getApiKey(request) if apikey is None: make_response("No API key provided", 401) if apikey != settings().get(["api", "key"]): @@ -85,7 +85,7 @@ def api_access(func): return decorated_view -def _getUserForApiKey(apikey): +def getUserForApiKey(apikey): if settings().get(["api", "enabled"]) and apikey is not None: if apikey == settings().get(["api", "key"]): # master key was used @@ -97,7 +97,7 @@ def _getUserForApiKey(apikey): return None -def _getApiKey(request): +def getApiKey(request): # Check Flask GET/POST arguments if hasattr(request, "values") and "apikey" in request.values: return request.values["apikey"] @@ -148,6 +148,10 @@ class PrinterStateConnection(SockJSConnection): def on_open(self, info): remoteAddress = self._getRemoteAddress(info) self._logger.info("New connection from client: %s" % remoteAddress) + + # connected => update the API key, might be necessary if the client was left open while the server restarted + self._emit("connected", {"apikey": octoprint.server.UI_API_KEY}) + self._printer.registerCallback(self) self._gcodeManager.registerCallback(self) octoprint.timelapse.registerCallback(self) @@ -316,9 +320,9 @@ def admin_validator(request): :param request: The Flask request object """ - apikey = _getApiKey(request) + apikey = getApiKey(request) if settings().get(["api", "enabled"]) and apikey is not None: - user = _getUserForApiKey(apikey) + user = getUserForApiKey(apikey) else: user = current_user diff --git a/src/octoprint/static/js/app/dataupdater.js b/src/octoprint/static/js/app/dataupdater.js index 978613d2..91a99541 100644 --- a/src/octoprint/static/js/app/dataupdater.js +++ b/src/octoprint/static/js/app/dataupdater.js @@ -27,31 +27,17 @@ function DataUpdater(loginStateViewModel, connectionViewModel, printerStateViewM self._socket.onopen = self._onconnect; self._socket.onclose = self._onclose; self._socket.onmessage = self._onmessage; - } + }; self.reconnect = function() { delete self._socket; self.connect(); - } + }; self._onconnect = function() { self._autoReconnecting = false; self._autoReconnectTrial = 0; - - if ($("#offline_overlay").is(":visible")) { - $("#offline_overlay").hide(); - self.logViewModel.requestData(); - self.timelapseViewModel.requestData(); - $("#webcam_image").attr("src", CONFIG_WEBCAM_STREAM + "?" + new Date().getTime()); - self.loginStateViewModel.requestData(); - self.gcodeFilesViewModel.requestData(); - self.gcodeViewModel.reset(); - - if ($('#tabs li[class="active"] a').attr("href") == "#control") { - $("#webcam_image").attr("src", CONFIG_WEBCAM_STREAM + "?" + new Date().getTime()); - } - } - } + }; self._onclose = function() { $("#offline_overlay_message").html( @@ -70,20 +56,43 @@ function DataUpdater(loginStateViewModel, connectionViewModel, printerStateViewM } else { self._onreconnectfailed(); } - } + }; self._onreconnectfailed = function() { $("#offline_overlay_message").html( "The server appears to be offline, at least I'm not getting any response from it. I could not reconnect automatically, " + "but you may try a manual reconnect using the button below." ); - } + }; self._onmessage = function(e) { for (var prop in e.data) { var data = e.data[prop]; switch (prop) { + case "connected": { + // update the current UI API key and send it with any request + UI_API_KEY = data["apikey"]; + $.ajaxSetup({ + headers: {"X-Api-Key": UI_API_KEY} + }); + + if ($("#offline_overlay").is(":visible")) { + $("#offline_overlay").hide(); + self.logViewModel.requestData(); + self.timelapseViewModel.requestData(); + $("#webcam_image").attr("src", CONFIG_WEBCAM_STREAM + "?" + new Date().getTime()); + self.loginStateViewModel.requestData(); + self.gcodeFilesViewModel.requestData(); + self.gcodeViewModel.reset(); + + if ($('#tabs li[class="active"] a').attr("href") == "#control") { + $("#webcam_image").attr("src", CONFIG_WEBCAM_STREAM + "?" + new Date().getTime()); + } + } + + break; + } case "history": { self.connectionViewModel.fromHistoryData(data); self.printerStateViewModel.fromHistoryData(data); @@ -160,7 +169,7 @@ function DataUpdater(loginStateViewModel, connectionViewModel, printerStateViewM } } } - } + }; self.connect(); } diff --git a/src/octoprint/static/js/app/main.js b/src/octoprint/static/js/app/main.js index c649279d..2ba5de67 100644 --- a/src/octoprint/static/js/app/main.js +++ b/src/octoprint/static/js/app/main.js @@ -25,7 +25,7 @@ $(function() { gcodeFilesViewModel, timelapseViewModel, gcodeViewModel, - logViewModel + logViewModel ); // work around a stupid iOS6 bug where ajax requests get cached and only work once, as described at @@ -35,6 +35,11 @@ $(function() { headers: { "cache-control": "no-cache" } }); + // send the current UI API key with any request + $.ajaxSetup({ + headers: {"X-Api-Key": UI_API_KEY} + }); + //~~ Show settings - to ensure centered $('#navbar_show_settings').click(function() { $('#settings_dialog').modal() diff --git a/src/octoprint/templates/index.jinja2 b/src/octoprint/templates/index.jinja2 index cf323a0b..4327c9b4 100644 --- a/src/octoprint/templates/index.jinja2 +++ b/src/octoprint/templates/index.jinja2 @@ -39,6 +39,8 @@ var SOCKJS_URI = window.location.protocol.slice(0, -1) + "://" + (window.document ? window.document.domain : window.location.hostname) + ":" + window.location.port + "/sockjs"; var SOCKJS_DEBUG = {% if debug -%} true; {% else %} false; {%- endif %} + + var UI_API_KEY = "{{ uiApiKey }}";